/*! * * 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 DeviceController.cpp * \author (last) Behrouz NematiPour * \date (last) 24-Apr-2024 * \author (original) Behrouz NematiPour * \date (original) 03-Jun-2021 * */ #include "DeviceController.h" // Linux #include #include // Qt #include #include #include #include // Project #include "Threads.h" #include "StorageGlobals.h" #include "Logger.h" #include "CloudSyncController.h" #include "ApplicationController.h" #include "FileHandler.h" #include "Settings.h" #include "encryption.h" // namespace using namespace Model; using namespace Device; using namespace Storage; DEVICE_DEV_DECLARATION_LIST /*! * \brief DeviceController::DeviceController * \details Constructor * \param parent - QObject parent owner object. * Qt handles the children destruction by their parent objects life-cycle. */ DeviceController::DeviceController(QObject *parent) : QObject(parent) { _fileSystemWatcher.setParent(this); DEVICE_DEV_PARENT_LIST } /*! * \brief DeviceController::init * \details Initializes the class by setting the connections and starting the timer * \return False if it has been called before. */ bool DeviceController::init() { if ( _init ) return false; _init = true; // runs in DeviceController thread initConnections(); startTimer(_interval); return true; } /*! * \brief DeviceController::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 DeviceController::init(QThread &vThread) { if ( ! init() ) return false; initThread(vThread); return true; } /*! * \brief DeviceController::quit * \details quits the class * Calls quitThread */ void DeviceController::quit() { quitThread(); } /*! * \brief DeviceController::initConnections * \details Initializes the required signal/slot connection between this class and other objects * to be able to communicate. */ void DeviceController::initConnections() { connect(&_ApplicationController , SIGNAL(didUSBDriveUmount()), this , SLOT( onUSBDriveUmount())); connect(this , SIGNAL(didScreenshot (const QImage &, const QString &)), this , SLOT( onScreenshot (const QImage &, const QString &))); connect(&_fileSystemWatcher , SIGNAL( fileChanged (const QString &)), this , SLOT( onWatchFileChanged (const QString &))); connect(&_ApplicationController , SIGNAL(didPOSTOSVersionData (const QString &)), this , SLOT( onPOSTOSVersionData (const QString &))); connect(&_ApplicationController , SIGNAL(didPOSTEthernetData (const QString &)), this , SLOT( onPOSTEthernetData (const QString &))); connect(&_ApplicationController , SIGNAL(didPOSTWirelessData (const QString &)), this , SLOT( onPOSTWirelessData (const QString &))); connect(&_ApplicationController , SIGNAL(didPOSTBluetoothData (const QString &)), this , SLOT( onPOSTBluetoothData (const QString &))); connect(&_ApplicationController , SIGNAL(didPOSTCloudSyncData (const QString &)), this , SLOT( onPOSTCloudSyncData (const QString &))); connect(&_Logger , SIGNAL(didLogBackup (const QString &)), this , SLOT( onLogBackup (const QString &))); connect(&_CloudSyncController , SIGNAL(didLogUpload (const QString &)), this , SLOT( onLogUpload (const QString &))); DEVICE_DEV_INIT_CONNECTIONS_LIST connect(this, SIGNAL(didEventThreadChange()), this, SLOT( onEventThreadChange())); } /*! * \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 DeviceController::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())); moveToThread(_thread); _thread->start(); emit didEventThreadChange( QPrivateSignal() ); } /*! * \brief DeviceController::quitThread * \details Moves this object to main thread to be handled by QApplication * And to be destroyed there. */ void DeviceController::quitThread() { if ( ! _thread ) return; // runs in thread moveToThread(qApp->thread()); // validated } /*! * \brief DeviceController::usbSeek * \details Tries to look for the available USB devices * Starts from sda1 to sdz1. * \note will only look for the first partition if there is any * \param vDevice - Found device (/dev/sda1) * \return true if a device found */ bool DeviceController::usbSeek(QString &vDevice) { // This function cannot be tested on any device other than target // because only on device the usb is /dev/sdX# and others are mmcblk___ // but on VM and normal Linuxes all drives are /dev/sdX# and cannot be identified as USB. // And the difference between how USB is detected and sd-card is that // for the USB we need to look into /dev/sdX# // but for the sd-card we look for the mounted path which is much easier. // That's why indication of the sd-card is more accurate than USB at least on VM. QString dev = "/dev/sd"; QString device = ""; for (char a = 'a'; a <= 'z'; a++) { device = dev + a + '1'; if (QFileInfo::exists(device)) { vDevice = device; return true; // application is deciding on the first existing drive } } vDevice = device; return false; } /*! * \brief DeviceController::driveSpaceCheck * \param vPath - Device mount point * \param vTotalBytes - Total volume size in bytes * \param vAvailableBytes - Size (in bytes) available for the current user * \param vPercent - The percentage of available space * \return - The drive mounted and ready * \note if device ejected manually system assumes it's still ready. */ bool DeviceController::driveSpaceCheck(const QString &vPath, qint64 &vTotalBytes, qint64 &vAvailableBytes, bool *vIsReadOnly) { QStorageInfo storage(vPath); bool isReady = storage.isReady (); bool isReadOnly = storage.isReadOnly(); bool isMounted = FileHandler::isMounted(vPath); if ( vIsReadOnly ) *vIsReadOnly = isReadOnly; if ( isReady ) { vTotalBytes = storage.bytesTotal(); vAvailableBytes = storage.bytesAvailable(); } if ( ! isMounted || ! isReady ) { isReady = false; vTotalBytes = 0; vAvailableBytes = 0; } return isReady; } /*! * \brief DeviceController::timerEvent * \details This event handler has been re-implemented in here * to receive timer events for the object * for the timer which has been set to _checkInterval * Runs the usbCheck on interval */ void DeviceController::timerEvent(QTimerEvent *) { #ifdef BUILD_FOR_TARGET usbCheck(); sdcardSpaceCheck(); // The treatment logs are held in a separate partition from the unencrypted // logs and need a separate disk space usage check settingsPartitionSpaceCheck(); #endif findPendingLogs(); checkWiFi(); } /*! * \brief DeviceController::checkWiFi * \details Periodically check for WiFi network */ void DeviceController::checkWiFi() { if ( ! _wifiAvailable) { emit didWiFiData(tr("WiFi Connection Error")); return; } const QNetworkInterface dev = QNetworkInterface::interfaceFromName("wlan0"); if ( ! dev.isValid() ) { emit didWiFiData(tr("No WiFi")); return; } QList addresses = dev.addressEntries(); if ( addresses.isEmpty() ) { emit didWiFiData(tr("WiFi Not Connected")); return; } if ( ! (dev.flags().testFlag(QNetworkInterface::IsUp) && dev.flags().testFlag(QNetworkInterface::IsRunning))) { emit didWiFiData(tr("WiFi Not Connected")); return; } emit didWiFiData(addresses.first().ip().toString()); } /*! * \brief DeviceController::usbCheck * \details Runs usbSeek to mount or umount or remove it * regarding the state it's in. */ void DeviceController::usbCheck() { QString device = ""; usbSpaceCheck(); if (usbSeek(device)) { if (! _umounted ) { // avoid to mount the USB which has just been unmounted if (! _mounted ) { // avoid to mount the USB which has just been mounted usbMount(device); } } else { // the umount is requested? usbUmount(USB_Mount_Point); } } else { if ( ! _removed ) { usbRemove(); } } } /*! * \brief DeviceController::sdcardSpaceCheck * \details Checks for the SD-Card drive space. */ void DeviceController::sdcardSpaceCheck() { static bool mInitialized = false; // Old Info ; // Current info static bool mOIsReady = false; bool mCIsReady = false; static bool mOIsReadOnly = false; bool mCIsReadOnly = false; static qint64 mOAvailable = 0; qint64 mCAvailable = 0; static quint8 mOPercent = 0; quint8 mCPercent = 0; qint64 mCTotal = 0; bool isMounted = FileHandler::isMounted(Storage::SDCard_Base_Path_Name); QString pathToCheckSpace = isMounted ? Storage::SDCard_Base_Path_Name : gStandard_tmp; mCIsReady = driveSpaceCheck(pathToCheckSpace, mCTotal, mCAvailable, &mCIsReadOnly); //DEBUG: qDebug()<< "Checking space for path of : " << pathToCheckSpace << " mCTotal " << mCTotal << " available " << mCAvailable; if (mOIsReadOnly != mCIsReadOnly || mOIsReady != mCIsReady || ! mInitialized ) { mOIsReadOnly = mCIsReadOnly; mOIsReady = mCIsReady; mInitialized = true; //DEBUG:0: qDebug() << " ~~~~~~~~~~ " << __FUNCTION__ << mInitialized << mCIsReady << mOIsReady << mCIsReadOnly << mOIsReadOnly; emit didSDCardStateChange(mCIsReady, mCIsReadOnly); } //NOTE: this if block has to be independent of the mOIsReady != mCIsReady // because current and old may be the same all the time and then this if block will not execute // and reaches to the log and fills the log unnecessarily. if (! mCIsReady ) { mOPercent = 0; mOAvailable = 0; emit didSDCardSpaceChange(mCIsReady, mCTotal, mCAvailable, mCPercent); return; } mCPercent = mCTotal ? ((100 * mCAvailable) / mCTotal) : 0; if (mCPercent < _minRequiredAvailableSpacePercent) { LOG_DEBUG(QString("SD-CARD space lower than %1%").arg(_minRequiredAvailableSpacePercent)); emit didSDCardSpaceChange(mCIsReady, mCTotal, mCAvailable, mCPercent); emit didSDCardSpaceTooLow(_minRequiredAvailableSpacePercent); } //DEBUG: qDebug() << Storage::SDCard_Base_Path_Name << mCIsReady << mCTotal << mOAvailable << mCAvailable << (mOAvailable == mCAvailable) << mOPercent << mCPercent << mCIsReadOnly; if (mOPercent != mCPercent || mOAvailable != mCAvailable ) { mOPercent = mCPercent ; mOAvailable = mCAvailable ; emit didSDCardSpaceChange(mCIsReady, mCTotal, mCAvailable, mCPercent); /// DEBUG: qDebug() << Storage::SDCard_Base_Path_Name << mCIsReady << mCTotal << mCAvailable << mPercent ; } } /*! * \brief DeviceController::usbSpaceCheck * \details Checks for the USB drive space. */ void DeviceController::usbSpaceCheck() { static bool mInitialized = false; // Old Info ; // Current info static bool mOIsReady = false; bool mCIsReady = false; static bool mOIsReadOnly = false; bool mCIsReadOnly = false; static qint64 mOTotal = 0; qint64 mCTotal = 0; static qint64 mOAvailable = 0; qint64 mCAvailable = 0; quint8 mPercent = 0; mCIsReady = driveSpaceCheck(Storage::USB_Mount_Point, mCTotal, mCAvailable, &mCIsReadOnly); #if BUILD_FOR_DESKTOP mCIsReady = true; // it is set to always true since on desktop a local folder is used for the USB folder which doesn't need (un)mount. #endif if (mOIsReadOnly != mCIsReadOnly || mOIsReady != mCIsReady || ! mInitialized ) { mOIsReadOnly = mCIsReadOnly; mOIsReady = mCIsReady; mInitialized = true; emit didUSBStateChange(mCIsReady, mCIsReadOnly); if (! mCIsReady ) { mOTotal = 0; mOAvailable = 0; emit didUSBSpaceChange(mCIsReady, mCTotal, mCAvailable, mPercent); return; } } mPercent = mCTotal ? ((100 * mCAvailable) / mCTotal) : 0; if (mOTotal != mCTotal || mOAvailable != mCAvailable) { mOTotal = mCTotal ; mOAvailable = mCAvailable ; emit didUSBSpaceChange(mCIsReady, mCTotal, mCAvailable, mPercent); } // DEBUG: qDebug() << "DeviceController::usbSpaceCheck" // << mCIsReady // << mCIsReadOnly // << mCTotal // << mCAvailable ; } /*! * \brief DeviceController::settingsPartitionSpaceCheck * \details Checks the disk space of the encrypted partition */ void DeviceController::settingsPartitionSpaceCheck() { static bool mInitialized = false; // Old Info ; // Current info static bool mOIsReady = false; bool mCIsReady = false; static bool mOIsReadOnly = false; bool mCIsReadOnly = false; static qint64 mOAvailable = 0; qint64 mCAvailable = 0; static quint8 mOPercent = 0; quint8 mCPercent = 0; qint64 mCTotal = 0; mCIsReady = driveSpaceCheck(Storage::Settings_Path(), mCTotal, mCAvailable, &mCIsReadOnly); //DEBUG: qDebug()<< "Checking space for path of : " << Storage::Settings_Path() << " mCTotal " << mCTotal << " available " << mCAvailable; if (mOIsReadOnly != mCIsReadOnly || mOIsReady != mCIsReady || ! mInitialized ) { mOIsReadOnly = mCIsReadOnly; mOIsReady = mCIsReady; mInitialized = true; //DEBUG:0: qDebug() << " ~~~~~~~~~~ " << __FUNCTION__ << mInitialized << mCIsReady << mOIsReady << mCIsReadOnly << mOIsReadOnly; emit didSettingsPartitionStateChange(mCIsReady, mCIsReadOnly); } //NOTE: this if block has to be independent of the mOIsReady != mCIsReady // because current and old may be the same all the time and then this if block will not execute // and reaches to the log and fills the log unnecessarily. if (! mCIsReady ) { mOPercent = 0; mOAvailable = 0; emit didSettingsPartitionSpaceChange(mCIsReady, mCTotal, mCAvailable, mCPercent); return; } mCPercent = mCTotal ? ((100 * mCAvailable) / mCTotal) : 0; if (mCPercent < _minRequiredAvailableSpacePercent) { LOG_DEBUG(QString("Settings partition space lower than %1%").arg(_minRequiredAvailableSpacePercent)); emit didSettingsPartitionSpaceChange(mCIsReady, mCTotal, mCAvailable, mCPercent); emit didSDCardSpaceTooLow(_minRequiredAvailableSpacePercent); } /// DEBUG: qDebug() << Storage::SDCard_Base_Path_Name << mCIsReady << mOTotal << mCTotal << (mOTotal == mCTotal) << mOAvailable << mCAvailable << (mOAvailable == mCAvailable) << mPercent << mCIsReadOnly; if (mOPercent != mCPercent && mOAvailable != mCAvailable ) { mOPercent = mCPercent ; mOAvailable = mCAvailable ; emit didSettingsPartitionSpaceChange(mCIsReady, mCTotal, mCAvailable, mCPercent); /// DEBUG: qDebug() << Storage::SDCard_Base_Path_Name << mCIsReady << mCTotal << mCAvailable << mPercent ; } } /*! * \brief DeviceController::usbError * \details Logs any error which has been happened * On USB device vDevice * \note When this method has been called error number will be read from errno variable, * Which has been set by umount or mount. * \param vDevice */ void DeviceController::usbError(const QString &vDevice) { QString error; static QString lastError; switch (errno) { case EBUSY: error = tr("%1 - Device or resource busy (%2)").arg(errno).arg(vDevice); _mounted = true; break; default: error = tr("%1 - %2 (%3 , %4)").arg(errno).arg(strerror(errno)).arg(vDevice).arg(USB_Mount_Point); break; } if (error != lastError) { LOG_DEBUG("USB: " + error); lastError = error; } } /*! * \brief DeviceController::onUSBDriveUmount * \details This is the slot connected to the _ApplicationController's didUSBDriveUmount SIGNAL, * To notify the USB drive detach. */ void DeviceController::onUSBDriveUmount() { _umounted = true; } /*! * \brief DeviceController::usbMount * \details Mounts the USB device vDevice * \note Emits didUSBDriveMount signal * \param vDevice - USB device to be mounted (e.g. /dev/sda1) * \return true on successful mount */ void DeviceController::usbMount(const QString &vDevice) { usbMountReq(vDevice); } /*! * \brief DeviceController::usbUmount * \details Unmounts the USB device vDevice * \note Emits didUSBDriveUmount signal * \param vDevice - USB device to be unmounted (e.g. /dev/sda1) * \return true on successful unmount */ void DeviceController::usbUmount(const QString &vDevice) { usbMountReq(vDevice, false); } /*! * \brief DeviceController::usbRemove * \details Removed the USB mount point * So next time it is not mounted as next device. * \note Emits didUSBDriveRemove signal */ void DeviceController::usbRemove() { usbUmount(USB_Mount_Point); _umounted = false; _removed = true; LOG_DEBUG("USB drive removed"); emit didUSBDriveRemove(); } /*! * \brief DeviceController::checkError * \details check if has error, then sets the base model accept to false and the reason to the error. * in that case logs the error message and emit the didAttributeResponse to notify the GUI. * \param vError - the error code, this can be the Gui enum or system number * \param vExtraLogInfo - any extra information to be logged. Not display to user. * \return */ template bool DeviceController::checkError(DeviceError::Scripts_Error_Enum vError, TModel &vModel, QString vExtraLogInfo) { if ( vError ) { QString src = (vError > DeviceError::eDevice_Scripts_Error_Start ? MAbstract::unitText(MAbstract::Unit_Enum::eUI) : MAbstract::unitText(MAbstract::Unit_Enum::eDV)) + ","; vModel._data.mAccepted = false ; vModel._data.mReason = vError ; vModel._data.mMessage = DeviceError::deviceErrorText(vError, vError); LOG_APPED(" ," + src + vModel._data.mMessage + " " + vExtraLogInfo); emit didAttributeResponse(vModel._data); return true; } return false; } /*! * \brief DeviceController::doScreenshot * \details emit the screenshot signal to run that in Device controller thread * \param vCurrentDateTime */ void DeviceController::doScreenshot(const QImage &vImage, const QString &vFileName) { emit didScreenshot(vImage, vFileName); } /*! * \brief DeviceController::onScreenshot * \details The function to save the image vImage in the file vFileName * \param vImage - The image source object * \param vFileName - The filename to same the image to */ void DeviceController::onScreenshot(const QImage &vImage, const QString &vFileName) { vImage.save(vFileName); LOG_DEBUG("Screenshot saved in " + vFileName); } /*! * \brief DeviceController::ondoAddWatch * \details The thread safe add file watch method * \param vFile - The file to add to watch. */ void DeviceController::ondoAddWatch(const QString &vFile, bool vCreate) { DeviceError::Scripts_Error_Enum err = DeviceError::eDevice_OK; if ( vCreate ) { if ( ! FileHandler::write ( vFile, "", false) ) { err = DeviceError::eDevice_Watch_Error_NotCreate; goto lErr; }} else { if ( ! QFileInfo::exists ( vFile ) ) { err = DeviceError::eDevice_Watch_Error_NotFound ; goto lErr; }} if ( ! _fileSystemWatcher.removePath ( vFile ) ) { LOG_APPED_UI(QString("Device NOT watching %1").arg(vFile)); } else { LOG_APPED_UI(QString("Device watch removed %1").arg(vFile)); } if ( ! _fileSystemWatcher.addPath ( vFile ) ) { err = DeviceError::eDevice_Watch_Error_NotAdded ; goto lErr; } LOG_APPED_UI(QString("Device watch %1").arg(vFile)); return; lErr: LOG_DEBUG(DeviceError::deviceErrorText(err, 0)); } /*! * \brief DeviceController::onWatchFileChanged * \details This slot is called once the files being watched is updated. * \param vFile - the file name. */ void DeviceController::onWatchFileChanged(const QString &vFile) { emit didWatchFileChange(vFile); } /*! * \brief DeviceController::onEventThreadChange * \details The signal handler for the DeviceController(this)::didEventThreadChange * to start checking for the Encrypted partition readiness. */ void DeviceController::onEventThreadChange() { //DEBUG qDebug() << " ---------- " << __FUNCTION__ << QThread::currentThread()->objectName() << QThread::currentThread() << qApp->thread(); if ( QThread::currentThread() != &Threads::_DeviceController_Thread ) { qCritical() << " ***** Device controller thread not initialized correctly ***** "; return; } _hasThread = true; checkConfugurationMountReady(); checkWifiConnectionReady(); } /*! * \brief DeviceController::onPOSTOSVersionData * \details Collects the OS Version * when it is ready after the POST is done reading OS Version * \param vMacAddress - The Ethernet MAC address */ void DeviceController::onPOSTOSVersionData(const QString &vOSVersion) { _osVersion = vOSVersion; emit didPOSTOSVersionData (vOSVersion); } /*! * \brief DeviceController::onPOSTEthernetData * \details Collects the ethernet mac address * when it is ready after the POST is done for the Ethernet * \param vMacAddress - The Ethernet MAC address */ void DeviceController::onPOSTEthernetData(const QString &vMacAddress) { _macEthernet = vMacAddress; emit didPOSTEthernetData (vMacAddress); } /*! * \brief DeviceController::onPOSTWirelessData * \details Collects the wireless mac address * when it is ready after the POST is done for the Wireless connection * \param vMacAddress - The Wireless MAC address */ void DeviceController::onPOSTWirelessData(const QString &vMacAddress) { _macWireless = vMacAddress; encryption::varSalt(vMacAddress); _hasSalt = ! vMacAddress.trimmed().isEmpty(); checkConfugurationMountReady(); emit didPOSTWirelessData (vMacAddress); } /*! * \brief DeviceController::onPOSTBluetoothData * \details Collects the bluetooth mac address * when it is ready after the POST is done for the Bluetooth * \param vMacAddress - The Bluetooth MAC address */ void DeviceController::onPOSTBluetoothData(const QString &vMacAddress) { _macBluetooth = vMacAddress; emit didPOSTBluetoothData (vMacAddress); } /*! * \brief DeviceController::onPOSTCloudSyncData * \details Collects the CloudSync Network Address * when it is ready after the POST is done for the CloudSync * \param vNetAddress - *** Not defined yet and is a placeholder for later use *** */ void DeviceController::onPOSTCloudSyncData(const QString &vNetAddress) { _netCloudSync = vNetAddress; emit didPOSTCloudSyncData (vNetAddress); } bool DeviceController::logBackup(const QString &vFileName) { if ( ! gLogUpload ) return false; // no log backup bool ok = true; QString fileSrc = vFileName; QFileInfo fileInfo(vFileName); QString filePath(fileInfo.absolutePath()); QString fileBase(fileInfo.baseName()); QString fileSufx(fileInfo.completeSuffix().prepend(_Logger.logFileNamePendingSubExt())); QString fileDest(QString("%1/%2.%3").arg(filePath, fileBase, fileSufx)); // DEBUG qDebug() << __FUNCTION__ << "\n" << vFileName << "\n" << fileDest; if ( gLogCompress ) { fileSrc += _Logger.logFileNameCompressExt(); fileDest += _Logger.logFileNameCompressExt(); ok = FileHandler::backupFile(vFileName); } ok = QFile::rename(fileSrc, fileDest); return ok; } void DeviceController::onLogBackup(const QString &vFileName) { if ( ! gLogUpload ) return; // no log backup ( slot ) bool ok = true; ok = logBackup( vFileName); emit didLogBackup(ok, vFileName); } bool DeviceController::logUpload(const QString &vFileName) { if ( ! gLogUpload ) return false; // no log Uploaded rename bool ok = true; QFileInfo fileInfo(vFileName); QString filePath; QString fileBase(fileInfo.baseName()); QString ext = _Logger.logFileNamePendingSubExt(); QString fileSufx(fileInfo.completeSuffix().remove(ext)); QString fileDest = QString("%1.%2").arg(fileBase, fileSufx); Logger::LogType logType = _Logger.logFileLogType(vFileName, filePath); ok = logType != Logger::eLogNone; if ( ! ok ) { LOG_APPED_UI(QString("CS Incorrect log upload type [%1]").arg(fileSufx)); goto lOut; } // DEBUG qDebug() << __FUNCTION__ << "\n" << vFileName << "\n" << fileDest; ok = QFile::rename(filePath + vFileName, filePath + fileDest); lOut: return ok; } void DeviceController::onLogUpload(const QString &vFileName) { if ( ! gLogUpload ) return; // no log uploaded rename ( slot ) bool ok = true; ok = logUpload( vFileName); emit didLogUpload(ok, vFileName); } /*! * \brief DeviceController::checkConfugurationMountReady * \details Cheks if the system is ready to mount the encrypted partition. * The object has to be on its own thread and the salt for the decryption has to be ready. */ void DeviceController::checkConfugurationMountReady() { /// in manufacturing or updating the system is logged with root and configurations are in /home/root /// therefore no need to mount the cononfiguraiton partition. /// also for manufacturing the partition is being set up /// and is less steps for setup if the partition is not mounted. if ( gEnableManufacturing ) return; // it should do the mount when gEnableUpdating, therefore here should not add gEnableUpdating. if ( ! ( _hasThread && _hasSalt ) ) return; DeviceCryptSetupRequestData data; data.mCommand = "mount"; bool ok = false; data.mPassword = encryption::configurationsPassword( ok ); if ( ! ok ) { // not enough infromation to create a secure passowrd // status(tr("Not enough secure information provided")); } else { // onAttributeRequest(data); // ==================================================== FIX ME } // I_AM_HERE // HERE move the settings read from ApplicationController to here // here is when the DeviceController is moved to its thread // and now can start mounting the encrypted partition // and then read the settings. // TODO don't forget to check for the required configurations files and parameters in the settings class. // and take care of the security flag in the Storage class. } /*! * \brief DeviceController::findPendingLogs * \details this function counts downs for the _pendingInterval * when the _pendingCounter reaches 0 will search for the files * and if there is any will get the recent file in the list */ void DeviceController::findPendingLogs() { if ( ! gLogUpload ) return; // no log upload pending detection static QString pendingLog = ""; if( _pendingCounter ) { _pendingCounter -- ; return; } else { _pendingCounter = _pendingInterval; // every minute } QFileInfoList pendingFiles; QString logLoc = Log_Folder_Base; QString logExt = QString("*.%1*").arg(_Logger.logFileNamePendingSubExt()); // "*.u.*"; for( auto logFolder : { Log_Folder_Application, Log_Folder_Service/*, Log_Folder_CloudSync*/ } ) { pendingFiles = Storage::FileHandler::find( logLoc + logFolder, { logExt } ); // look into the list. // if there are pending files, // send a request only for the top on the list /// Note I thought it makes sense to send the oldest on the application and service logs /// but there are some conversation about the situation if something happens on the device, /// and it would be a critical situation to get the recent/top log and won't wait for the old ones to upload. // * When gets uploaded, moves from pending then next one comes to top // the process repeats until there is no file in pending bool uploadOldestFirst = true; //TODO: make if configurable(cfg, or cli) if ( pendingFiles.count() ) { // the most recent/first log file, to first ask for the current log which has just been saved as pending if ( uploadOldestFirst ) { _pendingLog = pendingFiles.last().absoluteFilePath(); } else { _pendingLog = pendingFiles.first().absoluteFilePath(); } QString message = pendingLog; LOG_DEBUG(message); emit didPendingLog( _pendingLog, FileHandler::sha256sum( _pendingLog ) ); // when a pending file found in the first log folder stop // until there is none in first one (log) // then continue to the next log folder (service) ... goto lOut; } } lOut: return; } ////////////////////////////////////////////////////////////////////////////////////////////////// DeviceUSBMounting void DeviceController::onAttributeRequest(const DeviceUSBMountRequestData &vData) { usbMountReq(vData.usbDevice, vData.isMountRequest); } /*! * \brief DeviceController::usbMountReq * \details Calls the Usb unmount/mount script * \param vIsMount - indicate if the request is for mounting or unmounting * \param vDevice - the path to the USB device */ void DeviceController::usbMountReq(const QString &vDevice, bool vIsMount) { // DEBUG qDebug() << __FUNCTION__ << vDevice << vIsMount; _deviceUSBMountRequest._data.isMountRequest = vIsMount ; _deviceUSBMountRequest._data.usbDevice = vDevice ; // not necessary, but to be consistent // ----- check that script exists. QString script; if ( checkError( DeviceError::checkScript(script, (vIsMount ? Scripts_USB_Mount : Scripts_USB_Unmount )), _deviceUSBMountResponse, script) ) return; // ----- check if the process is not running if ( _processUSBMount.state() != QProcess::NotRunning ) { checkError(DeviceError::eDevice_Scripts_Error_IsRunning, _deviceUSBMountResponse); return; } // ----- run the process int timeout_ms = 5000; TimedProcess *timedProcess = new TimedProcess(&_processUSBMount, script, timeout_ms, {vDevice, USB_Mount_Point, USB_File_System}); timedProcess->start(); MDeviceUSBMountResponse model; model._data.mAccepted = false; model._data.mMessage = vIsMount ? tr("USB unmount started.") : tr("USB mount started"); emit didAttributeResponse(model.data()); } /*! * \brief DeviceController::processUSBMountResponse * \param vExitCode * \param vStatus */ void DeviceController::processUSBMountResponse(int vExitCode, QProcess::ExitStatus vStatus, QProcess::ProcessChannel /*vChannel*/) { MDeviceUSBMountResponse model; QByteArray deviceInfo; if ( vStatus ) vExitCode = Device::DeviceError::eDevice_Scripts_Error_Status; else deviceInfo = _processUSBMount.readAll(); model.fromByteArray( deviceInfo, &vExitCode ); emit didAttributeResponse(model.data()); // Re-evaluate the USB space available - need to call this here to avoid // visual lag caused by waiting to call this function on the timer timeout usbSpaceCheck(); bool ok = ! vStatus; QString usbDevice = _deviceUSBMountRequest._data.usbDevice; if(_deviceUSBMountRequest._data.isMountRequest) { // *** USB Mount if ( ok && ! _mounted ) { _mounted = true; _removed = false; LOG_DEBUG(QString("USB flash drive %1 has been mounted on %2").arg(usbDevice).arg(USB_Mount_Point)); emit didUSBDriveMount(); } else { usbError(usbDevice); } } else { // *** USB Unmount if ( ok && _mounted ) { _mounted = false; // _umounted = true; // I think it might be needed, but needs more testing. LOG_DEBUG(QString("USB drive %2 unmounted").arg(usbDevice)); emit didUSBDriveUmount(); } else { // the error is irrelevant, commented out for now // usbError(usbDrive); } } // log error and exit return; } ////////////////////////////////////////////////////////////////////////////////////////////////// DeviceBrightness /*! * \brief DeviceController::onAttributeRequest * \details Sets the brightness level * \param vBrightness */ void DeviceController::onAttributeRequest(const DeviceBrightnessRequestData &vData) { DeviceError::Scripts_Error_Enum error = DeviceError::eDevice_OK; // ----- initializing the member variable models _deviceBrightnessRequest._data = vData; // ----- extract the required data LOG_APPED( _deviceBrightnessRequest.toString() ); // ----- check that script exists. QString script; if ( checkError( DeviceError::checkScript(script, Scripts_Brightness), _deviceBrightnessResponse, script) ) return; // ----- check if the process is not running if ( _processBrightness.state() != QProcess::NotRunning ) { checkError(DeviceError::eDevice_Scripts_Error_IsRunning, _deviceBrightnessResponse); return; } QStringList params; params << _deviceBrightnessRequest.toByteArray(&error); if ( error ) { checkError(error, _deviceBrightnessResponse); return; } // ----- run the process int timeout_ms = 10000; TimedProcess *timedProcess = new TimedProcess(&_processBrightness, script, timeout_ms, params); timedProcess->start(); } /*! * \brief DeviceController::processBrightnessResponse * \details Called when the process to set the brightness has finished * \param vExitCode (int) the exit code * \note exit code -> 0 : set Accept [MBase] -> Log -> emit * !0 : set Attrib [MBrgh] -> Log -> emit * 1 - get an error when in onAttributeRequest : scriptErrorText([Gui Enum ]) * 2 - get an error when in onProcessBrightnessExitCode : scriptErrorText([vExitCode]) * 3 - get no error when in onProcessBrightnessExitCode : MDeviceResponse.toString() * - in case 3 the specific model _data has to be filled prior to the toString to have it in the log. */ void DeviceController::processBrightnessResponse(int vExitCode, QProcess::ExitStatus vStatus, QProcess::ProcessChannel vChannel) { Q_UNUSED(vStatus ) Q_UNUSED(vChannel ) //DEBUG qDebug() << " 1 ---------- " << __FUNCTION__ << vExitCode << vStatus << vChannel; static MDeviceBrightnessResponse model; if ( vExitCode < 0 ) { emit didAttributeResponse(model.data()); switch (vChannel) { case QProcess::StandardOutput : processBrightnessReadyOut(model); break; case QProcess::StandardError : processBrightnessReadyErr(model); break; } } else { processBrightnessComplete(model, vExitCode, vStatus); emit didAttributeResponse(model.data()); model._data.clear(); } } ////////////////////////////////////////////////////////////////////////////////////////////////// DeviceWifiList void DeviceController::onAttributeRequest(const DeviceWifiListRequestData &) { wifiListRequest(); } /*! * \brief DeviceController::wifiListRequest * \details Calls the Wifi List script * \note This function is created to be able to call independently if needed in this class. */ void DeviceController::wifiListRequest() { // ----- check that script exists. QString script; if ( checkError( DeviceError::checkScript(script, Scripts_Wifi_Scan), _deviceWifiListResponse, script) ) return; // ----- check if the process is not running if ( _processWifiList.state() != QProcess::NotRunning ) { checkError(DeviceError::eDevice_Scripts_Error_IsRunning, _deviceWifiListResponse); return; } // ----- run the process int timeout_ms = 10000; TimedProcess *timedProcess = new TimedProcess(&_processWifiList, script, timeout_ms, {}); //DEBUG qDebug() << script << timeout_ms; timedProcess->start(); MDeviceWifiListResponse model; model._data.mCompleted = false; model._data.mAccepted = false; model._data.mMessage = tr("WiFi list scan started."); emit didAttributeResponse(model.data()); } /*! * \brief DeviceController::processWifiListResponse * \param vExitCode * \param vStatus */ void DeviceController::processWifiListResponse(int vExitCode, QProcess::ExitStatus vStatus, QProcess::ProcessChannel vChannel) { //DEBUG qDebug() << " 1 ---------- " << __FUNCTION__ << vExitCode << vStatus << vChannel; static MDeviceWifiListResponse model; if ( vExitCode < 0 ) { emit didAttributeResponse(model.data()); switch (vChannel) { case QProcess::StandardOutput : processWifiListReadyOut(model); break; case QProcess::StandardError : processWifiListReadyErr(model); break; } } else { processWifiListComplete(model, vExitCode, vStatus); emit didAttributeResponse(model.data()); model._data.clear(); } } ////////////////////////////////////////////////////////////////////////////////////////////////// DeviceWifiInfo void DeviceController::onAttributeRequest(const DeviceWifiInfoRequestData &) { wifiInfoRequest(); } /*! * \brief DeviceController::wifiInfoRequest * \details Calls the Wifi Info script * \note This function is created to be able to call independently if needed in this class. */ void DeviceController::wifiInfoRequest() { // ----- check that script exists. QString script; if ( checkError( DeviceError::checkScript(script, Scripts_Wifi_Info), _deviceWifiInfoResponse, script) ) return; // ----- check if the process is not running if ( _processWifiInfo.state() != QProcess::NotRunning ) { checkError(DeviceError::eDevice_Scripts_Error_IsRunning, _deviceWifiInfoResponse); return; } // ----- run the process int timeout_ms = 10000; TimedProcess *timedProcess = new TimedProcess(&_processWifiInfo, script, timeout_ms, {}); timedProcess->start(); MDeviceWifiInfoResponse model; model._data.mCompleted = false; model._data.mAccepted = false; model._data.mMessage = tr("WiFi info started."); emit didAttributeResponse(model.data()); } /*! * \brief DeviceController::processWifiInfoResponse * \param vExitCode * \param vStatus */ void DeviceController::processWifiInfoResponse(int vExitCode, QProcess::ExitStatus vStatus, QProcess::ProcessChannel vChannel) { static MDeviceWifiInfoResponse model; if ( vExitCode < 0 ) { emit didAttributeResponse(model.data()); switch (vChannel) { case QProcess::StandardOutput : processWifiInfoReadyOut(model); break; case QProcess::StandardError : processWifiInfoReadyErr(model); break; } } else { _wifiAvailable = true; processWifiInfoComplete(model, vExitCode, vStatus); emit didAttributeResponse(model.data()); model._data.clear(); } } /*! * \brief DeviceController::checkWifiConnectionReady * \details This function will run the wifi_info.sh at start up when the * device controller thread is ready */ void DeviceController::checkWifiConnectionReady() { wifiInfoRequest(); } ////////////////////////////////////////////////////////////////////////////////////////////////// DeviceWifiConnect void DeviceController::onAttributeRequest(const DeviceWifiConnectRequestData &vData) { wifiConnectRequest(vData); } /*! * \brief DeviceController::wifiConnectRequest * \details Calls the Wifi connect/ disconnect script * \note This function is created to be able to call independently if needed in this class. */ void DeviceController::wifiConnectRequest(const DeviceWifiConnectRequestData &vData) { DeviceError::Scripts_Error_Enum error = DeviceError::eDevice_OK; // ----- initializing the member variable models _deviceWifiConnectRequest._data = vData; // ----- check that script exists. QString script; if ( checkError( DeviceError::checkScript(script, (vData.mConnect ? Scripts_Wifi_Connect : Scripts_Wifi_Disconnect )), _deviceWifiConnectResponse, script) ) return; // ----- check if the process is not running if ( _processWifiConnect.state() != QProcess::NotRunning ) { checkError(DeviceError::eDevice_Scripts_Error_IsRunning, _deviceWifiConnectResponse); return; } QStringList params; params << vData.mSsid; if (vData.mConnect ) { params << vData.mPassword; } if ( error ) { checkError(error, _deviceWifiConnectResponse); return; } // ----- run the process int timeout_ms = 10000; TimedProcess *timedProcess = new TimedProcess(&_processWifiConnect, script, timeout_ms, params); timedProcess->start(); MDeviceWifiConnectResponse model; model._data.mConnect = vData.mConnect; model._data.mAccepted = false; model._data.mMessage = vData.mConnect ? tr("Connecting to WiFi.") : tr("Disconnecting from WiFi."); emit didAttributeResponse(model.data()); } /*! * \brief DeviceController::processWifiConnectResponse * \param vExitCode * \param vStatus */ void DeviceController::processWifiConnectResponse(int vExitCode, QProcess::ExitStatus vStatus, QProcess::ProcessChannel vChannel) { Q_UNUSED(vStatus ) Q_UNUSED(vChannel ) static MDeviceWifiConnectResponse model; if ( vExitCode < 0 ) { emit didAttributeResponse(model.data()); switch (vChannel) { case QProcess::StandardOutput : processWifiConnectReadyOut(model); break; case QProcess::StandardError : processWifiConnectReadyErr(model); break; } } else { processWifiConnectComplete(model, vExitCode, vStatus); emit didAttributeResponse(model.data()); model._data.clear(); } }