diff options
| author | isanae <14251494+isanae@users.noreply.github.com> | 2019-11-16 22:25:55 -0500 |
|---|---|---|
| committer | isanae <14251494+isanae@users.noreply.github.com> | 2019-11-16 22:25:55 -0500 |
| commit | b8eab5d248272cab886bc800f2d619a49d8af54a (patch) | |
| tree | 614d36adf27842249050666bf9369b66c19248af /src | |
| parent | 9688f37bbe54568aba09e2dd755e0ea35eef2bb1 (diff) | |
threaded loot
Diffstat (limited to 'src')
| -rw-r--r-- | src/loot.cpp | 317 | ||||
| -rw-r--r-- | src/loot.h | 37 | ||||
| -rw-r--r-- | src/mainwindow.cpp | 3 |
3 files changed, 249 insertions, 108 deletions
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 <log.h> @@ -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<std::string> 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; + } } @@ -1,10 +1,47 @@ #ifndef MODORGANIZER_LOOT_H #define MODORGANIZER_LOOT_H +#include <windows.h> #include <QWidget> 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<QThread> m_thread; + std::atomic<bool> m_cancel; + std::atomic<bool> 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 <http://www.gnu.org/licenses/>. #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 |
