Added CSV parsing and export of Symbian-format Event logs that have had their tables...
[qwerkisync] / SyncerThread.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 "SyncerThread.h"
20
21 #include "DBBackends/AllBackends.h"
22 #include "EventProcessors/Hasher.h"
23 #include "EventProcessors/Writer.h"
24
25 #include "Attachment.h"
26 #include "EventPreventer.h"
27 #include "Settings.h"
28 #include "EventTypes/EventFromFileList.h"
29 #include "EventTypes/iEvent.h"
30 #include "EventLogBackupManager.h"
31 #include "EventParsers/Factory.h"
32 #include "EventParsers/iEventParser.h"
33
34 #include <QDebug>
35 #include <QDir>
36 #include <QFile>
37 #include <QFileInfo>
38 #include <QProcess>
39 #include <QSharedPointer>
40
41 #include <stdexcept>
42
43 typedef QPair<QFileInfo, uint> EventFileInfo;
44
45 QDebug operator<<(QDebug, QList<Attachment*> &);
46 QFileInfoList FindEvents(QFileInfoList);
47
48 SyncerThread::SyncerThread(Settings &settings) :
49         QThread(), m_Settings(settings)
50 {
51 //      m_Restart = false;
52         m_Abort = false;
53 }
54
55 SyncerThread::~SyncerThread()
56 {
57         m_Mutex.lock();
58         m_Abort = true;
59         m_Condition.wakeOne();
60         m_Mutex.unlock();
61
62         wait();
63 }
64
65 void SyncerThread::Sync()
66 {
67         if (!isRunning())
68         {
69                 start(LowPriority);
70         }
71         else
72         {
73                 m_Restart = true;
74                 m_Condition.wakeOne();
75         }
76 }
77
78 #include "NumberToNameLookup.h"
79
80 void SyncerThread::run()
81 {
82         try
83         {
84                 EventPreventer preventEvents(CurrentSettings());
85                 EventLogBackupManager backupManager(CurrentSettings());
86
87                 if(CurrentSettings().Mode() == Settings::MODE_EXPORT)
88                 {
89                         qDebug() << "Exporting events";
90
91                         // Temp - Remove directory first so it's always just this export for now
92                         QDir().rmpath(CurrentSettings().Directory());
93
94                         DBBackends::AllBackends allBackends(CurrentSettings());
95                         NumberToNameLookup lookup; // Prepare the telephone-address book ID lookup.
96                         EventProcessors::Writer eventWriter(CurrentSettings(), lookup);
97                         QObject::connect(&eventWriter, SIGNAL(EventProcessed(int,int)), this, SIGNAL(EventProcessed(int,int)));
98                         allBackends.Process(eventWriter);
99                         QObject::disconnect(&eventWriter, SIGNAL(EventProcessed(int,int)), this, SIGNAL(EventProcessed(int,int)));
100                 }
101                 else
102                 {
103                         qDebug() << "Importing events";
104
105                         backupManager.CreateBackup();
106
107                         qDebug() << "Scanning filesystem";
108
109                         // Open chosen directory and grab *all* files within (we filter at
110                         // parsing stage)
111                         QFileInfoList candidates(FindEvents(
112                                 QDir(CurrentSettings().Directory()).entryInfoList(QDir::AllEntries | QDir::NoDotAndDotDot)));
113
114                         // We're going to be storing hashes to match up events on disk to
115                         // those in the DB. Assuming 1 event per file is a good starting point.
116                         // Each file can provide multiple events, so sotre the hash of the
117                         // event, the file information, and the record index.
118                         uint totalEvents(candidates.count());
119                         QHash<QPair<QString, uint>, iHashable::Hash> hashesByPath;
120                         hashesByPath.reserve(totalEvents);
121                         QHash<iHashable::Hash, QPair<QString, uint> > pathsByHashes;
122                         pathsByHashes.reserve(totalEvents);
123
124                         qDebug() << "Hashing events";
125                         {
126                                 // Work our way through the candidates...
127                                 int idx = 0;
128                                 foreach(QFileInfo fileInfo, candidates)
129                                 {
130                                         ++idx;
131                                         try
132                                         {
133                                                 EventTypes::EventFromFileList fileEvents(ProcessFile(fileInfo.absoluteFilePath()));
134
135                                                 foreach(EventTypes::EventFromFile eventFromFile, fileEvents)
136                                                 {
137                                                         const uint hash(eventFromFile.first->HashCode());
138                                                         pathsByHashes.insert(hash, QPair<QString, iHashable::Hash>(fileInfo.absoluteFilePath(), eventFromFile.second));
139                                                         hashesByPath.insert(QPair<QString, iHashable::Hash>(fileInfo.absoluteFilePath(), eventFromFile.second), hash);
140
141                                                         qDebug() << hash;
142                                                 }
143                                         }
144                                         catch(std::runtime_error& exception)
145                                         {
146                                                 qDebug() << exception.what() << endl;
147                                         }
148
149                                         emit EventProcessed(idx, candidates.count());
150                                 }
151                         }
152
153                         qDebug() << "Determining new events";
154
155                         // Calculate the new events by removing the hashes of events already
156                         // found in the DB
157                         QSet<iHashable::Hash> newHashes(QSet<iHashable::Hash>::fromList( pathsByHashes.keys()));
158
159                         //EventHasher eventHasher;
160                         //ProcessDBEvents(eventHasher);
161                         DBBackends::AllBackends allBackends(CurrentSettings());
162                         EventProcessors::Hasher eventHasher;
163                         allBackends.Process(eventHasher);
164
165                         foreach(iHashable::Hash hash, eventHasher.m_Hashes)
166                                 newHashes.remove(hash);
167
168                         qDebug() << QString("%1 new hashes").arg(newHashes.size()) << endl;
169                         foreach(iHashable::Hash hash, newHashes)
170                                 qDebug() << hash << endl;
171
172                         // Now an optimisation: group the new hashes by the files they come
173                         // from. This enables each file to only be parsed once and return
174                         // all the required events from it.
175                         QHash<QString, QList<iHashable::Hash> > newHashesByPath;
176                         foreach(iHashable::Hash newHash, newHashes)
177                         {
178                                 const QString &currentPath(pathsByHashes.value(newHash).first);
179                                 if(newHashesByPath.contains(currentPath))
180                                         newHashesByPath[currentPath].append(newHash);
181                                 else
182                                         newHashesByPath[currentPath] = QList<iHashable::Hash>() << newHash;
183                         }
184
185                         qDebug() << "Scanning addressbook";
186
187                         // Prepare the telephone-address book ID lookup.
188                         NumberToNameLookup lookup;
189
190                         qDebug() << "Importing new events";
191
192                         // Re-parse the new events and insert them
193                         allBackends.PreInsert();
194                         {
195                                 int idx = 0;
196                                 foreach(QString filename, newHashesByPath.keys())
197                                 {
198                                         QList<uint> recordsToReturn;
199                                         foreach(iHashable::Hash newHash, newHashesByPath.value(filename))
200                                                 recordsToReturn.append(pathsByHashes.value(newHash).second);
201
202                                         // Repeating an action that caused an exception last time
203                                         // shouldn't happen again, but just in case...
204                                         try
205                                         {
206                                                 foreach(EventTypes::EventFromFile newEventFromFile, ProcessFile(filename, recordsToReturn))
207                                                 {
208                                                         // ...and insert it into the DB
209                                                         try
210                                                         {
211                                                                 allBackends.Insert(*newEventFromFile.first, lookup);
212                                                         }
213                                                         catch(const std::runtime_error &exception)
214                                                         {
215                                                                 qDebug() << "Unable to insert event: " << exception.what();
216                                                         }
217
218                                                         emit EventProcessed(++idx, newHashes.count());
219                                                 }
220                                         }
221                                         catch(const std::runtime_error &exception)
222                                         {
223                                                 qDebug() << exception.what() << endl;
224                                         }
225
226                                         // Just to make sure the listeners are synced even if the
227                                         // earlier call is skipped due to errors...
228                                         emit EventProcessed(idx, newHashes.count());
229                                 }
230                         }
231                         allBackends.PostInsert(); // Perform any post-insert cleanup (i.e. reindexing)
232
233                         // Need to find a better way of refreshing the conversations view...
234                         QProcess::execute("pkill rtcom");
235
236                         // Signal we completed successfully.
237                         backupManager.UnlockCurrentBackup();
238                 }
239
240                 // May as well call this explicitly despite it being called by the
241                 // destructor - it's harmless.
242                 preventEvents.RestoreAccounts();
243         }
244         catch(std::runtime_error exception)
245         {
246                 qDebug() << exception.what();
247         }
248 }
249
250 EventTypes::EventFromFileList SyncerThread::ProcessFile(const QString &path, const QList<uint> &recordsToReturn) const
251 {
252         qDebug() << path << endl;
253         QFile eventFile(path);
254
255         // If the file's ok, process it...
256         if (eventFile.open(QFile::ReadOnly))
257         {
258                 // Identify type of file...
259                 EventParsers::iEventParser * parser(EventParsers::Factory::CreateParser(CurrentSettings(), path));
260
261                 // ...and grab the events from it (if it's a supported format)
262                 if(NULL != parser)
263                         return parser->ParseFile(eventFile, recordsToReturn);
264                 else
265                         return EventTypes::EventFromFileList();
266         }
267         else
268                 throw std::runtime_error(QString("Unable to open: %1").arg(path).toStdString());
269 }
270
271 QFileInfoList FindEvents(QFileInfoList currentCandidate)
272 {
273         QFileInfoList foundEvents;
274         foreach(QFileInfo fileInfo, currentCandidate)
275         {
276                 if(fileInfo.isDir())
277                         foundEvents.append(FindEvents(QDir(fileInfo.absoluteFilePath()).entryInfoList(QDir::AllEntries | QDir::NoDotAndDotDot)));
278                 else
279                         foundEvents.append(fileInfo);
280         }
281
282         return foundEvents;
283 }
284
285 QDebug operator<<(QDebug dbg, QList<Attachment*> &attachments)
286 {
287         dbg.nospace() << "Attachments" << "\n";
288
289         foreach(Attachment* attachment, attachments)
290                 dbg.nospace() << *attachment << "\n";
291
292         dbg.nospace() << "\n";
293
294         return dbg;
295 }