Index: drydemo.pro.user =================================================================== diff -u -rc0b30f1fa82d0121706351057ab52b3bb1141459 -r5a248f0a45889844aa027f4f4a0661aa539975f0 --- drydemo.pro.user (.../drydemo.pro.user) (revision c0b30f1fa82d0121706351057ab52b3bb1141459) +++ drydemo.pro.user (.../drydemo.pro.user) (revision 5a248f0a45889844aa027f4f4a0661aa539975f0) @@ -1,6 +1,6 @@ - + EnvironmentId Index: drydemostates.scxml =================================================================== diff -u -rc0b30f1fa82d0121706351057ab52b3bb1141459 -r5a248f0a45889844aa027f4f4a0661aa539975f0 --- drydemostates.scxml (.../drydemostates.scxml) (revision c0b30f1fa82d0121706351057ab52b3bb1141459) +++ drydemostates.scxml (.../drydemostates.scxml) (revision 5a248f0a45889844aa027f4f4a0661aa539975f0) @@ -1,3 +1,20 @@ - - + + + + + + + + + + + + + + + + + + + Index: main.cpp =================================================================== diff -u -rc0b30f1fa82d0121706351057ab52b3bb1141459 -r5a248f0a45889844aa027f4f4a0661aa539975f0 --- main.cpp (.../main.cpp) (revision c0b30f1fa82d0121706351057ab52b3bb1141459) +++ main.cpp (.../main.cpp) (revision 5a248f0a45889844aa027f4f4a0661aa539975f0) @@ -52,6 +52,8 @@ #include "Threads.h" +#include "drydemostates.h" + // kernel #include @@ -532,6 +534,21 @@ signal(SIGTERM, signalhandler); #endif + drydemostates dryStates; + + dryStates.start(); + + dryStates.connectToState("Idle", QScxmlStateMachine::onEntry([&]() { + qDebug() << "In onEntry"; + QThread::sleep(4); + dryStates.submitEvent("Tx_Start_Rqst");})); + + dryStates.connectToState("Idle", QScxmlStateMachine::onExit([&] { + qDebug() << "Exiting";})); + + dryStates.connectToState("Treatment", QScxmlStateMachine::onEntry([&]() { + qDebug() << "In Treatment";})); + // setting the environment for the keyboard. qputenv("QT_IM_MODULE" , QByteArray("qtvirtualkeyboard" )); qputenv("QT_VIRTUALKEYBOARD_STYLE" , QByteArray("denali" )); @@ -571,15 +588,6 @@ // That is enough to call to the I function here to create the object in the thread that Settings is leaving in, // which currently is Application_Thread, since the Settings is created in that thread. _Settings; - QTranslator translator; - { // on-shot use of settings to load the translation only - // Storage::Settings settings; - //! - Reading localization - if ( ! Storage::Settings::readLocale() ) { - //! - Translation initialization - Storage::Settings::loadTranslation(translator); - } - } if (gFakeInterval) { QString msg = " ~~ !!!!! APPLICATION RUNNING IN THE TEST MODE !!!!! ~~ "; Index: sources/ApplicationController.cpp =================================================================== diff -u --- sources/ApplicationController.cpp (revision 0) +++ sources/ApplicationController.cpp (revision 5a248f0a45889844aa027f4f4a0661aa539975f0) @@ -0,0 +1,439 @@ +/*! + * + * Copyright (c) 2020-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 ApplicationController.cpp + * \author (last) Dara Navaei + * \date (last) 26-Mar-2024 + * \author (original) Behrouz NematiPour + * \date (original) 26-Aug-2020 + * + */ +#include "ApplicationController.h" + +// Qt +#include + +// Project +#include "MainTimer.h" +#include "MessageDispatcher.h" +#include "Logger.h" +//#include "FileHandler.h" +#include "Settings.h" +#include "MSettings.h" + +/*! + * \brief ApplicationController::ApplicationController + * \details Constructor + * \param parent - QObject parent owner object. + * Qt handles the children destruction by their parent objects life-cycle. + */ +ApplicationController::ApplicationController(QObject *parent) : QObject(parent) { + _post.setParent(this); +} + +/*! + * \brief ApplicationController initializer + */ +bool ApplicationController::init() +{ + if ( _init ) return false; + _init = true; + + initConnections(); + LOG_DEBUG(QString("%1 Initialized").arg(metaObject()->className())); + + return true; +} + +/*! + * \brief ApplicationController::init + * \details Initialized the Class by calling the init() method first + * And initializes the thread vThread by calling initThread + * on success init(). + * \param vThread - the thread + * \return returns the return value of the init() method + */ +bool ApplicationController::init(QThread &vThread) +{ + if ( ! init() ) return false; + initThread(vThread); + return true; +} + +/*! + * \brief ApplicationController::quit + * \details quits the class + * Calls quitThread + */ +void ApplicationController::quit() +{ + // disabled coco begin validated: Application termination is not correctly done in coco!!! + // it has been tested and works perfectly fine in normal run. + quitThread(); // validated +} +// disabled coco end + +/*! + * \brief ApplicationController::initConnections + * \details Initializes the required signal/slot connection between this class and other objects + * to be able to communicate. + */ +void ApplicationController::initConnections() +{ connect(&_MainTimer , SIGNAL( didTimeout()), + this , SLOT(onMainTimerTimeout())); + + // From HD/DG + connect(&_MessageDispatcher, SIGNAL(didFailedTransmit(Sequence)), + this , SLOT( onFailedTransmit(Sequence))); + + connect(&_Logger , SIGNAL(didExportLogs ()), + this , SLOT( onExport ())); + connect(&_Logger , SIGNAL(didExportStat (quint32, const QString &, quint8)), + this , SLOT( onExportStat (quint32, const QString &, quint8))); + connect(&_Logger , SIGNAL(didLogIOFail ()), + this , SLOT( onLogIOFail ())); + + connect(&_post , SIGNAL( didDone (bool )), + this , SLOT (onPOSTDone (bool ))); + + connect(&_settingsWatcher, SIGNAL(finished ()), + this , SLOT(onSettingsUpdate())); +} + +/*! + * \brief ApplicationController::initThread + * \details Moves this object into the thread vThread. + * And checks that this method is called from main thread. + * Also connects quitThread to application aboutToQuit. + * \param vThread - the thread + */ +void ApplicationController::initThread(QThread &vThread) +{ + // runs in main thread + Q_ASSERT_X(QThread::currentThread() == qApp->thread(), __func__, "The Class initialization must be done in Main Thread" ); + _thread = &vThread; + _thread->setObjectName(QString("%1_Thread").arg(metaObject()->className())); + connect(qApp, SIGNAL(aboutToQuit()), this, SLOT(quit())); + _thread->start(); + moveToThread(_thread); +} + +/*! + * \brief ApplicationController::quitThread + * \details Moves this object to main thread to be handled by QApplication + * And to be destroyed there. + */ +void ApplicationController::quitThread() +{ + // disabled coco begin validated: Application 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 +} +// disabled coco end + +/*! + * \brief ApplicationController::onFailedTransmit + * \details Called when failed to get a response back from the HD + * \param seq - The sequence that failed to send to the HD. + */ +void ApplicationController::onFailedTransmit(Sequence seq) +{ + emit didFailedTransmit(seq); +} + +/*! + * \brief Process the requested action + * \details Processes the requested action + * \param vAction - User requested Action + * \param vData - Action data to be transmitted. + */ +void ApplicationController::onActionTransmit(const QVariantList &vData) +{ + emit didActionTransmit(vData); +} + +/*! + * \brief An action has been confirmed + * \details GUI requested an action. + * In response HD confirmed the action. + * \param vAction - Received action + * \param vData - data of the action + */ +void ApplicationController::onActionReceive (const QVariantList &vData) +{ + emit didActionReceive(vData); +} + +/*! + * \brief ApplicationController::onMainTimerTimeout + * \details This slot is called by MainTimer::didTimeout each second + * to call required methods like keepAlive + */ +void ApplicationController::onMainTimerTimeout() +{ + keepAlive(); +} + +/*! + * \brief ApplicationController::onExport + * \details the slot which will be called by logger is done exporting. + */ +void ApplicationController::onExport() +{ + // disabled coco begin validated: This needs user interaction to plug-in USB device + // has been tested manually + emit didExport(); +} + +void ApplicationController::onExportStat(quint32 vIndex, const QString &vFileName, quint8 vPercent) +{ + // DEBUG: qDebug() << "1" << vIndex << vFileName << vPercent; + emit didExportStat(vIndex, vFileName, vPercent); +} +// disabled coco end + +/*! + * \brief ApplicationController::keepAlive + * \details This is the message which has to be send over the CANBus + * as an monitor for other nodes on the bus to notify UI is alive + */ +void ApplicationController::keepAlive() +{ + // DEBUG: return; +#ifndef DISABLE_KEEP_ALIVE + QVariantList mData; + int mFakeDataLen = gFakeData.length(); + // coco begin validated: This is a fake data generator for CANBus missing/swapped frames Testing + // will never be executed on the product + // has been tested manually + if (mFakeDataLen) { + if (gFakeSeqAtBegin) { + createFakeSeqAtBeginLongMessage(mData, mFakeDataLen); + } + else { + createFakeSequencedLongMessage (mData, mFakeDataLen); + } + } + // disabled coco end + else { + mData += 0; + } + onActionTransmit(mData); +#endif +} + +/*! + * \brief ApplicationController::createFakeSequencedLongMessage + * \details This method is creating the fake message with frame sequence + * which we use for Denali Message test + * \param vFakeDataLen + */ +void ApplicationController::createFakeSequencedLongMessage(QVariantList &vData, const int vFakeDataLen) +{ + // disabled coco begin validated: This is a fake data generator for CANBus missing/swapped frames Testing + // will never be executed on the product + // has been tested manually + QByteArray data; + if (vFakeDataLen == 1 && gFakeData == QByteArray::fromHex(gFakeData_default)) { + static quint16 txCount = 0; + Types::U16 seq; + quint8 dataBytesLeft = 0; + const quint8 crcBytesLen = 2; + for (int i = 0; i < 13; i++) { + switch (i) { + case 0: // First frame : HEAD + seq.value = txCount; + data += seq.bytes[0]; + data += seq.bytes[1]; + break; + + case 12: // Last frame with CRC + dataBytesLeft = 8 - sizeof(seq) - crcBytesLen; + for (int j = 0; j < dataBytesLeft; j++) { + data += (char)(0); + } + seq.value = txCount; + data += seq.bytes[0]; + data += seq.bytes[1]; + break; + + default: // Middle Frames + dataBytesLeft = 8 - sizeof(seq); + for (int j = 0; j < dataBytesLeft; j++) { + data += (char)(0); + } + seq.value = txCount; + data += seq.bytes[0]; + data += seq.bytes[1]; + break; + + } + Types::safeIncrement(txCount); + } + vData += QByteArray::fromHex(data.toHex()); + } + else { + vData += gFakeData; + } +} +// disabled coco end + +/*! + * \brief ApplicationController::createFakeSequencedAtBeginLongMessage + * \details This method is creating the fake message with frame sequence + * which we use for Denali Message test + * \param vFakeDataLen + */ +void ApplicationController::createFakeSeqAtBeginLongMessage(QVariantList &vData, const int vFakeDataLen) +{ + // disabled coco begin validated: This is a fake data generator for CANBus missing/swapped frames Testing + // will never be executed on the product + // has been tested manually + QByteArray data; + if (vFakeDataLen == 1 && gFakeData == QByteArray::fromHex(gFakeData_default)) { + static quint32 txCount = 0; + Types::U32 seq; + quint8 dataBytesLeft = 0; + const quint8 crcBytesLen = 2; + for (int i = 0; i < 13; i++) { + switch (i) { + case 0: // First frame : HEAD + seq.value = txCount; + data += seq.bytes[0]; + data += seq.bytes[1]; + //data += seq.bytes[3]; // Chopped off + //data += seq.bytes[4]; // Chopped off + break; + + case 12: // Last frame with CRC + seq.value = txCount; + data += seq.bytes[0]; + data += seq.bytes[1]; + data += seq.bytes[2]; + data += seq.bytes[3]; + dataBytesLeft = 8 - sizeof(seq) - crcBytesLen; + for (int j = 0; j < dataBytesLeft; j++) { + data += (char)(0); + } + break; + + default: // Middle Frames + dataBytesLeft = 8 - sizeof(seq); + seq.value = txCount; + data += seq.bytes[0]; + data += seq.bytes[1]; + data += seq.bytes[2]; + data += seq.bytes[3]; + for (int j = 0; j < dataBytesLeft; j++) { + data += (char)(0); + } + break; + } + Types::safeIncrement(txCount); + } + vData += QByteArray::fromHex(data.toHex()); + } + else { + vData += gFakeData; + } +} +// disabled coco end + +/*! + * \brief ApplicationController::initSettings + * \details The external method available to request for initializing the settings + * To start the task in Application Tread, emits a signal which will call a slot to take care of the execution. + */ +void ApplicationController::initSettings() +{ + QFuture mFuture = QtConcurrent::run( [=]() -> int { // made the call a lambda to make sure there is no function to accidentally being called, out of thread [developer safety]. + //TODO The Settings shall be the Singleton SettingsController and modify the MSettings like the others. + return Storage::Settings::readConfigurations(); + }); + _settingsWatcher.setFuture(mFuture); +} + +/*! + * \brief ApplicationController::onPOSTDone + * \details Sends the POST Final message + */ +void ApplicationController::onPOSTDone(bool /*vPass*/) { + LOG_DEBUG("ApplicationPOST Done"); + qDebug() << "POST DONE"; + /// in manufacturing or update mode the configurations must reside in /root/home + /// therefore the settings can be initialized after POST. + initSettings(); +} + +/*! + * \brief ApplicationController::onstartPOST + * \details The POST entry point + * - Sends the first async check-in to the HD to let HD know it can start it's POST and UI is ready to communicate. + * - Connects to the POST process to be able to trigger an alarm during the POST to be listed in the active alarms list. + * - Connects to the POST process to be able to send the final UI POST result. + * - Starts the UI POST + * \return void + */ +void ApplicationController::onstartPOST() { + LOG_DEBUG("ApplicationPost Start"); + _post.start(); +} + +/*! + * \brief onSettingsUpdate + * \details when the Settings reads the .conf files and fills the MSettings emits this finished signal + * then this slot is called to notify the GuiController about the settings that being ready. + */ +void ApplicationController::onSettingsUpdate() +{ + // TODO add new things + /* + onActionReceive(SettingsData()); + + /// POST /// + //call initialization functions when setting's ready. + //TODO move the initSettig in the Application POST since it is part of the post. + _settingsError = _settingsWatcher.result(); + //DEBUG qDebug() << " ***** " << sender() << _settingsError; + _post.isDone( ! _settingsError ); + if ( _settingsError ) { + LOG_APPED_PO(QString("Settings read failed")); + alarmTrigger(Gui::GuiAlarmID::ALARM_ID_HD_UI_POST_FAILURE_SETTINGS_BAD); + } else { + _Settings.datetimeFormat(); + } + + // this singal can be emmited here [ others are postponed to the onTreatmentRangesDone ] + // here we know the CRC was fine with not empty configuration and all files can be read. + emit didSettingsDone( ); // MessageDispatcher -> MessageInterpreter : updateUnhandledMessages + */ + qDebug() << "Call"; + _settingsError = _settingsWatcher.result(); + emit didSettingsDone( ); // MessageDispatcher -> MessageInterpreter : updateUnhandledMessag +} + +/*! + * \brief ApplicationController::onLogIOFail + * \details This slot is when an SD I/O failure is indicated + */ +void ApplicationController::onLogIOFail() +{ + //DEBUG qDebug()<<"SENDING " << Gui::GuiAlarmID::ALARM_ID_HD_UI_SDCARD_FAILURE; +} + + +void ApplicationController::onQuitApplication() +{ + //DEBUG qDebug() << metaObject()->className() << __FUNCTION__ << QThread::currentThread(); + emit didQuitApplication(); + qApp->quit(); +} + Index: sources/ApplicationController.h =================================================================== diff -u --- sources/ApplicationController.h (revision 0) +++ sources/ApplicationController.h (revision 5a248f0a45889844aa027f4f4a0661aa539975f0) @@ -0,0 +1,145 @@ +/*! + * + * Copyright (c) 2020-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 ApplicationController.h + * \author (last) Dara Navaei + * \date (last) 26-Mar-2024 + * \author (original) Behrouz NematiPour + * \date (original) 26-Aug-2020 + * + */ +#pragma once + +// Qt +#include + +// Project +#include "main.h" // Doxygen : do not remove +#include "ApplicationPost.h" +#include "MessageGlobals.h" + +// define +#define _ApplicationController ApplicationController::I() + +// forward declarations +class tst_initializations; + +// namespace +using namespace Can; + +/*! + * \brief The ApplicationController class + * \details Singleton class which is the main gateway of all signal/slots. + * This class is the main gate keeper for decisions and will decide if a message should pass through the other observers. + * Currently (08/30/2020) is a bridge and will pass all the messages. + * Later with implementation/help of the main state machine will decide on the state of the device what needs to be done. + * \note States are like [Idle, Run on Battery, Fault, FW connection lost] and such + */ +class ApplicationController : public QObject { + Q_OBJECT + + // Singleton + SINGLETON(ApplicationController) + + // friends + friend class ::tst_initializations; + + QThread *_thread = nullptr; + bool _init = false; + + QFutureWatcher _settingsWatcher; + int _settingsError = 0; + + ApplicationPost _post; // I may need to be put in a concurrent. + +public: + void initSettings(); + +public slots: + bool init(); + bool init(QThread &vThread); + void quit(); + +private: + void initConnections(); + + void initThread(QThread &vThread); + void quitThread(); + + void initSetttings(); + + void keepAlive(); + + void createFakeSequencedLongMessage (QVariantList &vData, const int vFakeDataLen); + void createFakeSeqAtBeginLongMessage(QVariantList &vData, const int vFakeDataLen); + + void versionsRequest(); + void institutionalRequest(); + +private slots: // Should be private for thread safety and is connected internally. + void onMainTimerTimeout(); + + void onActionReceive (const QVariantList &vData); // UI <= HD/DG + void onActionTransmit(const QVariantList &vData); + + void onExport (); + void onExportStat (quint32 vIndex, const QString &vFileName, quint8 vPercent); + + void onFailedTransmit(Sequence seq); + + void onSettingsUpdate(); + + void onPOSTDone (bool vPass); + + void onQuitApplication (); + + void onLogIOFail(); + +signals: + +signals: + void didActionReceive (const QVariantList &vData); // UI <= HD/DG + void didActionTransmit(const QVariantList &vData); // UI => HD/DG + void didFailedTransmit(Sequence seq); + + void didExport (); + void didExportStat (quint32 vIndex, const QString &vFileName, quint8 vPercent); + /*! + * \brief didSettingsInit - private signal to start initializing settings + * \details This signal used internally to make the read task of the settings happen in Application_Thread + * It's because no thread assigned to Settings itself, since this class will be used only once + * and does not need a thread by itself + */ + void didSettingsInit (QPrivateSignal); + /*! + * \brief didSettingsDone + * \details This signal will be emitted when the settings are read and ready to be used. + */ + void didSettingsDone (); + /*! + * \brief didPOSTPass + * \details This signal will be emitted when UI is done with the POST and will let other layers to know the result. + * As an example the Manufacturing or update will not start if the POST failed since it needs POST info for the Encrypted partition. + * \param vPass - true if passed. + */ + void didPOSTPass (bool vPass); + /*! + * \brief didKeepAliveBegin + * \details this signal will be emitted by ApplicationController + * when it is done with all the messaging and has nothing to say + * just to keep the conversation alive and let HD know UI is alive. + */ + void didKeepAliveBegin (); + /*! + * \brief didQuitApplication + * \details this signal is a placeholder for any farther notification to any class which needs to close itself. + * it will be used to let the other classes to stop and move to main thread. + */ + void didQuitApplication (); + + SAFE_CALL(startPOST) +}; Index: sources/ApplicationPost.cpp =================================================================== diff -u --- sources/ApplicationPost.cpp (revision 0) +++ sources/ApplicationPost.cpp (revision 5a248f0a45889844aa027f4f4a0661aa539975f0) @@ -0,0 +1,45 @@ +/*! + * + * Copyright (c) 2020-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 ApplicationPost.cpp + * \author (last) Behrouz NematiPour + * \date (last) 22-Apr-2024 + * \author (original) Behrouz NematiPour + * \date (original) 26-Aug-2020 + * + */ +#include "ApplicationPost.h" +// Qt +#include +#include +#include + +// Project +#include "Logger.h" +#include "FileHandler.h" + +/*! + * \brief ApplicationPost::ApplicationPost + * \details Constructor + * \param parent - QObject parent owner object. + * Qt handles the children destruction by their parent objects life-cycle. + */ +ApplicationPost::ApplicationPost(QObject *parent) : QObject(parent) { } + +/*! + * \brief ApplicationPost::start + * \details Starting the post application initialization + * \return + */ +void ApplicationPost::start() +{ + _isDone = true; + + emit didDone(_isDone); +} + + Index: sources/ApplicationPost.h =================================================================== diff -u --- sources/ApplicationPost.h (revision 0) +++ sources/ApplicationPost.h (revision 5a248f0a45889844aa027f4f4a0661aa539975f0) @@ -0,0 +1,98 @@ +/*! + * + * Copyright (c) 2020-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 ApplicationPost.h + * \author (last) Behrouz NematiPour + * \date (last) 10-Sep-2023 + * \author (original) Behrouz NematiPour + * \date (original) 26-Aug-2020 + * + */ +#pragma once + +// Qt +#include + +// Project + +/*! + * \brief The ApplicationPost class + * \details The Application POST class is checking for the list of POST items which has been done during the boot-up, + * from withing the run.sh and looks into the post.log to make sure the check items are passed. + * Among the list POST items: + * - Application checksum + * - CANBus + * - Display + * - Touch + * - SDCard + * - RTC + * - WiFi + * - Bluetooth + * - Ethernet + * - Sound + * + * Two items: + * - Ethernet + * - Sound + * are not faults and will not even send the AlarmTrigger. + * + * Two items: + * - WiFi + * - Bluetooth + * are two faults and will send the AlarmTrigger, but will not affect the UI POST final result. + * + * One item: + * - Application checksum + * for now is only done during boot-up and if the checksum is not correct then the UI Application will not even run. + * \todo Later could be a good idea to run another Application, that shows the Fault Screen only (something like a Red Screen Of Death). + * + */ +class ApplicationPost : public QObject +{ + Q_OBJECT + + QString _content; + // WARNING: these variables has to match with the ones in the run.sh. + const QString _postmsg_postfix_passed = " passed" ; // POSTMSG_POSTFIX_PASSED=" passed" + const QString _postmsg_postfix_failed = " failed" ; // POSTMSG_POSTFIX_FAILED=" failed" + + const QString _postmsg_osversion = "DIALITY_VERSION_ID=" ; // DIALITY_VERSION_ID="0.0.40" + const QString _postmsg_osbuild = "BUILD_ID=" ; // BUILD_ID="20230628230011" + const QString _postmsg_canbus = "CANBus" ; // POSTMSG_CANBUS="CANBus" + const QString _postmsg_sdcard = "SD-CARD" ; // POSTMSG_SDCARD="SD-CARD" + const QString _postmsg_touch = "Touch" ; // POSTMSG_TOUCH="Touch" + const QString _postmsg_rtc = "RTC" ; // POSTMSG_RTC="RTC" + const QString _postmsg_wifi = "WiFi" ; // POSTMSG_WIFI="WiFi" + const QString _postmsg_bluetooth = "Bluetooth" ; // POSTMSG_BLUETOOTH="Bluetooth" + const QString _postmsg_shasum = "App shasum" ; // POSTMSG_SHASUM="App shasum" + const QString _postmsg_cloudsync = "CloudSync" ; // POSTMSG_CLOUDSYNC="CloudSync" + + const quint8 _macAddrssLen = 17 ; // ff:ff:ff:ff:ff:ff + const quint8 _macAppearLen = 200 ; // the mac address shall be found within the next 200 characters. + const QString _devEthernet = "eth0:" ; + const QString _devWireless = "wlan0:" ; + const QString _devBluetooth = "hci0:" ; + const QString _macEthernetLabel = "link/ether " ; // the last space is important + const QString _macWirelessLabel = "link/ether " ; // the last space is important + const QString _macBluetoothLabel = "BD Address: " ; // the last space is important + QString _osVersion = "" ; + QString _macEthernet = "" ; + QString _macWireless = "" ; + QString _macBluetooth = "" ; + + const int _yearMinimum = 2022 ; // The year to check for minimum + bool _isDone = false ; + +public: + explicit ApplicationPost(QObject *parent = nullptr); + void start(); + +signals: + void didDone(bool vPass); + + +}; Index: sources/model/settings/MBluetooth.cpp =================================================================== diff -u --- sources/model/settings/MBluetooth.cpp (revision 0) +++ sources/model/settings/MBluetooth.cpp (revision 5a248f0a45889844aa027f4f4a0661aa539975f0) @@ -0,0 +1,44 @@ +/*! + * + * 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 MBluetooth.cpp + * \author (last) Vy + * \date (last) 05-Sep-2023 + * \author (original) Behrouz NematiPour + * \date (original) 24-Aug-2021 + * + */ + +#include "MBluetooth.h" + +using namespace Model; + +MBluetooth::MBluetooth(InterfaceStates vState , + QString vDeviceAddr , + QString vDeviceName , + quint8 vDeviceBatt , + quint8 vDevicePair , + qint16 vError , + bool vValid , + QString vServiceAddr , + QString vServiceName , + QString vDetailAddr , + QString vDetailName , + QString vDetailValue ): + state (vState ), + deviceAddr (vDeviceAddr ), + deviceName (vDeviceName ), + deviceBatt (vDeviceBatt ), + devicePair (vDevicePair ), + error (vError ), + valid (vValid ), + serviceAddr (vServiceAddr ), + serviceName (vServiceName ), + detailAddr (vDetailAddr ), + detailName (vDetailName ), + detailValue (vDetailValue ){ +} Index: sources/model/settings/MBluetooth.h =================================================================== diff -u --- sources/model/settings/MBluetooth.h (revision 0) +++ sources/model/settings/MBluetooth.h (revision 5a248f0a45889844aa027f4f4a0661aa539975f0) @@ -0,0 +1,128 @@ +/*! + * + * 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 MBluetooth.h + * \author (last) Vy + * \date (last) 05-Sep-2023 + * \author (original) Behrouz NematiPour + * \date (original) 24-Aug-2021 + * + */ +#pragma once + +// Qt +#include +#include + +// Project +#include "main.h" // Doxygen : do not remove + +// namespace +namespace Model { + +class MBluetooth +{ + Q_GADGET + +public: + enum InterfaceStates { + eIS_Idle , + + eIS_Local_Init , + eIS_Local_Connect , + eIS_Local_Error_Invalid , + eIS_Local_Error_POST , + eIS_Local_Error_Off , + eIS_Local_Error_IO , + eIS_Local_Error , + eIS_Local_Disconnect , + + eIS_Scan_Start , + eIS_Scan_Reject , + eIS_Scan_NotFound , + eIS_Scan_Discover , + eIS_Scan_Found , + eIS_Scan_Stop , + eIS_Scan_Done , + + eIS_Device_Init , + eIS_Device_Start , + eIS_Device_Connect , + eIS_Device_Waiting , + eIS_Device_Error_Init , + eIS_Device_Error , + eIS_Device_Done , + eIS_Device_Disconnect , + + eIS_Service_Start , + eIS_Service_Invalid , + eIS_Service_Error , + eIS_Service_Discover , + eIS_Service_Detail , + eIS_Service_Detail_Error , + eIS_Service_Detail_Done , + eIS_Service_Done , + + eIS_Detail_Change , + eIS_Detail_Read , + eIS_Detail_Write , + eIS_Config_Read , + eIS_Config_Write , + + eIS_Close , + }; + Q_ENUM(InterfaceStates) + + struct BluetoothDevice { + QString addr = ""; ///< the device address + QString name = ""; ///< the device name + bool pair = 0; ///< the device pairing status + bool operator ==(const BluetoothDevice &vDevice) const { + return vDevice.addr == addr && + vDevice.name == name ; + } + QString toString() const { + return addr + "," + pair + "," + name; + } + }; + + InterfaceStates state ; + QString localAddr = ""; + QString localName = ""; + QString deviceAddr = ""; + QString deviceName = ""; + quint8 deviceBatt = 0; + quint8 devicePair = 0; + qint16 error = 0; + bool valid = 1; + QString serviceAddr = ""; + QString serviceName = ""; + + QString detailAddr = ""; + QString detailName = ""; + QString detailValue = ""; + + MBluetooth( + InterfaceStates vState = eIS_Idle, + QString vDeviceAddr = "", + QString vDeviceName = "", + quint8 vDeviceBatt = 0, + quint8 vDevicePair = 0, + qint16 vError = 0, + bool vValid = 1, + QString vServiceAddr = "", + QString vServiceName = "", + QString vDetailAddr = "", + QString vDetailName = "", + QString vDetailValue = "" + ); +}; +} + +typedef Model::MBluetooth BluetoothData; +typedef Model::MBluetooth::BluetoothDevice BluetoothDeviceData; +typedef QList BluetoothDeviceListData; Index: sources/model/settings/MSettings.cpp =================================================================== diff -u --- sources/model/settings/MSettings.cpp (revision 0) +++ sources/model/settings/MSettings.cpp (revision 5a248f0a45889844aa027f4f4a0661aa539975f0) @@ -0,0 +1,165 @@ +/*! + * + * 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 MSettings.cpp + * \author (last) Behrouz NematiPour + * \date (last) 27-May-2023 + * \author (original) Behrouz NematiPour + * \date (original) 29-Mar-2021 + * + */ +#include "MSettings.h" + +// Qt +#include + +// Project +#include "StorageGlobals.h" + +// name spaces +using namespace Storage; + +/*! + * \brief MSettings::MSettings + * \details The constructor + * \param parent + */ +MSettings::MSettings(QObject *parent) : QObject(parent) { } + +/*! + * \brief MSettings::keys + * \details returns all keys in a group + * \param vGroup - the group to look for all the keys + * \return list of the keys in QString for the group vGroup + */ +QStringList MSettings::keys(const QString &vCategory, const QString &vGroup) +{ + const QMutexLocker locker(&_mutex); + QStringList mKeys; + TKeyValues keyValues = _settings[vCategory][vGroup]; + for (const auto &keyValue: keyValues ) { + mKeys += keyValue.key(); + } + return mKeys; +} + +QString MSettings::key(const QString &vCategory, const QString &vGroup, uint vIndex) +{ + QStringList mKeys = keys(vCategory, vGroup); + QString mKey = "[Unknown %1]"; + if ( vIndex < (unsigned)mKeys.count() ) { + mKey = mKeys [vIndex]; + } else { + mKey = mKey.arg(vIndex); + } + return mKey; +} + +/*! + * \brief MSettings::values + * \details returns all values in a group + * \param vGroup - the group to look for all the values + * \return list of the values in QString for the group vGroup + */ +QVariantList MSettings::values(const QString &vCategory, const QString &vGroup) +{ + QMutexLocker locker(&_mutex); + QVariantList mValues; + TKeyValues keyValues = _settings[vCategory][vGroup]; + for (const auto &keyValue: keyValues ) { + mValues += keyValue.val(); + } + return mValues; +} + +/*! + * \brief MSettings::value + * \details returns value of a key for the given group + * \param vGroup - the group to look for the value + * \param vKey - the key to look for the value + * \return the values in QString for the group vGroup + */ +QVariant MSettings::value(const QString &vCategory, const QString &vGroup, const QString &vKey) +{ + QMutexLocker locker(&_mutex); + TKeyValues mKeyValues = _settings[vCategory][vGroup]; + for (const auto &keyValue: mKeyValues ) { + if ( vKey == keyValue.key() ) return keyValue.val(); + } + return QVariant(); +} + +/*! + * \brief MSettings::groups + * \details The groups where have been read from the location vLocation + * \param vLocation - The location to look for the group(s) + * \return the groups in QString + */ +QStringList MSettings::groups(const QString &vCategory) +{ + QMutexLocker locker(&_mutex); + return _settings[vCategory].keys(); +} + +/*! + * \brief MSettings::categorys + * \details The list of all the categories in the setting + * \return the categories in QStringList + */ +QStringList MSettings::categorys() +{ + QMutexLocker locker(&_mutex); + return _settings.keys(); +} + +/*! + * \brief MSettings::add + * \details The function to be used to add elements in the settings + * \param vGroup - the group of the settings + * \param vKey - the key to be added under the group vGroup + * \param vValue - the value of the key to be added under group vGroup for the key vKey + */ +void MSettings::add(const QString &vCategory, const QString &vGroup, const QString &vKey, const QVariant &vValue, bool vEnableDuplicateKeys) +{ + QMutexLocker locker(&_mutex); + if ( vEnableDuplicateKeys ) { + _settings[vCategory][vGroup] += TKeyValue (vKey, vValue); + } + else { + TKeyValue keyValue (vKey, vValue); + TKeyValues keyValues = _settings[vCategory][vGroup]; + int index = keyValues.indexOf(keyValue); + if ( index >= 0 ) { + _settings[vCategory][vGroup].replace( index, keyValue ); + } + else { + _settings[vCategory][vGroup] += keyValue; + } + } +} + +QString MSettings::systemLocale () { return value(Storage::Settings_Category_Locale, "Localization", "Locale" ).toString(); } +QString MSettings::systemLanguage () { return value(Storage::Settings_Category_Locale, "Localization", "Language" ).toString(); } + +/********** The common helper functions **********/ + +/*! + * \brief Settings::getDatetimeFormat + * \details Get the date time format + * \return The String output of the data/time format. + */ +void MSettings::datetimeFormat() +{ + QString category = Storage::Settings_Category_SettingsSystem; + QVariant dateFotmat = _Settings.value(category, "Date", "Format"); + QVariant timeFotmat = _Settings.value(category, "Time", "Format"); + if (dateFotmat.isValid() && timeFotmat.isValid()) { + _dateFormat = dateFotmat.toString(); + _timeFormat = timeFotmat.toString(); + _datetimeFormat = _dateFormat + " " + _timeFormat; + } +} Index: sources/model/settings/MSettings.h =================================================================== diff -u --- sources/model/settings/MSettings.h (revision 0) +++ sources/model/settings/MSettings.h (revision 5a248f0a45889844aa027f4f4a0661aa539975f0) @@ -0,0 +1,102 @@ +/*! + * + * 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 MSettings.h + * \author (last) Behrouz NematiPour + * \date (last) 13-Sep-2023 + * \author (original) Behrouz NematiPour + * \date (original) 29-Mar-2021 + * + */ +#pragma once + +// Qt +#include +#include +#include + +// Project +#include "main.h" // Doxygen : do not remove + +// Define +#define _Settings Storage::MSettings::I() + +// namespace +namespace Storage { + +// FixMe: the model and controller need review and some functionalities need to be moved. +// FixMe: the controller should be the Singleton not the model. + +class MSettings : public QObject +{ + Q_OBJECT + + + typedef QString TCategory ; + typedef QString TGroup ; + typedef QString TKey ; + typedef QVariant TVal ; + + class KeyValue { + + TKey _key ; + TVal _val ; + + public: + KeyValue(const TKey &vKey, const TVal &vVal) : _key(vKey), _val(vVal) {} + TKey key() const { return _key.trimmed(); } + TVal val() const { return _val ; } + + void operator = (const TKey &vKey) { _key = vKey.trimmed(); } + void operator = (const TVal &vVal) { _val = vVal ; } + // void operator = (const KeyValue &vKeyValue) { + // _key = vKeyValue.key(); + // _val = vKeyValue.val(); + // } + + bool operator == (const KeyValue &vKeyValue) const { + return key() == vKeyValue.key(); + } + }; + typedef KeyValue TKeyValue ; + typedef QList < TKeyValue > TKeyValues; + typedef QMap < TGroup , TKeyValues > TData ; + typedef QMap < TCategory , TData > TSettings ; + + TSettings _settings ; + + QMutex _mutex; + + // Singleton + SINGLETON(MSettings) + +public: + QStringList categorys(); + QStringList groups (const QString &vCategory); + QStringList keys (const QString &vCategory, const QString &vGroup ); + QString key (const QString &vCategory, const QString &vGroup , uint vIndex); + QVariantList values (const QString &vCategory, const QString &vGroup ); + QVariant value (const QString &vCategory, const QString &vGroup , const QString &vKey); + + void add (const QString &vCategory, const QString &vGroup , const QString &vKey, const QVariant &vValue, bool vEnableDuplicateKeys); + + QString systemLocale (); + QString systemLanguage (); + +// the utility functions +private : QString _datetimeFormat = "MM/dd/yyyy HH:mm:ss"; +private : QString _dateFormat = "MM/dd/yyyy"; +private : QString _timeFormat = "HH:mm:ss"; +public : void datetimeFormat(); +public : QString getDatetimeFormat() { return _datetimeFormat;} +public : QString timeFormat() { return _timeFormat;} +public : QString dateFormat() { return _dateFormat;} +}; + +} + +typedef struct{} SettingsData; // it has literally been defined to be used for overloading Index: sources/model/settings/MWifiNetwork.h =================================================================== diff -u --- sources/model/settings/MWifiNetwork.h (revision 0) +++ sources/model/settings/MWifiNetwork.h (revision 5a248f0a45889844aa027f4f4a0661aa539975f0) @@ -0,0 +1,230 @@ +/*! + * + * 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 MWifiNetwork.h + * \author (last) Behrouz NematiPour + * \date (last) 26-Aug-2024 + * \author (original) Behrouz NematiPour + * \date (original) 11-May-2021 + * + */ +#pragma once + +// Qt +#include + +// Project + +// forward declarations +class tst_models; + +namespace Model { + + +/*! + * \brief The Network class + * Provides abstraction for a WiFi network + * \details Holds information about the WiFi network such as ssid, security, and connection status + * + */ +class MWifiNetwork { + +public: + enum SECURITY_TYPE { + UNSUPPORTED, + WEP, + WPA_TKIP, + WPA_TKIP_AES, + WPA_AES, + WPA2_AES, + WPA3_SAE_ONLY, + WPA3_WPA2_TRNS, + }; + + enum SIGNAL_LEVEL + { + NO_SIGNAL, + LVL_1, + LVL_2, + LVL_3, + LVL_4, + LVL_5 + }; + + enum STATUS { + NOT_CONNECTED, + CONNECTING, + CONNECTED, + DISCONNECTING + }; + + struct IPSettings { + QString mIPAddress; + QString mGateway; + QString mSubnetMask; + QString mBroadcast; + QString mDNS; + }; + + static SIGNAL_LEVEL convertSignalLevel(int vLevel) { + if (vLevel == 0) + return NO_SIGNAL; + else if (vLevel >= -50) + return LVL_5; + else if (vLevel >= -60) + return LVL_4; + else if (vLevel >= -70) + return LVL_3; + else if (vLevel >= -80) + return LVL_2; + else if (vLevel >= -90) + return LVL_1; + + return NO_SIGNAL; + } + + static QStringList securityTypesToStringList(QList vSecurityTypes) + { + QStringList result; + for (const SECURITY_TYPE &type : vSecurityTypes) + { + // NOTE : Order of case is very IMPORTANT + switch (type) { + case WPA3_SAE_ONLY: + result.append("WPA3_SAE"); + break; + case WPA3_WPA2_TRNS: + result.append("WPA3_WPA2"); + break; + case WPA2_AES: + result.append("WPA2_AES"); + break; + case WPA_AES: + result.append("WPA_AES"); + break; + case WPA_TKIP_AES: + result.append("WPA_TKIP_AES"); + break; + case WPA_TKIP: + result.append("WPA_TKIP"); + break; + case WEP: + result.append("WEP"); + break; + case UNSUPPORTED: + result.append("UNSUPPORTED"); + break; + default: + result.append("UNKNOWN"); + break; + } + } + return result; + } + + struct Data + { + + public: + + IPSettings mIPSettings; + + bool operator==(const Data &d1) { + if (ssid() == d1.ssid()) + return true; + return false; + } + + QString ssid() const { + return _ssid; + } + + void ssid(const QString &vSSID) { + _ssid = vSSID; + } + + QList securityTypes() const { + return _securityTypes; + } + + void securityTypes(const QList &vSecurityTypes) { + _securityTypes = vSecurityTypes; + } + + SIGNAL_LEVEL signalLevel() const { + return _signalLevel; + } + + void signalLevel(const SIGNAL_LEVEL &vLevel) { + _signalLevel = vLevel; + } + + STATUS status() const { + return _status; + } + + void status(const STATUS &vStatus) { + _status = vStatus; + } + + QString macAddress() const { + return _macAddress; + } + + void macAddress(const QString &vMacAddress) { + _macAddress = vMacAddress; + } + + bool requirePassword() const { + return _requirePassword; + } + + void requirePassword( const bool vRequirePassword ) { + _requirePassword = vRequirePassword; + } + explicit Data() {} + explicit Data(const QString &vMacAddress) { _macAddress = vMacAddress; } + explicit Data(const QString &vMacAddress, const QString &vSSID, const QList &vSecurityTypes, const STATUS &vStatus, const int &vSignalLevel) + : Data(vMacAddress, vSSID, vSecurityTypes, vStatus, vSignalLevel, true) + {} + + explicit Data(const QString &vMacAddress, const QString &vSSID, const QList &vSecurityTypes, const STATUS &vStatus, const int &vSignalLevel, const bool vIsRequirePassword) { + _macAddress = vMacAddress; + _ssid = vSSID; + _securityTypes = vSecurityTypes; + _status = vStatus; + _signalLevel = convertSignalLevel(vSignalLevel); + _requirePassword = vIsRequirePassword; + } + void clearSettings() { + mIPSettings.mIPAddress = ""; + mIPSettings.mDNS = ""; + mIPSettings.mGateway = ""; + mIPSettings.mSubnetMask = ""; + + _macAddress = ""; + _ssid = ""; + _securityTypes.clear(); + _status = NOT_CONNECTED; + _signalLevel = NO_SIGNAL; + _requirePassword = false; + } + + private: + QString _macAddress; + QString _ssid; + QList _securityTypes; + SIGNAL_LEVEL _signalLevel = NO_SIGNAL; + STATUS _status = NOT_CONNECTED; + bool _requirePassword = false; + }; + +}; + +} + +typedef Model::MWifiNetwork::Data WifiNetworkData; Index: sources/storage/FileHandler.cpp =================================================================== diff -u --- sources/storage/FileHandler.cpp (revision 0) +++ sources/storage/FileHandler.cpp (revision 5a248f0a45889844aa027f4f4a0661aa539975f0) @@ -0,0 +1,477 @@ +/*! + * + * Copyright (c) 2020-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 FileHandler.cpp + * \author (last) Behrouz NematiPour + * \date (last) 23-Apr-2024 + * \author (original) Behrouz NematiPour + * \date (original) 26-Aug-2020 + * + */ +#include "FileHandler.h" + +//Qt +#include +#include +#include +#include +#include + + +// Project +// #include "Logger.h" // logger should not be used in this class. +#include "StorageGlobals.h" + +extern QString gStandard_tmp; + +// namespace +using namespace Storage; + +/*! + * \brief FileHandler::write + * \details Writes the content of vContent into the file vFileName. + * \param vFileName - Source file name + * \param vContent - The content which is going to be written in the file. + * \param vAppend - if set to true the content will be appended at the end of the file. + * \return false if file cannot be opened. + */ +void FileHandler::errOut(const QString &vMessage) +{ + static uint count; + static QString mCritical; + // disabled coco begin validated : This has been manually test. Needs file system access to produce errors for hundred times. + if (mCritical != vMessage || !(count % 1000)) { + // disabled coco end + count = 0; + mCritical = vMessage; + QTextStream err(stderr); + err << "FS" << " " + << QDate::currentDate().toString("yyyy_MM_dd") << " " + << QTime::currentTime().toString("HH:mm:ss" ) << " " + << mCritical + << Qt::endl; + } + ++count; +} + +/*! + * \brief FileHandler::write + * \details writes content vContent into the file vFileName. + * appends to the file if vAppend is true otherwise overwrites the file content with vContent + * \param vFileName - Source file name + * \param vContent - content to be written into file. + * \param vAppend - append (true) or overwrite (false) + * \return false if file cannot be opened for write. + */ +bool FileHandler::write(const QString &vFileName, const QString &vContent, bool vAppend) +{ + QFile file(vFileName); + QFile::OpenMode openMode = vAppend ? + QFile::Text | QFile::Append : + QFile::Text | QFile::WriteOnly; + // disabled coco begin validated : This has been manually test. Needs file system access to make file the way it cannot be opened for writing. + if (! file.open(openMode)) { + QString msg = QString("Cannot open file for write (%1). Possible corrupted file system.").arg(vFileName); + // here cannot use LOG_XXXX because if the folder cannot be created then the log cannot be written. + errOut (msg); + return false; + } + // disabled coco end + QTextStream out(&file); + out << vContent; + out.flush(); + return true; +} + +/*! + * \brief FileHandler::read + * \details reads file vFilename content into vContent variable. + * \param vFileName - Source file name + * \param vContent - The content of the file which will be set when done. + * \return false if file cannot be opened. + */ +bool FileHandler::read(const QString &vFileName, QString &vContent, bool vAppend) +{ + QFile file(vFileName); + if (! file.open(QFile::Text | QFile::ReadOnly)) { + QString msg = QString("Cannot open file for read (%1). Possible corrupted file system").arg(vFileName); + // here cannot use LOG_XXXX because if the folder cannot be created then the log cannot be written. + errOut (msg); + return false; + } + QTextStream in(&file); + if ( vAppend ) { + vContent += in.readAll(); + } + else { + vContent = in.readAll(); + } + return true; +} + +/*! + * \brief FileHandler::backupFile + * \details Calls the gzip command + * the file is compressed and will be deleted by gzip + * the destination file is determined by gzip + * \param vSource - file to be compressed + * \return non-zero if successfull. + */ +int FileHandler::backupFile(const QString &vSource) +{ + QString cmd = "gzip"; + QStringList arguments; + arguments << vSource; + int result = QProcess::execute(cmd, arguments); + return result; +} + +/*! + * \brief FileHandler::copyFolder + * \details Copies all the file and folders recursively. + * \param vSource - The source folder + * \param vDestination - The destination folder + * \return True on successful execution. + * \note This method uses the Linux "cp -r vSource vDestination" command + * Not a Thread-Safe. + */ +int FileHandler::copyFolder(const QString &vSource, const QString &vDestination) +{ + // disabled coco begin validated: This needs user interaction to export to USB device + // has been tested manually since currently it is the only place it has been used. + QString cmd = "cp"; + QStringList arguments; + arguments << "-r" << vSource << vDestination; + int result = QProcess::execute(cmd, arguments); + return result; +} +// disabled coco end + +/*! + * \brief FileHandler::moveFolder + * \details Moves the source folder to destination. + * \param vFolder - The folder to be removed + * \return True on successful execution. + * \note This method uses the Linux "mv vSource vDestination" command + * Not a Thread-Safe. + */ +int FileHandler::moveFolder(const QString &vSource, const QString &vDestination) +{ + // disabled coco begin validated: This needs user interaction to check the file system + // has been tested manually since currently it is the only place it has been used. + QString cmd = "mv"; + QStringList arguments; + arguments << vSource << vDestination; + int result = QProcess::execute(cmd, arguments); + return result; +} +// disabled coco end + +/*! + * \brief FileHandler::removeFolder + * \details Removes the entire folder. + * \param vFolder - The folder + * \return True on successful execution. + * \note This method uses the Linux "rm -frd vSource vDestination" command + * <<<<< DANGEROUS BE CAREFUL >>>>> + * Not a Thread-Safe. + */ +int FileHandler::removeFolder(const QString &vFolder) +{ + // disabled coco begin validated: This needs user interaction to check the file system + // has been tested manually since currently it is the only place it has been used. + QString cmd = "rm"; + QStringList arguments; + arguments << "-frd" << vFolder; + int result = QProcess::execute(cmd, arguments); + return result; +} +// disabled coco end + +/*! + * \brief FileHandler::removeFiles + * \details + * \param vFolder + * \param vFilter + * \param vDateOlderThan + * \return + */ +int FileHandler::removeFiles(const QStringList &vFolders, const QStringList &vNameFilter, const QDate &vDateOlderThan) +{ + int countRemoved = 0; + for (const auto &folder : vFolders) { + QDir dir(folder); + dir.setFilter(QDir::Files | QDir::Hidden | QDir::NoSymLinks); + dir.setSorting(QDir::Time | QDir::Reversed); + QFileInfoList infoList = dir.entryInfoList(vNameFilter); + for (const auto &info : infoList) { + QDateTime fileTime = info.lastModified(); + QString fileName = info.absoluteFilePath(); + // disabled coco begin validated : This has been manually tested since requires to change in file system to reproduce the error. + if (fileTime.isValid()) { + if (fileTime.date() <= vDateOlderThan) { + if (QFile::remove(fileName)) { + ++countRemoved; + errOut(QString("%1 File(s) %2 removed").arg(countRemoved).arg(fileName)); + } + else { + errOut(QString("Cannot delete file : ") + fileName); + } + } + } + else { + errOut(QString("Cannot get last modified date of file : ") + fileName); + } + // disabled coco end + } + } + return countRemoved; +} + +/*! + * \brief FileHandler::makeFolder + * \details Create the folder vFolder if it does not exist. + * \param vFolder - the folder to create + * \return true on successful creation + */ +bool FileHandler::makeFolder(const QString &vFolder) +{ + QDir dir(vFolder); + if ( ! dir.exists(vFolder) ) { + if ( ! dir.mkpath(vFolder) ) { + QString msg = "Cannot create folder " + vFolder; + // here cannot use LOG_XXXX because if the folder cannot be created then the log cannot be written. + errOut(msg); + return false; + } + } + return true; +} + +/*! + * \brief FileHandler::isMounted + * \param vPath - the rootPath of the device mount point + * \return true - if the given vPath is not empty and is in list of mounted devices + * if so it also has to be ready and valid. + */ +bool FileHandler::isMounted(const QString &vPath, bool *vIsReadOnly) +{ + bool mounted = false; + // removing the extra '/' from the vPath if there is to be able to compare to the root path of the storage + QString path = vPath.trimmed(); + if (path.isEmpty()) return false; + int lastIndex = path.size() - 1; + if (path.at(lastIndex) == "/") path.remove(lastIndex, 1); + // check to see if the path in the list of mounted rootPaths + /// DEBUG: qDebug() << " +++++ " << QStorageInfo::mountedVolumes(); + // FIXME : This function blocks the Device controller thread + // It has been observed during the USB plug test and getting the drive info(space) that + // immediately after the mount this function laggs to get the information for about 2-5 sec. + auto mountedVolumes = QStorageInfo::mountedVolumes(); + foreach (const QStorageInfo &storage, mountedVolumes) { + if (storage.isValid() && storage.isReady()) { + if ( storage.rootPath() == path ) { + if (vIsReadOnly) *vIsReadOnly = storage.isReadOnly(); + mounted = true; + break; + } + } + } + return mounted; +} + +/*! + * \brief FileHandler::tmpUsable + * \details Checks if the temp folder is available for read and write to file and directory + * \note This function will only chek the temp folder usability once + * and next call will just return the first result, + * assuming that the temp file situation is not going to change and is stable. + * \return true on success + */ +bool FileHandler::tmpUsable() +{ + static bool ok = false; + static bool tested = false; + if ( tested ) return ok; + + QString tmp = gStandard_tmp; + QString tmpTestFolder = tmp + "tmp_test_folder" ; + QString tmpTestFile = tmp + "tmp_test_file" ; + QString tmpTestContent = "tmp_test_content"; + QDir dir(tmp); + if ( ! dir.exists() ) { ok = false; goto lOut; } + if ( ! FileHandler::makeFolder (tmpTestFolder )) { ok = false; goto lOut; } + if ( ! FileHandler::write (tmpTestFile , tmpTestContent )) { ok = false; goto lOut; } + if ( ! FileHandler::read (tmpTestFolder, tmpTestContent )) { ok = false; goto lOut; } +lOut: + tested = true; + return ok; +} + +/*! + * \brief FileHandler::find + * \details The function to find files. + * It mainly been implemented to find the files are using some amount of total storage in the vPath by percentage, + * and are the oldest. + * \param vPath - the path to search for the files + * \param vNameFilters - the files filter to search for. + * \param vRetainPercent - It means how many percentage of the space these file are able to retain. + * e.g. 90% retains means older files which use 10% of storage will be listed. + * e.g. 80% retains means older files which use 20% of storage will be listed. + * e.g. 0% retains means no file shall be retained, therefore all the files listed. + * e.g. 100% retains means all file shall be retained, therefore the list shall be empty. + * By default set t0 0 so do not have any size constraint + * \return list of the files found by their information. + * if vRetainPercent is used then it contains list of the file(s) to be removed. + */ +QFileInfoList FileHandler::find(const QString &vPath, QStringList vNameFilters, quint8 vRetainPercent) { + // disabled coco begin validated: Manually tested. Needs to fill up the storage to test some functionalities like vRetainPercent + + QFileInfoList fileInfoList; + // if all the files need to retain then no file shall be listed in the remove list. + if ( vRetainPercent == 100 ) return fileInfoList; + + // if the path is incorrect return with empty list + QDir dir(vPath); + if (!dir.exists()) return fileInfoList; + + // get the storage total + QStorageInfo storage(dir); + quint64 totalSizeStorage = storage.bytesTotal(); + + // get list of all the files in the path by the filter + QFileInfoList fileInfoListAll = find(vPath, vNameFilters); + + // if there is no file in the path with that filter then return empty list. + if (fileInfoListAll.count() == 0) return fileInfoList; + + // if vRetainPercent is 0 means all needs to be removed. + if (vRetainPercent == 0) return fileInfoListAll; + + // get the files total + quint64 totalSizeFiles = totalSize(fileInfoListAll); + + // the total size that all the + quint64 totalSizeRetain = totalSizeStorage * (vRetainPercent / 100.0); + + // if already totalSizeFiles <= totalSizeRetain, do not go any further and return empty list. + if (totalSizeFiles <= totalSizeRetain) return fileInfoList; + + // gets each file size from oldest to newest + // checks if the total files size subtracted by the current file size will be less that limit + // if it is breaks + // else adds the file to the list and continues. + // DEBUG: qDebug() << "%" << totalSizeStorage << totalSizeFiles << totalSizeRetain << totalSizeFiles - totalSizeRetain << vRetainPercent; + quint64 totalSizeRemoved = 0; Q_UNUSED(totalSizeRemoved); + for (auto it = fileInfoListAll.crbegin(); it != fileInfoListAll.crend(); ++it) { + // (totalSizeFiles <= totalSizeRetain) has been checked above and didn't return; , + // so at least one file should be checked and then check again in the loop. + quint64 size = it->size(); + totalSizeRemoved += size; + totalSizeFiles -= size; + fileInfoList += *it; + /// DEBUG: since it has been manually tested this will help next time for test. + /// debugging the find function + // qDebug() << QString("%1 , %2 , %3 , %4 , %5") + // .arg(totalSizeFiles , 12) + // .arg(size , 12) + // .arg(totalSizeRemoved , 12) + // .arg(it->lastModified().toString("yyyy-MM-dd-HH:mm")) + // .arg(it->fileName()) + // ; + if (totalSizeFiles <= totalSizeRetain) break; + } + /// DEBUG: since it has been manually tested this will help next time for test. + /// the total size & count removed. + // qDebug() << QString("%1 , %2") + // .arg(totalSizeRemoved , 12) + // .arg(fileInfoList.count(), 3) + // ; + return fileInfoList; +} +// disabled coco end + +/*! + * \brief FileHandler::find + * \details The function to find files. + * \param vPath - the path to search for the files + * \param vNameFilters - the files filter to search for. + * \return list of the files found by their information. + * if vRetainPercent is used then it contains list of the file(s) to be removed. + */ +QFileInfoList FileHandler::find(const QString &vPath, QStringList vNameFilters) { + // disabled coco begin validated: Needs to manually create specific folder with specific files to check the functionality + // manually tested + QFileInfoList fileInfoList; + QDir dir(vPath); + if (!dir.exists()) return fileInfoList; + fileInfoList = dir.entryInfoList( + vNameFilters, + QDir::NoDotAndDotDot | QDir::Files, + // the sorting may require to change from QDir::Time to QDir::Name + // since the birthTime always returns invalid + // and as part of our log naming we have the birthTime in file name. + // be careful that this function is being used in cases other than only just logging. + QDir::Time + ); + return fileInfoList; +} +// disabled coco end + +quint64 FileHandler::totalSize(const QFileInfoList &vFileInfoList) { + // disabled coco begin validated: Manually tested. requires list of files on file system to test and requires manual specific files for test. + quint64 total = 0; + for (auto it = vFileInfoList.crbegin(); it != vFileInfoList.crend(); ++it) { + total += it->size(); + } + return total; +} +// disabled coco end + +/*! + * \brief FileHandler::subFolders + * \details Look for the sub-folder in folder vFolder. + * \param vFolder - the folder to search for the sub folders. + * \return list of the sub-folders in QString + */ +QStringList FileHandler::subFolders(const QString &vFolder) +{ + QDir dir(vFolder); + dir.setFilter(QDir::NoDotAndDotDot | QDir::Dirs | QDir::Hidden | QDir::Readable); + return dir.entryList(); +} + +/*! + * \brief FileHandler::isPathSymLink + * \details Determines if the path passed is a symbolic link + * \param vFilePath - the targeted filepath to check + * \return true if path is a symbolic link, false otherwise + */ +bool FileHandler::isPathSymLink(const QString &vFilePath) +{ + QFileInfo fileInfo(vFilePath); + return fileInfo.canonicalFilePath() != fileInfo.filePath(); +} + +/*! + * \brief FileHandler::sha256sum + * \param vFileName - the file name including path to generate the sha256sum for + * \param vOk - if used will contain the success/true, fail/false of the checksum generation + * \return The checksum result in hex sting. + */ +QString FileHandler::sha256sum(QString vFileName, bool *vOk) { + bool ok = true; + QByteArray shasum; + QCryptographicHash hash(QCryptographicHash::Sha256); + QFile file(vFileName); + if ( ! file.open(QIODevice::ReadOnly)) { ok = false; goto lOut; } + hash.addData(&file); + shasum = hash.result().toHex(); +lOut: + if (vOk) *vOk = ok; + return shasum; +} Index: sources/storage/FileHandler.h =================================================================== diff -u --- sources/storage/FileHandler.h (revision 0) +++ sources/storage/FileHandler.h (revision 5a248f0a45889844aa027f4f4a0661aa539975f0) @@ -0,0 +1,168 @@ +/*! + * + * Copyright (c) 2020-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 FileHandler.h + * \author (last) Behrouz NematiPour + * \date (last) 04-Apr-2024 + * \author (original) Behrouz NematiPour + * \date (original) 26-Aug-2020 + * + */ +#pragma once + +// Qt +#include +#include + +// forward declarations +class tst_fileHandler; + +class QDate; + +namespace Storage { + +/*! + * \brief The FileHandler class + * This class is suppose to be the static class + * which contains methods to manipulate files and folders + * Since it is static it does not have thread so needs to be used with care + * and the classes which are using it need to take care of the threading. + */ +class FileHandler +{ + // friends + friend class ::tst_fileHandler; + +public: + enum FileCopyError_Enums { + eOK , + + eSrcFolderNotExist , + eSrcFileNotExist , + eDstFolderNotExist , + eDstFolderMakeError , + + eSrcOpenError , + eDstOpenError , + + eSrcReadError , + eSrcSymlinkError , + eDstWriteError , + eDstFlushError , + }; + +public: + static void errOut (const QString &vMessage); + + static bool write (const QString &vFileName, const QString &vContent, bool vAppend = true ); + static bool read (const QString &vFileName, QString &vContent, bool vAppend = false); + + static int backupFile (const QString &vSource); + static int copyFolder (const QString &vSource , const QString &vDestination); + static int moveFolder (const QString &vSource , const QString &vDestination); + static int removeFolder(const QString &vFolder); + static int removeFiles (const QStringList &vFolders, const QStringList &vNameFilter, const QDate &vDateOlderThan); + static bool makeFolder (const QString &vFolder); + static bool isMounted (const QString &vPath, bool *vIsReadOnly = nullptr); + + static bool tmpUsable (); + + static QFileInfoList find(const QString &vPath, QStringList vNameFilters, quint8 vRetainPercent); + static QFileInfoList find(const QString &vPath, QStringList vNameFilters); + static quint64 totalSize(const QFileInfoList &vFileInfoList); + + static QStringList subFolders(const QString &vFolder); + + static bool isPathSymLink(const QString &vFilePath); + + static QString sha256sum( QString vFileName, bool *vOk = nullptr ); + + + /*! + * \brief FileHandler::copyFile + * \details Copies a file chunk by chunk + * \return + */ + template + static int copyFile(const QString &vSource, const QString &vDestination, const QString &vFileName, const NotifierFunction *notifier = nullptr, quint32 vIndex = 0) + { + FileCopyError_Enums err = eOK; + QDir srcDir = QFileInfo (vSource ).absoluteDir(); + QDir dstDir = QFileInfo (vDestination ).absoluteDir(); + QFile srcFile = QFile (vSource + vFileName); + QFile dstFile = QFile (vDestination + vFileName); + qint64 totalSize = 0; + qint64 copySize = 0; + quint32 chunkSize = 1024 * 2; + bool createFolder = true; + + static quint8 mO_CopyPercent = 0; quint8 mC_CopyPercent = 0; + + if ( isPathSymLink(vSource + vFileName) ) { err = eSrcSymlinkError ; goto lErr; } + if ( ! srcDir .exists() ) { err = eSrcFolderNotExist ; goto lErr; } + if ( ! srcFile.exists() ) { err = eSrcFileNotExist ; goto lErr; } + + if ( ! createFolder ) { if ( ! dstDir.exists() ) { err = eDstFolderNotExist ; goto lErr; }} + else { if ( ! dstDir.mkpath ( dstDir.path() ) ) { err = eDstFolderMakeError ; goto lErr; }} + + + if ( ! srcFile.open(QIODevice::ReadOnly )) { err = eSrcOpenError ; goto lErr; } + if ( ! dstFile.open(QIODevice::WriteOnly )) { err = eDstOpenError ; goto lErr; } + + totalSize = srcFile.size(); + copySize = totalSize; + + qDebug() << "start ..."; + qDebug() << "Src: " << vSource + << " " + << (totalSize > 1024*1024 ? (totalSize / 1024 / 1024) : (totalSize / 1024)) + << (totalSize > 1024*1024 ? "M" : "K" ); + qDebug() << "Dst: " << vDestination; + + while ( copySize ) { + char chunkData[chunkSize] = {}; + const qint64 readSize = srcFile.read (chunkData, chunkSize); + if ( readSize < 0 ) { err = eSrcOpenError ; goto lErr; } + const qint64 writeSize = dstFile.write(chunkData, readSize); + if ( writeSize < 0 ) { err = eDstWriteError ; goto lErr; } + + if ( readSize >= chunkSize ) { // a full chunk was available to read + copySize -= writeSize; // writeSize; + } + else { // Not a full chunk available EOF + copySize = 0; + } + + mC_CopyPercent = int(((float((totalSize - copySize))) / float(totalSize)) * 100); + if ( mO_CopyPercent != mC_CopyPercent ) { + mO_CopyPercent = mC_CopyPercent; + // qDebug() << "\r" << "%" << mC_CopyPercent; + if ( notifier ) { + (*notifier )(vIndex, vFileName, mC_CopyPercent); + } + } + } + + // close source + srcFile.close(); + + //DEBUG qDebug()<< dstFile.fileName() <<" destination file size : " << dstFile.size() << " src==dest ? " << (dstFile.size() == totalSize); + + // close destination + if ( ! dstFile.flush() ) { err = eDstFlushError ; goto lErr; } + dstFile.close(); + + qDebug() << "\nFinish"; + return eOK; + + lErr: + qDebug() << "\nError: " << err; + return err; + } + +}; +} Index: sources/storage/Logger.cpp =================================================================== diff -u --- sources/storage/Logger.cpp (revision 0) +++ sources/storage/Logger.cpp (revision 5a248f0a45889844aa027f4f4a0661aa539975f0) @@ -0,0 +1,623 @@ +/*! + * + * Copyright (c) 2020-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 Logger.cpp + * \author (last) Behrouz NematiPour + * \date (last) 08-Apr-2024 + * \author (original) Behrouz NematiPour + * \date (original) 26-Aug-2020 + * + */ +#include "Logger.h" + +// Qt +#include +#include +#include +#include +#include +#include +#include + +// Project +#include "ApplicationController.h" +#include "Threads.h" +#include "StorageGlobals.h" +#include "MainTimer.h" +#include "FileHandler.h" +#include "format.h" + +using namespace Storage; + +/*! + * \brief Logger::Logger + * \details Constructor + * \param parent - QObject parent owner object. + * Qt handles the children destruction by their parent objects life-cycle. + */ +Logger::Logger(QObject *parent) : QObject(parent) { + if ( ! gLogLongName ) { + _fileDateFormat = "yyyy_MM_dd"; // date used in the file name + } +} + +/*! + * \brief Logger::init + * \details Initializes the Class. + * \return False if it has been called before. + */ +bool Logger::init() +{ + if ( _init ) return false; + _init = true; + + // runs in thread + checkLogPath(); + initConnections(); + + return true; +} + +/*! + * \brief Logger::init + * \details Initialized the Class by calling the init() method first + * And initializes the thread vThread by calling initThread + * on success init(). + * \param vThread - the thread + * \return returns the return value of the init() method + */ +bool Logger::init(QThread &vThread) +{ + // disabled coco begin validated: Application is not running in multi-threaded mode for testing + // it has been tested and works perfectly fine in normal run. + if ( ! init() ) return false; + initThread(vThread); + return true; +} +// disabled coco end + +/*! + * \brief Logger::postInit + * \details these are the actions that should be executed after the logger has been initialized + * but also cannot be done in the Logger class since it is not immediately moving to the thread. + */ +void Logger::postInit() +{ + if ( ! checkThread() ) return; + ADD_APPED_HEADER; + ADD_DEBUG_HEADER; + LOG_DEBUG(QString("%1 Initialized").arg(metaObject()->className())); + LOG_DEBUG(QString("Application %1 Started").arg(qApp->applicationName())); + LOG_APPED_UI(qApp-> applicationVersion()); +} + +/*! + * \brief Logger::checkThread + * \details Checks the current thread to be the logger thread + * otherwise sends out a message to the console and returns false. + * \return false if not logger thread + */ +bool Logger::checkThread() +{ + bool ok = true; + if ( this->thread() != &Threads::_Logger_Thread ) { + qDebug() << " ----- " << "The main Log function rejection: The Logger is not initialized for proper use"; + ok = false; + } + return ok; +} + +/*! + * \brief Logger quit + * \details quits the class + * Calls quitThread + */ +void Logger::quit() +{ + // disabled coco begin validated: Application termination is not correctly done in coco!!! + // it has been tested and works perfectly fine in normal run. + quitThread(); // validated +} +// disabled coco end + +/*! + * \brief Logger::initConnections + * \details Initializes the required signal/slot connection between this class and other objects + * to be able to communicate. + * \note No connection has been defined yet. + */ +void Logger::initConnections() +{ + connect(this, SIGNAL(didLog(QString,LogType,bool)), + this, SLOT( onLog(QString,LogType,bool))); + + connect(&_MainTimer, SIGNAL( didDateChange ()), + this , SLOT( concurrentRemoveLogs())); + + connect(&_removeLogsWatcher, SIGNAL(finished ()), + this , SLOT(onRemoveLogs())); +} + +/*! + * \brief Logger::initThread + * \details Moves this object into the thread vThread. + * And checks that this method is called from main thread. + * Also connects quitThread to application aboutToQuit. + * \param vThread - the thread + */ +void Logger::initThread(QThread &vThread) +{ + // disabled coco begin validated: Application is not running in multi-threaded mode for testing + // it has been tested and works perfectly fine in normal run. + // runs in main thread + Q_ASSERT_X(QThread::currentThread() == qApp->thread() , __func__, "The Class initialization must be done in Main Thread" ); + _thread = &vThread; + _thread->setObjectName(QString("%1_Thread").arg(metaObject()->className())); + connect(qApp, SIGNAL(aboutToQuit()), this, SLOT(quit())); + _thread->start(); + moveToThread(_thread); +} +// disabled coco end +/*! + * \brief Logger::quitThread + * \details Moves this object to main thread to be handled by QApplication + * And to be destroyed there. + */ +void Logger::quitThread() +{ + if (! _thread) return; + + // runs in thread + moveToThread(qApp->thread()); // validated +} + +void Logger::onLog(const QString &vContent, LogType vLogType, bool vTimestamp) +{ + static bool notified = false; + if ( ! _logStorageReady && ! gDisableSDCFailLogStop ) { + if ( ! notified ) { + notified = true; + qDebug() << "Log storage not ready, logging rejected"; + } + return; + } + + log(vContent, vLogType, vTimestamp); +} + +/*! + * \brief Logger::checkLogPath + * \details Sets the log paths and creates them if didn't exist. + */ +void Logger::checkLogPath() +{ + // The POST detects the SD card mount state, but logging needs it before + // POST is complete, need to determine via code whether the SD card is mounted + QStorageInfo baseSDCard; + baseSDCard.setPath(SDCard_Base_Path_Name); + bool isSDMounted = !baseSDCard.isRoot(); // When the SD card is not mounted, the path is set to the mount-point's root, ie: '/' + + setLogBasePath(!isSDMounted); // if the SD is not mounted, use tmp folder; else use /media/sd-card + setLogPath ( ); // check and create log folders // Note: it may require to check for write access regarding device setup if using tmp +} + +/*! + * \brief Logger::setLogBasePath + * \details Tries to the set the log path to the default log path (Log_Base_Path_Name) + * Will set the application folder as the base log path if cannot set the log path to the default. + * Will log the event in that case. + * \param vUseTempPath + */ +void Logger::setLogBasePath(bool vUseTempPath) +{ +#ifndef BUILD_FOR_TARGET + // Not building for target, not using tmp location + vUseTempPath = false; +#endif + + if (vUseTempPath) { + _dir.setPath(gStandard_tmp); + // NOTE: Do not use LOG_XXXXX, At this moment Logger has not been initialized yet + QString msg = QString("temp location used for events logging (%1)").arg(_dir.path()); + //DEBUG qDebug() << msg; + FileHandler::errOut(msg); + } + else { + _dir.setPath(SDCard_Base_Path_Name); + } +} + +/*! + * \brief Logger::setLogPath + * \details set the log path for each of the Datum, Event, Error log types + * \return False if can not st the log paths. + */ +bool Logger::setLogPath() +{ + bool ok = true; + if ( ok && ! setLogPath(LogType::eLogAppED) ) ok = false; + if ( ok && ! setLogPath(LogType::eLogDebug) ) ok = false; + return ok; +} + +/*! + * \brief Logger::onCryptSetupMount + * \details the handler for the _DeviceController::didCryptSetupMount + * to set the treatment folder when the encrypted partition is ready + * and successfully decrypted and mounted. + * It checks to make sure the folder exist and is able to be written and read. + */ +void Logger::onCryptSetupMount(bool /*vPass*/) +{ + LogType vLogType = LogType::eLogTrtmt; + QString basePath = Storage::Settings_Path(); + + // use the Settings path first (/var/configurations (Encrypted Partition)) + if ( ! QDir (basePath ).exists( )) { basePath = gStandard_tmp; goto lOut; } + if ( ! FileHandler::makeFolder (basePath + Storage::Txr_Folder_Treatment )) { basePath = gStandard_tmp; goto lOut; } + +lOut: + _logPathNames[vLogType] = basePath + Storage::Txr_Folder_Treatment; + emit didLogPathSet(vLogType, _logPathNames[vLogType]); + + FileHandler::errOut(tr("The '%1' folder selected for the treatment reports").arg(_logPathNames[vLogType])); +} + +/*! + * \brief Logger::setLogPath + * \details Sets the log path for the log type vLogType + * Creates the folder if not exists. + * \param vLogType - log type + * \return returns false if the path does not exist and folder cannot be created. + */ +bool Logger::setLogPath(LogType vLogType) +{ + bool ok = false; + // treatment logs moved to the encrypted partition/configurations. + // it handled in onCryptSetupMount + if (vLogType == LogType::eLogTrtmt ) return true; + + _logPathNames[vLogType] = _dir.path() + "/" + _logBasePathNames[vLogType]; + ok = FileHandler::makeFolder(_logPathNames[vLogType]); + if ( ok ) emit didLogPathSet(vLogType, _logPathNames[vLogType]); + return ok; +} + +/*! + * \brief Logger::log + * \details Logs the content vContent in log type of vLogType. + * \param vContent - Log content + * \param vLogType - Log type + * \note This method is not thread-safe so is private and needs to be called by concurrentLog + * Which uses QtConcurrent::run to run in thread and thread-safe. + */ +void Logger::log(const QString &vContent, LogType vLogType, bool vTimestamp) +{ + if ( ! checkThread() ) return; + + QString mContent; + + // - Add header + QString currentDate = QDate::currentDate().toString(_fileDateFormat); + QString currentTime = QTime::currentTime().toString(_fileTimeFormat); + if ( _logFileNameDate != currentDate ) { + if ( ! _logFileNameDate.isEmpty() ) { + switch ( vLogType ) { + case eLogAppED : mContent = _headerA; break; + case eLogDebug : mContent = _headerD; break; + // case LogType::eLogTrtmt: // this type of log will never happen here. Only put here to make sure it is intentional. + default : mContent = _headerD; break; + } + mContent += "\r\n"; + } + _logFileNameDate = currentDate; + _logFileNameTime = currentTime; + } + + // - Make log file name + if (_logFileNameMode.isEmpty()) { + _logFileNameMode = _logFileNameMode_init; + } + + QString fileName; + if ( ! gLogLongName ) { + fileName = _logFileNameDate + + _fileSeparator + "denali"; + } + else { + fileName = _logFileNameDate + + _fileSeparator + _logFileNameTime + + _fileSeparator + _logFileNameHDSN + + _fileSeparator + _logFileNameMode + ; + } + // case LogType::eLogTrtmt: // this type of log will never happen here. Only put here to make sure it is intentional. + switch (vLogType) { + case LogType::eLogAppED: { + // ------------------------------------------------------------------------ TODO: Improve : function declaration + fileName += _logFileNameExt[vLogType]; + static QString oFileName; + if( oFileName != fileName ) { + if( oFileName.trimmed().isEmpty() ) { + oFileName = fileName; + } + else { + emit didLogBackup( _logPathNames[vLogType] + oFileName ); + oFileName = fileName; + } + } + } + break; + case LogType::eLogDebug: { + // ------------------------------------------------------------------------ TODO: Improve : function declaration + fileName += _logFileNameExt[vLogType]; + static QString oFileName; + if( oFileName != fileName ) { + if( oFileName.trimmed().isEmpty() ) { + oFileName = fileName; + } + else { + emit didLogBackup( _logPathNames[vLogType] + oFileName ); + oFileName = fileName; + } + } + } + break; + + default: + fileName += _logFileNameExt[eLogDebug]; + LOG_DEBUG(QString("Incorrect type of logging %1").arg(vLogType)); + } + + // - Add timestamp + if ( vTimestamp ) + mContent = QTime::currentTime().toString(_timeFormat) + _separator; + + // - Add the content + mContent += vContent; + // some messages like the version having the U08(uchar) parameters which converts to '\0' and causes problems in reading the log file. + mContent.replace('\0', "0"); + + // - Make the log path and write to log. + QString logPathName = _logPathNames[vLogType]; + if (logPathName.isEmpty()) + logPathName = _logPathNames[eLogDebug]; + _logFileName = logPathName + fileName; + + bool isWritten = FileHandler::write(_logFileName, mContent + "\r\n", true); + if(!isWritten) emit didLogIOFail(); + + // console out the log if enabled. + if (_enableConsoleOut) { + qDebug().noquote() << mContent; + } +} + +/*! + * \brief Logger::concurrentExportTest + * \details Tests if a log is running + * \return if running return false + */ +bool Logger::concurrentExportIsOk() +{ + if ( _exportLogsWatcher.isRunning() ) { + LOG_DEBUG(QString("Export type of %1 is running").arg(_logNames[_exportLogsType])); + return false; + } + return true; +} + +/*! + * \brief Logger::onExportLogs + * \details Export log notification slot which logs result of export. + */ +void Logger::onExportLogs() +{ + // disabled coco begin validated: This needs user interaction to export to USB device + // has been tested manually + LOG_DEBUG(QString("Export %1 ended: %2").arg(_logNames[_exportLogsType]).arg(_exportLogsWatcher.result())); + + //DEBUG: qDebug()<< "_exportLogsWatcher.result()" << _exportLogsWatcher.result(); + if(!_exportLogsWatcher.result()){ + emit didLogIOFail(); + return; + } + emit didExportLogs(); +} +// disabled coco end + +/*! + * \brief Logger::removeLogs + * \details Remove old logs by iterating in the log/service folders and look for expired logs. + * \return count file(s) have been removed. + */ +int Logger::removeLogs(LogType vLogType) +{ + // Storage::FileHandler::find("/media/denali/0CAA-40C1/log/", {"*.err"}, 15); return 0; + // has been tested manually + LOG_DEBUG(tr("Initializing log clean up")); + int removeCount = 0; + auto logFiles = { eLogAppED , eLogDebug , eLogCloud }; // TODO: UI (Linux denali user has no access to the CloudSync log folders to delete) + auto logFile = { vLogType }; // Is mostly used for the txr files which are in separate partition. + + for ( const auto &iType : ( vLogType == eLogFiles ? logFiles : logFile ) ) { + // The Application does not have access to remove the CloudSync logs, + // therefore sends CloudSync a message with expected used percent of the log files, + // to remove the logs. + if ( iType == eLogCloud ) { emit didRetentionLogCS(_logTypeMaxUsageLimit[iType]); continue; } + + QString mCSource = _logPathNames [iType]; + QString mCExtension = _logFileNameExt[iType]; + // DEBUG: qDebug() << "@" << mCSource << mLogFileFilter << mCExtension << iType << _logTypeMaxUsageLimit[iType]; + QFileInfoList fileInfoList = FileHandler::find( + mCSource , // where to look + {"*.*"} , // what to delete // it means the removal will be for all the files in that location and won't look at the mCExtension + _logTypeMaxUsageLimit[iType]); // how many/much + removeCount = fileInfoList.count(); + // qDebug() << "@" << removeCount << fileInfoList; + if ( ! removeCount ) { LOG_DEBUG(QString("No log file to delete for type(%1) more than %2% limit from folder %3") + .arg(mCExtension) + .arg(_logTypeMaxUsageLimit[iType]) + .arg(mCSource)); continue; } + LOG_DEBUG(QString("Removing %1 logs of type (%2) more than %3% limit from folder %4") + .arg(removeCount) + .arg(mCExtension) + .arg(_logTypeMaxUsageLimit[iType]) + .arg(mCSource)); + for (const auto &info: fileInfoList) { + bool isWritable = info.isWritable(); + bool isOldLog = info.lastModified().date() != QDate().currentDate(); + if ( ! isWritable ) { LOG_DEBUG(QString("Log %1 cannot be deleted" ).arg(info.fileName())); continue; } + if ( ! isOldLog ) { LOG_DEBUG(QString("Current day log %1 cannot be deleted" ).arg(info.fileName())); continue; } + + QString mFileName = mCSource + info.fileName(); + // DEBUG: qDebug() << "#" << mFileName; + bool ok = QFile::remove(mFileName); + if (ok) { + LOG_DEBUG(QString("Removing %1 succeeded").arg(mFileName)); + } else { + LOG_DEBUG(QString("Removing %1 failed" ).arg(mFileName)); + } + } + } + return removeCount; +} + +/*! + * \brief Logger::concurrentRemoveLogs + * \details remove logs scheduler. + * \return always returns true for now. + * \note This method uses QtConcurrent run to execute the FileHandler copyFolder method. + */ +bool Logger::concurrentRemoveLogs(LogType vLogType) +{ + // disabled coco begin validated: This needs user interaction to check the old files deleted + // has been tested manually + LOG_DEBUG("Remove Logs Starting"); + emit didRemoveLogs(true); + QFuture mFuture = QtConcurrent::run(this, &Logger::removeLogs, vLogType); + _removeLogsWatcher.setFuture(mFuture); + return true; +} +// disabled coco end + +/*! + * \brief Logger::onRemoveLogs + * \details Remove old logs notification slot which logs result of remove. + */ +void Logger::onRemoveLogs() +{ + LOG_DEBUG(tr("Remove Logs Ended: %1").arg(_removeLogsWatcher.result())); + emit didRemoveLogs(false); +} + +void Logger::onSDCardStateChange(bool vReady, bool vReadonly) +{ +#if BUILD_FOR_DESKTOP + Q_UNUSED(vReady ) + Q_UNUSED(vReadonly ) + _logStorageReady = true; +#else + _logStorageReady = vReady && !vReadonly; +#endif +} + +/*! + * \brief Logger::onSDCardSpaceChange + * \details SD Card storage space parameter change slot. + * This slot when called is calling the function concurrentRemoveLogs, + * if percent of available space vPercent is less than Storage::Available_Space_Percent, + * if the SD Card is ready (vReady is true) + * \param vReady - The SD Card is Ready + * \param vTotal - Total storage space on the SD Card + * \param vAvailable - Available storage space on the SD Card + * \param vPercent - Percent of available storage space on the SD Card + */ +void Logger::onSDCardSpaceChange(bool vReady, qint64 vTotal, qint64 vAvailable, quint8 vPercent) +{ + // disabled coco begin validated: This needs user interaction to change the SD card files system. + // has been tested manually + Q_UNUSED(vTotal ) + Q_UNUSED(vAvailable ) + if ( ! vReady ) return; + + // DEBUG: qDebug() << vPercent << Storage::Available_Space_Percent; + if ( Storage::Log_Min_Available_Total_Space_IsLow(vPercent) ) { + concurrentRemoveLogs(); + } +} +// disabled coco end + + +/*! + * \brief Logger::onSettingsPartitionStateChange + * \details handle the state change of the settings partition + * \param vReady - The Settings Partition is Ready + * \param vReadonly - The Settings Partition is readonly + */ +void Logger::onSettingsPartitionStateChange(bool vReady, bool vReadonly) +{ + Q_UNUSED(vReadonly) + LOG_DEBUG(QString("Settings Partition State Changed | vReady: %1 ").arg(vReady)); +} + +/*! + * \brief Logger::onSettingsPartitionSpaceChange + * \details Settings Partition storage space parameter change slot. + * This slot when called is calling the function concurrentRemoveLogs, + * if percent of available space vPercent is less than Storage::Available_Space_Percent, + * if the Settings Partition is ready (vReady is true) + * \param vReady - The Settings Partition is Ready + * \param vTotal - Total storage space on the Settings Partition + * \param vAvailable - Available storage space on the Settings Partition + * \param vPercent - Percent of available storage space on the Settings Partition + */ +void Logger::onSettingsPartitionSpaceChange(bool vReady, qint64 vTotal, qint64 vAvailable, quint8 vPercent) +{ + // disabled coco begin validated: This needs user interaction to change the SD card files system. + // has been tested manually + Q_UNUSED(vTotal ) + Q_UNUSED(vAvailable ) + if ( ! vReady ) return; + + // DEBUG: qDebug() << vPercent << Storage::Available_Space_Percent; + if ( Storage::Txr_Min_Available_Total_Space_IsLow(vPercent) ) { + concurrentRemoveLogs(eLogTrtmt); + } +} +// disabled coco end + + +/*! + * \brief Logger::enableConsoleOut + * \details Enables or Disables the console output and logs the status + * \param vEnabled - Enable console output if true + */ +void Logger::enableConsoleOut(bool vEnabled) { + // disabled coco begin validated: This code meant to be used only for debugging and tested manually + if (_enableConsoleOut == vEnabled) return; + _enableConsoleOut = vEnabled; + if (_enableConsoleOut) { + LOG_DEBUG("Console out Logging enabled"); + } else { + LOG_DEBUG("Console out Logging disabled"); + } +} +// disabled coco end + +/*! + * \brief Logger::logPath + * \details The accessor of the log path for each log type. + * \param vLogType - type of the log + * \return the log path of the log type vLogType as string + * \sa Logger::LogType + */ +const QString &Logger::logPath(Logger::LogType vLogType) +{ + return _logPathNames[vLogType]; +} + Index: sources/storage/Logger.h =================================================================== diff -u --- sources/storage/Logger.h (revision 0) +++ sources/storage/Logger.h (revision 5a248f0a45889844aa027f4f4a0661aa539975f0) @@ -0,0 +1,316 @@ +/*! + * + * Copyright (c) 2020-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 Logger.h + * \author (last) Behrouz NematiPour + * \date (last) 04-Apr-2024 + * \author (original) Behrouz NematiPour + * \date (original) 26-Aug-2020 + * + */ +#pragma once + +// Qt +#include +#include +#include + +// Project +#include "main.h" // Doxygen : do not remove +#include "StorageGlobals.h" + +// Define +#define _Logger Storage::Logger::I() + +#define LOG_EXPORTLOG(vExportName) _Logger.concurrentExportLogs(vExportName) +#define LOG_EXPORTERR(vExportName) _Logger.concurrentExportErrs(vExportName) +#define LOG_EXPORTTRT(vExportName) _Logger.concurrentExportTrts(vExportName) + +#define ADD_APPED_HEADER emit Storage::Logger::I().didLog(_headerA, Storage::Logger::LogType::eLogAppED, false) +#define ADD_DEBUG_HEADER emit Storage::Logger::I().didLog(_headerD, Storage::Logger::LogType::eLogDebug, false) +#define LOG_APPED(vCONTENT) emit Storage::Logger::I().didLog(vCONTENT, Storage::Logger::LogType::eLogAppED, true ) +#define LOG_DEBUG(vCONTENT) emit Storage::Logger::I().didLog(vCONTENT, Storage::Logger::LogType::eLogDebug, true ) + +#define LOG_APPED_UI(vCONTENT) emit Storage::Logger::I().didLog(" ,UI," + vCONTENT, Storage::Logger::LogType::eLogAppED, true ) +#define LOG_APPED_PO(vCONTENT) emit Storage::Logger::I().didLog("POST,UI," + vCONTENT, Storage::Logger::LogType::eLogAppED, true ) +#define LOG_APPED_MSG(vID, vTEXT) LOG_APPED(QString("%1,%2").arg(QString("%1").arg(vID,4,16,QLatin1Char('0')).toUpper()).arg(vTEXT)) +#define LOG_APPED_CS(vCONTENT) emit Storage::Logger::I().didLog(" ,CS," + vCONTENT, Storage::Logger::LogType::eLogAppED, true ) + +// forward declarations +class tst_logging; + +namespace Storage { + +/*! + * \brief The Logger class + * \details Main logger class that has all the required implementation for logging. + * The provided interface is the LOG_EVENT, LOG_DEBUG, LOG_EXPORT defines + * and no other methods. + * + * This object is logging all the registered Denali Messages transactions over the CANBus in CSV format text file. + * Logger class currently has 3 types which are Event, Error, Datum. + * The Event and Data messages are logged in the csv file with "log" extension + * and the UI Application specific errors are logged in a csv file with the "err" extension. + * Logs file names format start with current system date by "" format and "_denali.". + * The content of the file is depending on each log type and has ",,,,". + * Currently, the file name and content formatting can be modified by changing the formatting values in Logger class. + * + * This class has its own thread for logging. + * Also for exporting the log files it uses asynchronous thread calling by QtConcurrent. + * It communicated with other classes only with Signal/Slots for thread safety and will notify other classes when the export is done by signals. + * + * \note + * PLEASE BE CAREFUL THIS CLASS IS USING QtConcurrent::run FOR THE EXPORT LOG FILES. + * AND ONLY PRIVATE VOID LOG (,) IS CALLING IN POOLED THREAD + * PLEASE BE VERY CAREFUL. + * ALL THE OTHER CLASSES TO USE THIS CLASS SHOULD ONLY USE LOG_EVENT, LOG_DEBUG + * TO DO THE LOGGING + */ +class Logger : public QObject +{ + Q_OBJECT + + // Singleton + SINGLETON(Logger) + + // friends + friend class ::tst_logging; + +public : + enum LogType { + eLogNone = -1, + + eLogAppED, ///< Application Events and Data : Massages on the CANBus + eLogDebug, ///< Application Error : Service logs + eLogCloud, ///< CloudSync Log Files : CloudSync debug logs + eLogTrtmt, ///< Treatment Rep Files : Treatment Report files + + eLogType_Count , + eLogFiles , + }; + Q_ENUM(LogType) + +private: + + bool _logStorageReady = true; + + const char *_headerA = "TimeStamp,ID,SubSys,Name,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40"; + const char *_headerD = "TimeStamp,Description"; + QDir _dir; + + // The HSSerial is going to be the HD Serial always, + // but while booting the logger is initialized first before the HD sends its serial number. + // therefore during the bootup and POST that we don't have the serial, will use BootPOST. + QString _logFileNameHDSN = "BootPOST" ; + + // The Mode is the device main states comming from HD_OpMode + // this is not exactly the HD_OpModes and will be interpreted in desired cycles. + // At the moment we decided to have Standby, Treatment, Disinfection, and I am adding Initial as well. + // NoRunning + // Treatment + // Disinfect + // DisinfectHeat // later: the UI message needs to get updated and not using SubMode + // DisinfectChem // later: the UI message needs to get updated and not using SubMode + // DisinfectChemFlsh // later: the UI message needs to get updated and not using SubMode + // DisinfectFlsh // later: the UI message needs to get updated and not using SubMode + // "Disinfect" + // "Treatment" + const char *_logFileNameMode_init = "NoRunning"; + QString _logFileNameMode = "" ; + QString _logFileNameDate = "" ; + QString _logFileNameTime = "" ; + + typedef QHash TLogData; + typedef QHash TLogMaxUse; + + TLogData _logPathNames; + const TLogData _logBasePathNames { + { LogType::eLogAppED, Storage::Log_Folder_Application}, + { LogType::eLogDebug, Storage::Log_Folder_Service }, + { LogType::eLogCloud, Storage::Log_Folder_CloudSync }, + { LogType::eLogTrtmt, Storage::Txr_Folder_Treatment }, + }; + + const TLogData _logNames { // Will be used for the logging in the file + { LogType::eLogAppED, "Log" }, + { LogType::eLogDebug, "Service" }, + { LogType::eLogCloud, "Cloud" }, + { LogType::eLogTrtmt, "Treatment" }, + }; + + const char * _logFileNamePendingSubExt = "u."; + const char * _logFileNameCompressExt = ".gz"; + const TLogData _logFileNameExt { + { LogType::eLogAppED, ".log" }, // Application log + { LogType::eLogDebug, ".err" }, // Application error + { LogType::eLogCloud, ".log" }, // CloudSync debug + { LogType::eLogTrtmt, ".txr" }, // Treatment report + }; + + // !!!!!!!!!! IMPORTANT !!!!!!!!!! + // be careful with these percentages + // please refer to the Storage namespace. + const TLogMaxUse _logTypeMaxUsageLimit { + { LogType::eLogAppED, Storage::Log_Max_Allowable_AppED_Space_Percent }, + { LogType::eLogDebug, Storage::Log_Max_Allowable_Debug_Space_Percent }, + { LogType::eLogCloud, Storage::Log_Max_Allowable_Cloud_Space_Percent }, + { LogType::eLogTrtmt, Storage::Txr_Max_Allowable_Trtmt_Space_Percent }, + }; + + const char *_fileDateFormat = "yyyyMMdd" ; // date used in the file name + const char *_fileTimeFormat = "HHmmss" ; // timestamp in the file + + const char *_timeFormat = "HH:mm:ss.zzz"; // timestamp in the file + + const char *_fileSeparator = "_"; // used in filename + const char *_separator = ","; + + bool _enableConsoleOut = false; + + QString _logFileName = ""; + QMutex _logRemoveRunning; + + LogType _exportLogsType = eLogNone; + QFutureWatcher _exportLogsWatcher; + QFutureWatcher _removeLogsWatcher; + + QThread *_thread = nullptr; + bool _init = false; + +public: + QString logFileNamePendingSubExt() { + return _logFileNamePendingSubExt; + } + + QString logFileNameCompressExt() { + return _logFileNameCompressExt; + } + + QString logFileNameExt ( LogType vLogType ) { + return _logFileNameExt[vLogType]; + } + + QString logPathName(LogType vLogType) { + return _logPathNames[vLogType]; + } + + /*! + * \brief logFileNameExt_AD + * \details Finds the type of the log by log file extention. + * \note Since the log file ext of the cloud and UI are the same, this finction will ignore the eLogCloud and will return eLogAppED type instead. + * \param vLogExt - the log file extention. + * \return + */ + LogType logFileNameExt_AD ( const QString &vLogExt ) { + if ( _logFileNameExt.values().contains(vLogExt) ) { + LogType logType = _logFileNameExt.key(vLogExt); + if ( logType == eLogCloud) logType = eLogAppED; + return logType; + } + return eLogNone; + } + + LogType logFileLogType (const QString &vFileName, QString &vFilePath) { + QString fileName = vFileName; + QFileInfo fileInfo(fileName.remove(_logFileNameCompressExt)); + QString ext = fileInfo.suffix(); + LogType logType = logFileNameExt_AD("." + ext); + QString logFilePath; + if ( logType != eLogNone ) { + logFilePath = Log_Folder_Base + _logBasePathNames[logType]; + } + if ( fileInfo.exists(logFilePath + vFileName) ) { + vFilePath = logFilePath; + return logType; + } + return eLogNone; + } +public: + void enableConsoleOut(bool vEnabled); + void postInit(); + +signals: + void didLogPathSet ( Logger::LogType vLogType, const QString &vLogPath ); + void didLogIOFail (); + void didLogBackup ( const QString vFileName ); + + void didRetentionLogCS ( quint8 vPercent ); + +public slots: + bool init(); + bool init(QThread &vThread); + void quit(); + +private: + void initConnections(); + + void initThread(QThread &vThread); + void quitThread(); + + bool checkThread(); + +private: + // ----- setting up + void checkLogPath (); + void setLogBasePath (bool vUseTempPath = false); + bool setLogPath (); + bool setLogPath (LogType vLogType); +public: + const QString &logPath(Logger::LogType vLogType); + + // ----- Export structure +private : + +public slots: // this slot is thread safe and can be called from outside by LOG_EXPORT. + bool concurrentExportIsOk (); + void onExportLogs (); + +signals: + void didExportLogs(); + void didExportStat(quint32 vIndex, const QString &vFileName, quint8 vPercent); + + // ----- Remove Old Logs structure +private: + int removeLogs(LogType vLogType = eLogFiles); +private slots: // this slot is thread safe and can be called from outside but preferred not to. + bool concurrentRemoveLogs(LogType vLogType = eLogFiles); + void onRemoveLogs(); + void onCryptSetupMount (bool vPass); + +signals: + /*! + * \brief didRemoveLogs + * \details This signal will be emitted mainly for DeviceController to not to emit the signal that Logger is connected to + * , while the logging cleanup is in progress, otherwise if the log cleanup takes more that 1sec (current interval) + * in the DeviceController then the cleanup will be called again for no good reason. + * \param vInProgress - true if the log cleanup is in progress, false otherwise. + */ + void didRemoveLogs(bool vInProgress); + + // ----- Available space is low +private slots: + void onSDCardStateChange(bool vReady, bool vReadonly); + void onSDCardSpaceChange(bool vReady, qint64 vTotal, qint64 vAvailable, quint8 vPercent); + void onSettingsPartitionStateChange(bool vReady, bool vReadonly); + void onSettingsPartitionSpaceChange(bool vReady, qint64 vTotal, qint64 vAvailable, quint8 vPercent); + + // ----- logging structure +private slots: + void onLog (const QString &vContent, LogType vLogType, bool vTimestamp); +private: + void log (const QString &vContent, LogType vLogType, bool vTimestamp); + +signals: + /*! + * \brief didLog + * \details Notifies the logger on a request for log + * \param vContent - content as type of string to be logged + * \param vLogType - the type of logging of type Storage::Logger::LogType + */ + void didLog (const QString &vContent, LogType vLogType, bool vTimestamp); +}; +} Index: sources/storage/Settings.cpp =================================================================== diff -u --- sources/storage/Settings.cpp (revision 0) +++ sources/storage/Settings.cpp (revision 5a248f0a45889844aa027f4f4a0661aa539975f0) @@ -0,0 +1,277 @@ +/*! + * + * 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 Settings.cpp + * \author (last) Behrouz NematiPour + * \date (last) 08-Aug-2023 + * \author (original) Behrouz NematiPour + * \date (original) 29-Mar-2021 + * + */ + +// Qt +#include +#include +#include + +// Project +#include "StorageGlobals.h" +#include "FileHandler.h" +#include "MSettings.h" +#include "Logger.h" + +// namespace +using namespace Storage; + +#include "Settings.h" + +/*! + * \brief Settings::readCategory + * \details Reads, parses and adds the read value to the settins. + * \param vCategory - the category of the configuration file to read. + * \return non-zero, error value on error, and zero on success. + */ +int Settings::readCategory(Category_Enum vCategory) +{ + int err = Settings::Settings_Error::eError_None; + QString msg; + QFileInfo mSettingFile; + QString mCategory; + QString mFileName; + Detail mDetail; + + mCategory = category(vCategory); + mFileName = fileName(vCategory); + qDebug() << mCategory << mFileName; + mSettingFile.setFile(mFileName); + QFile mFile(mSettingFile.absoluteFilePath()); + if (! mFile.exists() ) { err = Settings::Settings_Error::eError_SettingNotExists ; msg = errorMessage(err, mSettingFile.fileName ()) ; goto lOut; } + if (! mFile.open(QIODevice::ReadOnly | QIODevice::Text) ) { err = Settings::Settings_Error::eError_Read ; msg = errorMessage(err, Storage::Settings_Path()) ; goto lOut; } + + mDetail.content = mFile.readAll().trimmed(); + if (mDetail.content.isEmpty() ) { err = Settings::Settings_Error::eError_Empty ; msg = errorMessage(err, mSettingFile.fileName ()) ; goto lOut; } + + mDetail.location = mSettingFile.absolutePath() + "/"; + mDetail.category = mCategory; + + if ( ! parse(mDetail) ) { err = Settings::Settings_Error::eError_Parse ; msg = errorMessage(err, mSettingFile.fileName ()) ; goto lOut; } + else { msg = errorMessage(err, mSettingFile.fileName ()) ; } + +lOut: + LOG_APPED_PO( msg ); + return err; +} + +/*! + * \brief Settings::readConfigurations + * \details Reads all the configuration files + * \return non-zero, error value on error, and zero on success. + */ +int Settings::readConfigurations() +{ + int err = Settings::Settings_Error::eError_None; + for ( quint8 i = 0; i <= _configurationsCount; i++ ) { + readCategory(static_cast(i)); + } + return err; +} + +/*! + * \brief Settings::readLocale + * \return + */ +int Settings::readLocale() +{ + int err = Settings_Error::eError_None; + err = readCategory(eConfigurationsLocale); + return err; +} + +/*! + * \brief Settings::parse + * \details The function to parse the content of the conf file and fill in the Settings model. + * \param vDetail - the conf file detail has been read. + * \return bool - true on success. + */ +bool Settings::parse(const Detail &vDetail) { + bool enableDuplicateKey = false; + QString attribute = QString("%1%2").arg(_config_attribute_tag); + QString group = ""; + QStringList lines = vDetail.content.split('\n'); + for (QString line : lines) { + // ----- trim the line + line = line.trimmed(); + + // ----- ignore empty lines + if ( line.isEmpty() ) continue; + + // ----- find comments + int commentPosition = line.indexOf('#'); + + + // ----- ignore comment line or find attributes + if ( commentPosition == 0 ) { + + // ----- find the configuration file attribute + int attributeTagPosition = line.indexOf(_config_attribute_tag); + if ( attributeTagPosition == 0 ) { + + // ----- find the attribute : duplicate_key_... + if ( line == attribute.arg( _duplicate_key_on ) ) { enableDuplicateKey = true ;} + else if ( line == attribute.arg(_duplicate_key_off ) ) { enableDuplicateKey = false ;} + + else { + LOG_APPED_PO(( "Unknown '" + line + "' attribute in %1").arg(vDetail.category)); + return false; + } + } + + // next line + continue; + } + + // ----- remove inline comment + if ( commentPosition > 0 ) line.truncate(commentPosition); + line = line.trimmed(); + + // ----- find group + if (line.startsWith("[") && line.endsWith("]")) { + line.replace("[","").replace("]", ""); + group = line; + } + else { + if ( group.isEmpty() ) { + continue; + } + else { + if ( ! line.isEmpty() ) { + QString key = ""; + QString value = ""; + if ( line.contains('=') ) { + QStringList keyValue = line.split('='); + key = keyValue[0].trimmed().replace("\\n","\n"); + value = keyValue[1].trimmed().replace("\\n","\n"); + } + else { + key = line; + } + _Settings.add(vDetail.category, group, key, QVariant(value), enableDuplicateKey); + // DEBUG: qDebug() << group << key << value << location << category; + } + } + } + // DEBUG: qDebug() << group << line; + } + return true; +} + +/*! + * \brief Settings::save + * \details Writes the setting in the configuration files + * \return + */ +int Settings::save(const QString &vGroup, const QString &vKey, const QString &vValue, Category_Enum vCategory) +{ + // qDebug() << vCategory + // << vGroup + // << vKey + // << vValue; + + int err = Settings_Error::eError_None; + QString msg; + QString mCategory; + QString mPath; + QString mFileName; + QString mContent; + + if ( ! isCategoryWritable(vCategory) ) { err = Settings_Error::eError_Not_Writable ; msg = errorMessage(err ); goto lOut; } + + mCategory = category(vCategory); + mFileName = fileName(vCategory); + // -------------------------------------------------------------------------------------------------------------- + //Note: the configuration files which can be saved, are like settings and should not have duplicate values. + // as an example the Alarm volume can't have two separate duplicate entry in the settings. + // -------------------------------------------------------------------------------------------------------------- + _Settings.add(mCategory, vGroup, vKey, vValue, false); + mPath = QFileInfo(mFileName).absolutePath(); + if ( mPath.trimmed().isEmpty() ) { err = Settings_Error::eError_PathEmpty ; msg = errorMessage(err ); goto lOut; } + if ( ! FileHandler::makeFolder(mPath) ) { err = Settings_Error::eError_MkDir ; msg = errorMessage(err, mPath ); goto lOut; } + for ( const auto &group : _Settings.groups(mCategory) ) { + mContent += QString("\n[%1]\n").arg(group); + for ( const auto &key : _Settings.keys(mCategory, group) ) { + mContent += QString("%1 = %2\n").arg(key).arg(_Settings.value(mCategory, group, key).toString()); + } + } + if ( ! FileHandler::write(mFileName,mContent, false) ) { err = Settings_Error::eError_Write ; msg = errorMessage(err, mFileName ); goto lOut; } + +lOut: + if ( err ) LOG_DEBUG( msg ); + return err; +} + +/*! + * \brief Settings::configurationsMove + * \details After the encrypted partition is ready the configuration files are moved from the root home to denali home. + * \param vMessage + * \return + */ +int Settings::configurationsMove(QString *vMessage, bool vIsUpdate) +{ + int err = Settings_Error::eError_None ; + Location_Enum loc = Location_Enum ::eInit ; + QString src = Settings::location(Location_Enum ::eInit ); + QString dst = Settings::location(Location_Enum ::eSecured ); + QString msg = ""; + QStringList lstExclude; + if ( vIsUpdate ) { + lstExclude << QFileInfo( src + Storage::Settings_Category_SettingsSystem ).absolutePath(); + } + + if ( ! Settings ::configurationsPOST(loc )) { err = Settings_Error::eError_Remove; msg = errorMessage(err ); goto lOut; } + if ( ! FileHandler ::makeFolder ( dst )) { err = Settings_Error::eError_Copy ; msg = errorMessage(err ); goto lOut; } + for( QString dir : FileHandler::subFolders(src)) { + QString sub = src + dir; + if ( ! lstExclude.contains( sub ) ) { + if ( FileHandler ::copyFolder (sub, dst )) { err = Settings_Error::eError_MkDir ; msg = errorMessage(err, dir); goto lOut; } + } + if ( FileHandler ::removeFolder (sub )) { err = Settings_Error::eError_POST ; msg = errorMessage(err, dir); goto lOut; } + } + + Storage::Settings_Secured(); + +lOut: + if ( err ) LOG_DEBUG ( msg ); + if ( vMessage ) { *vMessage = msg; } + return err; +} + +/*! + * \brief Settings::loadTranslation + * \return + */ +int Settings::loadTranslation(QTranslator &vTranslator) +{ + int err = Settings_Error::eError_None; + QString msg; + QString translationFile; + bool ok = true; + + QString locale = _Settings.systemLocale(); + if( locale.isEmpty() ) { err = Settings_Error::eError_No_SettingsLocale ; msg = errorMessage(err ); goto lOut; } + + translationFile = fileName(eTranslation); + ok = vTranslator.load(translationFile); + + if ( ! ok ) { err = Settings_Error::eError_TranslationNotExists ; msg = errorMessage(err, translationFile ); goto lOut; } + else { msg = errorMessage(err, translationFile ); } + + QCoreApplication::installTranslator(&vTranslator); + +lOut: + LOG_APPED_PO( msg ); + return err; +} Index: sources/storage/Settings.h =================================================================== diff -u --- sources/storage/Settings.h (revision 0) +++ sources/storage/Settings.h (revision 5a248f0a45889844aa027f4f4a0661aa539975f0) @@ -0,0 +1,275 @@ +/*! + * + * 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 Settings.h + * \author (last) Behrouz NematiPour + * \date (last) 13-Sep-2023 + * \author (original) Behrouz NematiPour + * \date (original) 29-Mar-2021 + * + */ +#pragma once + +// Qt +#include +#include +#include +#include + +// Project +#include "main.h" // Doxygen : do not remove +#include "MSettings.h" +#include "StorageGlobals.h" +#include "Threads.h" + +// forward declarations +class tst_initializations; + +namespace Storage { + +// TODO - IMPORTANT: this MVC needs to change to configurations in oppose to Settings. +// It named settings because it suppose to be like QSettings, +// but since we have settings it is mixing up with that and is very confusing. +// FIXME- IMPORTANT: This set of Settings (MVC) design is not correct: +// - the data structure should change in Model from column based (keys, values) to tree/map based +// - the singleton should be the controller not the Model, to protect the model usage, and avoid the shared memory issues. +// - the model class should be private class, or an object defined in this class, not a global class. + +class Settings +{ +private: + // Settings + static constexpr const char *_config_attribute_tag = "#--"; + static constexpr const char *_duplicate_key_on = "duplicate_key_on"; + static constexpr const char *_duplicate_key_off = "duplicate_key_off"; + static constexpr const char*_settingsExt = "conf"; + static constexpr const char *_settingsFormat = "%1/%2.%3"; + static constexpr const char*_settingsLocalSeparator = "_"; + static constexpr const char*_translationExt = "qm"; + + struct Detail { + QString category; + QString location; + QString content; + }; + +// TODO make this the SettingsError class +// { Class SettingsError +public: + enum Settings_Error { + eError_None = 0 , // always has to be 0 + eError_POST , + eError_PathEmpty , + eError_MkDir , + eError_Write , + eError_Read , + eError_Empty , + eError_Parse , + eError_Copy , + eError_Remove , + eError_No_SettingsFolder , + eError_No_SettingsFile , + eError_SettingNotExists , + eError_No_SettingsLocale , + eError_Not_Writable , + eError_TranslationNotExists , + }; + +private: + static inline QHash settingsError_Message { // no translation for the error. My experience shows the error messages if translated is not useful for serviceability and debugging. + { eError_None , "The configuration file '%1' successfully loaded," }, + { eError_POST , "The configuration source check failed." }, + { eError_PathEmpty , "The settings path is empty." }, + { eError_MkDir , "The configuration folder '%1' cannot be created." }, + { eError_Write , "The settings file %1 can't be written." }, + { eError_Read , "The settings file %1 can't be read." }, + { eError_Empty , "The settings file %1 is empty." }, + { eError_Parse , "The settings file %1 parse error." }, + { eError_Copy , "The configuration folder '%1' cannot be copied." }, + { eError_Remove , "The configuration folder '%1' cannot be removed." }, + { eError_No_SettingsFolder , "No settings folder in the %1 path found." }, + { eError_No_SettingsFile , "No settings file in the %1 folder found." }, + { eError_SettingNotExists , "The setting file %1 doesn't exists." }, + { eError_No_SettingsLocale , "The system locale not defined. The default en_US used." }, + { eError_Not_Writable , "The setting file %1 is not writable." }, + { eError_TranslationNotExists , "The translation file %1 doesn't exists." }, + }; + static const QString errorMessage(int vErr, QString vArg1 = "", QString vArg2 = "") { + if ( settingsError_Message.contains(vErr) ) { + if ( ! vArg1.isEmpty() ) return QString(settingsError_Message[vErr]).arg(vArg1); + if ( ! vArg2.isEmpty() ) return QString(settingsError_Message[vErr]).arg(vArg1).arg(vArg2); + /* default */ return QString(settingsError_Message[vErr]); + } + return "[unknown]"; + } +// } Class SettingsError + + Settings() {} + +public: + enum Category_Enum { + eSettingsSystem = 0, // has to be the first one to read the other configs according to the locale set in the Systems.conf + eInstructions , + eParametersDataList , // TODO: the category for this conf is not used. may need to be merged into the Settings, but since it is list, It needs a little more thought. + eAlarms , + eEvents , + eRejects , + eMessagesUnhandled , + eGenericConfirm , + eTranslation , + eConfigurationsLocale , + + }; // IMPORTANT: not having a eCount in the enum is intentional. to make sure in switch no enum is missed when not using the default. + static constexpr int _configurationsCount = eGenericConfirm; + + enum Key_Enum { + eKeyTitle , + eKeyMessage , + eKeyListTitle , + eKeyConfirm , + eKeyCancel , + }; + + enum Location_Enum { + eInit , + eSecured , + }; + + static int readConfigurations (); + static int save (const QString &vGroup, const QString &vKey, const QString &vValue, Category_Enum vCategory = eSettingsSystem); + static int configurationsMove (QString *vMessage = nullptr, bool vIsUpdate = false); + static int readLocale (); + static int loadTranslation (QTranslator &vTranslator); + + static QString locale(Category_Enum vCategory, bool vSeparator = false) { + auto mLocale = [=](){ + QString s = _Settings.systemLocale(); + QString l = s.isEmpty() ? "" : QString( vSeparator ? _settingsLocalSeparator : "") + s ; + return l; + }; + switch (vCategory) { // NOTE: don't use default case + case eSettingsSystem : return "" ; + case eInstructions : return mLocale() ; + case eParametersDataList : return mLocale() ; + case eAlarms : return mLocale() ; + case eEvents : return mLocale() ; + case eRejects : return mLocale() ; + case eMessagesUnhandled : return "" ; + case eGenericConfirm : return mLocale() ; + case eTranslation : return mLocale() ; + case eConfigurationsLocale : return "" ; + } + return ""; + } + + static QString extention(Category_Enum vCategory) { + switch (vCategory) { // NOTE: don't use default case + case eSettingsSystem : return _settingsExt ; + case eInstructions : return _settingsExt ; + case eParametersDataList : return _settingsExt ; + case eAlarms : return _settingsExt ; + case eEvents : return _settingsExt ; + case eRejects : return _settingsExt ; + case eMessagesUnhandled : return _settingsExt ; + case eGenericConfirm : return _settingsExt ; + case eTranslation : return _translationExt ; + case eConfigurationsLocale : return _settingsExt ; + } + return ""; + } + + static QString path(Category_Enum vCategory) { + switch (vCategory) { // NOTE: don't use default case + case eSettingsSystem : return Storage::Settings_Path() ; + case eInstructions : return Storage::Settings_Path() ; + case eParametersDataList : return Storage::Settings_Path() ; + case eAlarms : return Storage::Settings_Path() ; + case eEvents : return Storage::Settings_Path() ; + case eRejects : return Storage::Settings_Path() ; + case eMessagesUnhandled : return Storage::Settings_Path() ; + case eGenericConfirm : return Storage::Settings_Path() ; + case eTranslation : return Storage::Translations_Path() ; + case eConfigurationsLocale : return Storage::Configurations_Path() ; + } + return ""; + } + + static QString category(Category_Enum vCategory) { + switch (vCategory) { // NOTE: don't use default case + case eSettingsSystem : return Storage::Settings_Category_SettingsSystem ; + case eInstructions : return Storage::Settings_Category_Instructions ; + case eParametersDataList : return Storage::Settings_Category_DataList ; + case eAlarms : return Storage::Settings_Category_Alarms ; + case eEvents : return Storage::Settings_Category_Events ; + case eRejects : return Storage::Settings_Category_Rejects ; + case eMessagesUnhandled : return Storage::Settings_Category_MessagesUnhandled ; + case eGenericConfirm : return Storage::Settings_Category_GenericConfirm ; + case eTranslation : return Storage::Settings_Category_Translation ; + case eConfigurationsLocale : return Storage::Settings_Category_Locale ; + } + return ""; + } + + static QString key(Key_Enum vKey) { + switch (vKey) { // NOTE: don't use default case + case eKeyTitle : return Storage::Settings_Key_Title ; + case eKeyMessage : return Storage::Settings_Key_Message ; + case eKeyListTitle : return Storage::Settings_Key_ListTitle ; + case eKeyConfirm : return Storage::Settings_Key_Confirm ; + case eKeyCancel : return Storage::Settings_Key_Cancel ; + } + return ""; + } + + //Note: this funtion is specific to the settings and should not use the StorageGlobals function. + static QString location(Location_Enum vLoc) { + switch (vLoc) { // NOTE: don't use default case + case eInit : return Storage::Settings_Path_Init; + case eSecured : return Storage::Settings_Path_Name; + } + return Storage::Settings_Path_Name; + } + + static bool isCategorySettingsSystem (const QString &vCategory) { return vCategory == category( eSettingsSystem ); } + static bool isCategoryInstructions (const QString &vCategory) { return vCategory == category( eInstructions ); } + static bool isCategoryParametersDataList (const QString &vCategory) { return vCategory == category( eParametersDataList ); } + static bool isCategoryAlarms (const QString &vCategory) { return vCategory == category( eAlarms ); } + static bool isCategoryEvents (const QString &vCategory) { return vCategory == category( eEvents ); } + static bool isCategoryRejects (const QString &vCategory) { return vCategory == category( eRejects ); } + static bool isCategoryMessagesUnhandled (const QString &vCategory) { return vCategory == category( eMessagesUnhandled ); } + static bool isCategoryConfirm (const QString &vCategory) { return vCategory == category( eGenericConfirm ); } + static bool isKeyTitle (const QString &vKey ) { return vKey == key ( eKeyTitle ); } + static bool isKeyMessage (const QString &vKey ) { return vKey == key ( eKeyMessage ); } + static bool isKeyListTitle (const QString &vKey ) { return vKey == key ( eKeyListTitle ); } + static bool isKeyConfirm (const QString &vKey ) { return vKey == key ( eKeyConfirm ); } + static bool isKeyCancel (const QString &vKey ) { return vKey == key ( eKeyCancel ); } + +private: // using the Category_Enum so have to be defined after. + /*! + * \brief Settings::fileName + * \details returns the conf file by the settings information provided. + * \param vCategory - the settings file category + * \param vLocale - the settings file locale + * \return QString configuration/settings file name + */ + static QString fileName ( Category_Enum vCategory ) { + return QString("%1%2%4.%3").arg(path(vCategory)).arg(category(vCategory)).arg(extention(vCategory)).arg(locale(vCategory,true)); + } + static bool isCategoryWritable ( Category_Enum vCategory ) { + const QVector mCategory_Writable { + eSettingsSystem , + eConfigurationsLocale , + }; + return mCategory_Writable.contains(vCategory); + } + + static bool parse (const Detail &vDetail ); + static int readCategory (Category_Enum vCategory ); + static int configurationsPOST (Location_Enum vLoc = Location_Enum::eSecured) { Q_UNUSED(vLoc); return true; } +}; + +} Index: sources/storage/StorageGlobals.cpp =================================================================== diff -u --- sources/storage/StorageGlobals.cpp (revision 0) +++ sources/storage/StorageGlobals.cpp (revision 5a248f0a45889844aa027f4f4a0661aa539975f0) @@ -0,0 +1,231 @@ +/*! + * + * Copyright (c) 2020-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 StorageGlobals.cpp + * \author (last) Dara Navaei + * \date (last) 07-Feb-2024 + * \author (original) Behrouz NematiPour + * \date (original) 26-Aug-2020 + * + */ +// Qt +#include +#include + +// Project +#include "StorageGlobals.h" + +extern bool gEnableManufacturing ; +extern bool gEnableUpdating ; +extern bool gUseRootHome ; + +/*! + * \brief Storage + * \details The container of the constant global variable withing Storage namespace members. + * For example it currently includes USB mount root folder and SD Card mount root folder and such, + * which is used by Logger, DeviceController, ... . + */ +namespace Storage +{ + const char *POST_LOG = "post.log"; // this file shall reside in the home folder where the application stored. + + + // OS + const quint16 OS_VERSION_MAJOR = 0; // DIALITY_VERSION_ID="0.0.40" + const quint16 OS_VERSION_MINOR = 0; // DIALITY_VERSION_ID="0.0.40" + const quint16 OS_VERSION_MICRO = 40; // DIALITY_VERSION_ID="0.0.40" + + // USB +#ifdef BUILD_FOR_TARGET + const char *USB_Mount_Point = "/media/usb/"; +#else + const char *USB_Mount_Point = "/home/denali/Desktop/usb-disk/"; +#endif + + const char *USB_File_System = "vfat"; + + // SD-CARD + // // Max Usage : 80 = 25 + 25 + 15 + 15 + // // Max Used : 85 = 100 - 15 + // // Min Buffer : 5 = 85 - 80 + // IMPORTANT: we have to keep 10% buffer otherwise competition between filling and deleting logs will cause application to crash eventually. + const short Log_Min_Available_Total_Space_Percent = 15; // Min free : 15 // always has to be 15 percent free space available, less than this triggers the log cleanup. + const short Log_Max_Allowable_AppED_Space_Percent = 50; + const short Log_Max_Allowable_Debug_Space_Percent = 15; + const short Log_Max_Allowable_Cloud_Space_Percent = 15; + short Log_Min_Available_Total_Space_IsLow(short vPercent) { return vPercent < Log_Min_Available_Total_Space_Percent; } + + // Settings/Encrypted + // the settings are roughly using the 5MB round it to 8MB to be 25% of 32MB of total encrypted partition. + // therefore the Txr shall not use more than 75% to leave the 25 for settings. + // when reaches that percent will be deleted to get total usage of 50% + // 50% percent of 32MB is 16MB which is enough to keep 10K (estimated each txr are 4K rounded to 5K and doubled.) of ~1600 files. + const short Txr_Min_Available_Total_Space_Percent = 20; // min space needed for settings by the current definitions. + const short Txr_Max_Allowable_Trtmt_Space_Percent = 50; // the treatment files are moved to the encrypted partition. + short Txr_Min_Available_Total_Space_IsLow(short vPercent) { return vPercent < Txr_Min_Available_Total_Space_Percent; } + +#ifdef BUILD_FOR_TARGET + const char *SDCard_Base_Path_Name = "/media/sd-card/"; // TODO NOTE: this should not change because it has been mounted change the name to sd-card mount point +#else + // should not be in the project application folder. [not tracking by git] + const char *SDCard_Base_Path_Name = "/home/denali/Desktop/sd-card/"; +#endif + +// Screenshot store folder + const char *Screenshot_Base_Path_Name = "Screenshots/"; // this is the base path which will use the USB_Mount_Point to store the screenshots. + + // Settings + static bool settings_secured = false; + const char *Settings_Path () { return gUseRootHome ? Settings_Path_Init : ( ( gEnableManufacturing || gEnableUpdating ) ? ( settings_secured ? Settings_Path_Name: Settings_Path_Init ) : Settings_Path_Name ); } + void Settings_Secured() { settings_secured = true; } + + const char *Configurations_Path() { +#ifdef BUILD_FOR_TARGET + return QString(QCoreApplication::applicationDirPath() + "/").toLatin1().constData(); +#else + return "/home/denali/Desktop/"; +#endif + } + + const char *Translations_Path() { +#ifdef BUILD_FOR_TARGET + return QString(QCoreApplication::applicationDirPath() + "/").toLatin1().constData(); +#else + return "/home/denali/Projects/application/resources/"; +#endif + } + +#ifdef BUILD_FOR_TARGET + //WARNING: This has to match with the crypt_setup.sh + #ifdef LEAHI_DIGI_BOARD + const char *Settings_Path_Init = "/opt/leahi/configurations/"; // this is the manufacturing or update setup and the user is root. + const char *Settings_Path_Name = "/mnt/data/configurations/"; + #else + const char *Settings_Path_Init = "/home/root/.config/"; // this is the manufacturing or update setup and the user is root. + const char *Settings_Path_Name = "/var/configurations/"; + #endif +#else + // should be in the project application folder. [is tracking by git] + const char *Settings_Path_Init = "/home/denali/Projects/drydemo/resources/settings/"; + const char *Settings_Path_Name = "/home/denali/Projects/drydemo/resources/settings/"; +#endif + const char *Settings_Category_Instructions = "Instructions/Instructions" ; + const char *Settings_Category_InstructionsImagesLoc = "%1/Instructions/" ; + const char *Settings_Category_DataList = "Parameters/DataList" ; + const char *Settings_Category_Alarms = "Alarms/Alarms" ; + const char *Settings_Category_Events = "Alarms/Events" ; + const char *Settings_Category_Rejects = "Alarms/Rejections" ; + const char *Settings_Category_MessagesUnhandled = "Messages/Unhandled" ; + const char *Settings_Category_SettingsSystem = "Settings/System" ; + const char *Settings_Category_GenericConfirm = "Confirm/Confirm" ; + const char *Settings_Category_Translation = "translations/translation" ; // in qrc + const char *Settings_Category_Locale = "settings/locale" ; // in app location + + // common key.value pairs + const char *Settings_Key_Title = "Title" ; + const char *Settings_Key_Message = "Message" ; + const char *Settings_Key_ListTitle = "ListTitle" ; + const char *Settings_Key_Confirm = "Confirm" ; + const char *Settings_Key_Cancel = "Cancel" ; + + // CloudSync credentials +#ifdef BUILD_FOR_TARGET + #ifdef LEAHI_DIGI_BOARD + const char *CloudSync_Base_Path_Name = gUseRootHome ? "/home/root/.cloudSync/" : "/mnt/data/configurations/CloudSync/"; + #else + const char *CloudSync_Base_Path_Name = gUseRootHome ? "/home/root/.cloudSync/" : "/var/configurations/CloudSync/"; + #endif +#else + // on VM it is a temporary file which is not being tracked + const char *CloudSync_Base_Path_Name = "/home/denali/Desktop/CloudSync/"; +#endif + +#ifdef BUILD_FOR_TARGET + const char *CloudSync_Credentials_Folder_Name = "credentials/"; +#else + // on VM it is a temporary file which is not being tracked + const char *CloudSync_Credentials_Folder_Name = "credentials/"; +#endif + + // Scripts + const QString Scripts_Path_Name() { + #ifdef BUILD_FOR_TARGET + // lockdown script suppose to be the last thing to run, + // and it is the lockdown.sh script which moves the scripts from root to denali home folder, + // therefore in manufacturing or update mode we are still running scripts as root. + return QCoreApplication::applicationDirPath() + ( ( gEnableManufacturing || gEnableUpdating )? "/scripts/" : "/scripts/"); + #else + return "/home/denali/Projects/application/scripts/"; + #endif + } + + + // and it needs to be concatenated after SDCard_Base_Path_Name for each build configuration + const char *Log_Folder_Base = SDCard_Base_Path_Name; // Base Log Folder + const char *Log_Folder_Application = "log/" ; // Event/Data Log + const char *Log_Folder_Service = "service/" ; // Service Log + const char *Log_Folder_CloudSync = "cloudsync/log/" ; // CloudSync Log + + // Txr_Max_Allowable_Trtmt_Space_Percent notice that is the folder not the path + + const char *Txr_Folder_Base = Settings_Path_Name ; // Base Txr Folder + const char *Txr_Folder_Treatment = "treatment/" ; // Treatment Rep + + // TODO : These need to be removed from here because they are only used in their specific classes. + // Date and Time + const char *Date_Time_Set_Sh = "date_time_set.sh"; + + // WiFi Settings + const char *Wifi_Disconnect_Network = "wifi_disconnect_network.sh"; + const char *Wifi_Generate_WPA_Supplicant = "wifi_generate_wpa_supplicant.sh"; + const char *Wifi_Read_DNS = "wifi_read_dns.sh"; + const char *Wifi_Read_Gateway = "wifi_read_gateway.sh"; + const char *Wifi_Read_IP_Settings = "wifi_read_ip_settings.sh"; + const char *Wifi_Get_Auto_Assigned_IP = "wifi_request_auto_assigned_ip.sh"; + const char *Wifi_Reset_Adapter = "wifi_reset_adapter.sh"; + const char *Wifi_Reset_Interface = "wifi_reset_interface.sh"; + const char *Wifi_Scan_For_Networks = "wifi_scan_for_networks.sh"; + const char *Wifi_Set_Auto_Assigned_IP = "wifi_set_auto_assigned_ip.sh"; + const char *Wifi_Set_DNS = "wifi_set_dns.sh"; + const char *Wifi_Set_Static_IP = "wifi_set_static_ip.sh"; + const char *Wifi_Set_Gateway = "wifi_set_gateway.sh"; + const char *Wifi_Set_SubnetMask = "wifi_set_subnetmask.sh"; + const char *Wifi_Start_WPA_Supplicant = "wifi_start_wpa_supplicant.sh"; + const char *Wifi_Read_Network_Info = "wifi_read_network_info.sh"; + // Brightness + const char *Brightness_Set = "brightness_set.sh"; + const char *Brightness_Get = "brightness_get.sh"; + + // Root SSH + const char *RootSSHAccess = "rootsshaccess.sh"; + + // Bluetooth + const char *Bluetooth_Paired_Reset = "bluetooth_paired_clear.sh"; + const char *Bluetooth_Paired_Query = "bluetooth_paired_query.sh"; + + // Encrypted Partition - cryptsetup + const char *Crypt_Setup = "crypt_setup.sh"; + + // Factory Reset + const char *Factory_Reset = "factory_reset.sh"; + + // Device Decommissioning + const char *Device_Decommission = "decommission.sh"; + + // Device Lockdown + const char *Device_Lockdown = "lockdown.sh"; + +#ifdef LEAHI_DIGI_BOARD + const char *CloudSyncPath = "/mnt/data/configurations/CloudSync/credentials/"; +#else + const char *CloudSyncPath = "/var/configurations/CloudSync/credentials/"; +#endif + // USB unmount/ mount + const char *USB_Unmount = "usb_unmount.sh"; + const char *USB_Mount = "usb_mount.sh"; + +} Index: sources/storage/StorageGlobals.h =================================================================== diff -u --- sources/storage/StorageGlobals.h (revision 0) +++ sources/storage/StorageGlobals.h (revision 5a248f0a45889844aa027f4f4a0661aa539975f0) @@ -0,0 +1,146 @@ +/*! + * + * Copyright (c) 2020-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 StorageGlobals.h + * \author (last) Dara Navaei + * \date (last) 07-Feb-2024 + * \author (original) Behrouz NematiPour + * \date (original) 26-Aug-2020 + * + */ +#pragma once + +// Qt +#include + +/*! + * Contains the shared constants between storage classes. + */ +namespace Storage +{ + // POST + extern const char *POST_LOG; + + // OS VERSION + extern const quint16 OS_VERSION_MAJOR; + extern const quint16 OS_VERSION_MINOR; + extern const quint16 OS_VERSION_MICRO; + + // USB + extern const char *USB_Mount_Point; + extern const char *USB_File_System; + + // SD-CARD + extern const short Log_Min_Available_Total_Space_Percent; + extern const short Log_Max_Allowable_AppED_Space_Percent; + extern const short Log_Max_Allowable_Debug_Space_Percent; + extern const short Log_Max_Allowable_Cloud_Space_Percent; + extern short Log_Min_Available_Total_Space_IsLow ( short vPercent ); + + extern const short Txr_Min_Available_Total_Space_Percent; + extern const short Txr_Max_Allowable_Trtmt_Space_Percent; + extern short Txr_Min_Available_Total_Space_IsLow ( short vPercent ); + + extern const char *SDCard_Base_Path_Name; + + // Screenshot store folder + extern const char *Screenshot_Base_Path_Name; + + // Settings + extern void Settings_Secured (); + extern const char *Settings_Path (); + extern const char *Configurations_Path (); + extern const char *Translations_Path (); + + extern const char *Settings_Path_Init ; + extern const char *Settings_Path_Name ; + extern const char *Settings_Category_Instructions ; + extern const char *Settings_Category_InstructionsImagesLoc ; + extern const char *Settings_Category_DataList ; + extern const char *Settings_Category_Alarms ; + extern const char *Settings_Category_Events ; + extern const char *Settings_Category_Rejects ; + extern const char *Settings_Category_MessagesUnhandled ; + extern const char *Settings_Category_SettingsSystem ; + extern const char *Settings_Category_GenericConfirm ; + extern const char *Settings_Category_Translation ; // in qrc + extern const char *Settings_Category_Locale ; // in app location /settings + // Settings - Keys + extern const char *Settings_Key_Title ; + extern const char *Settings_Key_Message ; + extern const char *Settings_Key_ListTitle ; + extern const char *Settings_Key_Confirm ; + extern const char *Settings_Key_Cancel ; + + // CloudSync_Credentials_Path_Name + extern const char *CloudSync_Base_Path_Name; + extern const char *CloudSync_Credentials_Folder_Name; + + // Scripts + extern const QString Scripts_Path_Name(); + + // Log Type Folders + extern const char *Log_Folder_Base; // Base Log Folder + extern const char *Log_Folder_Application; // Event/Data Log + extern const char *Log_Folder_Service; // Service Log + extern const char *Log_Folder_CloudSync; // CloudSync Log + + extern const char *Txr_Folder_Base; // Base Txr Folder + extern const char *Txr_Folder_Treatment; // Treatment Rep + + // Date and Time + extern const char *Date_Time_Set_Sh; + + // WiFi + extern const char *Wifi_Scripts_Dir; + extern const char *Wifi_Disconnect_Network; + extern const char *Wifi_Generate_WPA_Supplicant; + extern const char *Wifi_Read_DNS; + extern const char *Wifi_Read_Gateway; + extern const char *Wifi_Read_IP_Settings; + extern const char *Wifi_Get_Auto_Assigned_IP; + extern const char *Wifi_Reset_Adapter; + extern const char *Wifi_Reset_Interface; + extern const char *Wifi_Scan_For_Networks; + extern const char *Wifi_Set_Auto_Assigned_IP; + extern const char *Wifi_Set_DNS; + extern const char *Wifi_Set_Static_IP; + extern const char *Wifi_Set_Gateway; + extern const char *Wifi_Set_SubnetMask; + extern const char *Wifi_Start_WPA_Supplicant; + extern const char *Wifi_Read_Network_Info; + + // Brightness + extern const char *Brightness_Set; + extern const char *Brightness_Get; + + // RootSSHAccess + extern const char *RootSSHAccess; + + // Bluetooth + extern const char *Bluetooth_Paired_Reset; + extern const char *Bluetooth_Paired_Query; + + // Encrypted Partition - cryptsetup + extern const char *Crypt_Setup; + + // Factory Reset + extern const char *Factory_Reset; + + // Device Decommissioning + extern const char *Device_Decommission; + + // Device Lockdown + extern const char *Device_Lockdown; + + extern const char *CloudSyncPath; + + // USB mount/unmount + extern const char *USB_Unmount; + extern const char *USB_Mount; + +}