diff options
| author | isanae <14251494+isanae@users.noreply.github.com> | 2021-01-17 18:29:17 -0500 |
|---|---|---|
| committer | isanae <14251494+isanae@users.noreply.github.com> | 2021-01-18 08:21:41 -0500 |
| commit | 181acfe832bef26228e33ac3a021d39528b1a4a9 (patch) | |
| tree | 8d72ee70536e85361c6ebaf95cb97eef87bef0ea /src | |
| parent | 1c4d09d9e13571cf7cd7f1ed502bc069d27ed399 (diff) | |
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
Diffstat (limited to 'src')
| -rw-r--r-- | src/commandline.cpp | 100 | ||||
| -rw-r--r-- | src/commandline.h | 13 | ||||
| -rw-r--r-- | src/executableslist.cpp | 16 | ||||
| -rw-r--r-- | src/executableslist.h | 4 | ||||
| -rw-r--r-- | src/filetree.cpp | 4 | ||||
| -rw-r--r-- | src/loglist.cpp | 21 | ||||
| -rw-r--r-- | src/loglist.h | 1 | ||||
| -rw-r--r-- | src/main.cpp | 18 | ||||
| -rw-r--r-- | src/mainwindow.cpp | 4 | ||||
| -rw-r--r-- | src/moapplication.cpp | 2 | ||||
| -rw-r--r-- | src/organizercore.cpp | 4 | ||||
| -rw-r--r-- | src/organizerproxy.cpp | 2 | ||||
| -rw-r--r-- | src/processrunner.cpp | 38 | ||||
| -rw-r--r-- | src/processrunner.h | 22 |
14 files changed, 209 insertions, 40 deletions
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<int> 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<int> 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<int> CommandLine::runEarly() +{ + if (m_vm.count("logs")) { + // in loglist.h + logToStdout(true); + } + + if (m_command) { + return m_command->runEarly(); + } + + return {}; +} + +std::optional<int> CommandLine::runPostApplication(MOApplication& a) +{ + // handle -i with no arguments + if (m_vm.count("instance") && m_vm["instance"].as<std::string>() == "") { + 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<int> CommandLine::runPostMultiProcess(MOMultiProcess& mp) { if (m_command) { @@ -244,7 +285,7 @@ std::optional<int> 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<int> 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<std::string>(), "use the given instance (defaults to last used)") - ("profile,p", po::value<std::string>(), "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<std::string>()->implicit_value(""), + "use the given instance (defaults to last used)") + + ("profile,p", + po::value<std::string>(), + "use the given profile (defaults to last used)"); + po::options_description options; options.add_options() @@ -530,9 +584,9 @@ std::optional<int> Command::runEarly() return {}; } -bool Command::canForwardToPrimary() const +std::optional<int> Command::runPostApplication(MOApplication& a) { - return false; + return {}; } std::optional<int> Command::runPostMultiProcess(MOMultiProcess&) @@ -545,6 +599,11 @@ std::optional<int> 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<int> RunCommand::runPostOrganizer(OrganizerCore& core) if (vm()["executable"].as<bool>()) { 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<int> RunCommand::runPostOrganizer(OrganizerCore& core) p.setCurrentDirectory(QString::fromStdString(vm()["cwd"].as<std::string>())); } - 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 <memory> class OrganizerCore; +class MOApplication; class MOMultiProcess; namespace cl @@ -68,6 +69,11 @@ public: // virtual std::optional<int> runEarly(); + // called as soon as the MOApplication has been created, which is also the + // first time where Qt stuff is available + // + virtual std::optional<int> 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<int> 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<int> runPostApplication(MOApplication& a); + // calls Command::runPostMultiProcess() on the command, if any // std::optional<int> runPostMultiProcess(MOMultiProcess& mp); @@ -356,6 +367,8 @@ private: { (m_commands.push_back(std::make_unique<Ts>()), ...); } + + std::optional<int> 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 <http://www.gnu.org/licenses/>. #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<env::Console> 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,8 +15,17 @@ 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<WaitFlag>; |
