Index: sources/cloudsync/CloudSyncController.cpp =================================================================== diff -u -r27cc308ff5113a9386899d3c8f8b29962a8498e1 -r928a1ed5d70358fc00c49df45725f989d16c370e --- sources/cloudsync/CloudSyncController.cpp (.../CloudSyncController.cpp) (revision 27cc308ff5113a9386899d3c8f8b29962a8498e1) +++ sources/cloudsync/CloudSyncController.cpp (.../CloudSyncController.cpp) (revision 928a1ed5d70358fc00c49df45725f989d16c370e) @@ -1,13 +1,13 @@ /*! * - * Copyright (c) 2021-2022 Diality Inc. - All Rights Reserved. + * Copyright (c) 2021-2023 Diality Inc. - All Rights Reserved. * \copyright * THIS CODE MAY NOT BE COPIED OR REPRODUCED IN ANY FORM, IN PART OR IN * WHOLE, WITHOUT THE EXPLICIT PERMISSION OF THE COPYRIGHT OWNER. * * \file CloudSyncController.cpp * \author (last) Behrouz NematiPour - * \date (last) 15-Dec-2021 + * \date (last) 20-Dec-2022 * \author (original) Behrouz NematiPour * \date (original) 14-Oct-2021 * @@ -18,23 +18,23 @@ #include // Project -#include "MainTimer.h" #include "MessageDispatcher.h" -#include "GuiController.h" +#include "ApplicationController.h" #include "DeviceController.h" #include "FileHandler.h" -#include "crc.h" #include "TreatmentLog.h" +#include "crc.h" +SINGLETON_DISABLE(CloudSyncController) + /*! * \brief CloudSyncController::CloudSyncController * \details Constructor * \param parent - QObject parent owner object. * Qt handles the children destruction by their parent objects life-cycle. */ CloudSyncController::CloudSyncController(QObject *parent) : QObject(parent) { - checkDate(); - sendUIBuff("Ready"); + testWatchBuffDate(); startTimer(_interval); } @@ -74,11 +74,11 @@ */ void CloudSyncController::quit() { - // coco begin validated: CloudSync termination is not correctly done in coco!!! + // disabled coco begin validated: CloudSync termination is not correctly done in coco!!! // it has been tested and works perfectly fine in normal run. quitThread(); // validated } -// coco end +// disabled coco end /*! * \brief CloudSyncController::initConnections @@ -87,12 +87,18 @@ */ void CloudSyncController::initConnections() { + if ( ! gDisableCloudSyncFailStop ) { + SINGLETON_DISABLE_CONNECT(didPOSTCloudSync) + } + connect(&_DeviceController , SIGNAL(didWatchFileChange (const QString &)), this , SLOT( onWatchFileChange (const QString &))); - connect(&_MessageDispatcher , SIGNAL(didActionReceive (GuiActionType,const QVariantList &)), - this , SLOT( onActionReceive (GuiActionType,const QVariantList &))); + connect(&_MessageDispatcher , SIGNAL(didActionReceive (GuiActionType , const QVariantList &)), + this , SLOT( onActionReceive (GuiActionType , const QVariantList &))); connect(&_TreatmentLog , SIGNAL(didTreatmentLogSave(const QString &, const QString &, const QString &)), this , SLOT( onTreatmentLogSave(const QString &, const QString &, const QString &))); + connect(this , SIGNAL(didInitComplete ()), + this , SLOT( onInitComplete ()),Qt::QueuedConnection); // it has to be queued connection, don't remove it. } /*! @@ -120,15 +126,15 @@ */ void CloudSyncController::quitThread() { - // coco begin validated: CloudSync termination is not correctly done in coco!!! + // disabled coco begin validated: CloudSync termination is not correctly done in coco!!! // it has been tested and works perfectly fine in normal run. if ( ! _thread ) return; // runs in thread moveToThread(qApp->thread()); // validated } -// coco end +// disabled coco end /*! * \brief CloudSyncController::timerEvent @@ -140,17 +146,45 @@ { // TODO: touch the inp file to as a check-in for CloudSync to know we are up // a simple touch or a check-in message? - checkDate(); + testWatchBuffDate(); + testDeviceInfoWait(); + testPendingTxReports(); } /*! + * \brief CloudSyncController::event + * \details The override method of QObject event to handle the ThreadChange. + * \param vEvent - the QObject event + * \return true if the event e was recognized and processed + */ +bool CloudSyncController::event(QEvent *vEvent) { + if (vEvent->type() == QEvent::ThreadChange) { + emit didInitComplete(); + } + // Make sure the rest of events are handled + return QObject::event(vEvent); +} + +/*! + * \brief CloudSyncController::onInitComplete + * \details The slot to be called when the CloudSync initialization is complete, to call the testDeviceRegister. + */ +void CloudSyncController::onInitComplete() { + testDeviceRegister(); +} + +/*! * \brief CloudSyncController::onWatchFileChange * \details This slot will be called when the Device Controller identifies any changes in the watched files. * \param vFile - watched file * \note The DeviceController will emit the signal on any watched file update, it's up to the CloudSyncController to filter the result. */ void CloudSyncController::onWatchFileChange(const QString &vFile) { + // TODO: It may improve the performance of the code to make it more specific. + // Meaning, Device controller has only one file watcher and it will call all the slots of all the classes watching for a file change. + // then ignore what is not important, but it may effect, the call queue and the threading performance. + // Later investigation. if ( vFile != _date_out_File ) return; // ignore unwanted file updates. QString content; Storage::FileHandler::read(vFile, content); @@ -166,7 +200,7 @@ { bool ok = Storage::FileHandler::makeFolder(_location); QVariantList args {}; - Errors_Enum error = eError_OK; + Errors_Enum error = eError_Unknown; if ( ! ok ) { error = eError_LogFolder; args = {{ _location }}; ok = false; goto lErr; } _date_out_File = _location + // The location @@ -190,32 +224,52 @@ { bool ok = true; QVariantList args {}; - Errors_Enum error = eError_OK; + Errors_Enum error = eError_Unknown; - QStringList lines = vContent.split('\n',QString::SkipEmptyParts); - QString buffer = lines.last(); - LOG_DEBUG(QString("CloudSync Message received [%1]").arg(buffer)); + QStringList lines; + QString buffer; - int index = -1; - Message message ; + int index = -1; + quint32 count = 0; - QStringList items = buffer.split(_separator); - int count = items.count(); + Message message; + QStringList items; + quint32 id; + if ( vContent.isEmpty() ) { error = eError_OutFileEmpty; ok = false; goto lErr; } + + lines = vContent.split('\n',Qt::SkipEmptyParts); + buffer = lines.last(); + // DEBUG: LOG_DEBUG(QString("CS [%1]").arg(buffer)); + + + items = buffer.split(_separator); + count = items.count(); + // check the required message length - if ( count < eMessage_Count ) { error = eError_Count ; ok = false; goto lErr; } - index = eMessage_Timestamp ; message.timestamp = items[index].toInt (&ok); if (!ok) { error = eError_Timestamp ; ok = false; goto lErr; } - index = eMessage_Sequence ; message.sequence = items[index].toUInt(&ok); if (!ok) { error = eError_Sequence ; ok = false; goto lErr; } - index = eMessage_CRC ; message.crc = items[index].toUInt(&ok); if (!ok) { error = eError_CRC ; ok = false; goto lErr; } - index = eMessage_MessageID ; message.id = items[index].toInt (&ok); if (!ok) { error = eError_MessageID ; ok = false; goto lErr; } - index = eMessage_ParamCount; message.paramCount = items[index].toInt (&ok); if (!ok) { error = eError_ParamCount ; ok = false; goto lErr; } + if ( count < eMessage_Count ) { error = eError_HeaderCount ; ok = false; goto lErr; } + index = eMessage_Timestamp ; message.timestamp = items[index].toUInt(&ok); if (!ok) { error = eError_Timestamp ; ok = false; goto lErr; } + index = eMessage_Sequence ; message.sequence = items[index].toUInt(&ok); if (!ok) { error = eError_Sequence ; ok = false; goto lErr; } + index = eMessage_CRC ; message.crc = items[index].toUInt(&ok); if (!ok) { error = eError_CRC ; ok = false; goto lErr; } + index = eMessage_MessageID ; message.id = items[index].toInt (&ok); if (!ok) { error = eError_MessageID ; ok = false; goto lErr; } + index = eMessage_ParamCount; message.paramCount = items[index].toUInt(&ok); if (!ok) { error = eError_ParamCount ; ok = false; goto lErr; } // check the parameters count - if ( count - eMessage_Count < message.paramCount ) { error = eError_Parameter ; ok = false; goto lErr; } + if ( count - eMessage_Count != message.paramCount ) { error = eError_ParamMismatch ; ok = false; goto lErr; } + // check missing parameters + id = CS2UI(message.id); + if ( ! paramCount.contains(id)) paramCount[id] = 0; + if ( paramCount[id] != message.paramCount ) { error = eError_ParamMissing ; ok = false; goto lErr; } + + + // convert the message id and check its validity + message.id = CS2UI(static_cast(message.id)); + if ( eMessageID_Start > message.id || message.id > eMessageID_Count ) { error = eError_InvalidID ; ok = false; goto lErr; } + // getting the parameters - for ( int index = eMessage_Count; index < message.paramCount; index++ ) { - message.params.append( items[index] ); + for ( quint32 i = eMessage_Count; i < eMessage_Count + message.paramCount; i++ ) { + message.params.append( items[i] ); } vMessage = message; @@ -224,16 +278,21 @@ lErr: // building the error info for each error switch (error) { - case eError_Count : args = { count , eMessage_Count }; break; - case eError_Timestamp : args = { items[index].trimmed() }; break; - case eError_Sequence : args = { items[index].trimmed() }; break; - case eError_CRC : args = { items[index].trimmed() }; break; - case eError_MessageID : args = { items[index].trimmed() }; break; - case eError_ParamCount : args = { items[index].trimmed() }; break; - case eError_Parameter : args = { count - eMessage_Count , message.paramCount }; break; - default : break; + case eError_Unknown : args = { }; break; + case eError_OutFileEmpty : args = { }; break; + case eError_HeaderCount : args = { count , eMessage_Count }; break; + case eError_Timestamp : args = { items[index].trimmed() }; break; + case eError_Sequence : args = { items[index].trimmed() }; break; + case eError_CRC : args = { items[index].trimmed() }; break; + case eError_MessageID : args = { items[index].trimmed() }; break; + case eError_InvalidID : args = { items[index].trimmed() }; break; + case eError_ParamCount : args = { items[index].trimmed() }; break; + case eError_ParamMismatch : args = { count - eMessage_Count , message.paramCount }; break; + case eError_ParamMissing : args = { paramCount[message.id] , message.paramCount }; break; + // Interpreter function only handles the message parsing errors. So default can be used. + default : break; } - LOG_DEBUG(toText(error) + " " + toInfo(error, args)); + toLog(error, args); return false; } @@ -246,20 +305,35 @@ */ QString CloudSyncController::toText(CloudSyncController::Errors_Enum vErrorID) { - QString text; - if ( vErrorID == 0 ) return text; + QString text = tr( "CS Unknown Error" ) ; + if ( vErrorID == 0 ) return text; switch (vErrorID) { - case eError_Count : text = tr( "E,CS,Incorrect header" ) ; break; - case eError_Timestamp : text = tr( "E,CS,Incorrect timestamp" ) ; break; - case eError_Sequence : text = tr( "E,CS,Incorrect sequence" ) ; break; - case eError_CRC : text = tr( "E,CS,Incorrect CRC" ) ; break; - case eError_MessageID : text = tr( "E,CS,Incorrect ID" ) ; break; - case eError_ParamCount : text = tr( "E,CS,Incorrect parameter length" ) ; break; - case eError_Parameter : text = tr( "E,CS,Incorrect parameter count" ) ; break; - case eError_NoHistory : text = tr( "E,CS,No history available for the request" ) ; break; - case eError_LogFolder : text = tr( "E,CS,The log folder cannot be created." ) ; break; - case eError_LogFileInp : text = tr( "E,CS,Error writing to the input file." ) ; break; - default : text = tr( "E,CS,Unknown Error" ) ; break; + case eError_Unknown : /* "CS Unknown Error" */ ; break; + case eError_OutFileEmpty : text = tr( "CS Out buffer empty" ) ; break; + case eError_HeaderCount : text = tr( "CS Incorrect header" ) ; break; + case eError_Timestamp : text = tr( "CS Incorrect timestamp" ) ; break; + case eError_Sequence : text = tr( "CS Incorrect sequence" ) ; break; + case eError_CRC : text = tr( "CS Incorrect CRC" ) ; break; + case eError_MessageID : text = tr( "CS Incorrect ID" ) ; break; + case eError_ParamCount : text = tr( "CS Incorrect parameter count" ) ; break; + case eError_InvalidID : text = tr( "CS Invalid ID" ) ; break; + case eError_ParamMismatch : text = tr( "CS Mismatch parameter count" ) ; break; + case eError_ParamMissing : text = tr( "CS Missing parameter" ) ; break; + case eError_NoHistory : text = tr( "CS No history available" ) ; break; + case eError_Duplicate : text = tr( "CS Duplicate data" ) ; break; + case eError_LogFolder : text = tr( "CS The log folder cannot be created." ) ; break; + case eError_LogFileInp : text = tr( "CS Error writing to the input file." ) ; break; + case eError_CredentialFile : text = tr( "CS The credentials file does not exist." ) ; break; + case eError_CredentialMake : text = tr( "CS The credentials folder make failed." ) ; break; + case eError_CredentialCopy : text = tr( "CS The credentials file copy failed." ) ; break; + case eError_CredentialRemove: text = tr( "CS The credentials file remove failed." ) ; break; + case eError_CredentialEmpty : text = tr( "CS The credentials folder is empty." ) ; break; + case eError_TxCodeNoParam : text = tr( "CS No Treatment Code provided." ) ; break; + case eError_TxCodeEmpty : text = tr( "CS The provided Treatment Code is empty." ) ; break; + case eError_NotRegistered : text = tr( "CS Not Sent, Device not registered." ) ; break; + + case eError_Registration : text = tr( "CS The device registration failed." ) ; break; + case eError_TxReport : text = tr( "CS The treatment report delivery failed." ) ; break; } return text; } @@ -277,29 +351,63 @@ // It means it is designed to not throw out of bound error and just use "~" as a missing info argument. auto item = [=](uint i) { return vInfoItems.value(i,"~").toString(); }; - QString info = ""; + QString info = QString( "[%1]" ).arg( vErrorID ) ; switch (vErrorID) { - case eError_Count : info = QString( "[%1:%2/%3]" ).arg( vErrorID ).arg( item(0) ).arg( item(1) ) ; break; - case eError_Timestamp : info = QString( "[%1:%2]" ).arg( vErrorID ).arg( item(0) ) ; break; - case eError_Sequence : info = QString( "[%1:%2]" ).arg( vErrorID ).arg( item(0) ) ; break; - case eError_CRC : info = QString( "[%1:%2]" ).arg( vErrorID ).arg( item(0) ) ; break; - case eError_MessageID : info = QString( "[%1:%2]" ).arg( vErrorID ).arg( item(0) ) ; break; - case eError_ParamCount : info = QString( "[%1:%2]" ).arg( vErrorID ).arg( item(0) ) ; break; - case eError_Parameter : info = QString( "[%1:%2/%3]" ).arg( vErrorID ).arg( item(0) ).arg( item(1) ) ; break; - case eError_NoHistory : info = QString( "[%1:%2]" ).arg( vErrorID ).arg( item(0) ) ; break; - default : info = QString( "[%1]" ).arg( vErrorID ) ; break; + case eError_Unknown : ; break; + case eError_OutFileEmpty : ; break; + case eError_HeaderCount : info = QString( "[%1:%2/%3]" ).arg( vErrorID ).arg( item(0) ).arg( item(1) ) ; break; + case eError_Timestamp : info = QString( "[%1:%2]" ).arg( vErrorID ).arg( item(0) ) ; break; + case eError_Sequence : info = QString( "[%1:%2]" ).arg( vErrorID ).arg( item(0) ) ; break; + case eError_CRC : info = QString( "[%1:%2]" ).arg( vErrorID ).arg( item(0) ) ; break; + case eError_MessageID : info = QString( "[%1:%2]" ).arg( vErrorID ).arg( item(0) ) ; break; + case eError_InvalidID : info = QString( "[%1:%2]" ).arg( vErrorID ).arg( item(0) ) ; break; + case eError_ParamCount : info = QString( "[%1:%2]" ).arg( vErrorID ).arg( item(0) ) ; break; + case eError_ParamMismatch : info = QString( "[%1:%2/%3]" ).arg( vErrorID ).arg( item(0) ).arg( item(1) ) ; break; + case eError_ParamMissing : info = QString( "[%1:%2/%3]" ).arg( vErrorID ).arg( item(0) ).arg( item(1) ) ; break; + case eError_NoHistory : info = QString( "[%1:%2]" ).arg( vErrorID ).arg( item(0) ) ; break; + case eError_Duplicate : info = QString( "[%1:%2]" ).arg( vErrorID ).arg( item(0) ) ; break; + case eError_LogFolder : ; break; + case eError_LogFileInp : ; break; + case eError_CredentialFile : info = QString( "[%1:%2]" ).arg( vErrorID ).arg( item(0) ) ; break; + case eError_CredentialMake : info = QString( "[%1:%2]" ).arg( vErrorID ).arg( item(0) ) ; break; + case eError_CredentialCopy : info = QString( "[%1:%2]" ).arg( vErrorID ).arg( item(0) ) ; break; + case eError_CredentialRemove: info = QString( "[%1:%2]" ).arg( vErrorID ).arg( item(0) ) ; break; + case eError_CredentialEmpty : info = QString( "[%1:%2]" ).arg( vErrorID ).arg( item(0) ) ; break; + case eError_TxCodeNoParam : info = QString( "[%1:%2]" ).arg( vErrorID ).arg( item(0) ) ; break; + case eError_TxCodeEmpty : info = QString( "[%1:%2]" ).arg( vErrorID ).arg( item(0) ) ; break; + case eError_NotRegistered : info = QString( "[%1:%2]" ).arg( vErrorID ).arg( item(0) ) ; break; + case eError_Registration : info = QString( "[%1:%2]" ).arg( vErrorID ).arg( item(0) ) ; break; + case eError_TxReport : info = QString( "[%1:%2]" ).arg( vErrorID ).arg( item(0) ) ; break; } return info; } /*! + * \brief CloudSyncController::errorOut + * \details the function to send out the error message to the cloudsync inp buffer. + * \param vErrorID - the error id from CloudSyncController::Errors_Enum in range of 950 to 999 + * \param vInfoItems - + */ +void CloudSyncController::errorOut(CloudSyncController::Errors_Enum vErrorID, const QVariantList &vInfoItems) { + QString msg = QString::number(UI2CS(eMessageID_Error)); + QString len = QString::number(1 + vInfoItems.count()); + QString prm = QString::number(vErrorID); + QStringList buf = { msg, len, prm }; + for (const auto &item: vInfoItems) { + buf += item.toString(); + } + sendUIBuff(buf); +} + +/*! * \brief CloudSyncController::toLog * \details logs the error in debug mode (service log) * \param vErrorID - error id * \param vInfoItems - extra information */ void CloudSyncController::toLog(CloudSyncController::Errors_Enum vErrorID, const QVariantList &vInfoItems) { + errorOut(vErrorID, vInfoItems); LOG_DEBUG(toText(vErrorID) + " " + toInfo(vErrorID, vInfoItems)); } @@ -320,25 +428,23 @@ // TODO: messages have to have a sequence. // if the seq is duplicate it will be ignored. // seq, id, payload - // qDebug() << vContent; + // DEBUG: qDebug() << vContent; bool ok = true; Message message; if ( ! interpret(vContent, message) ) return false; // The error handling internal to interpret method. - switch (message.id) { - case eMessageID_DeviceState : ok = sendUIHistory(message.id); break; // The device state is stored in history but the other might be different so the switch is used. - default : break; - } + ok = sendMessage(message); + return ok; } /*! - * \brief CloudSyncController::checkDate + * \brief CloudSyncController::testWatchBuffDate * \details Checks the date and updated the watched file in case the date changed. */ -void CloudSyncController::checkDate() +void CloudSyncController::testWatchBuffDate() { - _datetime = QDateTime::currentDateTime(); + _datetime = QDateTime::currentDateTime().toUTC(); _secSinceEpoch = _datetime.toSecsSinceEpoch(); _timeFormatted = _datetime.toString(_timeFormat); QString dateFormatted = _datetime.toString(_dateFormat); @@ -350,31 +456,110 @@ } /*! + * \brief CloudSyncController::makeUIBuff + * \details make formatted UI buffer to send out. + * \param vMessage - The message id with destination added. 2k, 1K + * \return the formatted buffer + */ +QStringList CloudSyncController::makeUIBuff(const qint32 vMessageID, const QStringList &vPrm) +{ + int count = vPrm.count(); + QStringList prm = count ? vPrm : _uiHistory[ vMessageID ]; + QString msg = QString::number(vMessageID); + QString len = QString::number(count ); + + QStringList dataList; + dataList += msg; + dataList += len; + dataList += prm; + + return dataList; +} + +/*! + * \brief CloudSyncController::isDuplicate + * \param vMessage - current action/messageID received. + * \param vData - current data to compare with the history + * \return true if duplicate + */ +bool CloudSyncController::isDuplicate(const qint32 vMessageID, const QStringList &vData) +{ + return _uiHistory.contains(vMessageID) && _uiHistory[vMessageID] == vData; +} + +/*! + * \brief CloudSyncController::writeInpFile + * \details Writes to the CS Input buffer + * \param vBuffer - the string out buffer. + * \return true if successful + */ +bool CloudSyncController::writeInpFile(const QString &vInpBuff) +{ + bool ok = true; + QVariantList args ; + Errors_Enum error = eError_Unknown; + + _date_inp_File = _location + // The location + _dateFormatted + _dateSeparator + _inp_File; // The file name + + // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + // writing the message into the buffer. + LOG_APPED_UI( vInpBuff ); + if ( ! Storage::FileHandler::write(_date_inp_File, vInpBuff + "\n") ) { error = eError_LogFileInp; args = { _date_inp_File }; ok = false; goto lErr; } + return ok; + +lErr: + toLog(error, args); + return ok; +} + +/*! * \brief CloudSyncController::sendUIBuff * \details Sends the UI Buffer to the UI input file. * \param vData - the data to be sent out. * \return true on successful writing to the file buffer. * \sa _inp_File */ -bool CloudSyncController::sendUIBuff(const QString &vData) +bool CloudSyncController::sendUIBuff(const QStringList &vDataList) { bool ok = true; + QString inpBuff = "%1,%2,%3,%4"; + + inpBuff = inpBuff + .arg( _secSinceEpoch ) + .arg( Types::safeIncrement(_seq)) + .arg( generateCRC(_secSinceEpoch, _seq, vDataList)) + .arg( vDataList.join(_separator)); + + ok = writeInpFile(inpBuff); + return ok; +} + +/*! + * \brief CloudSyncController::saveUIHistory + * \details stores the action data in history, if vData is provided. + * \param vAction - the request id, which in current design is same as the Message comes to the UI. + * \param vData - The message data + * \return true if a message history is available, false otherwise. + */ +bool CloudSyncController::saveUIHistory(const qint32 vAction, const QVariantList &vData) +{ + bool ok = true; QVariantList args ; - Errors_Enum error = eError_OK; - quint8 crc = 0; // no crc generation for now. + Errors_Enum error = eError_Unknown; - QString inpBuff; - _date_inp_File = _location + // The location - _dateFormatted + _dateSeparator + _inp_File; // The file name - inpBuff = QString::number(_secSinceEpoch); - inpBuff += _separator + QString::number(_seq++); - inpBuff += _separator + QString::number(crc); - inpBuff += _separator + vData; - inpBuff += '\n'; + qint32 messageID = UI2CS(static_cast(vAction)); + QStringList data; - // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - // writing the message into the buffer. - if ( ! Storage::FileHandler::write(_date_inp_File, inpBuff) ) { error = eError_LogFileInp; args = { _date_inp_File }; ok = false; goto lErr; } + if ( vData.isEmpty() ) { error = eError_NoHistory; args = { messageID }; ok = false; goto lErr; } + + data = Format::fromVariantList(vData); + + if ( isDuplicate(messageID, data) ) { error = eError_Duplicate; args = { messageID }; ok = false; return ok; } // TODO: goto lErr; } don't log it just ignore and return false. + + // store the last message data + _uiHistory[messageID] = data; + return ok; lErr: @@ -384,22 +569,23 @@ /*! * \brief CloudSyncController::sendUIHistory - * \details sends a response to the cloud sync upon request from the stored history. + * \details sends the saved history of the message as a response to the cloud sync upon request. * \param vAction - the request id, which in current design is same as the Message comes to the UI. * \return true if a message history is available, false otherwise. */ -bool CloudSyncController::sendUIHistory(qint32 vAction) +bool CloudSyncController::sendUIHistory(const qint32 vAction) { bool ok = true; QVariantList args ; - Errors_Enum error = eError_OK; - if ( ! _lastReceivedData.contains( vAction ) ) { error = eError_NoHistory; args = { vAction }; ok = false; goto lErr; } + Errors_Enum error = eError_Unknown; - sendUIBuff(QString("%1,%2,%3") - .arg(vAction) - .arg(_lastReceivedData[ vAction ].count()) - .arg(_lastReceivedData[ vAction ].join(_separator))); + // only sends messages if device has been registered + qint32 messageID = UI2CS(static_cast(vAction)); + if ( ! isRegistered() && vAction != eMessageID_DeviceRegister ) { error = eError_NotRegistered ; args = { messageID }; ok = false; goto lErr; } + if ( ! _uiHistory.contains( messageID ) ) { error = eError_NoHistory ; args = { messageID }; ok = false; goto lErr; } + sendUIBuff(makeUIBuff(messageID)); + return ok; lErr: @@ -415,26 +601,37 @@ */ void CloudSyncController::onActionReceive(GuiActionType vAction, const QVariantList &vData) { - // convert the data to the string list store/fetching it out from history. - // the original vData will be used if their actual values needed. - QStringList data; - for (auto datum : vData) { data += datum.toString(); } + // TODO: This section is the translation/mapping section + // vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv + // preparing the message + // Same parameters will be sent for these messages for now, so data is not changed. switch (vAction) { - case GuiActionType::ID_HDOperationModeData: - case GuiActionType::ID_PreTreatmentStates : - case GuiActionType::ID_TreatmentStates : - case GuiActionType::ID_PostTreatmentStates: - case GuiActionType::ID_DisinfectStates : - // TODO: This section is the translation/mapping section - // vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv - // preparing the message + case GuiActionType::ID_HDOperationModeData : { + // ---------------------------------------------------------------------------------------- + // DEBUG : disable the HD HW message on the device on CloudSync integration testing. + // currently it is always on fault and resets CloudSync inp log + QVariantList varData = vData; + QStringList strData = Format::fromVariantList(vData); + if ( strData.length() == 2 ) { + if ( strData[0] == "0" ) { + if ( strData[1] == "1") { varData = {0,0}; } // use Fault, wait4Tx instead of 0,0 to send fault. to able to simulate and bypass HD. + else { return; } + } + } + // ---------------------------------------------------------------------------------------- - // store the last message data - _lastReceivedData[eMessageID_DeviceState] = data; - sendUIHistory (eMessageID_DeviceState); - break; + if (saveDeviceState(varData)) // if not empty, nor duplicate, and saved + sendDeviceState(); + } + break; + case GuiActionType::ID_AdjustSerialHDRsp : + case GuiActionType::ID_AdjustSerialDGRsp : + // this message is not complete in one step and saves only the received info and waits for the other if complete will send. + saveDeviceInfo(vAction, vData); + break; + default: break; } @@ -443,13 +640,390 @@ /*! * \brief CloudSyncController::onTreatmentLogSave * \details The slot being called when the TreatmentLogController notifies the CloudSyncController, about the treatment log being successfully saved. - * \param vPatientID - Patient ID * \param vDeviceID - Device ID + * \param vPatientID - Patient ID * \param vFileName - The complete Treatment log path and file name. */ -void CloudSyncController::onTreatmentLogSave(const QString &vPatientID, const QString &vDeviceID, const QString &vFileName) +void CloudSyncController::onTreatmentLogSave(const QString &/*vDeviceID*/, const QString &/*vPatientID*/, const QString &vFileName) { - QStringList data { vPatientID, vDeviceID, vFileName }; - _lastReceivedData[eMessageID_TxReport] = data; - sendUIHistory (eMessageID_TxReport); + QVariantList data { /*vDeviceID, vPatientID,*/ vFileName }; + sendCredentialsSend(); // TODO: This has to be removed later, it is a workaround for Cloud issue, during our talk with KBM. + saveUIHistory(eMessageID_TxReport, data ); + sendUIHistory(eMessageID_TxReport ); } + +/*! + * \brief CloudSyncController::sendMessage + * \details Makes and Sends the appropriate message for the vAction. + * Some messages are sent out upon request form the last received on the history and will not be asked from FW. + * \return true if there is a history for that message and no error happened. + */ +bool CloudSyncController::sendMessage(const Message &vMessage) { + bool ok = false; + + // this function is used in sendUIResponse, therefore the message IDs which are responses should be implemented here. + switch (vMessage.id) { + case eMessageID_DeviceRegister : /* No Request/Response */ break; + + case eMessageID_DeviceInfo : ok = sendDeviceInfo ( ); break; + case eMessageID_CredentialsSave : ok = sendCredentialsSave( vMessage ); break; + case eMessageID_CredentialsSend : ok = sendCredentialsSend( ); break; + case eMessageID_ResetFactory : ok = sendResetFactory ( ); break; + case eMessageID_DeviceState : ok = sendDeviceState ( ); break; + + case eMessageID_TxCodeDisplay : ok = sendTxCodeDisplay ( vMessage ); break; + + case eMessageID_TxReport : /* No Req/Rsp, it is event based */ break; // This message doesn't have the response since there is no request. UI will send when the data ready by HD. + + case eMessageID_Error : ok = errorHandler ( vMessage ); break; + + } + return ok; +} + +/*! + * \brief CloudSyncController::saveDeviceState + * \details Saves the device state in the history for later request + * \param vData - the device information + * \return + */ +bool CloudSyncController::saveDeviceState(const QVariantList &vData) +{ + bool ok = false; + ok = saveUIHistory(eMessageID_DeviceState, vData); + return ok; +} + +/*! + * \brief CloudSyncController::sendDeviceState + * \return Sends the last received device state from history to CloudSync + */ +bool CloudSyncController::sendDeviceState() +{ + bool ok = false; + sendCredentialsSend(); // TODO: This has to be removed later, it is a workaround for Cloud issue, during our talk with KBM. + ok = sendUIHistory(eMessageID_DeviceState); + return ok; +} + +void CloudSyncController::testPendingTxReports() +{ + QFileInfoList fileInfos; + fileInfos = Storage::FileHandler::find(_TreatmentLog.logPathPending(), {"*.log"}); + qDebug() << fileInfos; +} + +/*! + * \brief CloudSyncController::doResetFactory + * \details does the reset factory + * \return true on successful reset + */ +bool CloudSyncController::doResetFactory() +{ + // reset factory has not been implemented yet. + bool ok = true; + // TODO: call the UI Software Reset Factory here when it has implemented. + LOG_DEBUG("CloudSync Reset factory request has not been implemented yet."); + return ok; +} + +/*! + * \brief CloudSyncController::sendResetFactory + * \details sends the factory reset response to CloudSync + * \return true on successful send. + */ +bool CloudSyncController::sendResetFactory() +{ + enum { eSucceed, eFailed }; + bool ok = false; + ok = doResetFactory(); + // if ( ! ok ) { } /* Not defined */ + qint32 messageID = UI2CS(eMessageID_ResetFactory); + // TODO : Store registration success in here + ok = sendUIBuff({ QString("%1").arg( messageID ) ,"1", QString("%1").arg( ok ? eSucceed : eFailed ) }); + return ok; +} + +/*! + * \brief CloudSyncController::sendCredentialsSave + * \details saves the credentials files which their location have been sent. + * \param vMessage - the message received from CloudSync + * \return true on successfully saving the files. + */ +bool CloudSyncController::sendCredentialsSave( const Message &vMessage) +{ + bool ok = true; + QString destination = QString(Storage::CloudSync_Base_Path_Name) + Storage::CloudSync_Credentials_Folder_Name; + + // create the destination folder if does not exist. + if ( ! Storage::FileHandler::makeFolder( destination ) ) { toLog(eError_CredentialMake , { }); ok = false; goto lOut; } + + // file existence has been separated from copy remove to avoid partial copy. + for ( auto fileName : vMessage.params ) { + if ( ! QFileInfo::exists(fileName) ) { toLog(eError_CredentialFile , {fileName }); ok = false; goto lOut; } + } + + if ( ok ) { + for ( auto sourceFile : vMessage.params ) { + QString fileName = QFileInfo(sourceFile).fileName(); + // NOTE: If a file with the name newName already exists, copy() returns false (i.e., QFile will not overwrite it). + // For the current scenario it is ideal, since that folder will turn RO after the successful registration and copying the files and reboot. + if ( ! QFile::copy (sourceFile, destination + fileName) ) { toLog(eError_CredentialCopy , {fileName }); ok = false; goto lOut; } + } + // if all the copies are successful then remove them. + for ( auto sourceFile : vMessage.params ) { + QString fileName = QFileInfo(sourceFile).fileName(); + if ( ! QFile::remove(sourceFile ) ) { toLog(eError_CredentialRemove , {fileName }); ok = false; goto lOut; } + } + } + +lOut: + if ( ok ) sendCredentialsResponse(); + return ok; +} + +/*! + * \brief CloudSyncController::sendSaveCredentials + * \details saves the credentials files which their location have been sent. + * \param vMessage - the message received from CloudSync + * \return true on successfully saving the files. + */ +bool CloudSyncController::sendCredentialsSend() +{ + bool ok = true; + qint32 messageID = UI2CS(eMessageID_CredentialsSend); + QString source = QString(Storage::CloudSync_Base_Path_Name) + Storage::CloudSync_Credentials_Folder_Name; + QString destination = QStandardPaths::writableLocation(QStandardPaths::TempLocation) + "/" + Storage::CloudSync_Credentials_Folder_Name; + + if ( Storage::FileHandler::copyFolder(source, destination) != 0 ) { toLog(eError_CredentialCopy , {destination }); ok = false; goto lOut; } + ok = sendUIBuff({QString("%1").arg( messageID ), "1", QString("%1").arg(destination)}); + +lOut: + return ok; +} + +/*! + * \brief CloudSyncController::sendCredentialsResponse + * \return send the confirmation of the credential save on the device from UI to CS to let CS know when to delete them. + */ +bool CloudSyncController::sendCredentialsResponse() +{ + enum { eSucceed, eFailed }; + bool ok = false; + qint32 messageID = UI2CS(eMessageID_CredentialsSave); + ok = sendUIBuff({ QString("%1").arg( messageID ) ,"1", QString("%1").arg( ok ? eSucceed : eFailed ) }); + return ok; +} + +/*! + * \brief CloudSyncController::sendTxCodeDisplay + * \details reads the received Tx Code from CloudSync app and notifies with a signal. + * \param vMessage : message containing the Tx Code. + * \return true on successful extracting the Tc Code. + */ +bool CloudSyncController::sendTxCodeDisplay(const CloudSyncController::Message &vMessage) +{ + bool ok = true; + QString mTxCode; + + // although it has been checked in the interpreter, we won't risk the crash and check the list empty. + if ( vMessage.params.isEmpty() ) { toLog(eError_TxCodeNoParam , {}); ok = false; goto lOut; } + + mTxCode = vMessage.params[0].trimmed(); + if ( mTxCode.isEmpty() ) { toLog(eError_TxCodeEmpty , {}); ok = false; goto lOut; } + + emit didTxCodeReceive ( mTxCode ); + qDebug() << " ---------- " << mTxCode; + +lOut: + return ok; +} + +/*! + * \brief CloudSyncController::errorHandler + * \details nothing for now. + * \param vMessage - the received message + * \return always true for now. + */ +bool CloudSyncController::errorHandler(const Message &vMessage) +{ + qDebug() << "ERR: " << vMessage.params; + return true; +} + +/*! + * \brief CloudSyncController::sendDeviceRegister + * \details sends the device registration request + * \return true on successful send. + */ +bool CloudSyncController::sendDeviceRegister() +{ + bool ok = false; + ok = sendUIHistory(eMessageID_DeviceRegister); + return ok; +} + +/*! + * \brief CloudSyncController::testDeviceRegister + * \details checks if the device needs registration. + * \return true if registration required. + */ +bool CloudSyncController::testDeviceRegister() +{ + toLog(eError_CredentialEmpty,{}); // It is intentional that the vault folder path has not been sent to the log. + if( ! isRegistered() ) { + sendDeviceRegister(); + } + + return true; // for now always true. +} + +/*! + * \brief CloudSyncController::isRegistered + * \details checks if the device is registered by looking for existing credentials. + * \return true if device has been registered. + */ +bool CloudSyncController::isRegistered() +{ + QString source = QString(Storage::CloudSync_Base_Path_Name) + Storage::CloudSync_Credentials_Folder_Name; + QFileInfoList fileInfos = QDir(source).entryInfoList(QDir::NoDotAndDotDot|QDir::Files); + return !fileInfos.isEmpty(); +} + +/*! + * \brief CloudSyncController::saveDeviceInfo + * \details keeps the received device information and set a flag to wait for the next message. + * \param vAction - the action enum which identifies information source of HD or DG. + * \param vData - the device information + * \param vTimedOut - if true the missing device information will be filled by zero and will be sent anyway. + * \return + */ +bool CloudSyncController::saveDeviceInfo(GuiActionType vAction, const QVariantList &vData) +{ + bool ok = false; + + // the messages are coming from different sources and the order of receiving could not be guessed. + // so for each of these two we check and fill the designated variable + // if both values received + initDeviceInfoWait(); + + if ( vAction == GuiActionType::ID_AdjustSerialHDRsp ) { + if ( vData.count() ) { + _deviceInfoHD = vData[eDeviceInfo_Ix].toString(); + } else { + _deviceInfoHD = ""; + } + } + if ( vAction == GuiActionType::ID_AdjustSerialDGRsp ) { + if ( vData.count() ) { + _deviceInfoDG = vData[eDeviceInfo_Ix].toString(); + } else { + _deviceInfoDG = ""; + } + } + + if ( !_deviceInfoHD.isEmpty() && !_deviceInfoDG.isEmpty() ) { + saveDeviceInfoTimeOut(); + ok = true; + } + return ok; +} + +/*! + * \brief CloudSyncController::saveDeviceInfoTimeOut + * \details send the Device information regardless of the information filled or not. + * \return true on successful save and send. + */ +bool CloudSyncController::saveDeviceInfoTimeOut() +{ + bool ok = false; + if ( _deviceInfoUI.isEmpty() ) { _deviceInfoUI = qApp->applicationVersion(); } + saveUIHistory(eMessageID_DeviceInfo , { _deviceInfoHD, _deviceInfoDG, _deviceInfoUI } ); + saveUIHistory(eMessageID_DeviceRegister , { _deviceInfoHD, _deviceInfoDG, _deviceInfoUI } ); // the device registration request format is the same as Device info with different message id. + + testDeviceRegister (); // it is expected on the CloudSync App to get the + sendDeviceInfo (); // this one may need to be removed and only will be sent on the request + + stopDeviceInfoWait(); + + return ok; +} + +/*! + * \brief CloudSyncController::sendDeviceInfo + * \details sends the devices information if both the HD and DG information are ready otherwise will fill the missing with 0 if the vTimeout is set to true. + * \return + */ +bool CloudSyncController::sendDeviceInfo() +{ + bool ok = false; + ok = sendUIHistory(eMessageID_DeviceInfo); + return ok; +} + +/*! + * \brief CloudSyncController::testDeviceInfoWait + * \details tests the wait conditions if there is a wait, decrements until times out and sends the device info. + */ +void CloudSyncController::testDeviceInfoWait() +{ + if (_deviceInfoStop ) return; + // DEBUG: qDebug() << _deviceInfoWait; + if (_deviceInfoWait ){ + _deviceInfoWait -- ; + } + else { + saveDeviceInfoTimeOut(); + } +} + +/*! + * \brief CloudSyncController::stopDeviceInfoWait + * \details stops waiting for the device info. + */ +void CloudSyncController::stopDeviceInfoWait() { + _deviceInfoStop = true; + // NOTE: if it is accepted to use the last received info just comment these 3 cleanup lines. + // so the last message will update the history only and it will be sent out. + _deviceInfoHD = ""; + _deviceInfoDG = ""; + _deviceInfoUI = ""; +} + +/*! + * \brief CloudSyncController::initDeviceInfoWait + * \details if currently not in wait, then set the wait to the secs of wait. + */ +void CloudSyncController::initDeviceInfoWait() { + if ( _deviceInfoStop ) { // if timer is stopped + _deviceInfoWait = _deviceInfoSecs; // enable the timer. + _deviceInfoStop = false; + } +} + +/*! + * \brief CloudSyncController::generateCRC + * \details The crc algorithm is to get + * all the message sections including, epoch, seq, and the parameters, + * without any separator + * concatenate them together like a one long string + * and run the crc on them + * \example 1674193951,4,,1001,3,HD1234567890123,DG1234567890123,DEN-14517-UI-BN-S88.01192111.1 + * str: 1674193951410013HD1234567890123DG1234567890123DEN-14517-UI-BN-S88.01192111.1 + * out: 145 + * \note the parameters values will keep untouched, meaning, if a parameter has space it will not be removed. + * \param vSecSinceEpoch + * \param vSeq - the message sequence + * \param vData - the message payload in list of strings + * \return - quint8 crc code + */ +quint8 CloudSyncController::generateCRC(quint64 vSecSinceEpoch, quint64 vSeq, const QStringList &vDataList) +{ + quint8 crc = 0; + QByteArray buf; + buf += QByteArray::number(vSecSinceEpoch); + buf += QByteArray::number(vSeq); + buf += vDataList.join("").toLatin1(); + crc = crc8(buf); + return crc; +}