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
This commit is contained in:
isanae
2021-01-18 08:21:41 -05:00
parent 1c4d09d9e1
commit 181acfe832
14 changed files with 209 additions and 40 deletions
+83 -17
View File
@@ -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) {
+13
View File
@@ -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
+12 -4
View File
@@ -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
+2 -2
View File
@@ -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
+2 -2
View File
@@ -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();
}
+21
View File
@@ -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();
+1
View File
@@ -87,6 +87,7 @@ private:
};
void logToStdout(bool b);
void initLogging();
bool setLogDirectory(const QString& dir);
+18
View File
@@ -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;
}
}
+2 -2
View File
@@ -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();
}
+1 -1
View File
@@ -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)) {
+1 -3
View File
@@ -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);
}
+1 -1
View File
@@ -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
+32 -6
View File
@@ -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;
}
+20 -2
View File
@@ -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>;