summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorisanae <14251494+isanae@users.noreply.github.com>2019-11-16 22:25:55 -0500
committerisanae <14251494+isanae@users.noreply.github.com>2019-11-16 22:25:55 -0500
commitb8eab5d248272cab886bc800f2d619a49d8af54a (patch)
tree614d36adf27842249050666bf9369b66c19248af /src
parent9688f37bbe54568aba09e2dd755e0ea35eef2bb1 (diff)
threaded loot
Diffstat (limited to 'src')
-rw-r--r--src/loot.cpp317
-rw-r--r--src/loot.h37
-rw-r--r--src/mainwindow.cpp3
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;
+ }
}
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 <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