From 3bca0e761597f590fa3b1c6a57ee325e774e3f49 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sun, 10 Jan 2021 22:06:38 -0500 Subject: split run() into setup() and run() this is so command line processing can happen outside of MOApplication many important objects were local to run(), so they're not members of MOApplication instead --- src/main.cpp | 60 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 58 insertions(+), 2 deletions(-) (limited to 'src/main.cpp') diff --git a/src/main.cpp b/src/main.cpp index 877236e1..362caaad 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -4,8 +4,10 @@ #include "organizercore.h" #include "commandline.h" #include "env.h" +#include "instancemanager.h" #include "thread_utils.h" #include "shared/util.h" +#include #include using namespace MOBase; @@ -31,7 +33,7 @@ int main(int argc, char *argv[]) TimeThis tt("main()"); QApplication::setAttribute(Qt::AA_EnableHighDpiScaling); - MOApplication app(cl, argc, argv); + MOApplication app(argc, argv); MOMultiProcess multiProcess(cl.multiple()); if (multiProcess.ephemeral()) { @@ -40,7 +42,61 @@ int main(int argc, char *argv[]) tt.stop(); - return app.run(multiProcess); + + // MO runs in a loop because it can be restarted in several ways, such as + // when switching instances or changing some settings + for (;;) + { + try + { + auto& m = InstanceManager::singleton(); + + if (cl.instance()) { + m.overrideInstance(*cl.instance()); + } + + if (cl.profile()) { + m.overrideProfile(*cl.profile()); + } + + { + const auto r = app.setup(multiProcess); + + if (r == RestartExitCode) { + // resets things when MO is "restarted" + app.resetForRestart(); + + // don't reprocess command line + cl.clear(); + + continue; + } + } + + if (auto r=cl.setupCore(app.core())) { + return *r; + } + + const auto r = app.run(multiProcess); + + if (r == RestartExitCode) { + // resets things when MO is "restarted" + app.resetForRestart(); + + // don't reprocess command line + cl.clear(); + + continue; + } + + return r; + } + catch (const std::exception &e) + { + reportError(e.what()); + return 1; + } + } } int forwardToPrimary(MOMultiProcess& multiProcess, const cl::CommandLine& cl) -- cgit v1.3.1 From e4ae41e3a29fc3e4714e1c3f8c9f071768d17b68 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sun, 10 Jan 2021 22:28:38 -0500 Subject: fixed all commands being legacy split running command into pre and post organizer --- src/commandline.cpp | 36 ++++++++++++++++++++++++------------ src/commandline.h | 26 ++++++++++++++------------ src/main.cpp | 4 ++-- 3 files changed, 40 insertions(+), 26 deletions(-) (limited to 'src/main.cpp') diff --git a/src/commandline.cpp b/src/commandline.cpp index 8c722990..c31b726f 100644 --- a/src/commandline.cpp +++ b/src/commandline.cpp @@ -49,6 +49,7 @@ std::string table( CommandLine::CommandLine() + : m_command(nullptr) { createOptions(); m_commands.push_back(std::make_unique()); @@ -57,7 +58,7 @@ CommandLine::CommandLine() m_commands.push_back(std::make_unique()); } -std::optional CommandLine::run(const std::wstring& line) +std::optional CommandLine::process(const std::wstring& line) { try { @@ -125,8 +126,10 @@ std::optional CommandLine::run(const std::wstring& line) } } - // run the command - return c->run(line, m_vm, opts); + c->process(line, m_vm, opts); + m_command = c.get(); + + return m_command->runPreOrganizer(); } catch(po::error& e) { @@ -201,7 +204,7 @@ std::optional CommandLine::run(const std::wstring& line) } } -std::optional CommandLine::setupCore(OrganizerCore& organizer) const +std::optional CommandLine::run(OrganizerCore& organizer) const { if (m_shortcut.isValid()) { if (m_shortcut.hasExecutable()) { @@ -247,6 +250,8 @@ std::optional CommandLine::setupCore(OrganizerCore& organizer) const QObject::tr("failed to start application: %1").arg(e.what())); return 1; } + } else if (m_command) { + return m_command->runPostOrganizer(); } return {}; @@ -437,7 +442,7 @@ po::positional_options_description Command::positional() const bool Command::legacy() const { - return true; + return false; } std::string Command::getUsageLine() const @@ -463,8 +468,7 @@ po::positional_options_description Command::getPositional() const return {}; } - -std::optional Command::run( +void Command::process( const std::wstring& originalLine, po::variables_map vm, std::vector untouched) @@ -472,8 +476,16 @@ std::optional Command::run( m_original = originalLine; m_vm = vm; m_untouched = untouched; +} + +std::optional Command::runPreOrganizer() +{ + return {}; +} - return doRun(); +std::optional Command::runPostOrganizer() +{ + return {}; } const std::wstring& Command::originalCmd() const @@ -507,7 +519,7 @@ Command::Meta CrashDumpCommand::meta() const return {"crashdump", "writes a crashdump for a running process of MO"}; } -std::optional CrashDumpCommand::doRun() +std::optional CrashDumpCommand::runPreOrganizer() { env::Console console; @@ -537,7 +549,7 @@ bool LaunchCommand::legacy() const return true; } -std::optional LaunchCommand::doRun() +std::optional LaunchCommand::runPreOrganizer() { // needs at least the working directory and process name if (untouched().size() < 2) { @@ -643,7 +655,7 @@ Command::Meta ExeCommand::meta() const return {"exe", "launches a configured executable"}; } -std::optional ExeCommand::doRun() +std::optional ExeCommand::runPostOrganizer() { const auto exe = vm()["exe-name"].as(); const auto args = vm()["arguments"].as(); @@ -665,7 +677,7 @@ Command::Meta RunCommand::meta() const return {"run", "launches an arbitrary program"}; } -std::optional RunCommand::doRun() +std::optional RunCommand::runPostOrganizer() { std::cout << "not implemented\n"; return {}; diff --git a/src/commandline.h b/src/commandline.h index 09c1bb8e..14a453b2 100644 --- a/src/commandline.h +++ b/src/commandline.h @@ -50,13 +50,18 @@ public: // virtual bool legacy() const; - // runs this command, eventually calls doRun() // - std::optional run( + // + void process( const std::wstring& originalLine, po::variables_map vm, std::vector untouched); + // + // + virtual std::optional runPreOrganizer(); + virtual std::optional runPostOrganizer(); + protected: // meta information about this command, returned by derived classes // @@ -86,10 +91,6 @@ protected: // virtual Meta meta() const = 0; - // runs the command - // - virtual std::optional doRun() = 0; - // returns the original command line // @@ -117,7 +118,7 @@ class CrashDumpCommand : public Command protected: po::options_description getVisibleOptions() const override; Meta meta() const override; - std::optional doRun() override; + std::optional runPreOrganizer() override; }; @@ -140,7 +141,7 @@ public: protected: Meta meta() const override; - std::optional doRun() override; + std::optional runPreOrganizer() override; int SpawnWaitProcess(LPCWSTR workingDirectory, LPCWSTR commandLine); @@ -159,7 +160,7 @@ protected: po::options_description getInternalOptions() const override; po::positional_options_description getPositional() const override; Meta meta() const override; - std::optional doRun() override; + std::optional runPostOrganizer() override; }; @@ -170,7 +171,7 @@ class RunCommand : public Command protected: po::options_description getOptions() const; Meta meta() const override; - std::optional doRun() override; + std::optional runPostOrganizer() override; }; @@ -218,14 +219,14 @@ public: // returns an empty optional if execution should continue, or a return code // if MO must quit // - std::optional run(const std::wstring& line); + std::optional process(const std::wstring& line); // handles moshortcut, nxm links and starting processes // // returns an empty optional if execution should continue, or a return code // if MO must quit // - std::optional setupCore(OrganizerCore& organizer) const; + std::optional run(OrganizerCore& organizer) const; // clears parsed options, used when MO is "restarted" so the options aren't @@ -275,6 +276,7 @@ private: std::optional m_nxmLink; std::optional m_executable; QStringList m_untouched; + Command* m_command; void createOptions(); std::string more() const; diff --git a/src/main.cpp b/src/main.cpp index 362caaad..f85e13f3 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -23,7 +23,7 @@ int main(int argc, char *argv[]) setExceptionHandlers(); cl::CommandLine cl; - if (auto r=cl.run(GetCommandLineW())) { + if (auto r=cl.process(GetCommandLineW())) { return *r; } @@ -73,7 +73,7 @@ int main(int argc, char *argv[]) } } - if (auto r=cl.setupCore(app.core())) { + if (auto r=cl.run(app.core())) { return *r; } -- cgit v1.3.1 From 09eb507ddad0d164a39d72c5c121d332966ef462 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Mon, 11 Jan 2021 01:25:47 -0500 Subject: moved externalMessage() to MOApplication commands can forward to the primary instance added reload-plugin command MessageDialog now tries to find the main window when the reference is null, which actually used to happen often because activeWindow() is typically used, but that's null when the main window doesn't have focus (like when downloading from nexus, for example) --- src/commandline.cpp | 118 +++++++++++++++++++++++++++++++++++++++++++------- src/commandline.h | 37 +++++++++++++--- src/main.cpp | 34 +++++++-------- src/messagedialog.cpp | 22 +++++++--- src/moapplication.cpp | 49 ++++++++++++++++++--- src/moapplication.h | 4 +- src/organizercore.cpp | 19 -------- src/organizercore.h | 1 - 8 files changed, 213 insertions(+), 71 deletions(-) (limited to 'src/main.cpp') diff --git a/src/commandline.cpp b/src/commandline.cpp index fd0f6cb5..b6210463 100644 --- a/src/commandline.cpp +++ b/src/commandline.cpp @@ -2,8 +2,10 @@ #include "env.h" #include "organizercore.h" #include "instancemanager.h" +#include "multiprocess.h" #include "shared/util.h" #include "shared/error_report.h" +#include "shared/appconfig.h" #include #include @@ -54,9 +56,12 @@ CommandLine::CommandLine() : m_command(nullptr) { createOptions(); - m_commands.push_back(std::make_unique()); - m_commands.push_back(std::make_unique()); - m_commands.push_back(std::make_unique()); + + add< + RunCommand, + ReloadPluginCommand, + CrashDumpCommand, + LaunchCommand>(); } std::optional CommandLine::process(const std::wstring& line) @@ -130,7 +135,7 @@ std::optional CommandLine::process(const std::wstring& line) c->process(line, m_vm, opts); m_command = c.get(); - return m_command->runPreOrganizer(); + return m_command->runEarly(); } catch(po::error& e) { @@ -170,7 +175,7 @@ std::optional CommandLine::process(const std::wstring& line) return 1; } - // try as an moshorcut:// + // try as an moshortcut:// m_shortcut = qs; if (!m_shortcut.isValid()) { @@ -205,14 +210,34 @@ std::optional CommandLine::process(const std::wstring& line) } } -std::optional CommandLine::run(OrganizerCore& organizer) const +bool CommandLine::forwardToPrimary(MOMultiProcess& multiProcess) +{ + if (m_shortcut.isValid()) { + multiProcess.sendMessage(m_shortcut.toString()); + } else if (m_nxmLink) { + multiProcess.sendMessage(*m_nxmLink); + } else if (m_command && m_command->canForwardToPrimary()) { + multiProcess.sendMessage(QString::fromWCharArray(GetCommandLineW())); + } else { + return false; + } + + return true; +} + +std::optional CommandLine::runPostMultiProcess(MOMultiProcess& mp) +{ + return {}; +} + +std::optional CommandLine::runPostOrganizer(OrganizerCore& core) { if (m_shortcut.isValid()) { if (m_shortcut.hasExecutable()) { try { // make sure MO doesn't exit even if locking is disabled, ForceWait and // PreventExit will do that - organizer.processRunner() + core.processRunner() .setFromShortcut(m_shortcut) .setWaitForCompletion(ProcessRunner::ForceWait, UILocker::PreventExit) .run(); @@ -227,7 +252,7 @@ std::optional CommandLine::run(OrganizerCore& organizer) const } } else if (m_nxmLink) { log::debug("starting download from command line: {}", *m_nxmLink); - organizer.externalMessage(*m_nxmLink); + core.downloadRequestedNXM(*m_nxmLink); } else if (m_executable) { const QString exeName = *m_executable; log::debug("starting {} from command line", exeName); @@ -238,7 +263,7 @@ std::optional CommandLine::run(OrganizerCore& organizer) const // // make sure MO doesn't exit even if locking is disabled, ForceWait and // PreventExit will do that - organizer.processRunner() + core.processRunner() .setFromFileOrExecutable(exeName, m_untouched) .setWaitForCompletion(ProcessRunner::ForceWait, UILocker::PreventExit) .run(); @@ -252,7 +277,7 @@ std::optional CommandLine::run(OrganizerCore& organizer) const return 1; } } else if (m_command) { - return m_command->runPostOrganizer(organizer); + return m_command->runPostOrganizer(core); } return {}; @@ -479,7 +504,17 @@ void Command::process( m_untouched = untouched; } -std::optional Command::runPreOrganizer() +std::optional Command::runEarly() +{ + return {}; +} + +bool Command::canForwardToPrimary() const +{ + return false; +} + +std::optional Command::runPostMultiProcess(MOMultiProcess&) { return {}; } @@ -520,7 +555,7 @@ Command::Meta CrashDumpCommand::meta() const return {"crashdump", "writes a crashdump for a running process of MO"}; } -std::optional CrashDumpCommand::runPreOrganizer() +std::optional CrashDumpCommand::runEarly() { env::Console console; @@ -550,7 +585,7 @@ bool LaunchCommand::legacy() const return true; } -std::optional LaunchCommand::runPreOrganizer() +std::optional LaunchCommand::runEarly() { // needs at least the working directory and process name if (untouched().size() < 2) { @@ -657,7 +692,7 @@ Command::Meta RunCommand::meta() const return {"run", "runs a program, file or a configured executable"}; } -std::optional RunCommand::runPostOrganizer(OrganizerCore& organizer) +std::optional RunCommand::runPostOrganizer(OrganizerCore& core) { const auto program = QString::fromStdString(vm()["program"].as()); @@ -665,10 +700,10 @@ std::optional RunCommand::runPostOrganizer(OrganizerCore& organizer) { // make sure MO doesn't exit even if locking is disabled, ForceWait and // PreventExit will do that - auto p = organizer.processRunner(); + auto p = core.processRunner(); if (vm()["executable"].as()) { - const auto& exes = *organizer.executablesList(); + const auto& exes = *core.executablesList(); auto itor = exes.find(program); if (itor == exes.end()) { @@ -714,4 +749,55 @@ std::optional RunCommand::runPostOrganizer(OrganizerCore& organizer) } } + + +std::string ReloadPluginCommand::getUsageLine() const +{ + return "plugin-name"; +} + +po::options_description ReloadPluginCommand::getInternalOptions() const +{ + po::options_description d; + + d.add_options() + ("plugin-name", po::value()->required(), "plugin name"); + + return d; +} + +po::positional_options_description ReloadPluginCommand::getPositional() const +{ + po::positional_options_description d; + + d.add("plugin-name", 1); + + return d; +} + +Command::Meta ReloadPluginCommand::meta() const +{ + return {"reload-plugin", "reloads the given plugin"}; +} + +bool ReloadPluginCommand::canForwardToPrimary() const +{ + return true; +} + +std::optional ReloadPluginCommand::runPostOrganizer(OrganizerCore& core) +{ + const QString name = QString::fromStdString(vm()["plugin-name"].as()); + + QString filepath = QDir( + qApp->applicationDirPath() + "/" + + ToQString(AppConfig::pluginPath())) + .absoluteFilePath(name); + + log::debug("reloading plugin from {}", filepath); + core.pluginContainer().reloadPlugin(filepath); + + return {}; +} + } // namespace diff --git a/src/commandline.h b/src/commandline.h index d0d62a72..6f96d724 100644 --- a/src/commandline.h +++ b/src/commandline.h @@ -5,6 +5,7 @@ #include class OrganizerCore; +class MOMultiProcess; namespace cl { @@ -59,8 +60,10 @@ public: // // - virtual std::optional runPreOrganizer(); - virtual std::optional runPostOrganizer(OrganizerCore& organizer); + virtual std::optional runEarly(); + virtual bool canForwardToPrimary() const; + virtual std::optional runPostMultiProcess(MOMultiProcess& mp); + virtual std::optional runPostOrganizer(OrganizerCore& core); protected: // meta information about this command, returned by derived classes @@ -118,7 +121,7 @@ class CrashDumpCommand : public Command protected: po::options_description getVisibleOptions() const override; Meta meta() const override; - std::optional runPreOrganizer() override; + std::optional runEarly() override; }; @@ -141,7 +144,7 @@ public: protected: Meta meta() const override; - std::optional runPreOrganizer() override; + std::optional runEarly() override; int SpawnWaitProcess(LPCWSTR workingDirectory, LPCWSTR commandLine); @@ -160,7 +163,21 @@ protected: po::options_description getInternalOptions() const override; po::positional_options_description getPositional() const override; Meta meta() const override; - std::optional runPostOrganizer(OrganizerCore& organizer) override; + std::optional runPostOrganizer(OrganizerCore& core) override; +}; + + +// reloads the given plugin +// +class ReloadPluginCommand : public Command +{ +protected: + std::string getUsageLine() const override; + po::options_description getInternalOptions() const override; + po::positional_options_description getPositional() const override; + Meta meta() const override; + bool canForwardToPrimary() const override; + std::optional runPostOrganizer(OrganizerCore& core) override; }; @@ -215,7 +232,9 @@ public: // returns an empty optional if execution should continue, or a return code // if MO must quit // - std::optional run(OrganizerCore& organizer) const; + bool forwardToPrimary(MOMultiProcess& multiProcess); + std::optional runPostMultiProcess(MOMultiProcess& mp); + std::optional runPostOrganizer(OrganizerCore& core); // clears parsed options, used when MO is "restarted" so the options aren't @@ -269,6 +288,12 @@ private: void createOptions(); std::string more() const; + + template + void add() + { + (m_commands.push_back(std::make_unique()), ...); + } }; } // namespace diff --git a/src/main.cpp b/src/main.cpp index f85e13f3..adb33d91 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -37,12 +37,27 @@ int main(int argc, char *argv[]) MOMultiProcess multiProcess(cl.multiple()); if (multiProcess.ephemeral()) { - return forwardToPrimary(multiProcess, cl); + if (cl.forwardToPrimary(multiProcess)) { + return 0; + } else { + QMessageBox::information( + nullptr, QObject::tr("Mod Organizer"), + QObject::tr("An instance of Mod Organizer is already running")); + } + + return 0; + } + + if (auto r=cl.runPostMultiProcess(multiProcess)) { + return *r; } tt.stop(); + app.firstTimeSetup(multiProcess); + + // MO runs in a loop because it can be restarted in several ways, such as // when switching instances or changing some settings for (;;) @@ -73,7 +88,7 @@ int main(int argc, char *argv[]) } } - if (auto r=cl.run(app.core())) { + if (auto r=cl.runPostOrganizer(app.core())) { return *r; } @@ -99,21 +114,6 @@ int main(int argc, char *argv[]) } } -int forwardToPrimary(MOMultiProcess& multiProcess, const cl::CommandLine& cl) -{ - if (cl.shortcut().isValid()) { - multiProcess.sendMessage(cl.shortcut().toString()); - } else if (cl.nxmLink()) { - multiProcess.sendMessage(*cl.nxmLink()); - } else { - QMessageBox::information( - nullptr, QObject::tr("Mod Organizer"), - QObject::tr("An instance of Mod Organizer is already running")); - } - - return 0; -} - LONG WINAPI onUnhandledException(_EXCEPTION_POINTERS* ptrs) { const auto path = OrganizerCore::getGlobalCoreDumpPath(); diff --git a/src/messagedialog.cpp b/src/messagedialog.cpp index 78a5dd4d..1244e8c6 100644 --- a/src/messagedialog.cpp +++ b/src/messagedialog.cpp @@ -86,11 +86,23 @@ void MessageDialog::showMessage(const QString &text, QWidget *reference, bool br { log::debug("{}", text); - if (reference != nullptr) { - if (bringToFront || (qApp->activeWindow() != nullptr)) { - MessageDialog *dialog = new MessageDialog(text, reference); - dialog->show(); - reference->activateWindow(); + if (!reference) { + for (QWidget* w : qApp->topLevelWidgets()) { + if (dynamic_cast(w)) { + reference = w; + break; + } } } + + if (!reference) { + return; + } + + MessageDialog *dialog = new MessageDialog(text, reference); + dialog->show(); + + if (bringToFront) { + reference->activateWindow(); + } } diff --git a/src/moapplication.cpp b/src/moapplication.cpp index d1622d59..01d75ad1 100644 --- a/src/moapplication.cpp +++ b/src/moapplication.cpp @@ -31,6 +31,7 @@ along with Mod Organizer. If not, see . #include "tutorialmanager.h" #include "sanitychecks.h" #include "mainwindow.h" +#include "messagedialog.h" #include "shared/error_report.h" #include "shared/util.h" #include @@ -175,6 +176,14 @@ OrganizerCore& MOApplication::core() return *m_core; } +void MOApplication::firstTimeSetup(MOMultiProcess& multiProcess) +{ + connect( + &multiProcess, &MOMultiProcess::messageSent, this, + [this](auto&& s){ externalMessage(s); }, + Qt::QueuedConnection); +} + int MOApplication::setup(MOMultiProcess& multiProcess) { TimeThis tt("MOApplication setup()"); @@ -350,11 +359,10 @@ int MOApplication::run(MOMultiProcess& multiProcess) // main window m_nexus->getAccessManager()->setTopLevelWidget(&mainWindow); - QObject::connect(&mainWindow, SIGNAL(styleChanged(QString)), this, - SLOT(setStyleFile(QString))); - - QObject::connect(&multiProcess, SIGNAL(messageSent(QString)), m_core.get(), - SLOT(externalMessage(QString))); + connect( + &mainWindow, &MainWindow::styleChanged, this, + [this](auto&& file){ setStyleFile(file); }, + Qt::QueuedConnection); log::debug("displaying main window"); @@ -377,6 +385,37 @@ int MOApplication::run(MOMultiProcess& multiProcess) return res; } +void MOApplication::externalMessage(const QString& message) +{ + log::debug("received external message '{}'", message); + + MOShortcut moshortcut(message); + + if (moshortcut.isValid()) { + if(moshortcut.hasExecutable()) { + m_core->processRunner() + .setFromShortcut(moshortcut) + .setWaitForCompletion(ProcessRunner::Refresh) + .run(); + } + } else if (isNxmLink(message)) { + MessageDialog::showMessage(tr("Download started"), qApp->activeWindow()); + m_core->downloadRequestedNXM(message); + } else { + cl::CommandLine cl; + + if (auto r=cl.process(message.toStdWString())) { + log::debug( + "while processing external message, command line wants to " + "exit; ignoring"); + + return; + } + + cl.runPostOrganizer(*m_core); + } +} + std::unique_ptr MOApplication::getCurrentInstance() { auto& m = InstanceManager::singleton(); diff --git a/src/moapplication.h b/src/moapplication.h index 7e427ca8..2fe3409b 100644 --- a/src/moapplication.h +++ b/src/moapplication.h @@ -40,6 +40,7 @@ class MOApplication : public QApplication public: MOApplication(int& argc, char** argv); + void firstTimeSetup(MOMultiProcess& multiProcess); int setup(MOMultiProcess& multiProcess); int run(MOMultiProcess& multiProcess); void resetForRestart(); @@ -65,8 +66,7 @@ private: std::unique_ptr m_plugins; std::unique_ptr m_core; - int doOneRun(MOMultiProcess& multiProcess); - + void externalMessage(const QString& message); std::unique_ptr getCurrentInstance(); std::optional setupInstanceLoop(Instance& currentInstance, PluginContainer& pc); void purgeOldFiles(); diff --git a/src/organizercore.cpp b/src/organizercore.cpp index 5d079f89..fe17ee9a 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -347,25 +347,6 @@ void OrganizerCore::profileRemoved(QString const& profileName) m_ProfileRemoved(profileName); } - -void OrganizerCore::externalMessage(const QString &message) -{ - MOShortcut moshortcut(message); - - if (moshortcut.isValid()) { - if(moshortcut.hasExecutable()) { - processRunner() - .setFromShortcut(moshortcut) - .setWaitForCompletion(ProcessRunner::Refresh) - .run(); - } - } - else if (isNxmLink(message)) { - MessageDialog::showMessage(tr("Download started"), qApp->activeWindow()); - downloadRequestedNXM(message); - } -} - void OrganizerCore::downloadRequested(QNetworkReply *reply, QString gameName, int modID, const QString &fileName) { diff --git a/src/organizercore.h b/src/organizercore.h index 8add5542..39a2c3cb 100644 --- a/src/organizercore.h +++ b/src/organizercore.h @@ -351,7 +351,6 @@ public: // IPluginDiagnose interface public slots: void profileRefresh(); - void externalMessage(const QString &message); void syncOverwrite(); -- cgit v1.3.1 From 1c4d09d9e13571cf7cd7f1ed502bc069d27ed399 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Mon, 11 Jan 2021 02:20:33 -0500 Subject: comments --- src/main.cpp | 31 +++++++++++++++++++++++-------- src/moapplication.h | 20 +++++++++++++++++++- 2 files changed, 42 insertions(+), 9 deletions(-) (limited to 'src/main.cpp') diff --git a/src/main.cpp b/src/main.cpp index adb33d91..ba808db3 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -15,8 +15,6 @@ using namespace MOBase; thread_local LPTOP_LEVEL_EXCEPTION_FILTER g_prevExceptionFilter = nullptr; thread_local std::terminate_handler g_prevTerminateHandler = nullptr; -int forwardToPrimary(MOMultiProcess& multiProcess, const cl::CommandLine& cl); - int main(int argc, char *argv[]) { MOShared::SetThisThreadName("main"); @@ -30,24 +28,33 @@ int main(int argc, char *argv[]) initLogging(); // must be after logging - TimeThis tt("main()"); + TimeThis tt("main() multiprocess"); QApplication::setAttribute(Qt::AA_EnableHighDpiScaling); MOApplication app(argc, argv); + + // check if there's another process running MOMultiProcess multiProcess(cl.multiple()); + if (multiProcess.ephemeral()) { + // this is not the primary process + if (cl.forwardToPrimary(multiProcess)) { + // but there's something on the command line that could be forwarded to + // it, so just exit return 0; - } else { - QMessageBox::information( - nullptr, QObject::tr("Mod Organizer"), - QObject::tr("An instance of Mod Organizer is already running")); } - return 0; + QMessageBox::information( + nullptr, QObject::tr("Mod Organizer"), + QObject::tr("An instance of Mod Organizer is already running")); + + return 1; } + + // check if the command line wants to run something right now if (auto r=cl.runPostMultiProcess(multiProcess)) { return *r; } @@ -55,6 +62,7 @@ int main(int argc, char *argv[]) tt.stop(); + // stuff that's done only once, even if MO restarts in the loop below app.firstTimeSetup(multiProcess); @@ -74,6 +82,8 @@ int main(int argc, char *argv[]) m.overrideProfile(*cl.profile()); } + + // set up plugins, OrganizerCore, etc. { const auto r = app.setup(multiProcess); @@ -88,12 +98,17 @@ int main(int argc, char *argv[]) } } + + // check if the command line wants to run something right now if (auto r=cl.runPostOrganizer(app.core())) { return *r; } + + // run the main window const auto r = app.run(multiProcess); + if (r == RestartExitCode) { // resets things when MO is "restarted" app.resetForRestart(); diff --git a/src/moapplication.h b/src/moapplication.h index 2fe3409b..65180ece 100644 --- a/src/moapplication.h +++ b/src/moapplication.h @@ -40,14 +40,32 @@ class MOApplication : public QApplication public: MOApplication(int& argc, char** argv); + // called from main() only once for stuff that persists across "restarts" + // void firstTimeSetup(MOMultiProcess& multiProcess); + + // called from main() each time MO "restarts", loads settings, plugins, + // OrganizerCore and the current instance + // int setup(MOMultiProcess& multiProcess); + + // shows splash, starts an api check, shows the main window and blocks until + // MO exits + // int run(MOMultiProcess& multiProcess); + + // called from main() when MO "restarts", must clean up everything so setup() + // starts fresh + // void resetForRestart(); + // undefined if setup() wasn't called + // OrganizerCore& core(); - virtual bool notify(QObject* receiver, QEvent* event); + // wraps QApplication::notify() in a catch, reports errors and ignores them + // + bool notify(QObject* receiver, QEvent* event) override; public slots: bool setStyleFile(const QString& style); -- cgit v1.3.1 From 181acfe832bef26228e33ac3a021d39528b1a4a9 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sun, 17 Jan 2021 18:29:17 -0500 Subject: renamed Refresh to TriggerRefresh, added WaitForRefresh removed duplicate refreshDirectoryStructure() call that could never work added --logs to output logs to stdout, added final "mod organizer done" log added -i with no arguments to output the current instance name `run -e` now does an additional, case insensitive check for names fixed error being output along with --help --- src/commandline.cpp | 100 ++++++++++++++++++++++++++++++++++++++++-------- src/commandline.h | 13 +++++++ src/executableslist.cpp | 16 ++++++-- src/executableslist.h | 4 +- src/filetree.cpp | 4 +- src/loglist.cpp | 21 ++++++++++ src/loglist.h | 1 + src/main.cpp | 18 +++++++++ src/mainwindow.cpp | 4 +- src/moapplication.cpp | 2 +- src/organizercore.cpp | 4 +- src/organizerproxy.cpp | 2 +- src/processrunner.cpp | 38 +++++++++++++++--- src/processrunner.h | 22 ++++++++++- 14 files changed, 209 insertions(+), 40 deletions(-) (limited to 'src/main.cpp') diff --git a/src/commandline.cpp b/src/commandline.cpp index 1b7c7b0e..97d54b92 100644 --- a/src/commandline.cpp +++ b/src/commandline.cpp @@ -3,6 +3,7 @@ #include "organizercore.h" #include "instancemanager.h" #include "multiprocess.h" +#include "loglist.h" #include "shared/util.h" #include "shared/error_report.h" #include "shared/appconfig.h" @@ -124,19 +125,22 @@ std::optional CommandLine::process(const std::wstring& line) parsed = parser.run(); po::store(parsed, m_vm); - po::notify(m_vm); if (m_vm.count("help")) { env::Console console; std::cout << usage(c.get()) << "\n"; return 0; } + + // must be below the help check because it throws if required + // positional arguments are missing + po::notify(m_vm); } c->set(line, m_vm, opts); m_command = c.get(); - return m_command->runEarly(); + return runEarly(); } catch(po::error& e) { @@ -164,6 +168,7 @@ std::optional CommandLine::process(const std::wstring& line) return 0; } + if (!opts.empty()) { const auto qs = QString::fromStdWString(opts[0]); @@ -226,6 +231,42 @@ bool CommandLine::forwardToPrimary(MOMultiProcess& multiProcess) return true; } +std::optional CommandLine::runEarly() +{ + if (m_vm.count("logs")) { + // in loglist.h + logToStdout(true); + } + + if (m_command) { + return m_command->runEarly(); + } + + return {}; +} + +std::optional CommandLine::runPostApplication(MOApplication& a) +{ + // handle -i with no arguments + if (m_vm.count("instance") && m_vm["instance"].as() == "") { + env::Console c; + + if (auto i=InstanceManager::singleton().currentInstance()) { + std::cout << i->name().toStdString() << "\n"; + } else { + std::cout << "no instance configured\n"; + } + + return 0; + } + + if (m_command) { + return m_command->runPostApplication(a); + } + + return {}; +} + std::optional CommandLine::runPostMultiProcess(MOMultiProcess& mp) { if (m_command) { @@ -244,7 +285,7 @@ std::optional CommandLine::runPostOrganizer(OrganizerCore& core) // PreventExit will do that core.processRunner() .setFromShortcut(m_shortcut) - .setWaitForCompletion(ProcessRunner::ForceWait, UILocker::PreventExit) + .setWaitForCompletion(ProcessRunner::ForCommandLine, UILocker::PreventExit) .run(); return 0; @@ -270,7 +311,7 @@ std::optional CommandLine::runPostOrganizer(OrganizerCore& core) // PreventExit will do that core.processRunner() .setFromFileOrExecutable(exeName, m_untouched) - .setWaitForCompletion(ProcessRunner::ForceWait, UILocker::PreventExit) + .setWaitForCompletion(ProcessRunner::ForCommandLine, UILocker::PreventExit) .run(); return 0; @@ -298,10 +339,23 @@ void CommandLine::clear() void CommandLine::createOptions() { m_visibleOptions.add_options() - ("help", "show this message") - ("multiple", "allow multiple MO processes to run; see below") - ("instance,i", po::value(), "use the given instance (defaults to last used)") - ("profile,p", po::value(), "use the given profile (defaults to last used)"); + ("help", + "show this message") + + ("multiple", + "allow multiple MO processes to run; see below") + + ("logs", + "duplicates the logs to stdout") + + ("instance,i", + po::value()->implicit_value(""), + "use the given instance (defaults to last used)") + + ("profile,p", + po::value(), + "use the given profile (defaults to last used)"); + po::options_description options; options.add_options() @@ -530,9 +584,9 @@ std::optional Command::runEarly() return {}; } -bool Command::canForwardToPrimary() const +std::optional Command::runPostApplication(MOApplication& a) { - return false; + return {}; } std::optional Command::runPostMultiProcess(MOMultiProcess&) @@ -545,6 +599,11 @@ std::optional Command::runPostOrganizer(OrganizerCore&) return {}; } +bool Command::canForwardToPrimary() const +{ + return false; +} + const std::wstring& Command::originalCmd() const { return m_original; @@ -740,14 +799,21 @@ std::optional RunCommand::runPostOrganizer(OrganizerCore& core) if (vm()["executable"].as()) { const auto& exes = *core.executablesList(); - auto itor = exes.find(program); + // case sensitive + auto itor = exes.find(program, true); if (itor == exes.end()) { - MOShared::criticalOnTop( - QObject::tr("Executable '%1' not found in instance '%2'.") - .arg(program) - .arg(InstanceManager::singleton().currentInstance()->name())); + // case insensitive + itor = exes.find(program, false); - return 1; + if (itor == exes.end()) { + // not found + MOShared::criticalOnTop( + QObject::tr("Executable '%1' not found in instance '%2'.") + .arg(program) + .arg(InstanceManager::singleton().currentInstance()->name())); + + return 1; + } } p.setFromExecutable(*itor); @@ -763,7 +829,7 @@ std::optional RunCommand::runPostOrganizer(OrganizerCore& core) p.setCurrentDirectory(QString::fromStdString(vm()["cwd"].as())); } - p.setWaitForCompletion(ProcessRunner::ForceWait, UILocker::PreventExit); + p.setWaitForCompletion(ProcessRunner::ForCommandLine, UILocker::PreventExit); const auto r = p.run(); if (r == ProcessRunner::Error) { diff --git a/src/commandline.h b/src/commandline.h index 5dcb8871..6bd57132 100644 --- a/src/commandline.h +++ b/src/commandline.h @@ -5,6 +5,7 @@ #include class OrganizerCore; +class MOApplication; class MOMultiProcess; namespace cl @@ -68,6 +69,11 @@ public: // virtual std::optional runEarly(); + // called as soon as the MOApplication has been created, which is also the + // first time where Qt stuff is available + // + virtual std::optional runPostApplication(MOApplication& a); + // called as soon as the multi process checks have confirmed that this is // a primary instance; return something to exit immediately // @@ -270,6 +276,11 @@ public: // std::optional process(const std::wstring& line); + // called as soon as the MOApplication has been created; this handles a few + // global actions and forwards to the command, if any + // + std::optional runPostApplication(MOApplication& a); + // calls Command::runPostMultiProcess() on the command, if any // std::optional runPostMultiProcess(MOMultiProcess& mp); @@ -356,6 +367,8 @@ private: { (m_commands.push_back(std::make_unique()), ...); } + + std::optional runEarly(); }; } // namespace diff --git a/src/executableslist.cpp b/src/executableslist.cpp index 75f29e8f..78aa981d 100644 --- a/src/executableslist.cpp +++ b/src/executableslist.cpp @@ -211,14 +211,22 @@ Executable &ExecutablesList::getByBinary(const QFileInfo &info) throw std::runtime_error("invalid info"); } -ExecutablesList::iterator ExecutablesList::find(const QString &title) +ExecutablesList::iterator ExecutablesList::find(const QString &title, bool ci) { - return std::find_if(begin(), end(), [&](auto&& e) { return e.title() == title; }); + const auto cif = ci ? Qt::CaseInsensitive : Qt::CaseSensitive; + + return std::find_if(begin(), end(), [&](auto&& e) { + return (e.title().compare(title, cif) == 0); + }); } -ExecutablesList::const_iterator ExecutablesList::find(const QString &title) const +ExecutablesList::const_iterator ExecutablesList::find(const QString &title, bool ci) const { - return std::find_if(begin(), end(), [&](auto&& e) { return e.title() == title; }); + const auto cif = ci ? Qt::CaseInsensitive : Qt::CaseSensitive; + + return std::find_if(begin(), end(), [&](auto&& e) { + return (e.title().compare(title, cif) == 0); + }); } bool ExecutablesList::titleExists(const QString &title) const diff --git a/src/executableslist.h b/src/executableslist.h index a18042db..97d39500 100644 --- a/src/executableslist.h +++ b/src/executableslist.h @@ -148,8 +148,8 @@ public: /** * @brief returns an iterator for the given executable by title, or end() */ - iterator find(const QString &title); - const_iterator find(const QString &title) const; + iterator find(const QString &title, bool caseSensitive=true); + const_iterator find(const QString &title, bool caseSensitive=true) const; /** * @brief determine if an executable exists diff --git a/src/filetree.cpp b/src/filetree.cpp index f46216b5..7bc4d09c 100644 --- a/src/filetree.cpp +++ b/src/filetree.cpp @@ -202,7 +202,7 @@ void FileTree::open(FileTreeItem* item) m_core.processRunner() .setFromFile(m_tree->window(), targetInfo) .setHooked(false) - .setWaitForCompletion(ProcessRunner::Refresh) + .setWaitForCompletion(ProcessRunner::TriggerRefresh) .run(); } @@ -226,7 +226,7 @@ void FileTree::openHooked(FileTreeItem* item) m_core.processRunner() .setFromFile(m_tree->window(), targetInfo) .setHooked(true) - .setWaitForCompletion(ProcessRunner::Refresh) + .setWaitForCompletion(ProcessRunner::TriggerRefresh) .run(); } diff --git a/src/loglist.cpp b/src/loglist.cpp index 7f8f05bd..bea7363f 100644 --- a/src/loglist.cpp +++ b/src/loglist.cpp @@ -20,12 +20,18 @@ along with Mod Organizer. If not, see . #include "loglist.h" #include "organizercore.h" #include "copyeventfilter.h" +#include "env.h" using namespace MOBase; static LogModel* g_instance = nullptr; const std::size_t MaxLines = 1000; +static std::unique_ptr m_console; +static bool m_stdout = false; +static std::mutex m_stdoutMutex; + + LogModel::LogModel() { } @@ -324,6 +330,21 @@ void qtLogCallback( } } +void logToStdout(bool b) +{ + m_stdout = b; + + // logging to stdout is already set up in uibase by log::createDefault(), + // all it needs is to redirect stdout to the console, which is done by + // creating an env::Console object + + if (m_stdout) { + m_console.reset(new env::Console); + } else { + m_console.reset(); + } +} + void initLogging() { LogModel::create(); diff --git a/src/loglist.h b/src/loglist.h index d502f193..df7c1467 100644 --- a/src/loglist.h +++ b/src/loglist.h @@ -87,6 +87,7 @@ private: }; +void logToStdout(bool b); void initLogging(); bool setLogDirectory(const QString& dir); diff --git a/src/main.cpp b/src/main.cpp index ba808db3..0cd9ba26 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -15,7 +15,16 @@ using namespace MOBase; thread_local LPTOP_LEVEL_EXCEPTION_FILTER g_prevExceptionFilter = nullptr; thread_local std::terminate_handler g_prevTerminateHandler = nullptr; +int run(int argc, char *argv[]); + int main(int argc, char *argv[]) +{ + const int r = run(argc, argv); + std::cout << "mod organizer done\n"; + return r; +} + +int run(int argc, char *argv[]) { MOShared::SetThisThreadName("main"); setExceptionHandlers(); @@ -34,6 +43,12 @@ int main(int argc, char *argv[]) MOApplication app(argc, argv); + // check if the command line wants to run something right now + if (auto r=cl.runPostApplication(app)) { + return *r; + } + + // check if there's another process running MOMultiProcess multiProcess(cl.multiple()); @@ -95,6 +110,9 @@ int main(int argc, char *argv[]) cl.clear(); continue; + } else if (r != 0) { + // something failed, quit + return r; } } diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index a2b12686..a79aca2f 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1486,7 +1486,7 @@ void MainWindow::startExeAction() m_OrganizerCore.processRunner() .setFromExecutable(*itor) - .setWaitForCompletion(ProcessRunner::Refresh) + .setWaitForCompletion(ProcessRunner::TriggerRefresh) .run(); } @@ -2033,7 +2033,7 @@ void MainWindow::on_startButton_clicked() m_OrganizerCore.processRunner() .setFromExecutable(*selectedExecutable) - .setWaitForCompletion(ProcessRunner::Refresh) + .setWaitForCompletion(ProcessRunner::TriggerRefresh) .run(); } diff --git a/src/moapplication.cpp b/src/moapplication.cpp index 01d75ad1..da45e26c 100644 --- a/src/moapplication.cpp +++ b/src/moapplication.cpp @@ -395,7 +395,7 @@ void MOApplication::externalMessage(const QString& message) if(moshortcut.hasExecutable()) { m_core->processRunner() .setFromShortcut(moshortcut) - .setWaitForCompletion(ProcessRunner::Refresh) + .setWaitForCompletion(ProcessRunner::TriggerRefresh) .run(); } } else if (isNxmLink(message)) { diff --git a/src/organizercore.cpp b/src/organizercore.cpp index fe17ee9a..c7c1f828 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -1897,8 +1897,6 @@ bool OrganizerCore::beforeRun( void OrganizerCore::afterRun(const QFileInfo& binary, DWORD exitCode) { - refreshDirectoryStructure(); - // need to remove our stored load order because it may be outdated if a // foreign tool changed the file time. After removing that file, // refreshESPList will use the file time as the order @@ -1913,7 +1911,7 @@ void OrganizerCore::afterRun(const QFileInfo& binary, DWORD exitCode) savePluginList(); cycleDiagnostics(); - //These callbacks should not fiddle with directoy structure and ESPs. + //These callbacks should not fiddle with directory structure and ESPs. m_FinishedRun(binary.absoluteFilePath(), exitCode); } diff --git a/src/organizerproxy.cpp b/src/organizerproxy.cpp index acfb8404..fe3d502c 100644 --- a/src/organizerproxy.cpp +++ b/src/organizerproxy.cpp @@ -193,7 +193,7 @@ bool OrganizerProxy::waitForApplication(HANDLE handle, bool refresh, LPDWORD exi ProcessRunner::WaitFlags waitFlags = ProcessRunner::ForceWait; if (refresh) { - waitFlags |= ProcessRunner::Refresh; + waitFlags |= ProcessRunner::TriggerRefresh; } const auto r = runner diff --git a/src/processrunner.cpp b/src/processrunner.cpp index cdcbfa16..c932ce44 100644 --- a/src/processrunner.cpp +++ b/src/processrunner.cpp @@ -521,6 +521,13 @@ ProcessRunner& ProcessRunner::setWaitForCompletion( { m_waitFlags = flags; m_lockReason = reason; + + if (m_waitFlags.testFlag(WaitForRefresh) && !m_waitFlags.testFlag(TriggerRefresh)) { + log::warn( + "process runner: WaitForRefresh without TriggerRefresh " + "makes no sense, will be ignored"); + } + return *this; } @@ -825,8 +832,8 @@ bool ProcessRunner::shouldRefresh(Results r) const // 2) the mod info dialog is not set up to deal with refreshes, so that // it will crash because the old DirectoryEntry's are still being used // in the list - if (!m_waitFlags.testFlag(Refresh)) { - log::debug("not refreshing because the flag isn't set"); + if (!m_waitFlags.testFlag(TriggerRefresh)) { + log::debug("process runner: not refreshing because the flag isn't set"); return false; } @@ -834,13 +841,13 @@ bool ProcessRunner::shouldRefresh(Results r) const { case Completed: { - log::debug("refreshing because the process completed"); + log::debug("process runner: refreshing because the process completed"); return true; } case ForceUnlocked: { - log::debug("refreshing because the ui was force unlocked"); + log::debug("process runner: refreshing because the ui was force unlocked"); return true; } @@ -891,7 +898,10 @@ ProcessRunner::Results ProcessRunner::postRun() if (!lockEnabled) { // disabling locking is like clicking on unlock immediately - log::debug("not waiting for process because locking is disabled"); + log::debug( + "process runner: not waiting for process because " + "locking is disabled"); + return ForceUnlocked; } } @@ -917,8 +927,24 @@ ProcessRunner::Results ProcessRunner::postRun() } if (shouldRefresh(r)) { + QEventLoop loop; + const bool wait = m_waitFlags.testFlag(WaitForRefresh); + + if (wait) { + QObject::connect( + &m_core, &OrganizerCore::directoryStructureReady, + &loop, &QEventLoop::quit, + Qt::ConnectionType::QueuedConnection); + } + m_core.afterRun(m_sp.binary, m_exitCode); - } + + if (wait) { + log::debug("process runner: waiting until refresh finishes"); + loop.exec(); + log::debug("process runner: refresh is done"); + } +} return r; } diff --git a/src/processrunner.h b/src/processrunner.h index d8ff0227..b8c8935c 100644 --- a/src/processrunner.h +++ b/src/processrunner.h @@ -39,12 +39,30 @@ public: { NoFlags = 0x00, - // the ui will be refreshed once the process has completed - Refresh = 0x01, + // the directory structure will be refreshed once the process has completed + TriggerRefresh = 0x01, // the process will be waited for even if locking is disabled or the // process is not hooked ForceWait = 0x02, + + // only valid with TriggerRefresh; run() will block until the refresh has + // completed + WaitForRefresh = 0x04, + + // combination of flags used to run programs from the command line + // + // 1) TriggerRefresh: MO must refresh after running the program because + // programs can modify files behind its back; for example, external + // LOOT will modify loadorder.txt, so MO must read it back or it will + // write back the old order when exiting + // + // 2) WaitForRefresh: refreshing is asynchronous, so the refresh must + // complete before MO exits or stale data might be written to disk + // + // 3) ForceWait: MO must wait for the program to finish even if locking the + // ui is disabled + ForCommandLine = TriggerRefresh | WaitForRefresh | ForceWait }; using WaitFlags = QFlags; -- cgit v1.3.1