pollymc/launcher/modplatform/EnsureMetadataTask.cpp

245 lines
8.0 KiB
C++
Raw Normal View History

#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);
}