diff --git a/server-sdk b/server-sdk index a818055..031b6c2 160000 --- a/server-sdk +++ b/server-sdk @@ -1 +1 @@ -Subproject commit a818055b75dc0de97e26be44cbbfd2e94516c09e +Subproject commit 031b6c2535408bce18a5d7bbc5842f9ee4384328 diff --git a/src/app/main.cpp b/src/app/main.cpp index 34b50bf..3b35c33 100644 --- a/src/app/main.cpp +++ b/src/app/main.cpp @@ -45,13 +45,13 @@ int main(int argc, char *argv[]) // Initialize database and colnod API if (!syncManager.databaseInitialization()) { - qCritical("Not possible to initialize sync application"); + qCritical() << "Not possible to initialize sync application"; return -1; } // Synchronizaiton if (!syncManager.startSynchronization()) { - qCritical("Not possible to start synchronization"); + qCritical() << "Not possible to start synchronization"; return -1; } diff --git a/src/colnod/colnodapi.cpp b/src/colnod/colnodapi.cpp index 55d818e..14da4e8 100644 --- a/src/colnod/colnodapi.cpp +++ b/src/colnod/colnodapi.cpp @@ -1,38 +1,58 @@ #include "colnodapi.h" +#include + Q_LOGGING_CATEGORY(colnod, "colnod.api"); -ColnodAPI::ColnodAPI(serversdk::RequestManager* reqMan, const bool &dryMode, const QString &userName, const QString &password) - :mReqMan(reqMan), mUserName(userName), mPassword(password), mDryMode(dryMode) +ColnodAPI::ColnodAPI(serversdk::RequestManager* reqMan, const bool &dryMode, QString userName, QString password) + :mReqMan(reqMan), mUserName(std::move(userName)), mPassword(std::move(password)), mDryMode(dryMode) { // ----------------------------------- mReqMan->setIgnoreSsl(true); // ----------------------------------- } -void ColnodAPI::downloadData(const Timestamp &time) +/** + * @brief Downloads new data from Colnod -> emits downloadDataFinished signal + * @param time + * + * @return signal with nullptr when something went wrong + * @return signal with data when valid data returned + */ +void ColnodAPI::downloadData(const qint64 &time) { // Have to get Login auto login = requestLogin(); connect(login.get(), &serversdk::Request::finished, this, [=](){ // Get login token - if (!checkRequest(login)) { + if (login->status() != 200) { + emit downloadDataFinished(nullptr); + return; + } + AuthToken token; + auto responseData = login->responseData(); + if (responseData.isEmpty()) { + qCWarning(colnod) << "empty response data when logging in"; emit downloadDataFinished(nullptr); return; } - AuthToken token; - token.fromJson(login->responseData()); + token.fromJson(responseData); authToken = token.id(); + if (authToken.isEmpty()) { + qCCritical(colnod) << "Token uuid is empty"; + emit downloadDataFinished(nullptr); + return; + } auto data = std::make_shared(); - auto errorHandler = [=](std::shared_ptr request){ + auto errorHandler = [=](const std::shared_ptr& request){ if (!checkRequest(request)) { qWarning(colnod) << "ERROR during calling" << request->url() << ", request unsuccessful"; mValid = false; - emit downloadDataFinished(nullptr); + emit downloadDataFinished(nullptr); // TODO: think about it, will the other if passed? } else if (!checkResponse(request->responseData())) { qWarning(colnod) << "Data from" << request->url() << "are invalid"; @@ -40,31 +60,35 @@ void ColnodAPI::downloadData(const Timestamp &time) } }; + // if I have all data emit downloadDataFinished connect(this, &ColnodAPI::partOfDataReady, this, &ColnodAPI::areDataReady); - auto tokensRequest = getTokensUpdated(time); + Timestamp timestamp; + timestamp.setTimestamp(time); + + auto tokensRequest = getTokensUpdated(timestamp); connect(tokensRequest.get(), &serversdk::Request::finished, errorHandler); connect(tokensRequest.get(), &serversdk::Request::finished, [=](){ saveUpdatedTokens(data, tokensRequest->responseData()); emit partOfDataReady(data); }); - auto deletedTokensRequest = getTokensDeleted(time); + auto deletedTokensRequest = getTokensDeleted(timestamp); connect(deletedTokensRequest.get(), &serversdk::Request::finished, errorHandler); connect(deletedTokensRequest.get(), &serversdk::Request::finished, [=](){ saveDeletedTokens(data, deletedTokensRequest->responseData()); emit partOfDataReady(data); }); - auto subjectsRequest = getSubjectsUpdated(time); + auto subjectsRequest = getSubjectsUpdated(timestamp); connect(subjectsRequest.get(), &serversdk::Request::finished, errorHandler); connect(subjectsRequest.get(), &serversdk::Request::finished, [=](){ saveUpdatedSubjects(data, subjectsRequest->responseData()); emit partOfDataReady(data); }); - auto deletedSubjectsRequest = getSubjectsDeleted(time); + auto deletedSubjectsRequest = getSubjectsDeleted(timestamp); connect(deletedSubjectsRequest.get(), &serversdk::Request::finished, errorHandler); connect(deletedSubjectsRequest.get(), &serversdk::Request::finished, [=](){ saveDeletedSubjects(data, deletedSubjectsRequest->responseData()); @@ -107,14 +131,14 @@ std::shared_ptr ColnodAPI::requestLogin() { Q_ASSERT(mReqMan); LoginResp login; - login.set_username(userName()); + login.setUsername(userName()); // password QString encodedPassword = QCryptographicHash::hash( password().toLocal8Bit(), // -> bytearray QCryptographicHash::Sha512 // SHA 512 ).toHex().toUpper(); - login.set_password(encodedPassword); + login.setPassword(encodedPassword); auto loginRequest = composeRequest("/AaaManager/authSession", login.jsonData()); loginRequest->setUserName(login.username()); @@ -208,10 +232,7 @@ std::shared_ptr ColnodAPI::composeRequest(const QString &end bool ColnodAPI::checkRequest(const std::shared_ptr &request) { - if (request->status() == 200) { - return true; - } - return false; + return request->status() == 200; } bool ColnodAPI::isValid() @@ -236,7 +257,7 @@ qint64 ColnodAPI::lastSync() const void ColnodAPI::setlastSync(const qint64 &lastSync) { - mLastSync.set_timestamp(lastSync); + mLastSync.setTimestamp(lastSync); } @@ -250,7 +271,7 @@ void ColnodAPI::setUserName(const QString &userName) mUserName = userName; } -void ColnodAPI::areDataReady(std::shared_ptr data) +void ColnodAPI::areDataReady(const std::shared_ptr& data) { if (++amountData == 4) { @@ -273,12 +294,12 @@ void ColnodAPI::isDataProcessFinished() } } -void ColnodAPI::saveUpdatedTokens(std::shared_ptr data, const QByteArray &responseData) +void ColnodAPI::saveUpdatedTokens(const std::shared_ptr& data, const QByteArray &responseData) { data->loadTokensFromJson(responseData); } -void ColnodAPI::saveDeletedTokens(std::shared_ptr data, const QByteArray &responseData) +void ColnodAPI::saveDeletedTokens(const std::shared_ptr& data, const QByteArray &responseData) { QJsonDocument doc = QJsonDocument::fromJson(responseData); QJsonArray items = doc.object()["items"].toArray(); @@ -287,12 +308,12 @@ void ColnodAPI::saveDeletedTokens(std::shared_ptr data, const QByteArray & } } -void ColnodAPI::saveUpdatedSubjects(std::shared_ptr data, const QByteArray &responseData) +void ColnodAPI::saveUpdatedSubjects(const std::shared_ptr& data, const QByteArray &responseData) { data->loadSubjectsFormJson(responseData); } -void ColnodAPI::saveDeletedSubjects(std::shared_ptr data, const QByteArray &responseData) +void ColnodAPI::saveDeletedSubjects(const std::shared_ptr& data, const QByteArray &responseData) { QJsonDocument doc = QJsonDocument::fromJson(responseData); QJsonArray items = doc.object()["items"].toArray(); diff --git a/src/colnod/colnodapi.h b/src/colnod/colnodapi.h index 68ed360..859f2fc 100644 --- a/src/colnod/colnodapi.h +++ b/src/colnod/colnodapi.h @@ -10,31 +10,24 @@ Q_DECLARE_LOGGING_CATEGORY(colnod) class LoginResp : public serversdk::DatabaseTable { Q_OBJECT - TABLE_PROPERTY(QString, username, set_username, "") - TABLE_PROPERTY(QString, password, set_password, "") + TABLE_PROPERTY(QString, username, setUsername, "") + TABLE_PROPERTY(QString, password, setPassword, "") }; class AuthToken : public serversdk::DatabaseTable { Q_OBJECT - TABLE_PROPERTY(QString, id, set_id, QString()) + TABLE_PROPERTY(QString, id, setId, QString()) }; class Timestamp : public serversdk::DatabaseTable { Q_OBJECT - TABLE_PROPERTY(qint64, timestamp, set_timestamp, 0) + TABLE_PROPERTY(qint64, timestamp, setTimestamp, 0) public: Timestamp() = default; - explicit Timestamp(const Timestamp &time) + + void setTimestamp(QDateTime time) { - this->set_timestamp(time.timestamp()); - } - explicit Timestamp(qint64 time) - { - set_timestamp(time); - } - explicit Timestamp(QDateTime time) - { - set_timestamp(time.toMSecsSinceEpoch()); + setTimestamp(time.toMSecsSinceEpoch()); } QString toDateString() @@ -48,9 +41,9 @@ class ColnodAPI: public QObject Q_OBJECT public: ColnodAPI() = default; - ColnodAPI(serversdk::RequestManager* reqManconst, const bool &dryMode = false, const QString &userName = "spaceti", const QString &password = "spaceti1"); + ColnodAPI(serversdk::RequestManager* reqManconst, const bool &dryMode = false, QString userName = "spaceti", QString password = "spaceti1"); - void downloadData(const Timestamp &time); + void downloadData(const qint64 &time); void processData(const std::shared_ptr &data); // New approach @@ -76,14 +69,14 @@ public: bool isValid(); private: - Q_SLOT void areDataReady(std::shared_ptr data); + Q_SLOT void areDataReady(const std::shared_ptr& data); Q_SLOT void isDataProcessFinished(); private: - void saveUpdatedTokens(std::shared_ptr data, const QByteArray &responseData); - void saveUpdatedSubjects(std::shared_ptr data, const QByteArray &responseData); - void saveDeletedTokens(std::shared_ptr data, const QByteArray &responseData); - void saveDeletedSubjects(std::shared_ptr data, const QByteArray &responseData); + void saveUpdatedTokens(const std::shared_ptr& data, const QByteArray &responseData); + void saveUpdatedSubjects(const std::shared_ptr& data, const QByteArray &responseData); + void saveDeletedTokens(const std::shared_ptr& data, const QByteArray &responseData); + void saveDeletedSubjects(const std::shared_ptr& data, const QByteArray &responseData); void newData(); diff --git a/src/colnod/data.h b/src/colnod/data.h index 340c9eb..868bc03 100644 --- a/src/colnod/data.h +++ b/src/colnod/data.h @@ -1,5 +1,7 @@ #pragma once +#include + #include "token.h" #include "subject.h" @@ -13,7 +15,7 @@ constexpr auto tab = "\t"; class Message { public: - static QString massageTemplate(QString action, QString item, QString source, QString itemInfo) + static QString massageTemplate(const QString& action, const QString& item, const QString& source, const QString& itemInfo) { return action + " " + item + " in " + source + ", " + item + ": '" + itemInfo + "'"; } @@ -37,3 +39,16 @@ public: QList deletedSubjects; }; +struct PreparedData +{ +public: + PreparedData() = default; + PreparedData(const std::shared_ptr &database, const std::shared_ptr &colnod) + :databaseData(std::move(database)), colnodData(std::move(colnod)) + { + } + + // TODO: make unique_ptr ( neeed because of ColnodAPI ) + std::shared_ptr databaseData; + std::shared_ptr colnodData; +}; diff --git a/src/colnod/databaseapi.cpp b/src/colnod/databaseapi.cpp index 7c4b695..25d23db 100644 --- a/src/colnod/databaseapi.cpp +++ b/src/colnod/databaseapi.cpp @@ -12,7 +12,7 @@ DatabaseAPI::DatabaseAPI(serversdk::DatabaseManager *dbMan, bool dryMode) mDbMan->setDryMode(dryMode); } -bool DatabaseAPI::open(QString host, QString name, QString user, QString password) +bool DatabaseAPI::open(const QString& host, const QString& name, const QString& user, const QString& password) { return mDbMan->openPostgres(host, name, user, password); } @@ -118,7 +118,7 @@ void DatabaseAPI::setLastSync(qint64 timestamp) } } -void DatabaseAPI::setLastUpdate(const QHash> &tokens) +void DatabaseAPI::reupdateUpdated(const QHash> &tokens) { auto query = mDbMan->createQuery(); for (const auto &token : tokens){ @@ -135,7 +135,8 @@ void DatabaseAPI::setLastUpdate(const QHash> &to qInfo(database) << equals; } -void DatabaseAPI::setLastUpdate(const QList ids) + +void DatabaseAPI::reupdateDeleted(const QList& ids) { auto query = mDbMan->createQuery(); for (const auto &id : ids){ @@ -148,7 +149,6 @@ void DatabaseAPI::setLastUpdate(const QList ids) } } } - } qint64 DatabaseAPI::getNow() @@ -193,14 +193,6 @@ bool DatabaseAPI::uploadTokens(const QHash> &tok for (const auto &token : tokens) { // Get token - QSqlQuery query; - - - - - - - if (exists(token->id(), "colnod_token")) { query = utils->updateTable(token.get(), "colnod_token"); qInfo(database).noquote() << Message::massageTemplate("Updating", "token", "database", token->name()); @@ -299,10 +291,7 @@ bool DatabaseAPI::isAlreadyDeleted(const QString &id) qWarning(database) << "Token deleted in colnod doesn't exists in db, will be noted as already deleted in stats"; return true; } - if (token->deleted()) - return true; - return false; - + return token->deleted(); } bool DatabaseAPI::isValid() diff --git a/src/colnod/databaseapi.h b/src/colnod/databaseapi.h index 34b125d..dfb48c1 100644 --- a/src/colnod/databaseapi.h +++ b/src/colnod/databaseapi.h @@ -20,10 +20,10 @@ class DatabaseAPI : public QObject public: explicit DatabaseAPI(serversdk::DatabaseManager *dbMan, bool dryMode); - bool open(QString host = "192.168.1.94", - QString name = "deloitte", - QString user = "postgres", - QString password = "34rjkciea12"); + bool open(const QString& host = "192.168.1.94", + const QString& name = "deloitte", + const QString& user = "postgres", + const QString& password = "34rjkciea12"); bool executeQuery(QSqlQuery &query, const QString &statement = QString()); @@ -31,8 +31,8 @@ public: bool processData(const std::shared_ptr &data); qint64 getLastSync(); void setLastSync(qint64 timestamp); - void setLastUpdate(const QHash > &tokens); - void setLastUpdate(const QList ids); + void reupdateUpdated(const QHash > &tokens); + void reupdateDeleted(const QList& ids); qint64 getNow(); void initNewTokens(); @@ -48,13 +48,14 @@ public: bool isValid(); + qint16 getNumOfTokensOfSubject(const QString &id); + QString getSubjectNameByTokenId(const QString &tokenId); + private: int exists(const QString &id, const QString &tableName); QSqlQuery getUpdatedTokens(); std::shared_ptr composeData(const QSqlQuery &updatedQuery, QSqlQuery &deletedQuery); - qint16 getNumOfTokensOfSubject(const QString &id); - QString getSubjectNameByTokenId(const QString &tokenId); signals: void downloadDataFinished(std::shared_ptr = std::shared_ptr()); diff --git a/src/colnod/syncmanager.cpp b/src/colnod/syncmanager.cpp index 4020c02..9b3c909 100644 --- a/src/colnod/syncmanager.cpp +++ b/src/colnod/syncmanager.cpp @@ -13,9 +13,6 @@ SyncManager::SyncManager(bool dryMode) bool SyncManager::databaseInitialization() { database = std::make_shared(&dbMan, mDryMode); -// connect(database.get(), &DatabaseAPI::error, [=](){ -// QCoreApplication::exit(1); -// }); // Open database if (!database->open()) { @@ -31,7 +28,7 @@ bool SyncManager::databaseInitialization() } // here -> nothing can fail - mlastSync.set_timestamp(database->getLastSync()); + mlastSync.setTimestamp(database->getLastSync()); colnod = std::make_shared(&reqMan, mDryMode); return true; } @@ -49,9 +46,11 @@ bool SyncManager::startSynchronization() qInfo(syncmanager) << equals; qInfo(syncmanager) << "SYNCHRONIZATION BEGIN"; - // TODO: check error - database->initNewTokens(); - + 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(); @@ -61,85 +60,187 @@ bool SyncManager::startSynchronization() } // 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; - colnod->downloadData(Timestamp(syncBegin)); - connect(colnod.get(), &ColnodAPI::downloadDataFinished, [=](std::shared_ptr data) { - if (!data) { + if (!colnodData) { qCritical(syncmanager) << "Not possible to get correct data from colnod"; + QCoreApplication::exit(-1); return; } + // NOW I SHOULD HAVE VALID DATA FROM COLNOD - // 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(); + 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; - 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, [=](){ + 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; - updateLastUpdate(syncBegin); - connect(this, &SyncManager::syncFinished, [=](){ - if (database->isValid() && colnod->isValid()) { - database->setLastSync(syncBegin); + // 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; } - else { + 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).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"; + }); - - processData(); }); + return true; + } -bool SyncManager::downloadData() +std::unique_ptr SyncManager::prepareDataNew(const std::shared_ptr &databaseData, const std::shared_ptr &colnodData) { - bool res = true; + auto preparedColnodData = std::make_shared(); + auto preparedDatabaseData = std::make_shared(); + + QList conflicts; + QList alreadySynced; + qInfo(syncmanager) << equals; - qInfo(syncmanager) << "DOWNLOADING DATA -> BEGIN"; + qInfo(syncmanager) << "PREPARING DATA -> BEGIN"; qInfo(syncmanager) << dash; - qInfo(syncmanager).noquote() << "Modified data since last synchronization (" << mlastSync.toDateString() << "):"; + qInfo(syncmanager) << "Resolve conflicts ( changes on same item on both Colnod and DB ):"; qInfo(syncmanager) << dash; - connect(colnod.get(), &ColnodAPI::downloadDataFinished, this, &SyncManager::saveColnodData); - //connect(database.get(), &DatabaseAPI::downloadDataFinished, this, &SyncManager::saveDatabaseData); - //database->downloadData(); - //colnod->downloadData(); - return res; + 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; } -// finds conflicts -> changes on both sides (colnod and DB), apply newer change -bool SyncManager::prepareData() +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"; @@ -153,17 +254,17 @@ bool SyncManager::prepareData() // TODO: why copy data? - colnodDataToProcess = std::make_shared(*colnodData); - databaseDataToProcess = std::make_shared(*databaseData); + 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 : colnodDataToProcess->updatedTokens) { - if (databaseDataToProcess->updatedTokens.contains(colnodToken->id())) { + for (const auto &colnodToken : cloneColnodDataToProcess->updatedTokens) { + if (cloneDatabaseDataToProcess->updatedTokens.contains(colnodToken->id())) { // updated in db later - if (databaseDataToProcess->updatedTokens.value(colnodToken->id()) > colnodToken) { + if (cloneDatabaseDataToProcess->updatedTokens.value(colnodToken->id()) > colnodToken) { conflicts.append(colnodToken->id()); } continue; @@ -176,9 +277,9 @@ bool SyncManager::prepareData() } // remove tokens which should be not updated for (const auto &id : conflicts) - colnodDataToProcess->updatedTokens.remove(id); + cloneColnodDataToProcess->updatedTokens.remove(id); for (const auto &id : alreadySynced) - colnodDataToProcess->updatedTokens.remove(id); + cloneColnodDataToProcess->updatedTokens.remove(id); qInfo(syncmanager) << tab << "-> COLNOD DATA - CHANGES:"; qInfo(syncmanager) << tab << tab << conflicts.size() << "TOKENS WERE ADDED/UPDATED IN DATABASE TOO"; @@ -189,13 +290,13 @@ bool SyncManager::prepareData() 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) { + for (const auto &id : cloneColnodDataToProcess->deletedTokens) { if (database->isAlreadyDeleted(id)) { conflicts.append(id); } } for (const auto &id : conflicts) { - colnodDataToProcess->deletedTokens.removeOne(id); + cloneColnodDataToProcess->deletedTokens.removeOne(id); } qInfo(syncmanager) << tab << tab << conflicts.size() << "TOKENS WERE ALREADY DELETED"; @@ -204,9 +305,9 @@ bool SyncManager::prepareData() 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())) + for (const auto &databaseToken : cloneDatabaseDataToProcess->updatedTokens) { + if (cloneColnodDataToProcess->updatedTokens.contains(databaseToken->id())) { + if (databaseToken < cloneColnodDataToProcess->updatedTokens.value(databaseToken->id())) conflicts.append(databaseToken->id()); } else { @@ -218,7 +319,7 @@ bool SyncManager::prepareData() } } for (const auto &id : conflicts) - colnodDataToProcess->updatedTokens.remove(id); + cloneColnodDataToProcess->updatedTokens.remove(id); qInfo(syncmanager) << tab << "-> DATABASE DATA - CHANGES:"; qInfo(syncmanager) << tab << tab << conflicts.size() << "TOKENS WERE ADDED/UPDATED IN IN COLNOD TOO"; @@ -226,54 +327,11 @@ bool SyncManager::prepareData() 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 == 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(); + + // Prepare data + auto preparedData = std::make_unique(); + preparedData->colnodData = cloneColnodDataToProcess; + preparedData->databaseData = cloneDatabaseDataToProcess; + + return preparedData; } diff --git a/src/colnod/syncmanager.h b/src/colnod/syncmanager.h index 07c92e0..2bc2f3e 100644 --- a/src/colnod/syncmanager.h +++ b/src/colnod/syncmanager.h @@ -8,27 +8,32 @@ Q_DECLARE_LOGGING_CATEGORY(syncmanager) class SyncManager : public QObject { Q_OBJECT + friend class SyncManagerTest; public: explicit SyncManager(bool dryMode); bool databaseInitialization(); bool startSynchronization(); + // New approach private: - bool downloadData(); - bool prepareData(); - bool processData(); + /** + * @brief Preapre data to be pushed to database and colnod from actual downloaded data + * @param data from database + * @param data from colnod + * @return prepared data to be pushed into colnod and db + * + * @todo rename when removed old aproach + */ + std::unique_ptr prepareDataNew(const std::shared_ptr &databaseData, const std::shared_ptr &colnodData); + std::unique_ptr prepareDataUsindOld(const std::shared_ptr &databaseData, const std::shared_ptr &colnodData); + +private: + void initNewToken(const std::shared_ptr& token); + void updateLastUpdate(qint64 syncBegin); - -private: - Q_SLOT void saveColnodData(const std::shared_ptr &data); - Q_SLOT void saveDatabaseData(const std::shared_ptr &data); - Q_SLOT void areDataReady(); - signals: - void dataReady(); - void dataProcessFinished(); void syncFinished(); void error(); @@ -40,14 +45,5 @@ private: serversdk::DatabaseManager dbMan; Timestamp mlastSync; - std::shared_ptr colnodData; - std::shared_ptr databaseData; - - std::shared_ptr colnodDataToProcess; - std::shared_ptr databaseDataToProcess; - - int dataAmount = 0; bool mDryMode; - - std::function onError = nullptr; }; diff --git a/test/auto/colnod/tst_colnodapi.cpp b/test/auto/colnod/tst_colnodapi.cpp index dedd554..9ad3914 100644 --- a/test/auto/colnod/tst_colnodapi.cpp +++ b/test/auto/colnod/tst_colnodapi.cpp @@ -7,6 +7,7 @@ Q_DECLARE_METATYPE(std::shared_ptr) Q_DECLARE_METATYPE(std::shared_ptr); + class ColnodTest : public QObject { Q_OBJECT @@ -14,22 +15,20 @@ public: ColnodTest() { qRegisterMetaType>(); - qRegisterMetaType>(); + qRegisterMetaType>("std::shared_ptr"); } private slots: void login(); void getTokens(); void download_data_test(); + void download_data_test_now(); void download_data_fail_test(); private: QString getLoginToken(ColnodAPI &colnod); }; - - - void ColnodTest::login() { serversdk::RequestManager reqMan; @@ -52,35 +51,41 @@ void ColnodTest::getTokens() QSignalSpy spy(login.get(), &serversdk::Request::finished); spy.wait(); QString token = getLoginToken(colnod); - Timestamp time(QDateTime::currentMSecsSinceEpoch()); + Timestamp time; + time.setTimestamp(QDateTime::currentMSecsSinceEpoch()); colnod.getTokensUpdated(time); } void ColnodTest::download_data_test() { serversdk::RequestManager reqMan; + //reqMan.setIgnoreSsl(true); ColnodAPI colnod(&reqMan); - { - QSignalSpy spy(&colnod, &ColnodAPI::downloadDataFinished); - colnod.downloadData(Timestamp(1570190399259)); - QVERIFY(spy.wait()); - auto data = qvariant_cast>(spy.at(0).at(0)); - QVERIFY(data); + QSignalSpy spy(&colnod, &ColnodAPI::downloadDataFinished); + QVERIFY(spy.isValid()); + colnod.downloadData(1570190399259); + QVERIFY(spy.wait()); - QVERIFY(data->updatedTokens.size() > 0); - QVERIFY(data->updatedTokens.contains("5e2f1260-da1c-441f-9644-24dfe3b97b2a")); - } - { - Timestamp time(QDateTime::currentMSecsSinceEpoch()); - colnod.downloadData(time); - QSignalSpy spy(&colnod, &ColnodAPI::downloadDataFinished); - QVERIFY(spy.wait()); - auto data = qvariant_cast>(spy.at(0).at(0)); - QVERIFY(data); + auto data = qvariant_cast>(spy.at(0).at(0)); + QVERIFY(data); - QVERIFY(data->updatedTokens.size() == 0); - } + QVERIFY(! data->updatedTokens.isEmpty()); + QVERIFY(data->updatedTokens.contains("5e2f1260-da1c-441f-9644-24dfe3b97b2a")); +} + +void ColnodTest::download_data_test_now() +{ + serversdk::RequestManager reqMan; + ColnodAPI colnod(&reqMan); + + colnod.downloadData(QDateTime::currentMSecsSinceEpoch()); + QSignalSpy spy(&colnod, &ColnodAPI::downloadDataFinished); + QVERIFY(spy.wait()); + auto data = qvariant_cast>(spy.at(0).at(0)); + QVERIFY(data); + + QVERIFY(data->updatedTokens.isEmpty()); } void ColnodTest::download_data_fail_test() @@ -88,17 +93,14 @@ void ColnodTest::download_data_fail_test() serversdk::RequestManager reqMan; ColnodAPI colnod(&reqMan, false, "spaceti", "wrong password"); - Timestamp time(0); QSignalSpy spy(&colnod, &ColnodAPI::downloadDataFinished); - colnod.downloadData(time); + colnod.downloadData(0); QVERIFY(spy.wait()); auto data = qvariant_cast>(spy.at(0).at(0)); QVERIFY(!data); - - } QString ColnodTest::getLoginToken(ColnodAPI &colnod) diff --git a/test/auto/syncmanager/tst_syncmanager.cpp b/test/auto/syncmanager/tst_syncmanager.cpp index ad97eff..ff45e89 100644 --- a/test/auto/syncmanager/tst_syncmanager.cpp +++ b/test/auto/syncmanager/tst_syncmanager.cpp @@ -6,18 +6,149 @@ class SyncManagerTest : public QObject { Q_OBJECT +private: + std::shared_ptr tokenFromString(const QString &text); private slots: + void tokenFromString_test(); void sync_init_test(); + void prepareData_test(); + void prepareData_tokens(); + void prepareData_tokens_data(); }; +std::shared_ptr SyncManagerTest::tokenFromString(const QString &text) +{ + auto split = text.split(","); + if (split.count() != 2) + return nullptr; + + auto token = std::make_shared(); + token->setId(split.first()); + token->setDescription("mobile"); + token->setLastUpdate(QDateTime::fromString(split.last(), "yyyyMMdd:HHmmss")); + return token; +} + +void SyncManagerTest::tokenFromString_test() +{ + auto token = tokenFromString("id1,20191010:232300"); + QCOMPARE(token->id(), QStringLiteral("id1")); + QCOMPARE(token->lastUpdate(), QDateTime::fromString("20191010:232300", "yyyyMMdd:HHmmss")); +} + void SyncManagerTest::sync_init_test() { SyncManager sync(true); - QVERIFY(sync.databaseInitialization()); } + +void SyncManagerTest::prepareData_test() +{ + SyncManager sync(true); + QVERIFY(sync.databaseInitialization()); + auto dbData = std::make_shared(); + auto colnodData = std::make_shared(); + + auto token = std::make_shared(); + token->setDescription("mobile"); + token->setId("id1"); + dbData->updatedTokens.insert("id1", token); + + auto oldPreparedData = sync.prepareDataUsindOld(dbData, colnodData); + auto newPreparedData = sync.prepareDataNew(dbData, colnodData); + QVERIFY(oldPreparedData->databaseData->updatedTokens.contains("id1")); + QVERIFY(newPreparedData->databaseData->updatedTokens.contains("id1")); + QVERIFY(oldPreparedData->databaseData->updatedSubjects.isEmpty()); + QVERIFY(newPreparedData->databaseData->updatedSubjects.isEmpty()); + QVERIFY(oldPreparedData->databaseData->deletedTokens.isEmpty()); + QVERIFY(newPreparedData->databaseData->deletedTokens.isEmpty()); + QVERIFY(oldPreparedData->databaseData->deletedSubjects.isEmpty()); + QVERIFY(newPreparedData->databaseData->deletedSubjects.isEmpty()); +} + +void SyncManagerTest::prepareData_tokens() +{ + SyncManager sync(true); + QVERIFY(sync.databaseInitialization()); + + + + QFETCH(QString, db_token_updated); + QFETCH(QString, db_token_deleted); + QFETCH(QString, colnod_token_updated); + QFETCH(QString, colnod_token_deleted); + QFETCH(QString, expected_db_tokens_toupdate); + QFETCH(QString, expected_db_tokens_todelete); + QFETCH(QString, expected_colnod_tokens_toupdate); + QFETCH(QString, expected_colnod_tokens_todelete); + + // fake data + auto dbData = std::make_shared(); + auto colnodData = std::make_shared(); + + // Database + auto utoken = tokenFromString(db_token_updated); + if ( utoken ) + dbData->updatedTokens.insert(utoken->id(), utoken); + auto dtoken = tokenFromString(db_token_deleted); + if ( dtoken ) + dbData->deletedTokens.append(dtoken->id()); + + // Colnod + auto colnod_utoken = tokenFromString(colnod_token_updated); + if ( colnod_utoken ) + colnodData->updatedTokens.insert(colnod_utoken->id(), colnod_utoken); + auto colnod_dtoken = tokenFromString(colnod_token_deleted); + if ( colnod_dtoken ) + colnodData->deletedTokens.append(colnod_dtoken->id()); + + auto oldPreparedData = sync.prepareDataUsindOld(dbData, colnodData); + auto newPreparedData = sync.prepareDataNew(dbData, colnodData); + + QVERIFY(newPreparedData->databaseData->updatedTokens.contains(expected_db_tokens_toupdate)); + QVERIFY(newPreparedData->databaseData->deletedTokens.contains(expected_db_tokens_todelete)); + QVERIFY(newPreparedData->colnodData->updatedTokens.contains(expected_colnod_tokens_toupdate)); + QVERIFY(newPreparedData->colnodData->deletedTokens.contains(expected_colnod_tokens_todelete)); + + QVERIFY(oldPreparedData->databaseData->updatedTokens.contains(expected_db_tokens_toupdate)); + QVERIFY(oldPreparedData->databaseData->deletedTokens.contains(expected_db_tokens_todelete)); + QVERIFY(oldPreparedData->colnodData->updatedTokens.contains(expected_colnod_tokens_toupdate)); + QVERIFY(oldPreparedData->colnodData->deletedTokens.contains(expected_colnod_tokens_todelete)); + +} + +void SyncManagerTest::prepareData_tokens_data() +{ + QTest::addColumn("db_token_updated"); + QTest::addColumn("db_token_deleted"); + QTest::addColumn("colnod_token_updated"); + QTest::addColumn("colnod_token_deleted"); + QTest::addColumn("expected_db_tokens_toupdate"); + QTest::addColumn("expected_db_tokens_todelete"); + QTest::addColumn("expected_colnod_tokens_toupdate"); + QTest::addColumn("expected_colnod_tokens_todelete"); + + // in: db 1 new token + // out: 1 db token must be updated in colnod + QTest::newRow("db 1 update") << "id1,20191010:23:23:00" << "" + << "" << "" + // expected database: toupdate << todelete + << "id1" << "" + // expected colnod: toupdate << todelete + << "" << ""; + // in: id1 in database new/updated + // in: id1 deleted in colnod + // out: 1 colnod token must be deleted from database + QTest::newRow("delete 1 existing token in database") << "id1,20191010:23:23:00" << "" + << "" << "id1,20191010:23:23:00" + // expected database: toupdate << todelete + << "" << "id1" + // expected colnod: toupdate << todelete + << "" << ""; +} + QTEST_MAIN(SyncManagerTest) #include "tst_syncmanager.moc"