summaryrefslogtreecommitdiff
path: root/src/loot.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/loot.cpp')
-rw-r--r--src/loot.cpp332
1 files changed, 202 insertions, 130 deletions
diff --git a/src/loot.cpp b/src/loot.cpp
index eef16b45..88ea8ce8 100644
--- a/src/loot.cpp
+++ b/src/loot.cpp
@@ -1,10 +1,12 @@
#include "loot.h"
#include "spawn.h"
#include "organizercore.h"
+#include "json.h"
#include <log.h>
#include <report.h>
using namespace MOBase;
+using namespace json;
log::Levels levelFromLoot(lootcli::LogLevels level)
{
@@ -193,7 +195,6 @@ private:
ly->addLayout(buttons);
m_output = new QPlainTextEdit;
- m_output->setWordWrapMode(QTextOption::NoWrap);
ly->addWidget(m_output);
m_buttons = new QDialogButtonBox(QDialogButtonBox::Cancel);
@@ -254,6 +255,81 @@ private:
};
+struct Loot::Message
+{
+ QString type;
+ QString text;
+};
+
+struct Loot::File
+{
+ QString name;
+ QString displayName;
+};
+
+struct Loot::Dirty
+{
+ qint64 crc=0;
+ qint64 itm=0;
+ qint64 deletedReferences=0;
+ qint64 deletedNavmesh=0;
+ QString cleaningUtility;
+ QString info;
+
+ QString toString(bool isClean) const
+ {
+ if (isClean) {
+ return QObject::tr("Verified clean by %1")
+ .arg(cleaningUtility.isEmpty() ? "?" : cleaningUtility);
+ }
+
+ QString s = cleaningString();
+
+ if (!info.isEmpty()) {
+ s += " " + info;
+ }
+
+ return s;
+ }
+
+ QString cleaningString() const
+ {
+ return QObject::tr("%1 found %2 ITM record(s), %3 deleted reference(s) and %4 deleted navmesh(es).")
+ .arg(cleaningUtility.isEmpty() ? "?" : cleaningUtility)
+ .arg(itm)
+ .arg(deletedReferences)
+ .arg(deletedNavmesh);
+ }
+};
+
+struct Loot::Plugin
+{
+ QString name;
+ std::vector<File> incompatibilities;
+ std::vector<Message> messages;
+ std::vector<Dirty> dirty, clean;
+ std::vector<QString> missingMasters;
+ bool loadsArchive = false;
+ bool isMaster = false;
+ bool isLightMaster = false;
+};
+
+struct Loot::Stats
+{
+ qint64 time = 0;
+ QString version;
+};
+
+struct Loot::Report
+{
+ std::vector<Message> messages;
+ std::vector<Plugin> plugins;
+ Stats stats;
+};
+
+
+class ReportFailed {};
+
Loot::Loot()
: m_thread(nullptr), m_cancel(false), m_result(false)
{
@@ -522,49 +598,16 @@ void Loot::processMessage(const lootcli::Message& m)
}
}
-QString jsonType(const QJsonValue& v)
-{
- if (v.isUndefined()) {
- return "undefined";
- } else if (v.isNull()) {
- return "null";
- } else if (v.isArray()) {
- return "an array";
- } else if (v.isBool()) {
- return "a bool";
- } else if (v.isDouble()) {
- return "a double";
- } else if (v.isObject()) {
- return "an object";
- } else if (v.isString()) {
- return "a string";
- } else {
- return "an unknown type";
- }
-}
-
-QString jsonType(const QJsonDocument& doc)
-{
- if (doc.isEmpty()) {
- return "empty";
- } else if (doc.isNull()) {
- return "null";
- } else if (doc.isArray()) {
- return "an array";
- } else if (doc.isObject()) {
- return "an object";
- } else {
- return "an unknown type";
- }
-}
-
void Loot::processOutputFile()
{
+ log::info("parsing json output file at '{}'", m_outPath);
+
QFile outFile(m_outPath);
if (!outFile.open(QIODevice::ReadOnly)) {
- logJsonError(
- "failed to open file, {} (error {})",
- outFile.errorString(), outFile.error());
+ emit log(
+ MOBase::log::Error,
+ QString("failed to open file, %1 (error %2)")
+ .arg(outFile.errorString()).arg(outFile.error()));
return;
}
@@ -572,149 +615,178 @@ void Loot::processOutputFile()
QJsonParseError e;
const QJsonDocument doc = QJsonDocument::fromJson(outFile.readAll(), &e);
if (doc.isNull()) {
- logJsonError("invalid json, {} (error {})", e.errorString(), e.error);
- return;
- }
+ emit log(
+ MOBase::log::Error,
+ QString("invalid json, %1 (error %2)")
+ .arg(e.errorString()).arg(e.error));
- if (!doc.isObject()) {
- logJsonError("root is {}, not an object", jsonType(doc));
return;
}
- const QJsonObject object = doc.object();
+ const auto report = createReport(doc);
- if (object.contains("messages")) {
- const auto messagesValue = object["messages"];
+ for (auto&& m : report.messages) {
+ emit log(levelFromLoot(
+ lootcli::logLevelFromString(m.type.toStdString())),
+ m.text);
+ }
- if (messagesValue.isArray()) {
- processMessages(messagesValue.toArray());
- } else {
- logJsonError(
- "'messages' property is {}, not an array", jsonType(messagesValue));
+ for (auto&& p : report.plugins) {
+ for (auto&& d : p.dirty) {
+ emit information(p.name, d.toString(false));
}
-
}
+}
- if (object.contains("plugins")) {
- const auto pluginsValue = object["plugins"];
+Loot::Report Loot::createReport(const QJsonDocument& doc) const
+{
+ requireObject(doc, "root");
- if (pluginsValue.isArray()) {
- processPlugins(pluginsValue.toArray());
- } else {
- logJsonError(
- "'plugins' property is {}, not an array", jsonType(pluginsValue));
- }
- }
+ Report r;
+ const QJsonObject object = doc.object();
+
+ r.messages = reportMessages(getOpt<QJsonArray>(object, "messages"));
+ r.plugins = reportPlugins(getOpt<QJsonArray>(object, "plugins"));
+
+ return r;
}
-bool Loot::processMessages(const QJsonArray& messages)
+std::vector<Loot::Plugin> Loot::reportPlugins(const QJsonArray& plugins) const
{
- for (auto messageValue : messages) {
- if (messageValue.isObject()) {
- processMessage(messageValue.toObject());
- } else {
- logJsonError("a message is {}, not an object", jsonType(messageValue));
+ std::vector<Loot::Plugin> v;
+
+ for (auto pluginValue : plugins) {
+ const auto o = convertWarn<QJsonObject>(pluginValue, "plugin");
+ if (o.isEmpty()) {
+ continue;
+ }
+
+ auto p = reportPlugin(o);
+ if (!p.name.isEmpty()) {
+ v.emplace_back(std::move(p));
}
}
- return true;
+ return v;
}
-bool Loot::processMessage(const QJsonObject& message)
+Loot::Plugin Loot::reportPlugin(const QJsonObject& plugin) const
{
- const auto messageType = message["type"].toString();
- const auto messageString = message["message"].toString();
+ Plugin p;
- if (messageType.isEmpty()) {
- logJsonError("there's a message with no 'type' property");
- return false;
+ p.name = getWarn<QString>(plugin, "name");
+ if (p.name.isEmpty()) {
+ return {};
}
- if (messageString.isEmpty()) {
- logJsonError("there's a message with no 'message' property");
- return false;
+ if (plugin.contains("incompatibilities")) {
+ p.incompatibilities = reportFiles(getOpt<QJsonArray>(plugin, "incompatibilities"));
+ }
+
+ if (plugin.contains("messages")) {
+ p.messages = reportMessages(getOpt<QJsonArray>(plugin, "messages"));
}
- emit log(levelFromLoot(
- lootcli::logLevelFromString(messageType.toStdString())),
- messageString);
+ if (plugin.contains("dirty")) {
+ p.dirty = reportDirty(getOpt<QJsonArray>(plugin, "dirty"));
+ }
- return true;
-}
+ if (plugin.contains("clean")) {
+ p.clean = reportDirty(getOpt<QJsonArray>(plugin, "clean"));
+ }
-bool Loot::processPlugins(const QJsonArray& plugins)
-{
- for (auto pluginValue : plugins) {
- if (pluginValue.isObject()) {
- processPlugin(pluginValue.toObject());
- } else {
- logJsonError("a plugin is {}, not an object", jsonType(pluginValue));
- }
+ if (plugin.contains("missingMasters")) {
+ p.missingMasters = reportStringArray(getOpt<QJsonArray>(plugin, "missingMasters"));
}
- return true;
+ p.loadsArchive = getOpt(plugin, "loadsArchive", false);
+ p.isMaster = getOpt(plugin, "isMaster", false);
+ p.isLightMaster = getOpt(plugin, "isLightMaster", false);
+
+ return p;
}
-bool Loot::processPlugin(const QJsonObject& plugin)
+std::vector<Loot::Message> Loot::reportMessages(const QJsonArray& array) const
{
- if (!plugin.contains("name")) {
- logJsonError("plugin missing 'name' property");
- return false;
- }
+ std::vector<Loot::Message> v;
- const auto nameValue = plugin["name"];
- if (!nameValue.isString()) {
- logJsonError("plugin property 'name' is {}, not a string", jsonType(nameValue));
- return false;
- }
+ for (auto messageValue : array) {
+ const auto o = convertWarn<QJsonObject>(messageValue, "message");
+ if (o.isEmpty()) {
+ continue;
+ }
- const auto name = nameValue.toString();
+ Message m;
+ m.type = getWarn<QString>(o, "type");
+ m.text = getWarn<QString>(o, "text");
- processPluginDirty(name, plugin);
+ if (!m.text.isEmpty()) {
+ v.emplace_back(std::move(m));
+ }
+ }
- return true;
+ return v;
}
-
-bool Loot::processPluginDirty(const QString& name, const QJsonObject& plugin)
+std::vector<Loot::File> Loot::reportFiles(const QJsonArray& array) const
{
- if (!plugin.contains("dirty")) {
- return true;
- }
+ std::vector<Loot::File> v;
+
+ for (auto&& fileValue : array) {
+ const auto o = convertWarn<QJsonObject>(fileValue, "file");
+ if (o.isEmpty()) {
+ continue;
+ }
- const auto dirtyValue = plugin["dirty"];
+ File f;
- if (!dirtyValue.isArray()) {
- logJsonError(
- "'dirty' value for plugin '{}' is {}, not an array",
- name, jsonType(dirtyValue));
+ f.name = getWarn<QString>(o, "name");
+ f.displayName = getOpt<QString>(o, "displayName");
- return false;
+ if (!f.name.isEmpty()) {
+ v.emplace_back(std::move(f));
+ }
}
- const auto dirty = dirtyValue.toArray();
+ return v;
+}
+
+std::vector<Loot::Dirty> Loot::reportDirty(const QJsonArray& array) const
+{
+ std::vector<Loot::Dirty> v;
+ for (auto&& dirtyValue : array) {
+ const auto o = convertWarn<QJsonObject>(dirtyValue, "dirty");
- for (auto stringValue : dirty) {
- if (!stringValue.isString()) {
- logJsonError(
- "'dirty' value for plugin '{}' is {}, not a string",
- name, jsonType(stringValue));
+ Dirty d;
- continue;
- }
+ d.crc = getWarn<qint64>(o, "crc");
+ d.itm = getOpt<qint64>(o, "itm");
+ d.deletedReferences = getOpt<qint64>(o, "deletedReferences");
+ d.deletedNavmesh = getOpt<qint64>(o, "deletedNavmesh");
+ d.cleaningUtility = getOpt<QString>(o, "cleaningUtility");
+ d.info = getOpt<QString>(o, "info");
- const auto string = stringValue.toString();
+ v.emplace_back(std::move(d));
+ }
+
+ return v;
+}
- if (string.isEmpty()) {
- logJsonError("'dirty' string for plugin '{}' is empty", name);
+std::vector<QString> Loot::reportStringArray(const QJsonArray& array) const
+{
+ std::vector<QString> v;
+
+ for (auto&& sv : array) {
+ auto s = convertWarn<QString>(sv, "string");
+ if (s.isEmpty()) {
continue;
}
- emit information(name, string);
+ v.emplace_back(std::move(s));
}
- return true;
+ return v;
}