mirror of
https://github.com/ModOrganizer2/modorganizer.git
synced 2026-07-27 13:58:24 -07:00
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:
+83
-17
@@ -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) {
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -87,6 +87,7 @@ private:
|
||||
};
|
||||
|
||||
|
||||
void logToStdout(bool b);
|
||||
void initLogging();
|
||||
bool setLogDirectory(const QString& dir);
|
||||
|
||||
|
||||
@@ -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
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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)) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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
@@ -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
@@ -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>;
|
||||
|
||||
Reference in New Issue
Block a user