2 * Copyright (C) 2011, Jamie Thompson
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public
6 * License as published by the Free Software Foundation; either
7 * version 3 of the License, or (at your option) any later version.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * General Public License for more details.
14 * You should have received a copy of the GNU General Public
15 * License along with this program; If not, see
16 * <http://www.gnu.org/licenses/>.
19 #include "EventLogBackupManager.h"
20 #include "EventPreventer.h"
27 #include <QStringList>
28 #include <QtAlgorithms>
32 EventLogBackupManager::EventLogBackupManager(const Settings & currentSettings) :
33 m_kCurrentSettings(currentSettings)
35 setBackupDirectoryPath("/home/user/MyDocs/backups/");
36 setDataDirectoryPath("/home/user/.rtcom-eventlogger/");
37 setCurrentBackupName(QString::number(QDateTime::currentDateTimeUtc().toTime_t()) + ".qsbackup/");
38 setMaxNumberOfBackups(3);
39 setLockFilename(".inuse");
42 EventLogBackupManager::~EventLogBackupManager()
46 void copyFileInfoListRecusively(const QFileInfoList &sourceItems, const QString &sourcePath, const QString &destinationPath)
48 foreach(QFileInfo entry, sourceItems)
50 QString entryStubFilePath(entry.absoluteFilePath().replace(QRegExp("^" + sourcePath), ""));
53 if(!QDir().mkpath(destinationPath + entryStubFilePath))
54 throw std::runtime_error(QString("Unable to make the directory: %1%2").arg(destinationPath).arg(entryStubFilePath).toLocal8Bit().constData());
56 copyFileInfoListRecusively(
57 QDir(entry.absoluteFilePath()).entryInfoList(
58 QDir::AllEntries | QDir::NoDotAndDotDot,
64 if(!QFile(entry.absoluteFilePath()).copy(destinationPath + entryStubFilePath))
65 throw std::runtime_error(QString("Unable to copy the file '%1'' to '%2%3'").arg(entry.absoluteFilePath()).arg(destinationPath).arg(entryStubFilePath).toLocal8Bit().constData());
69 void EventLogBackupManager::CreateBackup()
73 // Make the new directory
74 if(QDir().mkpath(CurrentBackupPath()))
78 // Copy the data to it
79 copyFileInfoListRecusively(
80 QDir(DataDirectoryPath()).entryInfoList(
81 QStringList() << "*.db*" << "attachments" << "plugins",
82 QDir::AllEntries | QDir::NoDotAndDotDot,
90 catch(const std::runtime_error &exception)
92 RemoveDirRecusively(CurrentBackupPath());
96 throw std::runtime_error(QString("Unable to create backup directory '%1'").arg(CurrentBackupPath()).toLocal8Bit().constData());
99 void EventLogBackupManager::RestoreBackup(const QString &backupPath)
101 qDebug() << "Restoring backup: " << backupPath;
103 // Check backup is valid
104 EnsureBackupValid(backupPath);
106 // Remove old working-copy backups
108 RemoveDirRecusively(QFileInfo(DataDirectoryPath() + "/attachments.qsrestore"));
109 RemoveDirRecusively(QFileInfo(DataDirectoryPath() + "/plugins.qsrestore"));
110 foreach(QFileInfo entry, QDir(DataDirectoryPath()).entryInfoList(QStringList("*.db*.qsrestore")))
111 QFile(entry.absoluteFilePath()).remove();
114 // Disable new events and try restoring the content
115 EventPreventer noEventsPlease(CurrentSettings());
116 noEventsPlease.DisableAccounts();
119 // Move the attachments out of the way and copy in from the backup
120 if(!QDir().rename(DataDirectoryPath() + "/attachments", DataDirectoryPath() + "/attachments.qsrestore"))
121 throw std::runtime_error("");
122 copyFileInfoListRecusively(QDir(backupPath).entryInfoList(QStringList("attachments")), backupPath, DataDirectoryPath());
124 // Move the plugins out of the way and copy in from the backup
125 if(!QDir().rename(DataDirectoryPath() + "/plugins", DataDirectoryPath() + "/plugins.qsrestore"))
126 throw std::runtime_error("");
127 copyFileInfoListRecusively(QDir(backupPath).entryInfoList(QStringList("plugins")), backupPath, DataDirectoryPath());
129 // Move the database files out of the way and copy in from the backup
130 foreach(QFileInfo entry, QDir(DataDirectoryPath()).entryInfoList(QStringList("*.db*")))
131 QFile(entry.absoluteFilePath()).copy(DataDirectoryPath() + entry.fileName() + ".qsrestore");
132 foreach(QFileInfo entry, QDir(backupPath).entryInfoList(QStringList("*.db*")))
133 QFile(entry.absoluteFilePath()).copy(DataDirectoryPath() + entry.fileName());
135 // Now all of the backup components have been restored, we can reenable the accounts safely
136 noEventsPlease.RestoreAccounts();
138 // ...and we can remove the working-copy backups
139 foreach(QFileInfo entry, QDir(DataDirectoryPath()).entryInfoList(QStringList("*.db*.qsrestore")))
140 QFile(entry.absoluteFilePath()).remove();
141 RemoveDirRecusively(QFileInfo(DataDirectoryPath() + "/plugins.qsrestore"));
142 RemoveDirRecusively(QFileInfo(DataDirectoryPath() + "/attachments.qsrestore"));
144 catch(const std::runtime_error &exception)
146 // Remove the partially-restored data
147 foreach(QFileInfo entry, QDir(DataDirectoryPath()).entryInfoList(QStringList("*.db*.qsrestore")))
148 QFile(entry.absoluteFilePath().remove(".qsrestore")).remove();
149 RemoveDirRecusively(QFileInfo(DataDirectoryPath() + "/plugins"));
150 RemoveDirRecusively(QFileInfo(DataDirectoryPath() + "/attachments"));
152 // Revert attachments
153 if(!QDir().rename(DataDirectoryPath() + "/attachments.qsrestore", DataDirectoryPath() + "/attachments"))
154 throw std::runtime_error("");
157 if(!QDir().rename(DataDirectoryPath() + "/plugins.qsrestore", DataDirectoryPath() + "/plugins"))
158 throw std::runtime_error("");
161 foreach(QFileInfo entry, QDir(DataDirectoryPath()).entryInfoList(QStringList("*.db*.qsrestore")))
162 QFile(entry.absoluteFilePath()).copy(DataDirectoryPath() + entry.fileName().remove(".qsrestore"));
164 // Now all of the working-copy components have been restored, we can reenable the accounts safely
165 noEventsPlease.RestoreAccounts();
167 // ..but the restoe still failed, so tell the caller about it.
172 void EventLogBackupManager::LockCurrentBackup()
174 LockBackup(CurrentBackupPath());
177 void EventLogBackupManager::UnlockCurrentBackup()
179 UnlockBackup(CurrentBackupPath());
182 void EventLogBackupManager::LockBackup(const QString &backupPath)
184 qDebug() << "Locking backup: " << backupPath;
186 // Mark the backup as "in use" by touching a lockfile.
187 QFile lockfile(backupPath + LockFilename());
188 lockfile.open(QIODevice::WriteOnly);
191 void EventLogBackupManager::UnlockBackup(const QString &backupPath)
193 qDebug() << "Unlocking backup: " << backupPath;
195 QFile lockfile(QString("%1/%2").arg(backupPath).arg(LockFilename()));
199 // Ideally would be local to PurgeOldBackups, but template arguments have to
200 // refer to types with external linkage. Roll on C++0x!
201 class OrderByTimestamp
204 inline bool operator()(const QFileInfo &a, QFileInfo &b) const
206 return b.created() < a.created();
210 void EventLogBackupManager::PurgeOldBackups()
212 // Enumerate backups directory
213 QFileInfoList existingBackups(CurrentBackups(false));
215 // If more than maximum number of backups found, delete the oldest
216 if((uint)existingBackups.count() > MaxNumberOfBackups() - 1)
218 // This is important, so explicitly make sure the list is in the correct order
219 qSort(existingBackups.begin(), existingBackups.end(), OrderByTimestamp());
221 for(int i(0); i < existingBackups.count(); ++i)
224 qDebug() << existingBackups.value(i).absoluteFilePath();
226 RemoveDirRecusively(existingBackups.value(i));
231 const QFileInfoList EventLogBackupManager::CurrentBackups(bool lockedOnly)
233 QFileInfoList existingBackups;
234 QDir backupDirectory(BackupDirectoryPath());
235 foreach(QFileInfo entry, backupDirectory.entryInfoList(QStringList("*.qsbackup"), QDir::AllEntries | QDir::NoDotAndDotDot, QDir::Name | QDir::Reversed))
237 // If we only want locked backups, then skip those without the lock file present...
238 if(lockedOnly && QDir(entry.absoluteFilePath()).entryInfoList(QStringList(LockFilename()), QDir::Hidden).count() == 0)
240 qDebug() << "Ignoring unlocked backup: " << entry.absoluteFilePath();
244 qDebug() << "Locked backup found: " << entry.absoluteFilePath();
245 existingBackups.append(QFileInfo(entry));
248 return existingBackups;
251 void EventLogBackupManager::RemoveDirRecusively(const QFileInfo &dirInfo)
253 foreach(QFileInfo entry,
254 QDir(dirInfo.absoluteFilePath()).entryInfoList(
255 QDir::AllEntries | QDir::NoDotAndDotDot,
259 RemoveDirRecusively(entry);
261 QDir().remove(entry.absoluteFilePath());
264 // Dir will be empty as we've removed all dirs and files...
265 QDir().rmdir(dirInfo.absoluteFilePath());
268 void EventLogBackupManager::EnsureBackupValid(const QString &backupPath)
270 QString shortBackupPath(backupPath);
271 shortBackupPath.remove(BackupDirectoryPath());
273 bool oldDBPresent(QFile(backupPath + "/el.db").exists() && QFile(backupPath + "/el.db-journal").exists());
274 bool v1DBPresent(QFile(backupPath + "/el-v1.db").exists() && QFile(backupPath + "/el-v1.db-journal").exists());
275 if( !(oldDBPresent || v1DBPresent ) )
276 throw std::runtime_error(QString("The backup '%1' is missing the main event logger database.").arg(shortBackupPath).toLocal8Bit().constData());
278 if(!QDir(backupPath + "/attachments").exists())
279 throw std::runtime_error(QString("The backup '%1' is missing the attachments directory.").arg(shortBackupPath).toLocal8Bit().constData());
281 if(!QDir(backupPath + "/plugins").exists())
282 throw std::runtime_error(QString("The backup '%1' is missing the plugins directory.").arg(shortBackupPath).toLocal8Bit().constData());