Added missed call property to phone calls.
[qwerkisync] / EventLogBackupManager.cpp
1 /*
2  * Copyright (C) 2011, Jamie Thompson
3  *
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.
8  *
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.
13  *
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/>.
17  */
18
19 #include "EventLogBackupManager.h"
20 #include "EventPreventer.h"
21
22 #include <QtDebug>
23
24 #include <QDateTime>
25 #include <QDir>
26 #include <QFile>
27 #include <QStringList>
28 #include <QtAlgorithms>
29
30 #include <stdexcept>
31
32 EventLogBackupManager::EventLogBackupManager(const Settings & currentSettings) :
33         m_kCurrentSettings(currentSettings)
34 {
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");
40 }
41
42 EventLogBackupManager::~EventLogBackupManager()
43 {
44 }
45
46 void copyFileInfoListRecusively(const QFileInfoList &sourceItems, const QString &sourcePath, const QString &destinationPath)
47 {
48         foreach(QFileInfo entry, sourceItems)
49         {
50                 QString entryStubFilePath(entry.absoluteFilePath().replace(QRegExp("^" + sourcePath), ""));
51                 if(entry.isDir())
52                 {
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());
55
56                         copyFileInfoListRecusively(
57                                 QDir(entry.absoluteFilePath()).entryInfoList(
58                                         QDir::AllEntries | QDir::NoDotAndDotDot,
59                                         QDir::DirsFirst),
60                                 sourcePath,
61                                 destinationPath);
62                 }
63                 else
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());
66         }
67 }
68
69 void EventLogBackupManager::CreateBackup()
70 {
71         PurgeOldBackups();
72
73         // Make the new directory
74         if(QDir().mkpath(CurrentBackupPath()))
75         {
76                 try
77                 {
78                         // Copy the data to it
79                         copyFileInfoListRecusively(
80                                 QDir(DataDirectoryPath()).entryInfoList(
81                                         QStringList() << "*.db*" << "attachments" << "plugins",
82                                         QDir::AllEntries | QDir::NoDotAndDotDot,
83                                         QDir::DirsFirst),
84                                 DataDirectoryPath(),
85                                 CurrentBackupPath()
86                         );
87
88                         LockCurrentBackup();
89                 }
90                 catch(const std::runtime_error &exception)
91                 {
92                         RemoveDirRecusively(CurrentBackupPath());
93                 }
94         }
95         else
96                 throw std::runtime_error(QString("Unable to create backup directory '%1'").arg(CurrentBackupPath()).toLocal8Bit().constData());
97 }
98
99 void EventLogBackupManager::RestoreBackup(const QString &backupPath)
100 {
101         qDebug() << "Restoring backup: " << backupPath;
102
103         // Check backup is valid
104         EnsureBackupValid(backupPath);
105
106         // Remove old working-copy backups
107         {
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();
112         }
113
114         // Disable new events and try restoring the content
115         EventPreventer noEventsPlease(CurrentSettings());
116         noEventsPlease.DisableAccounts();
117         try
118         {
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());
123
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());
128
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());
134
135                 // Now all of the backup components have been restored, we can reenable the accounts safely
136                 noEventsPlease.RestoreAccounts();
137
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"));
143         }
144         catch(const std::runtime_error &exception)
145         {
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"));
151
152                 // Revert attachments
153                 if(!QDir().rename(DataDirectoryPath() + "/attachments.qsrestore", DataDirectoryPath() + "/attachments"))
154                         throw std::runtime_error("");
155
156                 // Revert plugins
157                 if(!QDir().rename(DataDirectoryPath() + "/plugins.qsrestore", DataDirectoryPath() + "/plugins"))
158                         throw std::runtime_error("");
159
160                 // Revert databases
161                 foreach(QFileInfo entry, QDir(DataDirectoryPath()).entryInfoList(QStringList("*.db*.qsrestore")))
162                         QFile(entry.absoluteFilePath()).copy(DataDirectoryPath() + entry.fileName().remove(".qsrestore"));
163
164                 // Now all of the working-copy components have been restored, we can reenable the accounts safely
165                 noEventsPlease.RestoreAccounts();
166
167                 // ..but the restoe still failed, so tell the caller about it.
168                 throw;
169         }
170 }
171
172 void EventLogBackupManager::LockCurrentBackup()
173 {
174         LockBackup(CurrentBackupPath());
175 }
176
177 void EventLogBackupManager::UnlockCurrentBackup()
178 {
179         UnlockBackup(CurrentBackupPath());
180 }
181
182 void EventLogBackupManager::LockBackup(const QString &backupPath)
183 {
184         qDebug() << "Locking backup: " << backupPath;
185
186         // Mark the backup as "in use" by touching a lockfile.
187         QFile lockfile(backupPath + LockFilename());
188         lockfile.open(QIODevice::WriteOnly);
189 }
190
191 void EventLogBackupManager::UnlockBackup(const QString &backupPath)
192 {
193         qDebug() << "Unlocking backup: " << backupPath;
194
195         QFile lockfile(QString("%1/%2").arg(backupPath).arg(LockFilename()));
196         lockfile.remove();
197 }
198
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
202 {
203 public:
204         inline bool operator()(const QFileInfo &a, QFileInfo &b) const
205         {
206                 return b.created() < a.created();
207         }
208 };
209
210 void EventLogBackupManager::PurgeOldBackups()
211 {
212         // Enumerate backups directory
213         QFileInfoList existingBackups(CurrentBackups(false));
214
215         // If more than maximum number of backups found, delete the oldest
216         if((uint)existingBackups.count() > MaxNumberOfBackups() - 1)
217         {
218                 // This is important, so explicitly make sure the list is in the correct order
219                 qSort(existingBackups.begin(), existingBackups.end(), OrderByTimestamp());
220
221                 for(int i(0); i < existingBackups.count(); ++i)
222                 {
223                         if(i < 2)
224                                 qDebug() << existingBackups.value(i).absoluteFilePath();
225                         else
226                                 RemoveDirRecusively(existingBackups.value(i));
227                 }
228         }
229 }
230
231 const QFileInfoList EventLogBackupManager::CurrentBackups(bool lockedOnly)
232 {
233         QFileInfoList existingBackups;
234         QDir backupDirectory(BackupDirectoryPath());
235         foreach(QFileInfo entry, backupDirectory.entryInfoList(QStringList("*.qsbackup"), QDir::AllEntries | QDir::NoDotAndDotDot, QDir::Name | QDir::Reversed))
236         {
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)
239                 {
240                         qDebug() << "Ignoring unlocked backup: " << entry.absoluteFilePath();
241                         continue;
242                 }
243
244                 qDebug() << "Locked backup found: " << entry.absoluteFilePath();
245                 existingBackups.append(QFileInfo(entry));
246         }
247
248         return existingBackups;
249 }
250
251 void EventLogBackupManager::RemoveDirRecusively(const QFileInfo &dirInfo)
252 {
253         foreach(QFileInfo entry,
254                         QDir(dirInfo.absoluteFilePath()).entryInfoList(
255                                 QDir::AllEntries | QDir::NoDotAndDotDot,
256                                 QDir::DirsFirst))
257         {
258                 if(entry.isDir())
259                         RemoveDirRecusively(entry);
260                 else
261                         QDir().remove(entry.absoluteFilePath());
262         }
263
264         // Dir will be empty as we've removed all dirs and files...
265         QDir().rmdir(dirInfo.absoluteFilePath());
266 }
267
268 void EventLogBackupManager::EnsureBackupValid(const QString &backupPath)
269 {
270         QString shortBackupPath(backupPath);
271         shortBackupPath.remove(BackupDirectoryPath());
272
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());
277
278         if(!QDir(backupPath + "/attachments").exists())
279                 throw std::runtime_error(QString("The backup '%1' is missing the attachments directory.").arg(shortBackupPath).toLocal8Bit().constData());
280
281         if(!QDir(backupPath + "/plugins").exists())
282                 throw std::runtime_error(QString("The backup '%1' is missing the plugins directory.").arg(shortBackupPath).toLocal8Bit().constData());
283 }