#include "syncmanager.h" Q_LOGGING_CATEGORY(syncmanager, "sync.manager"); SyncManager::SyncManager(bool dryMode) :mDryMode(dryMode) { } bool SyncManager::init() { database = std::make_shared(&dbMan, mDryMode); connect(database.get(), &DatabaseAPI::error, [=](){ QCoreApplication::exit(1); }); if (database->open()){ mlastSync.set_timestamp(database->getLastSync()); colnod = std::make_shared(&reqMan, mlastSync.timestamp(), mDryMode); connect(colnod.get(), &ColnodAPI::error, [=](){ QCoreApplication::exit(1); }); // -1 --> some error with getting data from DB if (mlastSync.timestamp() >= 0){ return true; } qCritical(syncmanager) << "Couldn't get time of last synchronization"; } else qCritical(syncmanager) << "Opening database wasn't successful"; return false; } bool SyncManager::run() { qint64 syncBegin = database->getNow(); qInfo(syncmanager) << equals; qInfo(syncmanager) << "SYNCHRONIZATION BEGIN"; database->initNewTokens(); downloadData(); connect(this, &SyncManager::dataReady, [=](){ prepareData(); connect(this, &SyncManager::dataProcessFinished, [=](){ qInfo(syncmanager) << "PROCESSING DATA -> FINISHED"; qInfo(syncmanager) << equals; updateLastUpdate(syncBegin); connect(this, &SyncManager::syncFinished, [=](){ if (database->isValid() && colnod->isValid()) { database->setLastSync(syncBegin); } else { qWarning(syncmanager) << "Synchronization was unsuccessful, next run of sync will be with same last sync time, check warnings"; } qInfo(syncmanager).noquote() << "Update last synchronization time to time of this sync (" << QDateTime::fromMSecsSinceEpoch(syncBegin).toString("yyyy-MM-dd hh:mm:ss.zzz") << ")"; qInfo(syncmanager) << equals; qInfo(syncmanager) << "SYNCHRONIZATION FINISHED"; qInfo(syncmanager) << equals; QCoreApplication::exit(); }); }); processData(); }); } bool SyncManager::downloadData() { bool res = true; qInfo(syncmanager) << equals; qInfo(syncmanager) << "DOWNLOADING DATA -> BEGIN"; qInfo(syncmanager) << dash; qInfo(syncmanager).noquote() << "Modified data since last synchronization (" << mlastSync.toDateString() << "):"; qInfo(syncmanager) << dash; connect(colnod.get(), &ColnodAPI::downloadDataFinished, this, &SyncManager::saveColnodData); connect(database.get(), &DatabaseAPI::downloadDataFinished, this, &SyncManager::saveDatabaseData); if (!database->downloadData()) { res = false; } colnod->downloadData(); return true; } // finds conflicts -> changes on both sides (colnod and DB), apply newer change bool SyncManager::prepareData() { qInfo(syncmanager) << equals; qInfo(syncmanager) << "PREPARING DATA -> BEGIN"; qInfo(syncmanager) << dash; qInfo(syncmanager) << "Resolve conflicts ( changes on same item on both Colnod and DB ):"; qInfo(syncmanager) << dash; colnodDataToProcess = std::make_shared(*colnodData); databaseDataToProcess = std::make_shared(*databaseData); QList conflicts; QList alreadySynced; // check modifed tokens from colnod, dont want update token, if it was edited in db after or has been already synced for (const auto &colnodToken : colnodDataToProcess->updatedTokens) { if (databaseDataToProcess->updatedTokens.contains(colnodToken->id())) { // updated in db later if (databaseDataToProcess->updatedTokens.value(colnodToken->id()) > colnodToken) { conflicts.append(colnodToken->id()); } continue; } auto token = database->getToken(colnodToken->id()); if ( token ) // alredy synced ( tokens are same ) if (token == colnodToken) alreadySynced.append(colnodToken->id()); } // remove tokens which should be not updated for (const auto &id : conflicts) colnodDataToProcess->updatedTokens.remove(id); for (const auto &id : alreadySynced) colnodDataToProcess->updatedTokens.remove(id); qInfo(syncmanager) << tab << "-> COLNOD DATA - CHANGES:"; qInfo(syncmanager) << tab << tab << conflicts.size() << "TOKENS WERE ADDED/UPDATED IN DATABASE TOO"; qInfo(syncmanager) << tab << tab << alreadySynced.size() << "TOKENS WERE ALREADY SYNCED"; conflicts.clear(); // chech for already deleted ( in previous synced tokens were deleted from colnod, so it come to this sync but we dont want to sync it again for (const auto &id : colnodDataToProcess->deletedTokens) { if (database->isAlreadyDeleted(id)) { conflicts.append(id); } } for (const auto &id : conflicts) { colnodDataToProcess->deletedTokens.removeOne(id); } qInfo(syncmanager) << tab << tab << conflicts.size() << "TOKENS WERE ALREADY DELETED"; qInfo(syncmanager) << dash; conflicts.clear(); // chech modified from db, remove token if it was edited in colnod later for (const auto &databaseToken : databaseDataToProcess->updatedTokens) { if (colnodDataToProcess->updatedTokens.contains(databaseToken->id())) { if (databaseToken < colnodDataToProcess->updatedTokens.value(databaseToken->id())) conflicts.append(databaseToken->id()); } else { // restriction cauesed by colnod rest api if (databaseToken->description() != "mobile") { qCritical(syncmanager) << "Token:" << databaseToken->id() << "doesn't have 'mobile' in description, can't be "; conflicts.append(databaseToken->id()); } } } for (const auto &id : conflicts) colnodDataToProcess->updatedTokens.remove(id); qInfo(syncmanager) << tab << "-> DATABASE DATA - CHANGES:"; qInfo(syncmanager) << tab << tab << conflicts.size() << "TOKENS WERE ADDED/UPDATED IN IN COLNOD TOO"; qInfo(syncmanager) << dash; qInfo(syncmanager) << "PREPARING DATA -> FINISHED"; qInfo(syncmanager) << equals; return true; } bool SyncManager::processData() { qInfo(syncmanager) << equals; qInfo(syncmanager) << "PROCESSING DATA -> BEGIN"; qInfo(syncmanager) << dash; qInfo(syncmanager) << "Push changes to both Colnod and Database:"; qInfo(syncmanager) << dash; database->processData(colnodDataToProcess); connect(colnod.get(), &ColnodAPI::dataProcessFinished, this, &SyncManager::dataProcessFinished); colnod->processData(databaseDataToProcess); return true; } void SyncManager::updateLastUpdate(qint64 syncBegin) { Timestamp time; time.set_timestamp(syncBegin); colnod->getTokensUpdated(time); database->setLastUpdate(databaseDataToProcess->deletedTokens); connect(colnod.get(), &ColnodAPI::partOfDataReady, [=](const std::shared_ptr &data){ database->setLastUpdate(data->updatedTokens); emit syncFinished(); }); } void SyncManager::areDataReady() { if (++dataAmount == 2) { qInfo(syncmanager) << "DOWNLOADING DATA -> FINISHED"; qInfo(syncmanager) << equals; emit dataReady(); } } void SyncManager::saveColnodData(const std::shared_ptr &data) { colnodData = data; areDataReady(); } void SyncManager::saveDatabaseData(const std::shared_ptr &data) { databaseData = data; areDataReady(); }