#include "syncmanager.h" Q_LOGGING_CATEGORY(syncmanager, "sync.manager"); SyncManager::SyncManager(bool dryMode) :mDryMode(dryMode) { } bool SyncManager::databaseInitialization() { database = std::make_shared(&dbMan, mDryMode); // connect(database.get(), &DatabaseAPI::error, [=](){ // QCoreApplication::exit(1); // }); // Open database if (!database->open()) { qCritical(syncmanager) << "Not possible to open database:" << dbMan.database().hostName(); return false; } // Get last synchronizaiton date qint64 lastsync_in_ms = database->getLastSync(); if (lastsync_in_ms == -1) { qCritical(syncmanager) << "Not possible to get last synchronization date"; return false; } // here -> nothing can fail mlastSync.set_timestamp(database->getLastSync()); colnod = std::make_shared(&reqMan, mlastSync.timestamp(), mDryMode); return true; } bool SyncManager::startSynchronization() { // I have to current time from database qint64 syncBegin = database->getNow(); if (syncBegin == -1) { qCritical(syncmanager) << "Not possible to get last sync date"; return false; } qInfo(syncmanager) << equals; qInfo(syncmanager) << "SYNCHRONIZATION BEGIN"; // TODO: check error database->initNewTokens(); // GETTING DATA FROM DATABASE auto databaseData = database->downloadData(); if (!databaseData) { qCritical(syncmanager) << "not possible to get data from database"; return false; } // NOW I HAVE DATA FROM DATABASE colnod->downloadData(); connect(colnod.get(), &ColnodAPI::downloadDataFinished, [=](std::shared_ptr data) { // NOW I HAVE DATA FROM COLNOD // I can process data qDebug(syncmanager) << "I have colnod and database data"; qDebug(syncmanager) << "updated token count" << data->updatedTokens.count(); }); return true; //downloadData(); auto colnodDataFinished = [=](std::shared_ptr data) { qDebug() << "Now I can check that I have all data I need"; }; //connect(this, &SyncManager::dataReady, processDownloadedData); 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(); }); return true; } void SyncManager::downloadData() { 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); //database->downloadData(); colnod->downloadData(); } // finds conflicts -> changes on both sides (colnod and DB), apply newer change void 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; Q_ASSERT(colnodData); Q_ASSERT(databaseData); // TODO: why copy data? 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; } void 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); } 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 == 1) { 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(); }