#include "syncmanager.h" Q_LOGGING_CATEGORY(syncmanager, "sync.manager"); SyncManager::SyncManager(bool dryMode) :mDryMode(dryMode) { } bool SyncManager::databaseInitialization() { database = std::make_shared(&dbMan, mDryMode); // 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.setTimestamp(database->getLastSync()); colnod = std::make_shared(&reqMan, 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"; qInfo(syncmanager) << equals; qInfo(syncmanager) << "DOWNLOADING DATA -> BEGIN"; qInfo(syncmanager) << dash; qInfo(syncmanager).noquote() << "Modified data since last synchronization (" << mlastSync.toDateString() << "):"; qInfo(syncmanager) << dash; // 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(syncBegin); connect(colnod.get(), &ColnodAPI::downloadDataFinished, [=](std::shared_ptr colnodData) { qInfo(syncmanager) << "DOWNLOADING DATA -> FINISHED"; qInfo(syncmanager) << equals; if (!colnodData) { qCritical(syncmanager) << "Not possible to get correct data from colnod"; QCoreApplication::exit(-1); return; } // NOW I SHOULD HAVE VALID DATA FROM COLNOD std::unique_ptr preparedData = prepareDataNew(databaseData, colnodData); if (!preparedData) { qCritical(syncmanager) << "Not possible to get correct data from colnod"; QCoreApplication::exit(-1); return; } qInfo(syncmanager) << equals; qInfo(syncmanager) << "PROCESSING DATA -> BEGIN"; qInfo(syncmanager) << dash; qInfo(syncmanager) << "Push changes to both Colnod and Database:"; qInfo(syncmanager) << dash; if (!database->processData(preparedData->databaseData)) { qCritical(syncmanager) << "Not possible to upload data to database"; } // data is processed to other sides connect(colnod.get(), &ColnodAPI::dataProcessFinished, [&](){ qInfo(syncmanager) << "PROCESSING DATA -> FINISHED"; qInfo(syncmanager) << equals; // REUPDATE tokens pushed from database to colnod (cause of lastUpdate time) Timestamp time; time.setTimestamp(syncBegin); database->reupdateDeleted(preparedData->databaseData->deletedTokens); // download data changes in this sync colnod->getTokensUpdated(time); // updated data are ready connect(colnod.get(), &ColnodAPI::partOfDataReady, [=](const std::shared_ptr &uploadedData){ if (!uploadedData) { qCritical(syncmanager) << "Not possible to get correct data from colnod"; QCoreApplication::exit(-1); return; } if (!database->uploadTokens(uploadedData->updatedTokens)) { qCritical(syncmanager) << "Not possible to upload data to database"; } database->reupdateDeleted(uploadedData->deletedTokens); if (!database->isValid() || !colnod->isValid()) { qWarning(syncmanager) << "Synchronization was unsuccessful, next run of sync will be with same last sync time, check warnings"; } else { database->setLastSync(syncBegin); 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(); }); }); colnod->processData(preparedData->colnodData); //qCritical(syncmanager) << "Not possible to upload data to colnod"; }); return true; } std::unique_ptr SyncManager::prepareDataNew(const std::shared_ptr &databaseData, const std::shared_ptr &colnodData) { auto preparedColnodData = std::make_shared(); auto preparedDatabaseData = std::make_shared(); QList conflicts; QList alreadySynced; 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; for (const auto &colnodToken : colnodData->updatedTokens) { // not conflict so add to prepered tokens if (!databaseData->updatedTokens.contains(colnodToken->id())) { preparedColnodData->updatedTokens.insert(colnodToken->id(), colnodToken); continue; } // change in colnod is newer if (databaseData->updatedTokens.value(colnodToken->id()) < colnodToken) { preparedColnodData->updatedTokens.insert(colnodToken->id(), colnodToken); } else { conflicts.append(colnodToken->id()); } auto token = database->getToken(colnodToken->id()); if ( token ) // alredy synced ( tokens are same ) if (token == colnodToken) alreadySynced.append(colnodToken->id()); } for (const auto &id : colnodData->deletedTokens) { if (!database->isAlreadyDeleted(id)) { preparedColnodData->deletedTokens.append(id); } } qInfo(syncmanager) << tab << "-> COLNOD DATA - CHANGES:"; qInfo(syncmanager) << tab << tab << conflicts.size() << "TOKENS WERE ADDED/UPDATED IN DATABASE AFTER"; qInfo(syncmanager) << tab << tab << alreadySynced.size() << "TOKENS WERE ALREADY SYNCED"; qInfo(syncmanager) << tab << tab << colnodData->deletedTokens.size() - preparedColnodData->deletedTokens.size() << "TOKENS WERE ALREADY DELETED"; qInfo(syncmanager) << dash; for (const auto &databaseToken : databaseData->updatedTokens) { if (databaseToken->name().isEmpty()) { initNewToken(databaseToken); } // restriction cauesed by colnod rest api if (databaseToken->description() != "mobile") { qCritical(syncmanager) << "Token:" << databaseToken->id() << "doesn't have 'mobile' in description, can't be "; continue; } // no conflict if (!colnodData->updatedTokens.contains(databaseToken->id())) { preparedDatabaseData->updatedTokens.insert(databaseToken->id(), databaseToken); continue; } // database change is newer if (databaseToken > colnodData->updatedTokens.value(databaseToken->id())) { preparedDatabaseData->updatedTokens.insert(databaseToken->id(), databaseToken); } } qInfo(syncmanager) << tab << "-> DATABASE DATA - CHANGES:"; qInfo(syncmanager) << tab << tab << databaseData->updatedTokens.size() - preparedColnodData->updatedTokens.size() << "TOKENS WERE ADDED/UPDATED IN COLNOD AFTER"; qInfo(syncmanager) << dash; qInfo(syncmanager) << "PREPARING DATA -> FINISHED"; qInfo(syncmanager) << equals; auto preparedData = std::make_unique(preparedDatabaseData, preparedColnodData); return preparedData; } void SyncManager::initNewToken(const std::shared_ptr& token) { Q_ASSERT(database); Q_ASSERT(token); qint16 num = database->getNumOfTokensOfSubject(token->subjectId()); token->setName(database->getSubjectNameByTokenId(token->id()) + "_mobile_" + QString::number(num + 1)); token->setDescription("mobile"); token->setTokenData(token->tokenData().toUpper()); qInfo(syncmanager).noquote() << Message::massageTemplate("Inicializing new", "token", "sync manager", token->name()); } std::unique_ptr SyncManager::prepareDataUsindOld(const std::shared_ptr &databaseData, const std::shared_ptr &colnodData) { 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? std::shared_ptr cloneColnodDataToProcess = std::make_shared(*colnodData); std::shared_ptr cloneDatabaseDataToProcess = 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 : cloneColnodDataToProcess->updatedTokens) { if (cloneDatabaseDataToProcess->updatedTokens.contains(colnodToken->id())) { // updated in db later if (cloneDatabaseDataToProcess->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) cloneColnodDataToProcess->updatedTokens.remove(id); for (const auto &id : alreadySynced) cloneColnodDataToProcess->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 : cloneColnodDataToProcess->deletedTokens) { if (database->isAlreadyDeleted(id)) { conflicts.append(id); } } for (const auto &id : conflicts) { cloneColnodDataToProcess->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 : cloneDatabaseDataToProcess->updatedTokens) { if (cloneColnodDataToProcess->updatedTokens.contains(databaseToken->id())) { if (databaseToken < cloneColnodDataToProcess->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) cloneColnodDataToProcess->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; // Prepare data auto preparedData = std::make_unique(); preparedData->colnodData = cloneColnodDataToProcess; preparedData->databaseData = cloneDatabaseDataToProcess; return preparedData; }