/*! * * Copyright (c) 2019-2020 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 DriveWatcher.cpp * \date 12/31/2019 * \author Behrouz NematiPour * */ #include "DriveWatcher.h" // Linux #include #include // Qt #include #include #include // Project #include "storageglobals.h" #include "logger.h" #include "applicationcontroller.h" // namespace using namespace Storage; /*! * \brief DriveWatcher::DriveWatcher * \details Constructor * \param parent - QObject parent owner object. * Qt handles the children destruction by their parent objects life-cycle. */ DriveWatcher::DriveWatcher(QObject *parent) : QObject(parent) { } /*! * \brief DriveWatcher::init * \details Initializes the class by setting the connections and starting the timer * \return False if it has been called before. */ bool DriveWatcher::init() { if ( _init ) return false; _init = true; // runs in DriveWatcher thread initConnections(); startTimer(_interval); return true; } /*! * \brief DriveWatcher::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 DriveWatcher::init(QThread &vThread) { if ( ! init() ) return false; initThread(vThread); return true; } /*! * \brief DriveWatcher::quit * \details quits the class * Calls quitThread */ void DriveWatcher::quit() { // 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 /*! * \brief DriveWatcher::initConnections * \details Initializes the required signal/slot connection between this class and other objects * to be able to communicate. */ void DriveWatcher::initConnections() { connect(&_ApplicationController, SIGNAL(didUSBDriveUmount()), this , SLOT( onUSBDriveUmount())); } /*! * \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 DriveWatcher::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 DriveWatcher::quitThread * \details Moves this object to main thread to be handled by QApplicaiton * And to be destroyed there. */ void DriveWatcher::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 /*! * \brief DriveWatcher::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 DriveWatcher::usbSeek(QString &vDevice) { // coco begin validated: Needed User Interaction so tested manually QString device = ""; for (char a = 'a'; a <= 'z'; a++) { device = QString("/dev/sd%1%2").arg(a).arg('1'); if (QFileInfo::exists(device)) { vDevice = device; return true; // application is deciding on the first existing drive } } vDevice = device; return false; // coco end } /*! * \brief DriveWatcher::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 availabe spcae * \return - The drive mounted and ready * \note if device ejected manually system assumes it's still ready. */ bool DriveWatcher::driveSpaceCheck(const QString &vPath, qint64 &vTotalBytes, qint64 &vAvailableBytes) { QStorageInfo storage(vPath); bool isReady = storage.isReady(); if (isReady) { vTotalBytes = storage.bytesTotal(); vAvailableBytes = storage.bytesAvailable(); } return isReady; } /*! * \brief DriveWatcher::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 DriveWatcher::timerEvent(QTimerEvent *) { usbCheck(); sdcardSpaceCheck(); } /*! * \brief DriveWatcher::usbcheck * \details Runs usbSeek to mount or umount or remove it * regarding the state it's in. */ void DriveWatcher::usbCheck() { // coco begin validated: Needed User Interaction so tested manually QString device = ""; if (usbSeek(device)) { if (! _umounted ) { usbMount(device); } else { usbUmount(USB_Mount_Point); } } else { if ( ! _removed ) { usbRemove(); } } // coco end } /*! * \brief DriveWatcher::sdcardFreeSpaceCheck * \details Checks the drivers for available free space. */ void DriveWatcher::sdcardSpaceCheck() { // coco begin validated: Needed User Interaction so tested manually quint8 mPercent ; // OLd Info ; // Current info static bool mOIsReady ; bool mCIsReady ; static qint64 mOTotal ; qint64 mCTotal ; static qint64 mOAvailable; qint64 mCAvailable; mCIsReady = driveSpaceCheck(Storage::SDCard_Base_Path_Name, mCTotal, mCAvailable); if (mOIsReady == mCIsReady && mOTotal == mCTotal && mOAvailable == mCAvailable ) return; mOIsReady = mCIsReady ; mOTotal = mCTotal ; mOAvailable = mCAvailable ; mPercent = (100 * mCAvailable) / mCTotal; emit didSDCardSpaceChange(mCIsReady, mCTotal, mCAvailable, mPercent); // coco end } /*! * \brief DriveWatcher::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 DriveWatcher::usbError(const QString &vDevice) { // coco begin validated: This needs user interaction to plug-in/out the USB device // has been tested manually 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(error); lastError = error; } } // coco end /*! * \brief DriveWatcher::onUSBDriveUmount * \details This is the slot connected to the _ApplicationController's didUSBDriveUmount SIGNAL, * To notify the USB drive detach. */ void DriveWatcher::onUSBDriveUmount() { // coco begin validated: This needs user interaction to plug-in/out the USB device // has been tested manually _umounted = true; } // coco end /*! * \brief DriveWatcher::usbMount * \details Mounts the USB device vDevice * \note Emits didUSBDriveMount signal * \param vDevice - USB device to be mounted (eg. /dev/sda1) * \return true on successful mount */ bool DriveWatcher::usbMount(const QString &vDevice) { // coco begin validated: This needs user interaction to plug-in the USB device // has been tested manually bool ok; _usbDrive = vDevice.toLatin1().constData(); ok = ::mount(_usbDrive, USB_Mount_Point, USB_File_System,MS_SYNCHRONOUS , "") == 0; if (ok) { _mounted = true; _removed = false; LOG_DEBUG(QString("USB flash drive %1 has been mounted on %2").arg(vDevice).arg(USB_Mount_Point)); emit didUSBDriveMount(); } else { usbError(vDevice); } return ok; } // coco end /*! * \brief DriveWatcher::usbUmount * \details Unmounts the USB device vDevice * \note Emits didUSBDriveUmount signal * \param vDevice - USB device to be unmounted (eg. /dev/sda1) * \return true on successful unmount */ bool DriveWatcher::usbUmount(const QString &vDevice) { // coco begin validated: This needs user interaction to plug-out the USB device // has been tested manually bool ok; ok = ::umount(vDevice.toLatin1().constData()) == 0; if (ok) { _mounted = false; LOG_DEBUG(QString("USB drive %2 unmounted").arg(vDevice)); emit didUSBDriveUmount(); } else { // the error is irrelevant, commented out for now //usbError(vDevice); } return ok; } // coco end /*! * \brief DriveWatcher::usbRemove * \details Removed the USB mount point * So next time it is not mounted as next device. * \note Emits didUSBDriveRemove signal */ void DriveWatcher::usbRemove() { // coco begin validated: This needs user interaction to plug-out the USB device // has been tested manually usbUmount(USB_Mount_Point); _umounted = false; _removed = true; LOG_DEBUG("USB drive removed"); emit didUSBDriveRemove(); } // coco end