From 93d404d2bbc88f6d517283abd9cefbe0585dc439 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mika=C3=ABl=20Capelle?= Date: Tue, 26 Sep 2023 21:29:32 +0200 Subject: [PATCH 1/4] Add working directory and arguments to onAboutToRun (optional). --- src/mainwindow.cpp | 12 ++++++++---- src/modlistcontextmenu.cpp | 2 +- src/organizercore.cpp | 26 +++++++++++--------------- src/organizercore.h | 15 +++++++-------- src/organizerproxy.cpp | 13 +++++++++++++ src/organizerproxy.h | 2 ++ src/processrunner.cpp | 4 ++-- 7 files changed, 44 insertions(+), 30 deletions(-) diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index ce2c4dd04..ed0ac724e 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1803,7 +1803,9 @@ void MainWindow::on_profileBox_currentIndexChanged(int index) m_OrganizerCore.managedGame()->feature(); if (invalidation != nullptr) { if (invalidation->prepareProfile(m_OrganizerCore.currentProfile())) { - QTimer::singleShot(5, &m_OrganizerCore, SLOT(profileRefresh())); + QTimer::singleShot(5, [this] { + m_OrganizerCore.refresh(); + }); } } } @@ -2395,7 +2397,9 @@ void MainWindow::on_actionAdd_Profile_triggered() m_OrganizerCore.managedGame()->feature(); if (invalidation != nullptr) { if (invalidation->prepareProfile(m_OrganizerCore.currentProfile())) { - QTimer::singleShot(5, &m_OrganizerCore, SLOT(profileRefresh())); + QTimer::singleShot(5, [this] { + m_OrganizerCore.refresh(); + }); } } } @@ -2555,7 +2559,7 @@ void MainWindow::setWindowEnabled(bool enabled) void MainWindow::refreshProfile_activated() { - m_OrganizerCore.profileRefresh(); + m_OrganizerCore.refresh(); } void MainWindow::saveArchiveList() @@ -2782,7 +2786,7 @@ void MainWindow::on_actionSettings_triggered() if ((settings.paths().mods() != oldModDirectory) || (settings.interface().displayForeign() != oldDisplayForeign)) { - m_OrganizerCore.profileRefresh(); + m_OrganizerCore.refresh(); } const auto state = settings.archiveParsing(); diff --git a/src/modlistcontextmenu.cpp b/src/modlistcontextmenu.cpp index 06cd19d6d..d4630ec36 100644 --- a/src/modlistcontextmenu.cpp +++ b/src/modlistcontextmenu.cpp @@ -97,7 +97,7 @@ void ModListGlobalContextMenu::populate(OrganizerCore& core, ModListView* view, addAction(tr("Auto assign categories"), [=]() { view->actions().assignCategories(); }); - addAction(tr("Refresh"), &core, &OrganizerCore::profileRefresh); + addAction(tr("Refresh"), &core, &OrganizerCore::refresh); addAction(tr("Export to csv..."), [=]() { view->actions().exportModListCSV(); }); diff --git a/src/organizercore.cpp b/src/organizercore.cpp index b4e758d1b..2552f9f0b 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -111,9 +111,9 @@ OrganizerCore::OrganizerCore(Settings& settings) connect(&m_DownloadManager, SIGNAL(downloadSpeed(QString, int)), this, SLOT(downloadSpeed(QString, int))); - connect(m_DirectoryRefresher.get(), SIGNAL(refreshed()), this, - SLOT(directory_refreshed())); - + connect(m_DirectoryRefresher.get(), &DirectoryRefresher::refreshed, [this]() { + onDirectoryRefreshed(); + }); connect(&m_ModList, SIGNAL(removeOrigin(QString)), this, SLOT(removeOrigin(QString))); connect(&m_ModList, &ModList::modStatesChanged, [=] { currentProfile()->writeModlist(); @@ -1134,8 +1134,8 @@ bool OrganizerCore::previewFile(QWidget* parent, const QString& originName, return true; } -boost::signals2::connection -OrganizerCore::onAboutToRun(const std::function& func) +boost::signals2::connection OrganizerCore::onAboutToRun( + const std::function& func) { return m_AboutToRun.connect(func); } @@ -1513,13 +1513,13 @@ void OrganizerCore::refreshDirectoryStructure() std::set(archives.begin(), archives.end())); // runs refresh() in a thread - QTimer::singleShot(0, m_DirectoryRefresher.get(), SLOT(refresh())); + QTimer::singleShot(0, m_DirectoryRefresher.get(), &DirectoryRefresher::refresh); } -void OrganizerCore::directory_refreshed() +void OrganizerCore::onDirectoryRefreshed() { log::debug("directory refreshed, finishing up"); - TimeThis tt("OrganizerCore::directory_refreshed()"); + TimeThis tt("OrganizerCore::onDirectoryRefreshed()"); DirectoryEntry* newStructure = m_DirectoryRefresher->stealDirectoryStructure(); Q_ASSERT(newStructure != m_DirectoryStructure); @@ -1571,11 +1571,6 @@ void OrganizerCore::directory_refreshed() log::debug("refresh done"); } -void OrganizerCore::profileRefresh() -{ - refresh(); -} - void OrganizerCore::clearCaches(std::vector const& indices) const { const auto insert = [](auto& dest, const auto& from) { @@ -1920,7 +1915,8 @@ ProcessRunner OrganizerCore::processRunner() } bool OrganizerCore::beforeRun( - const QFileInfo& binary, const QString& profileName, const QString& customOverwrite, + const QFileInfo& binary, const QDir& cwd, const QString& arguments, + const QString& profileName, const QString& customOverwrite, const QList& forcedLibraries) { saveCurrentProfile(); @@ -1939,7 +1935,7 @@ bool OrganizerCore::beforeRun( } // TODO: should also pass arguments - if (!m_AboutToRun(binary.absoluteFilePath())) { + if (!m_AboutToRun(binary.absoluteFilePath(), cwd, arguments)) { log::debug("start of \"{}\" cancelled by plugin", binary.absoluteFilePath()); return false; } diff --git a/src/organizercore.h b/src/organizercore.h index daeb7a7f3..ee091723b 100644 --- a/src/organizercore.h +++ b/src/organizercore.h @@ -83,7 +83,8 @@ class OrganizerCore : public QObject, public MOBase::IPluginDiagnose private: using SignalAboutToRunApplication = - boost::signals2::signal; + boost::signals2::signal; using SignalFinishedRunApplication = boost::signals2::signal; using SignalUserInterfaceInitialized = boost::signals2::signal; @@ -269,8 +270,8 @@ class OrganizerCore : public QObject, public MOBase::IPluginDiagnose ProcessRunner processRunner(); - bool beforeRun(const QFileInfo& binary, const QString& profileName, - const QString& customOverwrite, + bool beforeRun(const QFileInfo& binary, const QDir& cwd, const QString& arguments, + const QString& profileName, const QString& customOverwrite, const QList& forcedLibraries); void afterRun(const QFileInfo& binary, DWORD exitCode); @@ -356,8 +357,8 @@ class OrganizerCore : public QObject, public MOBase::IPluginDiagnose ModList* modList(); void refresh(bool saveChanges = true); - boost::signals2::connection - onAboutToRun(const std::function& func); + boost::signals2::connection onAboutToRun( + const std::function& func); boost::signals2::connection onFinishedRun(const std::function& func); boost::signals2::connection @@ -388,8 +389,6 @@ class OrganizerCore : public QObject, public MOBase::IPluginDiagnose public slots: - void profileRefresh(); - void syncOverwrite(); void savePluginList(); @@ -472,7 +471,7 @@ public slots: private slots: - void directory_refreshed(); + void onDirectoryRefreshed(); void downloadRequested(QNetworkReply* reply, QString gameName, int modID, const QString& fileName); void removeOrigin(const QString& name); diff --git a/src/organizerproxy.cpp b/src/organizerproxy.cpp index a47e8798a..13416cb0f 100644 --- a/src/organizerproxy.cpp +++ b/src/organizerproxy.cpp @@ -323,6 +323,19 @@ MOBase::IPluginGame const* OrganizerProxy::managedGame() const // CALLBACKS bool OrganizerProxy::onAboutToRun(const std::function& func) +{ + return m_Proxied + ->onAboutToRun(MOShared::callIfPluginActive( + this, + [func](const QString& binary, const QDir&, const QString&) { + return func(binary); + }, + true)) + .connected(); +} + +bool OrganizerProxy::onAboutToRun( + const std::function& func) { return m_Proxied->onAboutToRun(MOShared::callIfPluginActive(this, func, true)) .connected(); diff --git a/src/organizerproxy.h b/src/organizerproxy.h index 771d09810..ed39a69cd 100644 --- a/src/organizerproxy.h +++ b/src/organizerproxy.h @@ -73,6 +73,8 @@ class OrganizerProxy : public MOBase::IOrganizer virtual void refresh(bool saveChanges); virtual bool onAboutToRun(const std::function& func) override; + virtual bool onAboutToRun(const std::function& func) override; virtual bool onFinishedRun(const std::function& func) override; virtual bool diff --git a/src/processrunner.cpp b/src/processrunner.cpp index f748f4909..65e44598a 100644 --- a/src/processrunner.cpp +++ b/src/processrunner.cpp @@ -768,8 +768,8 @@ std::optional ProcessRunner::runBinary() // saves profile, sets up usvfs, notifies plugins, etc.; can return false if // a plugin doesn't want the program to run (such as when checkFNIS fails to // run FNIS and the user clicks cancel) - if (!m_core.beforeRun(m_sp.binary, m_profileName, m_customOverwrite, - m_forcedLibraries)) { + if (!m_core.beforeRun(m_sp.binary, m_sp.currentDirectory, m_sp.arguments, + m_profileName, m_customOverwrite, m_forcedLibraries)) { return Error; } From 061a8efe8a7283e6edbb0e9a2d36ee079de52ad4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mika=C3=ABl=20Capelle?= Date: Tue, 26 Sep 2023 21:34:21 +0200 Subject: [PATCH 2/4] Add onRefreshCallback functionality. --- src/organizercore.cpp | 72 +++++++++++++++++++++--------------------- src/organizercore.h | 29 +++++++++++++++-- src/organizerproxy.cpp | 24 +++++++++----- src/organizerproxy.h | 2 ++ 4 files changed, 81 insertions(+), 46 deletions(-) diff --git a/src/organizercore.cpp b/src/organizercore.cpp index 2552f9f0b..2ef3aeb56 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -1195,6 +1195,18 @@ OrganizerCore::onPluginDisabled(std::function const& func) return m_PluginDisabled.connect(func); } +boost::signals2::connection +OrganizerCore::onNextRefresh(std::function const& func, + RefreshCallbackGroup group, bool immediateIfPossible) +{ + if (!immediateIfPossible || m_DirectoryUpdate) { + return m_OnNextRefreshCallbacks.connect(static_cast(group), func); + } else { + func(); + return {}; + } +} + void OrganizerCore::refresh(bool saveChanges) { // don't lose changes! @@ -1212,25 +1224,21 @@ void OrganizerCore::refresh(bool saveChanges) void OrganizerCore::refreshESPList(bool force) { - TimeThis tt("OrganizerCore::refreshESPList()"); + onNextRefresh( + [this, force] { + TimeThis tt("OrganizerCore::refreshESPList()"); - if (m_DirectoryUpdate) { - // don't mess up the esp list if we're currently updating the directory - // structure - m_PostRefreshTasks.append([=]() { - this->refreshESPList(force); - }); - return; - } - m_CurrentProfile->writeModlist(); + m_CurrentProfile->writeModlist(); - // clear list - try { - m_PluginList.refresh(m_CurrentProfile->name(), *m_DirectoryStructure, - m_CurrentProfile->getLockedOrderFileName(), force); - } catch (const std::exception& e) { - reportError(tr("Failed to refresh list of esps: %1").arg(e.what())); - } + // clear list + try { + m_PluginList.refresh(m_CurrentProfile->name(), *m_DirectoryStructure, + m_CurrentProfile->getLockedOrderFileName(), force); + } catch (const std::exception& e) { + reportError(tr("Failed to refresh list of esps: %1").arg(e.what())); + } + }, + RefreshCallbackGroup::CORE); } void OrganizerCore::refreshBSAList() @@ -1543,23 +1551,18 @@ void OrganizerCore::onDirectoryRefreshed() log::debug("structure deleter thread done"); }); - m_DirectoryUpdate = false; - log::debug("clearing caches"); for (int i = 0; i < m_ModList.rowCount(); ++i) { ModInfo::Ptr modInfo = ModInfo::getByIndex(i); modInfo->clearCaches(); } - if (!m_PostRefreshTasks.empty()) { - log::debug("running {} post refresh tasks", m_PostRefreshTasks.size()); - - for (auto task : m_PostRefreshTasks) { - task(); - } + // needs to be done before post refresh tasks + m_DirectoryUpdate = false; - m_PostRefreshTasks.clear(); - } + log::debug("running {} post refresh tasks"); + m_OnNextRefreshCallbacks(); + m_OnNextRefreshCallbacks.disconnect_all_slots(); if (m_CurrentProfile != nullptr) { log::debug("refreshing lists"); @@ -1886,15 +1889,12 @@ bool OrganizerCore::saveCurrentLists() void OrganizerCore::savePluginList() { - if (m_DirectoryUpdate) { - // delay save till after directory update - m_PostRefreshTasks.append([this]() { - this->savePluginList(); - }); - return; - } - m_PluginList.saveTo(m_CurrentProfile->getLockedOrderFileName()); - m_PluginList.saveLoadOrder(*m_DirectoryStructure); + onNextRefresh( + [this]() { + m_PluginList.saveTo(m_CurrentProfile->getLockedOrderFileName()); + m_PluginList.saveLoadOrder(*m_DirectoryStructure); + }, + RefreshCallbackGroup::CORE); } void OrganizerCore::saveCurrentProfile() diff --git a/src/organizercore.h b/src/organizercore.h index ee091723b..89d977cde 100644 --- a/src/organizercore.h +++ b/src/organizercore.h @@ -212,6 +212,20 @@ class OrganizerCore : public QObject, public MOBase::IPluginDiagnose friend class OrganizerCore; }; + // enumeration for the groups where refresh callbacks can be put + // + enum class RefreshCallbackGroup : int + { + // for callbacks by the core itself, highest priority + CORE = 0, + + // internal MO2 callbacks + INTERNAL = 1, + + // plugins callbacks + PLUGIN = 2 + }; + public: OrganizerCore(Settings& settings); @@ -380,6 +394,16 @@ class OrganizerCore : public QObject, public MOBase::IPluginDiagnose boost::signals2::connection onPluginDisabled(std::function const& func); + // add a function to be called after the next refresh is done + // + // - group to add the function to + // - if immediateIfReady is true, the function will be called immediately if no + // directory update is running + boost::signals2::connection + onNextRefresh(std::function const& func, + RefreshCallbackGroup group = RefreshCallbackGroup::INTERNAL, + bool immediateIfReady = true); + public: // IPluginDiagnose interface virtual std::vector activeProblems() const; virtual QString shortDescription(unsigned int key) const; @@ -506,11 +530,12 @@ private slots: SignalPluginEnabled m_PluginEnabled; SignalPluginEnabled m_PluginDisabled; + boost::signals2::signal m_OnNextRefreshCallbacks; + ModList m_ModList; PluginList m_PluginList; QList> m_PostLoginTasks; - QList> m_PostRefreshTasks; ExecutablesList m_ExecutablesList; QStringList m_PendingDownloads; @@ -528,7 +553,7 @@ private slots: std::thread m_StructureDeleter; - bool m_DirectoryUpdate; + std::atomic m_DirectoryUpdate; bool m_ArchivesInit; MOBase::DelayedFileWriter m_PluginListsWriter; diff --git a/src/organizerproxy.cpp b/src/organizerproxy.cpp index 13416cb0f..dcf816cbf 100644 --- a/src/organizerproxy.cpp +++ b/src/organizerproxy.cpp @@ -347,6 +347,22 @@ bool OrganizerProxy::onFinishedRun( return m_Proxied->onFinishedRun(MOShared::callIfPluginActive(this, func)).connected(); } +bool OrganizerProxy::onUserInterfaceInitialized( + std::function const& func) +{ + // Always call this one to allow plugin to initialize themselves even when not active: + return m_UserInterfaceInitialized.connect(func).connected(); +} + +bool OrganizerProxy::onNextRefresh(const std::function& func, + bool immediateIfPossible) +{ + return m_Proxied + ->onNextRefresh(MOShared::callIfPluginActive(this, func), + OrganizerCore::RefreshCallbackGroup::PLUGIN, immediateIfPossible) + .connected(); +} + bool OrganizerProxy::onProfileCreated(std::function const& func) { return m_ProfileCreated.connect(func).connected(); @@ -368,14 +384,6 @@ bool OrganizerProxy::onProfileChanged( { return m_ProfileChanged.connect(func).connected(); } - -bool OrganizerProxy::onUserInterfaceInitialized( - std::function const& func) -{ - // Always call this one to allow plugin to initialize themselves even when not active: - return m_UserInterfaceInitialized.connect(func).connected(); -} - // Always call these one, otherwise plugin cannot detect they are being enabled / // disabled: bool OrganizerProxy::onPluginSettingChanged( diff --git a/src/organizerproxy.h b/src/organizerproxy.h index ed39a69cd..751464328 100644 --- a/src/organizerproxy.h +++ b/src/organizerproxy.h @@ -79,6 +79,8 @@ class OrganizerProxy : public MOBase::IOrganizer onFinishedRun(const std::function& func) override; virtual bool onUserInterfaceInitialized(std::function const& func) override; + virtual bool onNextRefresh(const std::function& func, + bool immediateIfPossible) override; virtual bool onProfileCreated(std::function const& func) override; virtual bool onProfileRenamed( From d817bbda555f9c04f5e76628e18ae8672a5e1b35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mika=C3=ABl=20Capelle?= Date: Thu, 28 Sep 2023 20:37:25 +0200 Subject: [PATCH 3/4] Rename PLUGIN callback group to EXTERNAL. Explicit immediateIfPossible. --- src/organizercore.cpp | 8 ++++---- src/organizercore.h | 22 ++++++++++++++++------ src/organizerproxy.cpp | 5 ++++- 3 files changed, 24 insertions(+), 11 deletions(-) diff --git a/src/organizercore.cpp b/src/organizercore.cpp index 2ef3aeb56..a7b8ac869 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -1197,9 +1197,9 @@ OrganizerCore::onPluginDisabled(std::function const& func) boost::signals2::connection OrganizerCore::onNextRefresh(std::function const& func, - RefreshCallbackGroup group, bool immediateIfPossible) + RefreshCallbackGroup group, RefreshCallbackMode mode) { - if (!immediateIfPossible || m_DirectoryUpdate) { + if (m_DirectoryUpdate || mode == RefreshCallbackMode::FORCE_WAIT_FOR_REFRESH) { return m_OnNextRefreshCallbacks.connect(static_cast(group), func); } else { func(); @@ -1238,7 +1238,7 @@ void OrganizerCore::refreshESPList(bool force) reportError(tr("Failed to refresh list of esps: %1").arg(e.what())); } }, - RefreshCallbackGroup::CORE); + RefreshCallbackGroup::CORE, RefreshCallbackMode::RUN_NOW_IF_POSSIBLE); } void OrganizerCore::refreshBSAList() @@ -1894,7 +1894,7 @@ void OrganizerCore::savePluginList() m_PluginList.saveTo(m_CurrentProfile->getLockedOrderFileName()); m_PluginList.saveLoadOrder(*m_DirectoryStructure); }, - RefreshCallbackGroup::CORE); + RefreshCallbackGroup::CORE, RefreshCallbackMode::RUN_NOW_IF_POSSIBLE); } void OrganizerCore::saveCurrentProfile() diff --git a/src/organizercore.h b/src/organizercore.h index 89d977cde..7238466a1 100644 --- a/src/organizercore.h +++ b/src/organizercore.h @@ -212,6 +212,17 @@ class OrganizerCore : public QObject, public MOBase::IPluginDiagnose friend class OrganizerCore; }; + // enumeration for the mode when adding refresh callbacks + // + enum class RefreshCallbackMode : int + { + // run the callbacks immediately if no refresh is running + RUN_NOW_IF_POSSIBLE = 0, + + // wait for the next refresh if none is running + FORCE_WAIT_FOR_REFRESH = 1 + }; + // enumeration for the groups where refresh callbacks can be put // enum class RefreshCallbackGroup : int @@ -222,8 +233,8 @@ class OrganizerCore : public QObject, public MOBase::IPluginDiagnose // internal MO2 callbacks INTERNAL = 1, - // plugins callbacks - PLUGIN = 2 + // external callbacks, typically MO2 plugins + EXTERNAL = 2 }; public: @@ -399,10 +410,9 @@ class OrganizerCore : public QObject, public MOBase::IPluginDiagnose // - group to add the function to // - if immediateIfReady is true, the function will be called immediately if no // directory update is running - boost::signals2::connection - onNextRefresh(std::function const& func, - RefreshCallbackGroup group = RefreshCallbackGroup::INTERNAL, - bool immediateIfReady = true); + boost::signals2::connection onNextRefresh(std::function const& func, + RefreshCallbackGroup group, + RefreshCallbackMode mode); public: // IPluginDiagnose interface virtual std::vector activeProblems() const; diff --git a/src/organizerproxy.cpp b/src/organizerproxy.cpp index dcf816cbf..0c1fb4099 100644 --- a/src/organizerproxy.cpp +++ b/src/organizerproxy.cpp @@ -357,9 +357,12 @@ bool OrganizerProxy::onUserInterfaceInitialized( bool OrganizerProxy::onNextRefresh(const std::function& func, bool immediateIfPossible) { + using enum OrganizerCore::RefreshCallbackMode; return m_Proxied ->onNextRefresh(MOShared::callIfPluginActive(this, func), - OrganizerCore::RefreshCallbackGroup::PLUGIN, immediateIfPossible) + OrganizerCore::RefreshCallbackGroup::EXTERNAL, + immediateIfPossible ? RUN_NOW_IF_POSSIBLE + : FORCE_WAIT_FOR_REFRESH) .connected(); } From 69f733f8cc186a69114969cec78af5086a888f99 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mika=C3=ABl=20Capelle?= Date: Thu, 28 Sep 2023 20:39:03 +0200 Subject: [PATCH 4/4] Remove TODO. --- src/organizercore.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/organizercore.cpp b/src/organizercore.cpp index a7b8ac869..769c6018e 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -1934,7 +1934,6 @@ bool OrganizerCore::beforeRun( m_CurrentProfile->writeModlistNow(true); } - // TODO: should also pass arguments if (!m_AboutToRun(binary.absoluteFilePath(), cwd, arguments)) { log::debug("start of \"{}\" cancelled by plugin", binary.absoluteFilePath()); return false;