From 838446e262ac628c748b2e2b2f2ec0cfaff01508 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 16 Nov 2019 19:41:17 -0500 Subject: split loot stuff to loot.h/cpp, no changes in functionality --- src/mainwindow.cpp | 227 +---------------------------------------------------- 1 file changed, 4 insertions(+), 223 deletions(-) (limited to 'src/mainwindow.cpp') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 9453f07c..11edfc81 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -39,7 +39,7 @@ along with Mod Organizer. If not, see . #include "spawn.h" #include "versioninfo.h" #include "instancemanager.h" - +#include "loot.h" #include "report.h" #include "modlist.h" #include "modlistsortproxy.h" @@ -6369,233 +6369,14 @@ void MainWindow::on_showHiddenBox_toggled(bool checked) m_OrganizerCore.downloadManager()->setShowHidden(checked); } - -void MainWindow::createStdoutPipe(HANDLE *stdOutRead, HANDLE *stdOutWrite) -{ - SECURITY_ATTRIBUTES secAttributes; - secAttributes.nLength = sizeof(SECURITY_ATTRIBUTES); - secAttributes.bInheritHandle = TRUE; - secAttributes.lpSecurityDescriptor = nullptr; - - if (!::CreatePipe(stdOutRead, stdOutWrite, &secAttributes, 0)) { - log::error("failed to create stdout reroute"); - } - - if (!::SetHandleInformation(*stdOutRead, HANDLE_FLAG_INHERIT, 0)) { - log::error("failed to correctly set up the stdout reroute"); - *stdOutWrite = *stdOutRead = INVALID_HANDLE_VALUE; - } -} - -std::string MainWindow::readFromPipe(HANDLE stdOutRead) -{ - static const int chunkSize = 128; - std::string result; - - char buffer[chunkSize + 1]; - buffer[chunkSize] = '\0'; - - DWORD read = 1; - while (read > 0) { - if (!::ReadFile(stdOutRead, buffer, chunkSize, &read, nullptr)) { - break; - } - if (read > 0) { - result.append(buffer, read); - if (read < chunkSize) { - break; - } - } - } - return result; -} - -void MainWindow::processLOOTOut(const std::string &lootOut, std::string &errorMessages, QProgressDialog &dialog) -{ - std::vector lines; - boost::split(lines, lootOut, boost::is_any_of("\r\n")); - - std::regex exRequires("\"([^\"]*)\" requires \"([^\"]*)\", but it is missing\\."); - std::regex exIncompatible("\"([^\"]*)\" is incompatible with \"([^\"]*)\", but both are present\\."); - - for (const std::string &line : lines) { - if (line.length() > 0) { - size_t progidx = line.find("[progress]"); - size_t erroridx = line.find("[error]"); - if (progidx != std::string::npos) { - dialog.setLabelText(line.substr(progidx + 11).c_str()); - } else if (erroridx != std::string::npos) { - log::warn("{}", line); - errorMessages.append(boost::algorithm::trim_copy(line.substr(erroridx + 8)) + "\n"); - } else { - std::smatch match; - if (std::regex_match(line, match, exRequires)) { - std::string modName(match[1].first, match[1].second); - std::string dependency(match[2].first, match[2].second); - m_OrganizerCore.pluginList()->addInformation(modName.c_str(), tr("depends on missing \"%1\"").arg(dependency.c_str())); - } else if (std::regex_match(line, match, exIncompatible)) { - std::string modName(match[1].first, match[1].second); - std::string dependency(match[2].first, match[2].second); - m_OrganizerCore.pluginList()->addInformation(modName.c_str(), tr("incompatible with \"%1\"").arg(dependency.c_str())); - } else { - log::debug("[loot] {}", line); - } - } - } - } -} - void MainWindow::on_bossButton_clicked() { - std::string errorMessages; - - //m_OrganizerCore.currentProfile()->writeModlistNow(); m_OrganizerCore.savePluginList(); - //Create a backup of the load orders w/ LOOT in name - //to make sure that any sorting is easily undo-able. - //Need to figure out how I want to do that. - bool success = false; - - try { - setEnabled(false); - ON_BLOCK_EXIT([&] () { setEnabled(true); }); - QProgressDialog dialog(this); - dialog.setLabelText(tr("Please wait while LOOT is running")); - dialog.setMaximum(0); - dialog.show(); - - QString outPath = QDir::temp().absoluteFilePath("lootreport.json"); - - QStringList parameters; - parameters << "--game" << m_OrganizerCore.managedGame()->gameShortName() - << "--gamePath" << QString("\"%1\"").arg(m_OrganizerCore.managedGame()->gameDirectory().absolutePath()) - << "--pluginListPath" << QString("\"%1/loadorder.txt\"").arg(m_OrganizerCore.profilePath()) - << "--out" << QString("\"%1\"").arg(outPath); - - if (m_DidUpdateMasterList) { - parameters << "--skipUpdateMasterlist"; - } - HANDLE stdOutWrite = INVALID_HANDLE_VALUE; - HANDLE stdOutRead = INVALID_HANDLE_VALUE; - createStdoutPipe(&stdOutRead, &stdOutWrite); - try { - m_OrganizerCore.prepareVFS(); - } catch (const UsvfsConnectorException &e) { - log::debug("{}", e.what()); - return; - } catch (const std::exception &e) { - QMessageBox::warning(qApp->activeWindow(), tr("Error"), e.what()); - return; - } - - spawn::SpawnParameters sp; - sp.binary = QFileInfo(qApp->applicationDirPath() + "/loot/lootcli.exe"); - sp.arguments = parameters.join(" "); - sp.currentDirectory.setPath(qApp->applicationDirPath() + "/loot"); - sp.hooked = true; - sp.stdOut = stdOutWrite; - - HANDLE loot = spawn::startBinary(this, sp); - - // we don't use the write end - ::CloseHandle(stdOutWrite); - - m_OrganizerCore.pluginList()->clearAdditionalInformation(); - - DWORD retLen; - JOBOBJECT_BASIC_PROCESS_ID_LIST info; - HANDLE processHandle = loot; - - if (loot != INVALID_HANDLE_VALUE) { - bool isJobHandle = true; - ULONG lastProcessID; - DWORD res = ::MsgWaitForMultipleObjects(1, &loot, false, 100, QS_KEY | QS_MOUSE); - while ((res != WAIT_FAILED) && (res != WAIT_OBJECT_0)) { - if (isJobHandle) { - if (::QueryInformationJobObject(loot, JobObjectBasicProcessIdList, &info, sizeof(info), &retLen) > 0) { - if (info.NumberOfProcessIdsInList == 0) { - log::debug("no more processes in job"); - break; - } else { - if (lastProcessID != info.ProcessIdList[0]) { - lastProcessID = info.ProcessIdList[0]; - if (processHandle != loot) { - ::CloseHandle(processHandle); - } - processHandle = ::OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, lastProcessID); - } - } - } else { - // the info-object I passed only provides space for 1 process id. but since this code only cares about whether there - // is more than one that's good enough. ERROR_MORE_DATA simply signals there are at least two processes running. - // any other error probably means the handle is a regular process handle, probably caused by running MO in a job without - // the right to break out. - if (::GetLastError() != ERROR_MORE_DATA) { - isJobHandle = false; - } - } - } - - if (dialog.wasCanceled()) { - if (isJobHandle) { - ::TerminateJobObject(loot, 1); - } else { - ::TerminateProcess(loot, 1); - } - } - - // keep processing events so the app doesn't appear dead - QCoreApplication::processEvents(); - std::string lootOut = readFromPipe(stdOutRead); - processLOOTOut(lootOut, errorMessages, dialog); - - res = ::MsgWaitForMultipleObjects(1, &loot, false, 100, QS_KEY | QS_MOUSE); - } - - std::string remainder = readFromPipe(stdOutRead).c_str(); - if (remainder.length() > 0) { - processLOOTOut(remainder, errorMessages, dialog); - } - DWORD exitCode = 0UL; - ::GetExitCodeProcess(processHandle, &exitCode); - ::CloseHandle(processHandle); - if (exitCode != 0UL) { - reportError(tr("loot failed. Exit code was: %1").arg(exitCode)); - return; - } else { - success = true; - QFile outFile(outPath); - outFile.open(QIODevice::ReadOnly); - QJsonDocument doc = QJsonDocument::fromJson(outFile.readAll()); - QJsonArray array = doc.array(); - for (auto iter = array.begin(); iter != array.end(); ++iter) { - QJsonObject pluginObj = (*iter).toObject(); - QJsonArray pluginMessages = pluginObj["messages"].toArray(); - for (auto msgIter = pluginMessages.begin(); msgIter != pluginMessages.end(); ++msgIter) { - QJsonObject msg = (*msgIter).toObject(); - m_OrganizerCore.pluginList()->addInformation(pluginObj["name"].toString(), - QString("%1: %2").arg(msg["type"].toString(), msg["message"].toString())); - } - if (pluginObj["dirty"].toString() == "yes") - m_OrganizerCore.pluginList()->addInformation(pluginObj["name"].toString(), "dirty"); - } - - } - } else { - reportError(tr("failed to start loot")); - } - } catch (const std::exception &e) { - reportError(tr("failed to run loot: %1").arg(e.what())); - } - - if (errorMessages.length() > 0) { - QMessageBox *warn = new QMessageBox(QMessageBox::Warning, tr("Errors occurred"), errorMessages.c_str(), QMessageBox::Ok, this); - warn->setModal(false); - warn->show(); - } + setEnabled(false); + ON_BLOCK_EXIT([&] () { setEnabled(true); }); - if (success) { + if (runLoot(this, m_OrganizerCore, m_DidUpdateMasterList)) { m_DidUpdateMasterList = true; m_OrganizerCore.refreshESPList(false); m_OrganizerCore.savePluginList(); -- cgit v1.3.1 From b8eab5d248272cab886bc800f2d619a49d8af54a Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 16 Nov 2019 22:25:55 -0500 Subject: threaded loot --- src/loot.cpp | 317 +++++++++++++++++++++++++++++++++++------------------ src/loot.h | 37 +++++++ src/mainwindow.cpp | 3 +- 3 files changed, 249 insertions(+), 108 deletions(-) (limited to 'src/mainwindow.cpp') diff --git a/src/loot.cpp b/src/loot.cpp index a9e716ae..ab152b80 100644 --- a/src/loot.cpp +++ b/src/loot.cpp @@ -1,3 +1,4 @@ +#include "loot.h" #include "spawn.h" #include "organizercore.h" #include @@ -9,11 +10,35 @@ using namespace MOBase; class LootDialog : public QDialog { public: - LootDialog(QWidget* parent) : - QDialog(parent), m_label(nullptr), m_progress(nullptr), m_buttons(nullptr), - m_cancelled(false) + LootDialog(QWidget* parent, OrganizerCore& core, Loot& loot) : + QDialog(parent), m_core(core), m_loot(loot), + m_label(nullptr), m_progress(nullptr), m_buttons(nullptr), m_finished(false) { createUI(); + + QObject::connect( + &m_loot, &Loot::output, this, + [&](auto&& s){ addOutput(s); }, Qt::QueuedConnection); + + QObject::connect( + &m_loot, &Loot::progress, + this, [&](auto&& s){ setText(s); }, Qt::QueuedConnection); + + QObject::connect( + &m_loot, &Loot::information, this, + [&](auto&& mod, auto&& i){ setInfo(mod, i); }, Qt::QueuedConnection); + + QObject::connect( + &m_loot, &Loot::errorMessage, this, + [&](auto&& s){ onErrorMessage(s); }, Qt::QueuedConnection); + + QObject::connect( + &m_loot, &Loot::error, this, + [&](auto&& s){ onError(s); }, Qt::QueuedConnection); + + QObject::connect( + &m_loot, &Loot::finished, this, + [&]{ onFinished(); }, Qt::QueuedConnection); } void setText(const QString& s) @@ -39,18 +64,52 @@ public: } } - bool cancelled() const + void setInfo(const QString& mod, const QString& info) { - return m_cancelled; + m_core.pluginList()->addInformation(mod.toStdString().c_str(), info); + } + + bool result() const + { + return m_loot.result(); + } + + void cancel() + { + m_loot.cancel(); + } + + int exec() override + { + m_loot.start(); + QDialog::exec(); + + if (m_errorMessages.length() > 0) { + QMessageBox *warn = new QMessageBox( + QMessageBox::Warning, QObject::tr("Errors occurred"), + m_errorMessages, QMessageBox::Ok, parentWidget()); + + warn->exec(); + } + + return 0; + } + + void onError(const QString& s) + { + reportError(s); } private: + OrganizerCore& m_core; + Loot& m_loot; QLabel* m_label; QProgressBar* m_progress; QDialogButtonBox* m_buttons; QPlainTextEdit* m_output; QString m_lastLine; - bool m_cancelled; + QString m_errorMessages; + bool m_finished; void createUI() { @@ -77,13 +136,18 @@ private: void closeEvent(QCloseEvent* e) override { - m_cancelled = true; + if (m_finished) { + QDialog::closeEvent(e); + } else { + cancel(); + e->ignore(); + } } void onButton(QAbstractButton* b) { if (m_buttons->buttonRole(b) == QDialogButtonBox::RejectRole) { - m_cancelled = true; + cancel(); } } @@ -96,10 +160,83 @@ private: m_output->appendPlainText(line); m_lastLine = line; } + + void onFinished() + { + m_finished = true; + close(); + } + + void onErrorMessage(const QString& s) + { + m_errorMessages += s; + } }; -void createStdoutPipe(HANDLE *stdOutRead, HANDLE *stdOutWrite) +Loot::Loot(QWidget* parent, OrganizerCore& core, bool didUpdateMasterList) : + m_thread(nullptr), m_cancel(false), m_result(false), + m_lootProcess(INVALID_HANDLE_VALUE), m_stdOutRead(INVALID_HANDLE_VALUE) +{ + m_outPath = QDir::temp().absoluteFilePath("lootreport.json"); + + QStringList parameters; + parameters + << "--game" << core.managedGame()->gameShortName() + << "--gamePath" << QString("\"%1\"").arg(core.managedGame()->gameDirectory().absolutePath()) + << "--pluginListPath" << QString("\"%1/loadorder.txt\"").arg(core.profilePath()) + << "--out" << QString("\"%1\"").arg(m_outPath); + + if (didUpdateMasterList) { + parameters << "--skipUpdateMasterlist"; + } + + HANDLE stdOutWrite = INVALID_HANDLE_VALUE; + createStdoutPipe(&m_stdOutRead, &stdOutWrite); + + core.prepareVFS(); + + spawn::SpawnParameters sp; + sp.binary = QFileInfo(qApp->applicationDirPath() + "/loot/lootcli.exe"); + sp.arguments = parameters.join(" "); + sp.currentDirectory.setPath(qApp->applicationDirPath() + "/loot"); + sp.hooked = true; + sp.stdOut = stdOutWrite; + + m_lootProcess = spawn::startBinary(parent, sp); + + // we don't use the write end + ::CloseHandle(stdOutWrite); + + core.pluginList()->clearAdditionalInformation(); + + m_thread.reset(QThread::create([&]{ + lootThread(); + emit finished(); + })); +} + +Loot::~Loot() +{ + m_thread->wait(); +} + +void Loot::start() +{ + m_thread->start(); +} + +void Loot::cancel() +{ + m_cancel = true; +} + +bool Loot::result() const +{ + return m_result; +} + +void Loot::createStdoutPipe(HANDLE *stdOutRead, HANDLE *stdOutWrite) { SECURITY_ATTRIBUTES secAttributes; secAttributes.nLength = sizeof(SECURITY_ATTRIBUTES); @@ -116,7 +253,7 @@ void createStdoutPipe(HANDLE *stdOutRead, HANDLE *stdOutWrite) } } -std::string readFromPipe(HANDLE stdOutRead) +std::string Loot::readFromPipe(HANDLE stdOutRead) { static const int chunkSize = 128; std::string result; @@ -139,11 +276,9 @@ std::string readFromPipe(HANDLE stdOutRead) return result; } -void processLOOTOut( - OrganizerCore& core, - const std::string &lootOut, std::string &errorMessages, LootDialog& dialog) +void Loot::processLOOTOut(const std::string &lootOut) { - dialog.addOutput(QString::fromStdString(lootOut)); + emit output(QString::fromStdString(lootOut)); std::vector lines; boost::split(lines, lootOut, boost::is_any_of("\r\n")); @@ -156,20 +291,25 @@ void processLOOTOut( size_t progidx = line.find("[progress]"); size_t erroridx = line.find("[error]"); if (progidx != std::string::npos) { - dialog.setText(line.substr(progidx + 11).c_str()); + emit progress(line.substr(progidx + 11).c_str()); } else if (erroridx != std::string::npos) { log::warn("{}", line); - errorMessages.append(boost::algorithm::trim_copy(line.substr(erroridx + 8)) + "\n"); + emit errorMessage(QString::fromStdString( + boost::algorithm::trim_copy(line.substr(erroridx + 8)) + "\n")); } else { std::smatch match; if (std::regex_match(line, match, exRequires)) { std::string modName(match[1].first, match[1].second); std::string dependency(match[2].first, match[2].second); - core.pluginList()->addInformation(modName.c_str(), QObject::tr("depends on missing \"%1\"").arg(dependency.c_str())); + emit information( + QString::fromStdString(modName), + QObject::tr("depends on missing \"%1\"").arg(dependency.c_str())); } else if (std::regex_match(line, match, exIncompatible)) { std::string modName(match[1].first, match[1].second); std::string dependency(match[2].first, match[2].second); - core.pluginList()->addInformation(modName.c_str(), QObject::tr("incompatible with \"%1\"").arg(dependency.c_str())); + emit information( + QString::fromStdString(modName), + QObject::tr("incompatible with \"%1\"").arg(dependency.c_str())); } else { log::debug("[loot] {}", line); } @@ -178,85 +318,29 @@ void processLOOTOut( } } -bool runLoot(QWidget* parent, OrganizerCore& core, bool didUpdateMasterList) +void Loot::lootThread() { - std::string errorMessages; - - //m_OrganizerCore.currentProfile()->writeModlistNow(); - core.savePluginList(); - - //Create a backup of the load orders w/ LOOT in name - //to make sure that any sorting is easily undo-able. - //Need to figure out how I want to do that. - - bool success = false; - try { - LootDialog dialog(parent); - - dialog.setText(QObject::tr("Please wait while LOOT is running")); - dialog.setIndeterminate(); - dialog.show(); - - QString outPath = QDir::temp().absoluteFilePath("lootreport.json"); - - QStringList parameters; - parameters - << "--game" << core.managedGame()->gameShortName() - << "--gamePath" << QString("\"%1\"").arg(core.managedGame()->gameDirectory().absolutePath()) - << "--pluginListPath" << QString("\"%1/loadorder.txt\"").arg(core.profilePath()) - << "--out" << QString("\"%1\"").arg(outPath); - - if (didUpdateMasterList) { - parameters << "--skipUpdateMasterlist"; - } - - HANDLE stdOutWrite = INVALID_HANDLE_VALUE; - HANDLE stdOutRead = INVALID_HANDLE_VALUE; - createStdoutPipe(&stdOutRead, &stdOutWrite); - - try { - core.prepareVFS(); - } catch (const UsvfsConnectorException &e) { - log::debug("{}", e.what()); - return false; - } catch (const std::exception &e) { - QMessageBox::warning(parent, QObject::tr("Error"), e.what()); - return false; - } - - spawn::SpawnParameters sp; - sp.binary = QFileInfo(qApp->applicationDirPath() + "/loot/lootcli.exe"); - sp.arguments = parameters.join(" "); - sp.currentDirectory.setPath(qApp->applicationDirPath() + "/loot"); - sp.hooked = true; - sp.stdOut = stdOutWrite; - - HANDLE loot = spawn::startBinary(parent, sp); - - // we don't use the write end - ::CloseHandle(stdOutWrite); - - core.pluginList()->clearAdditionalInformation(); + m_result = false; DWORD retLen; JOBOBJECT_BASIC_PROCESS_ID_LIST info; - HANDLE processHandle = loot; + HANDLE processHandle = m_lootProcess; - if (loot != INVALID_HANDLE_VALUE) { + if (m_lootProcess != INVALID_HANDLE_VALUE) { bool isJobHandle = true; ULONG lastProcessID; - DWORD res = ::MsgWaitForMultipleObjects(1, &loot, false, 100, QS_KEY | QS_MOUSE); + DWORD res = ::MsgWaitForMultipleObjects(1, &m_lootProcess, false, 100, QS_KEY | QS_MOUSE); while ((res != WAIT_FAILED) && (res != WAIT_OBJECT_0)) { if (isJobHandle) { - if (::QueryInformationJobObject(loot, JobObjectBasicProcessIdList, &info, sizeof(info), &retLen) > 0) { + if (::QueryInformationJobObject(m_lootProcess, JobObjectBasicProcessIdList, &info, sizeof(info), &retLen) > 0) { if (info.NumberOfProcessIdsInList == 0) { log::debug("no more processes in job"); break; } else { if (lastProcessID != info.ProcessIdList[0]) { lastProcessID = info.ProcessIdList[0]; - if (processHandle != loot) { + if (processHandle != m_lootProcess) { ::CloseHandle(processHandle); } processHandle = ::OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, lastProcessID); @@ -273,36 +357,37 @@ bool runLoot(QWidget* parent, OrganizerCore& core, bool didUpdateMasterList) } } - if (dialog.cancelled()) { + if (m_cancel) { if (isJobHandle) { - ::TerminateJobObject(loot, 1); + ::TerminateJobObject(m_lootProcess, 1); } else { - ::TerminateProcess(loot, 1); + ::TerminateProcess(m_lootProcess, 1); } } // keep processing events so the app doesn't appear dead QCoreApplication::processEvents(); - std::string lootOut = readFromPipe(stdOutRead); - processLOOTOut(core, lootOut, errorMessages, dialog); + std::string lootOut = readFromPipe(m_stdOutRead); + processLOOTOut(lootOut); - res = ::MsgWaitForMultipleObjects(1, &loot, false, 100, QS_KEY | QS_MOUSE); + res = ::MsgWaitForMultipleObjects(1, &m_lootProcess, false, 100, QS_KEY | QS_MOUSE); } - std::string remainder = readFromPipe(stdOutRead).c_str(); + std::string remainder = readFromPipe(m_stdOutRead).c_str(); if (remainder.length() > 0) { - processLOOTOut(core, remainder, errorMessages, dialog); + processLOOTOut(remainder); } DWORD exitCode = 0UL; ::GetExitCodeProcess(processHandle, &exitCode); ::CloseHandle(processHandle); if (exitCode != 0UL) { - reportError(QObject::tr("loot failed. Exit code was: %1").arg(exitCode)); - return false; + emit error(QObject::tr("loot failed. Exit code was: %1").arg(exitCode)); + m_result = false; + return; } else { - success = true; - QFile outFile(outPath); + m_result = true; + QFile outFile(m_outPath); outFile.open(QIODevice::ReadOnly); QJsonDocument doc = QJsonDocument::fromJson(outFile.readAll()); QJsonArray array = doc.array(); @@ -311,29 +396,47 @@ bool runLoot(QWidget* parent, OrganizerCore& core, bool didUpdateMasterList) QJsonArray pluginMessages = pluginObj["messages"].toArray(); for (auto msgIter = pluginMessages.begin(); msgIter != pluginMessages.end(); ++msgIter) { QJsonObject msg = (*msgIter).toObject(); - core.pluginList()->addInformation(pluginObj["name"].toString(), + emit information( + pluginObj["name"].toString(), QString("%1: %2").arg(msg["type"].toString(), msg["message"].toString())); } - if (pluginObj["dirty"].toString() == "yes") - core.pluginList()->addInformation(pluginObj["name"].toString(), "dirty"); + if (pluginObj["dirty"].toString() == "yes") { + emit information(pluginObj["name"].toString(), "dirty"); + } } - } } else { - reportError(QObject::tr("failed to start loot")); + emit error(QObject::tr("failed to start loot")); } } catch (const std::exception &e) { - reportError(QObject::tr("failed to run loot: %1").arg(e.what())); + emit error(QObject::tr("failed to run loot: %1").arg(e.what())); } +} - if (errorMessages.length() > 0) { - QMessageBox *warn = new QMessageBox( - QMessageBox::Warning, QObject::tr("Errors occurred"), - errorMessages.c_str(), QMessageBox::Ok, parent); - warn->setModal(false); - warn->show(); - } +bool runLoot(QWidget* parent, OrganizerCore& core, bool didUpdateMasterList) +{ + //m_OrganizerCore.currentProfile()->writeModlistNow(); + core.savePluginList(); - return success; + //Create a backup of the load orders w/ LOOT in name + //to make sure that any sorting is easily undo-able. + //Need to figure out how I want to do that. + + try { + Loot loot(parent, core, didUpdateMasterList); + LootDialog dialog(parent, core, loot); + + dialog.setText(QObject::tr("Please wait while LOOT is running")); + dialog.setIndeterminate(); + dialog.exec(); + + return dialog.result(); + } catch (const UsvfsConnectorException &e) { + log::debug("{}", e.what()); + return false; + } catch (const std::exception &e) { + reportError(QObject::tr("failed to run loot: %1").arg(e.what())); + return false; + } } diff --git a/src/loot.h b/src/loot.h index 2314e5b3..11f1dbc8 100644 --- a/src/loot.h +++ b/src/loot.h @@ -1,10 +1,47 @@ #ifndef MODORGANIZER_LOOT_H #define MODORGANIZER_LOOT_H +#include #include class OrganizerCore; +class Loot : public QObject +{ + Q_OBJECT; + +public: + Loot(QWidget* parent, OrganizerCore& core, bool didUpdateMasterList); + ~Loot(); + + void start(); + void cancel(); + bool result() const; + +signals: + void output(const QString& s); + void progress(const QString& s); + void information(const QString& mod, const QString& info); + void errorMessage(const QString& s); + void error(const QString& s); + void finished(); + +private: + std::unique_ptr m_thread; + std::atomic m_cancel; + std::atomic m_result; + QString m_outPath; + HANDLE m_lootProcess; + HANDLE m_stdOutRead; + + void createStdoutPipe(HANDLE *stdOutRead, HANDLE *stdOutWrite); + std::string readFromPipe(HANDLE stdOutRead); + + void processLOOTOut(const std::string &lootOut); + void lootThread(); +}; + + bool runLoot(QWidget* parent, OrganizerCore& core, bool didUpdateMasterList); #endif // MODORGANIZER_LOOT_H diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 11edfc81..9ea554a2 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -39,7 +39,6 @@ along with Mod Organizer. If not, see . #include "spawn.h" #include "versioninfo.h" #include "instancemanager.h" -#include "loot.h" #include "report.h" #include "modlist.h" #include "modlistsortproxy.h" @@ -191,6 +190,8 @@ const QSize SmallToolbarSize(24, 24); const QSize MediumToolbarSize(32, 32); const QSize LargeToolbarSize(42, 36); +bool runLoot(QWidget* parent, OrganizerCore& core, bool didUpdateMasterList); + MainWindow::MainWindow(Settings &settings , OrganizerCore &organizerCore -- cgit v1.3.1