aboutsummaryrefslogtreecommitdiff
path: root/launcher/modplatform/EnsureMetadataTask.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'launcher/modplatform/EnsureMetadataTask.cpp')
-rw-r--r--launcher/modplatform/EnsureMetadataTask.cpp244
1 files changed, 244 insertions, 0 deletions
diff --git a/launcher/modplatform/EnsureMetadataTask.cpp b/launcher/modplatform/EnsureMetadataTask.cpp
new file mode 100644
index 00000000..dc92d8ab
--- /dev/null
+++ b/launcher/modplatform/EnsureMetadataTask.cpp
@@ -0,0 +1,244 @@
+#include "EnsureMetadataTask.h"
+
+#include <MurmurHash2.h>
+#include <QDebug>
+
+#include "FileSystem.h"
+#include "Json.h"
+#include "minecraft/mod/Mod.h"
+#include "minecraft/mod/tasks/LocalModUpdateTask.h"
+#include "modplatform/flame/FlameAPI.h"
+#include "modplatform/flame/FlameModIndex.h"
+#include "modplatform/modrinth/ModrinthAPI.h"
+#include "modplatform/modrinth/ModrinthPackIndex.h"
+#include "net/NetJob.h"
+#include "tasks/MultipleOptionsTask.h"
+
+static ModPlatform::ProviderCapabilities ProviderCaps;
+
+static ModrinthAPI modrinth_api;
+static FlameAPI flame_api;
+
+EnsureMetadataTask::EnsureMetadataTask(Mod& mod, QDir& dir, bool try_all, ModPlatform::Provider prov)
+ : m_mod(mod), m_index_dir(dir), m_provider(prov), m_try_all(try_all)
+{}
+
+bool EnsureMetadataTask::abort()
+{
+ return m_task_handler->abort();
+}
+
+void EnsureMetadataTask::executeTask()
+{
+ // They already have the right metadata :o
+ if (m_mod.status() != ModStatus::NoMetadata && m_mod.metadata() && m_mod.metadata()->provider == m_provider) {
+ emitReady();
+ return;
+ }
+
+ // Folders don't have metadata
+ if (m_mod.type() == Mod::MOD_FOLDER) {
+ emitReady();
+ return;
+ }
+
+ setStatus(tr("Generating %1's metadata...").arg(m_mod.name()));
+ qDebug() << QString("Generating %1's metadata...").arg(m_mod.name());
+
+ QByteArray jar_data;
+
+ try {
+ jar_data = FS::read(m_mod.fileinfo().absoluteFilePath());
+ } catch (FS::FileSystemException& e) {
+ qCritical() << QString("Failed to open / read JAR file of %1").arg(m_mod.name());
+ qCritical() << QString("Reason: ") << e.cause();
+
+ emitFail();
+ return;
+ }
+
+ auto tsk = new MultipleOptionsTask(nullptr, "GetMetadataTask");
+
+ switch (m_provider) {
+ case (ModPlatform::Provider::MODRINTH):
+ modrinthEnsureMetadata(*tsk, jar_data);
+ if (m_try_all)
+ flameEnsureMetadata(*tsk, jar_data);
+
+ break;
+ case (ModPlatform::Provider::FLAME):
+ flameEnsureMetadata(*tsk, jar_data);
+ if (m_try_all)
+ modrinthEnsureMetadata(*tsk, jar_data);
+
+ break;
+ }
+
+ connect(tsk, &MultipleOptionsTask::finished, this, [tsk] { tsk->deleteLater(); });
+ connect(tsk, &MultipleOptionsTask::failed, [this] {
+ qCritical() << QString("Download of %1's metadata failed").arg(m_mod.name());
+
+ emitFail();
+ });
+ connect(tsk, &MultipleOptionsTask::succeeded, this, &EnsureMetadataTask::emitReady);
+
+ m_task_handler = tsk;
+
+ tsk->start();
+}
+
+void EnsureMetadataTask::emitReady()
+{
+ emit metadataReady();
+ emitSucceeded();
+}
+
+void EnsureMetadataTask::emitFail()
+{
+ qDebug() << QString("Failed to generate metadata for %1").arg(m_mod.name());
+ emit metadataFailed();
+ //emitFailed(tr("Failed to generate metadata for %1").arg(m_mod.name()));
+ emitSucceeded();
+}
+
+void EnsureMetadataTask::modrinthEnsureMetadata(SequentialTask& tsk, QByteArray& jar_data)
+{
+ // Modrinth currently garantees that some hash types will always be present.
+ // But let's be sure and cover all cases anyways :)
+ for (auto hash_type : ProviderCaps.hashType(ModPlatform::Provider::MODRINTH)) {
+ auto* response = new QByteArray();
+ auto hash = QString(ProviderCaps.hash(ModPlatform::Provider::MODRINTH, jar_data, hash_type).toHex());
+ auto ver_task = modrinth_api.currentVersion(hash, hash_type, response);
+
+ // Prevents unfortunate timings when aborting the task
+ if (!ver_task)
+ return;
+
+ connect(ver_task.get(), &NetJob::succeeded, this, [this, ver_task, response] {
+ QJsonParseError parse_error{};
+ QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error);
+ if (parse_error.error != QJsonParseError::NoError) {
+ qWarning() << "Error while parsing JSON response from " << m_mod.name() << " at " << parse_error.offset
+ << " reason: " << parse_error.errorString();
+ qWarning() << *response;
+
+ ver_task->failed(parse_error.errorString());
+ return;
+ }
+
+ auto doc_obj = Json::requireObject(doc);
+ auto ver = Modrinth::loadIndexedPackVersion(doc_obj, {}, m_mod.fileinfo().fileName());
+
+ // Minimal IndexedPack to create the metadata
+ ModPlatform::IndexedPack pack;
+ pack.name = m_mod.name();
+ pack.provider = ModPlatform::Provider::MODRINTH;
+ pack.addonId = ver.addonId;
+
+ // Prevent file name mismatch
+ ver.fileName = m_mod.fileinfo().fileName();
+
+ QDir tmp_index_dir(m_index_dir);
+
+ {
+ LocalModUpdateTask update_metadata(m_index_dir, pack, ver);
+ QEventLoop loop;
+ QTimer timeout;
+
+ QObject::connect(&update_metadata, &Task::finished, &loop, &QEventLoop::quit);
+ QObject::connect(&timeout, &QTimer::timeout, &loop, &QEventLoop::quit);
+
+ update_metadata.start();
+ timeout.start(100);
+
+ loop.exec();
+ }
+
+ auto mod_name = m_mod.name();
+ auto meta = new Metadata::ModStruct(Metadata::get(tmp_index_dir, mod_name));
+ m_mod.setMetadata(meta);
+ });
+
+ tsk.addTask(ver_task);
+ }
+}
+
+void EnsureMetadataTask::flameEnsureMetadata(SequentialTask& tsk, QByteArray& jar_data)
+{
+ QByteArray jar_data_treated;
+ for (char c : jar_data) {
+ // CF-specific
+ if (!(c == 9 || c == 10 || c == 13 || c == 32))
+ jar_data_treated.push_back(c);
+ }
+
+ auto* response = new QByteArray();
+
+ std::list<uint> fingerprints;
+ auto murmur = MurmurHash2(jar_data_treated, jar_data_treated.length());
+ fingerprints.push_back(murmur);
+
+ auto ver_task = flame_api.matchFingerprints(fingerprints, response);
+
+ connect(ver_task.get(), &Task::succeeded, this, [this, ver_task, response] {
+ QDir tmp_index_dir(m_index_dir);
+
+ QJsonParseError parse_error{};
+ QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error);
+ if (parse_error.error != QJsonParseError::NoError) {
+ qWarning() << "Error while parsing JSON response from " << m_mod.name() << " at " << parse_error.offset
+ << " reason: " << parse_error.errorString();
+ qWarning() << *response;
+
+ ver_task->failed(parse_error.errorString());
+ return;
+ }
+
+ try {
+ auto doc_obj = Json::requireObject(doc);
+ auto data_obj = Json::ensureObject(doc_obj, "data");
+ auto match_obj = Json::ensureObject(Json::ensureArray(data_obj, "exactMatches")[0], {});
+ if (match_obj.isEmpty()) {
+ qCritical() << "Fingerprint match is empty!";
+
+ ver_task->failed(parse_error.errorString());
+ return;
+ }
+
+ auto file_obj = Json::ensureObject(match_obj, "file");
+
+ ModPlatform::IndexedPack pack;
+ pack.name = m_mod.name();
+ pack.provider = ModPlatform::Provider::FLAME;
+ pack.addonId = Json::requireInteger(file_obj, "modId");
+
+ ModPlatform::IndexedVersion ver = FlameMod::loadIndexedPackVersion(file_obj);
+
+ // Prevent file name mismatch
+ ver.fileName = m_mod.fileinfo().fileName();
+
+ {
+ LocalModUpdateTask update_metadata(m_index_dir, pack, ver);
+ QEventLoop loop;
+ QTimer timeout;
+
+ QObject::connect(&update_metadata, &Task::finished, &loop, &QEventLoop::quit);
+ QObject::connect(&timeout, &QTimer::timeout, &loop, &QEventLoop::quit);
+
+ update_metadata.start();
+ timeout.start(100);
+
+ loop.exec();
+ }
+
+ auto mod_name = m_mod.name();
+ auto meta = new Metadata::ModStruct(Metadata::get(tmp_index_dir, mod_name));
+ m_mod.setMetadata(meta);
+
+ } catch (Json::JsonException& e) {
+ emitFailed(e.cause() + " : " + e.what());
+ }
+ });
+
+ tsk.addTask(ver_task);
+}