summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorisanae <14251494+isanae@users.noreply.github.com>2019-11-23 22:08:53 -0500
committerisanae <14251494+isanae@users.noreply.github.com>2019-11-23 22:08:53 -0500
commit5c9de17b6376ce94b1cdff4f4edbba798e9bfd08 (patch)
tree6da4bc1dc844dd7c3e7b7378765bb1a6b13a7c17
parent237825f5b6c77969376198ed60d81c26a6a8aded (diff)
rewrite of json report parsing
added json.h with some utilities
-rw-r--r--src/CMakeLists.txt2
-rw-r--r--src/json.h190
-rw-r--r--src/loot.cpp332
-rw-r--r--src/loot.h28
4 files changed, 410 insertions, 142 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 824ffa33..3b74ea37 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -265,6 +265,7 @@ SET(organizer_HDRS
processrunner.h
uilocker.h
loot.h
+ json.h
shared/windows_error.h
shared/error_report.h
@@ -468,6 +469,7 @@ set(utilities
usvfsconnector
shared/windows_error
loot
+ json
)
set(widgets
diff --git a/src/json.h b/src/json.h
new file mode 100644
index 00000000..d182f330
--- /dev/null
+++ b/src/json.h
@@ -0,0 +1,190 @@
+#ifndef MODORGANIZER_JSON_INCLUDED
+#define MODORGANIZER_JSON_INCLUDED
+
+#include <log.h>
+#include <QJsonDocument>
+#include <QJsonObject>
+#include <QJsonArray>
+#include <QJsonValue>
+
+namespace json
+{
+
+class failed {};
+
+
+namespace details
+{
+
+QString typeName(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 typeName(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";
+ }
+}
+
+
+template <class T>
+T convert(const QJsonValue& v) = delete;
+
+template <>
+bool convert<bool>(const QJsonValue& v)
+{
+ if (!v.isBool()) {
+ throw failed();
+ }
+
+ return v.toBool();
+}
+
+template <>
+QJsonObject convert<QJsonObject>(const QJsonValue& v)
+{
+ if (!v.isObject()) {
+ throw failed();
+ }
+
+ return v.toObject();
+}
+
+template <>
+QString convert<QString>(const QJsonValue& v)
+{
+ if (!v.isString()) {
+ throw failed();
+ }
+
+ return v.toString();
+}
+
+template <>
+QJsonArray convert<QJsonArray>(const QJsonValue& v)
+{
+ if (!v.isArray()) {
+ throw failed();
+ }
+
+ return v.toArray();
+}
+
+template <>
+qint64 convert<qint64>(const QJsonValue& v)
+{
+ if (!v.isDouble()) {
+ throw failed();
+ }
+
+ return static_cast<qint64>(v.toDouble());
+}
+
+} // namespace
+
+
+template <class T>
+T convert(const QJsonValue& value, const char* what)
+{
+ try
+ {
+ return details::convert<T>(value);
+ }
+ catch(failed&)
+ {
+ MOBase::log::error(
+ "'{}' is a {}, not a {}",
+ what, details::typeName(value), typeid(T).name);
+
+ throw;
+ }
+}
+
+template <class T>
+T convertWarn(const QJsonValue& value, const char* what, T def={})
+{
+ try
+ {
+ return details::convert<T>(value);
+ }
+ catch(failed&)
+ {
+ MOBase::log::warn(
+ "'{}' is a {}, not a {}",
+ what, details::typeName(value), typeid(T).name());
+
+ return def;
+ }
+}
+
+template <class T>
+T get(const QJsonObject& o, const char* e)
+{
+ if (!o.contains(e)) {
+ MOBase::log::error("property '{}' is missing", e);
+ throw failed();
+ }
+
+ return convert<T>(o[e], e);
+}
+
+template <class T>
+T getWarn(const QJsonObject& o, const char* e, T def={})
+{
+ if (!o.contains(e)) {
+ MOBase::log::warn("property '{}' is missing", e);
+ return def;
+ }
+
+ return convertWarn<T>(o[e], e);
+}
+
+template <class T>
+T getOpt(const QJsonObject& o, const char* e, T def={})
+{
+ if (!o.contains(e)) {
+ return def;
+ }
+
+ return convertWarn<T>(o[e], e);
+}
+
+
+template <class Value>
+void requireObject(const Value& v, const char* what)
+{
+ if (!v.isObject()) {
+ MOBase::log::error("{} is {}, not an object", what, details::typeName(v));
+ throw failed();
+ }
+}
+
+} // namespace
+
+#endif // MODORGANIZER_JSON_INCLUDED
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;
}
diff --git a/src/loot.h b/src/loot.h
index 54fc6fd1..95dbbe50 100644
--- a/src/loot.h
+++ b/src/loot.h
@@ -33,6 +33,14 @@ signals:
void finished();
private:
+ struct Report;
+ struct Stats;
+ struct Message;
+ struct Plugin;
+ struct Dirty;
+ struct File;
+ class BadReport {};
+
std::unique_ptr<QThread> m_thread;
std::atomic<bool> m_cancel;
std::atomic<bool> m_result;
@@ -50,20 +58,16 @@ private:
void processMessage(const lootcli::Message& m);
void processOutputFile();
- bool processMessages(const QJsonArray& messages);
- bool processMessage(const QJsonObject& message);
- bool processPlugins(const QJsonArray& plugins);
- bool processPlugin(const QJsonObject& plugin);
- bool processPluginDirty(const QString& name, const QJsonObject& plugin);
+ Report createReport(const QJsonDocument& doc) const;
+ Message reportMessage(const QJsonObject& message) const;
+ std::vector<Plugin> reportPlugins(const QJsonArray& plugins) const;
+ Loot::Plugin reportPlugin(const QJsonObject& plugin) const;
- template <class Format, class... Args>
- void logJsonError(Format&& f, Args&&... args)
- {
- MOBase::log::error(
- std::string("loot output file '{}': ") + f,
- m_outPath, std::forward<Args>(args)...);
- };
+ std::vector<Message> reportMessages(const QJsonArray& array) const;
+ std::vector<Loot::File> reportFiles(const QJsonArray& array) const;
+ std::vector<Loot::Dirty> reportDirty(const QJsonArray& array) const;
+ std::vector<QString> reportStringArray(const QJsonArray& array) const;
};