From 3f5a5ab209a12b9339e0d40c8d4739103933e0b3 Mon Sep 17 00:00:00 2001 From: Christian Heimlich Date: Sun, 15 Oct 2023 16:22:17 -0400 Subject: [PATCH] Allow for handling different daemon's dynamically at runtime --- CMakeLists.txt | 18 +- app/CMakeLists.txt | 33 +- app/src/kernel/core.cpp | 65 ++-- app/src/kernel/core.h | 2 +- app/src/kernel/driver.cpp | 10 +- app/src/kernel/driver.h | 11 +- app/src/task/t-mount.cpp | 148 +++++++-- app/src/task/t-mount.h | 36 ++- app/src/tools/mounter.cpp | 347 --------------------- app/src/tools/mounter_proxy.cpp | 131 ++++---- app/src/tools/mounter_proxy.h | 32 +- app/src/tools/mounter_qmp.cpp | 216 +++++++++++++ app/src/tools/{mounter.h => mounter_qmp.h} | 82 ++--- app/src/tools/mounter_router.cpp | 147 +++++++++ app/src/tools/mounter_router.h | 117 +++++++ app/src/tools/processbider.h | 2 +- 16 files changed, 792 insertions(+), 605 deletions(-) delete mode 100644 app/src/tools/mounter.cpp create mode 100644 app/src/tools/mounter_qmp.cpp rename app/src/tools/{mounter.h => mounter_qmp.h} (68%) create mode 100644 app/src/tools/mounter_router.cpp create mode 100644 app/src/tools/mounter_router.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 9496988..60e4af9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -27,14 +27,6 @@ set(TARGET_FP_VERSION_PREFIX 12.0) # Handled by fetched libs, but set this here formally since they aren't part of the main project option(BUILD_SHARED_LIBS "Build CLIFp with shared libraries" OFF) -if(CMAKE_SYSTEM_NAME STREQUAL Linux) - set(FP_PROXY_DEFAULT ON) -else() - set(FP_PROXY_DEFAULT OFF) -endif() - -option(FP_PROXY "Configure CLIFp to work with an install that uses an FP Proxy Server" ${FP_PROXY_DEFAULT}) - # C++ set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) @@ -87,13 +79,11 @@ ob_fetch_qx( # Fetch libfp (build and import from source) include(OB/Fetchlibfp) -ob_fetch_libfp("bca00c48d37e9306a311bc83f74a7692e169afae") +ob_fetch_libfp("239658b41b43c700adc612a390d4602683df334b") -if(NOT FP_PROXY) - # Fetch QI-QMP (build and import from source) - include(OB/FetchQI-QMP) - ob_fetch_qi_qmp("v0.2.2") -endif() +# Fetch QI-QMP (build and import from source) +include(OB/FetchQI-QMP) +ob_fetch_qi_qmp("v0.2.2") # Fetch QuaZip (build and import from source) include(OB/FetchQuaZip) diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt index b926ce3..b8ebc77 100644 --- a/app/CMakeLists.txt +++ b/app/CMakeLists.txt @@ -44,6 +44,12 @@ set(CLIFP_SOURCE tools/blockingprocessmanager.cpp tools/deferredprocessmanager.h tools/deferredprocessmanager.cpp + tools/mounter_proxy.h + tools/mounter_proxy.cpp + tools/mounter_qmp.h + tools/mounter_qmp.cpp + tools/mounter_router.h + tools/mounter_router.cpp frontend/statusrelay.h frontend/statusrelay.cpp controller.h @@ -67,6 +73,7 @@ set(CLIFP_LINKS Fp::Fp QuaZip::QuaZip magic_enum::magic_enum + QI-QMP::Qmpi ) if(CMAKE_SYSTEM_NAME STREQUAL Windows) @@ -98,27 +105,6 @@ if(CMAKE_SYSTEM_NAME STREQUAL Linux) ) endif() -# FP Proxy specific changes -if(FP_PROXY) - list(APPEND CLIFP_SOURCE - tools/mounter_proxy.h - tools/mounter_proxy.cpp - ) - list(APPEND CLIFP_DEFINITIONS - PRIVATE - "FP_PROXY" - ) -else() - list(APPEND CLIFP_SOURCE - tools/mounter.h - tools/mounter.cpp - ) - list(APPEND CLIFP_LINKS - PRIVATE - QI-QMP::Qmpi - ) -endif() - # Add via ob standard executable include(OB/Executable) ob_add_standard_executable(${APP_TARGET_NAME} @@ -132,11 +118,6 @@ ob_add_standard_executable(${APP_TARGET_NAME} WIN32 ) -# Add environment specific defines -if(CMAKE_SYSTEM_NAME STREQUAL Windows) - target_compile_definitions(${APP_TARGET_NAME} PRIVATE "FP_PROXY_MOUNTER") -endif() - ## Forward select project variables to C++ code include(OB/CppVars) ob_add_cpp_vars(${APP_TARGET_NAME} diff --git a/app/src/kernel/core.cpp b/app/src/kernel/core.cpp index 06e85c4..6aade86 100644 --- a/app/src/kernel/core.cpp +++ b/app/src/kernel/core.cpp @@ -322,8 +322,7 @@ void Core::attachFlashpoint(std::unique_ptr flashpointInstall) mFlashpointInstall = std::move(flashpointInstall); // Note install details - QString dmns = QString(magic_enum::enum_flags_name(static_cast(mFlashpointInstall->services().recognizedDaemons.toInt())).data()); - logEvent(NAME, LOG_EVENT_RECOGNIZED_DAEMONS.arg(dmns)); + logEvent(NAME, LOG_EVENT_OUTFITTED_DAEMON.arg(ENUM_NAME(mFlashpointInstall->outfittedDaemon()))); // Initialize child process env vars QProcessEnvironment de = QProcessEnvironment::systemEnvironment(); @@ -425,21 +424,23 @@ CoreError Core::enqueueStartupTasks() logEvent(NAME, LOG_EVENT_ENQ_START); #ifdef __linux__ - /* On Linux X11 Server needs to be temporarily be set to allow connections from root for docker - * - * TODO: It should be OK to skip this (and the corresponding shutdown task that reverses it), - * if docker isn't used, but leaving for now. + /* On Linux X11 Server needs to be temporarily be set to allow connections from root for docker, + * if it's in use */ - TExec* xhostSet = new TExec(this); - xhostSet->setIdentifier(u"xhost Set"_s); - xhostSet->setStage(Task::Stage::Startup); - xhostSet->setExecutable(u"xhost"_s); - xhostSet->setDirectory(mFlashpointInstall->fullPath()); - xhostSet->setParameters({u"+SI:localuser:root"_s}); - xhostSet->setProcessType(TExec::ProcessType::Blocking); - - mTaskQueue.push(xhostSet); - logTask(NAME, xhostSet); + + if(mFlashpointInstall->outfittedDaemon() == Fp::Daemon::Docker) + { + TExec* xhostSet = new TExec(this); + xhostSet->setIdentifier(u"xhost Set"_s); + xhostSet->setStage(Task::Stage::Startup); + xhostSet->setExecutable(u"xhost"_s); + xhostSet->setDirectory(mFlashpointInstall->fullPath()); + xhostSet->setParameters({u"+SI:localuser:root"_s}); + xhostSet->setProcessType(TExec::ProcessType::Blocking); + + mTaskQueue.push(xhostSet); + logTask(NAME, xhostSet); + } #endif // Get settings @@ -503,7 +504,7 @@ CoreError Core::enqueueStartupTasks() #ifdef __linux__ // On Linux the startup tasks take a while so make sure the docker image is actually running before proceeding - if(mFlashpointInstall->services().recognizedDaemons.testFlag(Fp::KnownDaemon::Docker)) + if(mFlashpointInstall->outfittedDaemon() == Fp::Daemon::Docker) { TAwaitDocker* dockerWait = new TAwaitDocker(this); dockerWait->setStage(Task::Stage::Startup); @@ -551,17 +552,20 @@ void Core::enqueueShutdownTasks() } #ifdef __linux__ - // Undo xhost permissions modifications - TExec* xhostClear = new TExec(this); - xhostClear->setIdentifier(u"xhost Clear"_s); - xhostClear->setStage(Task::Stage::Shutdown); - xhostClear->setExecutable(u"xhost"_s); - xhostClear->setDirectory(mFlashpointInstall->fullPath()); - xhostClear->setParameters({"-SI:localuser:root"}); - xhostClear->setProcessType(TExec::ProcessType::Blocking); - - mTaskQueue.push(xhostClear); - logTask(NAME, xhostClear); + // Undo xhost permissions modifications related to docker + if(mFlashpointInstall->outfittedDaemon() == Fp::Daemon::Docker) + { + TExec* xhostClear = new TExec(this); + xhostClear->setIdentifier(u"xhost Clear"_s); + xhostClear->setStage(Task::Stage::Shutdown); + xhostClear->setExecutable(u"xhost"_s); + xhostClear->setDirectory(mFlashpointInstall->fullPath()); + xhostClear->setParameters({"-SI:localuser:root"}); + xhostClear->setProcessType(TExec::ProcessType::Blocking); + + mTaskQueue.push(xhostClear); + logTask(NAME, xhostClear); + } #endif } @@ -679,15 +683,12 @@ Qx::Error Core::enqueueDataPackTasks(const Fp::GameData& gameData) { logEvent(NAME, LOG_EVENT_DATA_PACK_NEEDS_MOUNT); - // Determine if QEMU is involved - bool qemuUsed = mFlashpointInstall->services().recognizedDaemons.testFlag(Fp::KnownDaemon::Qemu); - // Create task TMount* mountTask = new TMount(this); mountTask->setStage(Task::Stage::Auxiliary); mountTask->setTitleId(gameData.gameId()); mountTask->setPath(packDestFolderPath + '/' + packFileName); - mountTask->setSkipQemu(!qemuUsed); + mountTask->setDaemon(mFlashpointInstall->outfittedDaemon()); mTaskQueue.push(mountTask); logTask(NAME, mountTask); diff --git a/app/src/kernel/core.h b/app/src/kernel/core.h index 5e8e7cc..cf71532 100644 --- a/app/src/kernel/core.h +++ b/app/src/kernel/core.h @@ -143,7 +143,7 @@ class Core : public QObject static inline const QString LOG_EVENT_G_HELP_SHOWN = u"Displayed general help information"_s; static inline const QString LOG_EVENT_VER_SHOWN = u"Displayed version information"_s; static inline const QString LOG_EVENT_NOTIFCATION_LEVEL = u"Notification Level is: %1"_s; - static inline const QString LOG_EVENT_RECOGNIZED_DAEMONS = u"Recognized service daemons: %1"_s; + static inline const QString LOG_EVENT_OUTFITTED_DAEMON = u"Recognized daemon: %1"_s; static inline const QString LOG_EVENT_ENQ_START = u"Enqueuing startup tasks..."_s; static inline const QString LOG_EVENT_ENQ_STOP = u"Enqueuing shutdown tasks..."_s; static inline const QString LOG_EVENT_ENQ_DATA_PACK = u"Enqueuing Data Pack tasks..."_s; diff --git a/app/src/kernel/driver.cpp b/app/src/kernel/driver.cpp index c851d40..8a6036f 100644 --- a/app/src/kernel/driver.cpp +++ b/app/src/kernel/driver.cpp @@ -163,7 +163,15 @@ std::unique_ptr Driver::findFlashpointInstall() // Attempt to instantiate fpInstall = std::make_unique(currentDir.absolutePath()); if(fpInstall->isValid()) - break; + { + if(fpInstall->outfittedDaemon() == Fp::Daemon::Unknown) + { + mCore->logError(NAME, Qx::GenericError(Qx::Warning, 12011, LOG_WARN_FP_UNRECOGNIZED_DAEMON)); + fpInstall.reset(); + } + else + break; + } else { mCore->logError(NAME, fpInstall->error().setSeverity(Qx::Warning)); diff --git a/app/src/kernel/driver.h b/app/src/kernel/driver.h index 423ecb8..bc9c1c9 100644 --- a/app/src/kernel/driver.h +++ b/app/src/kernel/driver.h @@ -15,7 +15,7 @@ class QX_ERROR_TYPE(DriverError, "DriverError", 1201) { friend class Driver; - //-Class Enums------------------------------------------------------------- +//-Class Enums------------------------------------------------------------- public: enum Type { @@ -25,7 +25,7 @@ class QX_ERROR_TYPE(DriverError, "DriverError", 1201) InvalidInstall = 3, }; - //-Class Variables------------------------------------------------------------- +//-Class Variables------------------------------------------------------------- private: static inline const QHash ERR_STRINGS{ {NoError, u""_s}, @@ -34,16 +34,16 @@ class QX_ERROR_TYPE(DriverError, "DriverError", 1201) {InvalidInstall, u"CLIFp does not appear to be deployed in a valid Flashpoint install"_s} }; - //-Instance Variables------------------------------------------------------------- +//-Instance Variables------------------------------------------------------------- private: Type mType; QString mSpecific; - //-Constructor------------------------------------------------------------- +//-Constructor------------------------------------------------------------- private: DriverError(Type t = NoError, const QString& s = {}); - //-Instance Functions------------------------------------------------------------- +//-Instance Functions------------------------------------------------------------- public: bool isValid() const; Type type() const; @@ -71,6 +71,7 @@ class Driver : public QObject // Logging static inline const QString LOG_EVENT_FLASHPOINT_SEARCH = u"Searching for Flashpoint root..."_s; static inline const QString LOG_EVENT_FLASHPOINT_ROOT_CHECK = uR"(Checking if "%1" is flashpoint root)"_s; + static inline const QString LOG_WARN_FP_UNRECOGNIZED_DAEMON = "Flashpoint install does not contain a recognized daemon!"; static inline const QString LOG_EVENT_FLASHPOINT_LINK = uR"(Linked to Flashpoint install at: "%1")"_s; static inline const QString LOG_EVENT_TASK_COUNT = u"%1 task(s) to perform"_s; static inline const QString LOG_EVENT_QUEUE_START = u"Processing Task queue"_s; diff --git a/app/src/task/t-mount.cpp b/app/src/task/t-mount.cpp index b8288ce..0bbb858 100644 --- a/app/src/task/t-mount.cpp +++ b/app/src/task/t-mount.cpp @@ -3,6 +3,10 @@ // Qt Includes #include +#include + +// Qx Includes +#include //=============================================================================================================== // TMount @@ -12,17 +16,24 @@ //Public: TMount::TMount(QObject* parent) : Task(parent), - mMounter() + mMounterProxy(nullptr), + mMounterQmp(nullptr), + mMounterRouter(nullptr) +{} + +//-Instance Functions------------------------------------------------------------- +//Private: +template + requires Qx::any_of +void TMount::initMounter(M*& mounter) { - // Connect mounter signals - connect(&mMounter, &Mounter::errorOccured, this, &TMount::errorOccurred); - connect(&mMounter, &Mounter::eventOccured, this, &TMount::eventOccurred); - connect(&mMounter, &Mounter::mountProgressMaximumChanged, this, &Task::longTaskTotalChanged); - connect(&mMounter, &Mounter::mountProgress, this, &Task::longTaskProgressChanged); - connect(&mMounter, &Mounter::mountFinished, this, &TMount::postMount); + mounter = new M(this); + + connect(mounter, &M::eventOccurred, this, &Task::eventOccurred); + connect(mounter, &M::errorOccurred, this, &Task::errorOccurred); + connect(mounter, &M::mountFinished, this, &TMount::mounterFinishHandler); } -//-Instance Functions------------------------------------------------------------- //Public: QString TMount::name() const { return NAME; } QStringList TMount::members() const @@ -33,52 +44,133 @@ QStringList TMount::members() const return ml; } -bool TMount::isSkipQemu() const { return mSkipQemu; } QUuid TMount::titleId() const { return mTitleId; } QString TMount::path() const { return mPath; } +Fp::Daemon TMount::daemon() const { return mDaemon; } -void TMount::setSkipQemu(bool skip) { mSkipQemu = skip; } void TMount::setTitleId(QUuid titleId) { mTitleId = titleId; } void TMount::setPath(QString path) { mPath = path; } +void TMount::setDaemon(Fp::Daemon daemon) { mDaemon = daemon; } void TMount::perform() { + mMounting = true; + // Log/label string QFileInfo packFileInfo(mPath); QString label = LOG_EVENT_MOUNTING_DATA_PACK.arg(packFileInfo.fileName()); - //-Setup Mounter------------------------------------ -#ifdef FP_PROXY - mMounter.setProxyServerPort(22501); -#else - mMounter.setWebServerPort(22500); - mMounter.setQemuMountPort(22501); - mMounter.setQemuProdPort(0); - mMounter.setQemuEnabled(!mSkipQemu); -#endif - // Start mount emit longTaskStarted(label); -#ifdef FP_PROXY - mMounter.mount(mPath); -#else - mMounter.mount(mTitleId, mPath); -#endif + + // Update state + emit longTaskProgressChanged(0); + emit longTaskTotalChanged(0); // Cause busy state + + //-Setup Mounter(s)------------------------------------ + + if(mDaemon == Fp::Daemon::FpProxy) + { + initMounter(mMounterProxy); + mMounterProxy->setFilePath(mPath); + mMounterProxy->setProxyServerPort(22501); + } + else + { + QString routerMountValue = QFileInfo(mPath).fileName(); + + if(mDaemon == Fp::Daemon::Qemu) + { + // Generate random sequence of 16 lowercase alphabetical characters to act as Drive ID + QByteArray alphaBytes; + alphaBytes.resize(16); + std::generate(alphaBytes.begin(), alphaBytes.end(), [](){ + // Funnel numbers into 0-25, use ASCI char 'a' (0x61) as a base value + return (QRandomGenerator::global()->generate() % 26) + 0x61; + }); + QString driveId = QString::fromLatin1(alphaBytes); + + // Convert UUID to 20 char drive serial by encoding to Base85 + Qx::Base85Encoding encoding(Qx::Base85Encoding::Btoa); + encoding.resetZeroGroupCharacter(); // No shortcut characters + QByteArray rawTitleId = mTitleId.toRfc4122(); // Binary representation of UUID + Qx::Base85 driveSerialEnc = Qx::Base85::encode(rawTitleId, &encoding); + QString driveSerial = driveSerialEnc.toString(); + + initMounter(mMounterQmp); + mMounterQmp->setDriveId(driveId); + mMounterQmp->setDriveSerial(driveSerial); + mMounterQmp->setFilePath(mPath); + mMounterQmp->setQemuMountPort(22501); + mMounterQmp->setQemuProdPort(0); // Unused + + routerMountValue = driveSerial; + } + + initMounter(mMounterRouter); + mMounterRouter->setMountValue(routerMountValue); + mMounterRouter->setRouterPort(22500); + } + + // Mount + switch(mDaemon) + { + case Fp::Daemon::FpProxy: + mMounterProxy->mount(); + break; + + case Fp::Daemon::Qemu: + mMounterQmp->mount(); + break; + + case Fp::Daemon::Docker: + mMounterRouter->mount(); + break; + + default: + qCritical("Mount attempted with unknown daemon!"); + break; + } + + // Await finished signal(s)... } void TMount::stop() { - if(mMounter.isMounting()) + if(mMounting) { emit eventOccurred(NAME, LOG_EVENT_STOPPING_MOUNT); - mMounter.abort(); + + // TODO: This could benefit from the mounters using a shared base, or + // some other kind of type erasure like the duck typing above. + if(mMounterProxy && mMounterProxy->isMounting()) + mMounterProxy->abort(); + if(mMounterQmp && mMounterQmp->isMounting()) + mMounterQmp->abort(); + if(mMounterRouter && mMounterRouter->isMounting()) + mMounterRouter->abort(); } } //-Signals & Slots------------------------------------------------------------------------------------------------------- //Private Slots: -void TMount::postMount(MounterError errorStatus) +void TMount::mounterFinishHandler(Qx::Error err) +{ + if(sender() == mMounterQmp && !err.isValid()) + mMounterRouter->mount(); + else + postMount(err); +} + +void TMount::postMount(Qx::Error errorStatus) { + mMounting = false; + + // Reset mounter pointers ('this' will delete them due to parenting so there's no leak) + mMounterProxy = nullptr; + mMounterQmp = nullptr; + mMounterRouter = nullptr; + // Handle result emit longTaskFinished(); emit complete(errorStatus); diff --git a/app/src/task/t-mount.h b/app/src/task/t-mount.h index 228b2db..84dbfb9 100644 --- a/app/src/task/t-mount.h +++ b/app/src/task/t-mount.h @@ -7,18 +7,18 @@ // Qt Includes #include +// libfp includes +#include + // Project Includes #include "task/task.h" -#ifdef FP_PROXY - #include "tools/mounter_proxy.h" -#else - #include "tools/mounter.h" -#endif +#include "tools/mounter_proxy.h" +#include "tools/mounter_qmp.h" +#include "tools/mounter_router.h" class TMount : public Task { Q_OBJECT; - //-Class Variables------------------------------------------------------------------------------------------------- private: // Meta @@ -26,15 +26,21 @@ class TMount : public Task // Logging static inline const QString LOG_EVENT_MOUNTING_DATA_PACK = u"Mounting Data Pack %1"_s; + static inline const QString LOG_EVENT_MOUNT_INFO_DETERMINED = u"Mount Info: {.filePath = \"%1\", .driveId = \"%2\", .driveSerial = \"%3\"}"_s; static inline const QString LOG_EVENT_STOPPING_MOUNT = u"Stopping current mount(s)..."_s; //-Instance Variables------------------------------------------------------------------------------------------------ private: - // Functional - Mounter mMounter; + // Mounters + MounterProxy* mMounterProxy; + MounterQmp* mMounterQmp; + MounterRouter* mMounterRouter; // Data - bool mSkipQemu; + bool mMounting; + + // Properties + Fp::Daemon mDaemon; QUuid mTitleId; QString mPath; @@ -43,24 +49,30 @@ class TMount : public Task TMount(QObject* parent); //-Instance Functions------------------------------------------------------------------------------------------------------ +private: + template + requires Qx::any_of + void initMounter(M*& mounter); + public: QString name() const override; QStringList members() const override; - bool isSkipQemu() const; QUuid titleId() const; QString path() const; + Fp::Daemon daemon() const; - void setSkipQemu(bool skip); void setTitleId(QUuid titleId); void setPath(QString path); + void setDaemon(Fp::Daemon daemon); void perform() override; void stop() override; //-Signals & Slots------------------------------------------------------------------------------------------------------- private slots: - void postMount(MounterError errorStatus); + void mounterFinishHandler(Qx::Error err); + void postMount(Qx::Error errorStatus); }; #endif // TMOUNT_H diff --git a/app/src/tools/mounter.cpp b/app/src/tools/mounter.cpp deleted file mode 100644 index f641560..0000000 --- a/app/src/tools/mounter.cpp +++ /dev/null @@ -1,347 +0,0 @@ -// Unit Includes -#include "mounter.h" - -// Qt Includes -#include -#include -#include -#include - -// Qx Includes -#include -#include -#include - -// Project Includes -#include "utility.h" - -//=============================================================================================================== -// MounterError -//=============================================================================================================== - -//-Constructor------------------------------------------------------------- -//Private: -MounterError::MounterError(Type t, const QString& s) : - mType(t), - mSpecific(s) -{} - -//-Instance Functions------------------------------------------------------------- -//Public: -bool MounterError::isValid() const { return mType != NoError; } -QString MounterError::specific() const { return mSpecific; } -MounterError::Type MounterError::type() const { return mType; } - -//Private: -Qx::Severity MounterError::deriveSeverity() const { return Qx::Critical; } -quint32 MounterError::deriveValue() const { return mType; } -QString MounterError::derivePrimary() const { return ERR_STRINGS.value(mType); } -QString MounterError::deriveSecondary() const { return mSpecific; } - -//=============================================================================================================== -// Mounter -//=============================================================================================================== - -//-Constructor---------------------------------------------------------------------------------------------------------- -//Public: -Mounter::Mounter(QObject* parent) : - QObject(parent), - mMounting(false), - mErrorStatus(MounterError(), ERROR_STATUS_CMP), - mWebServerPort(0), - mQemuMounter(QHostAddress::LocalHost, 0), - mQemuProdder(QHostAddress::LocalHost, 0), // Currently not used - mQemuEnabled(true) -{ - // Setup Network Access Manager - mNam.setAutoDeleteReplies(true); - mNam.setTransferTimeout(PHP_TRANSFER_TIMEOUT); - - // Setup QMPI - mQemuMounter.setTransactionTimeout(QMP_TRANSACTION_TIMEOUT); - - // Connections - Work - connect(&mQemuMounter, &Qmpi::readyForCommands, this, &Mounter::qmpiReadyForCommandsHandler); - connect(&mQemuMounter, &Qmpi::commandQueueExhausted, this, &Mounter::qmpiCommandsExhaustedHandler); - connect(&mQemuMounter, &Qmpi::finished, this, &Mounter::qmpiFinishedHandler); - connect(&mNam, &QNetworkAccessManager::finished, this, &Mounter::phpMountFinishedHandler); - - // Connections - Error - connect(&mQemuMounter, &Qmpi::connectionErrorOccurred, this, &Mounter::qmpiConnectionErrorHandler); - connect(&mQemuMounter, &Qmpi::communicationErrorOccurred, this, &Mounter::qmpiCommunicationErrorHandler); - connect(&mQemuMounter, &Qmpi::errorResponseReceived, this, &Mounter::qmpiCommandErrorHandler); - - // Connections - Log - connect(&mQemuMounter, &Qmpi::connected, this, &Mounter::qmpiConnectedHandler); - connect(&mQemuMounter, &Qmpi::responseReceived, this, &Mounter::qmpiCommandResponseHandler); - connect(&mQemuMounter, &Qmpi::eventReceived, this, &Mounter::qmpiEventOccurredHandler); - - /* Network check (none of these should be triggered, they are here in case a FP update would required - * them to be used as to help make that clear in the logs when the update causes this to stop working). - */ - connect(&mNam, &QNetworkAccessManager::authenticationRequired, this, [this](){ - emit eventOccured(NAME, u"Unexpected use of authentication by PHP server!"_s); - }); - connect(&mNam, &QNetworkAccessManager::preSharedKeyAuthenticationRequired, this, [this](){ - emit eventOccured(NAME, u"Unexpected use of PSK authentication by PHP server!"_s); - }); - connect(&mNam, &QNetworkAccessManager::proxyAuthenticationRequired, this, [this](){ - emit eventOccured(NAME, u"Unexpected use of proxy by PHP server!"_s); - }); - connect(&mNam, &QNetworkAccessManager::sslErrors, this, [this](QNetworkReply* reply, const QList& errors){ - Q_UNUSED(reply); - QString errStrList = Qx::String::join(errors, [](const QSslError& err){ return err.errorString(); }, u","_s); - emit eventOccured(NAME, u"Unexpected SSL errors from PHP server! {"_s + errStrList + u"}"_s"}"); - }); -} - -//-Instance Functions--------------------------------------------------------------------------------------------------------- -//Private: -void Mounter::finish() -{ - MounterError err = mErrorStatus.value(); - mErrorStatus.reset(); - mMounting = false; - mCurrentMountInfo = {}; - emit mountFinished(err); -} - -void Mounter::createMountPoint() -{ - emit eventOccured(NAME, EVENT_CREATING_MOUNT_POINT); - - // Build commands - QString blockDevAddCmd = u"blockdev-add"_s; - QString deviceAddCmd = u"device_add"_s; - - QJsonObject blockDevAddArgs; - blockDevAddArgs[u"node-name"_s] = mCurrentMountInfo.driveId; - blockDevAddArgs[u"driver"_s] = u"raw"_s; - blockDevAddArgs[u"read-only"_s] = true; - QJsonObject fileArgs; - fileArgs[u"driver"_s] = u"file"_s; - fileArgs[u"filename"_s] = mCurrentMountInfo.filePath; - blockDevAddArgs[u"file"_s] = fileArgs; - - QJsonObject deviceAddArgs; - deviceAddArgs[u"driver"_s] = u"virtio-blk-pci"_s; - deviceAddArgs[u"drive"_s] = mCurrentMountInfo.driveId; - deviceAddArgs[u"id"_s] = mCurrentMountInfo.driveId; - deviceAddArgs[u"serial"_s] = mCurrentMountInfo.driveSerial; - - // Log formatter - QJsonDocument formatter; - QString cmdLog; - - // Queue/Log commands - formatter.setObject(blockDevAddArgs); - cmdLog = formatter.toJson(QJsonDocument::Compact); - mQemuMounter.execute(blockDevAddCmd, blockDevAddArgs, - blockDevAddCmd + ' ' + cmdLog); - - formatter.setObject(deviceAddArgs); - cmdLog = formatter.toJson(QJsonDocument::Compact); - mQemuMounter.execute(deviceAddCmd, deviceAddArgs, - deviceAddCmd + ' ' + cmdLog); - - // Await finished() signal... -} - -void Mounter::setMountOnServer() -{ - emit eventOccured(NAME, EVENT_MOUNTING_THROUGH_SERVER); - - // Create mount request - QUrl mountUrl; - mountUrl.setScheme(u"http"_s); - mountUrl.setHost(u"127.0.0.1"_s); - mountUrl.setPort(mWebServerPort); - mountUrl.setPath(u"/mount.php"_s); - - QUrlQuery query; - QString queryKey = u"file"_s; - QString queryValue = QUrl::toPercentEncoding(mQemuEnabled ? mCurrentMountInfo.driveSerial : - QFileInfo(mCurrentMountInfo.filePath).fileName()); - query.addQueryItem(queryKey, queryValue); - mountUrl.setQuery(query); - - QNetworkRequest mountReq(mountUrl); - - // GET request - mPhpMountReply = mNam.get(mountReq); - - // Log request - emit eventOccured(NAME, EVENT_REQUEST_SENT.arg(ENUM_NAME(mPhpMountReply->operation()), mountUrl.toString())); - - // Await finished() signal... -} - -void Mounter::notePhpMountResponse(const QString& response) -{ - emit eventOccured(NAME, EVENT_PHP_RESPONSE.arg(response)); - finish(); -} - -void Mounter::logMountInfo(const MountInfo& info) -{ - emit eventOccured(NAME, EVENT_MOUNT_INFO_DETERMINED.arg(info.filePath, info.driveId, info.driveSerial)); -} - -//Public: -bool Mounter::isMounting() { return mMounting; } - -quint16 Mounter::webServerPort() const { return mWebServerPort; } -quint16 Mounter::qemuMountPort() const { return mQemuMounter.port(); } -quint16 Mounter::qemuProdPort() const { return mQemuProdder.port(); } -bool Mounter::isQemuEnabled() const { return mQemuEnabled; } - -void Mounter::setWebServerPort(quint16 port) { mWebServerPort = port; } -void Mounter::setQemuMountPort(quint16 port) { mQemuMounter.setPort(port); } -void Mounter::setQemuProdPort(quint16 port) { mQemuProdder.setPort(port); } -void Mounter::setQemuEnabled(bool enabled) { mQemuEnabled = enabled; } - -//-Signals & Slots------------------------------------------------------------------------------------------------------------ -//Private Slots: -void Mounter::qmpiConnectedHandler(QJsonObject version, QJsonArray capabilities) -{ - QJsonDocument formatter(version); - QString versionStr = formatter.toJson(QJsonDocument::Compact); - formatter.setArray(capabilities); - QString capabilitiesStr = formatter.toJson(QJsonDocument::Compact); - emit eventOccured(NAME, EVENT_QMP_WELCOME_MESSAGE.arg(versionStr, capabilitiesStr)); -} - -void Mounter::qmpiCommandsExhaustedHandler() -{ - emit eventOccured(NAME, EVENT_DISCONNECTING_FROM_QEMU); - mQemuMounter.disconnectFromHost(); -} - -void Mounter::qmpiFinishedHandler() -{ - if(mErrorStatus.isSet()) - finish(); - else - setMountOnServer(); -} - -void Mounter::qmpiReadyForCommandsHandler() { createMountPoint(); } - -void Mounter::phpMountFinishedHandler(QNetworkReply* reply) -{ - assert(reply == mPhpMountReply.get()); - - // FP (as of 11) is currently bugged and is expected to give an internal server error so it must be ignored - if(reply->error() != QNetworkReply::NoError && reply->error() != QNetworkReply::InternalServerError) - { - MounterError err(MounterError::PhpMount, reply->errorString()); - mErrorStatus = err; - - emit errorOccured(NAME, err); - finish(); - } - else - { - QByteArray response = reply->readAll(); - notePhpMountResponse(QString::fromLatin1(response)); - } -} - -void Mounter::qmpiConnectionErrorHandler(QAbstractSocket::SocketError error) -{ - MounterError err(MounterError::QemuConnection, ENUM_NAME(error)); - mErrorStatus = err; - - emit errorOccured(NAME, err); -} - -void Mounter::qmpiCommunicationErrorHandler(Qmpi::CommunicationError error) -{ - MounterError err(MounterError::QemuCommunication, ENUM_NAME(error)); - mErrorStatus = err; - - emit errorOccured(NAME, err); -} - -void Mounter::qmpiCommandErrorHandler(QString errorClass, QString description, std::any context) -{ - QString commandErr = ERR_QMP_COMMAND.arg(std::any_cast(context), errorClass, description); - - MounterError err(MounterError::QemuCommand, commandErr); - mErrorStatus = err; - - emit errorOccured(NAME, err); - mQemuMounter.abort(); -} - -void Mounter::qmpiCommandResponseHandler(QJsonValue value, std::any context) -{ - emit eventOccured(NAME, EVENT_QMP_COMMAND_RESPONSE.arg(std::any_cast(context), Qx::asString(value))); -} - -void Mounter::qmpiEventOccurredHandler(QString name, QJsonObject data, QDateTime timestamp) -{ - QJsonDocument formatter(data); - QString dataStr = formatter.toJson(QJsonDocument::Compact); - QString timestampStr = timestamp.toString(u"hh:mm:s s.zzz"_s); - emit eventOccured(NAME, EVENT_QMP_EVENT.arg(name, dataStr, timestampStr)); -} - -//Public Slots: -void Mounter::mount(QUuid titleId, QString filePath) -{ - // Update state - mMounting = true; - emit mountProgress(0); - emit mountProgressMaximumChanged(0); // Cause busy state - - //-Determine mount info------------------------------------------------- - - // Set file path - mCurrentMountInfo.filePath = filePath; - - // Generate random sequence of 16 lowercase alphabetical characters to act as Drive ID - QByteArray alphaBytes; - alphaBytes.resize(16); - std::generate(alphaBytes.begin(), alphaBytes.end(), [](){ - // Funnel numbers into 0-25, use ASCI char 'a' (0x61) as a base value - return (QRandomGenerator::global()->generate() % 26) + 0x61; - }); - mCurrentMountInfo.driveId = QString::fromLatin1(alphaBytes); - - // Convert UUID to 20 char drive serial by encoding to Base85 - Qx::Base85Encoding encoding(Qx::Base85Encoding::Btoa); - encoding.resetZeroGroupCharacter(); // No shortcut characters - QByteArray rawTitleId = titleId.toRfc4122(); // Binary representation of UUID - Qx::Base85 driveSerial = Qx::Base85::encode(rawTitleId, &encoding); - mCurrentMountInfo.driveSerial = driveSerial.toString(); - - // Log info - logMountInfo(mCurrentMountInfo); - emit eventOccured(NAME, EVENT_QEMU_DETECTION.arg(mQemuEnabled ? u"is"_s : u"isn't"_s)); - - // Connect to QEMU instance, or go straight to web server portion if bypassing - if(mQemuEnabled) - { - emit eventOccured(NAME, EVENT_CONNECTING_TO_QEMU); - mQemuMounter.connectToHost(); - // Await readyForCommands() signal... - } - else - setMountOnServer(); -} - -void Mounter::abort() -{ - if(mQemuMounter.isConnectionActive()) - { - // Aborting this doesn't cause an error so we must set one here manually. - MounterError err(MounterError::QemuConnection, ERR_QMP_CONNECTION_ABORT); - mErrorStatus = err; - - emit errorOccured(NAME, err); - mQemuMounter.abort(); // Call last here because it causes finished signal to emit immediately - } - if(mPhpMountReply && mPhpMountReply->isRunning()) - mPhpMountReply->abort(); -} diff --git a/app/src/tools/mounter_proxy.cpp b/app/src/tools/mounter_proxy.cpp index f1d2985..3c63caa 100644 --- a/app/src/tools/mounter_proxy.cpp +++ b/app/src/tools/mounter_proxy.cpp @@ -4,11 +4,8 @@ // Qt Includes #include #include -#include // Qx Includes -#include -#include #include // Project Includes @@ -20,22 +17,22 @@ //-Constructor------------------------------------------------------------- //Private: -MounterError::MounterError(Type t, const QString& s) : +MounterProxyError::MounterProxyError(Type t, const QString& s) : mType(t), mSpecific(s) {} //-Instance Functions------------------------------------------------------------- //Public: -bool MounterError::isValid() const { return mType != NoError; } -QString MounterError::specific() const { return mSpecific; } -MounterError::Type MounterError::type() const { return mType; } +bool MounterProxyError::isValid() const { return mType != NoError; } +QString MounterProxyError::specific() const { return mSpecific; } +MounterProxyError::Type MounterProxyError::type() const { return mType; } //Private: -Qx::Severity MounterError::deriveSeverity() const { return Qx::Critical; } -quint32 MounterError::deriveValue() const { return mType; } -QString MounterError::derivePrimary() const { return ERR_STRINGS.value(mType); } -QString MounterError::deriveSecondary() const { return mSpecific; } +Qx::Severity MounterProxyError::deriveSeverity() const { return Qx::Critical; } +quint32 MounterProxyError::deriveValue() const { return mType; } +QString MounterProxyError::derivePrimary() const { return ERR_STRINGS.value(mType); } +QString MounterProxyError::deriveSecondary() const { return mSpecific; } //=============================================================================================================== // Mounter @@ -43,7 +40,7 @@ QString MounterError::deriveSecondary() const { return mSpecific; } //-Constructor---------------------------------------------------------------------------------------------------------- //Public: -Mounter::Mounter(QObject* parent) : +MounterProxy::MounterProxy(QObject* parent) : QObject(parent), mMounting(false), mProxyServerPort(0) @@ -53,115 +50,109 @@ Mounter::Mounter(QObject* parent) : mNam.setTransferTimeout(PROXY_TRANSFER_TIMEOUT); // Connections - Work - connect(&mNam, &QNetworkAccessManager::finished, this, &Mounter::proxyMountFinishedHandler); + connect(&mNam, &QNetworkAccessManager::finished, this, &MounterProxy::proxyMountFinishedHandler); /* Network check (none of these should be triggered, they are here in case a FP update would required * them to be used as to help make that clear in the logs when the update causes this to stop working). */ connect(&mNam, &QNetworkAccessManager::authenticationRequired, this, [this](){ - emit eventOccured(NAME, u"Unexpected use of authentication by PHP server!"_s); + emit eventOccurred(NAME, u"Unexpected use of authentication by PHP server!"_s); }); connect(&mNam, &QNetworkAccessManager::preSharedKeyAuthenticationRequired, this, [this](){ - emit eventOccured(NAME, u"Unexpected use of PSK authentication by PHP server!"_s); + emit eventOccurred(NAME, u"Unexpected use of PSK authentication by PHP server!"_s); }); connect(&mNam, &QNetworkAccessManager::proxyAuthenticationRequired, this, [this](){ - emit eventOccured(NAME, u"Unexpected use of proxy by PHP server!"_s); + emit eventOccurred(NAME, u"Unexpected use of proxy by PHP server!"_s); }); connect(&mNam, &QNetworkAccessManager::sslErrors, this, [this](QNetworkReply* reply, const QList& errors){ Q_UNUSED(reply); QString errStrList = Qx::String::join(errors, [](const QSslError& err){ return err.errorString(); }, u","_s); - emit eventOccured(NAME, u"Unexpected SSL errors from PHP server! {"_s + errStrList + u"}"_s"}"); + emit eventOccurred(NAME, u"Unexpected SSL errors from PHP server! {"_s + errStrList + u"}"_s"}"); }); } //-Instance Functions--------------------------------------------------------------------------------------------------------- //Private: -void Mounter::finish(const MounterError& errorState) +void MounterProxy::finish(const MounterProxyError& errorState) { mMounting = false; emit mountFinished(errorState); } -void Mounter::postMountToServer(QStringView filePath) +void MounterProxy::noteProxyRequest(QNetworkAccessManager::Operation op, const QUrl& url, QByteArrayView data) { - emit eventOccured(NAME, EVENT_MOUNTING); - - //-Create mount request------------------------- - - // Url - QUrl mountUrl; - mountUrl.setScheme(u"http"_s); - mountUrl.setHost(u"localhost"_s); - mountUrl.setPort(mProxyServerPort); - mountUrl.setPath(u"/fpProxy/api/mountzip"_s); - - // Req - QNetworkRequest mountReq(mountUrl); - - // Header - mountReq.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); - - // Data (could use QJsonDocument but for such a simple object that's overkill - QByteArray data = "{\"filePath\":\""_ba + filePath.toLatin1() + "\"}"_ba; - - //-POST Request--------------------------------- - mProxyMountReply = mNam.post(mountReq, data); - - // Log request - noteProxyRequest(mProxyMountReply->operation(), mountUrl, data); - - // Await finished() signal... + emit eventOccurred(NAME, EVENT_REQUEST_SENT.arg(ENUM_NAME(op), url.toString(), QString::fromLatin1(data))); } -void Mounter::noteProxyRequest(QNetworkAccessManager::Operation op, const QUrl& url, QByteArrayView data) +void MounterProxy::noteProxyResponse(const QString& response) { - emit eventOccured(NAME, EVENT_REQUEST_SENT.arg(ENUM_NAME(op), url.toString(), QString::fromLatin1(data))); -} - -void Mounter::noteProxyResponse(const QString& response) -{ - emit eventOccured(NAME, EVENT_PROXY_RESPONSE.arg(response)); + emit eventOccurred(NAME, EVENT_PROXY_RESPONSE.arg(response)); } //Public: -bool Mounter::isMounting() { return mMounting; } -quint16 Mounter::proxyServerPort() const { return mProxyServerPort; } -void Mounter::setProxyServerPort(quint16 port) { mProxyServerPort = port; } +bool MounterProxy::isMounting() { return mMounting; } + +quint16 MounterProxy::proxyServerPort() const { return mProxyServerPort; } +QString MounterProxy::filePath() const { return mFilePath; } + +void MounterProxy::setProxyServerPort(quint16 port) { mProxyServerPort = port; } +void MounterProxy::setFilePath(const QString& path) { mFilePath = path; } //-Signals & Slots------------------------------------------------------------------------------------------------------------ //Private Slots: -void Mounter::proxyMountFinishedHandler(QNetworkReply* reply) +void MounterProxy::proxyMountFinishedHandler(QNetworkReply* reply) { assert(reply == mProxyMountReply.get()); + MounterProxyError err; + if(reply->error() != QNetworkReply::NoError) { - MounterError err(MounterError::ProxyMount, reply->errorString()); - - emit errorOccured(NAME, err); - finish(err); + err = MounterProxyError(MounterProxyError::ProxyMount, reply->errorString()); + emit errorOccurred(NAME, err); } else { QByteArray response = reply->readAll(); noteProxyResponse(QString::fromLatin1(response)); - finish(MounterError()); } + + finish(err); } //Public Slots: -void Mounter::mount(QStringView filePath) +void MounterProxy::mount() { - // Update state - mMounting = true; - emit mountProgress(0); - emit mountProgressMaximumChanged(0); // Cause busy state + emit eventOccurred(NAME, EVENT_MOUNTING); + + //-Create mount request------------------------- + + // Url + QUrl mountUrl; + mountUrl.setScheme(u"http"_s); + mountUrl.setHost(u"localhost"_s); + mountUrl.setPort(mProxyServerPort); + mountUrl.setPath(u"/fpProxy/api/mountzip"_s); + + // Req + QNetworkRequest mountReq(mountUrl); + + // Header + mountReq.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); + + // Data (could use QJsonDocument but for such a simple object that's overkill + QByteArray data = "{\"filePath\":\""_ba + mFilePath.toLatin1() + "\"}"_ba; - // Send mount request - postMountToServer(filePath); + //-POST Request--------------------------------- + mProxyMountReply = mNam.post(mountReq, data); + + // Log request + noteProxyRequest(mProxyMountReply->operation(), mountUrl, data); + + // Await finished() signal... } -void Mounter::abort() +void MounterProxy::abort() { if(mProxyMountReply && mProxyMountReply->isRunning()) mProxyMountReply->abort(); diff --git a/app/src/tools/mounter_proxy.h b/app/src/tools/mounter_proxy.h index 4945c13..4e9d5a4 100644 --- a/app/src/tools/mounter_proxy.h +++ b/app/src/tools/mounter_proxy.h @@ -1,5 +1,5 @@ -#ifndef MOUNTER_H -#define MOUNTER_H +#ifndef MOUNTER_PROXY_H +#define MOUNTER_PROXY_H // Qt Includes #include @@ -11,9 +11,9 @@ #include #include -class QX_ERROR_TYPE(MounterError, "MounterError", 1232) +class QX_ERROR_TYPE(MounterProxyError, "MounterError", 1232) { - friend class Mounter; + friend class MounterProxy; //-Class Enums------------------------------------------------------------- public: enum Type @@ -36,7 +36,7 @@ class QX_ERROR_TYPE(MounterError, "MounterError", 1232) //-Constructor------------------------------------------------------------- private: - MounterError(Type t = NoError, const QString& s = {}); + MounterProxyError(Type t = NoError, const QString& s = {}); //-Instance Functions------------------------------------------------------------- public: @@ -51,7 +51,7 @@ class QX_ERROR_TYPE(MounterError, "MounterError", 1232) QString deriveSecondary() const override; }; -class Mounter : public QObject +class MounterProxy : public QObject { Q_OBJECT //-Class Variables------------------------------------------------------------------------------------------------------ @@ -76,42 +76,46 @@ class Mounter : public QObject private: bool mMounting; int mProxyServerPort; + QString mFilePath; QNetworkAccessManager mNam; QPointer mProxyMountReply; //-Constructor------------------------------------------------------------------------------------------------- public: - explicit Mounter(QObject* parent = nullptr); + explicit MounterProxy(QObject* parent = nullptr); //-Instance Functions--------------------------------------------------------------------------------------------------------- private: - void finish(const MounterError& errorState); - void postMountToServer(const QStringView filePath); + void finish(const MounterProxyError& errorState); void noteProxyRequest(QNetworkAccessManager::Operation op, const QUrl& url, QByteArrayView data); void noteProxyResponse(const QString& response); public: bool isMounting(); + quint16 proxyServerPort() const; + QString filePath() const; + void setProxyServerPort(quint16 port); + void setFilePath(const QString& path); //-Signals & Slots------------------------------------------------------------------------------------------------------------ private slots: void proxyMountFinishedHandler(QNetworkReply* reply); public slots: - void mount(QStringView filePath); + void mount(); void abort(); signals: - void eventOccured(QString name, const QString& event); - void errorOccured(QString name, MounterError errorMessage); - void mountFinished(MounterError errorState); + void eventOccurred(QString name, const QString& event); + void errorOccurred(QString name, MounterProxyError errorMessage); + void mountFinished(MounterProxyError errorState); // For now these just cause a busy state void mountProgress(qint64 progress); void mountProgressMaximumChanged(qint64 maximum); }; -#endif // MOUNTER_H +#endif // MOUNTER_PROXY_H diff --git a/app/src/tools/mounter_qmp.cpp b/app/src/tools/mounter_qmp.cpp new file mode 100644 index 0000000..bbb34a1 --- /dev/null +++ b/app/src/tools/mounter_qmp.cpp @@ -0,0 +1,216 @@ +// Unit Includes +#include "mounter_qmp.h" + +// Qx Includes +#include + +// Project Includes +#include "utility.h" + +//=============================================================================================================== +// MounterQmpError +//=============================================================================================================== + +//-Constructor------------------------------------------------------------- +//Private: +MounterQmpError::MounterQmpError(Type t, const QString& s) : + mType(t), + mSpecific(s) +{} + +//-Instance Functions------------------------------------------------------------- +//Public: +bool MounterQmpError::isValid() const { return mType != NoError; } +QString MounterQmpError::specific() const { return mSpecific; } +MounterQmpError::Type MounterQmpError::type() const { return mType; } + +//Private: +Qx::Severity MounterQmpError::deriveSeverity() const { return Qx::Critical; } +quint32 MounterQmpError::deriveValue() const { return mType; } +QString MounterQmpError::derivePrimary() const { return ERR_STRINGS.value(mType); } +QString MounterQmpError::deriveSecondary() const { return mSpecific; } + +//=============================================================================================================== +// MounterQmp +//=============================================================================================================== + +//-Constructor---------------------------------------------------------------------------------------------------------- +//Public: +MounterQmp::MounterQmp(QObject* parent) : + QObject(parent), + mMounting(false), + mErrorStatus(MounterQmpError::NoError), + mQemuMounter(QHostAddress::LocalHost, 0), + mQemuProdder(QHostAddress::LocalHost, 0) // Currently not used +{ + // Setup QMPI + mQemuMounter.setTransactionTimeout(QMP_TRANSACTION_TIMEOUT); + + // Connections - Work + connect(&mQemuMounter, &Qmpi::readyForCommands, this, &MounterQmp::qmpiReadyForCommandsHandler); + connect(&mQemuMounter, &Qmpi::commandQueueExhausted, this, &MounterQmp::qmpiCommandsExhaustedHandler); + connect(&mQemuMounter, &Qmpi::finished, this, &MounterQmp::qmpiFinishedHandler); + + // Connections - Error + connect(&mQemuMounter, &Qmpi::connectionErrorOccurred, this, &MounterQmp::qmpiConnectionErrorHandler); + connect(&mQemuMounter, &Qmpi::communicationErrorOccurred, this, &MounterQmp::qmpiCommunicationErrorHandler); + connect(&mQemuMounter, &Qmpi::errorResponseReceived, this, &MounterQmp::qmpiCommandErrorHandler); + + // Connections - Log + connect(&mQemuMounter, &Qmpi::connected, this, &MounterQmp::qmpiConnectedHandler); + connect(&mQemuMounter, &Qmpi::responseReceived, this, &MounterQmp::qmpiCommandResponseHandler); + connect(&mQemuMounter, &Qmpi::eventReceived, this, &MounterQmp::qmpiEventOccurredHandler); +} + +//-Instance Functions--------------------------------------------------------------------------------------------------------- +//Private: +void MounterQmp::finish() +{ + MounterQmpError err = mErrorStatus.value(); + mErrorStatus.reset(); + mMounting = false; + emit mountFinished(err); +} + +void MounterQmp::createMountPoint() +{ + emit eventOccurred(NAME, EVENT_CREATING_MOUNT_POINT); + + // Build commands + QString blockDevAddCmd = u"blockdev-add"_s; + QString deviceAddCmd = u"device_add"_s; + + QJsonObject blockDevAddArgs; + blockDevAddArgs[u"node-name"_s] = mDriveId; + blockDevAddArgs[u"driver"_s] = u"raw"_s; + blockDevAddArgs[u"read-only"_s] = true; + QJsonObject fileArgs; + fileArgs[u"driver"_s] = u"file"_s; + fileArgs[u"filename"_s] = mFilePath; + blockDevAddArgs[u"file"_s] = fileArgs; + + QJsonObject deviceAddArgs; + deviceAddArgs[u"driver"_s] = u"virtio-blk-pci"_s; + deviceAddArgs[u"drive"_s] = mDriveId; + deviceAddArgs[u"id"_s] = mDriveId; + deviceAddArgs[u"serial"_s] = mDriveSerial; + + // Log formatter + QJsonDocument formatter; + QString cmdLog; + + // Queue/Log commands + formatter.setObject(blockDevAddArgs); + cmdLog = formatter.toJson(QJsonDocument::Compact); + mQemuMounter.execute(blockDevAddCmd, blockDevAddArgs, + blockDevAddCmd + ' ' + cmdLog); + + formatter.setObject(deviceAddArgs); + cmdLog = formatter.toJson(QJsonDocument::Compact); + mQemuMounter.execute(deviceAddCmd, deviceAddArgs, + deviceAddCmd + ' ' + cmdLog); + + // Await finished() signal... +} + +//Public: +bool MounterQmp::isMounting() { return mMounting; } + +QString MounterQmp::driveId() const { return mDriveId; } +QString MounterQmp::driveSerial() const { return mDriveSerial; } +QString MounterQmp::filePath() const { return mFilePath; } +quint16 MounterQmp::qemuMountPort() const { return mQemuMounter.port(); } +quint16 MounterQmp::qemuProdPort() const { return mQemuProdder.port(); } + +void MounterQmp::setDriveId(const QString& id) { mDriveId = id; } +void MounterQmp::setDriveSerial(const QString& serial){ mDriveSerial = serial; } +void MounterQmp::setFilePath(const QString& path) { mFilePath = path; } +void MounterQmp::setQemuMountPort(quint16 port) { mQemuMounter.setPort(port); } +void MounterQmp::setQemuProdPort(quint16 port) { mQemuProdder.setPort(port); } + +//-Signals & Slots------------------------------------------------------------------------------------------------------------ +//Private Slots: +void MounterQmp::qmpiConnectedHandler(QJsonObject version, QJsonArray capabilities) +{ + QJsonDocument formatter(version); + QString versionStr = formatter.toJson(QJsonDocument::Compact); + formatter.setArray(capabilities); + QString capabilitiesStr = formatter.toJson(QJsonDocument::Compact); + emit eventOccurred(NAME, EVENT_QMP_WELCOME_MESSAGE.arg(versionStr, capabilitiesStr)); +} + +void MounterQmp::qmpiCommandsExhaustedHandler() +{ + emit eventOccurred(NAME, EVENT_DISCONNECTING_FROM_QEMU); + mQemuMounter.disconnectFromHost(); +} + +void MounterQmp::qmpiFinishedHandler() +{ + finish(); +} + +void MounterQmp::qmpiReadyForCommandsHandler() { createMountPoint(); } + +void MounterQmp::qmpiConnectionErrorHandler(QAbstractSocket::SocketError error) +{ + MounterQmpError err(MounterQmpError::QemuConnection, ENUM_NAME(error)); + mErrorStatus = err; + + emit errorOccurred(NAME, err); +} + +void MounterQmp::qmpiCommunicationErrorHandler(Qmpi::CommunicationError error) +{ + MounterQmpError err(MounterQmpError::QemuCommunication, ENUM_NAME(error)); + mErrorStatus = err; + + emit errorOccurred(NAME, err); +} + +void MounterQmp::qmpiCommandErrorHandler(QString errorClass, QString description, std::any context) +{ + QString commandErr = ERR_QMP_COMMAND.arg(std::any_cast(context), errorClass, description); + + MounterQmpError err(MounterQmpError::QemuCommand, commandErr); + mErrorStatus = err; + + emit errorOccurred(NAME, err); + mQemuMounter.abort(); +} + +void MounterQmp::qmpiCommandResponseHandler(QJsonValue value, std::any context) +{ + emit eventOccurred(NAME, EVENT_QMP_COMMAND_RESPONSE.arg(std::any_cast(context), Qx::asString(value))); +} + +void MounterQmp::qmpiEventOccurredHandler(QString name, QJsonObject data, QDateTime timestamp) +{ + QJsonDocument formatter(data); + QString dataStr = formatter.toJson(QJsonDocument::Compact); + QString timestampStr = timestamp.toString(u"hh:mm:s s.zzz"_s); + emit eventOccurred(NAME, EVENT_QMP_EVENT.arg(name, dataStr, timestampStr)); +} + +//Public Slots: +void MounterQmp::mount() +{ + // Connect to QEMU instance + emit eventOccurred(NAME, EVENT_CONNECTING_TO_QEMU); + mQemuMounter.connectToHost(); + + // Await readyForCommands() signal... +} + +void MounterQmp::abort() +{ + if(mQemuMounter.isConnectionActive()) + { + // Aborting this doesn't cause an error so we must set one here manually. + MounterQmpError err(MounterQmpError::QemuConnection, ERR_QMP_CONNECTION_ABORT); + mErrorStatus = err; + + emit errorOccurred(NAME, err); + mQemuMounter.abort(); // Call last here because it causes finished signal to emit immediately + } +} diff --git a/app/src/tools/mounter.h b/app/src/tools/mounter_qmp.h similarity index 68% rename from app/src/tools/mounter.h rename to app/src/tools/mounter_qmp.h index 52e03ee..36e9efa 100644 --- a/app/src/tools/mounter.h +++ b/app/src/tools/mounter_qmp.h @@ -1,39 +1,34 @@ -#ifndef MOUNTER_H -#define MOUNTER_H +#ifndef MOUNTER_QMP_H +#define MOUNTER_QMP_H // Qt Includes #include -#include -#include -#include // Qx Includes -#include #include #include +#include // QI-QMP Includes #include -class QX_ERROR_TYPE(MounterError, "MounterError", 1232) +class QX_ERROR_TYPE(MounterQmpError, "MounterQmpError", 1233) { - friend class Mounter; + friend class MounterQmp; //-Class Enums------------------------------------------------------------- public: enum Type { NoError = 0, - PhpMount = 1, - QemuConnection = 2, - QemuCommunication = 3, - QemuCommand = 4 + QemuConnection, + QemuCommunication, + QemuCommand }; //-Class Variables------------------------------------------------------------- private: static inline const QHash ERR_STRINGS{ {NoError, u""_s}, - {PhpMount, u"Failed to mount data pack (PHP)."_s}, {QemuConnection, u"QMPI connection error."_s}, {QemuCommunication, u"QMPI communication error."_s}, {QemuCommand, u"QMPI command error."_s}, @@ -46,7 +41,7 @@ class QX_ERROR_TYPE(MounterError, "MounterError", 1232) //-Constructor------------------------------------------------------------- private: - MounterError(Type t = NoError, const QString& s = {}); + MounterQmpError(Type t = NoError, const QString& s = {}); //-Instance Functions------------------------------------------------------------- public: @@ -61,17 +56,9 @@ class QX_ERROR_TYPE(MounterError, "MounterError", 1232) QString deriveSecondary() const override; }; -class Mounter : public QObject +class MounterQmp : public QObject { Q_OBJECT -//-Class Structs -private: - struct MountInfo - { - QString filePath; - QString driveId; - QString driveSerial; - }; //-Class Variables------------------------------------------------------------------------------------------------------ private: @@ -79,7 +66,7 @@ class Mounter : public QObject static inline const QString NAME = u"Mounter"_s; // Error Status Helper - static inline const auto ERROR_STATUS_CMP = [](const MounterError& a, const MounterError& b){ + static inline const auto ERROR_STATUS_CMP = [](const MounterQmpError& a, const MounterQmpError& b){ return a.type() == b.type(); }; @@ -91,7 +78,6 @@ class Mounter : public QObject static inline const QString EVENT_QMP_WELCOME_MESSAGE = u"QMPI connected to QEMU Version: %1 | Capabilities: %2"_s; static inline const QString EVENT_QMP_COMMAND_RESPONSE = u"QMPI command %1 returned - \"%2\""_s; static inline const QString EVENT_QMP_EVENT = u"QMPI event occurred at %1 - [%2] \"%3\""_s; - static inline const QString EVENT_PHP_RESPONSE = u"Mount.php Response: \"%1\""_s; // Events - Internal static inline const QString EVENT_CONNECTING_TO_QEMU = u"Connecting to FP QEMU instance..."_s; @@ -99,52 +85,45 @@ class Mounter : public QObject static inline const QString EVENT_QEMU_DETECTION = u"QEMU %1 in use."_s; static inline const QString EVENT_CREATING_MOUNT_POINT = u"Creating data pack mount point on QEMU instance..."_s; static inline const QString EVENT_DISCONNECTING_FROM_QEMU = u"Disconnecting from FP QEMU instance..."_s; - static inline const QString EVENT_MOUNTING_THROUGH_SERVER = u"Mounting data pack via PHP server..."_s; - static inline const QString EVENT_REQUEST_SENT = u"Sent request (%1): %2"_s; // Connections static const int QMP_TRANSACTION_TIMEOUT = 5000; // ms - static const int PHP_TRANSFER_TIMEOUT = 30000; // ms //-Instance Variables------------------------------------------------------------------------------------------------------------ private: bool mMounting; - Qx::SetOnce mErrorStatus; - MountInfo mCurrentMountInfo; + Qx::SetOnce mErrorStatus; + + QString mDriveId; + QString mDriveSerial; + QString mFilePath; - int mWebServerPort; Qmpi mQemuMounter; Qmpi mQemuProdder; // Not actually used; no, need unless issues with mounting are reported - bool mQemuEnabled; - - QNetworkAccessManager mNam; - QPointer mPhpMountReply; - QString mPhpMountReplyResponse; //-Constructor------------------------------------------------------------------------------------------------- public: - explicit Mounter(QObject* parent = nullptr); + explicit MounterQmp(QObject* parent = nullptr); //-Instance Functions--------------------------------------------------------------------------------------------------------- private: void finish(); void createMountPoint(); - void setMountOnServer(); - void notePhpMountResponse(const QString& response); - void logMountInfo(const MountInfo& info); public: bool isMounting(); - quint16 webServerPort() const; + QString driveId() const; + QString driveSerial() const; + QString filePath() const; quint16 qemuMountPort() const; quint16 qemuProdPort() const; - bool isQemuEnabled() const; - void setWebServerPort(quint16 port); + void setDriveId(const QString& id); + void setDriveSerial(const QString& serial); + void setFilePath(const QString& path); void setQemuMountPort(quint16 port); void setQemuProdPort(quint16 port); - void setQemuEnabled(bool enabled); //-Signals & Slots------------------------------------------------------------------------------------------------------------ private slots: @@ -152,7 +131,6 @@ private slots: void qmpiCommandsExhaustedHandler(); void qmpiFinishedHandler(); void qmpiReadyForCommandsHandler(); - void phpMountFinishedHandler(QNetworkReply* reply); void qmpiConnectionErrorHandler(QAbstractSocket::SocketError error); void qmpiCommunicationErrorHandler(Qmpi::CommunicationError error); @@ -161,17 +139,13 @@ private slots: void qmpiEventOccurredHandler(QString name, QJsonObject data, QDateTime timestamp); public slots: - void mount(QUuid titleId, QString filePath); + void mount(); void abort(); signals: - void eventOccured(QString name, QString event); - void errorOccured(QString name, MounterError errorMessage); - void mountFinished(MounterError errorState); - - // For now these just cause a busy state - void mountProgress(qint64 progress); - void mountProgressMaximumChanged(qint64 maximum); + void eventOccurred(QString name, QString event); + void errorOccurred(QString name, MounterQmpError errorMessage); + void mountFinished(MounterQmpError errorState); }; -#endif // MOUNTER_H +#endif // MOUNTER_QMP_H diff --git a/app/src/tools/mounter_router.cpp b/app/src/tools/mounter_router.cpp new file mode 100644 index 0000000..05f6500 --- /dev/null +++ b/app/src/tools/mounter_router.cpp @@ -0,0 +1,147 @@ +// Unit Includes +#include "mounter_router.h" + +// Qt Includes +#include +#include + +// Qx Includes +#include + +// Project Includes +#include "utility.h" + +//=============================================================================================================== +// MounterRouterError +//=============================================================================================================== + +//-Constructor------------------------------------------------------------- +//Private: +MounterRouterError::MounterRouterError(Type t, const QString& s) : + mType(t), + mSpecific(s) +{} + +//-Instance Functions------------------------------------------------------------- +//Public: +bool MounterRouterError::isValid() const { return mType != NoError; } +QString MounterRouterError::specific() const { return mSpecific; } +MounterRouterError::Type MounterRouterError::type() const { return mType; } + +//Private: +Qx::Severity MounterRouterError::deriveSeverity() const { return Qx::Critical; } +quint32 MounterRouterError::deriveValue() const { return mType; } +QString MounterRouterError::derivePrimary() const { return ERR_STRINGS.value(mType); } +QString MounterRouterError::deriveSecondary() const { return mSpecific; } + +//=============================================================================================================== +// Mounter +//=============================================================================================================== + +//-Constructor---------------------------------------------------------------------------------------------------------- +//Public: +MounterRouter::MounterRouter(QObject* parent) : + QObject(parent), + mMounting(false), + mRouterPort(0) +{ + // Setup Network Access Manager + mNam.setAutoDeleteReplies(true); + mNam.setTransferTimeout(PHP_TRANSFER_TIMEOUT); + + // Connections + connect(&mNam, &QNetworkAccessManager::finished, this, &MounterRouter::mountFinishedHandler); + + /* Network check (none of these should be triggered, they are here in case a FP update would required + * them to be used as to help make that clear in the logs when the update causes this to stop working). + */ + connect(&mNam, &QNetworkAccessManager::authenticationRequired, this, [this](){ + emit eventOccurred(NAME, u"Unexpected use of authentication by PHP server!"_s); + }); + connect(&mNam, &QNetworkAccessManager::preSharedKeyAuthenticationRequired, this, [this](){ + emit eventOccurred(NAME, u"Unexpected use of PSK authentication by PHP server!"_s); + }); + connect(&mNam, &QNetworkAccessManager::proxyAuthenticationRequired, this, [this](){ + emit eventOccurred(NAME, u"Unexpected use of proxy by PHP server!"_s); + }); + connect(&mNam, &QNetworkAccessManager::sslErrors, this, [this](QNetworkReply* reply, const QList& errors){ + Q_UNUSED(reply); + QString errStrList = Qx::String::join(errors, [](const QSslError& err){ return err.errorString(); }, u","_s); + emit eventOccurred(NAME, u"Unexpected SSL errors from PHP server! {"_s + errStrList + u"}"_s"}"); + }); +} + +//-Instance Functions--------------------------------------------------------------------------------------------------------- +//Private: +void MounterRouter::finish(const MounterRouterError& result) +{ + mMounting = false; + emit mountFinished(result); +} + +//Public: +bool MounterRouter::isMounting() { return mMounting; } + +quint16 MounterRouter::routerPort() const { return mRouterPort; } +QString MounterRouter::mountValue() const { return mMountValue; } + +void MounterRouter::setRouterPort(quint16 port) { mRouterPort = port; } +void MounterRouter::setMountValue(const QString& value) { mMountValue = value; } + +//-Signals & Slots------------------------------------------------------------------------------------------------------------ +//Private Slots: +void MounterRouter::mountFinishedHandler(QNetworkReply* reply) +{ + assert(reply == mRouterMountReply.get()); + + MounterRouterError err; + + // FP (as of 11) is currently bugged and is expected to give an internal server error so it must be ignored + if(reply->error() != QNetworkReply::NoError && reply->error() != QNetworkReply::InternalServerError) + { + err = MounterRouterError(MounterRouterError::Failed, reply->errorString()); + emit errorOccurred(NAME, err); + } + else + { + QByteArray response = reply->readAll(); + emit eventOccurred(NAME, EVENT_ROUTER_RESPONSE.arg(response)); + } + + finish(err); +} + +//Public Slots: +void MounterRouter::mount() +{ + emit eventOccurred(NAME, EVENT_MOUNTING_THROUGH_ROUTER); + + // Create mount request + QUrl mountUrl; + mountUrl.setScheme(u"http"_s); + mountUrl.setHost(u"127.0.0.1"_s); + mountUrl.setPort(mRouterPort); + mountUrl.setPath(u"/mount.php"_s); + + QUrlQuery query; + QString queryKey = u"file"_s; + QString queryValue = QUrl::toPercentEncoding(mMountValue); + query.addQueryItem(queryKey, queryValue); + mountUrl.setQuery(query); + + QNetworkRequest mountReq(mountUrl); + + // GET request + mRouterMountReply = mNam.get(mountReq); + + // Log request + emit eventOccurred(NAME, EVENT_REQUEST_SENT.arg(ENUM_NAME(mRouterMountReply->operation()), mountUrl.toString())); + + // Await finished() signal... +} + +void MounterRouter::abort() +{ + if(mRouterMountReply && mRouterMountReply->isRunning()) + mRouterMountReply->abort(); +} diff --git a/app/src/tools/mounter_router.h b/app/src/tools/mounter_router.h new file mode 100644 index 0000000..7c201bd --- /dev/null +++ b/app/src/tools/mounter_router.h @@ -0,0 +1,117 @@ +#ifndef MOUNTER_ROUTER_H +#define MOUNTER_ROUTER_H + +// Qt Includes +#include +#include +#include +#include + +// Qx Includes +#include +#include + +class QX_ERROR_TYPE(MounterRouterError, "MounterRouterError", 1234) +{ + friend class MounterRouter; + //-Class Enums------------------------------------------------------------- +public: + enum Type + { + NoError = 0, + Failed = 1 + }; + + //-Class Variables------------------------------------------------------------- +private: + static inline const QHash ERR_STRINGS{ + {NoError, u""_s}, + {Failed, u"Failed to mount data pack via router."_s}, + }; + + //-Instance Variables------------------------------------------------------------- +private: + Type mType; + QString mSpecific; + + //-Constructor------------------------------------------------------------- +private: + MounterRouterError(Type t = NoError, const QString& s = {}); + + //-Instance Functions------------------------------------------------------------- +public: + bool isValid() const; + Type type() const; + QString specific() const; + +private: + Qx::Severity deriveSeverity() const override; + quint32 deriveValue() const override; + QString derivePrimary() const override; + QString deriveSecondary() const override; +}; + +class MounterRouter : public QObject +{ + Q_OBJECT + +//-Class Variables------------------------------------------------------------------------------------------------------ +private: + // Meta + static inline const QString NAME = u"Mounter"_s; + + // Error + static inline const QString ERR_QMP_CONNECTION_ABORT = u"The connection was aborted."_s; + static inline const QString ERR_QMP_COMMAND = u"Command %1 - [%2] \"%3\""_s; + + // Events - External + static inline const QString EVENT_ROUTER_RESPONSE = u"Mount.php Response: \"%1\""_s; + + // Events - Internal + static inline const QString EVENT_MOUNTING_THROUGH_ROUTER = u"Mounting data pack via router..."_s; + static inline const QString EVENT_REQUEST_SENT = u"Sent request (%1): %2"_s; + + // Connections + static const int PHP_TRANSFER_TIMEOUT = 30000; // ms + +//-Instance Variables------------------------------------------------------------------------------------------------------------ +private: + bool mMounting; + int mRouterPort; + QString mMountValue; + + QNetworkAccessManager mNam; + QPointer mRouterMountReply; + +//-Constructor------------------------------------------------------------------------------------------------- +public: + explicit MounterRouter(QObject* parent = nullptr); + +//-Instance Functions--------------------------------------------------------------------------------------------------------- +private: + void finish(const MounterRouterError& result); + +public: + bool isMounting(); + + quint16 routerPort() const; + QString mountValue() const; + + void setRouterPort(quint16 port); + void setMountValue(const QString& value); + +//-Signals & Slots------------------------------------------------------------------------------------------------------------ +private slots: + void mountFinishedHandler(QNetworkReply* reply); + +public slots: + void mount(); + void abort(); + +signals: + void eventOccurred(QString name, QString event); + void errorOccurred(QString name, MounterRouterError errorMessage); + void mountFinished(MounterRouterError errorState); +}; + +#endif // MOUNTER_ROUTER_H diff --git a/app/src/tools/processbider.h b/app/src/tools/processbider.h index d211d6c..becd969 100644 --- a/app/src/tools/processbider.h +++ b/app/src/tools/processbider.h @@ -28,7 +28,7 @@ * handle the quit upon its next event loop cycle. */ -class QX_ERROR_TYPE(ProcessBiderError, "ProcessBiderError", 1233) +class QX_ERROR_TYPE(ProcessBiderError, "ProcessBiderError", 1235) { friend class ProcessBider; //-Class Enums-------------------------------------------------------------