/*
* Copyright (C) 2011, Jamie Thompson
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this program; If not, see
* .
*/
#include "EventLogBackupManager.h"
#include "EventPreventer.h"
#include
#include
#include
#include
#include
#include
#include
EventLogBackupManager::EventLogBackupManager(const Settings & currentSettings) :
m_kCurrentSettings(currentSettings)
{
setBackupDirectoryPath("/home/user/MyDocs/backups/");
setDataDirectoryPath("/home/user/.rtcom-eventlogger/");
setCurrentBackupName(QString::number(QDateTime::currentDateTimeUtc().toTime_t()) + ".qsbackup/");
setMaxNumberOfBackups(3);
setLockFilename(".inuse");
}
EventLogBackupManager::~EventLogBackupManager()
{
}
void copyFileInfoListRecusively(const QFileInfoList &sourceItems, const QString &sourcePath, const QString &destinationPath)
{
foreach(QFileInfo entry, sourceItems)
{
QString entryStubFilePath(entry.absoluteFilePath().replace(QRegExp("^" + sourcePath), ""));
if(entry.isDir())
{
if(!QDir().mkpath(destinationPath + entryStubFilePath))
throw std::runtime_error(QString("Unable to make the directory: %1%2").arg(destinationPath).arg(entryStubFilePath).toLocal8Bit().constData());
copyFileInfoListRecusively(
QDir(entry.absoluteFilePath()).entryInfoList(
QDir::AllEntries | QDir::NoDotAndDotDot,
QDir::DirsFirst),
sourcePath,
destinationPath);
}
else
if(!QFile(entry.absoluteFilePath()).copy(destinationPath + entryStubFilePath))
throw std::runtime_error(QString("Unable to copy the file '%1'' to '%2%3'").arg(entry.absoluteFilePath()).arg(destinationPath).arg(entryStubFilePath).toLocal8Bit().constData());
}
}
void EventLogBackupManager::CreateBackup()
{
PurgeOldBackups();
// Make the new directory
if(QDir().mkpath(CurrentBackupPath()))
{
try
{
// Copy the data to it
copyFileInfoListRecusively(
QDir(DataDirectoryPath()).entryInfoList(
QStringList() << "*.db*" << "attachments" << "plugins",
QDir::AllEntries | QDir::NoDotAndDotDot,
QDir::DirsFirst),
DataDirectoryPath(),
CurrentBackupPath()
);
LockCurrentBackup();
}
catch(const std::runtime_error &exception)
{
RemoveDirRecusively(CurrentBackupPath());
}
}
else
throw std::runtime_error(QString("Unable to create backup directory '%1'").arg(CurrentBackupPath()).toLocal8Bit().constData());
}
void EventLogBackupManager::RestoreBackup(const QString &backupPath)
{
qDebug() << "Restoring backup: " << backupPath;
// Check backup is valid
EnsureBackupValid(backupPath);
// Remove old working-copy backups
{
RemoveDirRecusively(QFileInfo(DataDirectoryPath() + "/attachments.qsrestore"));
RemoveDirRecusively(QFileInfo(DataDirectoryPath() + "/plugins.qsrestore"));
foreach(QFileInfo entry, QDir(DataDirectoryPath()).entryInfoList(QStringList("*.db*.qsrestore")))
QFile(entry.absoluteFilePath()).remove();
}
// Disable new events and try restoring the content
EventPreventer noEventsPlease(CurrentSettings());
noEventsPlease.DisableAccounts();
try
{
// Move the attachments out of the way and copy in from the backup
if(!QDir().rename(DataDirectoryPath() + "/attachments", DataDirectoryPath() + "/attachments.qsrestore"))
throw std::runtime_error("");
copyFileInfoListRecusively(QDir(backupPath).entryInfoList(QStringList("attachments")), backupPath, DataDirectoryPath());
// Move the plugins out of the way and copy in from the backup
if(!QDir().rename(DataDirectoryPath() + "/plugins", DataDirectoryPath() + "/plugins.qsrestore"))
throw std::runtime_error("");
copyFileInfoListRecusively(QDir(backupPath).entryInfoList(QStringList("plugins")), backupPath, DataDirectoryPath());
// Move the database files out of the way and copy in from the backup
foreach(QFileInfo entry, QDir(DataDirectoryPath()).entryInfoList(QStringList("*.db*")))
QFile(entry.absoluteFilePath()).copy(DataDirectoryPath() + entry.fileName() + ".qsrestore");
foreach(QFileInfo entry, QDir(backupPath).entryInfoList(QStringList("*.db*")))
QFile(entry.absoluteFilePath()).copy(DataDirectoryPath() + entry.fileName());
// Now all of the backup components have been restored, we can reenable the accounts safely
noEventsPlease.RestoreAccounts();
// ...and we can remove the working-copy backups
foreach(QFileInfo entry, QDir(DataDirectoryPath()).entryInfoList(QStringList("*.db*.qsrestore")))
QFile(entry.absoluteFilePath()).remove();
RemoveDirRecusively(QFileInfo(DataDirectoryPath() + "/plugins.qsrestore"));
RemoveDirRecusively(QFileInfo(DataDirectoryPath() + "/attachments.qsrestore"));
}
catch(const std::runtime_error &exception)
{
// Remove the partially-restored data
foreach(QFileInfo entry, QDir(DataDirectoryPath()).entryInfoList(QStringList("*.db*.qsrestore")))
QFile(entry.absoluteFilePath().remove(".qsrestore")).remove();
RemoveDirRecusively(QFileInfo(DataDirectoryPath() + "/plugins"));
RemoveDirRecusively(QFileInfo(DataDirectoryPath() + "/attachments"));
// Revert attachments
if(!QDir().rename(DataDirectoryPath() + "/attachments.qsrestore", DataDirectoryPath() + "/attachments"))
throw std::runtime_error("");
// Revert plugins
if(!QDir().rename(DataDirectoryPath() + "/plugins.qsrestore", DataDirectoryPath() + "/plugins"))
throw std::runtime_error("");
// Revert databases
foreach(QFileInfo entry, QDir(DataDirectoryPath()).entryInfoList(QStringList("*.db*.qsrestore")))
QFile(entry.absoluteFilePath()).copy(DataDirectoryPath() + entry.fileName().remove(".qsrestore"));
// Now all of the working-copy components have been restored, we can reenable the accounts safely
noEventsPlease.RestoreAccounts();
// ..but the restoe still failed, so tell the caller about it.
throw;
}
}
void EventLogBackupManager::LockCurrentBackup()
{
LockBackup(CurrentBackupPath());
}
void EventLogBackupManager::UnlockCurrentBackup()
{
UnlockBackup(CurrentBackupPath());
}
void EventLogBackupManager::LockBackup(const QString &backupPath)
{
qDebug() << "Locking backup: " << backupPath;
// Mark the backup as "in use" by touching a lockfile.
QFile lockfile(backupPath + LockFilename());
lockfile.open(QIODevice::WriteOnly);
}
void EventLogBackupManager::UnlockBackup(const QString &backupPath)
{
qDebug() << "Unlocking backup: " << backupPath;
QFile lockfile(QString("%1/%2").arg(backupPath).arg(LockFilename()));
lockfile.remove();
}
// Ideally would be local to PurgeOldBackups, but template arguments have to
// refer to types with external linkage. Roll on C++0x!
class OrderByTimestamp
{
public:
inline bool operator()(const QFileInfo &a, QFileInfo &b) const
{
return b.created() < a.created();
}
};
void EventLogBackupManager::PurgeOldBackups()
{
// Enumerate backups directory
QFileInfoList existingBackups(CurrentBackups(false));
// If more than maximum number of backups found, delete the oldest
if((uint)existingBackups.count() > MaxNumberOfBackups() - 1)
{
// This is important, so explicitly make sure the list is in the correct order
qSort(existingBackups.begin(), existingBackups.end(), OrderByTimestamp());
for(int i(0); i < existingBackups.count(); ++i)
{
if(i < 2)
qDebug() << existingBackups.value(i).absoluteFilePath();
else
RemoveDirRecusively(existingBackups.value(i));
}
}
}
const QFileInfoList EventLogBackupManager::CurrentBackups(bool lockedOnly)
{
QFileInfoList existingBackups;
QDir backupDirectory(BackupDirectoryPath());
foreach(QFileInfo entry, backupDirectory.entryInfoList(QStringList("*.qsbackup"), QDir::AllEntries | QDir::NoDotAndDotDot, QDir::Name | QDir::Reversed))
{
// If we only want locked backups, then skip those without the lock file present...
if(lockedOnly && QDir(entry.absoluteFilePath()).entryInfoList(QStringList(LockFilename()), QDir::Hidden).count() == 0)
{
qDebug() << "Ignoring unlocked backup: " << entry.absoluteFilePath();
continue;
}
qDebug() << "Locked backup found: " << entry.absoluteFilePath();
existingBackups.append(QFileInfo(entry));
}
return existingBackups;
}
void EventLogBackupManager::RemoveDirRecusively(const QFileInfo &dirInfo)
{
foreach(QFileInfo entry,
QDir(dirInfo.absoluteFilePath()).entryInfoList(
QDir::AllEntries | QDir::NoDotAndDotDot,
QDir::DirsFirst))
{
if(entry.isDir())
RemoveDirRecusively(entry);
else
QDir().remove(entry.absoluteFilePath());
}
// Dir will be empty as we've removed all dirs and files...
QDir().rmdir(dirInfo.absoluteFilePath());
}
void EventLogBackupManager::EnsureBackupValid(const QString &backupPath)
{
QString shortBackupPath(backupPath);
shortBackupPath.remove(BackupDirectoryPath());
bool oldDBPresent(QFile(backupPath + "/el.db").exists() && QFile(backupPath + "/el.db-journal").exists());
bool v1DBPresent(QFile(backupPath + "/el-v1.db").exists() && QFile(backupPath + "/el-v1.db-journal").exists());
if( !(oldDBPresent || v1DBPresent ) )
throw std::runtime_error(QString("The backup '%1' is missing the main event logger database.").arg(shortBackupPath).toLocal8Bit().constData());
if(!QDir(backupPath + "/attachments").exists())
throw std::runtime_error(QString("The backup '%1' is missing the attachments directory.").arg(shortBackupPath).toLocal8Bit().constData());
if(!QDir(backupPath + "/plugins").exists())
throw std::runtime_error(QString("The backup '%1' is missing the plugins directory.").arg(shortBackupPath).toLocal8Bit().constData());
}