Index: sources/storage/Logger.cpp =================================================================== diff -u -r376859852f4d1a07263c44524aec347db29e0133 -r6c6f1f5d466badd9b4fd67be7c907234c342b2a2 --- sources/storage/Logger.cpp (.../Logger.cpp) (revision 376859852f4d1a07263c44524aec347db29e0133) +++ sources/storage/Logger.cpp (.../Logger.cpp) (revision 6c6f1f5d466badd9b4fd67be7c907234c342b2a2) @@ -1,15 +1,15 @@ /*! * - * Copyright (c) 2019-2020 Diality Inc. - All Rights Reserved. + * Copyright (c) 2020-2023 Diality Inc. - All Rights Reserved. * \copyright * THIS CODE MAY NOT BE COPIED OR REPRODUCED IN ANY FORM, IN PART OR IN * WHOLE, WITHOUT THE EXPLICIT PERMISSION OF THE COPYRIGHT OWNER. * - * \file Logger.cpp - * \author (last) Behrouz NematiPour - * \date (last) 08-Sep-2020 - * \author (original) Behrouz NematiPour - * \date (original) 26-Aug-2020 + * \file Logger.cpp + * \author (last) Behrouz NematiPour + * \date (last) 15-Oct-2022 + * \author (original) Behrouz NematiPour + * \date (original) 26-Aug-2020 * */ #include "Logger.h" @@ -24,7 +24,7 @@ #include // Project -#include "DriveWatcher.h" +#include "DeviceController.h" #include "Threads.h" #include "StorageGlobals.h" #include "MainTimer.h" @@ -57,9 +57,6 @@ checkLogPath(); initConnections(); - ADD_EVENT_HEADER; - LOG_DEBUG("UI," + tr("%1 Initialized").arg(metaObject()->className())); - return true; } @@ -73,26 +70,57 @@ */ bool Logger::init(QThread &vThread) { - // coco begin validated: Application is not running in multi-threaded mode for testing + // 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; } -// coco end +// 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(tr("%1 Initialized").arg(metaObject()->className())); + LOG_DEBUG(tr("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() { - // coco begin validated: Application termination is not correctly done in coco!!! + // 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 } -// coco end +// disabled coco end /*! * \brief Logger::initConnections @@ -114,8 +142,13 @@ connect(&_removeLogsWatcher, SIGNAL(finished ()), this , SLOT(onRemoveLogs())); - connect(&_DriveWatcher, SIGNAL( didSDCardSpaceChange(bool, qint64, qint64, quint8)), - this , SLOT( onSDCardSpaceChange(bool, qint64, qint64, quint8))); + connect(&_DeviceController, SIGNAL( didSDCardStateChange(bool, bool)), + this , SLOT( onSDCardStateChange(bool, bool))); + connect(&_DeviceController, SIGNAL( didSDCardSpaceChange(bool, qint64, qint64, quint8)), + this , SLOT( onSDCardSpaceChange(bool, qint64, qint64, quint8))); + + connect(&_DeviceController, SIGNAL(didCryptSetupMount(bool)), + this , SLOT( onCryptSetupMount(bool))); } /*! @@ -127,7 +160,7 @@ */ void Logger::initThread(QThread &vThread) { - // coco begin validated: Application is not running in multi-threaded mode for testing + // 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" ); @@ -137,25 +170,31 @@ _thread->start(); moveToThread(_thread); } -// coco end +// disabled coco end /*! * \brief Logger::quitThread - * \details Moves this object to main thread to be handled by QApplicaiton + * \details Moves this object to main thread to be handled by QApplication * And to be destroyed there. */ void Logger::quitThread() { - // 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 } -// coco end 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); } @@ -166,28 +205,27 @@ void Logger::checkLogPath() { setLogBasePath(); // try to use /media/sd_card on device - // coco begin validated: It can only happen if the file system is readonly for any reason. - // it has been tested and works perfectly fine in normal run. if (! setLogPath()) { // check and create log folders & if unsuccessful then - // coco end - setLogBasePath(true); // try to use application folder + setLogBasePath(true); // try to use temp folder setLogPath ( ); // check and create log folders // Note: it may require to check for write access regarding device setup } } /*! * \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 can't set the log path to the default. + * 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 vUseApplicationDirPath + * \param vUseTempPath */ -void Logger::setLogBasePath(bool vUseApplicationDirPath) +void Logger::setLogBasePath(bool vUseTempPath) { - if (vUseApplicationDirPath) { - _dir.setPath(qApp->applicationDirPath()); - // Don't use LOG_XXXXX, At this moment Logger has not been initialized yet - qDebug() << QString("Application path used for events logging (%1)").arg(_dir.path()); + if (vUseTempPath) { + _dir.setPath(Storage::Standard_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); @@ -202,34 +240,50 @@ bool Logger::setLogPath() { bool ok = true; - // coco begin validated: Has been tested manually, this needs user interaction to check the file system - if ( ok && ! setLogPath(LogType::eLogEvent) ) ok = false; - if ( ok && ! setLogPath(LogType::eLogDatum) ) ok = false; + if ( ok && ! setLogPath(LogType::eLogAppED) ) ok = false; if ( ok && ! setLogPath(LogType::eLogDebug) ) ok = false; - if ( ok && ! setLogPath(LogType::eLogTrtmt) ) ok = false; return ok; } -// coco end /*! + * \brief Logger::onCryptSetupMount + * \details the handlet for the _DeviceController::didCryptSetupMount + * to set the treatment fodler when the encrypted parttion 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 = Storage::Standard_tmp; goto lOut; } + if ( ! FileHandler::makeFolder (basePath + Storage::Log_Folder_Treatment )) { basePath = Storage::Standard_tmp; goto lOut; } + if ( ! FileHandler::makeFolder (basePath + Storage::Log_Folder_Pending )) { basePath = Storage::Standard_tmp; goto lOut; } + +lOut: + _logPathNames[vLogType] = basePath + Storage::Log_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 doesn't exist and folder can't be created. + * \return returns false if the path does not exist and folder cannot be created. */ bool Logger::setLogPath(LogType vLogType) { bool ok = false; - switch (vLogType) { - case LogType::eLogDebug: - _logPathNames[vLogType] = _dir.path() + "/" + _logBasePathNames[vLogType]; - break; + // treatment logs moved to the encrypted partition/configurations. + // it handled in onCryptSetupMount + if (vLogType == LogType::eLogTrtmt ) return true; - default: - _logPathNames[vLogType] = _dir.path() + "/" + _logBasePathNames[vLogType]; - break; - } + _logPathNames[vLogType] = _dir.path() + "/" + _logBasePathNames[vLogType]; ok = FileHandler::makeFolder(_logPathNames[vLogType]); if ( ok ) emit didLogPathSet(vLogType, _logPathNames[vLogType]); return ok; @@ -245,21 +299,30 @@ */ void Logger::log(const QString &vContent, LogType vLogType, bool vTimestamp) { + if ( ! checkThread() ) return; + static QString date; QString mContent; + + // - Add header QString currentDate = QDate::currentDate().toString(_dateFormat); - if (date != currentDate) { - if (!date.isEmpty()) { - mContent = _headerE; - mContent += "\r\n" ; + if ( date != currentDate ) { + if ( ! date.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"; } date = currentDate; } + // - Make log file name QString fileName = date + _dateSeparator + _logFileNamePrefix; switch (vLogType) { - case LogType::eLogEvent: - case LogType::eLogDatum: + case LogType::eLogAppED: case LogType::eLogDebug: // case LogType::eLogTrtmt: // this type of log will never happen here. Only put here to make sure it is intentional. fileName += _logFileNameExt[vLogType]; @@ -270,26 +333,26 @@ LOG_DEBUG(QString("Incorrect type of logging %1").arg(vLogType)); } + // - Add timestamp if ( vTimestamp ) mContent = QTime::currentTime().toString(_timeFormat) + _separator; - QString logPrefix = _logPrefix[vLogType]; - if (vTimestamp && ! logPrefix.isEmpty()) { - mContent += logPrefix; - mContent += _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; FileHandler::write(_logFileName, mContent + "\r\n", true); - // coco begin validated: This code is only for debugging purposes and had been tested manually. + + // console out the log if enabled. if (_enableConsoleOut) { qDebug().noquote() << mContent; } - // coco end } /*! @@ -298,56 +361,110 @@ * into USB drive folder (Storage::USB_Mount_Point) * \return true if at least one file has been exported */ -bool Logger::exportLogs() +bool Logger::exportLogs(const Gui::GuiStringIndexMap &vExportList) { - // coco begin validated: This needs user interaction to check the old files deleted - // has been tested manually - int result = 0; - static QString mOSource; - QString mDestination = USB_Mount_Point; - for ( const auto &iType : { eLogEvent, eLogDatum /*, eLogDebug*/ } ) { - QString mCSource = _logPathNames[iType]; - // if the event and datum are mixed (mOSource == mCSource) in one file then no need to go over the same files in same folder and do it again. - if (mOSource != mCSource) { - mOSource = mCSource; - // Copy Folder - result = FileHandler::copyFolder( mCSource, mDestination); - } + return exportList(vExportList, eLogAppED); +} + +/*! + * \brief Logger::exportService + * \details Exports the service files from service folder (Storage::Log_Base_Path_Name_Location) + * into USB drive folder (Storage::USB_Mount_Point) + * \return true if at least one file has been exported + */ +bool Logger::exportErrs(const Gui::GuiStringIndexMap &vExportList) +{ + return exportList(vExportList, eLogDebug); +} + +/*! + * \brief Logger::exportTreatment + * \details Exports the treatment files from treatment folder (Storage::Log_Base_Path_Name_Location) + * into USB drive folder (Storage::USB_Mount_Point) + * \return true if at least one file has been exported + */ +bool Logger::exportTrts(const Gui::GuiStringIndexMap &vExportList) +{ + return exportList(vExportList, eLogTrtmt); +} + +/*! + * \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; } - mOSource = ""; - return result >= 0; // refer to QProcess::execute(hit F1 on execute) doc. + return true; } -// coco end /*! * \brief Logger::concurrentExportLogs * \details Export logs scheduler. * \return always returns true for now. * \note This method uses QtConcurrent run to execute the FileHandler copyFolder method. */ -bool Logger::concurrentExportLogs() +bool Logger::concurrentExportLogs(const Gui::GuiStringIndexMap &vExportList) { - // coco begin validated: This needs user interaction to export to USB device - // has been tested manually - LOG_DEBUG("Export Logs Start"); - QFuture future = QtConcurrent::run(this, &Logger::exportLogs); + if ( ! concurrentExportIsOk() ) return false; + + _exportLogsType = eLogAppED; + LOG_DEBUG(QString("Export %1 start").arg(_logNames[_exportLogsType])); + QFuture future = QtConcurrent::run(this, &Logger::exportLogs, vExportList); _exportLogsWatcher.setFuture(future); return true; } -// coco end /*! + * \brief Logger::concurrentExportLogs + * \details Export logs scheduler. + * \return always returns true for now. + * \note This method uses QtConcurrent run to execute the FileHandler copyFolder method. + */ +bool Logger::concurrentExportErrs(const Gui::GuiStringIndexMap &vExportList) +{ + if ( ! concurrentExportIsOk() ) return false; + + _exportLogsType = eLogDebug; + LOG_DEBUG(QString("Export %1 start").arg(_logNames[_exportLogsType])); + QFuture future = QtConcurrent::run(this, &Logger::exportErrs, vExportList); + _exportLogsWatcher.setFuture(future); + return true; +} + +/*! + * \brief Logger::concurrentExportLogs + * \details Export logs scheduler. + * \return always returns true for now. + * \note This method uses QtConcurrent run to execute the FileHandler copyFolder method. + */ +bool Logger::concurrentExportTrts(const Gui::GuiStringIndexMap &vExportList) +{ + if ( ! concurrentExportIsOk() ) return false; + + _exportLogsType = eLogTrtmt; + LOG_DEBUG(QString("Export %1 start").arg(_logNames[_exportLogsType])); + QFuture future = QtConcurrent::run(this, &Logger::exportTrts, vExportList); + _exportLogsWatcher.setFuture(future); + return true; +} + +/*! * \brief Logger::onExportLogs * \details Export log notification slot which logs result of export. */ void Logger::onExportLogs() { - // coco begin validated: This needs user interaction to export to USB device + // disabled coco begin validated: This needs user interaction to export to USB device // has been tested manually - LOG_DEBUG(QString("Export Logs Ended: %1").arg(_exportLogsWatcher.result())); + LOG_DEBUG(QString("Export %1 ended: %2").arg(_logNames[_exportLogsType]).arg(_exportLogsWatcher.result())); emit didExportLogs(); } -// coco end +// disabled coco end /*! * \brief Logger::removeLogs @@ -356,59 +473,45 @@ */ int Logger::removeLogs() { - // coco begin validated: This needs user interaction to check the old files deleted // Storage::FileHandler::find("/media/denali/0CAA-40C1/log/", {"*.err"}, 15); return 0; // has been tested manually - LOG_DEBUG("Initializing log clean up"); - static QString mOSource; - static QString mOExtension; + LOG_DEBUG(tr("Initializing log clean up")); int removeCount = 0; - QString mLogFileFilter; - for ( const auto &iType : { eLogEvent , eLogDatum , eLogDebug , eLogTrtmt } ) { + for ( const auto &iType : { eLogAppED , eLogDebug , eLogTrtmt } ) { QString mCSource = _logPathNames [iType]; QString mCExtension = _logFileNameExt[iType]; - // if the event and datum are mixed (mOSource == mCSource && mCExtension == mOExtension) in one file then no need to go over the same files in same folder and do it again. - if (mOSource != mCSource || mCExtension != mOExtension) { - mOSource = mCSource; - mOExtension = mCExtension; - // Remove Logs - mLogFileFilter = QString("*%1").arg(mCExtension); - /// DEBUG: since it has been manually tested this will help next time for test. - /// qDebug() << "@" << mCSource << mLogFileFilter << mCExtension << iType << _logTypeMaxUsageLimit[iType]; - QFileInfoList fileInfoList = FileHandler::find(mCSource, {mLogFileFilter}, _logTypeMaxUsageLimit[iType]); - removeCount = fileInfoList.count(); - if (removeCount) { - 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) { - if (info.lastModified().date() == QDate().currentDate()) { - LOG_DEBUG(QString("Current day log %1 cannot be deleted").arg(info.fileName())); + // DEBUG: qDebug() << "@" << mCSource << mLogFileFilter << mCExtension << iType << _logTypeMaxUsageLimit[iType]; + QFileInfoList fileInfoList = FileHandler::find(mCSource, {"*.*"}, _logTypeMaxUsageLimit[iType]); + removeCount = fileInfoList.count(); + // qDebug() << "@" << removeCount << fileInfoList; + if (removeCount) { + 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) { + if (info.lastModified().date() == QDate().currentDate()) { + LOG_DEBUG(QString("Current day log %1 cannot be deleted").arg(info.fileName())); + } + else { + 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)); } - else { - QString mFileName = mCSource + info.fileName(); - /// DEBUG: since it has been manually tested this will help next time for test. - /// 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)); - } - } } } - else { - LOG_DEBUG("No log file is deleted for " + mCExtension); - } } + else { + LOG_DEBUG("No log file is deleted for " + mCExtension); + } } - mOSource = ""; return removeCount; } -// coco end /*! * \brief Logger::concurrentRemoveLogs @@ -418,29 +521,37 @@ */ bool Logger::concurrentRemoveLogs() { - // coco begin validated: This needs user interaction to check the old files deleted + // 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); _removeLogsWatcher.setFuture(mFuture); return true; } -// coco end +// disabled coco end /*! * \brief Logger::onRemoveLogs * \details Remove old logs notification slot which logs result of remove. */ void Logger::onRemoveLogs() { - // coco begin validated: This needs user interaction to export to USB device - // has been tested manually - LOG_DEBUG(QString("Remove Logs Ended: %1").arg(_removeLogsWatcher.result())); + LOG_DEBUG(tr("Remove Logs Ended: %1").arg(_removeLogsWatcher.result())); emit didRemoveLogs(false); } -// coco end +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. @@ -454,25 +565,26 @@ */ void Logger::onSDCardSpaceChange(bool vReady, qint64 vTotal, qint64 vAvailable, quint8 vPercent) { - // coco begin validated: This needs user interaction to change the SD card files system. + // 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 ( vPercent < Storage::Available_Space_Percent ) { + + // DEBUG: qDebug() << vPercent << Storage::Available_Space_Percent; + if ( Storage::Log_Min_Available_Total_Space_IsLow(vPercent) ) { concurrentRemoveLogs(); } } -// coco end +// 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) { - // coco begin validated: This code meant to be used only for debugging and tested manually + // disabled coco begin validated: This code meant to be used only for debugging and tested manually if (_enableConsoleOut == vEnabled) return; _enableConsoleOut = vEnabled; if (_enableConsoleOut) { @@ -481,7 +593,7 @@ LOG_DEBUG("Console out Logging disabled"); } } -// coco end +// disabled coco end /*! * \brief Logger::logPath @@ -494,3 +606,40 @@ { return _logPathNames[vLogType]; } + +/*! + * \brief Logger::exportList + * \details Exports files from the list if the vExportList has any item, otherwise exports all the log files of type vLogType. + * \param vExportList - List of files to export + * \param vLogType - type of the log files to get the correct location for export. + * \return true if the export is successful. + */ +bool Logger::exportList(const Gui::GuiStringIndexMap &vExportList, Logger::LogType vLogType) +{ + // DEBUG: qDebug() << __FUNCTION__ << vExportList; + // qDebug() << " ~~~~~~~~~~ " << QThread::currentThread()->objectName(); + + auto notifier = [this] (quint32 vIndex, const QString &vFileName, quint8 vPercent) { + emit didExportStat(vIndex, vFileName, vPercent); + // qDebug() << "0" << vIndex << vFileName << vPercent; + }; + + int result = 0; + QString mSource = _logPathNames[vLogType]; + QString mDestination = USB_Mount_Point; + if ( vExportList.isEmpty() ) { + // Copy Folder + result = FileHandler::copyFolder( mSource, mDestination); + } + else { + Gui::GuiStringIndexMapIterator it(vExportList); + while ( it.hasNext() ) { + it.next(); + // qDebug() << it.key() << it.value() << mSource << mDestination; + auto index = it.key (); + auto filename = it.value(); + result = FileHandler::copyFile(mSource, mDestination + _logBasePathNames[vLogType], filename, ¬ifier, index); + } + } + return result >= 0; // refer to QProcess::execute(hit F1 on execute) doc. +}