From 1588793b47ee914878ecf43c91399dee6df0b5ea Mon Sep 17 00:00:00 2001 From: Christian Heimlich Date: Sat, 18 Nov 2023 18:14:47 -0500 Subject: [PATCH 01/26] Make ProcessBider cross platform Required to potentially allow CLIFp to run off of the Launcher's services. --- app/CMakeLists.txt | 7 +- app/src/task/t-bideprocess.cpp | 33 +---- app/src/task/t-bideprocess.h | 43 ------ app/src/tools/processbider.cpp | 191 +++++++------------------ app/src/tools/processbider.h | 46 +++--- app/src/tools/processbider_p.h | 73 ++++++++++ app/src/tools/processbider_p_linux.cpp | 48 +++++++ app/src/tools/processbider_p_win.cpp | 137 ++++++++++++++++++ 8 files changed, 349 insertions(+), 229 deletions(-) create mode 100644 app/src/tools/processbider_p.h create mode 100644 app/src/tools/processbider_p_linux.cpp create mode 100644 app/src/tools/processbider_p_win.cpp diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt index f91780d..14cb167 100644 --- a/app/CMakeLists.txt +++ b/app/CMakeLists.txt @@ -57,6 +57,9 @@ set(CLIFP_SOURCE tools/mounter_qmp.cpp tools/mounter_router.h tools/mounter_router.cpp + tools/processbider_p.h + tools/processbider.h + tools/processbider.cpp frontend/message.h frontend/statusrelay.h frontend/statusrelay.cpp @@ -90,8 +93,7 @@ if(CMAKE_SYSTEM_NAME STREQUAL Windows) task/t-exec_win.cpp task/t-bideprocess.h task/t-bideprocess.cpp - tools/processbider.h - tools/processbider.cpp + tools/processbider_p_win.cpp ) list(APPEND CLIFP_LINKS @@ -106,6 +108,7 @@ if(CMAKE_SYSTEM_NAME STREQUAL Linux) task/t-awaitdocker.h task/t-awaitdocker.cpp task/t-exec_linux.cpp + tools/processbider_p_linux.cpp ) list(APPEND CLIFP_LINKS PRIVATE diff --git a/app/src/task/t-bideprocess.cpp b/app/src/task/t-bideprocess.cpp index e380e48..bb80fbd 100644 --- a/app/src/task/t-bideprocess.cpp +++ b/app/src/task/t-bideprocess.cpp @@ -1,29 +1,6 @@ // Unit Include #include "t-bideprocess.h" -//=============================================================================================================== -// TBideProcessError -//=============================================================================================================== - -//-Constructor------------------------------------------------------------- -//Private: -TBideProcessError::TBideProcessError(Type t, const QString& s) : - mType(t), - mSpecific(s) -{} - -//-Instance Functions------------------------------------------------------------- -//Public: -bool TBideProcessError::isValid() const { return mType != NoError; } -QString TBideProcessError::specific() const { return mSpecific; } -TBideProcessError::Type TBideProcessError::type() const { return mType; } - -//Private: -Qx::Severity TBideProcessError::deriveSeverity() const { return Qx::Err; } -quint32 TBideProcessError::deriveValue() const { return mType; } -QString TBideProcessError::derivePrimary() const { return ERR_STRINGS.value(mType); } -QString TBideProcessError::deriveSecondary() const { return mSpecific; } - //=============================================================================================================== // TBideProcess //=============================================================================================================== @@ -32,9 +9,10 @@ QString TBideProcessError::deriveSecondary() const { return mSpecific; } //Public: TBideProcess::TBideProcess(QObject* parent) : Task(parent), - mProcessBider(nullptr, STANDARD_GRACE) + mProcessBider(nullptr) { // Setup bider + mProcessBider.setRespawnGrace(STANDARD_GRACE); connect(&mProcessBider, &ProcessBider::statusChanged, this, [this](QString statusMessage){ emit eventOccurred(NAME, statusMessage); }); @@ -61,7 +39,8 @@ void TBideProcess::setProcessName(QString processName) { mProcessName = processN void TBideProcess::perform() { // Start bide - mProcessBider.start(mProcessName); + mProcessBider.setProcessName(mProcessName); + mProcessBider.start(); } void TBideProcess::stop() @@ -69,8 +48,8 @@ void TBideProcess::stop() if(mProcessBider.isRunning()) { emit eventOccurred(NAME, LOG_EVENT_STOPPING_BIDE_PROCESS); - if(!mProcessBider.closeProcess()) - emit errorOccurred(NAME, TBideProcessError(TBideProcessError::CantClose)); + if(ProcessBiderError err = mProcessBider.closeProcess(); err.isValid()) + emit errorOccurred(NAME, err); } } diff --git a/app/src/task/t-bideprocess.h b/app/src/task/t-bideprocess.h index eebb9cd..d1b899d 100644 --- a/app/src/task/t-bideprocess.h +++ b/app/src/task/t-bideprocess.h @@ -1,53 +1,10 @@ #ifndef TBIDEPROCESS_H #define TBIDEPROCESS_H -// Qx Includes -#include - // Project Includes #include "task/task.h" #include "tools/processbider.h" -class QX_ERROR_TYPE(TBideProcessError, "TBideProcessError", 1251) -{ - friend class TBideProcess; - //-Class Enums------------------------------------------------------------- -public: - enum Type - { - NoError = 0, - CantClose = 1, - }; - - //-Class Variables------------------------------------------------------------- -private: - static inline const QHash ERR_STRINGS{ - {NoError, u""_s}, - {CantClose, u"Could not automatically end the running title! It will have to be closed manually."_s}, - }; - - //-Instance Variables------------------------------------------------------------- -private: - Type mType; - QString mSpecific; - - //-Constructor------------------------------------------------------------- -private: - TBideProcessError(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 TBideProcess : public Task { Q_OBJECT; diff --git a/app/src/tools/processbider.cpp b/app/src/tools/processbider.cpp index 03d6867..e5aa2a9 100644 --- a/app/src/tools/processbider.cpp +++ b/app/src/tools/processbider.cpp @@ -1,13 +1,9 @@ // Unit Include #include "processbider.h" +#include "processbider_p.h" // Qx Includes -#include #include -#include - -// Windows Include -#include //=============================================================================================================== // ProcessBiderError @@ -38,73 +34,25 @@ QString ProcessBiderError::deriveSecondary() const { return mSpecific; } //-Constructor------------------------------------------------------------- //Public: -ProcessBider::ProcessBider(QObject* parent, uint respawnGrace) : +ProcessBider::ProcessBider(QObject* parent, const QString& name) : QThread(parent), - mRespawnGrace(respawnGrace), - mProcessHandle(nullptr) + mProcessName(name), + mRespawnGrace(30000), + mPollRate(500) {} -//-Class Functions------------------------------------------------------------- -//Private: -bool ProcessBider::closeAdminProcess(DWORD processId, bool force) -{ - /* Killing an elevated process from this process while it is unelevated requires (without COM non-sense) starting - * a new process as admin to do the job. While a special purpose executable could be made, taskkill already - * perfectly suitable here - */ - - // Setup taskkill args - QString tkArgs; - if(force) - tkArgs += u"/F "_s; - tkArgs += u"/PID "_s; - tkArgs += QString::number(processId); - const std::wstring tkArgsStd = tkArgs.toStdWString(); - - // Setup taskkill info - SHELLEXECUTEINFOW tkExecInfo = {0}; - tkExecInfo.cbSize = sizeof(SHELLEXECUTEINFOW); // Required - tkExecInfo.fMask = SEE_MASK_NOCLOSEPROCESS; // Causes hProcess member to be set to process handle - tkExecInfo.hwnd = NULL; - tkExecInfo.lpVerb = L"runas"; - tkExecInfo.lpFile = L"taskkill"; - tkExecInfo.lpParameters = tkArgsStd.data(); - tkExecInfo.lpDirectory = NULL; - tkExecInfo.nShow = SW_HIDE; - - // Start taskkill - if(!ShellExecuteEx(&tkExecInfo)) - return false; - - // Check for handle - HANDLE tkHandle = tkExecInfo.hProcess; - if(!tkHandle) - return false; - - // Wait for taskkill to finish (should be fast) - if(WaitForSingleObject(tkHandle, 5000) != WAIT_OBJECT_0) - return false; - - DWORD exitCode; - if(!GetExitCodeProcess(tkHandle, &exitCode)) - return false; - - // Cleanup taskkill handle - CloseHandle(tkHandle); - - // Return taskkill result - return exitCode == 0; -} - //-Instance Functions------------------------------------------------------------- //Private: ProcessBiderError ProcessBider::doWait() { - // Lock other threads from interaction while managing process handle - QMutexLocker handleLocker(&mProcessHandleMutex); + mWaiter = new ProcessWaiter(mProcessName); + mWaiter->setPollRate(mPollRate); + + // Block outer access until waiting + QWriteLocker writeLock(&mRWLock); // Wait until process has stopped running for grace period - DWORD spProcessId; + quint32 procId; do { // Yield for grace period @@ -113,49 +61,29 @@ ProcessBiderError ProcessBider::doWait() QThread::sleep(mRespawnGrace); // Find process ID by name - spProcessId = Qx::processId(mProcessName); + procId = Qx::processId(mProcessName); // Check that process was found (is running) - if(spProcessId) + if(procId) { emit statusChanged(LOG_EVENT_BIDE_RUNNING.arg(mProcessName)); - // Get process handle and see if it is valid - DWORD rights = PROCESS_QUERY_LIMITED_INFORMATION | SYNCHRONIZE; - if((mProcessHandle = OpenProcess(rights, FALSE, spProcessId)) == NULL) - { - Qx::SystemError nativeError = Qx::SystemError::fromHresult(HRESULT_FROM_WIN32(GetLastError())); - ProcessBiderError err(ProcessBiderError::HandleAquisition, nativeError.cause()); - - emit errorOccurred(err); - return err; - } - // Attempt to wait on process to terminate emit statusChanged(LOG_EVENT_BIDE_ON.arg(mProcessName)); - handleLocker.unlock(); // Allow interaction while waiting - DWORD waitError = WaitForSingleObject(mProcessHandle, INFINITE); - handleLocker.relock(); // Explicitly lock again - - // Close handle to process - CloseHandle(mProcessHandle); - mProcessHandle = nullptr; - - /* Here the status can technically can be WAIT_ABANDONED, WAIT_OBJECT_0, WAIT_TIMEOUT, or WAIT_FAILED, but the first - * and third should never occur here (the wait won't ever be abandoned, and the timeout is infinite), so this check is fine - */ - if(waitError != WAIT_OBJECT_0) + mWaiter->updateId(procId); + writeLock.unlock(); // To allow close attempts + if(!mWaiter->wait()) // Blocks until process ends { - Qx::SystemError nativeError = Qx::SystemError::fromHresult(HRESULT_FROM_WIN32(GetLastError())); - ProcessBiderError err(ProcessBiderError::ProcessHook, nativeError.cause()); - + ProcessBiderError err(ProcessBiderError::Wait, mProcessName); emit errorOccurred(err); return err; } + writeLock.relock(); + emit statusChanged(LOG_EVENT_BIDE_QUIT.arg(mProcessName)); } } - while(spProcessId); + while(procId); // Return success emit statusChanged(LOG_EVENT_BIDE_FINISHED.arg(mProcessName)); @@ -165,63 +93,52 @@ ProcessBiderError ProcessBider::doWait() void ProcessBider::run() { ProcessBiderError status = doWait(); + qxDelete(mWaiter); emit bideFinished(status); } //Public: -void ProcessBider::setRespawnGrace(uint respawnGrace) { mRespawnGrace = respawnGrace; } +void ProcessBider::setProcessName(const QString& name) +{ + mRWLock.lockForWrite(); + mProcessName = name; + mRWLock.unlock(); +} +void ProcessBider::setRespawnGrace(uint respawnGrace) +{ + mRWLock.lockForWrite(); + mRespawnGrace = respawnGrace; + mRWLock.unlock(); +} -bool ProcessBider::closeProcess() +void ProcessBider::setPollRate(uint pollRate) { - if(!mProcessHandle) - return false; - - // Lock access to handle and auto-unlock when done - QMutexLocker handleLocker(&mProcessHandleMutex); - - /* Get process ID for use in some of the following calls so that the specific permissions the mProcessHandle - * was opened with don't have to be considered - */ - DWORD processId = GetProcessId(mProcessHandle); - - // Check if admin rights are needed (CLIFp shouldn't be run as admin, but check anyway) - bool selfElevated; - if(Qx::processIsElevated(selfElevated).isValid()) - selfElevated = false; // If check fails, assume CLIFP is not elevated to be safe - bool waitProcessElevated; - if(Qx::processIsElevated(waitProcessElevated, processId).isValid()) - waitProcessElevated = true; // If check fails, assume process is elevated to be safe - - bool elevate = !selfElevated && waitProcessElevated; - - // Try clean close first - if(!elevate) - Qx::cleanKillProcess(processId); - else - closeAdminProcess(processId, false); - - // Wait for process to close (allow up to 2 seconds) - DWORD waitRes = WaitForSingleObject(mProcessHandle, 2000); - - // See if process closed - if(waitRes == WAIT_OBJECT_0) - return true; - - // Force close - if(!elevate) - return !Qx::forceKillProcess(processId).isValid(); - else - return closeAdminProcess(processId, true); + mRWLock.lockForWrite(); + mPollRate = pollRate; + mRWLock.unlock(); +} + +/* TODO: Since this doesn't allow explicitly abandoning the wait, if for some reason the process opens itself over and over, + * it's still possible to get stuck in a dead lock even if the close is successful + */ +ProcessBiderError ProcessBider::closeProcess() +{ + QReadLocker readLocker(&mRWLock); + if(mWaiter && mWaiter->isWaiting() && !mWaiter->close()) + { + ProcessBiderError err(ProcessBiderError::Close, mProcessName); + emit errorOccurred(err); + return err; + } + + return ProcessBiderError(); } //-Signals & Slots------------------------------------------------------------------------------------------------------------ //Public Slots: -void ProcessBider::start(QString processName) +void ProcessBider::start() { // Start new thread for waiting if(!isRunning()) - { - mProcessName = processName; QThread::start(); - } } diff --git a/app/src/tools/processbider.h b/app/src/tools/processbider.h index db493c2..3595f20 100644 --- a/app/src/tools/processbider.h +++ b/app/src/tools/processbider.h @@ -3,18 +3,18 @@ // Qt Includes #include -#include +#include // Qx Includes -#include -#include +#include +#include /* This uses the approach of sub-classing QThread instead of the worker/object model. This means that by default there is no event * loop running in the new thread (not needed with current setup), and that only the contents of run() take place in the new thread, * with everything else happening in the thread that contains the instance of this class. * * This does mean that a Mutex must be used to protected against access to the same data between threads where necessary, but in - * this case that is desirable as when the parent thread calls `endProcess()` we want it to get blocked if the run() thread is + * this case that is desirable as when the parent thread calls `closeProcess()` we want it to get blocked if the run() thread is * busy doing work until it goes back to waiting (or the wait process stops on its own), since we want to make sure that the wait * process has ended before Driver takes its next steps. * @@ -26,6 +26,10 @@ * caveats since a thread spawned by the OS is used to trigger the specified callback function. It would potentially be safe if that * callback function simply emits an internal signal with a queued connection that then triggers the thread managing the object to * handle the quit upon its next event loop cycle. + * + * NOTE: Technically the thread synchronization here is imperfect as a blocked closeProcess() is unlocked one step before the wait + * actually starts so there could be a race between that and the waiter being marked as "in wait". Not a great way to avoid this though + * since the lock can't be unlocked any later since the waiting thread deadlocks once the wait is started. */ class QX_ERROR_TYPE(ProcessBiderError, "ProcessBiderError", 1235) @@ -36,16 +40,16 @@ class QX_ERROR_TYPE(ProcessBiderError, "ProcessBiderError", 1235) enum Type { NoError = 0, - HandleAquisition = 1, - ProcessHook = 2 + Wait = 1, + Close = 2 }; //-Class Variables------------------------------------------------------------- private: static inline const QHash ERR_STRINGS{ {NoError, u""_s}, - {HandleAquisition, u"Could not get a wait handle to a restartable process, the title will likely not work correctly."_s}, - {ProcessHook, u"Could not hook a restartable process for waiting, the title will likely not work correctly."_s}, + {Wait, u"Could not setup a wait on the process."_s}, + {Close, u"Could not close the wait on process."_s}, }; //-Instance Variables------------------------------------------------------------- @@ -70,6 +74,8 @@ class QX_ERROR_TYPE(ProcessBiderError, "ProcessBiderError", 1235) QString deriveSecondary() const override; }; +class ProcessWaiter; + class ProcessBider : public QThread { Q_OBJECT @@ -84,35 +90,35 @@ class ProcessBider : public QThread //-Instance Variables------------------------------------------------------------------------------------------------------------ private: + // Work + ProcessWaiter* mWaiter; + QReadWriteLock mRWLock; + // Process Info QString mProcessName; uint mRespawnGrace; - - // Process Handling - HANDLE mProcessHandle; - QMutex mProcessHandleMutex; + uint mPollRate; //-Constructor------------------------------------------------------------------------------------------------- public: - ProcessBider(QObject* parent = nullptr, uint respawnGrace = 30000); - -//-Class Functions--------------------------------------------------------------------------------------------------------- -private: - static bool closeAdminProcess(DWORD processId, bool force); + ProcessBider(QObject* parent = nullptr, const QString& name = {}); //-Instance Functions--------------------------------------------------------------------------------------------------------- private: + // Run in wait thread ProcessBiderError doWait(); void run() override; public: + // Run in external thread + void setProcessName(const QString& name); void setRespawnGrace(uint respawnGrace); - - bool closeProcess(); + void setPollRate(uint pollRate); // Ignored on Windows + ProcessBiderError closeProcess(); //-Signals & Slots------------------------------------------------------------------------------------------------------------ public slots: - void start(QString processName); + void start(); signals: void statusChanged(QString statusMessage); diff --git a/app/src/tools/processbider_p.h b/app/src/tools/processbider_p.h new file mode 100644 index 0000000..78900b4 --- /dev/null +++ b/app/src/tools/processbider_p.h @@ -0,0 +1,73 @@ +#ifndef PROCESSWAITER_P_H +#define PROCESSWAITER_P_H + +#include +#include +#ifdef __linux__ + #include +#endif +#ifdef _WIN32 + typedef void* HANDLE; +#endif + +class ProcessWaiter +{ +//-Instance Variables------------------------------------------------------------------------------------------------------------ +private: + bool mWaiting; + QString mName; + quint32 mId; + uint mPollRate; + QMutex mMutex; + +#ifdef _WIN32 + HANDLE mHandle; +#endif +#ifdef __linux__ + QWaitCondition mCloseNotifier; +#endif + +//-Constructor------------------------------------------------------------------------------------------------- +public: + ProcessWaiter(const QString& name) : + mWaiting(false), + mName(name), + mId(0), + mPollRate(500) + {} + +//-Instance Functions--------------------------------------------------------------------------------------------------------- +private: + bool _wait(); + bool _close(); + +public: + // Used in waiting thread + bool wait() + { + mWaiting = true; + bool r =_wait(); + mWaiting = false; + return r; + } + + // Used from external thread; + void updateId(quint32 id) + { + mMutex.lock(); + mId = id; + mMutex.unlock(); + }; + + void setPollRate(uint pollRate) + { + mMutex.lock(); + mPollRate = pollRate; + mMutex.unlock(); + } + + bool isWaiting() { return mWaiting; } + bool close() { return mWaiting ? _close() : true; } +}; + +#endif // PROCESSWAITER_P_H diff --git a/app/src/tools/processbider_p_linux.cpp b/app/src/tools/processbider_p_linux.cpp new file mode 100644 index 0000000..74e55ec --- /dev/null +++ b/app/src/tools/processbider_p_linux.cpp @@ -0,0 +1,48 @@ +// Unit Include +#include "processbider_p.h" + +// Qt Includes +#include + +// Qx Includes +#include + +//=============================================================================================================== +// ProcessWaiter +//=============================================================================================================== + +//-Instance Functions------------------------------------------------------------- +//Public: +bool ProcessWaiter::_wait() +{ + // Poll for process existence + QString currentName = mName; + while(currentName == mName) + { + QThread::msleep(mPollRate); + mMutex.lock(); // Don't allow close during check + currentName = Qx::processName(mId); + mMutex.unlock(); + } + + // Notify that the process closed + mCloseNotifier.wakeAll(); + + return true; +} + +bool ProcessWaiter::_close() +{ + // NOTE: Does not handle killing processes that require greater permissions + + // Try clean close first + Qx::cleanKillProcess(mId); + + // See if process closes (max 2 seconds, though al) + mMutex.lock(); + if(mCloseNotifier.wait(&mMutex, std::max(uint(2000), mPollRate + 100))) + return true; + + // Force close + return !Qx::forceKillProcess(mId).isValid(); +} diff --git a/app/src/tools/processbider_p_win.cpp b/app/src/tools/processbider_p_win.cpp new file mode 100644 index 0000000..afdf1c7 --- /dev/null +++ b/app/src/tools/processbider_p_win.cpp @@ -0,0 +1,137 @@ +// Unit Include +#include "processbider_p.h" + +// Qx Includes +#include +#include +#include + +// Windows Include +#include + +namespace +{ + +bool closeAdminProcess(DWORD processId, bool force) +{ + /* Killing an elevated process from this process while it is unelevated requires (without COM non-sense) starting + * a new process as admin to do the job. While a special purpose executable could be made, taskkill already + * perfectly suitable here + */ + + // Setup taskkill args + QString tkArgs; + if(force) + tkArgs += u"/F "_s; + tkArgs += u"/PID "_s; + tkArgs += QString::number(processId); + const std::wstring tkArgsStd = tkArgs.toStdWString(); + + // Setup taskkill info + SHELLEXECUTEINFOW tkExecInfo = {0}; + tkExecInfo.cbSize = sizeof(SHELLEXECUTEINFOW); // Required + tkExecInfo.fMask = SEE_MASK_NOCLOSEPROCESS; // Causes hProcess member to be set to process handle + tkExecInfo.hwnd = NULL; + tkExecInfo.lpVerb = L"runas"; + tkExecInfo.lpFile = L"taskkill"; + tkExecInfo.lpParameters = tkArgsStd.data(); + tkExecInfo.lpDirectory = NULL; + tkExecInfo.nShow = SW_HIDE; + + // Start taskkill + if(!ShellExecuteEx(&tkExecInfo)) + return false; + + // Check for handle + HANDLE tkHandle = tkExecInfo.hProcess; + if(!tkHandle) + return false; + + // Wait for taskkill to finish (should be fast) + if(WaitForSingleObject(tkHandle, 5000) != WAIT_OBJECT_0) + return false; + + DWORD exitCode; + if(!GetExitCodeProcess(tkHandle, &exitCode)) + return false; + + // Cleanup taskkill handle + CloseHandle(tkHandle); + + // Return taskkill result + return exitCode == 0; +} + +} + +//=============================================================================================================== +// ProcessWaiter +//=============================================================================================================== + +//-Instance Functions------------------------------------------------------------- +//Public: +bool ProcessWaiter::_wait() +{ + // Prevent changes while setting up the wait + QMutexLocker mutLock(&mMutex); + + // Get process handle and see if it is valid + DWORD rights = PROCESS_QUERY_LIMITED_INFORMATION | SYNCHRONIZE; + if((mHandle = OpenProcess(rights, FALSE, mId)) == NULL) // Can use Qx::lastError() for error info here if desired + return false; + + // Attempt to wait on process to terminate + mutLock.unlock(); // Allow changes while waiting + DWORD waitError = WaitForSingleObject(mHandle, INFINITE); + mutLock.relock(); // Prevent changes during cleanup + + // Close handle to process + CloseHandle(mHandle); + mHandle = nullptr; + + /* Here the status can technically can be WAIT_ABANDONED, WAIT_OBJECT_0, WAIT_TIMEOUT, or WAIT_FAILED, but the first + * and third should never occur here (the wait won't ever be abandoned, and the timeout is infinite), so this check is fine + */ + if(waitError != WAIT_OBJECT_0) // Can use Qx::lastError() for error info here if desired + return false; + + return true; +} + +bool ProcessWaiter::_close() +{ + if(!mHandle) + return true; + + // Prevent wait setup/cleanup during closure and auto-unlock when done + QMutexLocker mutLock(&mMutex); + + // Check if admin rights are needed (CLIFp shouldn't be run as admin, but check anyway) + bool selfElevated; + if(Qx::processIsElevated(selfElevated).isValid()) + selfElevated = false; // If check fails, assume CLIFP is not elevated to be safe + bool waitProcessElevated; + if(Qx::processIsElevated(waitProcessElevated, mId).isValid()) + waitProcessElevated = true; // If check fails, assume process is elevated to be safe + + bool elevate = !selfElevated && waitProcessElevated; + + // Try clean close first + if(!elevate) + Qx::cleanKillProcess(mId); + else + closeAdminProcess(mId, false); + + // Wait for process to close (allow up to 2 seconds) + DWORD waitRes = WaitForSingleObject(mHandle, 2000); + + // See if process closed + if(waitRes == WAIT_OBJECT_0) + return true; + + // Force close + if(!elevate) + return !Qx::forceKillProcess(mId).isValid(); + else + return closeAdminProcess(mId, true); +} From 56c33f3bbc8ede5161e459d7e4eb6e6ae5682beb Mon Sep 17 00:00:00 2001 From: Christian Heimlich Date: Thu, 9 Nov 2023 04:48:30 -0500 Subject: [PATCH 02/26] Allow all TitleCommand options for CPrepare Also fixes the command's help option as that option was inadvertently excluded before. --- app/src/command/c-prepare.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/command/c-prepare.cpp b/app/src/command/c-prepare.cpp index fae8ef5..22df190 100644 --- a/app/src/command/c-prepare.cpp +++ b/app/src/command/c-prepare.cpp @@ -14,7 +14,7 @@ CPrepare::CPrepare(Core& coreRef) : TitleCommand(coreRef) {} //-Instance Functions------------------------------------------------------------- //Protected: -QList CPrepare::options() { return {&CL_OPTION_ID, &CL_OPTION_TITLE, &CL_OPTION_TITLE_STRICT}; } +QList CPrepare::options() { return TitleCommand::options(); } QString CPrepare::name() { return NAME; } Qx::Error CPrepare::perform() From 97c83a2f526f8a2522c4fa0742d892385e0d94e8 Mon Sep 17 00:00:00 2001 From: Christian Heimlich Date: Thu, 9 Nov 2023 14:20:22 -0500 Subject: [PATCH 03/26] Allow multiple downloads with TDownload --- CMakeLists.txt | 2 +- app/src/command/c-update.cpp | 5 ++- app/src/kernel/core.cpp | 6 ++-- app/src/task/t-download.cpp | 67 ++++++++++++++---------------------- app/src/task/t-download.h | 45 ++++++++++-------------- 5 files changed, 50 insertions(+), 75 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 650c2b4..01cbfac 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -72,7 +72,7 @@ endif() include(OB/FetchQx) ob_fetch_qx( - REF "v0.5.5.1" + REF "e3ea74ec98d3e5ed57957f9a9feb83c031fec88d" COMPONENTS ${CLIFP_QX_COMPONENTS} ) diff --git a/app/src/command/c-update.cpp b/app/src/command/c-update.cpp index 8ed4f8f..b2d131a 100644 --- a/app/src/command/c-update.cpp +++ b/app/src/command/c-update.cpp @@ -330,9 +330,8 @@ CUpdateError CUpdate::checkAndPrepareUpdate() const QString tempName = u"clifp_update.zip"_s; TDownload* downloadTask = new TDownload(&mCore); downloadTask->setStage(Task::Stage::Primary); - downloadTask->setTargetFile(aItr->browser_download_url); - downloadTask->setDestinationPath(uDownloadDir.absolutePath()); - downloadTask->setDestinationFilename(tempName); + downloadTask->setDescription(u"update"_s); + downloadTask->addFile({.target = aItr->browser_download_url, .dest = uDownloadDir.absoluteFilePath(tempName)}); mCore.enqueueSingleTask(downloadTask); TExtract* extractTask = new TExtract(&mCore); diff --git a/app/src/kernel/core.cpp b/app/src/kernel/core.cpp index 4f7c9b2..960b2b3 100644 --- a/app/src/kernel/core.cpp +++ b/app/src/kernel/core.cpp @@ -738,10 +738,8 @@ Qx::Error Core::enqueueDataPackTasks(const Fp::GameData& gameData) TDownload* downloadTask = new TDownload(this); downloadTask->setStage(Task::Stage::Auxiliary); - downloadTask->setDestinationPath(packDestFolderPath); - downloadTask->setDestinationFilename(packFileName); - downloadTask->setTargetFile(gameSourceBase + '/' + packFileName); - downloadTask->setSha256(packSha256); + downloadTask->setDescription(u"data pack "_s + QFileInfo(packFile).fileName()); + downloadTask->addFile({.target = gameSourceBase + '/' + packFileName, .dest = packFile.fileName(), .checksum = packSha256}); mTaskQueue.push(downloadTask); logTask(NAME, downloadTask); diff --git a/app/src/task/t-download.cpp b/app/src/task/t-download.cpp index 3fadb9b..a2a40b5 100644 --- a/app/src/task/t-download.cpp +++ b/app/src/task/t-download.cpp @@ -35,6 +35,7 @@ TDownload::TDownload(QObject* parent) : { // Setup download manager mDownloadManager.setOverwrite(true); + mDownloadManager.setVerificationMethod(QCryptographicHash::Sha256); // Since this is only for one download, the size will be adjusted to the correct total as soon as the download starts mDownloadManager.setSkipEnumeration(true); @@ -69,37 +70,38 @@ QString TDownload::name() const { return NAME; } QStringList TDownload::members() const { QStringList ml = Task::members(); - ml.append(u".destinationPath() = \""_s + QDir::toNativeSeparators(mDestinationPath) + u"\""_s); - ml.append(u".destinationFilename() = \""_s + mDestinationFilename + u"\""_s); - ml.append(u".targetFile() = \""_s + mTargetFile.toString() + u"\""_s); - ml.append(u".sha256() = "_s + mSha256); + + QString files = u".files() = {\n"_s; + for(auto i = 0; i < 10 && i < mFiles.size(); i++) + { + auto f = mFiles.at(i); + bool sum = !f.checksum.isEmpty(); + files += u"\t"_s + FILE_DOWNLOAD_TEMPLATE.arg(f.target.toString(), f.dest, sum ? f.checksum : FILE_NO_CHECKSUM) + '\n'; + } + if(mFiles.size() > 10) + files += FILE_DOWNLOAD_ELIDE.arg(mFiles.size() - 10) + '\n'; + files += u"}\n"_s; + ml.append(files); + ml.append(u".description() = \""_s + mDescription + u"\""_s); + return ml; } -QString TDownload::destinationPath() const { return mDestinationPath; } -QString TDownload::destinationFilename() const { return mDestinationFilename; } -QUrl TDownload::targetFile() const { return mTargetFile; } -QString TDownload::sha256() const { return mSha256; } +QList TDownload::files() const { return mFiles; } +QString TDownload::description() const { return mDescription; } -void TDownload::setDestinationPath(QString path) { mDestinationPath = path; } -void TDownload::setDestinationFilename(QString filename) { mDestinationFilename = filename; } -void TDownload::setTargetFile(QUrl targetFile) { mTargetFile = targetFile; } -void TDownload::setSha256(QString sha256) { mSha256 = sha256; } +void TDownload::addFile(const Qx::DownloadTask file) { mFiles.append(file); } +void TDownload::setDescription(const QString& desc) { mDescription = desc; } void TDownload::perform() { - // Setup download - QFile file(mDestinationPath + '/' + mDestinationFilename); - QFileInfo fileInfo(file); - Qx::DownloadTask download{ - .target = mTargetFile, - .dest = fileInfo.absoluteFilePath() - }; - mDownloadManager.appendTask(download); + // Add files + for(const auto& f : mFiles) + mDownloadManager.appendTask(f); // Log/label string - QString label = LOG_EVENT_DOWNLOADING_FILE.arg(fileInfo.fileName()); - emit eventOccurred(NAME, label); + QString label = LOG_EVENT_DOWNLOAD.arg(mDescription); + emit eventOccurred(NAME, LOG_EVENT_DOWNLOAD); // Start download emit longTaskStarted(label); @@ -123,28 +125,11 @@ void TDownload::postDownload(Qx::DownloadManagerReport downloadReport) // Handle result emit longTaskFinished(); - if(downloadReport.wasSuccessful()) - { - // Confirm checksum is correct, if supplied - if(!mSha256.isEmpty()) - { - QFile file(mDestinationPath + '/' + mDestinationFilename); - bool checksumMatch; - Qx::IoOpReport cr = Qx::fileMatchesChecksum(checksumMatch, file, mSha256, QCryptographicHash::Sha256); - if(cr.isFailure() || !checksumMatch) - { - TDownloadError err(TDownloadError::ChecksumMismatch, cr.isFailure() ? cr.outcomeInfo() : u""_s); - errorStatus = err; - emit errorOccurred(NAME, errorStatus); - } - } - + if(downloadReport.wasSuccessful()) emit eventOccurred(NAME, LOG_EVENT_DOWNLOAD_SUCC); - } else { - TDownloadError err(TDownloadError::Incomeplete, downloadReport.outcomeString()); - errorStatus = err; + errorStatus = TDownloadError(TDownloadError::Incomeplete, downloadReport.outcomeString()); emit errorOccurred(NAME, errorStatus); } diff --git a/app/src/task/t-download.h b/app/src/task/t-download.h index ff40bb9..867b6bb 100644 --- a/app/src/task/t-download.h +++ b/app/src/task/t-download.h @@ -11,33 +11,31 @@ class QX_ERROR_TYPE(TDownloadError, "TDownloadError", 1252) { friend class TDownload; - //-Class Enums------------------------------------------------------------- +//-Class Enums------------------------------------------------------------- public: enum Type { NoError = 0, - ChecksumMismatch = 1, Incomeplete = 2 }; - //-Class Variables------------------------------------------------------------- +//-Class Variables------------------------------------------------------------- private: static inline const QHash ERR_STRINGS{ {NoError, u""_s}, - {ChecksumMismatch, u"The file's checksum does not match its record!"_s}, - {Incomeplete, u"The download could not be completed."_s} + {Incomeplete, u"The download(s) could not be completed."_s} }; - //-Instance Variables------------------------------------------------------------- +//-Instance Variables------------------------------------------------------------- private: Type mType; QString mSpecific; - //-Constructor------------------------------------------------------------- +//-Constructor------------------------------------------------------------- private: TDownloadError(Type t = NoError, const QString& s = {}); - //-Instance Functions------------------------------------------------------------- +//-Instance Functions------------------------------------------------------------- public: bool isValid() const; Type type() const; @@ -59,25 +57,24 @@ class TDownload : public Task static inline const QString NAME = u"TDownload"_s; // Logging - static inline const QString LOG_EVENT_DOWNLOADING_FILE = u"Downloading file %1"_s; - static inline const QString LOG_EVENT_DOWNLOAD_SUCC = u"File downloaded successfully"_s; + static inline const QString LOG_EVENT_DOWNLOAD = u"Downloading %1"_s; + static inline const QString LOG_EVENT_DOWNLOAD_SUCC = u"File(s) downloaded successfully"_s; static inline const QString LOG_EVENT_DOWNLOAD_AUTH = u"File download unexpectedly requires authentication (%1)"_s; static inline const QString LOG_EVENT_STOPPING_DOWNLOADS = u"Stopping current download(s)..."_s; + // Members + static inline const QString FILE_NO_CHECKSUM = u"NO SUM"_s; + static inline const QString FILE_DOWNLOAD_TEMPLATE = uR"("%1" -> "%2" (%3))"_s; + static inline const QString FILE_DOWNLOAD_ELIDE = u"+%1 more..."_s; + //-Instance Variables------------------------------------------------------------------------------------------------ private: // Functional Qx::AsyncDownloadManager mDownloadManager; - /* NOTE: If it ever becomes required to perform multiple downloads in a run the DM instance should - * be made a static member of TDownload, or all downloads need to be determined at the same time - * and the task made capable of holding all of them - */ // Data - QString mDestinationPath; - QString mDestinationFilename; - QUrl mTargetFile; - QString mSha256; + QList mFiles; + QString mDescription; //-Constructor---------------------------------------------------------------------------------------------------------- public: @@ -88,15 +85,11 @@ class TDownload : public Task QString name() const override; QStringList members() const override; - QString destinationPath() const; - QString destinationFilename() const; - QUrl targetFile() const; - QString sha256() const; + QList files() const; + QString description() const; - void setDestinationPath(QString path); - void setDestinationFilename(QString filename); - void setTargetFile(QUrl targetFile); - void setSha256(QString sha256); + void addFile(const Qx::DownloadTask file); + void setDescription(const QString& desc); void perform() override; void stop() override; From 014374f2676191c01b82611547b0c75d104f4007 Mon Sep 17 00:00:00 2001 From: Christian Heimlich Date: Thu, 9 Nov 2023 16:56:56 -0500 Subject: [PATCH 04/26] Add CDownload, allows for batch downloading of datapacks from playlists --- CMakeLists.txt | 4 +- README.md | 8 +++ app/CMakeLists.txt | 2 + app/src/command/c-download.cpp | 114 +++++++++++++++++++++++++++++++++ app/src/command/c-download.h | 89 +++++++++++++++++++++++++ app/src/kernel/core.cpp | 104 ++++++++++++++++++------------ app/src/kernel/core.h | 5 +- app/src/task/t-download.cpp | 7 +- 8 files changed, 285 insertions(+), 48 deletions(-) create mode 100644 app/src/command/c-download.cpp create mode 100644 app/src/command/c-download.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 01cbfac..3ffa92a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -72,14 +72,14 @@ endif() include(OB/FetchQx) ob_fetch_qx( - REF "e3ea74ec98d3e5ed57957f9a9feb83c031fec88d" + REF "7acb9d0f3e65de2f0a6e91c979b1216e6be7de7d" COMPONENTS ${CLIFP_QX_COMPONENTS} ) # Fetch libfp (build and import from source) include(OB/Fetchlibfp) -ob_fetch_libfp("v0.5.1.1") +ob_fetch_libfp("d27276305defe2fbd98946b23ec9d135ce3ae1bd") # Fetch QI-QMP (build and import from source) include(OB/FetchQI-QMP) diff --git a/README.md b/README.md index 74de0b5..eb67ab7 100644 --- a/README.md +++ b/README.md @@ -171,6 +171,14 @@ The **-title-strict** and **-subtitle-strict** options only consider exact match Tip: You can use **-subtitle** with an empty string (i.e. `-s ""`) to see all of the additional-apps for a given title. ### Command List: + +**download** - Downloads data packs for games that require them in bulk + +Options: +- **-p | --playlist** Name of the playlist to download games for. + +-------------------------------------------------------------------------------- + **link** - Creates a shortcut to a Flashpoint title Options: diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt index af6d3df..f91780d 100644 --- a/app/CMakeLists.txt +++ b/app/CMakeLists.txt @@ -11,6 +11,8 @@ set(CLIFP_SOURCE kernel/errorstatus.cpp command/command.h command/command.cpp + command/c-download.h + command/c-download.cpp command/c-link.h command/c-link.cpp command/c-play.h diff --git a/app/src/command/c-download.cpp b/app/src/command/c-download.cpp new file mode 100644 index 0000000..9ecf18c --- /dev/null +++ b/app/src/command/c-download.cpp @@ -0,0 +1,114 @@ +// Unit Include +#include "c-download.h" + +// Project Includes +#include "task/t-download.h" +#include "task/t-generic.h" + +//=============================================================================================================== +// CDownloadError +//=============================================================================================================== + +//-Constructor------------------------------------------------------------- +//Private: +CDownloadError::CDownloadError(Type t, const QString& s) : + mType(t), + mSpecific(s) +{} + +//-Instance Functions------------------------------------------------------------- +//Public: +bool CDownloadError::isValid() const { return mType != NoError; } +QString CDownloadError::specific() const { return mSpecific; } +CDownloadError::Type CDownloadError::type() const { return mType; } + +//Private: +Qx::Severity CDownloadError::deriveSeverity() const { return Qx::Critical; } +quint32 CDownloadError::deriveValue() const { return mType; } +QString CDownloadError::derivePrimary() const { return ERR_STRINGS.value(mType); } +QString CDownloadError::deriveSecondary() const { return mSpecific; } + +//=============================================================================================================== +// CDownload +//=============================================================================================================== + +//-Constructor------------------------------------------------------------- +//Public: +CDownload::CDownload(Core& coreRef) : Command(coreRef) {} + +//-Instance Functions------------------------------------------------------------- +//Protected: +QList CDownload::options() { return CL_OPTIONS_SPECIFIC + Command::options(); } +QSet CDownload::requiredOptions() { return CL_OPTIONS_REQUIRED + Command::requiredOptions(); } +QString CDownload::name() { return NAME; } + +Qx::Error CDownload::perform() +{ + QString playlistName = mParser.value(CL_OPTION_PLAYLIST).trimmed(); + mCore.setStatus(STATUS_DOWNLOAD, playlistName); + + Fp::Db* db = mCore.fpInstall().database(); + Fp::PlaylistManager* pm = mCore.fpInstall().playlistManager(); + if(Qx::Error pError = pm->populate(); pError.isValid()) + return pError; + + // Find playlist + QList playlists = pm->playlists(); + auto pItr = std::find_if(playlists.cbegin(), playlists.cend(), [&playlistName](auto p){ + return p.title() == playlistName || p.title().trimmed() == playlistName; // Some playlists have spaces for sorting purposes + }); + + if(pItr == playlists.cend()) + { + CDownloadError err(CDownloadError::InvalidPlaylist, playlistName); + mCore.postError(NAME, err); + return err; + } + mCore.logEvent(NAME, LOG_PLAYLIST_MATCH.arg(pItr->id().toString(QUuid::WithoutBraces))); + + // Queue downloads for each game + TDownload* downloadTask = new TDownload(&mCore); + downloadTask->setStage(Task::Stage::Primary); + downloadTask->setDescription(u"playlist data packs"_s); + QList dataIds; + + for(const auto& pg : pItr->playlistGames()) + { + // Get data + Fp::GameData gameData; + if(Fp::DbError gdErr = db->getGameData(gameData, pg.gameId()); gdErr.isValid()) + { + mCore.postError(NAME, gdErr); + return gdErr; + } + + if(gameData.isNull()) + { + mCore.logEvent(NAME, LOG_NON_DATAPACK.arg(pg.gameId().toString(QUuid::WithoutBraces))); + continue; + } + + // Queue download + QString filename = gameData.path(); + downloadTask->addFile({.target = mCore.datapackUrl(filename), .dest = mCore.datapackPath(filename), .checksum = gameData.sha256()}); + + // Note data id + dataIds.append(gameData.id()); + } + + // Enqueue download task + mCore.enqueueSingleTask(downloadTask); + + // Enqueue onDiskState update task + Core* corePtr = &mCore; // Safe, will outlive task + TGeneric* onDiskUpdateTask = new TGeneric(corePtr); + onDiskUpdateTask->setStage(Task::Stage::Primary); + onDiskUpdateTask->setDescription(u"Update GameData onDisk state."_s); + onDiskUpdateTask->setAction([dataIds, corePtr]{ + return corePtr->fpInstall().database()->updateGameDataOnDiskState(dataIds, true); + }); + mCore.enqueueSingleTask(onDiskUpdateTask); + + // Return success + return CDownloadError(); +} diff --git a/app/src/command/c-download.h b/app/src/command/c-download.h new file mode 100644 index 0000000..1302a1c --- /dev/null +++ b/app/src/command/c-download.h @@ -0,0 +1,89 @@ +#ifndef CDOWNLOAD_H +#define CDOWNLOAD_H + +// Qx Includes +#include + +// Project Includes +#include "command/command.h" + +class QX_ERROR_TYPE(CDownloadError, "CDownloadError", 1217) +{ + friend class CDownload; + //-Class Enums------------------------------------------------------------- +public: + enum Type + { + NoError, + InvalidPlaylist + }; + + //-Class Variables------------------------------------------------------------- +private: + static inline const QHash ERR_STRINGS{ + {NoError, u""_s}, + {InvalidPlaylist, u""_s} + }; + + //-Instance Variables------------------------------------------------------------- +private: + Type mType; + QString mSpecific; + + //-Constructor------------------------------------------------------------- +private: + CDownloadError(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 CDownload : public Command +{ + //-Class Variables------------------------------------------------------------------------------------------------------ +private: + // Status + static inline const QString STATUS_DOWNLOAD = u"Downloading data packs"_s; + + // Logging + static inline const QString LOG_PLAYLIST_MATCH = u"Playlist matches ID: %1"_s; + static inline const QString LOG_NON_DATAPACK = u"Game %1 does not use a data pack."_s; + + // Command line option strings + static inline const QString CL_OPT_PLAYLIST_S_NAME = u"p"_s; + static inline const QString CL_OPT_PLAYLIST_L_NAME = u"playlist"_s; + static inline const QString CL_OPT_PLAYLIST_DESC = u"Name of the playlist to download games for."_s; + + // Command line options + static inline const QCommandLineOption CL_OPTION_PLAYLIST{{CL_OPT_PLAYLIST_S_NAME, CL_OPT_PLAYLIST_L_NAME}, CL_OPT_PLAYLIST_DESC, u"playlist"_s}; // Takes value + static inline const QList CL_OPTIONS_SPECIFIC{&CL_OPTION_PLAYLIST}; + static inline const QSet CL_OPTIONS_REQUIRED{&CL_OPTION_PLAYLIST}; + +public: + // Meta + static inline const QString NAME = u"download"_s; + static inline const QString DESCRIPTION = u"Download game data packs in bulk"_s; + + //-Constructor---------------------------------------------------------------------------------------------------------- +public: + CDownload(Core& coreRef); + + //-Instance Functions------------------------------------------------------------------------------------------------------ +protected: + QList options() override; + QSet requiredOptions() override; + QString name() override; + Qx::Error perform() override; +}; +REGISTER_COMMAND(CDownload::NAME, CDownload, CDownload::DESCRIPTION); + +#endif // CDOWNLOAD_H diff --git a/app/src/kernel/core.cpp b/app/src/kernel/core.cpp index 960b2b3..00f451d 100644 --- a/app/src/kernel/core.cpp +++ b/app/src/kernel/core.cpp @@ -507,6 +507,56 @@ Qx::Error Core::findAddAppIdFromName(QUuid& returnBuffer, QUuid parent, QString return searchAndFilterEntity(returnBuffer, name, exactName, parent); } +QString Core::datapackPath(const QString& packFilename) const +{ + static QDir folder(mFlashpointInstall->fullPath() + '/' + mFlashpointInstall->preferences().dataPacksFolderPath); + return folder.absoluteFilePath(packFilename); +} + +QUrl Core::datapackUrl(const QString& packFilename) const +{ + static QString urlBase = [&packFilename, this]{ + /* TODO: This is ugly, it would be ideal to handle this more gracefully as a regular error as it once was, but + * this function design doesn't really work with that and for now this source can be relied upon. + */ + auto sources = mFlashpointInstall->preferences().gameDataSources; + if(!sources.contains(u"Flashpoint Project"_s)) + qCritical("Expected game data source 'Flashpoint Project' missing!"); + + return sources.value(u"Flashpoint Project"_s).arguments.value(0); + }(); + + return urlBase + '/' + packFilename; +} + +bool Core::datapackIsPresent(const Fp::GameData& gameData) +{ + // Get current file checksum if it exists + QString packFilename = gameData.path(); + QFile packFile(datapackPath(packFilename)); + bool checksumMatches = false; + + if(!gameData.presentOnDisk() || !packFile.exists()) + return false; + + // Checking the sum in addition to the flag is somewhat overkill, but may help in situations + // where the flag is set but the datapack's contents have changed + Qx::IoOpReport checksumReport = Qx::fileMatchesChecksum(checksumMatches, packFile, gameData.sha256(), QCryptographicHash::Sha256); + if(checksumReport.isFailure()) + { + logError(NAME, checksumReport); + return false; + } + + if(!checksumMatches) + { + postError(NAME, CoreError(CoreError::DataPackSumMismatch, packFilename, Qx::Warning)); + return false; + } + + return true; +} + bool Core::blockNewInstances() { bool b = Qx::enforceSingleInstance(SINGLE_INSTANCE_ID); @@ -697,49 +747,20 @@ Qx::Error Core::enqueueDataPackTasks(const Fp::GameData& gameData) { logEvent(NAME, LOG_EVENT_ENQ_DATA_PACK); - // Extract relevant data - QString packDestFolderPath = mFlashpointInstall->fullPath() + '/' + mFlashpointInstall->preferences().dataPacksFolderPath; - QString packFileName = gameData.path(); - QString packSha256 = gameData.sha256(); - QString packParameters = gameData.parameters(); - QFile packFile(packDestFolderPath + '/' + packFileName); + QString packFilename = gameData.path(); + QString packPath = datapackPath(packFilename); - // Get current file checksum if it exists - bool checksumMatches = false; - - if(gameData.presentOnDisk() && packFile.exists()) + // Enqueue pack download if it's not available + if(!datapackIsPresent(gameData)) { - // Checking the sum in addition to the flag is somewhat overkill, but may help in situations - // where the flag is set but the datapack's contents have changed - Qx::IoOpReport checksumReport = Qx::fileMatchesChecksum(checksumMatches, packFile, packSha256, QCryptographicHash::Sha256); - if(checksumReport.isFailure()) - logError(NAME, checksumReport); - - if(checksumMatches) - logEvent(NAME, LOG_EVENT_DATA_PACK_FOUND); - else - postError(NAME, CoreError(CoreError::DataPackSumMismatch, packFileName, Qx::Warning)); - } - else logEvent(NAME, LOG_EVENT_DATA_PACK_MISS); - // Enqueue pack download if it doesn't exist or is different than expected - if(!packFile.exists() || !checksumMatches) - { - if(!mFlashpointInstall->preferences().gameDataSources.contains(u"Flashpoint Project"_s)) - { - CoreError err(CoreError::DataPackSourceMissing, u"Flashpoint Project"_s); - postError(NAME, err); - return err; - } - - Fp::GameDataSource gameSource = mFlashpointInstall->preferences().gameDataSources.value(u"Flashpoint Project"_s); - QString gameSourceBase = gameSource.arguments.value(0); + QUrl packUrl = datapackUrl(packFilename); TDownload* downloadTask = new TDownload(this); downloadTask->setStage(Task::Stage::Auxiliary); - downloadTask->setDescription(u"data pack "_s + QFileInfo(packFile).fileName()); - downloadTask->addFile({.target = gameSourceBase + '/' + packFileName, .dest = packFile.fileName(), .checksum = packSha256}); + downloadTask->setDescription(u"data pack "_s + packFilename); + downloadTask->addFile({.target = packUrl, .dest = packPath, .checksum = gameData.sha256()}); mTaskQueue.push(downloadTask); logTask(NAME, downloadTask); @@ -751,21 +772,24 @@ Qx::Error Core::enqueueDataPackTasks(const Fp::GameData& gameData) onDiskUpdateTask->setStage(Task::Stage::Auxiliary); onDiskUpdateTask->setDescription(u"Update GameData onDisk state."_s); onDiskUpdateTask->setAction([gameDataId, this]{ - return mFlashpointInstall->database()->updateGameDataOnDiskState(gameDataId, true); + return mFlashpointInstall->database()->updateGameDataOnDiskState({gameDataId}, true); }); mTaskQueue.push(onDiskUpdateTask); logTask(NAME, onDiskUpdateTask); } + else + logEvent(NAME, LOG_EVENT_DATA_PACK_FOUND); // Enqueue pack mount or extract - if(packParameters.contains(u"-extract"_s)) + + if(gameData.parameters().contains(u"-extract"_s)) { logEvent(NAME, LOG_EVENT_DATA_PACK_NEEDS_EXTRACT); TExtract* extractTask = new TExtract(this); extractTask->setStage(Task::Stage::Auxiliary); - extractTask->setPackPath(packDestFolderPath + '/' + packFileName); + extractTask->setPackPath(packPath); extractTask->setPathInPack(u"content"_s); extractTask->setDestinationPath(mFlashpointInstall->preferences().htdocsFolderPath); @@ -780,7 +804,7 @@ Qx::Error Core::enqueueDataPackTasks(const Fp::GameData& gameData) TMount* mountTask = new TMount(this); mountTask->setStage(Task::Stage::Auxiliary); mountTask->setTitleId(gameData.gameId()); - mountTask->setPath(packDestFolderPath + '/' + packFileName); + mountTask->setPath(packPath); mountTask->setDaemon(mFlashpointInstall->outfittedDaemon()); mTaskQueue.push(mountTask); diff --git a/app/src/kernel/core.h b/app/src/kernel/core.h index f134043..1a8edb5 100644 --- a/app/src/kernel/core.h +++ b/app/src/kernel/core.h @@ -289,10 +289,13 @@ class Core : public QObject Qx::Error initialize(QStringList& commandLine); void attachFlashpoint(std::unique_ptr flashpointInstall); - // Helper + // Helper (TODO: Move some of these, especially the lower ones, to libfp) QString resolveTrueAppPath(const QString& appPath, const QString& platform); Qx::Error findGameIdFromTitle(QUuid& returnBuffer, QString title, bool exactTitle = true); Qx::Error findAddAppIdFromName(QUuid& returnBuffer, QUuid parent, QString name, bool exactName = true); + QString datapackPath(const QString& packFilename) const; + QUrl datapackUrl(const QString& packFilename) const; + bool datapackIsPresent(const Fp::GameData& gameData); // Common bool blockNewInstances(); diff --git a/app/src/task/t-download.cpp b/app/src/task/t-download.cpp index a2a40b5..a5087ae 100644 --- a/app/src/task/t-download.cpp +++ b/app/src/task/t-download.cpp @@ -37,9 +37,6 @@ TDownload::TDownload(QObject* parent) : mDownloadManager.setOverwrite(true); mDownloadManager.setVerificationMethod(QCryptographicHash::Sha256); - // Since this is only for one download, the size will be adjusted to the correct total as soon as the download starts - mDownloadManager.setSkipEnumeration(true); - // Download event handlers connect(&mDownloadManager, &Qx::AsyncDownloadManager::sslErrors, this, [this](Qx::Error errorMsg, bool* ignore) { int choice; @@ -80,7 +77,7 @@ QStringList TDownload::members() const } if(mFiles.size() > 10) files += FILE_DOWNLOAD_ELIDE.arg(mFiles.size() - 10) + '\n'; - files += u"}\n"_s; + files += u"}"_s; ml.append(files); ml.append(u".description() = \""_s + mDescription + u"\""_s); @@ -101,7 +98,7 @@ void TDownload::perform() // Log/label string QString label = LOG_EVENT_DOWNLOAD.arg(mDescription); - emit eventOccurred(NAME, LOG_EVENT_DOWNLOAD); + emit eventOccurred(NAME, label); // Start download emit longTaskStarted(label); From ff05293c361bbb3f011722c2741d9c8492f8c6f0 Mon Sep 17 00:00:00 2001 From: Christian Heimlich Date: Thu, 9 Nov 2023 14:53:01 -0500 Subject: [PATCH 05/26] Use Command::requiredOptions() in CRun Don't need to check for required options manually anymore --- app/src/command/c-run.cpp | 9 +-------- app/src/command/c-run.h | 8 ++++---- 2 files changed, 5 insertions(+), 12 deletions(-) diff --git a/app/src/command/c-run.cpp b/app/src/command/c-run.cpp index c82dc60..5d37fa5 100644 --- a/app/src/command/c-run.cpp +++ b/app/src/command/c-run.cpp @@ -38,18 +38,11 @@ CRun::CRun(Core& coreRef) : Command(coreRef) {} //-Instance Functions------------------------------------------------------------- //Protected: QList CRun::options() { return CL_OPTIONS_SPECIFIC + Command::options(); } +QSet CRun::requiredOptions() { return CL_OPTIONS_REQUIRED + Command::requiredOptions(); } QString CRun::name() { return NAME; } Qx::Error CRun::perform() { - // Make sure that at least an app was provided - if(!mParser.isSet(CL_OPTION_PARAM)) - { - CRunError err(CRunError::MissingApp); - mCore.logError(NAME, err); - return err; - } - // Enqueue startup tasks if(Qx::Error ee = mCore.enqueueStartupTasks(); ee.isValid()) return ee; diff --git a/app/src/command/c-run.h b/app/src/command/c-run.h index 5c65a80..4cfba5c 100644 --- a/app/src/command/c-run.h +++ b/app/src/command/c-run.h @@ -14,15 +14,13 @@ class QX_ERROR_TYPE(CRunError, "CRunError", 1215) public: enum Type { - NoError = 0, - MissingApp = 1 + NoError = 0 }; //-Class Variables------------------------------------------------------------- private: static inline const QHash ERR_STRINGS{ - {NoError, u""_s}, - {MissingApp, u"No application to run was provided."_s} + {NoError, u""_s} }; //-Instance Variables------------------------------------------------------------- @@ -67,6 +65,7 @@ class CRun : public Command static inline const QCommandLineOption CL_OPTION_APP{{CL_OPT_APP_S_NAME, CL_OPT_APP_L_NAME}, CL_OPT_APP_DESC, u"application"_s}; // Takes value static inline const QCommandLineOption CL_OPTION_PARAM{{CL_OPT_PARAM_S_NAME, CL_OPT_PARAM_L_NAME}, CL_OPT_PARAM_DESC, u"parameters"_s}; // Takes value static inline const QList CL_OPTIONS_SPECIFIC{&CL_OPTION_APP, &CL_OPTION_PARAM}; + static inline const QSet CL_OPTIONS_REQUIRED{&CL_OPTION_APP}; public: // Meta @@ -80,6 +79,7 @@ class CRun : public Command //-Instance Functions------------------------------------------------------------------------------------------------------ protected: QList options() override; + QSet requiredOptions() override; QString name() override; Qx::Error perform() override; }; From 5167993e0b4983a194ee85e443b1aad4e3308a28 Mon Sep 17 00:00:00 2001 From: Christian Heimlich Date: Thu, 9 Nov 2023 17:21:12 -0500 Subject: [PATCH 06/26] Tweak Qt error message string --- app/src/kernel/core.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/kernel/core.h b/app/src/kernel/core.h index 1a8edb5..83fef90 100644 --- a/app/src/kernel/core.h +++ b/app/src/kernel/core.h @@ -49,7 +49,7 @@ class QX_ERROR_TYPE(CoreError, "CoreError", 1200) private: static inline const QHash ERR_STRINGS{ {NoError, u""_s}, - {InternalError, u"Internal system error."_s}, + {InternalError, u"Internal error."_s}, {InvalidOptions, u"Invalid global options provided."_s}, {TitleNotFound, u"Could not find the title in the Flashpoint database."_s}, {TooManyResults, u"More results than can be presented were returned in a search."_s}, From b57d94f48c7a6df66de4c6dd0b8b7c3f3b97df9a Mon Sep 17 00:00:00 2001 From: Christian Heimlich Date: Thu, 9 Nov 2023 17:55:25 -0500 Subject: [PATCH 07/26] Bump Qx off dead commit --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 3ffa92a..33c8080 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -72,7 +72,7 @@ endif() include(OB/FetchQx) ob_fetch_qx( - REF "7acb9d0f3e65de2f0a6e91c979b1216e6be7de7d" + REF "2bbf83e59b0aadc3891440193892be1a2a19c00e" COMPONENTS ${CLIFP_QX_COMPONENTS} ) From 94ea0fa1e420ab1157e4f6d9e408d336cbed575a Mon Sep 17 00:00:00 2001 From: Christian Heimlich Date: Fri, 10 Nov 2023 12:00:27 -0500 Subject: [PATCH 08/26] Update libfp, use Fp::Toolkit --- CMakeLists.txt | 2 +- app/src/command/c-download.cpp | 4 +- app/src/command/c-play.cpp | 20 +++--- app/src/command/c-run.cpp | 4 +- app/src/kernel/core.cpp | 108 ++++++--------------------------- app/src/kernel/core.h | 15 ++--- 6 files changed, 37 insertions(+), 116 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 33c8080..4648ea9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -79,7 +79,7 @@ ob_fetch_qx( # Fetch libfp (build and import from source) include(OB/Fetchlibfp) -ob_fetch_libfp("d27276305defe2fbd98946b23ec9d135ce3ae1bd") +ob_fetch_libfp("e8dccecbb03d72abfddfd3c8c5582506a0bd4048") # Fetch QI-QMP (build and import from source) include(OB/FetchQI-QMP) diff --git a/app/src/command/c-download.cpp b/app/src/command/c-download.cpp index 9ecf18c..83d8eb3 100644 --- a/app/src/command/c-download.cpp +++ b/app/src/command/c-download.cpp @@ -72,6 +72,7 @@ Qx::Error CDownload::perform() downloadTask->setDescription(u"playlist data packs"_s); QList dataIds; + const Fp::Toolkit* tk = mCore.fpInstall().toolkit(); for(const auto& pg : pItr->playlistGames()) { // Get data @@ -89,8 +90,7 @@ Qx::Error CDownload::perform() } // Queue download - QString filename = gameData.path(); - downloadTask->addFile({.target = mCore.datapackUrl(filename), .dest = mCore.datapackPath(filename), .checksum = gameData.sha256()}); + downloadTask->addFile({.target = tk->datapackUrl(gameData), .dest = tk->datapackPath(gameData), .checksum = gameData.sha256()}); // Note data id dataIds.append(gameData.id()); diff --git a/app/src/command/c-play.cpp b/app/src/command/c-play.cpp index 092dca9..4478cef 100644 --- a/app/src/command/c-play.cpp +++ b/app/src/command/c-play.cpp @@ -231,14 +231,14 @@ Qx::Error CPlay::enqueueAdditionalApp(const Fp::AddApp& addApp, const QString& p } else { - QString addAppPath = mCore.resolveTrueAppPath(addApp.appPath(), platform); - QFileInfo fulladdAppPathInfo(mCore.fpInstall().fullPath() + '/' + addAppPath); + QString addAppPath = mCore.resolveFullAppPath(addApp.appPath(), platform); + QFileInfo addAppPathInfo(addAppPath); TExec* addAppTask = new TExec(&mCore); addAppTask->setIdentifier(addApp.name()); addAppTask->setStage(taskStage); - addAppTask->setExecutable(fulladdAppPathInfo.canonicalFilePath()); - addAppTask->setDirectory(fulladdAppPathInfo.absoluteDir()); + addAppTask->setExecutable(addAppPathInfo.canonicalFilePath()); + addAppTask->setDirectory(addAppPathInfo.absoluteDir()); addAppTask->setParameters(addApp.launchCommand()); addAppTask->setEnvironment(mCore.childTitleProcessEnvironment()); addAppTask->setProcessType(addApp.isWaitExit() || taskStage == Task::Stage::Primary ? TExec::ProcessType::Blocking : TExec::ProcessType::Deferred); @@ -247,7 +247,7 @@ Qx::Error CPlay::enqueueAdditionalApp(const Fp::AddApp& addApp, const QString& p #ifdef _WIN32 // Add wait task if required - if(Qx::Error ee = mCore.conditionallyEnqueueBideTask(fulladdAppPathInfo); ee.isValid()) + if(Qx::Error ee = mCore.conditionallyEnqueueBideTask(addAppPathInfo); ee.isValid()) return ee; #endif } @@ -258,15 +258,15 @@ Qx::Error CPlay::enqueueAdditionalApp(const Fp::AddApp& addApp, const QString& p Qx::Error CPlay::enqueueGame(const Fp::Game& game, const Fp::GameData& gameData, Task::Stage taskStage) { - QString gamePath = mCore.resolveTrueAppPath(!gameData.isNull() ? gameData.appPath() : game.appPath(), + QString gamePath = mCore.resolveFullAppPath(!gameData.isNull() ? gameData.appPath() : game.appPath(), game.platformName()); - QFileInfo fullGamePathInfo(mCore.fpInstall().fullPath() + '/' + gamePath); + QFileInfo gamePathInfo(gamePath); TExec* gameTask = new TExec(&mCore); gameTask->setIdentifier(game.title()); gameTask->setStage(taskStage); - gameTask->setExecutable(fullGamePathInfo.canonicalFilePath()); - gameTask->setDirectory(fullGamePathInfo.absoluteDir()); + gameTask->setExecutable(gamePathInfo.canonicalFilePath()); + gameTask->setDirectory(gamePathInfo.absoluteDir()); gameTask->setParameters(!gameData.isNull() ? gameData.launchCommand() : game.launchCommand()); gameTask->setEnvironment(mCore.childTitleProcessEnvironment()); gameTask->setProcessType(TExec::ProcessType::Blocking); @@ -276,7 +276,7 @@ Qx::Error CPlay::enqueueGame(const Fp::Game& game, const Fp::GameData& gameData, #ifdef _WIN32 // Add wait task if required - if(Qx::Error ee = mCore.conditionallyEnqueueBideTask(fullGamePathInfo); ee.isValid()) + if(Qx::Error ee = mCore.conditionallyEnqueueBideTask(gamePathInfo); ee.isValid()) return ee; #endif diff --git a/app/src/command/c-run.cpp b/app/src/command/c-run.cpp index 5d37fa5..139d09f 100644 --- a/app/src/command/c-run.cpp +++ b/app/src/command/c-run.cpp @@ -47,8 +47,8 @@ Qx::Error CRun::perform() if(Qx::Error ee = mCore.enqueueStartupTasks(); ee.isValid()) return ee; - QString inputPath = mCore.resolveTrueAppPath(mParser.value(CL_OPTION_APP), u""_s); // No way of knowing platform - QFileInfo inputInfo = QFileInfo(mCore.fpInstall().fullPath() + '/' + inputPath); + QString inputPath = mCore.resolveFullAppPath(mParser.value(CL_OPTION_APP), u""_s); // No way of knowing platform + QFileInfo inputInfo = QFileInfo(inputPath); TExec* runTask = new TExec(&mCore); runTask->setIdentifier(NAME + u" program"_s); diff --git a/app/src/kernel/core.cpp b/app/src/kernel/core.cpp index 00f451d..6c296b0 100644 --- a/app/src/kernel/core.cpp +++ b/app/src/kernel/core.cpp @@ -454,45 +454,19 @@ void Core::attachFlashpoint(std::unique_ptr flashpointInstall) } // TODO: Might make sense to make this a function in libfp -QString Core::resolveTrueAppPath(const QString& appPath, const QString& platform) +QString Core::resolveFullAppPath(const QString& appPath, const QString& platform) { - // If appPath is absolute, convert it to relative temporarily - QString transformedPath = appPath; - - QString fpPath = mFlashpointInstall->fullPath(); - bool isFpAbsolute = transformedPath.startsWith(fpPath); - if(isFpAbsolute) - { - // Remove FP root and separator - transformedPath.remove(fpPath); - if(!transformedPath.isEmpty() && (transformedPath.front() == '/' || transformedPath.front() == '\\')) - transformedPath = transformedPath.mid(1); - } - - /* TODO: If this is made into a libfp function, isolate this part of it so it stays here, - * or add a map argument to the libfp function that allows for passing custom "swaps" - * and then call it with one for the following switch. - * - * CLIFp doesn't support the Launcher's built in browser (obviously), so manually - * override it with Basilisk. Basilisk was removed in FP11 but the app path overrides - * contains an entry for it that's appropriate on both platforms. - */ - if(transformedPath == u":browser-mode:"_s) - transformedPath = u"FPSoftware\\Basilisk-Portable\\Basilisk-Portable.exe"_s; - - // Resolve both swap types - transformedPath = mFlashpointInstall->resolveExecSwaps(transformedPath, platform); - transformedPath = mFlashpointInstall->resolveAppPathOverrides(transformedPath); + static const QHash clifpOverrides{ + {u":browser-mode:"_s, u"FPSoftware\\Basilisk-Portable\\Basilisk-Portable.exe"_s} + }; - // Rebuild full path if applicable - if(isFpAbsolute) - transformedPath = fpPath + '/' + transformedPath; + const Fp::Toolkit* tk = mFlashpointInstall->toolkit(); - if(transformedPath != appPath) - logEvent(NAME, LOG_EVENT_APP_PATH_ALT.arg(appPath, transformedPath)); + QString swapPath = appPath; + if(tk->resolveTrueAppPath(swapPath, platform, clifpOverrides)) + logEvent(NAME, LOG_EVENT_APP_PATH_ALT.arg(appPath, swapPath)); - // Convert Windows separators to universal '/' - return transformedPath.replace('\\','/'); + return mFlashpointInstall->fullPath() + '/' + swapPath; } Qx::Error Core::findGameIdFromTitle(QUuid& returnBuffer, QString title, bool exactTitle) @@ -507,56 +481,6 @@ Qx::Error Core::findAddAppIdFromName(QUuid& returnBuffer, QUuid parent, QString return searchAndFilterEntity(returnBuffer, name, exactName, parent); } -QString Core::datapackPath(const QString& packFilename) const -{ - static QDir folder(mFlashpointInstall->fullPath() + '/' + mFlashpointInstall->preferences().dataPacksFolderPath); - return folder.absoluteFilePath(packFilename); -} - -QUrl Core::datapackUrl(const QString& packFilename) const -{ - static QString urlBase = [&packFilename, this]{ - /* TODO: This is ugly, it would be ideal to handle this more gracefully as a regular error as it once was, but - * this function design doesn't really work with that and for now this source can be relied upon. - */ - auto sources = mFlashpointInstall->preferences().gameDataSources; - if(!sources.contains(u"Flashpoint Project"_s)) - qCritical("Expected game data source 'Flashpoint Project' missing!"); - - return sources.value(u"Flashpoint Project"_s).arguments.value(0); - }(); - - return urlBase + '/' + packFilename; -} - -bool Core::datapackIsPresent(const Fp::GameData& gameData) -{ - // Get current file checksum if it exists - QString packFilename = gameData.path(); - QFile packFile(datapackPath(packFilename)); - bool checksumMatches = false; - - if(!gameData.presentOnDisk() || !packFile.exists()) - return false; - - // Checking the sum in addition to the flag is somewhat overkill, but may help in situations - // where the flag is set but the datapack's contents have changed - Qx::IoOpReport checksumReport = Qx::fileMatchesChecksum(checksumMatches, packFile, gameData.sha256(), QCryptographicHash::Sha256); - if(checksumReport.isFailure()) - { - logError(NAME, checksumReport); - return false; - } - - if(!checksumMatches) - { - postError(NAME, CoreError(CoreError::DataPackSumMismatch, packFilename, Qx::Warning)); - return false; - } - - return true; -} - bool Core::blockNewInstances() { bool b = Qx::enforceSingleInstance(SINGLE_INSTANCE_ID); @@ -717,9 +641,11 @@ void Core::enqueueShutdownTasks() #ifdef _WIN32 Qx::Error Core::conditionallyEnqueueBideTask(QFileInfo precedingAppInfo) { + const Fp::Toolkit* tk = mFlashpointInstall->toolkit(); + // Add wait for apps that involve secure player bool involvesSecurePlayer; - Qx::Error securePlayerCheckError = Fp::Install::appInvolvesSecurePlayer(involvesSecurePlayer, precedingAppInfo); + Qx::Error securePlayerCheckError = tk->appInvolvesSecurePlayer(involvesSecurePlayer, precedingAppInfo); if(securePlayerCheckError.isValid()) { postError(NAME, securePlayerCheckError); @@ -730,7 +656,7 @@ Qx::Error Core::conditionallyEnqueueBideTask(QFileInfo precedingAppInfo) { TBideProcess* waitTask = new TBideProcess(this); waitTask->setStage(Task::Stage::Auxiliary); - waitTask->setProcessName(Fp::Install::SECURE_PLAYER_INFO.fileName()); + waitTask->setProcessName(tk->SECURE_PLAYER_INFO.fileName()); mTaskQueue.push(waitTask); logTask(NAME, waitTask); @@ -747,15 +673,17 @@ Qx::Error Core::enqueueDataPackTasks(const Fp::GameData& gameData) { logEvent(NAME, LOG_EVENT_ENQ_DATA_PACK); + const Fp::Toolkit* tk = mFlashpointInstall->toolkit(); + QString packFilename = gameData.path(); - QString packPath = datapackPath(packFilename); + QString packPath = tk->datapackPath(gameData); // Enqueue pack download if it's not available - if(!datapackIsPresent(gameData)) + if(!tk->datapackIsPresent(gameData)) { logEvent(NAME, LOG_EVENT_DATA_PACK_MISS); - QUrl packUrl = datapackUrl(packFilename); + QUrl packUrl = tk->datapackUrl(gameData); TDownload* downloadTask = new TDownload(this); downloadTask->setStage(Task::Stage::Auxiliary); diff --git a/app/src/kernel/core.h b/app/src/kernel/core.h index 83fef90..e155f0f 100644 --- a/app/src/kernel/core.h +++ b/app/src/kernel/core.h @@ -40,9 +40,7 @@ class QX_ERROR_TYPE(CoreError, "CoreError", 1200) InvalidOptions, TitleNotFound, TooManyResults, - ConfiguredServerMissing, - DataPackSumMismatch, - DataPackSourceMissing + ConfiguredServerMissing }; //-Class Variables------------------------------------------------------------- @@ -53,9 +51,7 @@ class QX_ERROR_TYPE(CoreError, "CoreError", 1200) {InvalidOptions, u"Invalid global options provided."_s}, {TitleNotFound, u"Could not find the title in the Flashpoint database."_s}, {TooManyResults, u"More results than can be presented were returned in a search."_s}, - {ConfiguredServerMissing, u"The server specified in the Flashpoint config was not found within the Flashpoint services store."_s}, - {DataPackSumMismatch, u"The existing Data Pack of the selected title does not contain the data expected. It will be re-downloaded."_s}, - {DataPackSourceMissing, u"The expected primary data pack source was missing."_s} + {ConfiguredServerMissing, u"The server specified in the Flashpoint config was not found within the Flashpoint services store."_s} }; //-Instance Variables------------------------------------------------------------- @@ -289,13 +285,10 @@ class Core : public QObject Qx::Error initialize(QStringList& commandLine); void attachFlashpoint(std::unique_ptr flashpointInstall); - // Helper (TODO: Move some of these, especially the lower ones, to libfp) - QString resolveTrueAppPath(const QString& appPath, const QString& platform); + // Helper (TODO: Move some of these to libfp Toolkit) + QString resolveFullAppPath(const QString& appPath, const QString& platform); Qx::Error findGameIdFromTitle(QUuid& returnBuffer, QString title, bool exactTitle = true); Qx::Error findAddAppIdFromName(QUuid& returnBuffer, QUuid parent, QString name, bool exactName = true); - QString datapackPath(const QString& packFilename) const; - QUrl datapackUrl(const QString& packFilename) const; - bool datapackIsPresent(const Fp::GameData& gameData); // Common bool blockNewInstances(); From 623c71d02f5564bd6c291ca3a8210012897227fd Mon Sep 17 00:00:00 2001 From: Christian Heimlich Date: Fri, 10 Nov 2023 12:07:03 -0500 Subject: [PATCH 09/26] CDownload check if pack exists before queing. --- app/src/command/c-download.cpp | 13 +++++++++++-- app/src/command/c-download.h | 5 +++-- app/src/task/t-download.cpp | 2 ++ app/src/task/t-download.h | 2 ++ 4 files changed, 18 insertions(+), 4 deletions(-) diff --git a/app/src/command/c-download.cpp b/app/src/command/c-download.cpp index 83d8eb3..0702f06 100644 --- a/app/src/command/c-download.cpp +++ b/app/src/command/c-download.cpp @@ -64,7 +64,7 @@ Qx::Error CDownload::perform() mCore.postError(NAME, err); return err; } - mCore.logEvent(NAME, LOG_PLAYLIST_MATCH.arg(pItr->id().toString(QUuid::WithoutBraces))); + mCore.logEvent(NAME, LOG_EVENT_PLAYLIST_MATCH.arg(pItr->id().toString(QUuid::WithoutBraces))); // Queue downloads for each game TDownload* downloadTask = new TDownload(&mCore); @@ -85,10 +85,13 @@ Qx::Error CDownload::perform() if(gameData.isNull()) { - mCore.logEvent(NAME, LOG_NON_DATAPACK.arg(pg.gameId().toString(QUuid::WithoutBraces))); + mCore.logEvent(NAME, LOG_EVENT_NON_DATAPACK.arg(pg.gameId().toString(QUuid::WithoutBraces))); continue; } + if(tk->datapackIsPresent(gameData)) + continue; + // Queue download downloadTask->addFile({.target = tk->datapackUrl(gameData), .dest = tk->datapackPath(gameData), .checksum = gameData.sha256()}); @@ -96,6 +99,12 @@ Qx::Error CDownload::perform() dataIds.append(gameData.id()); } + if(downloadTask->isEmpty()) + { + mCore.logEvent(NAME, LOG_EVENT_NO_OP); + return CDownloadError(); + } + // Enqueue download task mCore.enqueueSingleTask(downloadTask); diff --git a/app/src/command/c-download.h b/app/src/command/c-download.h index 1302a1c..71f1697 100644 --- a/app/src/command/c-download.h +++ b/app/src/command/c-download.h @@ -55,8 +55,9 @@ class CDownload : public Command static inline const QString STATUS_DOWNLOAD = u"Downloading data packs"_s; // Logging - static inline const QString LOG_PLAYLIST_MATCH = u"Playlist matches ID: %1"_s; - static inline const QString LOG_NON_DATAPACK = u"Game %1 does not use a data pack."_s; + static inline const QString LOG_EVENT_PLAYLIST_MATCH = u"Playlist matches ID: %1"_s; + static inline const QString LOG_EVENT_NON_DATAPACK = u"Game %1 does not use a data pack."_s; + static inline const QString LOG_EVENT_NO_OP = u"No datapacks to download."_s; // Command line option strings static inline const QString CL_OPT_PLAYLIST_S_NAME = u"p"_s; diff --git a/app/src/task/t-download.cpp b/app/src/task/t-download.cpp index a5087ae..fa15350 100644 --- a/app/src/task/t-download.cpp +++ b/app/src/task/t-download.cpp @@ -84,6 +84,8 @@ QStringList TDownload::members() const return ml; } +bool TDownload::isEmpty() const { return mFiles.isEmpty(); } +qsizetype TDownload::fileCount() const { return mFiles.size(); } QList TDownload::files() const { return mFiles; } QString TDownload::description() const { return mDescription; } diff --git a/app/src/task/t-download.h b/app/src/task/t-download.h index 867b6bb..11e8055 100644 --- a/app/src/task/t-download.h +++ b/app/src/task/t-download.h @@ -85,6 +85,8 @@ class TDownload : public Task QString name() const override; QStringList members() const override; + bool isEmpty() const; + qsizetype fileCount() const; QList files() const; QString description() const; From 37172720b0f1b3d54e43cd56cb9b0bab79fddfee Mon Sep 17 00:00:00 2001 From: Christian Heimlich Date: Fri, 10 Nov 2023 12:12:12 -0500 Subject: [PATCH 10/26] Update libfp --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 4648ea9..ab50923 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -79,7 +79,7 @@ ob_fetch_qx( # Fetch libfp (build and import from source) include(OB/Fetchlibfp) -ob_fetch_libfp("e8dccecbb03d72abfddfd3c8c5582506a0bd4048") +ob_fetch_libfp("bc0013caceaef019241c08c0c8abb0d72fece29f") # Fetch QI-QMP (build and import from source) include(OB/FetchQI-QMP) From 729b9598b8ae161e9e3a9917e80b1f7dcf529866 Mon Sep 17 00:00:00 2001 From: Christian Heimlich Date: Fri, 10 Nov 2023 13:54:03 -0500 Subject: [PATCH 11/26] Use parsed data pack parameters --- CMakeLists.txt | 2 +- app/src/kernel/core.cpp | 7 +++++-- app/src/kernel/core.h | 6 ++++-- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index ab50923..45ee53c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -79,7 +79,7 @@ ob_fetch_qx( # Fetch libfp (build and import from source) include(OB/Fetchlibfp) -ob_fetch_libfp("bc0013caceaef019241c08c0c8abb0d72fece29f") +ob_fetch_libfp("253bc528096875c6a3b0134f5762a20ed0b50b57") # Fetch QI-QMP (build and import from source) include(OB/FetchQI-QMP) diff --git a/app/src/kernel/core.cpp b/app/src/kernel/core.cpp index 6c296b0..804d9df 100644 --- a/app/src/kernel/core.cpp +++ b/app/src/kernel/core.cpp @@ -709,9 +709,12 @@ Qx::Error Core::enqueueDataPackTasks(const Fp::GameData& gameData) else logEvent(NAME, LOG_EVENT_DATA_PACK_FOUND); - // Enqueue pack mount or extract + // Handle datapack parameters + Fp::GameDataParameters param = gameData.parameters(); + if(param.hasError()) + postError(NAME, CoreError(CoreError::UnknownDatapackParam, param.errorString(), Qx::Warning)); - if(gameData.parameters().contains(u"-extract"_s)) + if(param.isExtract()) { logEvent(NAME, LOG_EVENT_DATA_PACK_NEEDS_EXTRACT); diff --git a/app/src/kernel/core.h b/app/src/kernel/core.h index e155f0f..6a96bf0 100644 --- a/app/src/kernel/core.h +++ b/app/src/kernel/core.h @@ -40,7 +40,8 @@ class QX_ERROR_TYPE(CoreError, "CoreError", 1200) InvalidOptions, TitleNotFound, TooManyResults, - ConfiguredServerMissing + ConfiguredServerMissing, + UnknownDatapackParam }; //-Class Variables------------------------------------------------------------- @@ -51,7 +52,8 @@ class QX_ERROR_TYPE(CoreError, "CoreError", 1200) {InvalidOptions, u"Invalid global options provided."_s}, {TitleNotFound, u"Could not find the title in the Flashpoint database."_s}, {TooManyResults, u"More results than can be presented were returned in a search."_s}, - {ConfiguredServerMissing, u"The server specified in the Flashpoint config was not found within the Flashpoint services store."_s} + {ConfiguredServerMissing, u"The server specified in the Flashpoint config was not found within the Flashpoint services store."_s}, + {UnknownDatapackParam, u"Unrecognized datapack parameters were present. The game likely won't work correctly."_s}, }; //-Instance Variables------------------------------------------------------------- From b57ea4cf2a60cf1bfdddd2eed78efd7e5b6a1ae1 Mon Sep 17 00:00:00 2001 From: Christian Heimlich Date: Sun, 12 Nov 2023 07:35:19 -0500 Subject: [PATCH 12/26] Update libfp for Entry alias --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 45ee53c..7277d2a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -79,7 +79,7 @@ ob_fetch_qx( # Fetch libfp (build and import from source) include(OB/Fetchlibfp) -ob_fetch_libfp("253bc528096875c6a3b0134f5762a20ed0b50b57") +ob_fetch_libfp("3ff98dd41fb2de72f17c3fee3ebc85d03bb0368c") # Fetch QI-QMP (build and import from source) include(OB/FetchQI-QMP) From a43a2372ee0f9870a3238b42d189aa972b9c3e98 Mon Sep 17 00:00:00 2001 From: Christian Heimlich Date: Sun, 12 Nov 2023 08:11:51 -0500 Subject: [PATCH 13/26] Refactor CPlay; more organized, no need to clear task queue --- app/src/command/c-play.cpp | 220 ++++++++++++++++++------------------- app/src/command/c-play.h | 7 +- app/src/kernel/core.cpp | 1 - app/src/kernel/core.h | 1 - 4 files changed, 107 insertions(+), 122 deletions(-) diff --git a/app/src/command/c-play.cpp b/app/src/command/c-play.cpp index 4478cef..c505c98 100644 --- a/app/src/command/c-play.cpp +++ b/app/src/command/c-play.cpp @@ -69,144 +69,133 @@ Fp::AddApp CPlay::buildAdditionalApp(const Fp::Db::QueryBuffer& addAppResult) //-Instance Functions------------------------------------------------------------- //Private: -Qx::Error CPlay::enqueueAutomaticTasks(bool& wasStandalone, QUuid targetId) +Qx::Error CPlay::handleEntry(const Fp::Game& game) { - // Clear standalone check - wasStandalone = false; + mCore.logEvent(NAME, LOG_EVENT_ID_MATCH_TITLE.arg(game.title())); - mCore.logEvent(NAME, LOG_EVENT_ENQ_AUTO); + Qx::Error sError; + Fp::Db* db = mCore.fpInstall().database(); - // Get entry via ID - Fp::Db* database = mCore.fpInstall().database(); + // Enqueue services + if(sError = mCore.enqueueStartupTasks(); sError.isValid()) + return sError; - std::variant entry; - if(Fp::DbError eErr = database->getEntry(entry, targetId); eErr.isValid()) + // Get game data (if present) + Fp::GameData gameData; + if(Fp::DbError gdErr = db->getGameData(gameData, game.id()); gdErr.isValid()) { - mCore.postError(NAME, eErr); - return eErr; + mCore.postError(NAME, gdErr); + return gdErr; } - // Enqueue if result is additional app - if(std::holds_alternative(entry)) + // Check if entry uses a data pack + if(!gameData.isNull()) { - // Get add app - const Fp::AddApp& addApp = std::get(entry); + mCore.logEvent(NAME, LOG_EVENT_DATA_PACK_TITLE); - mCore.logEvent(NAME, LOG_EVENT_ID_MATCH_ADDAPP.arg(addApp.name(), - addApp.parentId().toString(QUuid::WithoutBraces))); + if(sError = mCore.enqueueDataPackTasks(gameData); sError.isValid()) + return sError; + } - // Clear queue if this entry is a message or extra - if(addApp.appPath() == Fp::Db::Table_Add_App::ENTRY_MESSAGE || addApp.appPath() == Fp::Db::Table_Add_App::ENTRY_EXTRAS) - { - mCore.clearTaskQueue(); - mCore.logEvent(NAME, LOG_EVENT_QUEUE_CLEARED); - wasStandalone = true; - } + // Get game's additional apps + Fp::Db::EntryFilter addAppFilter{.type = Fp::Db::EntryType::AddApp, .parent = game.id()}; - // Check if parent entry uses a data pack - QUuid parentId = addApp.parentId(); - Fp::GameData parentGameData; - if(Fp::DbError gdErr = database->getGameData(parentGameData, parentId); gdErr.isValid()) - { - mCore.postError(NAME, gdErr); - return gdErr; - } - - if(!parentGameData.isNull()) - { - mCore.logEvent(NAME, LOG_EVENT_DATA_PACK_TITLE); + Fp::DbError addAppSearchError; + Fp::Db::QueryBuffer addAppSearchResult; - if(Qx::Error ee = mCore.enqueueDataPackTasks(parentGameData); ee.isValid()) - return ee; - } + addAppSearchError = db->queryEntrys(addAppSearchResult, addAppFilter); + if(addAppSearchError.isValid()) + { + mCore.postError(NAME, addAppSearchError); + return addAppSearchError; + } - // Get parent info to determine platform - Fp::Db::EntryFilter parentFilter{.type = Fp::Db::EntryType::Primary, .id = parentId}; + // Enqueue auto-run before apps + for(int i = 0; i < addAppSearchResult.size; i++) + { + // Go to next record + addAppSearchResult.result.next(); - Fp::Db::QueryBuffer parentResult; + // Build + Fp::AddApp addApp = buildAdditionalApp(addAppSearchResult); - if(Fp::DbError pge = database->queryEntrys(parentResult, parentFilter); pge.isValid()) + // Enqueue if auto-run before + if(addApp.isAutorunBefore()) { - mCore.postError(NAME, pge); - return pge; + mCore.logEvent(NAME, LOG_EVENT_FOUND_AUTORUN.arg(addApp.name())); + + if(sError = enqueueAdditionalApp(addApp, game.platformName(), Task::Stage::Auxiliary); sError.isValid()) + return sError; } + } - // Advance result to only record - parentResult.result.next(); + // Enqueue game + if(sError = enqueueGame(game, gameData, Task::Stage::Primary); sError.isValid()) + return sError; - // Determine platform (don't bother building entire game object since only one value is needed) - QString platformName = parentResult.result.value(Fp::Db::Table_Game::COL_PLATFORM_NAME).toString(); + // Enqueue service shutdown + mCore.enqueueShutdownTasks(); - // Enqueue - mCore.setStatus(STATUS_PLAY, addApp.name()); + return Qx::Error(); +} - if(Qx::Error ee = enqueueAdditionalApp(addApp, platformName, Task::Stage::Primary); ee.isValid()) - return ee; - } - else if(std::holds_alternative(entry)) // Get auto-run additional apps if result is game - { - // Get game - const Fp::Game& game = std::get(entry); +Qx::Error CPlay::handleEntry(const Fp::AddApp& addApp) +{ + mCore.logEvent(NAME, LOG_EVENT_ID_MATCH_ADDAPP.arg(addApp.name(), + addApp.parentId().toString(QUuid::WithoutBraces))); - mCore.logEvent(NAME, LOG_EVENT_ID_MATCH_TITLE.arg(game.title())); + Qx::Error sError; + Fp::Db* db = mCore.fpInstall().database(); - // Get game data (if present) - Fp::GameData gameData; - if(Fp::DbError gdErr = database->getGameData(gameData, targetId); gdErr.isValid()) - { - mCore.postError(NAME, gdErr); - return gdErr; - } + // Enqueue services if needed + bool needsServices = addApp.appPath() != Fp::Db::Table_Add_App::ENTRY_MESSAGE && addApp.appPath() != Fp::Db::Table_Add_App::ENTRY_EXTRAS; + if(needsServices && (sError = mCore.enqueueStartupTasks()).isValid()) + return sError; - // Check if entry uses a data pack - if(!gameData.isNull()) - { - mCore.logEvent(NAME, LOG_EVENT_DATA_PACK_TITLE); + // Check if parent entry uses a data pack + QUuid parentId = addApp.parentId(); + Fp::GameData parentGameData; + if(Fp::DbError gdErr = db->getGameData(parentGameData, parentId); gdErr.isValid()) + { + mCore.postError(NAME, gdErr); + return gdErr; + } - if(Qx::Error ee = mCore.enqueueDataPackTasks(gameData); ee.isValid()) - return ee; - } + if(!parentGameData.isNull()) + { + mCore.logEvent(NAME, LOG_EVENT_DATA_PACK_TITLE); - // Get game's additional apps - Fp::Db::EntryFilter addAppFilter{.type = Fp::Db::EntryType::AddApp, .parent = targetId}; + if(sError = mCore.enqueueDataPackTasks(parentGameData); sError.isValid()) + return sError; + } - Fp::DbError addAppSearchError; - Fp::Db::QueryBuffer addAppSearchResult; + // Get parent info to determine platform + Fp::Db::EntryFilter parentFilter{.type = Fp::Db::EntryType::Primary, .id = parentId}; - addAppSearchError = database->queryEntrys(addAppSearchResult, addAppFilter); - if(addAppSearchError.isValid()) - { - mCore.postError(NAME, addAppSearchError); - return addAppSearchError; - } + Fp::Db::QueryBuffer parentResult; - // Enqueue auto-run before apps - for(int i = 0; i < addAppSearchResult.size; i++) - { - // Go to next record - addAppSearchResult.result.next(); + if(Fp::DbError pge = db->queryEntrys(parentResult, parentFilter); pge.isValid()) + { + mCore.postError(NAME, pge); + return pge; + } - // Build - Fp::AddApp addApp = buildAdditionalApp(addAppSearchResult); + // Advance result to only record + parentResult.result.next(); - // Enqueue if auto-run before - if(addApp.isAutorunBefore()) - { - mCore.logEvent(NAME, LOG_EVENT_FOUND_AUTORUN.arg(addApp.name())); + // Determine platform (don't bother building entire game object since only one value is needed) + QString platformName = parentResult.result.value(Fp::Db::Table_Game::COL_PLATFORM_NAME).toString(); - if(Qx::Error ee = enqueueAdditionalApp(addApp, game.platformName(), Task::Stage::Auxiliary); ee.isValid()) - return ee; - } - } + // Enqueue + mCore.setStatus(STATUS_PLAY, addApp.name()); - // Enqueue game - if(Qx::Error ee = enqueueGame(game, gameData, Task::Stage::Primary); ee.isValid()) - return ee; - } - else - qFatal("Auto ID search result source must be 'game' or 'additional_app'"); + if(sError = enqueueAdditionalApp(addApp, platformName, Task::Stage::Primary); sError.isValid()) + return sError; + + // Enqueue service shutdown if needed + if(needsServices) + mCore.enqueueShutdownTasks(); - // Return success return Qx::Error(); } @@ -308,19 +297,18 @@ Qx::Error CPlay::perform() return ide; - Qx::Error errorStatus; + mCore.logEvent(NAME, LOG_EVENT_HANDLING_AUTO); - // Enqueue required tasks - if((errorStatus = mCore.enqueueStartupTasks()).isValid()) - return errorStatus; - - bool standaloneTask; - if((errorStatus = enqueueAutomaticTasks(standaloneTask, titleId)).isValid()) - return errorStatus; + // Get entry via ID + Fp::Db* db = mCore.fpInstall().database(); - if(!standaloneTask) - mCore.enqueueShutdownTasks(); + Fp::Entry entry; + if(Fp::DbError eErr = db->getEntry(entry, titleId); eErr.isValid()) + { + mCore.postError(NAME, eErr); + return eErr; + } - // Return success - return Qx::Error(); + // Handle entry + return std::visit([this](auto arg) { return this->handleEntry(arg); }, entry); } diff --git a/app/src/command/c-play.h b/app/src/command/c-play.h index 44a5148..feadd65 100644 --- a/app/src/command/c-play.h +++ b/app/src/command/c-play.h @@ -69,11 +69,10 @@ class CPlay : public TitleCommand static inline const QList CL_OPTIONS_SPECIFIC{&CL_OPTION_URL}; // Logging - Messages - static inline const QString LOG_EVENT_ENQ_AUTO = u"Enqueuing automatic tasks..."_s; + static inline const QString LOG_EVENT_HANDLING_AUTO = u"Handling automatic tasks..."_s; static inline const QString LOG_EVENT_URL_ID = u"ID from URL: %1"_s; static inline const QString LOG_EVENT_ID_MATCH_TITLE = u"ID matches main title: %1"_s; static inline const QString LOG_EVENT_ID_MATCH_ADDAPP = u"ID matches additional app: %1 (Child of %2)"_s; - static inline const QString LOG_EVENT_QUEUE_CLEARED = u"Previous queue entries cleared due to auto task being a Message/Extra"_s; static inline const QString LOG_EVENT_FOUND_AUTORUN = u"Found autorun-before additional app: %1"_s; static inline const QString LOG_EVENT_DATA_PACK_TITLE = u"Selected title uses a data pack"_s; @@ -94,8 +93,8 @@ class CPlay : public TitleCommand //-Instance Functions------------------------------------------------------------------------------------------------------ private: // Queue - //TODO: Eventually rework to return via ref arg a list of tasks and a bool if app is message/extra so that startup tasks can be enqueued afterwords and queue clearing is unnecessary - Qx::Error enqueueAutomaticTasks(bool& wasStandalone, QUuid targetId); + Qx::Error handleEntry(const Fp::Game& game); + Qx::Error handleEntry(const Fp::AddApp& addApp); Qx::Error enqueueAdditionalApp(const Fp::AddApp& addApp, const QString& platform, Task::Stage taskStage); Qx::Error enqueueGame(const Fp::Game& game, const Fp::GameData& gameData, Task::Stage taskStage); diff --git a/app/src/kernel/core.cpp b/app/src/kernel/core.cpp index 804d9df..f649a1f 100644 --- a/app/src/kernel/core.cpp +++ b/app/src/kernel/core.cpp @@ -747,7 +747,6 @@ Qx::Error Core::enqueueDataPackTasks(const Fp::GameData& gameData) } void Core::enqueueSingleTask(Task* task) { mTaskQueue.push(task); logTask(NAME, task); } -void Core::clearTaskQueue() { mTaskQueue = {}; } bool Core::isLogOpen() const { return mLogger->isOpen(); } diff --git a/app/src/kernel/core.h b/app/src/kernel/core.h index 6a96bf0..73c548f 100644 --- a/app/src/kernel/core.h +++ b/app/src/kernel/core.h @@ -301,7 +301,6 @@ class Core : public QObject #endif Qx::Error enqueueDataPackTasks(const Fp::GameData& gameData); void enqueueSingleTask(Task* task); - void clearTaskQueue(); // TODO: See if this can be done away with, it's awkward (i.e. not fill queue in first place). Think I tried to before though. // Notifications/Logging bool isLogOpen() const; From d73d62f5562ee09d29d5b474d660768ed6009768 Mon Sep 17 00:00:00 2001 From: Christian Heimlich Date: Sun, 12 Nov 2023 08:13:10 -0500 Subject: [PATCH 14/26] Use Fp::Entry alias where possible --- app/src/command/c-link.cpp | 4 ++-- app/src/command/title-command.cpp | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/command/c-link.cpp b/app/src/command/c-link.cpp index d6e0489..372d41a 100644 --- a/app/src/command/c-link.cpp +++ b/app/src/command/c-link.cpp @@ -55,7 +55,7 @@ Qx::Error CLink::perform() Fp::Db* database = mCore.fpInstall().database(); // Get entry (also confirms that ID is present in database, which is why we do this even if a custom name is set) - std::variant entry_v; + Fp::Entry entry_v; Fp::DbError dbError = database->getEntry(entry_v, shortcutId); if(dbError.isValid()) { @@ -85,7 +85,7 @@ Qx::Error CLink::perform() shortcutName = parent.title() + u" ("_s + addApp.name() + u")"_s; } else - qCritical("Invalid variant state for std::variant."); + qCritical("Invalid variant state for Fp::Entry."); // Override shortcut name with user input if(mParser.isSet(CL_OPTION_NAME)) diff --git a/app/src/command/title-command.cpp b/app/src/command/title-command.cpp index ab9b925..4141c42 100644 --- a/app/src/command/title-command.cpp +++ b/app/src/command/title-command.cpp @@ -145,7 +145,7 @@ Qx::Error TitleCommand::getRandomSelectionInfo(QString& infoBuffer, QUuid mainId Fp::Db* database = mCore.fpInstall().database(); // Get main entry info - std::variant entry_v; + Fp::Entry entry_v; searchError = database->getEntry(entry_v, mainId); if(searchError.isValid()) { From 7848c4332cbb615092d44bdc8ac51334da9a1b27 Mon Sep 17 00:00:00 2001 From: Christian Heimlich Date: Sun, 12 Nov 2023 10:17:03 -0500 Subject: [PATCH 15/26] Implement handling of '-server' mount parameter --- CMakeLists.txt | 2 +- app/src/command/c-play.cpp | 42 +++++++++++++++++++++++++++----------- app/src/command/c-play.h | 3 ++- app/src/kernel/core.cpp | 16 ++++++++------- app/src/kernel/core.h | 4 ++-- 5 files changed, 44 insertions(+), 23 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 7277d2a..56cb262 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -79,7 +79,7 @@ ob_fetch_qx( # Fetch libfp (build and import from source) include(OB/Fetchlibfp) -ob_fetch_libfp("3ff98dd41fb2de72f17c3fee3ebc85d03bb0368c") +ob_fetch_libfp("9c0def94b376c5d81ef448a845f48ec0d540f066") # Fetch QI-QMP (build and import from source) include(OB/FetchQI-QMP) diff --git a/app/src/command/c-play.cpp b/app/src/command/c-play.cpp index c505c98..3e1879b 100644 --- a/app/src/command/c-play.cpp +++ b/app/src/command/c-play.cpp @@ -69,6 +69,15 @@ Fp::AddApp CPlay::buildAdditionalApp(const Fp::Db::QueryBuffer& addAppResult) //-Instance Functions------------------------------------------------------------- //Private: +QString CPlay::getServerOverride(const Fp::GameData& gd) +{ + QString override = gd.isNull() ? QString() : gd.parameters().server(); + if(!override.isNull()) + mCore.logEvent(NAME, LOG_EVENT_SERVER_OVERRIDE.arg(override)); + + return override; +} + Qx::Error CPlay::handleEntry(const Fp::Game& game) { mCore.logEvent(NAME, LOG_EVENT_ID_MATCH_TITLE.arg(game.title())); @@ -76,10 +85,6 @@ Qx::Error CPlay::handleEntry(const Fp::Game& game) Qx::Error sError; Fp::Db* db = mCore.fpInstall().database(); - // Enqueue services - if(sError = mCore.enqueueStartupTasks(); sError.isValid()) - return sError; - // Get game data (if present) Fp::GameData gameData; if(Fp::DbError gdErr = db->getGameData(gameData, game.id()); gdErr.isValid()) @@ -87,9 +92,17 @@ Qx::Error CPlay::handleEntry(const Fp::Game& game) mCore.postError(NAME, gdErr); return gdErr; } + bool hasDatapack = !gameData.isNull(); + + // Get server override (if not present, will result in the default server being used) + QString serverOverride = getServerOverride(gameData); + + // Enqueue services + if(sError = mCore.enqueueStartupTasks(serverOverride); sError.isValid()) + return sError; - // Check if entry uses a data pack - if(!gameData.isNull()) + // Handle datapack tasks + if(hasDatapack) { mCore.logEvent(NAME, LOG_EVENT_DATA_PACK_TITLE); @@ -147,11 +160,6 @@ Qx::Error CPlay::handleEntry(const Fp::AddApp& addApp) Qx::Error sError; Fp::Db* db = mCore.fpInstall().database(); - // Enqueue services if needed - bool needsServices = addApp.appPath() != Fp::Db::Table_Add_App::ENTRY_MESSAGE && addApp.appPath() != Fp::Db::Table_Add_App::ENTRY_EXTRAS; - if(needsServices && (sError = mCore.enqueueStartupTasks()).isValid()) - return sError; - // Check if parent entry uses a data pack QUuid parentId = addApp.parentId(); Fp::GameData parentGameData; @@ -160,8 +168,18 @@ Qx::Error CPlay::handleEntry(const Fp::AddApp& addApp) mCore.postError(NAME, gdErr); return gdErr; } + bool hasDatapack = !parentGameData.isNull(); + + // Get server override (if not present, will result in the default server being used) + QString serverOverride = getServerOverride(parentGameData); + + // Enqueue services if needed + bool needsServices = addApp.appPath() != Fp::Db::Table_Add_App::ENTRY_MESSAGE && addApp.appPath() != Fp::Db::Table_Add_App::ENTRY_EXTRAS; + if(needsServices && (sError = mCore.enqueueStartupTasks(serverOverride)).isValid()) + return sError; - if(!parentGameData.isNull()) + // Handle datapack tasks + if(hasDatapack) { mCore.logEvent(NAME, LOG_EVENT_DATA_PACK_TITLE); diff --git a/app/src/command/c-play.h b/app/src/command/c-play.h index feadd65..49f5d54 100644 --- a/app/src/command/c-play.h +++ b/app/src/command/c-play.h @@ -75,6 +75,7 @@ class CPlay : public TitleCommand static inline const QString LOG_EVENT_ID_MATCH_ADDAPP = u"ID matches additional app: %1 (Child of %2)"_s; static inline const QString LOG_EVENT_FOUND_AUTORUN = u"Found autorun-before additional app: %1"_s; static inline const QString LOG_EVENT_DATA_PACK_TITLE = u"Selected title uses a data pack"_s; + static inline const QString LOG_EVENT_SERVER_OVERRIDE = u"Selected title overrides the server to: %1"_s; public: // Meta @@ -92,7 +93,7 @@ class CPlay : public TitleCommand //-Instance Functions------------------------------------------------------------------------------------------------------ private: - // Queue + QString getServerOverride(const Fp::GameData& gd); Qx::Error handleEntry(const Fp::Game& game); Qx::Error handleEntry(const Fp::AddApp& addApp); Qx::Error enqueueAdditionalApp(const Fp::AddApp& addApp, const QString& platform, Task::Stage taskStage); diff --git a/app/src/kernel/core.cpp b/app/src/kernel/core.cpp index f649a1f..4f6192c 100644 --- a/app/src/kernel/core.cpp +++ b/app/src/kernel/core.cpp @@ -488,7 +488,7 @@ bool Core::blockNewInstances() return b; } -CoreError Core::enqueueStartupTasks() +CoreError Core::enqueueStartupTasks(const QString& serverOverride) { logEvent(NAME, LOG_EVENT_ENQ_START); @@ -513,6 +513,7 @@ CoreError Core::enqueueStartupTasks() #endif // Get settings + const Fp::Toolkit* fpTk = mFlashpointInstall->toolkit(); Fp::Services fpServices = mFlashpointInstall->services(); Fp::Config fpConfig = mFlashpointInstall->config(); Fp::Preferences fpPreferences = mFlashpointInstall->preferences(); @@ -535,22 +536,23 @@ CoreError Core::enqueueStartupTasks() // Add Server entry from services if applicable if(fpConfig.startServer) { - if(!fpServices.server.contains(fpPreferences.server)) + std::optional foundServer = fpTk->getServer(serverOverride); // Will pull fpPreferences.server if empty + if(!foundServer) { CoreError err(CoreError::ConfiguredServerMissing); postError(NAME, err); return err; } - Fp::ServerDaemon configuredServer = fpServices.server.value(fpPreferences.server); + Fp::ServerDaemon server = foundServer.value(); TExec* serverTask = new TExec(this); serverTask->setIdentifier(u"Server"_s); serverTask->setStage(Task::Stage::Startup); - serverTask->setExecutable(configuredServer.filename); - serverTask->setDirectory(mFlashpointInstall->fullPath() + '/' + configuredServer.path); - serverTask->setParameters(configuredServer.arguments); - serverTask->setProcessType(configuredServer.kill ? TExec::ProcessType::Deferred : TExec::ProcessType::Detached); + serverTask->setExecutable(server.filename); + serverTask->setDirectory(mFlashpointInstall->fullPath() + '/' + server.path); + serverTask->setParameters(server.arguments); + serverTask->setProcessType(server.kill ? TExec::ProcessType::Deferred : TExec::ProcessType::Detached); mTaskQueue.push(serverTask); logTask(NAME, serverTask); diff --git a/app/src/kernel/core.h b/app/src/kernel/core.h index 73c548f..702420d 100644 --- a/app/src/kernel/core.h +++ b/app/src/kernel/core.h @@ -52,7 +52,7 @@ class QX_ERROR_TYPE(CoreError, "CoreError", 1200) {InvalidOptions, u"Invalid global options provided."_s}, {TitleNotFound, u"Could not find the title in the Flashpoint database."_s}, {TooManyResults, u"More results than can be presented were returned in a search."_s}, - {ConfiguredServerMissing, u"The server specified in the Flashpoint config was not found within the Flashpoint services store."_s}, + {ConfiguredServerMissing, u"The configured server was not found within the Flashpoint services store."_s}, {UnknownDatapackParam, u"Unrecognized datapack parameters were present. The game likely won't work correctly."_s}, }; @@ -294,7 +294,7 @@ class Core : public QObject // Common bool blockNewInstances(); - CoreError enqueueStartupTasks(); + CoreError enqueueStartupTasks(const QString& serverOverride = {}); void enqueueShutdownTasks(); #ifdef _WIN32 Qx::Error conditionallyEnqueueBideTask(QFileInfo precedingAppInfo); From 95c820b98840a2626672d058ffd6edcd7d102986 Mon Sep 17 00:00:00 2001 From: Christian Heimlich Date: Sun, 12 Nov 2023 10:42:15 -0500 Subject: [PATCH 16/26] Implement handling of '-extracted' mount parameter --- app/src/kernel/core.cpp | 24 ++++++++++++++++-------- app/src/kernel/core.h | 4 ++++ 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/app/src/kernel/core.cpp b/app/src/kernel/core.cpp index 4f6192c..91211c2 100644 --- a/app/src/kernel/core.cpp +++ b/app/src/kernel/core.cpp @@ -720,14 +720,22 @@ Qx::Error Core::enqueueDataPackTasks(const Fp::GameData& gameData) { logEvent(NAME, LOG_EVENT_DATA_PACK_NEEDS_EXTRACT); - TExtract* extractTask = new TExtract(this); - extractTask->setStage(Task::Stage::Auxiliary); - extractTask->setPackPath(packPath); - extractTask->setPathInPack(u"content"_s); - extractTask->setDestinationPath(mFlashpointInstall->preferences().htdocsFolderPath); - - mTaskQueue.push(extractTask); - logTask(NAME, extractTask); + QDir extractRoot(mFlashpointInstall->fullPath() + '/' + mFlashpointInstall->preferences().htdocsFolderPath); + QString marker = param.extractedMarkerFile(); + // Check if files are already present + if(!marker.isEmpty() && QFile::exists(extractRoot.absoluteFilePath(marker))) + logEvent(NAME, LOG_EVENT_DATA_PACK_ALREADY_EXTRACTED); + else + { + TExtract* extractTask = new TExtract(this); + extractTask->setStage(Task::Stage::Auxiliary); + extractTask->setPackPath(packPath); + extractTask->setPathInPack(u"content"_s); + extractTask->setDestinationPath(extractRoot.absolutePath()); + + mTaskQueue.push(extractTask); + logTask(NAME, extractTask); + } } else { diff --git a/app/src/kernel/core.h b/app/src/kernel/core.h index 702420d..4a53728 100644 --- a/app/src/kernel/core.h +++ b/app/src/kernel/core.h @@ -172,6 +172,7 @@ class Core : public QObject static inline const QString LOG_EVENT_DATA_PACK_FOUND = u"Title Data Pack with correct hash is already present, no need to download"_s; static inline const QString LOG_EVENT_DATA_PACK_NEEDS_MOUNT = u"Title Data Pack requires mounting"_s; static inline const QString LOG_EVENT_DATA_PACK_NEEDS_EXTRACT = u"Title Data Pack requires extraction"_s; + static inline const QString LOG_EVENT_DATA_PACK_ALREADY_EXTRACTED = u"Extracted files already present"_s; static inline const QString LOG_EVENT_TASK_ENQ = u"Enqueued %1: {%2}"_s; static inline const QString LOG_EVENT_APP_PATH_ALT = u"App path \"%1\" maps to alternative \"%2\"."_s; @@ -303,6 +304,9 @@ class Core : public QObject void enqueueSingleTask(Task* task); // Notifications/Logging + /* TODO: Within each place that uses the log options that need the src parameter, like the Commands, and maybe even Core itself, add methods + * with the same names that call mCore.logX(NAME, ...) automatically so that NAME doesn't need to be passed every time + */ bool isLogOpen() const; void logCommand(QString src, QString commandName); void logCommandOptions(QString src, QString commandOptions); From afb63841b5b2f30eb106dbb721886be67a4c5570 Mon Sep 17 00:00:00 2001 From: Christian Heimlich Date: Sun, 12 Nov 2023 10:53:44 -0500 Subject: [PATCH 17/26] Handle libfp root path access change --- CMakeLists.txt | 2 +- app/src/kernel/core.cpp | 18 +++++++++--------- app/src/kernel/driver.cpp | 2 +- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 56cb262..9722b94 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -79,7 +79,7 @@ ob_fetch_qx( # Fetch libfp (build and import from source) include(OB/Fetchlibfp) -ob_fetch_libfp("9c0def94b376c5d81ef448a845f48ec0d540f066") +ob_fetch_libfp("c903a9eaca5f8cfeb3abc851f075f660d281f46a") # Fetch QI-QMP (build and import from source) include(OB/FetchQI-QMP) diff --git a/app/src/kernel/core.cpp b/app/src/kernel/core.cpp index 91211c2..5bb879a 100644 --- a/app/src/kernel/core.cpp +++ b/app/src/kernel/core.cpp @@ -401,7 +401,7 @@ void Core::attachFlashpoint(std::unique_ptr flashpointInstall) // Initialize child process env vars QProcessEnvironment de = QProcessEnvironment::systemEnvironment(); - QString fpPath = mFlashpointInstall->fullPath(); + QString fpPath = mFlashpointInstall->dir().absolutePath(); #ifdef __linux__ // Add platform support environment variables @@ -466,7 +466,7 @@ QString Core::resolveFullAppPath(const QString& appPath, const QString& platform if(tk->resolveTrueAppPath(swapPath, platform, clifpOverrides)) logEvent(NAME, LOG_EVENT_APP_PATH_ALT.arg(appPath, swapPath)); - return mFlashpointInstall->fullPath() + '/' + swapPath; + return mFlashpointInstall->dir().absoluteFilePath(swapPath); } Qx::Error Core::findGameIdFromTitle(QUuid& returnBuffer, QString title, bool exactTitle) @@ -503,7 +503,7 @@ CoreError Core::enqueueStartupTasks(const QString& serverOverride) xhostSet->setIdentifier(u"xhost Set"_s); xhostSet->setStage(Task::Stage::Startup); xhostSet->setExecutable(u"xhost"_s); - xhostSet->setDirectory(mFlashpointInstall->fullPath()); + xhostSet->setDirectory(mFlashpointInstall->dir()); xhostSet->setParameters({u"+SI:localuser:root"_s}); xhostSet->setProcessType(TExec::ProcessType::Blocking); @@ -525,7 +525,7 @@ CoreError Core::enqueueStartupTasks(const QString& serverOverride) currentTask->setIdentifier(startEntry.filename); currentTask->setStage(Task::Stage::Startup); currentTask->setExecutable(startEntry.filename); - currentTask->setDirectory(mFlashpointInstall->fullPath() + '/' + startEntry.path); + currentTask->setDirectory(mFlashpointInstall->dir().absoluteFilePath(startEntry.path)); currentTask->setParameters(startEntry.arguments); currentTask->setProcessType(TExec::ProcessType::Blocking); @@ -550,7 +550,7 @@ CoreError Core::enqueueStartupTasks(const QString& serverOverride) serverTask->setIdentifier(u"Server"_s); serverTask->setStage(Task::Stage::Startup); serverTask->setExecutable(server.filename); - serverTask->setDirectory(mFlashpointInstall->fullPath() + '/' + server.path); + serverTask->setDirectory(mFlashpointInstall->dir().absoluteFilePath(server.path)); serverTask->setParameters(server.arguments); serverTask->setProcessType(server.kill ? TExec::ProcessType::Deferred : TExec::ProcessType::Detached); @@ -565,7 +565,7 @@ CoreError Core::enqueueStartupTasks(const QString& serverOverride) currentTask->setIdentifier(u"Daemon"_s); currentTask->setStage(Task::Stage::Startup); currentTask->setExecutable(d.filename); - currentTask->setDirectory(mFlashpointInstall->fullPath() + '/' + d.path); + currentTask->setDirectory(mFlashpointInstall->dir().absoluteFilePath(d.path)); currentTask->setParameters(d.arguments); currentTask->setProcessType(d.kill ? TExec::ProcessType::Deferred : TExec::ProcessType::Detached); @@ -614,7 +614,7 @@ void Core::enqueueShutdownTasks() shutdownTask->setIdentifier(stopEntry.filename); shutdownTask->setStage(Task::Stage::Shutdown); shutdownTask->setExecutable(stopEntry.filename); - shutdownTask->setDirectory(mFlashpointInstall->fullPath() + '/' + stopEntry.path); + shutdownTask->setDirectory(mFlashpointInstall->dir().absoluteFilePath(stopEntry.path)); shutdownTask->setParameters(stopEntry.arguments); shutdownTask->setProcessType(TExec::ProcessType::Blocking); @@ -630,7 +630,7 @@ void Core::enqueueShutdownTasks() xhostClear->setIdentifier(u"xhost Clear"_s); xhostClear->setStage(Task::Stage::Shutdown); xhostClear->setExecutable(u"xhost"_s); - xhostClear->setDirectory(mFlashpointInstall->fullPath()); + xhostClear->setDirectory(mFlashpointInstall->dir()); xhostClear->setParameters({"-SI:localuser:root"}); xhostClear->setProcessType(TExec::ProcessType::Blocking); @@ -720,7 +720,7 @@ Qx::Error Core::enqueueDataPackTasks(const Fp::GameData& gameData) { logEvent(NAME, LOG_EVENT_DATA_PACK_NEEDS_EXTRACT); - QDir extractRoot(mFlashpointInstall->fullPath() + '/' + mFlashpointInstall->preferences().htdocsFolderPath); + QDir extractRoot(mFlashpointInstall->dir().absoluteFilePath(mFlashpointInstall->preferences().htdocsFolderPath)); QString marker = param.extractedMarkerFile(); // Check if files are already present if(!marker.isEmpty() && QFile::exists(extractRoot.absoluteFilePath(marker))) diff --git a/app/src/kernel/driver.cpp b/app/src/kernel/driver.cpp index b215947..3d75545 100644 --- a/app/src/kernel/driver.cpp +++ b/app/src/kernel/driver.cpp @@ -292,7 +292,7 @@ void Driver::drive() finish(); return; } - mCore->logEvent(NAME, LOG_EVENT_FLASHPOINT_LINK.arg(QDir::toNativeSeparators(flashpointInstall->fullPath()))); + mCore->logEvent(NAME, LOG_EVENT_FLASHPOINT_LINK.arg(QDir::toNativeSeparators(flashpointInstall->dir().absolutePath()))); // Insert into core mCore->attachFlashpoint(std::move(flashpointInstall)); From dc52faf2b2c5db84a1092a3b00877d4016ee83df Mon Sep 17 00:00:00 2001 From: Christian Heimlich Date: Fri, 17 Nov 2023 15:37:02 -0500 Subject: [PATCH 18/26] Update libfp Fixes '--extracted' & '--server' mount parameter parsing. --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 9722b94..18fe66f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -79,7 +79,7 @@ ob_fetch_qx( # Fetch libfp (build and import from source) include(OB/Fetchlibfp) -ob_fetch_libfp("c903a9eaca5f8cfeb3abc851f075f660d281f46a") +ob_fetch_libfp("b12cfa7168e42748b4c24464bced36e2508bcf70") # Fetch QI-QMP (build and import from source) include(OB/FetchQI-QMP) From 5b925c7ade0d599e3d023a5668705a126c33ef1c Mon Sep 17 00:00:00 2001 From: Christian Heimlich Date: Fri, 17 Nov 2023 15:59:25 -0500 Subject: [PATCH 19/26] Normalize '--extracted' mount parameter slashes before check --- app/src/kernel/core.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/kernel/core.cpp b/app/src/kernel/core.cpp index 5bb879a..7b1bd5e 100644 --- a/app/src/kernel/core.cpp +++ b/app/src/kernel/core.cpp @@ -721,7 +721,7 @@ Qx::Error Core::enqueueDataPackTasks(const Fp::GameData& gameData) logEvent(NAME, LOG_EVENT_DATA_PACK_NEEDS_EXTRACT); QDir extractRoot(mFlashpointInstall->dir().absoluteFilePath(mFlashpointInstall->preferences().htdocsFolderPath)); - QString marker = param.extractedMarkerFile(); + QString marker = QDir::fromNativeSeparators(param.extractedMarkerFile()); // Check if files are already present if(!marker.isEmpty() && QFile::exists(extractRoot.absoluteFilePath(marker))) logEvent(NAME, LOG_EVENT_DATA_PACK_ALREADY_EXTRACTED); From 8c8de0437bbe2b50b5db6954cf12f6112462dc4b Mon Sep 17 00:00:00 2001 From: Christian Heimlich Date: Fri, 17 Nov 2023 21:13:09 -0500 Subject: [PATCH 20/26] Fix superfluous search repeats in TExec::escapeForShell() --- app/src/task/t-exec_linux.cpp | 3 ++- app/src/task/t-exec_win.cpp | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/app/src/task/t-exec_linux.cpp b/app/src/task/t-exec_linux.cpp index 4d57b16..d4fe9a6 100644 --- a/app/src/task/t-exec_linux.cpp +++ b/app/src/task/t-exec_linux.cpp @@ -125,10 +125,11 @@ QString TExec::escapeForShell(const QString& argStr) QString escapedArgs; bool inQuotes = false; + auto lastQuoteIdx = argStr.lastIndexOf('"'); // If uneven number of quotes, treat last quote as a regular char for(int i = 0; i < argStr.size(); i++) { const QChar& chr = argStr.at(i); - if(chr== '"' && (inQuotes || i != argStr.lastIndexOf('"'))) + if(chr== '"' && (inQuotes || i != lastQuoteIdx)) inQuotes = !inQuotes; if(inQuotes) diff --git a/app/src/task/t-exec_win.cpp b/app/src/task/t-exec_win.cpp index 142bf42..3e2fc03 100644 --- a/app/src/task/t-exec_win.cpp +++ b/app/src/task/t-exec_win.cpp @@ -86,10 +86,11 @@ QString TExec::escapeForShell(const QString& argStr) QString escapedArgs; bool inQuotes = false; + auto lastQuoteIdx = argStr.lastIndexOf('"'); // If uneven number of quotes, treat last quote as a regular char for(int i = 0; i < argStr.size(); i++) { const QChar& chr = argStr.at(i); - if(chr== '"' && (inQuotes || i != argStr.lastIndexOf('"'))) + if(chr== '"' && (inQuotes || i != lastQuoteIdx)) inQuotes = !inQuotes; escapedArgs.append((!inQuotes && escapeChars.contains(chr)) ? '^' + chr : chr); From ac4c5ad218746a806502aaa0ec2dbd506cc17fbf Mon Sep 17 00:00:00 2001 From: Christian Heimlich Date: Fri, 17 Nov 2023 21:52:46 -0500 Subject: [PATCH 21/26] De-quote fully quoted arguments when it's unnecessary Some Flashpoint service arguments are fully quoted even though they don't need to be (which works for the regular launcher since it uses the shell to start most child processes, unlike CLIFp), which creates issues when starting such a process via the OS directly and not a shell, as the quotes will be escaped and included as part of the argument, when the FP configure clearly just intended the argument to be quoted. --- app/src/task/t-exec.cpp | 41 +++++++++++++++++++++++++++++++++++++++++ app/src/task/t-exec.h | 2 ++ 2 files changed, 43 insertions(+) diff --git a/app/src/task/t-exec.cpp b/app/src/task/t-exec.cpp index 96ae23f..b87ba6a 100644 --- a/app/src/task/t-exec.cpp +++ b/app/src/task/t-exec.cpp @@ -107,6 +107,46 @@ QString TExec::createEscapedShellArguments() return escapedArgs; } +void TExec::removeRedundantFullQuotes(QProcess& process) +{ + /* Sometimes service arguments have been observed to be "pre-prepped" for shell use by being fully quoted even + * when not needed. This is an issue since QProcess will quote non-native arguments automatically when not using + * the shell, which we don't for most things other than scripts, so we have to remove such quotes here. Note this + * affects all execution tasks though, not just services. + */ + QStringList args = process.arguments(); + for(QString& a : args) + { + // Determine if arg is simply fully quoted + if(a.size() < 3 || (a.front() != '"' && a.back() != '"')) // min 3 maintains " and "" which theoretically could be significant + continue; + + QStringView inner(a.cbegin() + 1, a.cend() - 1); + bool redundant = true; + bool escaped = false; + for(const QChar& c : inner) + { + if(c == '\\') + escaped = true; + else if(c == '"' && !escaped) + { + redundant = false; + break; + } + else + escaped = false; + } + + if(redundant) + { + emit eventOccurred(NAME, LOG_EVENT_REMOVED_REDUNDANT_QUOTES.arg(a)); + a = inner.toString(); + } + } + + process.setArguments(args); +} + TExecError TExec::cleanStartProcess(QProcess* process) { // Note directories @@ -185,6 +225,7 @@ void TExec::perform() // Prepare process object QProcess* taskProcess = prepareProcess(QFileInfo(execPath)); + removeRedundantFullQuotes(*taskProcess); logPreparedProcess(taskProcess); // Set common process properties diff --git a/app/src/task/t-exec.h b/app/src/task/t-exec.h index 844a060..ac5342f 100644 --- a/app/src/task/t-exec.h +++ b/app/src/task/t-exec.h @@ -73,6 +73,7 @@ class TExec : public Task // Logging - Process Prep static inline const QString LOG_EVENT_PREPARING_PROCESS = u"Preparing %1 process '%2' (%3)..."_s; + static inline const QString LOG_EVENT_REMOVED_REDUNDANT_QUOTES = u"Removed unnecessary outer quotes on argument (%1)."_s; static inline const QString LOG_EVENT_FINAL_EXECUTABLE = u"Final Executable: %1"_s; static inline const QString LOG_EVENT_FINAL_PARAMETERS = u"Final Parameters: %1"_s; @@ -133,6 +134,7 @@ class TExec : public Task QString escapeForShell(const QString& argStr); QString createEscapedShellArguments(); QProcess* prepareProcess(const QFileInfo& execInfo); + void removeRedundantFullQuotes(QProcess& process); TExecError cleanStartProcess(QProcess* process); // Logging From 53794f0b9c2747895a27f56ea36c1e074c4f9e9d Mon Sep 17 00:00:00 2001 From: Christian Heimlich Date: Sat, 18 Nov 2023 09:42:21 -0500 Subject: [PATCH 22/26] Change :browser_mode: override to Chrome This should give greater compatibility since Electron uses Chromium internally for browser tasks. --- app/src/kernel/core.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/kernel/core.cpp b/app/src/kernel/core.cpp index 7b1bd5e..89b0fe6 100644 --- a/app/src/kernel/core.cpp +++ b/app/src/kernel/core.cpp @@ -453,11 +453,11 @@ void Core::attachFlashpoint(std::unique_ptr flashpointInstall) #endif } -// TODO: Might make sense to make this a function in libfp QString Core::resolveFullAppPath(const QString& appPath, const QString& platform) { + // We don't have a browser mode. Since Electron bundles chromium, chrome should give the closest experience to the launcher's browser mode. static const QHash clifpOverrides{ - {u":browser-mode:"_s, u"FPSoftware\\Basilisk-Portable\\Basilisk-Portable.exe"_s} + {u":browser-mode:"_s, u"FPSoftware\\startChrome.bat"_s} }; const Fp::Toolkit* tk = mFlashpointInstall->toolkit(); From d8e22932dce6b3a6005543dfe90168926872449d Mon Sep 17 00:00:00 2001 From: Christian Heimlich Date: Sun, 19 Nov 2023 15:34:27 -0500 Subject: [PATCH 23/26] Add companion mode: CLIFp piggybacks off the Launcher's services --- CMakeLists.txt | 6 +- README.md | 7 ++ app/CMakeLists.txt | 5 - app/src/command/c-play.cpp | 3 + app/src/command/c-play.h | 3 + app/src/command/c-prepare.cpp | 3 + app/src/command/c-prepare.h | 3 + app/src/command/c-run.cpp | 3 + app/src/command/c-run.h | 3 + app/src/command/command.cpp | 1 + app/src/command/command.h | 1 + app/src/kernel/core.cpp | 117 ++++++++++++++------ app/src/kernel/core.h | 29 ++++- app/src/kernel/driver.cpp | 50 ++++++--- app/src/kernel/driver.h | 5 +- app/src/task/t-bideprocess.cpp | 63 ++++++++--- app/src/task/t-bideprocess.h | 56 ++++++++-- app/src/tools/processbider.cpp | 144 ------------------------- app/src/tools/processbider.h | 129 ---------------------- app/src/tools/processbider_p.h | 73 ------------- app/src/tools/processbider_p_linux.cpp | 48 --------- app/src/tools/processbider_p_win.cpp | 137 ----------------------- 22 files changed, 272 insertions(+), 617 deletions(-) delete mode 100644 app/src/tools/processbider.cpp delete mode 100644 app/src/tools/processbider.h delete mode 100644 app/src/tools/processbider_p.h delete mode 100644 app/src/tools/processbider_p_linux.cpp delete mode 100644 app/src/tools/processbider_p_win.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 18fe66f..f3584a9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -13,7 +13,7 @@ project(CLIFp # Get helper scripts include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/FetchOBCMake.cmake) -fetch_ob_cmake("v0.3.3") +fetch_ob_cmake("928cbafc2036f97cfaeb50cd892209b6e1037b6d") # Initialize project according to standard rules include(OB/Project) @@ -72,14 +72,14 @@ endif() include(OB/FetchQx) ob_fetch_qx( - REF "2bbf83e59b0aadc3891440193892be1a2a19c00e" + REF "01560b1470364cd0c11d7341fdf9b11afa24dcff" COMPONENTS ${CLIFP_QX_COMPONENTS} ) # Fetch libfp (build and import from source) include(OB/Fetchlibfp) -ob_fetch_libfp("b12cfa7168e42748b4c24464bced36e2508bcf70") +ob_fetch_libfp("47af4e2fed04e6fdef7111a3bb5b12e58bebe77b") # Fetch QI-QMP (build and import from source) include(OB/FetchQI-QMP) diff --git a/README.md b/README.md index eb67ab7..9b32315 100644 --- a/README.md +++ b/README.md @@ -114,6 +114,7 @@ If the application needs to use files from a Data Pack that pack will need to be The applications and arguments that are used for each game/animation can be found within the Flashpoint database ([FP Install Dir]\Data\flashpoint.sqlite) ### Flashpoint Protocol + CLIFp supports the "flashpoint" protocol, which means it can launch titles through URL with a custom scheme, followed by a title's UUID, like this: flashpoint://37e5c215-9c39-4a3d-9912-b4343a17027e @@ -135,6 +136,12 @@ If for whatever reason the service through which you wish to share a link does n > [!IMPORTANT] > You will want to disable the "Register As Protocol Handler" option in the default launcher or else it will replace CLIFp as the "flashpoint" protocol handler every time it's started. +### Companion Mode + +It is recommended to only use CLIFp when the regular launcher isn't running as it allows fully independent operation since it can start and stop required services on its own; however, CLIFp can be started while the standard launcher is running, in which case it will run in "Companion Mode" and utilize the launcher's services instead. + +The catch with this mode is that CLIFp will be required to shutdown if at any point the standard launcher is closed. + ## All Commands/Options Most options have short and long forms, which are interchangeable. For options that take a value, a space or **=** can be used between the option and its value, i.e. diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt index 14cb167..2bd230b 100644 --- a/app/CMakeLists.txt +++ b/app/CMakeLists.txt @@ -57,9 +57,6 @@ set(CLIFP_SOURCE tools/mounter_qmp.cpp tools/mounter_router.h tools/mounter_router.cpp - tools/processbider_p.h - tools/processbider.h - tools/processbider.cpp frontend/message.h frontend/statusrelay.h frontend/statusrelay.cpp @@ -93,7 +90,6 @@ if(CMAKE_SYSTEM_NAME STREQUAL Windows) task/t-exec_win.cpp task/t-bideprocess.h task/t-bideprocess.cpp - tools/processbider_p_win.cpp ) list(APPEND CLIFP_LINKS @@ -108,7 +104,6 @@ if(CMAKE_SYSTEM_NAME STREQUAL Linux) task/t-awaitdocker.h task/t-awaitdocker.cpp task/t-exec_linux.cpp - tools/processbider_p_linux.cpp ) list(APPEND CLIFP_LINKS PRIVATE diff --git a/app/src/command/c-play.cpp b/app/src/command/c-play.cpp index 3e1879b..583cb11 100644 --- a/app/src/command/c-play.cpp +++ b/app/src/command/c-play.cpp @@ -330,3 +330,6 @@ Qx::Error CPlay::perform() // Handle entry return std::visit([this](auto arg) { return this->handleEntry(arg); }, entry); } + +//Public: +bool CPlay::requiresServices() const { return true; } diff --git a/app/src/command/c-play.h b/app/src/command/c-play.h index 49f5d54..b50813d 100644 --- a/app/src/command/c-play.h +++ b/app/src/command/c-play.h @@ -103,6 +103,9 @@ class CPlay : public TitleCommand QList options() override; QString name() override; Qx::Error perform() override; + +public: + bool requiresServices() const override; }; REGISTER_COMMAND(CPlay::NAME, CPlay, CPlay::DESCRIPTION); diff --git a/app/src/command/c-prepare.cpp b/app/src/command/c-prepare.cpp index 22df190..4993cd8 100644 --- a/app/src/command/c-prepare.cpp +++ b/app/src/command/c-prepare.cpp @@ -45,3 +45,6 @@ Qx::Error CPrepare::perform() // Return success return Qx::Error(); } + +//Public: +bool CPrepare::requiresServices() const { return true; } diff --git a/app/src/command/c-prepare.h b/app/src/command/c-prepare.h index 75e62f8..bd28800 100644 --- a/app/src/command/c-prepare.h +++ b/app/src/command/c-prepare.h @@ -31,6 +31,9 @@ class CPrepare : public TitleCommand QList options() override; QString name() override; Qx::Error perform() override; + +public: + bool requiresServices() const override; }; REGISTER_COMMAND(CPrepare::NAME, CPrepare, CPrepare::DESCRIPTION); diff --git a/app/src/command/c-run.cpp b/app/src/command/c-run.cpp index 139d09f..b31e083 100644 --- a/app/src/command/c-run.cpp +++ b/app/src/command/c-run.cpp @@ -71,3 +71,6 @@ Qx::Error CRun::perform() // Return success return CRunError(); } + +//Public: +bool CRun::requiresServices() const { return true; } diff --git a/app/src/command/c-run.h b/app/src/command/c-run.h index 4cfba5c..5deded8 100644 --- a/app/src/command/c-run.h +++ b/app/src/command/c-run.h @@ -82,6 +82,9 @@ class CRun : public Command QSet requiredOptions() override; QString name() override; Qx::Error perform() override; + +public: + bool requiresServices() const override; }; REGISTER_COMMAND(CRun::NAME, CRun, CRun::DESCRIPTION); diff --git a/app/src/command/command.cpp b/app/src/command/command.cpp index 010fd9a..e66b58c 100644 --- a/app/src/command/command.cpp +++ b/app/src/command/command.cpp @@ -177,6 +177,7 @@ QSet Command::requiredOptions() { return {}; } //Public: bool Command::requiresFlashpoint() const { return true; } +bool Command::requiresServices() const { return false; } bool Command::autoBlockNewInstances() const { return true; } Qx::Error Command::process(const QStringList& commandLine) diff --git a/app/src/command/command.h b/app/src/command/command.h index 79a9169..d539acc 100644 --- a/app/src/command/command.h +++ b/app/src/command/command.h @@ -163,6 +163,7 @@ class Command public: virtual bool requiresFlashpoint() const; + virtual bool requiresServices() const; virtual bool autoBlockNewInstances() const; Qx::Error process(const QStringList& commandLine); }; diff --git a/app/src/kernel/core.cpp b/app/src/kernel/core.cpp index 89b0fe6..7075e81 100644 --- a/app/src/kernel/core.cpp +++ b/app/src/kernel/core.cpp @@ -61,7 +61,8 @@ Core::Core(QObject* parent) : QObject(parent), mCriticalErrorOccurred(false), mStatusHeading(u"Initializing"_s), - mStatusMessage(u"..."_s) + mStatusMessage(u"..."_s), + mServicesMode(ServicesMode::Standalone) { establishCanonCore(*this); // Ignore return value as there should never be more than one Core with current design } @@ -342,50 +343,82 @@ Qx::Error Core::initialize(QStringList& commandLine) logEvent(NAME, LOG_EVENT_GLOBAL_OPT.arg(globalOptions)); // Check for valid arguments - if(validArgs) + if(!validArgs) { - // Handle each global option - mNotificationVerbosity = clParser.isSet(CL_OPTION_SILENT) ? NotificationVerbosity::Silent : + commandLine.clear(); // Clear remaining options since they are now irrelevant + showHelp(); + + CoreError err(CoreError::InvalidOptions, clParser.errorText()); + postError(NAME, err); + return err; + } + + // Handle each global option + mNotificationVerbosity = clParser.isSet(CL_OPTION_SILENT) ? NotificationVerbosity::Silent : clParser.isSet(CL_OPTION_QUIET) ? NotificationVerbosity::Quiet : NotificationVerbosity::Full; - logEvent(NAME, LOG_EVENT_NOTIFCATION_LEVEL.arg(ENUM_NAME(mNotificationVerbosity))); + logEvent(NAME, LOG_EVENT_NOTIFCATION_LEVEL.arg(ENUM_NAME(mNotificationVerbosity))); - if(clParser.isSet(CL_OPTION_VERSION)) - { - showVersion(); - commandLine.clear(); // Clear args so application terminates after Core setup - logEvent(NAME, LOG_EVENT_VER_SHOWN); - } - else if(clParser.isSet(CL_OPTION_HELP) || (!isActionableOptionSet(clParser) && clParser.positionalArguments().count() == 0)) // Also when no parameters + if(clParser.isSet(CL_OPTION_VERSION)) + { + showVersion(); + commandLine.clear(); // Clear args so application terminates after Core setup + logEvent(NAME, LOG_EVENT_VER_SHOWN); + } + else if(clParser.isSet(CL_OPTION_HELP) || (!isActionableOptionSet(clParser) && clParser.positionalArguments().count() == 0)) // Also when no parameters + { + showHelp(); + commandLine.clear(); // Clear args so application terminates after Core setup + logEvent(NAME, LOG_EVENT_G_HELP_SHOWN); + } + else + { + QStringList pArgs = clParser.positionalArguments(); + if(pArgs.count() == 1 && pArgs.front().startsWith(FLASHPOINT_PROTOCOL_SCHEME)) { - showHelp(); - commandLine.clear(); // Clear args so application terminates after Core setup - logEvent(NAME, LOG_EVENT_G_HELP_SHOWN); + logEvent(NAME, LOG_EVENT_PROTOCOL_FORWARD); + commandLine = {"play", "-u", pArgs.front()}; } else - { - QStringList pArgs = clParser.positionalArguments(); - if(pArgs.count() == 1 && pArgs.front().startsWith(FLASHPOINT_PROTOCOL_SCHEME)) - { - logEvent(NAME, LOG_EVENT_PROTOCOL_FORWARD); - commandLine = {"play", "-u", pArgs.front()}; - } - else - commandLine = pArgs; // Remove core options from command line list - } - - // Return success - return CoreError(); + commandLine = pArgs; // Remove core options from command line list } - else - { - commandLine.clear(); // Clear remaining options since they are now irrelevant - showHelp(); - CoreError err(CoreError::InvalidOptions, clParser.errorText()); + // Return success + return CoreError(); +} + +void Core::setServicesMode(ServicesMode mode) +{ + logEvent(NAME, LOG_EVENT_MODE_SET.arg(ENUM_NAME(mode))); + mServicesMode = mode; + + if(mode == ServicesMode::Companion) + watchLauncher(); +} + +void Core::watchLauncher() +{ + logEvent(NAME, LOG_EVENT_LAUNCHER_WATCH); + + using namespace std::chrono_literals; + mLauncherWatcher.setProcessName(Fp::Install::LAUNCHER_NAME); +#ifdef __linux__ + mLauncherWatcher.setPollRate(1s); // Generous rate since while we need to know quickly, we don't THAT quickly +#endif + connect(&mLauncherWatcher, &Qx::ProcessBider::established, this, [this]{ + logEvent(NAME, LOG_EVENT_LAUNCHER_WATCH_HOOKED); + }); + connect(&mLauncherWatcher, &Qx::ProcessBider::errorOccurred, this, [this](Qx::ProcessBiderError err){ + logError(NAME, err); + }); + connect(&mLauncherWatcher, &Qx::ProcessBider::finished, this, [this]{ + // Launcher closed (or can't be hooked), need to bail + CoreError err(CoreError::CompanionModeLauncherClose, LOG_EVENT_LAUNCHER_CLOSED_RESULT); postError(NAME, err); - return err; - } + emit abort(err); + }); + mLauncherWatcher.start(); + // The bide is automatically abandoned when core, and therefore the bider, is destroyed } void Core::attachFlashpoint(std::unique_ptr flashpointInstall) @@ -491,6 +524,13 @@ bool Core::blockNewInstances() CoreError Core::enqueueStartupTasks(const QString& serverOverride) { logEvent(NAME, LOG_EVENT_ENQ_START); + + if(mServicesMode == ServicesMode::Companion) + { + logEvent(NAME, LOG_EVENT_SERVICES_FROM_LAUNCHER); + // TODO: Allegedly apache and php are going away at some point so this hopefully isn't needed for long + return !serverOverride.isEmpty() ? CoreError(CoreError::CompanionModeServerOverride) : CoreError(); + } #ifdef __linux__ /* On Linux X11 Server needs to be temporarily be set to allow connections from root for docker, @@ -607,6 +647,13 @@ CoreError Core::enqueueStartupTasks(const QString& serverOverride) void Core::enqueueShutdownTasks() { logEvent(NAME, LOG_EVENT_ENQ_STOP); + + if(mServicesMode == ServicesMode::Companion) + { + logEvent(NAME, LOG_EVENT_SERVICES_FROM_LAUNCHER); + return; + } + // Add Stop entries from services for(const Fp::StartStop& stopEntry : qxAsConst(mFlashpointInstall->services().stop)) { diff --git a/app/src/kernel/core.h b/app/src/kernel/core.h index 4a53728..0fe59bf 100644 --- a/app/src/kernel/core.h +++ b/app/src/kernel/core.h @@ -15,6 +15,7 @@ // Qx Includes #include +#include #include // libfp Includes @@ -31,12 +32,14 @@ using ErrorCode = quint32; class QX_ERROR_TYPE(CoreError, "CoreError", 1200) { friend class Core; - //-Class Enums------------------------------------------------------------- +//-Class Enums------------------------------------------------------------- public: enum Type { NoError, InternalError, + CompanionModeLauncherClose, + CompanionModeServerOverride, InvalidOptions, TitleNotFound, TooManyResults, @@ -44,11 +47,13 @@ class QX_ERROR_TYPE(CoreError, "CoreError", 1200) UnknownDatapackParam }; - //-Class Variables------------------------------------------------------------- +//-Class Variables------------------------------------------------------------- private: static inline const QHash ERR_STRINGS{ {NoError, u""_s}, {InternalError, u"Internal error."_s}, + {CompanionModeLauncherClose, u"The standard launcher was closed while in companion mode."_s}, + {CompanionModeServerOverride, u"Cannot enact game server override in companion mode."_s}, {InvalidOptions, u"Invalid global options provided."_s}, {TitleNotFound, u"Could not find the title in the Flashpoint database."_s}, {TooManyResults, u"More results than can be presented were returned in a search."_s}, @@ -56,17 +61,17 @@ class QX_ERROR_TYPE(CoreError, "CoreError", 1200) {UnknownDatapackParam, u"Unrecognized datapack parameters were present. The game likely won't work correctly."_s}, }; - //-Instance Variables------------------------------------------------------------- +//-Instance Variables------------------------------------------------------------- private: Type mType; QString mSpecific; Qx::Severity mSeverity; - //-Constructor------------------------------------------------------------- +//-Constructor------------------------------------------------------------- private: CoreError(Type t = NoError, const QString& s = {}, Qx::Severity sv = Qx::Critical); - //-Instance Functions------------------------------------------------------------- +//-Instance Functions------------------------------------------------------------- public: bool isValid() const; Type type() const; @@ -85,6 +90,7 @@ class Core : public QObject //-Class Enums----------------------------------------------------------------------- public: enum class NotificationVerbosity { Full, Quiet, Silent }; + enum ServicesMode { Standalone, Companion }; //-Class Structs--------------------------------------------------------------------- public: @@ -154,6 +160,7 @@ class Core : public QObject // Logging - Messages static inline const QString LOG_EVENT_INIT = u"Initializing CLIFp..."_s; + static inline const QString LOG_EVENT_MODE_SET = u"Services mode set: %1"_s; static inline const QString LOG_EVENT_GLOBAL_OPT = u"Global Options: %1"_s; static inline const QString LOG_EVENT_FURTHER_INSTANCE_BLOCK_SUCC = u"Successfully locked standard instance count..."_s; static inline const QString LOG_EVENT_FURTHER_INSTANCE_BLOCK_FAIL = u"Failed to lock standard instance count"_s; @@ -175,6 +182,10 @@ class Core : public QObject static inline const QString LOG_EVENT_DATA_PACK_ALREADY_EXTRACTED = u"Extracted files already present"_s; static inline const QString LOG_EVENT_TASK_ENQ = u"Enqueued %1: {%2}"_s; static inline const QString LOG_EVENT_APP_PATH_ALT = u"App path \"%1\" maps to alternative \"%2\"."_s; + static inline const QString LOG_EVENT_SERVICES_FROM_LAUNCHER = u"Using services from standard Launcher due to companion mode."_s; + static inline const QString LOG_EVENT_LAUNCHER_WATCH = u"Starting bide on Launcher process..."_s; + static inline const QString LOG_EVENT_LAUNCHER_WATCH_HOOKED = u"Launcher hooked for waiting"_s; + static inline const QString LOG_EVENT_LAUNCHER_CLOSED_RESULT = u"CLIFp cannot continue running in companion mode without the launcher's services."_s; // Logging - Title Search static inline const QString LOG_EVENT_GAME_SEARCH = u"Searching for game with title '%1'"_s; @@ -252,6 +263,7 @@ class Core : public QObject std::unique_ptr mLogger; // Processing + ServicesMode mServicesMode; bool mCriticalErrorOccurred; NotificationVerbosity mNotificationVerbosity; std::queue mTaskQueue; @@ -262,6 +274,7 @@ class Core : public QObject // Other QProcessEnvironment mChildTitleProcEnv; + Qx::ProcessBider mLauncherWatcher; //-Constructor---------------------------------------------------------------------------------------------------------- public: @@ -286,6 +299,8 @@ class Core : public QObject public: // Setup Qx::Error initialize(QStringList& commandLine); + void setServicesMode(ServicesMode mode = ServicesMode::Standalone); + void watchLauncher(); void attachFlashpoint(std::unique_ptr flashpointInstall); // Helper (TODO: Move some of these to libfp Toolkit) @@ -324,6 +339,7 @@ class Core : public QObject bool requestQuestionAnswer(const QString& question); // Member access + ServicesMode mode() const; Fp::Install& fpInstall(); const QProcessEnvironment& childTitleProcessEnvironment(); NotificationVerbosity notifcationVerbosity() const; @@ -351,6 +367,9 @@ class Core : public QObject void message(const Message& message); void clipboardUpdateRequested(const QString& text); void questionAnswerRequested(QSharedPointer response, const QString& question); + + // Driver specific + void abort(CoreError err); }; //-Metatype Declarations----------------------------------------------------------------------------------------- diff --git a/app/src/kernel/driver.cpp b/app/src/kernel/driver.cpp index 3d75545..d23ba66 100644 --- a/app/src/kernel/driver.cpp +++ b/app/src/kernel/driver.cpp @@ -1,6 +1,9 @@ // Unit Include #include "driver.h" +// Qt Includes +#include + // Qx Includes #include #include @@ -65,6 +68,11 @@ void Driver::init() connect(mCore, &Core::itemSelectionRequested, this, &Driver::itemSelectionRequested); connect(mCore, &Core::clipboardUpdateRequested, this, &Driver::clipboardUpdateRequested); connect(mCore, &Core::questionAnswerRequested, this, &Driver::questionAnswerRequested); + connect(mCore, &Core::abort, this, [this](CoreError err){ + mCore->logEvent(NAME, LOG_EVENT_CORE_ABORT); + mErrorStatus = err; + quit(); + }); //-Setup deferred process manager------ /* NOTE: It looks like the manager should just be a stack member of TExec that is constructed @@ -166,6 +174,15 @@ void Driver::finish() emit finished(mCore->logFinish(NAME, mErrorStatus.value())); } +void Driver::quit() +{ + mQuitRequested = true; + + // Stop current task (assuming it can be) + if(mCurrentTask) + mCurrentTask->stop(); +} + // Helper functions std::unique_ptr Driver::findFlashpointInstall() { @@ -257,6 +274,12 @@ void Driver::drive() // Create command instance std::unique_ptr commandProcessor = Command::acquire(commandStr, *mCore); + //-Set Service Mode-------------------------------------------------------------------- + + // Check state of standard launcher + bool launcherRunning = Qx::processIsRunning(Fp::Install::LAUNCHER_NAME); + mCore->setServicesMode(launcherRunning && commandProcessor->requiresServices() ? Core::Companion : Core::Standalone); + //-Restrict app to only one instance--------------------------------------------------- if(commandProcessor->autoBlockNewInstances() && !mCore->blockNewInstances()) { @@ -267,19 +290,9 @@ void Driver::drive() return; } - //-Handle Flashpoint Steps---------------------------------------------------------- + //-Get Flashpoint Install------------------------------------------------------------- if(commandProcessor->requiresFlashpoint()) { - // Ensure Flashpoint Launcher isn't running - if(Qx::processIsRunning(Fp::Install::LAUNCHER_NAME)) - { - DriverError err(DriverError::LauncherRunning, ERR_LAUNCHER_RUNNING_TIP); - mCore->postError(NAME, err); - mErrorStatus = err; - finish(); - return; - } - // Find and link to Flashpoint Install std::unique_ptr flashpointInstall; mCore->logEvent(NAME, LOG_EVENT_FLASHPOINT_SEARCH); @@ -298,6 +311,15 @@ void Driver::drive() mCore->attachFlashpoint(std::move(flashpointInstall)); } + //-Catch early core errors------------------------------------------------------------------- + QThread::msleep(100); + QApplication::processEvents(); + if(mErrorStatus.isSet()) + { + finish(); + return; + } + //-Process command----------------------------------------------------------------------------- mErrorStatus = commandProcessor->process(mArguments); if(mErrorStatus.isSet()) @@ -333,10 +355,6 @@ void Driver::quitNow() return; } - mQuitRequested = true; mCore->logEvent(NAME, LOG_EVENT_QUIT_REQUEST); - - // Stop current task (assuming it can be) - if(mCurrentTask) - mCurrentTask->stop(); + quit(); } diff --git a/app/src/kernel/driver.h b/app/src/kernel/driver.h index b9b22e8..044c64f 100644 --- a/app/src/kernel/driver.h +++ b/app/src/kernel/driver.h @@ -21,7 +21,6 @@ class QX_ERROR_TYPE(DriverError, "DriverError", 1201) { NoError, AlreadyOpen, - LauncherRunning, InvalidInstall, }; @@ -30,7 +29,6 @@ class QX_ERROR_TYPE(DriverError, "DriverError", 1201) static inline const QHash ERR_STRINGS{ {NoError, u""_s}, {AlreadyOpen, u"Only one instance of CLIFp can be used at a time!"_s}, - {LauncherRunning, u"The CLI cannot be used while the Flashpoint Launcher is running."_s}, {InvalidInstall, u"CLIFp does not appear to be deployed in a valid Flashpoint install"_s} }; @@ -62,7 +60,6 @@ class Driver : public QObject //-Class Variables------------------------------------------------------------------------------------------------------ private: // Error Messages - static inline const QString ERR_LAUNCHER_RUNNING_TIP = u"Please close the Launcher first."_s; static inline const QString ERR_INSTALL_INVALID_TIP = u"You may need to update (i.e. the 'update' command)."_s; // Logging @@ -84,6 +81,7 @@ class Driver : public QObject static inline const QString LOG_EVENT_QUIT_REQUEST = u"Received quit request"_s; static inline const QString LOG_EVENT_QUIT_REQUEST_REDUNDANT = u"Received redundant quit request"_s; static inline const QString LOG_EVENT_CLEARED_UPDATE_CACHE = u"Cleared stale update cache."_s; + static inline const QString LOG_EVENT_CORE_ABORT = u"Core abort signaled, quitting now."_s; // Meta static inline const QString NAME = u"driver"_s; @@ -126,6 +124,7 @@ class Driver : public QObject void cleanup(); void finish(); + void quit(); // Helper std::unique_ptr findFlashpointInstall(); diff --git a/app/src/task/t-bideprocess.cpp b/app/src/task/t-bideprocess.cpp index bb80fbd..976eb06 100644 --- a/app/src/task/t-bideprocess.cpp +++ b/app/src/task/t-bideprocess.cpp @@ -1,6 +1,29 @@ // Unit Include #include "t-bideprocess.h" +//=============================================================================================================== +// TBideProcessError +//=============================================================================================================== + +//-Constructor------------------------------------------------------------- +//Private: +TBideProcessError::TBideProcessError(const QString& pn, Type t) : + mType(t), + mProcessName(pn) +{} + +//-Instance Functions------------------------------------------------------------- +//Public: +bool TBideProcessError::isValid() const { return mType != NoError; } +TBideProcessError::Type TBideProcessError::type() const { return mType; } +QString TBideProcessError::processName() const { return mProcessName; } + +//Private: +Qx::Severity TBideProcessError::deriveSeverity() const { return Qx::Critical; } +quint32 TBideProcessError::deriveValue() const { return mType; } +QString TBideProcessError::derivePrimary() const { return ERR_STRINGS.value(mType); } +QString TBideProcessError::deriveSecondary() const { return mProcessName; } + //=============================================================================================================== // TBideProcess //=============================================================================================================== @@ -8,18 +31,27 @@ //-Constructor-------------------------------------------------------------------- //Public: TBideProcess::TBideProcess(QObject* parent) : - Task(parent), - mProcessBider(nullptr) + Task(parent) { // Setup bider - mProcessBider.setRespawnGrace(STANDARD_GRACE); - connect(&mProcessBider, &ProcessBider::statusChanged, this, [this](QString statusMessage){ - emit eventOccurred(NAME, statusMessage); + using namespace std::chrono_literals; + static const auto grace = 2s; + mProcessBider.setRespawnGrace(grace); + mProcessBider.setInitialGrace(true); // Process will be stopped at first + connect(&mProcessBider, &Qx::ProcessBider::established, this, [this]{ + emit eventOccurred(NAME, LOG_EVENT_BIDE_RUNNING.arg(mProcessName)); + emit eventOccurred(NAME, LOG_EVENT_BIDE_ON.arg(mProcessName)); + }); + connect(&mProcessBider, &Qx::ProcessBider::processStopped, this, [this]{ + emit eventOccurred(NAME, LOG_EVENT_BIDE_QUIT.arg(mProcessName)); }); - connect(&mProcessBider, &ProcessBider::errorOccurred, this, [this](ProcessBiderError errorMessage){ - emit errorOccurred(NAME, errorMessage); + connect(&mProcessBider, &Qx::ProcessBider::graceStarted, this, [this]{ + emit eventOccurred(NAME, LOG_EVENT_BIDE_GRACE.arg(QString::number(grace.count()), mProcessName)); }); - connect(&mProcessBider, &ProcessBider::bideFinished, this, &TBideProcess::postBide); + connect(&mProcessBider, &Qx::ProcessBider::errorOccurred, this, [this](Qx::ProcessBiderError err){ + emit errorOccurred(NAME, err); + }); + connect(&mProcessBider, &Qx::ProcessBider::finished, this, &TBideProcess::postBide); } //-Instance Functions------------------------------------------------------------- @@ -45,17 +77,22 @@ void TBideProcess::perform() void TBideProcess::stop() { - if(mProcessBider.isRunning()) + if(mProcessBider.isBiding()) { emit eventOccurred(NAME, LOG_EVENT_STOPPING_BIDE_PROCESS); - if(ProcessBiderError err = mProcessBider.closeProcess(); err.isValid()) - emit errorOccurred(NAME, err); + mProcessBider.closeProcess(); } } //-Signals & Slots------------------------------------------------------------------------------------------------------- //Private Slots: -void TBideProcess::postBide(Qx::Error errorStatus) +void TBideProcess::postBide(Qx::ProcessBider::ResultType type) { - emit complete(errorStatus); + if(type == Qx::ProcessBider::Fail)\ + emit complete(TBideProcessError(mProcessName, TBideProcessError::BideFail)); + else + { + emit eventOccurred(NAME, LOG_EVENT_BIDE_FINISHED.arg(mProcessName)); + emit complete(TBideProcessError()); + } } diff --git a/app/src/task/t-bideprocess.h b/app/src/task/t-bideprocess.h index d1b899d..9c4e6f9 100644 --- a/app/src/task/t-bideprocess.h +++ b/app/src/task/t-bideprocess.h @@ -1,9 +1,51 @@ #ifndef TBIDEPROCESS_H #define TBIDEPROCESS_H +// Qx Includes +#include + // Project Includes #include "task/task.h" -#include "tools/processbider.h" + +class QX_ERROR_TYPE(TBideProcessError, "TBideError", 1256) +{ + friend class TBideProcess; +//-Class Enums------------------------------------------------------------- +public: + enum Type + { + NoError, + BideFail, + }; + +//-Class Variables------------------------------------------------------------- +private: + static inline const QHash ERR_STRINGS{ + {NoError, u""_s}, + {BideFail, u"Could not bide on process."_s} + }; + +//-Instance Variables------------------------------------------------------------- +private: + Type mType; + QString mProcessName; + +//-Constructor------------------------------------------------------------- +private: + TBideProcessError(const QString& pn = {}, Type t = NoError); + +//-Instance Functions------------------------------------------------------------- +public: + bool isValid() const; + Type type() const; + QString processName() const; + +private: + Qx::Severity deriveSeverity() const override; + quint32 deriveValue() const override; + QString derivePrimary() const override; + QString deriveSecondary() const override; +}; class TBideProcess : public Task { @@ -14,18 +56,20 @@ class TBideProcess : public Task static inline const QString NAME = u"TBideProcess"_s; // Logging + static inline const QString LOG_EVENT_BIDE_GRACE = u"Waiting %1 seconds for process %2 to be running"_s; + static inline const QString LOG_EVENT_BIDE_RUNNING = u"Wait-on process %1 is running"_s; + static inline const QString LOG_EVENT_BIDE_ON = u"Waiting for process %1 to finish"_s; + static inline const QString LOG_EVENT_BIDE_QUIT = u"Wait-on process %1 has finished"_s; + static inline const QString LOG_EVENT_BIDE_FINISHED = u"Wait-on process %1 was not running after the grace period"_s; static inline const QString LOG_EVENT_STOPPING_BIDE_PROCESS = u"Stopping current bide process..."_s; // Errors static inline const QString ERR_CANT_CLOSE_BIDE_PROCESS = u"Could not automatically end the running title! It will have to be closed manually."_s; - // Functional - static const uint STANDARD_GRACE = 2; // Seconds to allow the process to restart in cases it does - //-Instance Variables------------------------------------------------------------------------------------------------ private: // Functional - ProcessBider mProcessBider; + Qx::ProcessBider mProcessBider; // Data QString mProcessName; @@ -48,7 +92,7 @@ class TBideProcess : public Task //-Signals & Slots------------------------------------------------------------------------------------------------------- private slots: - void postBide(Qx::Error errorStatus); + void postBide(Qx::ProcessBider::ResultType type); }; #endif // TBIDEPROCESS_H diff --git a/app/src/tools/processbider.cpp b/app/src/tools/processbider.cpp deleted file mode 100644 index e5aa2a9..0000000 --- a/app/src/tools/processbider.cpp +++ /dev/null @@ -1,144 +0,0 @@ -// Unit Include -#include "processbider.h" -#include "processbider_p.h" - -// Qx Includes -#include - -//=============================================================================================================== -// ProcessBiderError -//=============================================================================================================== - -//-Constructor------------------------------------------------------------- -//Private: -ProcessBiderError::ProcessBiderError(Type t, const QString& s) : - mType(t), - mSpecific(s) -{} - -//-Instance Functions------------------------------------------------------------- -//Public: -bool ProcessBiderError::isValid() const { return mType != NoError; } -QString ProcessBiderError::specific() const { return mSpecific; } -ProcessBiderError::Type ProcessBiderError::type() const { return mType; } - -//Private: -Qx::Severity ProcessBiderError::deriveSeverity() const { return Qx::Warning; } -quint32 ProcessBiderError::deriveValue() const { return mType; } -QString ProcessBiderError::derivePrimary() const { return ERR_STRINGS.value(mType); } -QString ProcessBiderError::deriveSecondary() const { return mSpecific; } - -//=============================================================================================================== -// ProcessBider -//=============================================================================================================== - -//-Constructor------------------------------------------------------------- -//Public: -ProcessBider::ProcessBider(QObject* parent, const QString& name) : - QThread(parent), - mProcessName(name), - mRespawnGrace(30000), - mPollRate(500) -{} - -//-Instance Functions------------------------------------------------------------- -//Private: -ProcessBiderError ProcessBider::doWait() -{ - mWaiter = new ProcessWaiter(mProcessName); - mWaiter->setPollRate(mPollRate); - - // Block outer access until waiting - QWriteLocker writeLock(&mRWLock); - - // Wait until process has stopped running for grace period - quint32 procId; - do - { - // Yield for grace period - emit statusChanged(LOG_EVENT_BIDE_GRACE.arg(mRespawnGrace).arg(mProcessName)); - if(mRespawnGrace > 0) - QThread::sleep(mRespawnGrace); - - // Find process ID by name - procId = Qx::processId(mProcessName); - - // Check that process was found (is running) - if(procId) - { - emit statusChanged(LOG_EVENT_BIDE_RUNNING.arg(mProcessName)); - - // Attempt to wait on process to terminate - emit statusChanged(LOG_EVENT_BIDE_ON.arg(mProcessName)); - mWaiter->updateId(procId); - writeLock.unlock(); // To allow close attempts - if(!mWaiter->wait()) // Blocks until process ends - { - ProcessBiderError err(ProcessBiderError::Wait, mProcessName); - emit errorOccurred(err); - return err; - } - writeLock.relock(); - - emit statusChanged(LOG_EVENT_BIDE_QUIT.arg(mProcessName)); - } - } - while(procId); - - // Return success - emit statusChanged(LOG_EVENT_BIDE_FINISHED.arg(mProcessName)); - return ProcessBiderError(); -} - -void ProcessBider::run() -{ - ProcessBiderError status = doWait(); - qxDelete(mWaiter); - emit bideFinished(status); -} - -//Public: -void ProcessBider::setProcessName(const QString& name) -{ - mRWLock.lockForWrite(); - mProcessName = name; - mRWLock.unlock(); -} -void ProcessBider::setRespawnGrace(uint respawnGrace) -{ - mRWLock.lockForWrite(); - mRespawnGrace = respawnGrace; - mRWLock.unlock(); -} - -void ProcessBider::setPollRate(uint pollRate) -{ - mRWLock.lockForWrite(); - mPollRate = pollRate; - mRWLock.unlock(); -} - -/* TODO: Since this doesn't allow explicitly abandoning the wait, if for some reason the process opens itself over and over, - * it's still possible to get stuck in a dead lock even if the close is successful - */ -ProcessBiderError ProcessBider::closeProcess() -{ - QReadLocker readLocker(&mRWLock); - if(mWaiter && mWaiter->isWaiting() && !mWaiter->close()) - { - ProcessBiderError err(ProcessBiderError::Close, mProcessName); - emit errorOccurred(err); - return err; - } - - return ProcessBiderError(); -} - -//-Signals & Slots------------------------------------------------------------------------------------------------------------ -//Public Slots: -void ProcessBider::start() -{ - // Start new thread for waiting - if(!isRunning()) - QThread::start(); -} diff --git a/app/src/tools/processbider.h b/app/src/tools/processbider.h deleted file mode 100644 index 3595f20..0000000 --- a/app/src/tools/processbider.h +++ /dev/null @@ -1,129 +0,0 @@ -#ifndef PROCESSWAITER_H -#define PROCESSWAITER_H - -// Qt Includes -#include -#include - -// Qx Includes -#include -#include - -/* This uses the approach of sub-classing QThread instead of the worker/object model. This means that by default there is no event - * loop running in the new thread (not needed with current setup), and that only the contents of run() take place in the new thread, - * with everything else happening in the thread that contains the instance of this class. - * - * This does mean that a Mutex must be used to protected against access to the same data between threads where necessary, but in - * this case that is desirable as when the parent thread calls `closeProcess()` we want it to get blocked if the run() thread is - * busy doing work until it goes back to waiting (or the wait process stops on its own), since we want to make sure that the wait - * process has ended before Driver takes its next steps. - * - * If for whatever reason the worker/object approach is desired in the future, an alternative to achieve the same thing would be to - * use a QueuedBlocking connection between the signal emitted by the worker instance method `endProcess()` (which acts as the - * interface to the object) and the slot in the object instance; however, in order for the object to be able to process the quit - * signal it cannot be sitting there blocked by WaitOnSingleObject (since it's now that threads responsibility to perform the quit - * instead of the thread that emitted the signal), so instead RegisterWaitForSingleObject would have to be used, which may have - * caveats since a thread spawned by the OS is used to trigger the specified callback function. It would potentially be safe if that - * callback function simply emits an internal signal with a queued connection that then triggers the thread managing the object to - * handle the quit upon its next event loop cycle. - * - * NOTE: Technically the thread synchronization here is imperfect as a blocked closeProcess() is unlocked one step before the wait - * actually starts so there could be a race between that and the waiter being marked as "in wait". Not a great way to avoid this though - * since the lock can't be unlocked any later since the waiting thread deadlocks once the wait is started. - */ - -class QX_ERROR_TYPE(ProcessBiderError, "ProcessBiderError", 1235) -{ - friend class ProcessBider; - //-Class Enums------------------------------------------------------------- -public: - enum Type - { - NoError = 0, - Wait = 1, - Close = 2 - }; - - //-Class Variables------------------------------------------------------------- -private: - static inline const QHash ERR_STRINGS{ - {NoError, u""_s}, - {Wait, u"Could not setup a wait on the process."_s}, - {Close, u"Could not close the wait on process."_s}, - }; - - //-Instance Variables------------------------------------------------------------- -private: - Type mType; - QString mSpecific; - - //-Constructor------------------------------------------------------------- -private: - ProcessBiderError(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 ProcessWaiter; - -class ProcessBider : public QThread -{ - Q_OBJECT -//-Class Variables------------------------------------------------------------------------------------------------------ -private: - // Status Messages - static inline const QString LOG_EVENT_BIDE_GRACE = u"Waiting %1 seconds for process %2 to be running"_s; - static inline const QString LOG_EVENT_BIDE_RUNNING = u"Wait-on process %1 is running"_s; - static inline const QString LOG_EVENT_BIDE_ON = u"Waiting for process %1 to finish"_s; - static inline const QString LOG_EVENT_BIDE_QUIT = u"Wait-on process %1 has finished"_s; - static inline const QString LOG_EVENT_BIDE_FINISHED = u"Wait-on process %1 was not running after the grace period"_s; - -//-Instance Variables------------------------------------------------------------------------------------------------------------ -private: - // Work - ProcessWaiter* mWaiter; - QReadWriteLock mRWLock; - - // Process Info - QString mProcessName; - uint mRespawnGrace; - uint mPollRate; - -//-Constructor------------------------------------------------------------------------------------------------- -public: - ProcessBider(QObject* parent = nullptr, const QString& name = {}); - -//-Instance Functions--------------------------------------------------------------------------------------------------------- -private: - // Run in wait thread - ProcessBiderError doWait(); - void run() override; - -public: - // Run in external thread - void setProcessName(const QString& name); - void setRespawnGrace(uint respawnGrace); - void setPollRate(uint pollRate); // Ignored on Windows - ProcessBiderError closeProcess(); - -//-Signals & Slots------------------------------------------------------------------------------------------------------------ -public slots: - void start(); - -signals: - void statusChanged(QString statusMessage); - void errorOccurred(ProcessBiderError errorMessage); - void bideFinished(ProcessBiderError errorStatus); -}; - -#endif // PROCESSWAITER_H diff --git a/app/src/tools/processbider_p.h b/app/src/tools/processbider_p.h deleted file mode 100644 index 78900b4..0000000 --- a/app/src/tools/processbider_p.h +++ /dev/null @@ -1,73 +0,0 @@ -#ifndef PROCESSWAITER_P_H -#define PROCESSWAITER_P_H - -#include -#include -#ifdef __linux__ - #include -#endif -#ifdef _WIN32 - typedef void* HANDLE; -#endif - -class ProcessWaiter -{ -//-Instance Variables------------------------------------------------------------------------------------------------------------ -private: - bool mWaiting; - QString mName; - quint32 mId; - uint mPollRate; - QMutex mMutex; - -#ifdef _WIN32 - HANDLE mHandle; -#endif -#ifdef __linux__ - QWaitCondition mCloseNotifier; -#endif - -//-Constructor------------------------------------------------------------------------------------------------- -public: - ProcessWaiter(const QString& name) : - mWaiting(false), - mName(name), - mId(0), - mPollRate(500) - {} - -//-Instance Functions--------------------------------------------------------------------------------------------------------- -private: - bool _wait(); - bool _close(); - -public: - // Used in waiting thread - bool wait() - { - mWaiting = true; - bool r =_wait(); - mWaiting = false; - return r; - } - - // Used from external thread; - void updateId(quint32 id) - { - mMutex.lock(); - mId = id; - mMutex.unlock(); - }; - - void setPollRate(uint pollRate) - { - mMutex.lock(); - mPollRate = pollRate; - mMutex.unlock(); - } - - bool isWaiting() { return mWaiting; } - bool close() { return mWaiting ? _close() : true; } -}; - -#endif // PROCESSWAITER_P_H diff --git a/app/src/tools/processbider_p_linux.cpp b/app/src/tools/processbider_p_linux.cpp deleted file mode 100644 index 74e55ec..0000000 --- a/app/src/tools/processbider_p_linux.cpp +++ /dev/null @@ -1,48 +0,0 @@ -// Unit Include -#include "processbider_p.h" - -// Qt Includes -#include - -// Qx Includes -#include - -//=============================================================================================================== -// ProcessWaiter -//=============================================================================================================== - -//-Instance Functions------------------------------------------------------------- -//Public: -bool ProcessWaiter::_wait() -{ - // Poll for process existence - QString currentName = mName; - while(currentName == mName) - { - QThread::msleep(mPollRate); - mMutex.lock(); // Don't allow close during check - currentName = Qx::processName(mId); - mMutex.unlock(); - } - - // Notify that the process closed - mCloseNotifier.wakeAll(); - - return true; -} - -bool ProcessWaiter::_close() -{ - // NOTE: Does not handle killing processes that require greater permissions - - // Try clean close first - Qx::cleanKillProcess(mId); - - // See if process closes (max 2 seconds, though al) - mMutex.lock(); - if(mCloseNotifier.wait(&mMutex, std::max(uint(2000), mPollRate + 100))) - return true; - - // Force close - return !Qx::forceKillProcess(mId).isValid(); -} diff --git a/app/src/tools/processbider_p_win.cpp b/app/src/tools/processbider_p_win.cpp deleted file mode 100644 index afdf1c7..0000000 --- a/app/src/tools/processbider_p_win.cpp +++ /dev/null @@ -1,137 +0,0 @@ -// Unit Include -#include "processbider_p.h" - -// Qx Includes -#include -#include -#include - -// Windows Include -#include - -namespace -{ - -bool closeAdminProcess(DWORD processId, bool force) -{ - /* Killing an elevated process from this process while it is unelevated requires (without COM non-sense) starting - * a new process as admin to do the job. While a special purpose executable could be made, taskkill already - * perfectly suitable here - */ - - // Setup taskkill args - QString tkArgs; - if(force) - tkArgs += u"/F "_s; - tkArgs += u"/PID "_s; - tkArgs += QString::number(processId); - const std::wstring tkArgsStd = tkArgs.toStdWString(); - - // Setup taskkill info - SHELLEXECUTEINFOW tkExecInfo = {0}; - tkExecInfo.cbSize = sizeof(SHELLEXECUTEINFOW); // Required - tkExecInfo.fMask = SEE_MASK_NOCLOSEPROCESS; // Causes hProcess member to be set to process handle - tkExecInfo.hwnd = NULL; - tkExecInfo.lpVerb = L"runas"; - tkExecInfo.lpFile = L"taskkill"; - tkExecInfo.lpParameters = tkArgsStd.data(); - tkExecInfo.lpDirectory = NULL; - tkExecInfo.nShow = SW_HIDE; - - // Start taskkill - if(!ShellExecuteEx(&tkExecInfo)) - return false; - - // Check for handle - HANDLE tkHandle = tkExecInfo.hProcess; - if(!tkHandle) - return false; - - // Wait for taskkill to finish (should be fast) - if(WaitForSingleObject(tkHandle, 5000) != WAIT_OBJECT_0) - return false; - - DWORD exitCode; - if(!GetExitCodeProcess(tkHandle, &exitCode)) - return false; - - // Cleanup taskkill handle - CloseHandle(tkHandle); - - // Return taskkill result - return exitCode == 0; -} - -} - -//=============================================================================================================== -// ProcessWaiter -//=============================================================================================================== - -//-Instance Functions------------------------------------------------------------- -//Public: -bool ProcessWaiter::_wait() -{ - // Prevent changes while setting up the wait - QMutexLocker mutLock(&mMutex); - - // Get process handle and see if it is valid - DWORD rights = PROCESS_QUERY_LIMITED_INFORMATION | SYNCHRONIZE; - if((mHandle = OpenProcess(rights, FALSE, mId)) == NULL) // Can use Qx::lastError() for error info here if desired - return false; - - // Attempt to wait on process to terminate - mutLock.unlock(); // Allow changes while waiting - DWORD waitError = WaitForSingleObject(mHandle, INFINITE); - mutLock.relock(); // Prevent changes during cleanup - - // Close handle to process - CloseHandle(mHandle); - mHandle = nullptr; - - /* Here the status can technically can be WAIT_ABANDONED, WAIT_OBJECT_0, WAIT_TIMEOUT, or WAIT_FAILED, but the first - * and third should never occur here (the wait won't ever be abandoned, and the timeout is infinite), so this check is fine - */ - if(waitError != WAIT_OBJECT_0) // Can use Qx::lastError() for error info here if desired - return false; - - return true; -} - -bool ProcessWaiter::_close() -{ - if(!mHandle) - return true; - - // Prevent wait setup/cleanup during closure and auto-unlock when done - QMutexLocker mutLock(&mMutex); - - // Check if admin rights are needed (CLIFp shouldn't be run as admin, but check anyway) - bool selfElevated; - if(Qx::processIsElevated(selfElevated).isValid()) - selfElevated = false; // If check fails, assume CLIFP is not elevated to be safe - bool waitProcessElevated; - if(Qx::processIsElevated(waitProcessElevated, mId).isValid()) - waitProcessElevated = true; // If check fails, assume process is elevated to be safe - - bool elevate = !selfElevated && waitProcessElevated; - - // Try clean close first - if(!elevate) - Qx::cleanKillProcess(mId); - else - closeAdminProcess(mId, false); - - // Wait for process to close (allow up to 2 seconds) - DWORD waitRes = WaitForSingleObject(mHandle, 2000); - - // See if process closed - if(waitRes == WAIT_OBJECT_0) - return true; - - // Force close - if(!elevate) - return !Qx::forceKillProcess(mId).isValid(); - else - return closeAdminProcess(mId, true); -} From b570f56d2e01a0bb2fc3ec9e28a004ce2c64d42e Mon Sep 17 00:00:00 2001 From: Christian Heimlich Date: Thu, 23 Nov 2023 13:58:07 -0500 Subject: [PATCH 24/26] Add core logging helpers to reduce 'NAME' usage. --- app/src/command/c-download.cpp | 16 +-- app/src/command/c-download.h | 6 +- app/src/command/c-link.cpp | 20 ++-- app/src/command/c-link.h | 6 +- app/src/command/c-link_win.cpp | 4 +- app/src/command/c-play.cpp | 30 ++--- app/src/command/c-play.h | 4 +- app/src/command/c-prepare.cpp | 8 +- app/src/command/c-prepare.h | 4 +- app/src/command/c-run.cpp | 6 +- app/src/command/c-run.h | 6 +- app/src/command/c-share.cpp | 14 +-- app/src/command/c-share.h | 4 +- app/src/command/c-show.cpp | 6 +- app/src/command/c-show.h | 4 +- app/src/command/c-update.cpp | 46 ++++---- app/src/command/c-update.h | 4 +- app/src/command/command.cpp | 20 +++- app/src/command/command.h | 16 ++- app/src/command/title-command.cpp | 34 +++--- app/src/command/title-command.h | 2 +- app/src/kernel/core.cpp | 147 +++++++++++++------------ app/src/kernel/core.h | 30 +++-- app/src/kernel/driver.cpp | 67 ++++++----- app/src/kernel/driver.h | 10 ++ app/src/task/t-bideprocess.cpp | 14 +-- app/src/task/t-download.cpp | 16 +-- app/src/task/t-exec.cpp | 24 ++-- app/src/task/t-exec_win.cpp | 4 +- app/src/task/t-extra.cpp | 4 +- app/src/task/t-extract.cpp | 4 +- app/src/task/t-generic.cpp | 6 +- app/src/task/t-message.cpp | 2 +- app/src/task/t-mount.cpp | 2 +- app/src/task/t-sleep.cpp | 6 +- app/src/task/task.cpp | 6 + app/src/task/task.h | 16 ++- app/src/tools/blockingprocessmanager.h | 2 +- app/src/tools/deferredprocessmanager.h | 4 +- app/src/tools/mounter_proxy.cpp | 19 ++-- app/src/tools/mounter_proxy.h | 9 +- app/src/tools/mounter_qmp.cpp | 23 ++-- app/src/tools/mounter_qmp.h | 9 +- app/src/tools/mounter_router.cpp | 19 ++-- app/src/tools/mounter_router.h | 7 +- 45 files changed, 401 insertions(+), 309 deletions(-) diff --git a/app/src/command/c-download.cpp b/app/src/command/c-download.cpp index 0702f06..fb8ad67 100644 --- a/app/src/command/c-download.cpp +++ b/app/src/command/c-download.cpp @@ -38,9 +38,9 @@ CDownload::CDownload(Core& coreRef) : Command(coreRef) {} //-Instance Functions------------------------------------------------------------- //Protected: -QList CDownload::options() { return CL_OPTIONS_SPECIFIC + Command::options(); } -QSet CDownload::requiredOptions() { return CL_OPTIONS_REQUIRED + Command::requiredOptions(); } -QString CDownload::name() { return NAME; } +QList CDownload::options() const { return CL_OPTIONS_SPECIFIC + Command::options(); } +QSet CDownload::requiredOptions() const { return CL_OPTIONS_REQUIRED + Command::requiredOptions(); } +QString CDownload::name() const { return NAME; } Qx::Error CDownload::perform() { @@ -61,10 +61,10 @@ Qx::Error CDownload::perform() if(pItr == playlists.cend()) { CDownloadError err(CDownloadError::InvalidPlaylist, playlistName); - mCore.postError(NAME, err); + postError(err); return err; } - mCore.logEvent(NAME, LOG_EVENT_PLAYLIST_MATCH.arg(pItr->id().toString(QUuid::WithoutBraces))); + logEvent(LOG_EVENT_PLAYLIST_MATCH.arg(pItr->id().toString(QUuid::WithoutBraces))); // Queue downloads for each game TDownload* downloadTask = new TDownload(&mCore); @@ -79,13 +79,13 @@ Qx::Error CDownload::perform() Fp::GameData gameData; if(Fp::DbError gdErr = db->getGameData(gameData, pg.gameId()); gdErr.isValid()) { - mCore.postError(NAME, gdErr); + postError(gdErr); return gdErr; } if(gameData.isNull()) { - mCore.logEvent(NAME, LOG_EVENT_NON_DATAPACK.arg(pg.gameId().toString(QUuid::WithoutBraces))); + logEvent(LOG_EVENT_NON_DATAPACK.arg(pg.gameId().toString(QUuid::WithoutBraces))); continue; } @@ -101,7 +101,7 @@ Qx::Error CDownload::perform() if(downloadTask->isEmpty()) { - mCore.logEvent(NAME, LOG_EVENT_NO_OP); + logEvent(LOG_EVENT_NO_OP); return CDownloadError(); } diff --git a/app/src/command/c-download.h b/app/src/command/c-download.h index 71f1697..5a1f2a5 100644 --- a/app/src/command/c-download.h +++ b/app/src/command/c-download.h @@ -80,9 +80,9 @@ class CDownload : public Command //-Instance Functions------------------------------------------------------------------------------------------------------ protected: - QList options() override; - QSet requiredOptions() override; - QString name() override; + QList options() const override; + QSet requiredOptions() const override; + QString name() const override; Qx::Error perform() override; }; REGISTER_COMMAND(CDownload::NAME, CDownload, CDownload::DESCRIPTION); diff --git a/app/src/command/c-link.cpp b/app/src/command/c-link.cpp index 372d41a..959eb0b 100644 --- a/app/src/command/c-link.cpp +++ b/app/src/command/c-link.cpp @@ -34,9 +34,9 @@ CLink::CLink(Core& coreRef) : TitleCommand(coreRef) {} //-Instance Functions------------------------------------------------------------- //Protected: -QList CLink::options() { return CL_OPTIONS_SPECIFIC + TitleCommand::options(); } -QSet CLink::requiredOptions() { return CL_OPTIONS_REQUIRED + TitleCommand::requiredOptions(); } -QString CLink::name() { return NAME; } +QList CLink::options() const { return CL_OPTIONS_SPECIFIC + TitleCommand::options(); } +QSet CLink::requiredOptions() const { return CL_OPTIONS_REQUIRED + TitleCommand::requiredOptions(); } +QString CLink::name() const { return NAME; } Qx::Error CLink::perform() { @@ -59,7 +59,7 @@ Qx::Error CLink::perform() Fp::DbError dbError = database->getEntry(entry_v, shortcutId); if(dbError.isValid()) { - mCore.postError(NAME, dbError); + postError(dbError); return dbError; } @@ -76,7 +76,7 @@ Qx::Error CLink::perform() Fp::DbError dbError = database->getEntry(entry_v, addApp.parentId()); if(dbError.isValid()) { - mCore.postError(NAME, dbError); + postError(dbError); return dbError; } Q_ASSERT(std::holds_alternative(entry_v)); @@ -96,7 +96,7 @@ Qx::Error CLink::perform() shortcutDir = mParser.value(CL_OPTION_PATH); else { - mCore.logEvent(NAME, LOG_EVENT_NO_PATH); + logEvent(LOG_EVENT_NO_PATH); // Prompt user for path Core::ExistingDirRequest edr{ @@ -107,12 +107,12 @@ Qx::Error CLink::perform() if(selectedPath.isEmpty()) { - mCore.logEvent(NAME, LOG_EVENT_DIAG_CANCEL); + logEvent(LOG_EVENT_DIAG_CANCEL); return CLinkError(); } else { - mCore.logEvent(NAME, LOG_EVENT_SEL_PATH.arg(QDir::toNativeSeparators(selectedPath))); + logEvent(LOG_EVENT_SEL_PATH.arg(QDir::toNativeSeparators(selectedPath))); shortcutDir = selectedPath; } } @@ -123,10 +123,10 @@ Qx::Error CLink::perform() if(!shortcutDir.mkpath(shortcutDir.absolutePath())) { CLinkError err(CLinkError::InvalidPath); - mCore.postError(NAME, err); + postError(err); return err; } - mCore.logEvent(NAME, LOG_EVENT_CREATED_DIR_PATH.arg(QDir::toNativeSeparators(shortcutDir.absolutePath()))); + logEvent(LOG_EVENT_CREATED_DIR_PATH.arg(QDir::toNativeSeparators(shortcutDir.absolutePath()))); } // Create shortcut diff --git a/app/src/command/c-link.h b/app/src/command/c-link.h index 70778af..0f5e338 100644 --- a/app/src/command/c-link.h +++ b/app/src/command/c-link.h @@ -96,9 +96,9 @@ class CLink : public TitleCommand Qx::Error createShortcut(const QString& name, const QDir& dir, QUuid id); protected: - QList options() override; - QSet requiredOptions() override; - QString name() override; + QList options() const override; + QSet requiredOptions() const override; + QString name() const override; Qx::Error perform() override ; }; REGISTER_COMMAND(CLink::NAME, CLink, CLink::DESCRIPTION); diff --git a/app/src/command/c-link_win.cpp b/app/src/command/c-link_win.cpp index 0999d70..d97dab1 100644 --- a/app/src/command/c-link_win.cpp +++ b/app/src/command/c-link_win.cpp @@ -31,11 +31,11 @@ Qx::Error CLink::createShortcut(const QString& name, const QDir& dir, QUuid id) // Check for creation failure if(shortcutError.isValid()) { - mCore.postError(NAME, shortcutError); + postError(shortcutError); return shortcutError; } else - mCore.logEvent(NAME, LOG_EVENT_CREATED_SHORTCUT.arg(id.toString(QUuid::WithoutBraces), QDir::toNativeSeparators(fullShortcutPath))); + logEvent(LOG_EVENT_CREATED_SHORTCUT.arg(id.toString(QUuid::WithoutBraces), QDir::toNativeSeparators(fullShortcutPath))); // Return success return CLinkError(); diff --git a/app/src/command/c-play.cpp b/app/src/command/c-play.cpp index 583cb11..5331cc2 100644 --- a/app/src/command/c-play.cpp +++ b/app/src/command/c-play.cpp @@ -73,14 +73,14 @@ QString CPlay::getServerOverride(const Fp::GameData& gd) { QString override = gd.isNull() ? QString() : gd.parameters().server(); if(!override.isNull()) - mCore.logEvent(NAME, LOG_EVENT_SERVER_OVERRIDE.arg(override)); + logEvent(LOG_EVENT_SERVER_OVERRIDE.arg(override)); return override; } Qx::Error CPlay::handleEntry(const Fp::Game& game) { - mCore.logEvent(NAME, LOG_EVENT_ID_MATCH_TITLE.arg(game.title())); + logEvent(LOG_EVENT_ID_MATCH_TITLE.arg(game.title())); Qx::Error sError; Fp::Db* db = mCore.fpInstall().database(); @@ -89,7 +89,7 @@ Qx::Error CPlay::handleEntry(const Fp::Game& game) Fp::GameData gameData; if(Fp::DbError gdErr = db->getGameData(gameData, game.id()); gdErr.isValid()) { - mCore.postError(NAME, gdErr); + postError(gdErr); return gdErr; } bool hasDatapack = !gameData.isNull(); @@ -104,7 +104,7 @@ Qx::Error CPlay::handleEntry(const Fp::Game& game) // Handle datapack tasks if(hasDatapack) { - mCore.logEvent(NAME, LOG_EVENT_DATA_PACK_TITLE); + logEvent(LOG_EVENT_DATA_PACK_TITLE); if(sError = mCore.enqueueDataPackTasks(gameData); sError.isValid()) return sError; @@ -119,7 +119,7 @@ Qx::Error CPlay::handleEntry(const Fp::Game& game) addAppSearchError = db->queryEntrys(addAppSearchResult, addAppFilter); if(addAppSearchError.isValid()) { - mCore.postError(NAME, addAppSearchError); + postError(addAppSearchError); return addAppSearchError; } @@ -135,7 +135,7 @@ Qx::Error CPlay::handleEntry(const Fp::Game& game) // Enqueue if auto-run before if(addApp.isAutorunBefore()) { - mCore.logEvent(NAME, LOG_EVENT_FOUND_AUTORUN.arg(addApp.name())); + logEvent(LOG_EVENT_FOUND_AUTORUN.arg(addApp.name())); if(sError = enqueueAdditionalApp(addApp, game.platformName(), Task::Stage::Auxiliary); sError.isValid()) return sError; @@ -154,7 +154,7 @@ Qx::Error CPlay::handleEntry(const Fp::Game& game) Qx::Error CPlay::handleEntry(const Fp::AddApp& addApp) { - mCore.logEvent(NAME, LOG_EVENT_ID_MATCH_ADDAPP.arg(addApp.name(), + logEvent(LOG_EVENT_ID_MATCH_ADDAPP.arg(addApp.name(), addApp.parentId().toString(QUuid::WithoutBraces))); Qx::Error sError; @@ -165,7 +165,7 @@ Qx::Error CPlay::handleEntry(const Fp::AddApp& addApp) Fp::GameData parentGameData; if(Fp::DbError gdErr = db->getGameData(parentGameData, parentId); gdErr.isValid()) { - mCore.postError(NAME, gdErr); + postError(gdErr); return gdErr; } bool hasDatapack = !parentGameData.isNull(); @@ -181,7 +181,7 @@ Qx::Error CPlay::handleEntry(const Fp::AddApp& addApp) // Handle datapack tasks if(hasDatapack) { - mCore.logEvent(NAME, LOG_EVENT_DATA_PACK_TITLE); + logEvent(LOG_EVENT_DATA_PACK_TITLE); if(sError = mCore.enqueueDataPackTasks(parentGameData); sError.isValid()) return sError; @@ -194,7 +194,7 @@ Qx::Error CPlay::handleEntry(const Fp::AddApp& addApp) if(Fp::DbError pge = db->queryEntrys(parentResult, parentFilter); pge.isValid()) { - mCore.postError(NAME, pge); + postError(pge); return pge; } @@ -292,8 +292,8 @@ Qx::Error CPlay::enqueueGame(const Fp::Game& game, const Fp::GameData& gameData, } //Protected: -QList CPlay::options() { return CL_OPTIONS_SPECIFIC + TitleCommand::options(); } -QString CPlay::name() { return NAME; } +QList CPlay::options() const { return CL_OPTIONS_SPECIFIC + TitleCommand::options(); } +QString CPlay::name() const { return NAME; } Qx::Error CPlay::perform() { @@ -305,7 +305,7 @@ Qx::Error CPlay::perform() if(!urlMatch.hasMatch()) { CPlayError err(CPlayError::InvalidUrl); - mCore.postError(NAME, err); + postError(err); return err; } @@ -315,7 +315,7 @@ Qx::Error CPlay::perform() return ide; - mCore.logEvent(NAME, LOG_EVENT_HANDLING_AUTO); + logEvent(LOG_EVENT_HANDLING_AUTO); // Get entry via ID Fp::Db* db = mCore.fpInstall().database(); @@ -323,7 +323,7 @@ Qx::Error CPlay::perform() Fp::Entry entry; if(Fp::DbError eErr = db->getEntry(entry, titleId); eErr.isValid()) { - mCore.postError(NAME, eErr); + postError(eErr); return eErr; } diff --git a/app/src/command/c-play.h b/app/src/command/c-play.h index b50813d..a76c41f 100644 --- a/app/src/command/c-play.h +++ b/app/src/command/c-play.h @@ -100,8 +100,8 @@ class CPlay : public TitleCommand Qx::Error enqueueGame(const Fp::Game& game, const Fp::GameData& gameData, Task::Stage taskStage); protected: - QList options() override; - QString name() override; + QList options() const override; + QString name() const override; Qx::Error perform() override; public: diff --git a/app/src/command/c-prepare.cpp b/app/src/command/c-prepare.cpp index 4993cd8..f233d03 100644 --- a/app/src/command/c-prepare.cpp +++ b/app/src/command/c-prepare.cpp @@ -14,8 +14,8 @@ CPrepare::CPrepare(Core& coreRef) : TitleCommand(coreRef) {} //-Instance Functions------------------------------------------------------------- //Protected: -QList CPrepare::options() { return TitleCommand::options(); } -QString CPrepare::name() { return NAME; } +QList CPrepare::options() const { return TitleCommand::options(); } +QString CPrepare::name() const { return NAME; } Qx::Error CPrepare::perform() { @@ -28,7 +28,7 @@ Qx::Error CPrepare::perform() Fp::GameData titleGameData; if(Fp::DbError gdErr = mCore.fpInstall().database()->getGameData(titleGameData, id); gdErr.isValid()) { - mCore.postError(NAME, gdErr); + postError(gdErr); return gdErr; } @@ -40,7 +40,7 @@ Qx::Error CPrepare::perform() mCore.setStatus(STATUS_PREPARE, id.toString(QUuid::WithoutBraces)); } else - mCore.logError(NAME, Qx::GenericError(Qx::Warning, 12141, LOG_WRN_PREP_NOT_DATA_PACK.arg(id.toString(QUuid::WithoutBraces)))); + logError(Qx::GenericError(Qx::Warning, 12141, LOG_WRN_PREP_NOT_DATA_PACK.arg(id.toString(QUuid::WithoutBraces)))); // Return success return Qx::Error(); diff --git a/app/src/command/c-prepare.h b/app/src/command/c-prepare.h index bd28800..8c9478d 100644 --- a/app/src/command/c-prepare.h +++ b/app/src/command/c-prepare.h @@ -28,8 +28,8 @@ class CPrepare : public TitleCommand //-Instance Functions------------------------------------------------------------------------------------------------------ protected: - QList options() override; - QString name() override; + QList options() const override; + QString name() const override; Qx::Error perform() override; public: diff --git a/app/src/command/c-run.cpp b/app/src/command/c-run.cpp index b31e083..76a9553 100644 --- a/app/src/command/c-run.cpp +++ b/app/src/command/c-run.cpp @@ -37,9 +37,9 @@ CRun::CRun(Core& coreRef) : Command(coreRef) {} //-Instance Functions------------------------------------------------------------- //Protected: -QList CRun::options() { return CL_OPTIONS_SPECIFIC + Command::options(); } -QSet CRun::requiredOptions() { return CL_OPTIONS_REQUIRED + Command::requiredOptions(); } -QString CRun::name() { return NAME; } +QList CRun::options() const { return CL_OPTIONS_SPECIFIC + Command::options(); } +QSet CRun::requiredOptions() const { return CL_OPTIONS_REQUIRED + Command::requiredOptions(); } +QString CRun::name() const { return NAME; } Qx::Error CRun::perform() { diff --git a/app/src/command/c-run.h b/app/src/command/c-run.h index 5deded8..78a2af6 100644 --- a/app/src/command/c-run.h +++ b/app/src/command/c-run.h @@ -78,9 +78,9 @@ class CRun : public Command //-Instance Functions------------------------------------------------------------------------------------------------------ protected: - QList options() override; - QSet requiredOptions() override; - QString name() override; + QList options() const override; + QSet requiredOptions() const override; + QString name() const override; Qx::Error perform() override; public: diff --git a/app/src/command/c-share.cpp b/app/src/command/c-share.cpp index aa1f459..afc298f 100644 --- a/app/src/command/c-share.cpp +++ b/app/src/command/c-share.cpp @@ -41,20 +41,20 @@ CShare::CShare(Core& coreRef) : TitleCommand(coreRef) {} //-Instance Functions------------------------------------------------------------- //Protected: -QList CShare::options() { return CL_OPTIONS_SPECIFIC + TitleCommand::options(); } -QString CShare::name() { return NAME; } +QList CShare::options() const { return CL_OPTIONS_SPECIFIC + TitleCommand::options(); } +QString CShare::name() const { return NAME; } Qx::Error CShare::perform() { // Prioritize scheme (un)registration if(mParser.isSet(CL_OPTION_CONFIGURE)) { - mCore.logEvent(NAME, LOG_EVENT_REGISTRATION); + logEvent(LOG_EVENT_REGISTRATION); if(!Qx::setDefaultProtocolHandler(SCHEME, SCHEME_NAME)) { CShareError err(CShareError::RegistrationFailed); - mCore.postError(NAME, err); + postError(err); return err; } @@ -68,7 +68,7 @@ Qx::Error CShare::perform() } else if(mParser.isSet(CL_OPTION_UNCONFIGURE)) { - mCore.logEvent(NAME, LOG_EVENT_UNREGISTRATION); + logEvent(LOG_EVENT_UNREGISTRATION); #ifdef __linux__ // Function is too jank on linux right now, so always fail/no-op there if(true) @@ -77,7 +77,7 @@ Qx::Error CShare::perform() #endif { CShareError err(CShareError::UnregistrationFailed); - mCore.postError(NAME, err); + postError(err); return err; } @@ -100,7 +100,7 @@ Qx::Error CShare::perform() // Generate URL QString idStr = shareId.toString(QUuid::WithoutBraces); QString shareUrl = mParser.isSet(CL_OPTION_UNIVERSAL) ? SCHEME_TEMPLATE_UNI.arg(idStr) : SCHEME_TEMPLATE_STD.arg(idStr); - mCore.logEvent(NAME, LOG_EVENT_URL.arg(shareUrl)); + logEvent(LOG_EVENT_URL.arg(shareUrl)); // Add URL to clipboard mCore.requestClipboardUpdate(shareUrl); diff --git a/app/src/command/c-share.h b/app/src/command/c-share.h index c7196e3..54b7892 100644 --- a/app/src/command/c-share.h +++ b/app/src/command/c-share.h @@ -100,8 +100,8 @@ class CShare : public TitleCommand //-Instance Functions------------------------------------------------------------------------------------------------------ protected: - QList options() override; - QString name() override; + QList options() const override; + QString name() const override; Qx::Error perform() override; }; REGISTER_COMMAND(CShare::NAME, CShare, CShare::DESCRIPTION); diff --git a/app/src/command/c-show.cpp b/app/src/command/c-show.cpp index 7fdeeb4..2671d84 100644 --- a/app/src/command/c-show.cpp +++ b/app/src/command/c-show.cpp @@ -38,8 +38,8 @@ CShow::CShow(Core& coreRef) : Command(coreRef) {} //-Instance Functions------------------------------------------------------------- //Protected: -QList CShow::options() { return CL_OPTIONS_SPECIFIC + Command::options(); } -QString CShow::name() { return NAME; } +QList CShow::options() const { return CL_OPTIONS_SPECIFIC + Command::options(); } +QString CShow::name() const { return NAME; } Qx::Error CShow::perform() { @@ -65,7 +65,7 @@ Qx::Error CShow::perform() else { CShowError err(CShowError::MissingThing); - mCore.postError(NAME, err); + postError(err); return err; } diff --git a/app/src/command/c-show.h b/app/src/command/c-show.h index 679a29a..257a13f 100644 --- a/app/src/command/c-show.h +++ b/app/src/command/c-show.h @@ -80,8 +80,8 @@ class CShow : public Command //-Instance Functions------------------------------------------------------------------------------------------------------ protected: - QList options() override; - QString name() override; + QList options() const override; + QString name() const override; Qx::Error perform() override; }; REGISTER_COMMAND(CShow::NAME, CShow, CShow::DESCRIPTION); diff --git a/app/src/command/c-update.cpp b/app/src/command/c-update.cpp index b2d131a..5d0c27d 100644 --- a/app/src/command/c-update.cpp +++ b/app/src/command/c-update.cpp @@ -209,7 +209,7 @@ QString CUpdate::getTargetAssetName(const QString& tagName) const CUpdateError CUpdate::handleTransfers(const UpdateTransfers& transfers) const { auto doTransfer = [&](const FileTransfer& ft, bool mkpath, bool move, bool overwrite){ - mCore.logEvent(NAME, LOG_EVENT_FILE_TRANSFER.arg(ft.source, ft.dest)); + logEvent(LOG_EVENT_FILE_TRANSFER.arg(ft.source, ft.dest)); if(mkpath) { @@ -225,12 +225,12 @@ CUpdateError CUpdate::handleTransfers(const UpdateTransfers& transfers) const }; // Backup, and note for restore - mCore.logEvent(NAME, LOG_EVENT_BACKUP_FILES); + logEvent(LOG_EVENT_BACKUP_FILES); QList restoreTransfers; QScopeGuard restoreOnFail([&]{ if(!restoreTransfers.isEmpty()) { - mCore.logEvent(NAME, LOG_EVENT_RESTORE_FILES); + logEvent(LOG_EVENT_RESTORE_FILES); for(const auto& t : restoreTransfers) doTransfer(t, false, true, true); } }); @@ -240,20 +240,20 @@ CUpdateError CUpdate::handleTransfers(const UpdateTransfers& transfers) const if(!doTransfer(ft, true, true, true)) { CUpdateError err(CUpdateError::TransferFail, ft.dest); - mCore.postError(NAME, err); + postError(err); return err; } restoreTransfers << FileTransfer{.source = ft.dest, .dest = ft.source}; } // Install - mCore.logEvent(NAME, LOG_EVENT_INSTALL_FILES); + logEvent(LOG_EVENT_INSTALL_FILES); for(const auto& ft : transfers.install) { if(!doTransfer(ft, true, false, false)) { CUpdateError err(CUpdateError::TransferFail, ft.dest); - mCore.postError(NAME, err); + postError(err); return err; } } @@ -268,19 +268,19 @@ CUpdateError CUpdate::checkAndPrepareUpdate() const if(!mCore.blockNewInstances()) { CUpdateError err(CUpdateError::AlreadyOpen); - mCore.postError(NAME, err); + postError(err); return err; } // Check for update mCore.setStatus(STATUS, STATUS_CHECKING); - mCore.logEvent(NAME, LOG_EVENT_CHECKING_FOR_NEWER_VERSION); + logEvent(LOG_EVENT_CHECKING_FOR_NEWER_VERSION); // Get new release data ReleaseData rd; if(CUpdateError ue = getLatestReleaseData(rd); ue.isValid()) { - mCore.postError(NAME, ue); + postError(ue); return ue; } @@ -291,17 +291,17 @@ CUpdateError CUpdate::checkAndPrepareUpdate() const if(newVersion.isNull()) { CUpdateError err(CUpdateError::InvalidReleaseVersion); - mCore.postError(NAME, err); + postError(err); return err; } if(newVersion <= currentVersion) { mCore.postMessage(Message{.text = MSG_NO_UPDATES}); - mCore.logEvent(NAME, MSG_NO_UPDATES); + logEvent(MSG_NO_UPDATES); return CUpdateError(); } - mCore.logEvent(NAME, LOG_EVENT_UPDATE_AVAILABLE.arg(rd.tag_name)); + logEvent(LOG_EVENT_UPDATE_AVAILABLE.arg(rd.tag_name)); // Get current build info BuildInfo bi = mCore.buildInfo(); @@ -315,13 +315,13 @@ CUpdateError CUpdate::checkAndPrepareUpdate() const if(aItr == rd.assets.cend()) { - mCore.postError(NAME, Qx::GenericError(Qx::Warning, 12181, WRN_NO_MATCHING_BUILD_P, WRN_NO_MATCHING_BUILD_S)); + postError(Qx::GenericError(Qx::Warning, 12181, WRN_NO_MATCHING_BUILD_P, WRN_NO_MATCHING_BUILD_S)); return CUpdateError(); } if(mCore.requestQuestionAnswer(QUES_UPDATE.arg(rd.name))) { - mCore.logEvent(NAME, LOG_EVENT_UPDATE_ACCEPED); + logEvent(LOG_EVENT_UPDATE_ACCEPED); // Queue update QDir uDownloadDir = updateDownloadDir(); @@ -354,7 +354,7 @@ CUpdateError CUpdate::checkAndPrepareUpdate() const smPersistCache = true; } else - mCore.logEvent(NAME, LOG_EVENT_UPDATE_REJECTED); + logEvent(LOG_EVENT_UPDATE_REJECTED); return CUpdateError(); } @@ -371,7 +371,7 @@ Qx::Error CUpdate::installUpdate(const QFileInfo& existingAppInfo) const do { - mCore.logEvent(NAME, LOG_EVENT_WAITING_ON_OLD_CLOSE.arg(totalGrace - currentGrace)); + logEvent(LOG_EVENT_WAITING_ON_OLD_CLOSE.arg(totalGrace - currentGrace)); QThread::msleep(step); currentGrace += step; haveLock = mCore.blockNewInstances(); @@ -382,18 +382,18 @@ Qx::Error CUpdate::installUpdate(const QFileInfo& existingAppInfo) const if(!haveLock) { CUpdateError err(CUpdateError::OldProcessNotFinished, "Aborting update."); - mCore.postError(NAME, err); + postError(err); return err; } //-Install update------------------------------------------------------------ - mCore.logEvent(NAME, LOG_EVENT_INSTALLING_UPDATE); + logEvent(LOG_EVENT_INSTALLING_UPDATE); // Ensure old executable exists where expected if(!existingAppInfo.exists()) { CUpdateError err(CUpdateError::InvalidPath, "Missing " + existingAppInfo.absoluteFilePath()); - mCore.postError(NAME, err); + postError(err); return err; } @@ -410,7 +410,7 @@ Qx::Error CUpdate::installUpdate(const QFileInfo& existingAppInfo) const QStringList updateFiles; if(Qx::IoOpReport rep = determineNewFiles(updateFiles, ts.updateRoot); rep.isFailure()) { - mCore.postError(NAME, rep); + postError(rep); return rep; } @@ -421,14 +421,14 @@ Qx::Error CUpdate::installUpdate(const QFileInfo& existingAppInfo) const return err; // Success - mCore.logEvent(NAME, MSG_UPDATE_COMPLETE); + logEvent(MSG_UPDATE_COMPLETE); mCore.postMessage(Message{.text = MSG_UPDATE_COMPLETE}); return CUpdateError(); } //Protected: -QList CUpdate::options() { return CL_OPTIONS_SPECIFIC + Command::options(); } -QString CUpdate::name() { return NAME; } +QList CUpdate::options() const { return CL_OPTIONS_SPECIFIC + Command::options(); } +QString CUpdate::name() const { return NAME; } Qx::Error CUpdate::perform() { diff --git a/app/src/command/c-update.h b/app/src/command/c-update.h index 24088cc..eecfe42 100644 --- a/app/src/command/c-update.h +++ b/app/src/command/c-update.h @@ -196,8 +196,8 @@ class CUpdate : public Command Qx::Error installUpdate(const QFileInfo& existingAppInfo) const; protected: - QList options() override; - QString name() override; + QList options() const override; + QString name() const override; Qx::Error perform() override; public: diff --git a/app/src/command/command.cpp b/app/src/command/command.cpp index e66b58c..dec1087 100644 --- a/app/src/command/command.cpp +++ b/app/src/command/command.cpp @@ -102,7 +102,7 @@ CommandError Command::parse(const QStringList& commandLine) else { CommandError parseErr = CommandError(ERR_INVALID_ARGS).wDetails(mParser.errorText()); - mCore.postError(NAME, parseErr); + postError(parseErr); return parseErr; } } @@ -135,7 +135,7 @@ CommandError Command::checkRequiredOptions() void Command::showHelp() { - mCore.logEvent(name(), LOG_EVENT_C_HELP_SHOWN.arg(name())); + logEvent(LOG_EVENT_C_HELP_SHOWN.arg(name())); // Help string static QString helpStr; @@ -172,8 +172,18 @@ void Command::showHelp() } //Protected: -QList Command::options() { return CL_OPTIONS_STANDARD; } -QSet Command::requiredOptions() { return {}; } +QList Command::options() const { return CL_OPTIONS_STANDARD; } +QSet Command::requiredOptions() const { return {}; } + +// Notifications/Logging (core-forwarders) +void Command::logCommand(QString commandName) const {mCore.logCommand(name(), commandName); } +void Command::logCommandOptions(QString commandOptions) const {mCore.logCommandOptions(name(), commandOptions); } +void Command::logError(Qx::Error error) const {mCore.logError(name(), error); } +void Command::logEvent(QString event) const {mCore.logEvent(name(), event); } +void Command::logTask(const Task* task) const {mCore.logTask(name(), task); } +ErrorCode Command::logFinish(Qx::Error errorState) const {return mCore.logFinish(name(), errorState); } +void Command::postError(Qx::Error error, bool log) const {mCore.postError(name(), error, log); } +int Command::postBlockingError(Qx::Error error, bool log, QMessageBox::StandardButtons bs, QMessageBox::StandardButton def) const {return mCore.postBlockingError(name(), error, log); } //Public: bool Command::requiresFlashpoint() const { return true; } @@ -195,7 +205,7 @@ Qx::Error Command::process(const QStringList& commandLine) processError = checkRequiredOptions(); if(processError.isValid()) { - mCore.postError(NAME, processError); + postError(processError); return processError; } diff --git a/app/src/command/command.h b/app/src/command/command.h index d539acc..54b1e5e 100644 --- a/app/src/command/command.h +++ b/app/src/command/command.h @@ -156,11 +156,21 @@ class Command protected: // Command specific - virtual QList options() = 0; - virtual QSet requiredOptions(); - virtual QString name() = 0; + virtual QList options() const = 0; + virtual QSet requiredOptions() const; + virtual QString name() const = 0; virtual Qx::Error perform() = 0; + // Notifications/Logging (core-forwarders) + void logCommand(QString commandName) const; + void logCommandOptions(QString commandOptions) const; + void logError(Qx::Error error) const; + void logEvent(QString event) const; + void logTask(const Task* task) const; + ErrorCode logFinish(Qx::Error errorState) const; + void postError(Qx::Error error, bool log = true) const; + int postBlockingError(Qx::Error error, bool log = true, QMessageBox::StandardButtons bs = QMessageBox::Ok, QMessageBox::StandardButton def = QMessageBox::NoButton) const; + public: virtual bool requiresFlashpoint() const; virtual bool requiresServices() const; diff --git a/app/src/command/title-command.cpp b/app/src/command/title-command.cpp index 4141c42..c0ba6d2 100644 --- a/app/src/command/title-command.cpp +++ b/app/src/command/title-command.cpp @@ -41,7 +41,7 @@ TitleCommand::TitleCommand(Core& coreRef) : //Private: Qx::Error TitleCommand::randomlySelectId(QUuid& mainIdBuffer, QUuid& subIdBuffer, Fp::Db::LibraryFilter lbFilter) { - mCore.logEvent(NAME, LOG_EVENT_SEL_RAND); + logEvent(LOG_EVENT_SEL_RAND); // Reset buffers mainIdBuffer = QUuid(); @@ -58,7 +58,7 @@ Qx::Error TitleCommand::randomlySelectId(QUuid& mainIdBuffer, QUuid& subIdBuffer searchError = database->queryAllGameIds(mainGameIdQuery, lbFilter); if(searchError.isValid()) { - mCore.postError(NAME, searchError); + postError(searchError); return searchError; } @@ -76,13 +76,13 @@ Qx::Error TitleCommand::randomlySelectId(QUuid& mainIdBuffer, QUuid& subIdBuffer if(!gameId.isNull()) playableIds.append(gameId); else - mCore.logError(NAME, Qx::GenericError(Qx::Warning, 12011, LOG_WRN_INVALID_RAND_ID.arg(gameIdString))); + logError(Qx::GenericError(Qx::Warning, 12011, LOG_WRN_INVALID_RAND_ID.arg(gameIdString))); } - mCore.logEvent(NAME, LOG_EVENT_PLAYABLE_COUNT.arg(QLocale(QLocale::system()).toString(playableIds.size()))); + logEvent(LOG_EVENT_PLAYABLE_COUNT.arg(QLocale(QLocale::system()).toString(playableIds.size()))); // Select main game mainIdBuffer = playableIds.value(QRandomGenerator::global()->bounded(playableIds.size())); - mCore.logEvent(NAME, LOG_EVENT_INIT_RAND_ID.arg(mainIdBuffer.toString(QUuid::WithoutBraces))); + logEvent(LOG_EVENT_INIT_RAND_ID.arg(mainIdBuffer.toString(QUuid::WithoutBraces))); // Get entry's playable additional apps Fp::Db::EntryFilter addAppFilter{.type = Fp::Db::EntryType::AddApp, .parent = mainIdBuffer, .playableOnly = true}; @@ -91,10 +91,10 @@ Qx::Error TitleCommand::randomlySelectId(QUuid& mainIdBuffer, QUuid& subIdBuffer searchError = database->queryEntrys(addAppQuery, addAppFilter); if(searchError.isValid()) { - mCore.postError(NAME, searchError); + postError(searchError); return searchError; } - mCore.logEvent(NAME, LOG_EVENT_INIT_RAND_PLAY_ADD_COUNT.arg(addAppQuery.size)); + logEvent(LOG_EVENT_INIT_RAND_PLAY_ADD_COUNT.arg(addAppQuery.size)); QVector playableSubIds; @@ -110,18 +110,18 @@ Qx::Error TitleCommand::randomlySelectId(QUuid& mainIdBuffer, QUuid& subIdBuffer if(!addAppId.isNull()) playableSubIds.append(addAppId); else - mCore.logError(NAME, Qx::GenericError(Qx::Warning, 12101, LOG_WRN_INVALID_RAND_ID.arg(addAppIdString))); + logError(Qx::GenericError(Qx::Warning, 12101, LOG_WRN_INVALID_RAND_ID.arg(addAppIdString))); } // Select final ID int randIndex = QRandomGenerator::global()->bounded(playableSubIds.size() + 1); if(randIndex == 0) - mCore.logEvent(NAME, LOG_EVENT_RAND_DET_PRIM); + logEvent(LOG_EVENT_RAND_DET_PRIM); else { subIdBuffer = playableSubIds.value(randIndex - 1); - mCore.logEvent(NAME, LOG_EVENT_RAND_DET_ADD_APP.arg(subIdBuffer.toString(QUuid::WithoutBraces))); + logEvent(LOG_EVENT_RAND_DET_ADD_APP.arg(subIdBuffer.toString(QUuid::WithoutBraces))); } // Return success @@ -130,7 +130,7 @@ Qx::Error TitleCommand::randomlySelectId(QUuid& mainIdBuffer, QUuid& subIdBuffer Qx::Error TitleCommand::getRandomSelectionInfo(QString& infoBuffer, QUuid mainId, QUuid subId) { - mCore.logEvent(NAME, LOG_EVENT_RAND_GET_INFO); + logEvent(LOG_EVENT_RAND_GET_INFO); // Reset buffer infoBuffer = QString(); @@ -149,7 +149,7 @@ Qx::Error TitleCommand::getRandomSelectionInfo(QString& infoBuffer, QUuid mainId searchError = database->getEntry(entry_v, mainId); if(searchError.isValid()) { - mCore.postError(NAME, searchError); + postError(searchError); return searchError; } @@ -171,7 +171,7 @@ Qx::Error TitleCommand::getRandomSelectionInfo(QString& infoBuffer, QUuid mainId searchError = database->getEntry(entry_v, subId); if(searchError.isValid()) { - mCore.postError(NAME, searchError); + postError(searchError); return searchError; } @@ -190,7 +190,7 @@ Qx::Error TitleCommand::getRandomSelectionInfo(QString& infoBuffer, QUuid mainId } //Protected: -QList TitleCommand::options() { return CL_OPTIONS_SPECIFIC + Command::options(); } +QList TitleCommand::options() const { return CL_OPTIONS_SPECIFIC + Command::options(); } Qx::Error TitleCommand::getTitleId(QUuid& id) { @@ -207,7 +207,7 @@ Qx::Error TitleCommand::getTitleId(QUuid& id) if((titleId = QUuid(idStr)).isNull()) { TitleCommandError err(TitleCommandError::InvalidId, idStr); - mCore.postError(NAME, err); + postError(err); return err; } } @@ -253,7 +253,7 @@ Qx::Error TitleCommand::getTitleId(QUuid& id) else { TitleCommandError err(TitleCommandError::InvalidRandomFilter, rawRandFilter); - mCore.postError(NAME, err); + postError(err); return err; } @@ -275,7 +275,7 @@ Qx::Error TitleCommand::getTitleId(QUuid& id) else { TitleCommandError err(TitleCommandError::MissingTitle); - mCore.postError(NAME, err); + postError(err); return err; } diff --git a/app/src/command/title-command.h b/app/src/command/title-command.h index 5bba30f..9f6cc18 100644 --- a/app/src/command/title-command.h +++ b/app/src/command/title-command.h @@ -135,7 +135,7 @@ class TitleCommand : public Command Qx::Error getRandomSelectionInfo(QString& infoBuffer, QUuid mainId, QUuid subId); protected: - virtual QList options() override; + virtual QList options() const override; Qx::Error getTitleId(QUuid& id); }; diff --git a/app/src/kernel/core.cpp b/app/src/kernel/core.cpp index 7075e81..bf0b114 100644 --- a/app/src/kernel/core.cpp +++ b/app/src/kernel/core.cpp @@ -167,16 +167,16 @@ Qx::Error Core::searchAndFilterEntity(QUuid& returnBuffer, QString name, bool ex if((searchError = mFlashpointInstall->database()->queryEntrys(searchResult, filter)).isValid()) { - postError(NAME, searchError); + postError(searchError); return searchError; } - logEvent(NAME, LOG_EVENT_TITLE_ID_COUNT.arg(searchResult.size).arg(name)); + logEvent(LOG_EVENT_TITLE_ID_COUNT.arg(searchResult.size).arg(name)); if(searchResult.size < 1) { CoreError err(CoreError::TitleNotFound, name); - postError(NAME, err); + postError(err); return err; } else if(searchResult.size == 1) @@ -190,19 +190,19 @@ Qx::Error Core::searchAndFilterEntity(QUuid& returnBuffer, QString name, bool ex Fp::Db::Table_Add_App::COL_ID; returnBuffer = QUuid(searchResult.result.value(idKey).toString()); - logEvent(NAME, LOG_EVENT_TITLE_ID_DETERMINED.arg(name, returnBuffer.toString(QUuid::WithoutBraces))); + logEvent(LOG_EVENT_TITLE_ID_DETERMINED.arg(name, returnBuffer.toString(QUuid::WithoutBraces))); return CoreError(); } else if (searchResult.size > FIND_ENTRY_LIMIT) { CoreError err(CoreError::TooManyResults, name); - postError(NAME, err); + postError(err); return err; } else { - logEvent(NAME, LOG_EVENT_TITLE_SEL_PROMNPT); + logEvent(LOG_EVENT_TITLE_SEL_PROMNPT); QHash idMap; QStringList idChoices; @@ -241,12 +241,12 @@ Qx::Error Core::searchAndFilterEntity(QUuid& returnBuffer, QString name, bool ex QString userChoice = requestItemSelection(isr); if(userChoice.isNull()) - logEvent(NAME, LOG_EVENT_TITLE_SEL_CANCELED); + logEvent(LOG_EVENT_TITLE_SEL_CANCELED); else { // Set return buffer returnBuffer = idMap.value(userChoice); // If user choice is null, this will - logEvent(NAME, LOG_EVENT_TITLE_ID_DETERMINED.arg(name, returnBuffer.toString(QUuid::WithoutBraces))); + logEvent(LOG_EVENT_TITLE_ID_DETERMINED.arg(name, returnBuffer.toString(QUuid::WithoutBraces))); } return CoreError(); @@ -271,23 +271,32 @@ void Core::logQtMessage(QtMsgType type, const QMessageLogContext& context, const switch (type) { case QtDebugMsg: - logEvent(NAME, u"SYSTEM DEBUG) "_s + msgWithContext); + logEvent(u"SYSTEM DEBUG) "_s + msgWithContext); break; case QtInfoMsg: - logEvent(NAME, u"SYSTEM INFO) "_s + msgWithContext); + logEvent(u"SYSTEM INFO) "_s + msgWithContext); break; case QtWarningMsg: - logError(NAME, CoreError(CoreError::InternalError, msgWithContext, Qx::Warning)); + logError(CoreError(CoreError::InternalError, msgWithContext, Qx::Warning)); break; case QtCriticalMsg: - logError(NAME, CoreError(CoreError::InternalError, msgWithContext, Qx::Err)); + logError(CoreError(CoreError::InternalError, msgWithContext, Qx::Err)); break; case QtFatalMsg: - logError(NAME, CoreError(CoreError::InternalError, msgWithContext, Qx::Critical)); + logError(CoreError(CoreError::InternalError, msgWithContext, Qx::Critical)); break; } } +void Core::logCommand(const QString& commandName) { logCommand(NAME, commandName); } +void Core::logCommandOptions(const QString& commandOptions) { logCommandOptions(NAME, commandOptions); } +void Core::logError(const Qx::Error& error) { logError(NAME, error); } +void Core::logEvent(const QString& event) { logEvent(NAME, event); } +void Core::logTask(const Task* task) { logTask(NAME, task); } +ErrorCode Core::logFinish(const Qx::Error& errorState) { return logFinish(NAME, errorState); } +void Core::postError(const Qx::Error& error, bool log) { postError(NAME, error, log); } +int Core::postBlockingError(const Qx::Error& error, bool log, QMessageBox::StandardButtons bs, QMessageBox::StandardButton def) { return postBlockingError(NAME, error, log); } + //Public: Qx::Error Core::initialize(QStringList& commandLine) { @@ -334,13 +343,13 @@ Qx::Error Core::initialize(QStringList& commandLine) // Open log Qx::IoOpReport logOpen = mLogger->openLog(); if(logOpen.isFailure()) - postError(NAME, Qx::Error(logOpen).setSeverity(Qx::Warning), false); + postError(Qx::Error(logOpen).setSeverity(Qx::Warning), false); // Log initialization step - logEvent(NAME, LOG_EVENT_INIT); + logEvent(LOG_EVENT_INIT); // Log global options - logEvent(NAME, LOG_EVENT_GLOBAL_OPT.arg(globalOptions)); + logEvent(LOG_EVENT_GLOBAL_OPT.arg(globalOptions)); // Check for valid arguments if(!validArgs) @@ -349,33 +358,33 @@ Qx::Error Core::initialize(QStringList& commandLine) showHelp(); CoreError err(CoreError::InvalidOptions, clParser.errorText()); - postError(NAME, err); + postError(err); return err; } // Handle each global option mNotificationVerbosity = clParser.isSet(CL_OPTION_SILENT) ? NotificationVerbosity::Silent : clParser.isSet(CL_OPTION_QUIET) ? NotificationVerbosity::Quiet : NotificationVerbosity::Full; - logEvent(NAME, LOG_EVENT_NOTIFCATION_LEVEL.arg(ENUM_NAME(mNotificationVerbosity))); + logEvent(LOG_EVENT_NOTIFCATION_LEVEL.arg(ENUM_NAME(mNotificationVerbosity))); if(clParser.isSet(CL_OPTION_VERSION)) { showVersion(); commandLine.clear(); // Clear args so application terminates after Core setup - logEvent(NAME, LOG_EVENT_VER_SHOWN); + logEvent(LOG_EVENT_VER_SHOWN); } else if(clParser.isSet(CL_OPTION_HELP) || (!isActionableOptionSet(clParser) && clParser.positionalArguments().count() == 0)) // Also when no parameters { showHelp(); commandLine.clear(); // Clear args so application terminates after Core setup - logEvent(NAME, LOG_EVENT_G_HELP_SHOWN); + logEvent(LOG_EVENT_G_HELP_SHOWN); } else { QStringList pArgs = clParser.positionalArguments(); if(pArgs.count() == 1 && pArgs.front().startsWith(FLASHPOINT_PROTOCOL_SCHEME)) { - logEvent(NAME, LOG_EVENT_PROTOCOL_FORWARD); + logEvent(LOG_EVENT_PROTOCOL_FORWARD); commandLine = {"play", "-u", pArgs.front()}; } else @@ -388,7 +397,7 @@ Qx::Error Core::initialize(QStringList& commandLine) void Core::setServicesMode(ServicesMode mode) { - logEvent(NAME, LOG_EVENT_MODE_SET.arg(ENUM_NAME(mode))); + logEvent(LOG_EVENT_MODE_SET.arg(ENUM_NAME(mode))); mServicesMode = mode; if(mode == ServicesMode::Companion) @@ -397,7 +406,7 @@ void Core::setServicesMode(ServicesMode mode) void Core::watchLauncher() { - logEvent(NAME, LOG_EVENT_LAUNCHER_WATCH); + logEvent(LOG_EVENT_LAUNCHER_WATCH); using namespace std::chrono_literals; mLauncherWatcher.setProcessName(Fp::Install::LAUNCHER_NAME); @@ -405,15 +414,15 @@ void Core::watchLauncher() mLauncherWatcher.setPollRate(1s); // Generous rate since while we need to know quickly, we don't THAT quickly #endif connect(&mLauncherWatcher, &Qx::ProcessBider::established, this, [this]{ - logEvent(NAME, LOG_EVENT_LAUNCHER_WATCH_HOOKED); + logEvent(LOG_EVENT_LAUNCHER_WATCH_HOOKED); }); connect(&mLauncherWatcher, &Qx::ProcessBider::errorOccurred, this, [this](Qx::ProcessBiderError err){ - logError(NAME, err); + logError(err); }); connect(&mLauncherWatcher, &Qx::ProcessBider::finished, this, [this]{ // Launcher closed (or can't be hooked), need to bail CoreError err(CoreError::CompanionModeLauncherClose, LOG_EVENT_LAUNCHER_CLOSED_RESULT); - postError(NAME, err); + postError(err); emit abort(err); }); @@ -427,10 +436,10 @@ void Core::attachFlashpoint(std::unique_ptr flashpointInstall) mFlashpointInstall = std::move(flashpointInstall); // Note install details - logEvent(NAME, LOG_EVENT_FLASHPOINT_VERSION_TXT.arg(mFlashpointInstall->nameVersionString())); - logEvent(NAME, LOG_EVENT_FLASHPOINT_VERSION.arg(mFlashpointInstall->version().toString())); - logEvent(NAME, LOG_EVENT_FLASHPOINT_EDITION.arg(ENUM_NAME(mFlashpointInstall->edition()))); - logEvent(NAME, LOG_EVENT_OUTFITTED_DAEMON.arg(ENUM_NAME(mFlashpointInstall->outfittedDaemon()))); + logEvent(LOG_EVENT_FLASHPOINT_VERSION_TXT.arg(mFlashpointInstall->nameVersionString())); + logEvent(LOG_EVENT_FLASHPOINT_VERSION.arg(mFlashpointInstall->version().toString())); + logEvent(LOG_EVENT_FLASHPOINT_EDITION.arg(ENUM_NAME(mFlashpointInstall->edition()))); + logEvent(LOG_EVENT_OUTFITTED_DAEMON.arg(ENUM_NAME(mFlashpointInstall->outfittedDaemon()))); // Initialize child process env vars QProcessEnvironment de = QProcessEnvironment::systemEnvironment(); @@ -497,37 +506,37 @@ QString Core::resolveFullAppPath(const QString& appPath, const QString& platform QString swapPath = appPath; if(tk->resolveTrueAppPath(swapPath, platform, clifpOverrides)) - logEvent(NAME, LOG_EVENT_APP_PATH_ALT.arg(appPath, swapPath)); + logEvent(LOG_EVENT_APP_PATH_ALT.arg(appPath, swapPath)); return mFlashpointInstall->dir().absoluteFilePath(swapPath); } Qx::Error Core::findGameIdFromTitle(QUuid& returnBuffer, QString title, bool exactTitle) { - logEvent(NAME, LOG_EVENT_GAME_SEARCH.arg(title)); + logEvent(LOG_EVENT_GAME_SEARCH.arg(title)); return searchAndFilterEntity(returnBuffer, title, exactTitle); } Qx::Error Core::findAddAppIdFromName(QUuid& returnBuffer, QUuid parent, QString name, bool exactName) { - logEvent(NAME, LOG_EVENT_ADD_APP_SEARCH.arg(name, parent.toString())); + logEvent(LOG_EVENT_ADD_APP_SEARCH.arg(name, parent.toString())); return searchAndFilterEntity(returnBuffer, name, exactName, parent); } bool Core::blockNewInstances() { bool b = Qx::enforceSingleInstance(SINGLE_INSTANCE_ID); - logEvent(NAME, b ? LOG_EVENT_FURTHER_INSTANCE_BLOCK_SUCC : LOG_EVENT_FURTHER_INSTANCE_BLOCK_FAIL); + logEvent(b ? LOG_EVENT_FURTHER_INSTANCE_BLOCK_SUCC : LOG_EVENT_FURTHER_INSTANCE_BLOCK_FAIL); return b; } CoreError Core::enqueueStartupTasks(const QString& serverOverride) { - logEvent(NAME, LOG_EVENT_ENQ_START); + logEvent(LOG_EVENT_ENQ_START); if(mServicesMode == ServicesMode::Companion) { - logEvent(NAME, LOG_EVENT_SERVICES_FROM_LAUNCHER); + logEvent(LOG_EVENT_SERVICES_FROM_LAUNCHER); // TODO: Allegedly apache and php are going away at some point so this hopefully isn't needed for long return !serverOverride.isEmpty() ? CoreError(CoreError::CompanionModeServerOverride) : CoreError(); } @@ -548,7 +557,7 @@ CoreError Core::enqueueStartupTasks(const QString& serverOverride) xhostSet->setProcessType(TExec::ProcessType::Blocking); mTaskQueue.push(xhostSet); - logTask(NAME, xhostSet); + logTask(xhostSet); } #endif @@ -570,7 +579,7 @@ CoreError Core::enqueueStartupTasks(const QString& serverOverride) currentTask->setProcessType(TExec::ProcessType::Blocking); mTaskQueue.push(currentTask); - logTask(NAME, currentTask); + logTask(currentTask); } // Add Server entry from services if applicable @@ -580,7 +589,7 @@ CoreError Core::enqueueStartupTasks(const QString& serverOverride) if(!foundServer) { CoreError err(CoreError::ConfiguredServerMissing); - postError(NAME, err); + postError(err); return err; } @@ -595,7 +604,7 @@ CoreError Core::enqueueStartupTasks(const QString& serverOverride) serverTask->setProcessType(server.kill ? TExec::ProcessType::Deferred : TExec::ProcessType::Detached); mTaskQueue.push(serverTask); - logTask(NAME, serverTask); + logTask(serverTask); } // Add Daemon entry from services @@ -610,7 +619,7 @@ CoreError Core::enqueueStartupTasks(const QString& serverOverride) currentTask->setProcessType(d.kill ? TExec::ProcessType::Deferred : TExec::ProcessType::Detached); mTaskQueue.push(currentTask); - logTask(NAME, currentTask); + logTask(currentTask); } #ifdef __linux__ @@ -624,7 +633,7 @@ CoreError Core::enqueueStartupTasks(const QString& serverOverride) dockerWait->setTimeout(10000); mTaskQueue.push(dockerWait); - logTask(NAME, dockerWait); + logTask(dockerWait); } #endif @@ -638,7 +647,7 @@ CoreError Core::enqueueStartupTasks(const QString& serverOverride) initDelay->setDuration(1500); // NOTE: Might need to be made longer mTaskQueue.push(initDelay); - logTask(NAME, initDelay); + logTask(initDelay); // Return success return CoreError(); @@ -646,11 +655,11 @@ CoreError Core::enqueueStartupTasks(const QString& serverOverride) void Core::enqueueShutdownTasks() { - logEvent(NAME, LOG_EVENT_ENQ_STOP); + logEvent(LOG_EVENT_ENQ_STOP); if(mServicesMode == ServicesMode::Companion) { - logEvent(NAME, LOG_EVENT_SERVICES_FROM_LAUNCHER); + logEvent(LOG_EVENT_SERVICES_FROM_LAUNCHER); return; } @@ -666,7 +675,7 @@ void Core::enqueueShutdownTasks() shutdownTask->setProcessType(TExec::ProcessType::Blocking); mTaskQueue.push(shutdownTask); - logTask(NAME, shutdownTask); + logTask(shutdownTask); } #ifdef __linux__ @@ -682,7 +691,7 @@ void Core::enqueueShutdownTasks() xhostClear->setProcessType(TExec::ProcessType::Blocking); mTaskQueue.push(xhostClear); - logTask(NAME, xhostClear); + logTask(xhostClear); } #endif } @@ -697,7 +706,7 @@ Qx::Error Core::conditionallyEnqueueBideTask(QFileInfo precedingAppInfo) Qx::Error securePlayerCheckError = tk->appInvolvesSecurePlayer(involvesSecurePlayer, precedingAppInfo); if(securePlayerCheckError.isValid()) { - postError(NAME, securePlayerCheckError); + postError(securePlayerCheckError); return securePlayerCheckError; } @@ -708,7 +717,7 @@ Qx::Error Core::conditionallyEnqueueBideTask(QFileInfo precedingAppInfo) waitTask->setProcessName(tk->SECURE_PLAYER_INFO.fileName()); mTaskQueue.push(waitTask); - logTask(NAME, waitTask); + logTask(waitTask); } // Return success @@ -720,7 +729,7 @@ Qx::Error Core::conditionallyEnqueueBideTask(QFileInfo precedingAppInfo) Qx::Error Core::enqueueDataPackTasks(const Fp::GameData& gameData) { - logEvent(NAME, LOG_EVENT_ENQ_DATA_PACK); + logEvent(LOG_EVENT_ENQ_DATA_PACK); const Fp::Toolkit* tk = mFlashpointInstall->toolkit(); @@ -730,7 +739,7 @@ Qx::Error Core::enqueueDataPackTasks(const Fp::GameData& gameData) // Enqueue pack download if it's not available if(!tk->datapackIsPresent(gameData)) { - logEvent(NAME, LOG_EVENT_DATA_PACK_MISS); + logEvent(LOG_EVENT_DATA_PACK_MISS); QUrl packUrl = tk->datapackUrl(gameData); @@ -740,7 +749,7 @@ Qx::Error Core::enqueueDataPackTasks(const Fp::GameData& gameData) downloadTask->addFile({.target = packUrl, .dest = packPath, .checksum = gameData.sha256()}); mTaskQueue.push(downloadTask); - logTask(NAME, downloadTask); + logTask(downloadTask); // Add task to update DB with onDiskState int gameDataId = gameData.id(); @@ -753,25 +762,25 @@ Qx::Error Core::enqueueDataPackTasks(const Fp::GameData& gameData) }); mTaskQueue.push(onDiskUpdateTask); - logTask(NAME, onDiskUpdateTask); + logTask(onDiskUpdateTask); } else - logEvent(NAME, LOG_EVENT_DATA_PACK_FOUND); + logEvent(LOG_EVENT_DATA_PACK_FOUND); // Handle datapack parameters Fp::GameDataParameters param = gameData.parameters(); if(param.hasError()) - postError(NAME, CoreError(CoreError::UnknownDatapackParam, param.errorString(), Qx::Warning)); + postError(CoreError(CoreError::UnknownDatapackParam, param.errorString(), Qx::Warning)); if(param.isExtract()) { - logEvent(NAME, LOG_EVENT_DATA_PACK_NEEDS_EXTRACT); + logEvent(LOG_EVENT_DATA_PACK_NEEDS_EXTRACT); QDir extractRoot(mFlashpointInstall->dir().absoluteFilePath(mFlashpointInstall->preferences().htdocsFolderPath)); QString marker = QDir::fromNativeSeparators(param.extractedMarkerFile()); // Check if files are already present if(!marker.isEmpty() && QFile::exists(extractRoot.absoluteFilePath(marker))) - logEvent(NAME, LOG_EVENT_DATA_PACK_ALREADY_EXTRACTED); + logEvent(LOG_EVENT_DATA_PACK_ALREADY_EXTRACTED); else { TExtract* extractTask = new TExtract(this); @@ -781,12 +790,12 @@ Qx::Error Core::enqueueDataPackTasks(const Fp::GameData& gameData) extractTask->setDestinationPath(extractRoot.absolutePath()); mTaskQueue.push(extractTask); - logTask(NAME, extractTask); + logTask(extractTask); } } else { - logEvent(NAME, LOG_EVENT_DATA_PACK_NEEDS_MOUNT); + logEvent(LOG_EVENT_DATA_PACK_NEEDS_MOUNT); // Create task TMount* mountTask = new TMount(this); @@ -796,32 +805,32 @@ Qx::Error Core::enqueueDataPackTasks(const Fp::GameData& gameData) mountTask->setDaemon(mFlashpointInstall->outfittedDaemon()); mTaskQueue.push(mountTask); - logTask(NAME, mountTask); + logTask(mountTask); } // Return success return CoreError(); } -void Core::enqueueSingleTask(Task* task) { mTaskQueue.push(task); logTask(NAME, task); } +void Core::enqueueSingleTask(Task* task) { mTaskQueue.push(task); logTask(task); } bool Core::isLogOpen() const { return mLogger->isOpen(); } -void Core::logCommand(QString src, QString commandName) +void Core::logCommand(const QString& src, const QString& commandName) { Qx::IoOpReport logReport = mLogger->recordGeneralEvent(src, COMMAND_LABEL.arg(commandName)); if(logReport.isFailure()) postError(src, Qx::Error(logReport).setSeverity(Qx::Warning), false); } -void Core::logCommandOptions(QString src, QString commandOptions) +void Core::logCommandOptions(const QString& src, const QString& commandOptions) { Qx::IoOpReport logReport = mLogger->recordGeneralEvent(src, COMMAND_OPT_LABEL.arg(commandOptions)); if(logReport.isFailure()) postError(src, Qx::Error(logReport).setSeverity(Qx::Warning), false); } -void Core::logError(QString src, Qx::Error error) +void Core::logError(const QString& src, const Qx::Error& error) { Qx::IoOpReport logReport = mLogger->recordErrorEvent(src, error); @@ -832,16 +841,16 @@ void Core::logError(QString src, Qx::Error error) mCriticalErrorOccurred = true; } -void Core::logEvent(QString src, QString event) +void Core::logEvent(const QString& src, const QString& event) { Qx::IoOpReport logReport = mLogger->recordGeneralEvent(src, event); if(logReport.isFailure()) postError(src, Qx::Error(logReport).setSeverity(Qx::Warning), false); } -void Core::logTask(QString src, const Task* task) { logEvent(src, LOG_EVENT_TASK_ENQ.arg(task->name(), task->members().join(u", "_s))); } +void Core::logTask(const QString& src, const Task* task) { logEvent(src, LOG_EVENT_TASK_ENQ.arg(task->name(), task->members().join(u", "_s))); } -ErrorCode Core::logFinish(QString src, Qx::Error errorState) +ErrorCode Core::logFinish(const QString& src, const Qx::Error& errorState) { if(mCriticalErrorOccurred) logEvent(src, LOG_ERR_CRITICAL); @@ -856,7 +865,7 @@ ErrorCode Core::logFinish(QString src, Qx::Error errorState) return code; } -void Core::postError(QString src, Qx::Error error, bool log) +void Core::postError(const QString& src, const Qx::Error& error, bool log) { // Logging if(log) @@ -876,7 +885,7 @@ void Core::postError(QString src, Qx::Error error, bool log) } } -int Core::postBlockingError(QString src, Qx::Error error, bool log, QMessageBox::StandardButtons bs, QMessageBox::StandardButton def) +int Core::postBlockingError(const QString& src, const Qx::Error& error, bool log, QMessageBox::StandardButtons bs, QMessageBox::StandardButton def) { // Logging if(log) diff --git a/app/src/kernel/core.h b/app/src/kernel/core.h index 0fe59bf..8c1a6cd 100644 --- a/app/src/kernel/core.h +++ b/app/src/kernel/core.h @@ -296,6 +296,20 @@ class Core : public QObject Qx::Error searchAndFilterEntity(QUuid& returnBuffer, QString name, bool exactName, QUuid parent = QUuid()); void logQtMessage(QtMsgType type, const QMessageLogContext& context, const QString& msg); + /* TODO: See if instead of repeating these with auto-source overloads everywhere if instead a template function can be made that just works + * in all places where core is available. This would likely require a public ::NAME static member for each type that uses core, though this + * would be tricky for the tasks that emit signals instead of using core directly. + */ + // Notifications/Logging (self-forwarders) + void logCommand(const QString& commandName); + void logCommandOptions(const QString& commandOptions); + void logError(const Qx::Error& error); + void logEvent(const QString& event); + void logTask(const Task* task); + ErrorCode logFinish(const Qx::Error& errorState); + void postError(const Qx::Error& error, bool log = true); + int postBlockingError(const Qx::Error& error, bool log = true, QMessageBox::StandardButtons bs = QMessageBox::Ok, QMessageBox::StandardButton def = QMessageBox::NoButton); + public: // Setup Qx::Error initialize(QStringList& commandLine); @@ -323,14 +337,14 @@ class Core : public QObject * with the same names that call mCore.logX(NAME, ...) automatically so that NAME doesn't need to be passed every time */ bool isLogOpen() const; - void logCommand(QString src, QString commandName); - void logCommandOptions(QString src, QString commandOptions); - void logError(QString src, Qx::Error error); - void logEvent(QString src, QString event); - void logTask(QString src, const Task* task); - ErrorCode logFinish(QString src, Qx::Error errorState); - void postError(QString src, Qx::Error error, bool log = true); - int postBlockingError(QString src, Qx::Error error, bool log = true, QMessageBox::StandardButtons bs = QMessageBox::Ok, QMessageBox::StandardButton def = QMessageBox::NoButton); + void logCommand(const QString& src, const QString& commandName); + void logCommandOptions(const QString& src, const QString& commandOptions); + void logError(const QString& src, const Qx::Error& error); + void logEvent(const QString& src, const QString& event); + void logTask(const QString& src, const Task* task); + ErrorCode logFinish(const QString& src, const Qx::Error& errorState); + void postError(const QString& src, const Qx::Error& error, bool log = true); + int postBlockingError(const QString& src, const Qx::Error& error, bool log = true, QMessageBox::StandardButtons bs = QMessageBox::Ok, QMessageBox::StandardButton def = QMessageBox::NoButton); void postMessage(const Message& msg); QString requestSaveFilePath(const SaveFileRequest& request); QString requestExistingDirPath(const ExistingDirRequest& request); diff --git a/app/src/kernel/driver.cpp b/app/src/kernel/driver.cpp index d23ba66..cc7fb75 100644 --- a/app/src/kernel/driver.cpp +++ b/app/src/kernel/driver.cpp @@ -69,7 +69,7 @@ void Driver::init() connect(mCore, &Core::clipboardUpdateRequested, this, &Driver::clipboardUpdateRequested); connect(mCore, &Core::questionAnswerRequested, this, &Driver::questionAnswerRequested); connect(mCore, &Core::abort, this, [this](CoreError err){ - mCore->logEvent(NAME, LOG_EVENT_CORE_ABORT); + logEvent(LOG_EVENT_CORE_ABORT); mErrorStatus = err; quit(); }); @@ -85,8 +85,9 @@ void Driver::init() * would make deleting the object slightly tricky. This way it can just be parented to core */ DeferredProcessManager* dpm = new DeferredProcessManager(mCore); - connect(dpm, &DeferredProcessManager::eventOccurred, mCore, &Core::logEvent); - connect(dpm, &DeferredProcessManager::errorOccurred, mCore, &Core::logError); + // qOverload because it gets confused with the shorter versions within core even though they're private :/ + connect(dpm, &DeferredProcessManager::eventOccurred, mCore, qOverload(&Core::logEvent)); + connect(dpm, &DeferredProcessManager::errorOccurred, mCore, qOverload(&Core::logError)); TExec::installDeferredProcessManager(dpm); } @@ -102,7 +103,7 @@ void Driver::startNextTask() mCurrentTaskNumber++; // Log task start - mCore->logEvent(NAME, LOG_EVENT_TASK_START.arg(QString::number(mCurrentTaskNumber), + logEvent(LOG_EVENT_TASK_START.arg(QString::number(mCurrentTaskNumber), mCurrentTask->name(), ENUM_NAME(mCurrentTask->stage()))); @@ -110,7 +111,7 @@ void Driver::startNextTask() bool isShutdown = mCurrentTask->stage() == Task::Stage::Shutdown; if(mErrorStatus.isSet() && !isShutdown) { - mCore->logEvent(NAME, LOG_EVENT_TASK_SKIP_ERROR); + logEvent(LOG_EVENT_TASK_SKIP_ERROR); // Queue up finished handler directly (executes on next event loop cycle) since task was skipped // Can't connect directly because newer connect syntax doesn't support default args @@ -118,7 +119,7 @@ void Driver::startNextTask() } else if(mQuitRequested && !isShutdown) { - mCore->logEvent(NAME, LOG_EVENT_TASK_SKIP_QUIT); + logEvent(LOG_EVENT_TASK_SKIP_QUIT); // Queue up finished handler directly (executes on next event loop cycle) since task was skipped // Can't connect directly because newer connect syntax doesn't support default args @@ -128,7 +129,7 @@ void Driver::startNextTask() { // Connect task notifiers connect(mCurrentTask, &Task::notificationReady, mCore, &Core::postMessage); - connect(mCurrentTask, &Task::eventOccurred, mCore, &Core::logEvent); + connect(mCurrentTask, &Task::eventOccurred, mCore, qOverload(&Core::logEvent)); connect(mCurrentTask, &Task::errorOccurred, mCore, [this](QString taskName, Qx::Error error){ mCore->postError(taskName, error); // Can't connect directly because newer connect syntax doesn't support default args }); @@ -151,13 +152,13 @@ void Driver::startNextTask() void Driver::cleanup() { - mCore->logEvent(NAME, LOG_EVENT_CLEANUP_START); + logEvent(LOG_EVENT_CLEANUP_START); // Close each remaining child process - mCore->logEvent(NAME, LOG_EVENT_ENDING_CHILD_PROCESSES); + logEvent(LOG_EVENT_ENDING_CHILD_PROCESSES); TExec::deferredProcessManager()->closeProcesses(); - mCore->logEvent(NAME, LOG_EVENT_CLEANUP_FINISH); + logEvent(LOG_EVENT_CLEANUP_FINISH); } void Driver::finish() @@ -166,12 +167,12 @@ void Driver::finish() if(CUpdate::isUpdateCacheClearable()) { if(CUpdateError err = CUpdate::clearUpdateCache(); err.isValid()) - mCore->logError(NAME, err); + logError(err); else - mCore->logEvent(NAME, LOG_EVENT_CLEARED_UPDATE_CACHE); + logEvent(LOG_EVENT_CLEARED_UPDATE_CACHE); } - emit finished(mCore->logFinish(NAME, mErrorStatus.value())); + emit finished(logFinish(mErrorStatus.value())); } void Driver::quit() @@ -191,7 +192,7 @@ std::unique_ptr Driver::findFlashpointInstall() do { - mCore->logEvent(NAME, LOG_EVENT_FLASHPOINT_ROOT_CHECK.arg(QDir::toNativeSeparators(currentDir.absolutePath()))); + logEvent(LOG_EVENT_FLASHPOINT_ROOT_CHECK.arg(QDir::toNativeSeparators(currentDir.absolutePath()))); // Attempt to instantiate fpInstall = std::make_unique(currentDir.absolutePath()); @@ -199,7 +200,7 @@ std::unique_ptr Driver::findFlashpointInstall() { if(fpInstall->outfittedDaemon() == Fp::Daemon::Unknown) { - mCore->logError(NAME, Qx::GenericError(Qx::Warning, 12011, LOG_WARN_FP_UNRECOGNIZED_DAEMON)); + logError(Qx::GenericError(Qx::Warning, 12011, LOG_WARN_FP_UNRECOGNIZED_DAEMON)); fpInstall.reset(); } else @@ -207,7 +208,7 @@ std::unique_ptr Driver::findFlashpointInstall() } else { - mCore->logError(NAME, fpInstall->error().setSeverity(Qx::Warning)); + logError(fpInstall->error().setSeverity(Qx::Warning)); fpInstall.reset(); } } @@ -217,7 +218,15 @@ std::unique_ptr Driver::findFlashpointInstall() return std::move(fpInstall); } - +// Notifications/Logging (core-forwarders) +void Driver::logCommand(QString commandName) { Q_ASSERT(mCore); mCore->logCommand(NAME, commandName); } +void Driver::logCommandOptions(QString commandOptions) { Q_ASSERT(mCore); mCore->logCommandOptions(NAME, commandOptions); } +void Driver::logError(Qx::Error error) { Q_ASSERT(mCore); mCore->logError(NAME, error); } +void Driver::logEvent(QString event) { Q_ASSERT(mCore); mCore->logEvent(NAME, event); } +void Driver::logTask(const Task* task) { Q_ASSERT(mCore); mCore->logTask(NAME, task); } +ErrorCode Driver::logFinish(Qx::Error errorState) { Q_ASSERT(mCore); return mCore->logFinish(NAME, errorState); } +void Driver::postError(Qx::Error error, bool log) { Q_ASSERT(mCore); mCore->postError(NAME, error, log); } +int Driver::postBlockingError(Qx::Error error, bool log, QMessageBox::StandardButtons bs, QMessageBox::StandardButton def) { Q_ASSERT(mCore); return mCore->postBlockingError(NAME, error, log); } //-Slots-------------------------------------------------------------------------------- //Private: @@ -227,11 +236,11 @@ void Driver::completeTaskHandler(Qx::Error e) if(e.isValid()) { mErrorStatus = e; - mCore->logEvent(NAME, LOG_EVENT_TASK_FINISH_ERR.arg(mCurrentTaskNumber)); // Record early end of task + logEvent(LOG_EVENT_TASK_FINISH_ERR.arg(mCurrentTaskNumber)); // Record early end of task } // Cleanup handled task - mCore->logEvent(NAME, LOG_EVENT_TASK_FINISH.arg(mCurrentTaskNumber)); + logEvent(LOG_EVENT_TASK_FINISH.arg(mCurrentTaskNumber)); qxDelete(mCurrentTask); // Perform next task if any remain @@ -239,7 +248,7 @@ void Driver::completeTaskHandler(Qx::Error e) startNextTask(); else { - mCore->logEvent(NAME, LOG_EVENT_QUEUE_FINISH); + logEvent(LOG_EVENT_QUEUE_FINISH); cleanup(); finish(); } @@ -265,7 +274,7 @@ void Driver::drive() // Check for valid command if(CommandError ce = Command::isRegistered(commandStr); ce.isValid()) { - mCore->postError(NAME, ce); + postError(ce); mErrorStatus = ce; finish(); return; @@ -284,7 +293,7 @@ void Driver::drive() if(commandProcessor->autoBlockNewInstances() && !mCore->blockNewInstances()) { DriverError err(DriverError::AlreadyOpen); - mCore->postError(NAME, err); + postError(err); mErrorStatus = err; finish(); return; @@ -295,17 +304,17 @@ void Driver::drive() { // Find and link to Flashpoint Install std::unique_ptr flashpointInstall; - mCore->logEvent(NAME, LOG_EVENT_FLASHPOINT_SEARCH); + logEvent(LOG_EVENT_FLASHPOINT_SEARCH); if(!(flashpointInstall = findFlashpointInstall())) { DriverError err(DriverError::InvalidInstall, ERR_INSTALL_INVALID_TIP); - mCore->postError(NAME, err); + postError(err); mErrorStatus = err; finish(); return; } - mCore->logEvent(NAME, LOG_EVENT_FLASHPOINT_LINK.arg(QDir::toNativeSeparators(flashpointInstall->dir().absolutePath()))); + logEvent(LOG_EVENT_FLASHPOINT_LINK.arg(QDir::toNativeSeparators(flashpointInstall->dir().absolutePath()))); // Insert into core mCore->attachFlashpoint(std::move(flashpointInstall)); @@ -329,11 +338,11 @@ void Driver::drive() } //-Handle Tasks----------------------------------------------------------------------- - mCore->logEvent(NAME, LOG_EVENT_TASK_COUNT.arg(mCore->taskCount())); + logEvent(LOG_EVENT_TASK_COUNT.arg(mCore->taskCount())); if(mCore->hasTasks()) { // Process task queue - mCore->logEvent(NAME, LOG_EVENT_QUEUE_START); + logEvent(LOG_EVENT_QUEUE_START); startNextTask(); } else @@ -351,10 +360,10 @@ void Driver::quitNow() // Handle quit state if(mQuitRequested) { - mCore->logEvent(NAME, LOG_EVENT_QUIT_REQUEST_REDUNDANT); + logEvent(LOG_EVENT_QUIT_REQUEST_REDUNDANT); return; } - mCore->logEvent(NAME, LOG_EVENT_QUIT_REQUEST); + logEvent(LOG_EVENT_QUIT_REQUEST); quit(); } diff --git a/app/src/kernel/driver.h b/app/src/kernel/driver.h index 044c64f..1451ed7 100644 --- a/app/src/kernel/driver.h +++ b/app/src/kernel/driver.h @@ -129,6 +129,16 @@ class Driver : public QObject // Helper std::unique_ptr findFlashpointInstall(); + // Notifications/Logging (core-forwarders) + void logCommand(QString commandName); + void logCommandOptions(QString commandOptions); + void logError(Qx::Error error); + void logEvent(QString event); + void logTask(const Task* task); + ErrorCode logFinish(Qx::Error errorState); + void postError(Qx::Error error, bool log = true); + int postBlockingError(Qx::Error error, bool log = true, QMessageBox::StandardButtons bs = QMessageBox::Ok, QMessageBox::StandardButton def = QMessageBox::NoButton); + //-Signals & Slots------------------------------------------------------------------------------------------------------------ private slots: void completeTaskHandler(Qx::Error e = {}); diff --git a/app/src/task/t-bideprocess.cpp b/app/src/task/t-bideprocess.cpp index 976eb06..36e3032 100644 --- a/app/src/task/t-bideprocess.cpp +++ b/app/src/task/t-bideprocess.cpp @@ -39,17 +39,17 @@ TBideProcess::TBideProcess(QObject* parent) : mProcessBider.setRespawnGrace(grace); mProcessBider.setInitialGrace(true); // Process will be stopped at first connect(&mProcessBider, &Qx::ProcessBider::established, this, [this]{ - emit eventOccurred(NAME, LOG_EVENT_BIDE_RUNNING.arg(mProcessName)); - emit eventOccurred(NAME, LOG_EVENT_BIDE_ON.arg(mProcessName)); + emitEventOccurred(LOG_EVENT_BIDE_RUNNING.arg(mProcessName)); + emitEventOccurred(LOG_EVENT_BIDE_ON.arg(mProcessName)); }); connect(&mProcessBider, &Qx::ProcessBider::processStopped, this, [this]{ - emit eventOccurred(NAME, LOG_EVENT_BIDE_QUIT.arg(mProcessName)); + emitEventOccurred(LOG_EVENT_BIDE_QUIT.arg(mProcessName)); }); connect(&mProcessBider, &Qx::ProcessBider::graceStarted, this, [this]{ - emit eventOccurred(NAME, LOG_EVENT_BIDE_GRACE.arg(QString::number(grace.count()), mProcessName)); + emitEventOccurred(LOG_EVENT_BIDE_GRACE.arg(QString::number(grace.count()), mProcessName)); }); connect(&mProcessBider, &Qx::ProcessBider::errorOccurred, this, [this](Qx::ProcessBiderError err){ - emit errorOccurred(NAME, err); + emitErrorOccurred(err); }); connect(&mProcessBider, &Qx::ProcessBider::finished, this, &TBideProcess::postBide); } @@ -79,7 +79,7 @@ void TBideProcess::stop() { if(mProcessBider.isBiding()) { - emit eventOccurred(NAME, LOG_EVENT_STOPPING_BIDE_PROCESS); + emitEventOccurred(LOG_EVENT_STOPPING_BIDE_PROCESS); mProcessBider.closeProcess(); } } @@ -92,7 +92,7 @@ void TBideProcess::postBide(Qx::ProcessBider::ResultType type) emit complete(TBideProcessError(mProcessName, TBideProcessError::BideFail)); else { - emit eventOccurred(NAME, LOG_EVENT_BIDE_FINISHED.arg(mProcessName)); + emitEventOccurred(LOG_EVENT_BIDE_FINISHED.arg(mProcessName)); emit complete(TBideProcessError()); } } diff --git a/app/src/task/t-download.cpp b/app/src/task/t-download.cpp index fa15350..439fd09 100644 --- a/app/src/task/t-download.cpp +++ b/app/src/task/t-download.cpp @@ -40,20 +40,20 @@ TDownload::TDownload(QObject* parent) : // Download event handlers connect(&mDownloadManager, &Qx::AsyncDownloadManager::sslErrors, this, [this](Qx::Error errorMsg, bool* ignore) { int choice; - emit blockingErrorOccurred(NAME, &choice, errorMsg, QMessageBox::Yes | QMessageBox::No); + emitBlockingErrorOccurred(&choice, errorMsg, QMessageBox::Yes | QMessageBox::No); *ignore = choice == QMessageBox::Yes; }); connect(&mDownloadManager, &Qx::AsyncDownloadManager::authenticationRequired, this, [this](QString prompt) { - emit eventOccurred(NAME, LOG_EVENT_DOWNLOAD_AUTH.arg(prompt)); + emitEventOccurred(LOG_EVENT_DOWNLOAD_AUTH.arg(prompt)); }); connect(&mDownloadManager, &Qx::AsyncDownloadManager::preSharedKeyAuthenticationRequired, this, [this](QString prompt) { - emit eventOccurred(NAME, LOG_EVENT_DOWNLOAD_AUTH.arg(prompt)); + emitEventOccurred(LOG_EVENT_DOWNLOAD_AUTH.arg(prompt)); }); connect(&mDownloadManager, &Qx::AsyncDownloadManager::proxyAuthenticationRequired, this, [this](QString prompt) { - emit eventOccurred(NAME, LOG_EVENT_DOWNLOAD_AUTH.arg(prompt)); + emitEventOccurred(LOG_EVENT_DOWNLOAD_AUTH.arg(prompt)); }); connect(&mDownloadManager, &Qx::AsyncDownloadManager::downloadTotalChanged, this, &TDownload::longTaskTotalChanged); @@ -100,7 +100,7 @@ void TDownload::perform() // Log/label string QString label = LOG_EVENT_DOWNLOAD.arg(mDescription); - emit eventOccurred(NAME, label); + emitEventOccurred(label); // Start download emit longTaskStarted(label); @@ -111,7 +111,7 @@ void TDownload::stop() { if(mDownloadManager.isProcessing()) { - emit eventOccurred(NAME, LOG_EVENT_STOPPING_DOWNLOADS); + emitEventOccurred(LOG_EVENT_STOPPING_DOWNLOADS); mDownloadManager.abort(); } } @@ -125,11 +125,11 @@ void TDownload::postDownload(Qx::DownloadManagerReport downloadReport) // Handle result emit longTaskFinished(); if(downloadReport.wasSuccessful()) - emit eventOccurred(NAME, LOG_EVENT_DOWNLOAD_SUCC); + emitEventOccurred(LOG_EVENT_DOWNLOAD_SUCC); else { errorStatus = TDownloadError(TDownloadError::Incomeplete, downloadReport.outcomeString()); - emit errorOccurred(NAME, errorStatus); + emitErrorOccurred(errorStatus); } emit complete(errorStatus); diff --git a/app/src/task/t-exec.cpp b/app/src/task/t-exec.cpp index b87ba6a..8318011 100644 --- a/app/src/task/t-exec.cpp +++ b/app/src/task/t-exec.cpp @@ -85,7 +85,7 @@ QString TExec::createEscapedShellArguments() QString args = std::get(mParameters); escapedArgs = escapeForShell(args); if(args != escapedArgs) - emit eventOccurred(NAME, LOG_EVENT_ARGS_ESCAPED.arg(args, escapedArgs)); + emitEventOccurred(LOG_EVENT_ARGS_ESCAPED.arg(args, escapedArgs)); } else { @@ -99,7 +99,7 @@ QString TExec::createEscapedShellArguments() QStringList rebuild = QProcess::splitCommand(escapedArgs); if(rebuild != parameters) { - emit eventOccurred(NAME, LOG_EVENT_ARGS_ESCAPED.arg(u"{\""_s + parameters.join(uR"(", ")"_s) + u"\"}"_s, + emitEventOccurred(LOG_EVENT_ARGS_ESCAPED.arg(u"{\""_s + parameters.join(uR"(", ")"_s) + u"\"}"_s, u"{\""_s + rebuild.join(uR"(", ")"_s) + u"\"}"_s)); } } @@ -139,7 +139,7 @@ void TExec::removeRedundantFullQuotes(QProcess& process) if(redundant) { - emit eventOccurred(NAME, LOG_EVENT_REMOVED_REDUNDANT_QUOTES.arg(a)); + emitEventOccurred(LOG_EVENT_REMOVED_REDUNDANT_QUOTES.arg(a)); a = inner.toString(); } } @@ -155,27 +155,27 @@ TExecError TExec::cleanStartProcess(QProcess* process) // Go to working directory QDir::setCurrent(newDirPath); - emit eventOccurred(NAME, LOG_EVENT_CD.arg(QDir::toNativeSeparators(newDirPath))); + emitEventOccurred(LOG_EVENT_CD.arg(QDir::toNativeSeparators(newDirPath))); // Start process process->start(); - emit eventOccurred(NAME, LOG_EVENT_STARTING.arg(mIdentifier, process->program())); + emitEventOccurred(LOG_EVENT_STARTING.arg(mIdentifier, process->program())); // Return to previous working directory QDir::setCurrent(currentDirPath); - emit eventOccurred(NAME, LOG_EVENT_CD.arg(QDir::toNativeSeparators(currentDirPath))); + emitEventOccurred(LOG_EVENT_CD.arg(QDir::toNativeSeparators(currentDirPath))); // Make sure process starts if(!process->waitForStarted()) { TExecError err(TExecError::CouldNotStart, ERR_DETAILS_TEMPLATE.arg(process->program(), ENUM_NAME(process->error()))); - emit errorOccurred(NAME, err); + emitErrorOccurred(err); delete process; // Clear finished process handle from heap return err; } // Return success - emit eventOccurred(NAME, LOG_EVENT_STARTED_PROCESS.arg(mIdentifier)); + emitEventOccurred(LOG_EVENT_STARTED_PROCESS.arg(mIdentifier)); return TExecError(); } @@ -211,14 +211,14 @@ void TExec::setIdentifier(QString identifier) { mIdentifier = identifier; } void TExec::perform() { - emit eventOccurred(NAME, LOG_EVENT_PREPARING_PROCESS.arg(ENUM_NAME(mProcessType), mIdentifier, mExecutable)); + emitEventOccurred(LOG_EVENT_PREPARING_PROCESS.arg(ENUM_NAME(mProcessType), mIdentifier, mExecutable)); // Get final executable path QString execPath = resolveExecutablePath(); if(execPath.isEmpty()) { TExecError err(TExecError::CouldNotFind, mExecutable, mStage == Stage::Shutdown ? Qx::Err : Qx::Critical); - emit errorOccurred(NAME, err); + emitErrorOccurred(err); emit complete(err); return; } @@ -275,7 +275,7 @@ void TExec::perform() if(!taskProcess->startDetached()) { TExecError err(TExecError::CouldNotStart, ERR_DETAILS_TEMPLATE.arg(taskProcess->program(), ENUM_NAME(taskProcess->error()))); - emit errorOccurred(NAME, err); + emitErrorOccurred(err); emit complete(err); return; } @@ -290,7 +290,7 @@ void TExec::stop() { if(mBlockingProcessManager) { - emit eventOccurred(NAME, LOG_EVENT_STOPPING_BLOCKING_PROCESS.arg(mIdentifier)); + emitEventOccurred(LOG_EVENT_STOPPING_BLOCKING_PROCESS.arg(mIdentifier)); mBlockingProcessManager->closeProcess(); } } diff --git a/app/src/task/t-exec_win.cpp b/app/src/task/t-exec_win.cpp index 3e2fc03..8a051b6 100644 --- a/app/src/task/t-exec_win.cpp +++ b/app/src/task/t-exec_win.cpp @@ -113,8 +113,8 @@ QProcess* TExec::prepareProcess(const QFileInfo& execInfo) void TExec::logPreparedProcess(const QProcess* process) { - emit eventOccurred(NAME, LOG_EVENT_FINAL_EXECUTABLE.arg(process->program())); - emit eventOccurred(NAME, LOG_EVENT_FINAL_PARAMETERS.arg(!process->nativeArguments().isEmpty() ? + emitEventOccurred(LOG_EVENT_FINAL_EXECUTABLE.arg(process->program())); + emitEventOccurred(LOG_EVENT_FINAL_PARAMETERS.arg(!process->nativeArguments().isEmpty() ? process->nativeArguments() : !process->arguments().isEmpty() ? u"{\""_s + process->arguments().join(uR"(", ")"_s) + u"\"}"_s : diff --git a/app/src/task/t-extra.cpp b/app/src/task/t-extra.cpp index 6c5231d..d4a1015 100644 --- a/app/src/task/t-extra.cpp +++ b/app/src/task/t-extra.cpp @@ -62,12 +62,12 @@ void TExtra::perform() { // Open extra QDesktopServices::openUrl(QUrl::fromLocalFile(mDirectory.absolutePath())); - emit eventOccurred(NAME, LOG_EVENT_SHOW_EXTRA.arg(QDir::toNativeSeparators(mDirectory.path()))); + emitEventOccurred(LOG_EVENT_SHOW_EXTRA.arg(QDir::toNativeSeparators(mDirectory.path()))); } else { errorStatus = TExtraError(TExtraError::NotFound, QDir::toNativeSeparators(mDirectory.path())); - emit errorOccurred(NAME, errorStatus); + emitErrorOccurred(errorStatus); } emit complete(errorStatus); diff --git a/app/src/task/t-extract.cpp b/app/src/task/t-extract.cpp index 19c90b7..3500644 100644 --- a/app/src/task/t-extract.cpp +++ b/app/src/task/t-extract.cpp @@ -215,13 +215,13 @@ void TExtract::perform() { // Log string QFileInfo packFileInfo(mPackPath); - emit eventOccurred(NAME, LOG_EVENT_EXTRACTING_ARCHIVE.arg(packFileInfo.fileName())); + emitEventOccurred(LOG_EVENT_EXTRACTING_ARCHIVE.arg(packFileInfo.fileName())); // Extract pack Extractor extractor(mPackPath, mPathInPack, mDestinationPath); TExtractError ee = extractor.extract(); if(ee.isValid()) - emit errorOccurred(NAME, ee); + emitErrorOccurred(ee); emit complete(ee); } diff --git a/app/src/task/t-generic.cpp b/app/src/task/t-generic.cpp index eb04c2d..16ae5b5 100644 --- a/app/src/task/t-generic.cpp +++ b/app/src/task/t-generic.cpp @@ -28,12 +28,12 @@ void TGeneric::setAction(std::function action) { mAction = action; void TGeneric::perform() { - emit eventOccurred(NAME, LOG_EVENT_START_ACTION.arg(mDescription)); + emitEventOccurred(LOG_EVENT_START_ACTION.arg(mDescription)); Qx::Error err = mAction(); if(err.isValid()) - emit errorOccurred(NAME, err); + emitErrorOccurred(err); - emit eventOccurred(NAME, LOG_EVENT_END_ACTION); + emitEventOccurred(LOG_EVENT_END_ACTION); emit complete(err); } diff --git a/app/src/task/t-message.cpp b/app/src/task/t-message.cpp index 68ec0f0..43582cf 100644 --- a/app/src/task/t-message.cpp +++ b/app/src/task/t-message.cpp @@ -38,7 +38,7 @@ void TMessage::perform() .blocking = mBlocking, .selectable = mSelectable }); - emit eventOccurred(NAME, LOG_EVENT_SHOW_MESSAGE); + emitEventOccurred(LOG_EVENT_SHOW_MESSAGE); // Return success emit complete(Qx::Error()); diff --git a/app/src/task/t-mount.cpp b/app/src/task/t-mount.cpp index 934f471..1be8bf1 100644 --- a/app/src/task/t-mount.cpp +++ b/app/src/task/t-mount.cpp @@ -146,7 +146,7 @@ void TMount::stop() { if(mMounting) { - emit eventOccurred(NAME, LOG_EVENT_STOPPING_MOUNT); + emitEventOccurred(LOG_EVENT_STOPPING_MOUNT); // TODO: This could benefit from the mounters using a shared base, or // some other kind of type erasure like the duck typing above. diff --git a/app/src/task/t-sleep.cpp b/app/src/task/t-sleep.cpp index dafd864..f3661b1 100644 --- a/app/src/task/t-sleep.cpp +++ b/app/src/task/t-sleep.cpp @@ -31,13 +31,13 @@ void TSleep::setDuration(uint msecs) { mDuration = msecs; } void TSleep::perform() { - emit eventOccurred(NAME, LOG_EVENT_START_SLEEP.arg(mDuration)); + emitEventOccurred(LOG_EVENT_START_SLEEP.arg(mDuration)); mSleeper.start(mDuration); } void TSleep::stop() { - emit eventOccurred(NAME, LOG_EVENT_SLEEP_INTERUPTED); + emitEventOccurred(LOG_EVENT_SLEEP_INTERUPTED); mSleeper.stop(); emit complete(Qx::Error()); } @@ -46,6 +46,6 @@ void TSleep::stop() //Privates Slots: void TSleep::timerFinished() { - emit eventOccurred(NAME, LOG_EVENT_FINISH_SLEEP); + emitEventOccurred(LOG_EVENT_FINISH_SLEEP); emit complete(Qx::Error()); } diff --git a/app/src/task/task.cpp b/app/src/task/task.cpp index 621c01c..7480e48 100644 --- a/app/src/task/task.cpp +++ b/app/src/task/task.cpp @@ -22,6 +22,12 @@ Task::Task(QObject* parent) : {} //-Instance Functions------------------------------------------------------------- +//Protected: +// Notifications/Logging (signal-forwarders) +void Task::emitEventOccurred(const QString& event) { emit eventOccurred(name(), event); } +void Task::emitErrorOccurred(const Qx::Error& error) { emit errorOccurred(name(), error); } +void Task::emitBlockingErrorOccurred(int* response, const Qx::Error& error, QMessageBox::StandardButtons choices) { emit blockingErrorOccurred(name(), response, error, choices); } + //Public: QStringList Task::members() const { return {u".stage() = "_s + ENUM_NAME(mStage)}; } diff --git a/app/src/task/task.h b/app/src/task/task.h index 4ee3e39..304c078 100644 --- a/app/src/task/task.h +++ b/app/src/task/task.h @@ -31,6 +31,12 @@ class Task : public QObject virtual ~Task() = default; //-Instance Functions------------------------------------------------------------------------------------------------------ +protected: + // Notifications/Logging (signal-forwarders) + void emitEventOccurred(const QString& event); + void emitErrorOccurred(const Qx::Error& error); + void emitBlockingErrorOccurred(int* response, const Qx::Error& error, QMessageBox::StandardButtons choices); + public: virtual QString name() const = 0; virtual QStringList members() const; @@ -44,16 +50,16 @@ class Task : public QObject //-Signals & Slots------------------------------------------------------------------------------------------------------------ signals: void notificationReady(const Message& msg); - void eventOccurred(QString taskName, QString event); - void errorOccurred(QString taskName, Qx::Error error); - void blockingErrorOccurred(QString taskName, int* response, Qx::Error error, QMessageBox::StandardButtons choices); + void eventOccurred(const QString& taskName, const QString& event); + void errorOccurred(const QString& taskName, const Qx::Error& error); + void blockingErrorOccurred(const QString& taskName, int* response, const Qx::Error& error, QMessageBox::StandardButtons choices); - void longTaskStarted(QString procedure); + void longTaskStarted(const QString& procedure); void longTaskTotalChanged(quint64 total); void longTaskProgressChanged(quint64 progress); void longTaskFinished(); - void complete(Qx::Error errorState); + void complete(const Qx::Error& errorState); }; #endif // TASK_H diff --git a/app/src/tools/blockingprocessmanager.h b/app/src/tools/blockingprocessmanager.h index d66ad1c..f2b7b00 100644 --- a/app/src/tools/blockingprocessmanager.h +++ b/app/src/tools/blockingprocessmanager.h @@ -46,7 +46,7 @@ private slots: void processStandardErrorHandler(); signals: - void eventOccurred(QString name, QString event); + void eventOccurred(const QString& name, const QString& event); void finished(); }; diff --git a/app/src/tools/deferredprocessmanager.h b/app/src/tools/deferredprocessmanager.h index a3221cf..724e257 100644 --- a/app/src/tools/deferredprocessmanager.h +++ b/app/src/tools/deferredprocessmanager.h @@ -54,8 +54,8 @@ private slots: void processStandardErrorHandler(); signals: - void eventOccurred(QString name, QString event); - void errorOccurred(QString name, Qx::GenericError error); + void eventOccurred(const QString& name, const QString& event); + void errorOccurred(const QString& name, const Qx::GenericError& error); }; #endif // DEFERREDPROCESSMANAGER_H diff --git a/app/src/tools/mounter_proxy.cpp b/app/src/tools/mounter_proxy.cpp index 1eff247..1227acb 100644 --- a/app/src/tools/mounter_proxy.cpp +++ b/app/src/tools/mounter_proxy.cpp @@ -59,18 +59,18 @@ MounterProxy::MounterProxy(QObject* parent) : * 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); + signalEventOccurred(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); + signalEventOccurred(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); + signalEventOccurred(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"}"); + signalEventOccurred(u"Unexpected SSL errors from PHP server! {"_s + errStrList + u"}"_s"}"); }); } @@ -84,14 +84,17 @@ void MounterProxy::finish(const MounterProxyError& errorState) void MounterProxy::noteProxyRequest(QNetworkAccessManager::Operation op, const QUrl& url, QByteArrayView data) { - emit eventOccurred(NAME, EVENT_REQUEST_SENT.arg(ENUM_NAME(op), url.toString(), QString::fromLatin1(data))); + signalEventOccurred(EVENT_REQUEST_SENT.arg(ENUM_NAME(op), url.toString(), QString::fromLatin1(data))); } void MounterProxy::noteProxyResponse(const QString& response) { - emit eventOccurred(NAME, EVENT_PROXY_RESPONSE.arg(response)); + signalEventOccurred(EVENT_PROXY_RESPONSE.arg(response)); } +void MounterProxy::signalEventOccurred(const QString& event) { emit eventOccurred(NAME, event); } +void MounterProxy::signalErrorOccurred(const MounterProxyError& errorMessage) { emit errorOccurred(NAME, errorMessage); } + //Public: bool MounterProxy::isMounting() { return mMounting; } @@ -112,7 +115,7 @@ void MounterProxy::proxyMountFinishedHandler(QNetworkReply* reply) if(reply->error() != QNetworkReply::NoError) { err = MounterProxyError(MounterProxyError::ProxyMount, reply->errorString()); - emit errorOccurred(NAME, err); + signalErrorOccurred(err); } else { @@ -126,7 +129,7 @@ void MounterProxy::proxyMountFinishedHandler(QNetworkReply* reply) //Public Slots: void MounterProxy::mount() { - emit eventOccurred(NAME, EVENT_MOUNTING); + signalEventOccurred(EVENT_MOUNTING); //-Create mount request------------------------- diff --git a/app/src/tools/mounter_proxy.h b/app/src/tools/mounter_proxy.h index 4e9d5a4..864d183 100644 --- a/app/src/tools/mounter_proxy.h +++ b/app/src/tools/mounter_proxy.h @@ -91,6 +91,9 @@ class MounterProxy : public QObject void noteProxyRequest(QNetworkAccessManager::Operation op, const QUrl& url, QByteArrayView data); void noteProxyResponse(const QString& response); + void signalEventOccurred(const QString& event); + void signalErrorOccurred(const MounterProxyError& errorMessage); + public: bool isMounting(); @@ -109,9 +112,9 @@ public slots: void abort(); signals: - void eventOccurred(QString name, const QString& event); - void errorOccurred(QString name, MounterProxyError errorMessage); - void mountFinished(MounterProxyError errorState); + void eventOccurred(const QString& name, const QString& event); + void errorOccurred(const QString& name, const MounterProxyError& errorMessage); + void mountFinished(const MounterProxyError& errorState); // For now these just cause a busy state void mountProgress(qint64 progress); diff --git a/app/src/tools/mounter_qmp.cpp b/app/src/tools/mounter_qmp.cpp index bbb34a1..3341e89 100644 --- a/app/src/tools/mounter_qmp.cpp +++ b/app/src/tools/mounter_qmp.cpp @@ -74,7 +74,7 @@ void MounterQmp::finish() void MounterQmp::createMountPoint() { - emit eventOccurred(NAME, EVENT_CREATING_MOUNT_POINT); + signalEventOccurred(EVENT_CREATING_MOUNT_POINT); // Build commands QString blockDevAddCmd = u"blockdev-add"_s; @@ -113,6 +113,9 @@ void MounterQmp::createMountPoint() // Await finished() signal... } +void MounterQmp::signalEventOccurred(const QString& event) { emit eventOccurred(NAME, event); } +void MounterQmp::signalErrorOccurred(const MounterQmpError& errorMessage) { emit errorOccurred(NAME, errorMessage); } + //Public: bool MounterQmp::isMounting() { return mMounting; } @@ -136,12 +139,12 @@ void MounterQmp::qmpiConnectedHandler(QJsonObject version, QJsonArray capabiliti 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)); + signalEventOccurred(EVENT_QMP_WELCOME_MESSAGE.arg(versionStr, capabilitiesStr)); } void MounterQmp::qmpiCommandsExhaustedHandler() { - emit eventOccurred(NAME, EVENT_DISCONNECTING_FROM_QEMU); + signalEventOccurred(EVENT_DISCONNECTING_FROM_QEMU); mQemuMounter.disconnectFromHost(); } @@ -157,7 +160,7 @@ void MounterQmp::qmpiConnectionErrorHandler(QAbstractSocket::SocketError error) MounterQmpError err(MounterQmpError::QemuConnection, ENUM_NAME(error)); mErrorStatus = err; - emit errorOccurred(NAME, err); + signalErrorOccurred(err); } void MounterQmp::qmpiCommunicationErrorHandler(Qmpi::CommunicationError error) @@ -165,7 +168,7 @@ void MounterQmp::qmpiCommunicationErrorHandler(Qmpi::CommunicationError error) MounterQmpError err(MounterQmpError::QemuCommunication, ENUM_NAME(error)); mErrorStatus = err; - emit errorOccurred(NAME, err); + signalErrorOccurred(err); } void MounterQmp::qmpiCommandErrorHandler(QString errorClass, QString description, std::any context) @@ -175,13 +178,13 @@ void MounterQmp::qmpiCommandErrorHandler(QString errorClass, QString description MounterQmpError err(MounterQmpError::QemuCommand, commandErr); mErrorStatus = err; - emit errorOccurred(NAME, err); + signalErrorOccurred(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))); + signalEventOccurred(EVENT_QMP_COMMAND_RESPONSE.arg(std::any_cast(context), Qx::asString(value))); } void MounterQmp::qmpiEventOccurredHandler(QString name, QJsonObject data, QDateTime timestamp) @@ -189,14 +192,14 @@ void MounterQmp::qmpiEventOccurredHandler(QString name, QJsonObject data, QDateT 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)); + signalEventOccurred(EVENT_QMP_EVENT.arg(name, dataStr, timestampStr)); } //Public Slots: void MounterQmp::mount() { // Connect to QEMU instance - emit eventOccurred(NAME, EVENT_CONNECTING_TO_QEMU); + signalEventOccurred(EVENT_CONNECTING_TO_QEMU); mQemuMounter.connectToHost(); // Await readyForCommands() signal... @@ -210,7 +213,7 @@ void MounterQmp::abort() MounterQmpError err(MounterQmpError::QemuConnection, ERR_QMP_CONNECTION_ABORT); mErrorStatus = err; - emit errorOccurred(NAME, err); + signalErrorOccurred(err); mQemuMounter.abort(); // Call last here because it causes finished signal to emit immediately } } diff --git a/app/src/tools/mounter_qmp.h b/app/src/tools/mounter_qmp.h index 36e9efa..0e3fe58 100644 --- a/app/src/tools/mounter_qmp.h +++ b/app/src/tools/mounter_qmp.h @@ -110,6 +110,9 @@ class MounterQmp : public QObject void finish(); void createMountPoint(); + void signalEventOccurred(const QString& event); + void signalErrorOccurred(const MounterQmpError& errorMessage); + public: bool isMounting(); @@ -143,9 +146,9 @@ public slots: void abort(); signals: - void eventOccurred(QString name, QString event); - void errorOccurred(QString name, MounterQmpError errorMessage); - void mountFinished(MounterQmpError errorState); + void eventOccurred(const QString& name, const QString& event); + void errorOccurred(const QString& name, const MounterQmpError& errorMessage); + void mountFinished(const MounterQmpError& errorState); }; #endif // MOUNTER_QMP_H diff --git a/app/src/tools/mounter_router.cpp b/app/src/tools/mounter_router.cpp index 05f6500..81beeb7 100644 --- a/app/src/tools/mounter_router.cpp +++ b/app/src/tools/mounter_router.cpp @@ -56,18 +56,18 @@ MounterRouter::MounterRouter(QObject* parent) : * 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); + signalEventOccurred(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); + signalEventOccurred(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); + signalEventOccurred(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"}"); + signalEventOccurred(u"Unexpected SSL errors from PHP server! {"_s + errStrList + u"}"_s"}"); }); } @@ -88,6 +88,9 @@ QString MounterRouter::mountValue() const { return mMountValue; } void MounterRouter::setRouterPort(quint16 port) { mRouterPort = port; } void MounterRouter::setMountValue(const QString& value) { mMountValue = value; } +void MounterRouter::signalEventOccurred(const QString& event) { emit eventOccurred(NAME, event); } +void MounterRouter::signalErrorOccurred(const MounterRouterError& errorMessage) { emit errorOccurred(NAME, errorMessage); } + //-Signals & Slots------------------------------------------------------------------------------------------------------------ //Private Slots: void MounterRouter::mountFinishedHandler(QNetworkReply* reply) @@ -100,12 +103,12 @@ void MounterRouter::mountFinishedHandler(QNetworkReply* reply) if(reply->error() != QNetworkReply::NoError && reply->error() != QNetworkReply::InternalServerError) { err = MounterRouterError(MounterRouterError::Failed, reply->errorString()); - emit errorOccurred(NAME, err); + signalErrorOccurred(err); } else { QByteArray response = reply->readAll(); - emit eventOccurred(NAME, EVENT_ROUTER_RESPONSE.arg(response)); + signalEventOccurred(EVENT_ROUTER_RESPONSE.arg(response)); } finish(err); @@ -114,7 +117,7 @@ void MounterRouter::mountFinishedHandler(QNetworkReply* reply) //Public Slots: void MounterRouter::mount() { - emit eventOccurred(NAME, EVENT_MOUNTING_THROUGH_ROUTER); + signalEventOccurred(EVENT_MOUNTING_THROUGH_ROUTER); // Create mount request QUrl mountUrl; @@ -135,7 +138,7 @@ void MounterRouter::mount() mRouterMountReply = mNam.get(mountReq); // Log request - emit eventOccurred(NAME, EVENT_REQUEST_SENT.arg(ENUM_NAME(mRouterMountReply->operation()), mountUrl.toString())); + signalEventOccurred(EVENT_REQUEST_SENT.arg(ENUM_NAME(mRouterMountReply->operation()), mountUrl.toString())); // Await finished() signal... } diff --git a/app/src/tools/mounter_router.h b/app/src/tools/mounter_router.h index 7c201bd..ea85892 100644 --- a/app/src/tools/mounter_router.h +++ b/app/src/tools/mounter_router.h @@ -91,6 +91,9 @@ class MounterRouter : public QObject private: void finish(const MounterRouterError& result); + void signalEventOccurred(const QString& event); + void signalErrorOccurred(const MounterRouterError& errorMessage); + public: bool isMounting(); @@ -109,8 +112,8 @@ public slots: void abort(); signals: - void eventOccurred(QString name, QString event); - void errorOccurred(QString name, MounterRouterError errorMessage); + void eventOccurred(const QString& name, const QString& event); + void errorOccurred(const QString& name, const MounterRouterError& errorMessage); void mountFinished(MounterRouterError errorState); }; From 7ea6207a1d8589f87ecbc09083b0c6cec618245f Mon Sep 17 00:00:00 2001 From: Christian Heimlich Date: Sun, 26 Nov 2023 15:01:59 -0500 Subject: [PATCH 25/26] Update README.md --- README.md | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 9b32315..ab4dad9 100644 --- a/README.md +++ b/README.md @@ -45,7 +45,12 @@ Update: ## Compatability ### General -All Flashpoint features are generally supported, other than editing configuration files and user data (like playlists) and querying title meta-data through the command-line, and searching is more limited. See the [All Commands/Options](#all-commandsoptions) section for more information. + +Most flashpoint features are supported. The regular launcher still must be used for the following: +- Changing user configuration, like preferences, playlists, etc. +- Updating the launcher and downloading game updates + +See the [All Commands/Options](#all-commandsoptions) section for more information. While constantly testing for complete compatibility is infeasible given the size of Flashpoint, CLIFp was designed with full compatibility in mind and theoretically is 100% compatible with the Flashpoint collection. @@ -105,7 +110,7 @@ Or if feeling spontaneous, use the **-r** switch, followed by a library filter t See the [All Commands/Options](#all-commandsoptions) section for more information. **Direct Execution:** -The legacy approach is to use the **run** command with the **--app** and **--param** switches. This will start Flashpoint's webserver and then start the application specified with the provided parameters: +The legacy approach is to use the **run** command with the **--app** and **--param** switches. This will start Flashpoint's services and then start the application specified with the provided parameters: CLIFp run --app="FPSoftware\Flash\flashplayer_32_sa.exe" --param="http://www.mowa.org/work/buttons/buttons_art/basic.swf" @@ -214,7 +219,7 @@ Options: -------------------------------------------------------------------------------- - **run** - Start Flashpoint's webserver and then execute the provided application + **run** - Start Flashpoint's services and then execute the provided application Options: - **-a | --app:** Relative (to Flashpoint Directory) path of application to launch From 68a811c73bbf619ca8544011d1388bcaf8de86fd Mon Sep 17 00:00:00 2001 From: Christian Heimlich Date: Sun, 26 Nov 2023 19:00:09 -0500 Subject: [PATCH 26/26] Bump --- CMakeLists.txt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index f3584a9..070179c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,14 +6,14 @@ cmake_minimum_required(VERSION 3.24.0...3.26.0) # Project # NOTE: DON'T USE TRAILING ZEROS IN VERSIONS project(CLIFp - VERSION 0.9.9.1 + VERSION 0.9.10 LANGUAGES CXX DESCRIPTION "Command-line Interface for Flashpoint Archive" ) # Get helper scripts include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/FetchOBCMake.cmake) -fetch_ob_cmake("928cbafc2036f97cfaeb50cd892209b6e1037b6d") +fetch_ob_cmake("v0.3.4") # Initialize project according to standard rules include(OB/Project) @@ -72,14 +72,14 @@ endif() include(OB/FetchQx) ob_fetch_qx( - REF "01560b1470364cd0c11d7341fdf9b11afa24dcff" + REF "v0.5.6" COMPONENTS ${CLIFP_QX_COMPONENTS} ) # Fetch libfp (build and import from source) include(OB/Fetchlibfp) -ob_fetch_libfp("47af4e2fed04e6fdef7111a3bb5b12e58bebe77b") +ob_fetch_libfp("v0.5.2") # Fetch QI-QMP (build and import from source) include(OB/FetchQI-QMP)