/*
* 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 "SyncerThread.h"
#include "DBBackends/AllBackends.h"
#include "EventProcessors/Hasher.h"
#include "EventProcessors/Writer.h"
#include "Attachment.h"
#include "EventPreventer.h"
#include "Settings.h"
#include "EventTypes/EventFromFileList.h"
#include "EventTypes/iEvent.h"
#include "EventLogBackupManager.h"
#include "EventParsers/Factory.h"
#include "EventParsers/iEventParser.h"
#include
#include
#include
#include
#include
#include
#include
typedef QPair EventFileInfo;
QDebug operator<<(QDebug, QList &);
QFileInfoList FindEvents(QFileInfoList);
SyncerThread::SyncerThread(Settings &settings) :
QThread(), m_Settings(settings)
{
// m_Restart = false;
m_Abort = false;
}
SyncerThread::~SyncerThread()
{
m_Mutex.lock();
m_Abort = true;
m_Condition.wakeOne();
m_Mutex.unlock();
wait();
}
void SyncerThread::Sync()
{
if (!isRunning())
{
start(LowPriority);
}
else
{
m_Restart = true;
m_Condition.wakeOne();
}
}
#include "NumberToNameLookup.h"
void SyncerThread::run()
{
try
{
EventPreventer preventEvents(CurrentSettings());
EventLogBackupManager backupManager(CurrentSettings());
if(CurrentSettings().Mode() == Settings::MODE_EXPORT)
{
qDebug() << "Exporting events";
// Temp - Remove directory first so it's always just this export for now
QDir().rmpath(CurrentSettings().Directory());
DBBackends::AllBackends allBackends(CurrentSettings());
EventProcessors::Writer eventWriter(CurrentSettings());
QObject::connect(&eventWriter, SIGNAL(EventProcessed(int,int)), this, SIGNAL(EventProcessed(int,int)));
allBackends.Process(eventWriter);
}
else
{
qDebug() << "Importing events";
backupManager.CreateBackup();
qDebug() << "Scanning filesystem";
// Open chosen directory and grab *all* files within (we filter at
// parsing stage)
QFileInfoList candidates(FindEvents(
QDir(CurrentSettings().Directory()).entryInfoList(QDir::AllEntries | QDir::NoDotAndDotDot)));
// We're going to be storing hashes to match up events on disk to
// those in the DB. Assuming 1 event per file is a good starting point.
// Each file can provide multiple events, so sotre the hash of the
// event, the file information, and the record index.
uint totalEvents(candidates.count());
QHash, iHashable::Hash> hashesByPath;
hashesByPath.reserve(totalEvents);
QHash > pathsByHashes;
pathsByHashes.reserve(totalEvents);
qDebug() << "Hashing events";
{
// Work our way through the candidates...
int idx = 0;
foreach(QFileInfo fileInfo, candidates)
{
++idx;
try
{
EventTypes::EventFromFileList fileEvents(ProcessFile(fileInfo.absoluteFilePath()));
foreach(EventTypes::EventFromFile eventFromFile, fileEvents)
{
const uint hash(eventFromFile.first->HashCode());
pathsByHashes.insert(hash, QPair(fileInfo.absoluteFilePath(), eventFromFile.second));
hashesByPath.insert(QPair(fileInfo.absoluteFilePath(), eventFromFile.second), hash);
qDebug() << hash;
}
}
catch(std::runtime_error& exception)
{
qDebug() << exception.what() << endl;
}
emit EventProcessed(idx, candidates.count());
}
}
qDebug() << "Determining new events";
// Calculate the new events by removing the hashes of events already
// found in the DB
QSet newHashes(QSet::fromList( pathsByHashes.keys()));
//EventHasher eventHasher;
//ProcessDBEvents(eventHasher);
DBBackends::AllBackends allBackends(CurrentSettings());
EventProcessors::Hasher eventHasher;
allBackends.Process(eventHasher);
foreach(iHashable::Hash hash, eventHasher.m_Hashes)
newHashes.remove(hash);
qDebug() << QString("%1 new hashes").arg(newHashes.size()) << endl;
foreach(iHashable::Hash hash, newHashes)
qDebug() << hash << endl;
// Now an optimisation: group the new hashes by the files they come
// from. This enables each file to only be parsed once and return
// all the required events from it.
QHash > newHashesByPath;
foreach(iHashable::Hash newHash, newHashes)
{
const QString ¤tPath(pathsByHashes.value(newHash).first);
if(newHashesByPath.contains(currentPath))
newHashesByPath[currentPath].append(newHash);
else
newHashesByPath[currentPath] = QList() << newHash;
}
qDebug() << "Scanning addressbook";
// Prepare the telephone-address book ID lookup.
NumberToNameLookup lookup;
qDebug() << "Importing new events";
// Re-parse the new events and insert them
allBackends.PreInsert();
{
int idx = 0;
foreach(QString filename, newHashesByPath.keys())
{
QList recordsToReturn;
foreach(iHashable::Hash newHash, newHashesByPath.value(filename))
recordsToReturn.append(pathsByHashes.value(newHash).second);
// Repeating an action that caused an exception last time
// shouldn't happen again, but just in case...
try
{
foreach(EventTypes::EventFromFile newEventFromFile, ProcessFile(filename, recordsToReturn))
{
// ...and insert it into the DB
try
{
allBackends.Insert(*newEventFromFile.first, lookup);
}
catch(const std::runtime_error &exception)
{
qDebug() << "Unable to insert event: " << exception.what();
}
emit EventProcessed(++idx, newHashes.count());
}
}
catch(const std::runtime_error &exception)
{
qDebug() << exception.what() << endl;
}
// Just to make sure the listeners are synced even if the
// earlier call is skipped due to errors...
emit EventProcessed(idx, newHashes.count());
}
}
allBackends.PostInsert(); // Perform any post-insert cleanup (i.e. reindexing)
// Need to find a better way of refreshing the conversations view...
QProcess::execute("pkill rtcom");
// Signal we completed successfully.
backupManager.UnlockCurrentBackup();
}
// May as well call this explicitly despite it being called by the
// destructor - it's harmless.
preventEvents.RestoreAccounts();
}
catch(std::runtime_error exception)
{
qDebug() << exception.what();
}
}
EventTypes::EventFromFileList SyncerThread::ProcessFile(const QString &path, const QList &recordsToReturn) const
{
qDebug() << path << endl;
QFile eventFile(path);
// If the file's ok, process it...
if (eventFile.open(QFile::ReadOnly))
{
// Identify type of file...
EventParsers::iEventParser * parser(EventParsers::Factory::CreateParser(CurrentSettings(), path));
// ...and grab the events from it (if it's a supported format)
if(NULL != parser)
return parser->ParseFile(eventFile, recordsToReturn);
else
return EventTypes::EventFromFileList();
}
else
throw std::runtime_error(QString("Unable to open: %1").arg(path).toStdString());
}
QFileInfoList FindEvents(QFileInfoList currentCandidate)
{
QFileInfoList foundEvents;
foreach(QFileInfo fileInfo, currentCandidate)
{
if(fileInfo.isDir())
foundEvents.append(FindEvents(QDir(fileInfo.absoluteFilePath()).entryInfoList(QDir::AllEntries | QDir::NoDotAndDotDot)));
else
foundEvents.append(fileInfo);
}
return foundEvents;
}
QDebug operator<<(QDebug dbg, QList &attachments)
{
dbg.nospace() << "Attachments" << "\n";
foreach(Attachment* attachment, attachments)
dbg.nospace() << *attachment << "\n";
dbg.nospace() << "\n";
return dbg;
}