/*! * * Copyright (c) 2021-2024 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 TreatmentLog.cpp * \author (last) Behrouz NematiPour * \date (last) 13-Mar-2024 * \author (original) Behrouz NematiPour * \date (original) 04-May-2021 * */ #include "TreatmentLog.h" // Qt #include // Project #include "StorageGlobals.h" #include "FileHandler.h" #include "ApplicationController.h" #include "Logger.h" #include "Settings.h" #include "CloudSyncController.h" using namespace Storage; #define NONE "N/A" #define FLOAT3 0,'f',3 #define ADDTITLE(vTITLE) logContent += QString("[%1]\n").arg(vTITLE) #define ADDALINE(vTEXT ) logContent += QString("%1\n" ).arg(vTEXT ) #define ADDTOLOG( vINDEX ) index = vINDEX; logContent += title(index) + _sep + value(index) + _sep + unit(index) + "\n"; #define ADDTOLOG_MT(vINDEX, vVALUE ) \ index = vINDEX; \ logContent += title(index) + _sep + \ ( value(index).trimmed().isEmpty() ? vVALUE : value(index) ) + _sep + \ unit(index) + "\n"; /*! * \brief TreatmentLog::TreatmentLog * The constructor to initial the Treatment Log * \param parent */ TreatmentLog::TreatmentLog(QObject *parent) : QObject(parent) { initConnections(); logPath(Logger::eLogTrtmt, _Logger.logPath(Logger::eLogTrtmt)); } /*! \brief Connection Initializer \details All the class signal/slot connections are defined here. */ void TreatmentLog::initConnections() { connect(&_ApplicationController , QOverload< const SettingsData & >::of( &ApplicationController::didActionReceive ), [ this ] ( const SettingsData & ) { readAlarmTitles(); readEventTitles(); }); connect(&_ApplicationController , QOverload< const AdjustSerialNumberTDResponseData & >::of( &ApplicationController::didActionReceive ), [ this ] ( const AdjustSerialNumberTDResponseData &vData ) { // The serial number is going to be received from HD // on POST or on request // and will be used for the device ID // which we designed initially to be different. _deviceID = vData.mSerialNumber; }); connect(&_Logger , SIGNAL(didLogPathSet(Logger::LogType, const QString &)), this , SLOT( onLogPathSet(Logger::LogType, const QString &))); connect(&_exportWatcher , SIGNAL(finished()), this , SLOT(onExport())); connect(&_saveWatcher , SIGNAL(finished()), this , SLOT(onSave ())); connect(&_CloudSyncController , SIGNAL(didCloudSyncStatus(bool)), this , SLOT( onCloudSyncStatus(bool))); connect(&_CloudSyncController , SIGNAL(didTxCodeReceive(const QString &)), this , SLOT( onTxCodeReceive(const QString &))); //DEBUG onCloudSyncStatus(true); // to bypass the setting partition mount in debug testing. } /*! * \brief TreatmentLog::readAlarmTitles * \details Reads the ListTitle text from the Alarms.conf for the titles of the alarms going to be written in the TxReport. */ void TreatmentLog::readAlarmTitles() { //NOTE: It currently is the duplicate of VAlarmActiveList titles, // but intentionally kept it separated to be able to implement a different text/titles in case on cloud we would want something different. QString category = Storage::Settings_Category_Alarms; QStringList groups = _Settings.groups(category); for (const auto &group : groups) { bool ok = true; quint32 id = group.toInt( &ok ); if ( ! ok ) { LOG_DEBUG(QString("Invalid Tx Alarm ID [%1]").arg(group)); continue; } for (const QString &key : _Settings.keys(category, group)) { if (Storage::Settings::isKeyListTitle ( key ) ) { _treatmentLogAlarmTitles[id] = _Settings.value(category, group, key).toString(); } } } } /*! * \brief TreatmentLog::readEventTitles * \details Reads the Title text from the Events.conf for the titles of the events going to be written in the TxReport. */ void TreatmentLog::readEventTitles() { QString category = Storage::Settings_Category_Events; QStringList groups = _Settings.groups(category); for (const auto &group : groups) { bool ok = true; quint32 id = group.toInt( &ok ); if ( ! ok ) { LOG_DEBUG(QString("Undefined Tx Events ID [%1]").arg(group)); continue; } for (const QString &key : _Settings.keys(category, group)) { if (Storage::Settings::isKeyTitle ( key ) ) { _treatmentLogEventTitles[id] = _Settings.value(category, group, key).toString(); } } } } /*! * \brief TreatmentLog::alarmTitle * \details prepares the alarm title and returns. * \param vID - the alarm ID * \param vShowID - flag indicting whether to include the alarm ID in the returned title or not * \return the alarm title */ QString TreatmentLog::alarmTitle(quint32 vID, const bool vShowID) { QString str = "%1-%2"; QString title = "undefined"; if ( _treatmentLogAlarmTitles.contains(vID) ) { title = _treatmentLogAlarmTitles[vID] .replace("," , "-") // the Txr is a csv file and any ',' in the title will break it. .replace("\n" , "-"); // removing any new line since this will break the Txr as well. } return vShowID ? str.arg(vID, 3).arg(title) : title; } /*! * \brief TreatmentLog::eventTitle * \details prepares the event title and returns. * \param vID - the event ID * \param vShowID - flag indicting whether to include the alarm ID in the returned title or not * \return the event title */ QString TreatmentLog::eventTitle(quint32 vID, const bool vShowID) { QString str = "%1 - %2"; QString title = "undefined"; if ( _treatmentLogEventTitles.contains(vID) ) { title = _treatmentLogEventTitles[vID] .replace("," , "-") // the Txr is a csv file and any ',' in the title will break it. .replace("\n" , "-"); // removing any new line since this will break the Txr as well. } return vShowID ? str.arg(vID, 3).arg(title) : title; } /*! * \brief TreatmentLog::timerEvent * \details The overloaded method of the main class to capture the QObject timer. */ void TreatmentLog::timerEvent(QTimerEvent *) { testPendingTxReports(); } /*! * \brief TreatmentLog::initModel * Initializing the model for the constant values. * \param vData - the response model data. */ void TreatmentLog::initModel(const AdjustTreatmentLogResponseData &vData, const QString &vPatientID, bool vHeparinBolusVolumeOff, bool vHeparinDispensingRateOff) { // Formatted values QString mStrText = "%1"; // DEBUG: qDebug() << _Settings.groups(); // SRSUI910 : PRS178 : Clinical - Data - Order bool isHeparinOff = vHeparinBolusVolumeOff && vHeparinDispensingRateOff; QString mTreatmentDurationFmt = QTime (0, 0).addSecs(int(vData.mTreatmentDuration )).toString(_timeFormat); QString mActualTreatmentDurationFmt = QTime (0, 0).addSecs(int(vData.mActualTreatmentDuration )).toString(_timeFormat); QString mTreatmentDuration = QString::number(vData.mTreatmentDuration / 60 ); // Converted seconds to minutes. QString mActualTreatmentDuration = QString::number(vData.mActualTreatmentDuration / 60 ); // Converted seconds to minutes. QString mTreatmentStartDateTime = Format::fromEpoch( vData.mTreatmentStartEpoch , _datetimeFormat); QString mTreatmentEndDateTime = Format::fromEpoch( vData.mTreatmentEndEpoch , _datetimeFormat); // TODO : The settings needs modification not only to define the category and groups in a common header but also the settings itself needs some mods. // - the category shall become as part of the group // - the structure of the settings should become horizontal which vertical now. QString mCategory = Storage::Settings_Category_DataList; QString mAcidConcentrateType = _Settings.key(mCategory, "Acid Concentrate Options" , vData.mAcidConcentrateType ); QString mBicarbonateConcentrateType = _Settings.key(mCategory, "Bicarbonate Concentrate Options" , vData.mBicarbonateConcentrateType ); QString mDialyzerType = _Settings.key(mCategory, "Dialyzer Type Options" , vData.mDialyzerType ); QString mHeparinType = _Settings.key(mCategory, "Heparin Type Options" , vData.mHeparinType ); QString mWaterSampleTestResult = _Settings.key(mCategory, "Water Sample Result" , vData.mWaterSampleTestResult ); // init/fill/clear the _values clearModel(); _values[ePatientID ] = vPatientID.trimmed() ; _values[eBloodFlowRate ] = mStrText.arg(vData.mBloodFlowRate ); _values[eDialysateFlowRate ] = mStrText.arg(vData.mDialysateFlowRate ); _values[eTreatmentDuration ] = mTreatmentDuration ; _values[eActualTreatmentDuration ] = mActualTreatmentDuration ; _values[eAcidConcentrateType ] = mAcidConcentrateType ; _values[eBicarbonateConcentrateType ] = mBicarbonateConcentrateType ; _values[ePotassiumConcentration ] = mStrText.arg(vData.mPotassiumConcentration ); _values[eCalciumConcentration ] = mStrText.arg(vData.mCalciumConcentration ); _values[eBicarbonateConcentration ] = mStrText.arg(vData.mBicarbonateConcentration ); _values[eSodiumConcentration ] = mStrText.arg(vData.mSodiumConcentration ); _values[eDialysateTemperature ] = mStrText.arg(vData.mDialysateTemperature ,FLOAT3 ); _values[eDialyzerType ] = mDialyzerType ; _values[eTreatmentStartDateTime ] = mTreatmentStartDateTime ; _values[eTreatmentEndDateTime ] = mTreatmentEndDateTime ; _values[eWaterSampleTestResult ] = mWaterSampleTestResult ; _values[eDialysateVolumeUsed ] = mStrText.arg(vData.mDialysateVolumeUsed ,FLOAT3 ); _values[eOriginUFVolume ] = mStrText.arg(vData.mOriginUFVolume ,FLOAT3 ); _values[eTargetUFVolume ] = mStrText.arg(vData.mTargetUFVolume ,FLOAT3 ); _values[eActualUFVolume ] = mStrText.arg(vData.mActualUFVolume ,FLOAT3 ); _values[eOriginUFRate ] = mStrText.arg(vData.mOriginUFRate ,FLOAT3 ); _values[eTargetUFRate ] = mStrText.arg(vData.mTargetUFRate ,FLOAT3 ); _values[eActualUFRate ] = mStrText.arg(vData.mActualUFRate ,FLOAT3 ); _values[eSalineBolusVolume ] = mStrText.arg(vData.mSalineBolusVolume ); _values[eHeparinType ] = isHeparinOff ? tr("NONE") : mHeparinType ; _values[eHeparinConcentration ] = isHeparinOff ? tr("NONE") : mStrText.arg(vData.mHeparinConcentration ); _values[eHeparinBolusVolume ] = vHeparinBolusVolumeOff ? tr("OFF" ) : mStrText.arg(vData.mHeparinBolusVolume ,FLOAT3 ); _values[eHeparinDispenseRate ] = vHeparinDispensingRateOff ? tr("OFF" ) : mStrText.arg(vData.mHeparinDispenseRate ,FLOAT3 ); _values[eHeparinStop ] = vHeparinDispensingRateOff ? tr("OFF" ) : mStrText.arg(vData.mHeparinStop ); _values[eHeparinDeliveredVolume ] = isHeparinOff ? tr("OFF" ) : mStrText.arg(vData.mHeparinDeliveredVolume ,FLOAT3 ); emit didTxCodeReceive(""); } /*! * \brief TreatmentLog::append * \param vData - the received data to be kept in the list to be appended at the end of the Treatment Log */ void TreatmentLog::append(const TreatmentLogAvrgeData &vData) { _treatmentLogAvrgeData.append(vData); } void TreatmentLog::append(const TreatmentLogAlarmData &vData) { _treatmentLogAlarmData.append(vData); } void TreatmentLog::append(const TreatmentLogEventData &vData) { _treatmentLogEventData.append(vData); } /*! * \brief TreatmentLog::onLogPathSet * \details sets the logging path * \param vLogType - The log type which in this should be eLogTrtmt * \param vLogPath - The full treatment log path */ void TreatmentLog::onLogPathSet(Logger::LogType vLogType, const QString &vLogPath) { logPath(vLogType, vLogPath); } /*! * \brief TreatmentLog::logPath * \details sets the treatment log path if the given type is treatment log, with the path, vLogPath. * If the given vLogPath is empty, the defaults in Storage will be used. * \sa Storage::SDCard_Base_Path_Name * \sa Storage::Txr_Folder_Treatment * \param vLogType - The Log type of type Logger::LogType * \param vLogPath - the path to be set and used. */ void TreatmentLog::logPath(Logger::LogType vLogType, QString vLogPath) { if (vLogType == Logger::eLogTrtmt) { if ( vLogPath.trimmed().isEmpty() ) { _treatmentLogPath = QString("%1%2") .arg(Storage::Settings_Path()) .arg(Storage::Txr_Folder_Treatment ); } else { _treatmentLogPath = vLogPath; } LOG_DEBUG(QString("Treatment log folder has been set to %1" ).arg(_treatmentLogPath )); } } // ----- Save /*! * \brief TreatmentLog::doSave * The save slot to be exposed to the UI to be able to request for save */ void TreatmentLog::doSave() { if (_saveWatcher.isRunning()) return; isIdle(false); saveLogConcurrent(); } /*! * \brief TreatmentLog::saveLogConcurrent * The treatment log save which is using a thread pool to run the save process. */ void TreatmentLog::saveLogConcurrent() { LOG_DEBUG("Save Treatment Log Started"); QFuture mFuture = QtConcurrent::run(this, &TreatmentLog::saveLog); _saveWatcher.setFuture(mFuture); } /*! * \brief TreatmentLog::saveLog * The actual treatment log save function which does the save into the treatment log * \return true on successful save and false otherwise. */ bool TreatmentLog::saveLog() { _lastTxInfo.clear(); bool ok = (unsigned)_values.count() >= eTreatmentLogIndexCount; if (!ok) return false; QString logContent ; QString csv = "%1" + _sep ; QString end = "%1" ; uint index = 0 ; QString txCode = "%1,%2," ; ADDTITLE("Title"); ADDALINE(txCode .arg(_txCodeKey).arg(_gTxCode) ); ADDTOLOG_MT( ePatientID, _emptyPatinetID ); ADDTITLE("Treatment Parameters" ); ADDTOLOG( eTreatmentDuration ); ADDTOLOG( eBloodFlowRate ); ADDTOLOG( eDialysateFlowRate ); ADDTOLOG( eAcidConcentrateType ); ADDTOLOG( eBicarbonateConcentrateType ); ADDTOLOG( ePotassiumConcentration ); ADDTOLOG( eCalciumConcentration ); ADDTOLOG( eBicarbonateConcentration ); ADDTOLOG( eSodiumConcentration ); ADDTOLOG( eDialysateTemperature ); ADDTOLOG( eDialyzerType ); ADDTOLOG( eHeparinType ); ADDTOLOG( eHeparinConcentration ); ADDTOLOG( eHeparinBolusVolume ); ADDTOLOG( eHeparinDispenseRate ); ADDTOLOG( eHeparinStop ); ADDTITLE("Treatment Time" ); ADDTOLOG( eTreatmentStartDateTime ); ADDTOLOG( eTreatmentEndDateTime ); ADDTOLOG( eActualTreatmentDuration ); ADDTITLE("Device Treatment Data" ); ADDTOLOG( eDialysateVolumeUsed ); ADDTOLOG( eOriginUFVolume ); ADDTOLOG( eTargetUFVolume ); ADDTOLOG( eActualUFVolume ); ADDTOLOG( eOriginUFRate ); ADDTOLOG( eTargetUFRate ); ADDTOLOG( eActualUFRate ); ADDTOLOG( eSalineBolusVolume ); ADDTOLOG( eHeparinDeliveredVolume ); ADDTITLE("Extra" ); ADDTOLOG( eWaterSampleTestResult ); ADDTITLE("Treatment Data" ); for ( const TreatmentLogAvrgeData &item : _treatmentLogAvrgeData ) { QString line; line += csv.arg(item.mTimeStamp ); // line += csv.arg(NONE ); // removed during our meeting with Sean and Jahnavi 04/11/2022@16:00. line += csv.arg(item.mBloodFlowRate ,FLOAT3 ); line += csv.arg(item.mDialysateFlowRate ,FLOAT3 ); line += csv.arg(item.mUFRate ,FLOAT3 ); line += csv.arg(item.mArterialPressure ,FLOAT3 ); line += csv.arg(item.mVenousPressure ,FLOAT3 ); line += csv.arg(item.mSystolic ); line += csv.arg(item.mDiastolic ); line += end.arg(item.mHeartRate ); ADDALINE(line); } ADDTITLE("Treatment Alarms" ); for ( const TreatmentLogAlarmData &item : _treatmentLogAlarmData ) { QString line; // TODO : QString alarmText = Model::MAlarmStatus::toText(static_cast(item.mAlarmID)); line += csv.arg( item.mTimeStamp ); line += csv.arg( alarmTitle(item.mAlarmID )); line += csv.arg( item.mParam1 ); line += end.arg( item.mParam2 ); ADDALINE(line); } ADDTITLE("Treatment Events" ); for ( const TreatmentLogEventData &item : _treatmentLogEventData ) { QString line; line += csv.arg( item.mTimeStamp ); line += csv.arg( eventTitle(item.mEventID )); line += csv.arg( item.mOldValue ,FLOAT3 ); line += end.arg( item.mNewValue ,FLOAT3 ); ADDALINE(line); } ADDALINE(""); _lastTxInfo.mDateTime = _values[eTreatmentStartDateTime]; _lastTxInfo.mDateTime.replace("/", "" ); // remove date separator _lastTxInfo.mDateTime.replace(":", "" ); // remove time separator _lastTxInfo.mDateTime.replace(" ", "_"); // replace spaces _lastTxInfo.mDeviceID = _deviceID; _lastTxInfo.mPatientID = _values[ePatientID]; _lastTxInfo.mFileName = QString("%1%2_%3.%4") .arg(_treatmentLogPath) .arg(_lastTxInfo.mDateTime) .arg(_lastTxInfo.mDeviceID) .arg(_treatmentLogExtPending); //DEBUG qDebug() << _lastTxInfo.mFileName; ok = Storage::FileHandler::makeFolder(_treatmentLogPath); if ( ! ok ) { LOG_DEBUG(QString("Cannot create folder %1").arg(_treatmentLogPath )); return ok; } ok = Storage::FileHandler::write(_lastTxInfo.mFileName, logContent, false); if ( ! ok ) { LOG_DEBUG(QString("Cannot write to file %1").arg(_lastTxInfo.mFileName)); return ok; } _treatmentLogAvrgeData.clear(); _treatmentLogAlarmData.clear(); _treatmentLogEventData.clear(); _lastTxInfo.mStatus = ok; sendPending(); // reset the timer to find the latest saved pending and ask for Tx Code. return ok; } /*! * \brief TreatmentLog::onSave * The private save slot which is called after the save process is finished saving. */ void TreatmentLog::onSave() { LOG_DEBUG(QString("Save Treatment Log Ended: %1").arg(_saveWatcher.result())); isIdle(true); } // ----- Export /*! * \brief TreatmentLog::doExport * The export treatment log slot to be exposed to the UI to be able to request for the export. */ void TreatmentLog::doExport() { if (_exportWatcher.isRunning()) return; isIdle(false); exportLogConcurrent(); } /*! * \brief TreatmentLog::exportLogConcurrent * The treatment log export which is using a thread pool to run the save process. */ void TreatmentLog::exportLogConcurrent() { LOG_DEBUG("Export Treatment Log Started"); QFuture mFuture = QtConcurrent::run(this, &TreatmentLog::exportLog); _exportWatcher.setFuture(mFuture); } /*! * \brief TreatmentLog::exportLog * \details The actual treatment log export function which does the export of the treatment log into the USB drive. * \return true on successful export. */ bool TreatmentLog::exportLog() { bool ok = true; QString status = ""; QString dstPath = Storage::USB_Mount_Point ; dstPath += Storage::Txr_Folder_Treatment ; QString srcFile = _lastTxInfo.mFileName ; QString srcFileName = QFileInfo(srcFile).fileName() ; QString dstFile = dstPath + srcFileName; // HERE: expose to the UI dialog as the rejection/notification result if ( ! Storage::FileHandler::makeFolder ( dstPath ) ) { status = QString( "Couldn't create folder on USB drive to export TxLog" ); ok = false; goto lOut; } if ( ! QFileInfo::exists ( srcFile ) ) { status = QString( "Treatment Log '%1' doesn't exist" ).arg( srcFile ); ok = false; goto lOut; } if ( QFileInfo::exists ( dstFile ) ) { status = QString( "Treatment Log '%1' already exists" ).arg( dstFile ); ok = false; goto lOut; } if ( ! QFile::copy (srcFile, dstFile ) ) { status = QString( "Unable to Export TxLog '%1' to '%2'" ).arg( srcFile ).arg( dstFile ); ok = false; goto lOut; } lOut: if ( ! ok ) { LOG_DEBUG(status); // The log debug order in this block is by design status = "Unable to export treatment log '" + srcFileName +"'"; } else { status = "Treatment log '" + srcFileName + "' exported successfully"; LOG_APPED_UI(status); } emit didNotification(status); return ok; } /*! * \brief TreatmentLog::onExport * The private export slot which is called after the export process is finished exporting. */ void TreatmentLog::onExport() { LOG_DEBUG(QString("Export Treatment Log Ended: %1").arg(_exportWatcher.result())); isIdle(true); } /*! * \brief TreatmentLog::onCloudSyncStatus * \details This is the handler to the slot CloudSyncController::didCloudSyncStatus, * which will be emitted when the Cloud is running and the device is registered, with vReady as true. * Therefore pending Treatment logs can be sent. * or with ready as false if CloudSync stops or for any error can't communicate to send the logs. */ void TreatmentLog::onCloudSyncStatus(bool vReady) { static int id; if ( vReady ) { id = startTimer(_interval); } else { killTimer(id); } } /*! * \brief TreatmentLog::onTxCodeReceive * \details this slot will update the treatment code sent by CS. * \param vTxCode - The treatment code sent by CS. * \note there should not be a getter for this, for thread safety, and who needs the data needs to connect to the signal. */ void TreatmentLog::onTxCodeReceive(const QString &vTxCode) { _rTxCode = vTxCode; // This has to be checked before the addTxCode, // because that function will change the _lastTxInfo.mFilename after it is moved from pending. bool isLastTxInfo = ! _lastTxInfo.mFileName.isEmpty() && _pendingTxr == _lastTxInfo.mFileName; // getting the Tx out of pending and add the received Tx in the file checkTxCode(); if ( isLastTxInfo ) { // avoid updating the screen with another pending Tx in queue emit didTxCodeReceive(_rTxCode); } } /*! * \brief TreatmentLog::checkTxCode * \details Compares the received TxCode with the generated and if matches renames Txr from pending. * \return true on success, false on any case of read, write, remove. */ bool TreatmentLog::checkTxCode() { bool ok = true; QString src = _pendingTxr; QString dst = QString("%1%2.%3") .arg(_treatmentLogPath) .arg(QFileInfo(src).baseName()) .arg(_treatmentLogExtUploaded); ok = _gTxCode == _rTxCode; if ( ! ok ) { LOG_DEBUG(QString("Generated '%1' TxCode and Received TxCode '%2' does not match").arg(_gTxCode).arg(_rTxCode)); return ok; } ok = QFile::rename(src, dst); if ( ! ok ) { LOG_DEBUG(QString("Couldn't change the state of pending treatment log file '%1'").arg(src)); return ok; } _lastTxInfo.mFileName = dst; // Update the last Tx file to the new location [ export ] sendPending(); // start looking for the next pending, instead of waiting to timeout return ok; } bool TreatmentLog::findTxCode() { bool ok = false; QString content = ""; FileHandler::read(_pendingTxr, content); QStringList lines = content.split('\n'); for ( const QString &line : lines ) { if ( line.contains(_txCodeKey) ) { QStringList fields = line.split(','); if ( fields.count() >= Role::eCount ) { if ( fields[Role::eTitle] == _txCodeKey ) { if ( fields[Role::eValue].length() ) { _gTxCode = fields[Role::eValue]; ok = true; } } } } } return ok; } /*! * \brief TreatmentLog::sendPending * \details Resets the pending counter to immediately/ASAP as for the pending and won't waits for the timeout. */ void TreatmentLog::sendPending() { _pendingCounter = 0; } /*! * \brief TreatmentLog::testPendingTxReports * \details this function counts downs for the _pendingInterval * when the _pendingCounter reaches 0 will search for the files * and if there is any will get the recent file in the list * and asks for the TxCode by emitting the didPendingTxr signal */ void TreatmentLog::testPendingTxReports() { static QString pendingTxr = ""; if ( _pendingCounter ) { _pendingCounter -- ; return; } else { _pendingCounter = _pendingInterval; // every minute } QFileInfoList pendingFiles; pendingFiles = Storage::FileHandler::find( _TreatmentLog.logPath(), { QString("*.%1").arg(_treatmentLogExtPending) } ); // look into the list. // if there are pending files, // send a request only for the top on the list // * When gets the Tx Code, moves from pending then next one comes to top // the process repeats until there is no file in pending if ( pendingFiles.count() ) { // the most recent/first Tx file, to first ask for the current treatment which has just saved as pending on screen _pendingTxr = pendingFiles.first().absoluteFilePath(); // DEBUG qDebug() << _pendingTxr; if (pendingTxr != _pendingTxr ) { pendingTxr = _pendingTxr; findTxCode(); } QString message = pendingTxr + " , " + _pendingTxr + " , " + _gTxCode; LOG_DEBUG(message); emit didPendingTxr( _pendingTxr ); } }