summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorisanae <14251494+isanae@users.noreply.github.com>2021-01-17 18:29:17 -0500
committerisanae <14251494+isanae@users.noreply.github.com>2021-01-18 08:21:41 -0500
commit181acfe832bef26228e33ac3a021d39528b1a4a9 (patch)
tree8d72ee70536e85361c6ebaf95cb97eef87bef0ea
parent1c4d09d9e13571cf7cd7f1ed502bc069d27ed399 (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
-rw-r--r--src/commandline.cpp100
-rw-r--r--src/commandline.h13
-rw-r--r--src/executableslist.cpp16
-rw-r--r--src/executableslist.h4
-rw-r--r--src/filetree.cpp4
-rw-r--r--src/loglist.cpp21
-rw-r--r--src/loglist.h1
-rw-r--r--src/main.cpp18
-rw-r--r--src/mainwindow.cpp4
-rw-r--r--src/moapplication.cpp2
-rw-r--r--src/organizercore.cpp4
-rw-r--r--src/organizerproxy.cpp2
-rw-r--r--src/processrunner.cpp38
-rw-r--r--src/processrunner.h22
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>;