refactor ModrinthCreationTask

Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
This commit is contained in:
Trial97 2026-05-08 21:26:10 +03:00
parent c196125daa
commit 9f175a4a4d
No known key found for this signature in database
GPG key ID: 55EF5DA53DB36318
4 changed files with 163 additions and 148 deletions

View file

@ -189,22 +189,6 @@ void BaseInstance::setManagedPack(const QString& type,
m_settings->set("ManagedPackVersionName", version);
}
void BaseInstance::copyManagedPack(BaseInstance& other)
{
m_settings->set("ManagedPack", other.isManagedPack());
m_settings->set("ManagedPackType", other.getManagedPackType());
m_settings->set("ManagedPackID", other.getManagedPackID());
m_settings->set("ManagedPackName", other.getManagedPackName());
m_settings->set("ManagedPackVersionID", other.getManagedPackVersionID());
m_settings->set("ManagedPackVersionName", other.getManagedPackVersionName());
if (APPLICATION->settings()->get("AutomaticJavaSwitch").toBool() && m_settings->get("AutomaticJava").toBool() &&
m_settings->get("OverrideJavaLocation").toBool()) {
m_settings->set("OverrideJavaLocation", false);
m_settings->set("JavaPath", "");
}
}
QStringList BaseInstance::getLinkedInstances() const
{
auto setting = m_settings->get("linkedInstances").toString();

View file

@ -166,7 +166,6 @@ class BaseInstance : public QObject {
QString getManagedPackVersionID() const;
QString getManagedPackVersionName() const;
void setManagedPack(const QString& type, const QString& id, const QString& name, const QString& versionId, const QString& version);
void copyManagedPack(BaseInstance& other);
virtual QStringList extraArguments();

View file

@ -16,7 +16,6 @@
#include "net/ChecksumValidator.h"
#include "net/ApiDownload.h"
#include "net/ApiHeaderProxy.h"
#include "net/NetJob.h"
#include "modplatform/ModIndex.h"
@ -39,10 +38,10 @@ bool ModrinthCreationTask::abort()
if (m_task) {
m_task->abort();
}
return InstanceCreationTask::abort();
return InstanceTask::abort();
}
bool ModrinthCreationTask::updateInstance()
void ModrinthCreationTask::executeTask()
{
auto* instanceList = APPLICATION->instances();
@ -58,28 +57,30 @@ bool ModrinthCreationTask::updateInstance()
inst = instanceList->getInstanceById(originalName());
if (!inst) {
return false;
createInstance();
return;
}
}
}
QString indexPath = FS::PathCombine(m_stagingPath, "modrinth.index.json");
if (!parseManifest(indexPath, m_files, true, false)) {
return false;
return;
}
auto versionName = inst->getManagedPackVersionName();
m_root_path = QFileInfo(inst->gameRoot()).fileName();
m_rootPath = QFileInfo(inst->gameRoot()).fileName();
auto versionStr = !versionName.isEmpty() ? tr(" (version %1)").arg(versionName) : "";
if (shouldConfirmUpdate()) {
auto shouldUpdate = askIfShouldUpdate(m_parent, versionStr);
if (shouldUpdate == ShouldUpdate::SkipUpdating) {
return false;
createInstance();
return;
}
if (shouldUpdate == ShouldUpdate::Cancel) {
m_abort = true;
return false;
emitAborted();
return;
}
}
@ -147,30 +148,28 @@ bool ModrinthCreationTask::updateInstance()
QMessageBox::Warning, QMessageBox::Ok | QMessageBox::Cancel);
if (dialog->exec() == QDialog::DialogCode::Rejected) {
m_abort = true;
return false;
emitAborted();
return;
}
}
setOverride(true, inst->id());
qDebug() << "Will override instance!";
m_instance = inst;
m_oldInstance = inst;
// We let it go through the createInstance() stage, just with a couple modifications for updating
return false;
createInstance();
}
// https://docs.modrinth.com/docs/modpacks/format_definition/
std::unique_ptr<MinecraftInstance> ModrinthCreationTask::createInstance()
void ModrinthCreationTask::createInstance()
{
QEventLoop loop;
QString parentFolder(FS::PathCombine(m_stagingPath, "mrpack"));
QString indexPath = FS::PathCombine(m_stagingPath, "modrinth.index.json");
if (m_files.empty() && !parseManifest(indexPath, m_files, true, true)) {
return nullptr;
return;
}
// Keep index file in case we need it some other time (like when changing versions)
@ -178,7 +177,7 @@ std::unique_ptr<MinecraftInstance> ModrinthCreationTask::createInstance()
FS::ensureFilePathExists(newIndexPlace);
FS::move(indexPath, newIndexPlace);
auto mcPath = FS::PathCombine(m_stagingPath, m_root_path);
auto mcPath = FS::PathCombine(m_stagingPath, m_rootPath);
auto overridePath = FS::PathCombine(m_stagingPath, "overrides");
if (QFile::exists(overridePath)) {
@ -187,8 +186,8 @@ std::unique_ptr<MinecraftInstance> ModrinthCreationTask::createInstance()
// Apply the overrides
if (!FS::move(overridePath, mcPath)) {
setError(tr("Could not rename the overrides folder:\n") + "overrides");
return nullptr;
emitFailed(tr("Could not rename the overrides folder:\n") + "overrides");
return;
}
}
@ -200,85 +199,80 @@ std::unique_ptr<MinecraftInstance> ModrinthCreationTask::createInstance()
// Apply the overrides
if (!FS::overrideFolder(mcPath, clientOverridePath)) {
setError(tr("Could not rename the client overrides folder:\n") + "client overrides");
return nullptr;
emitFailed(tr("Could not rename the client overrides folder:\n") + "client overrides");
return;
}
}
QString configPath = FS::PathCombine(m_stagingPath, "instance.cfg");
auto instanceSettings = std::make_unique<INISettingsObject>(configPath);
auto instance = std::make_unique<MinecraftInstance>(m_globalSettings, std::move(instanceSettings), m_stagingPath);
m_newInstance = std::make_unique<MinecraftInstance>(m_globalSettings, std::move(instanceSettings), m_stagingPath);
auto* components = instance->getPackProfile();
auto* components = m_newInstance->getPackProfile();
components->buildingFromScratch();
components->setComponentVersion("net.minecraft", m_minecraft_version, true);
components->setComponentVersion("net.minecraft", m_minecraftVersion, true);
QString loader;
if (!m_fabric_version.isEmpty()) {
components->setComponentVersion("net.fabricmc.fabric-loader", m_fabric_version);
if (!m_fabricVersion.isEmpty()) {
components->setComponentVersion("net.fabricmc.fabric-loader", m_fabricVersion);
loader = ModPlatform::getModLoaderAsString(ModPlatform::ModLoaderType::Fabric);
}
if (!m_quilt_version.isEmpty()) {
components->setComponentVersion("org.quiltmc.quilt-loader", m_quilt_version);
if (!m_quiltVersion.isEmpty()) {
components->setComponentVersion("org.quiltmc.quilt-loader", m_quiltVersion);
loader = ModPlatform::getModLoaderAsString(ModPlatform::ModLoaderType::Quilt);
}
if (!m_forge_version.isEmpty()) {
components->setComponentVersion("net.minecraftforge", m_forge_version);
if (!m_forgeVersion.isEmpty()) {
components->setComponentVersion("net.minecraftforge", m_forgeVersion);
loader = ModPlatform::getModLoaderAsString(ModPlatform::ModLoaderType::Forge);
}
if (!m_neoForge_version.isEmpty()) {
components->setComponentVersion("net.neoforged", m_neoForge_version);
if (!m_neoForgeVersion.isEmpty()) {
components->setComponentVersion("net.neoforged", m_neoForgeVersion);
loader = ModPlatform::getModLoaderAsString(ModPlatform::ModLoaderType::NeoForge);
}
if (m_instIcon != "default") {
instance->setIconKey(m_instIcon);
} else if (!m_managed_id.isEmpty()) {
instance->setIconKey("modrinth");
m_newInstance->setIconKey(m_instIcon);
} else if (!m_managedId.isEmpty()) {
m_newInstance->setIconKey("modrinth");
}
// Don't add managed info to packs without an ID (most likely imported from ZIP)
if (!m_managed_id.isEmpty()) {
instance->setManagedPack("modrinth", m_managed_id, m_managed_name, m_managed_version_id, version());
} else {
instance->setManagedPack("modrinth", "", name(), "", "");
}
setManagedPack(m_newInstance.get());
instance->setName(name());
instance->saveNow();
m_newInstance->setName(name());
m_newInstance->saveNow();
auto downloadMods = makeShared<NetJob>(tr("Mod Download Modrinth"), APPLICATION->network());
auto rootModpackPath = FS::PathCombine(m_stagingPath, m_root_path);
auto rootModpackPath = FS::PathCombine(m_stagingPath, m_rootPath);
auto rootModpackUrl = QUrl::fromLocalFile(rootModpackPath);
// TODO make this work with other sorts of resource
QHash<QString, Resource*> resources;
for (auto& file : m_files) {
auto fileName = file.path;
fileName = FS::RemoveInvalidPathChars(fileName);
auto filePath = FS::PathCombine(rootModpackPath, fileName);
if (!rootModpackUrl.isParentOf(QUrl::fromLocalFile(filePath))) {
// This means we somehow got out of the root folder, so abort here to prevent exploits
setError(tr("One of the files has a path that leads to an arbitrary location (%1). This is a security risk and isn't allowed.")
.arg(fileName));
return nullptr;
emitFailed(
tr("One of the files has a path that leads to an arbitrary location (%1). This is a security risk and isn't allowed.")
.arg(fileName));
return;
}
if (fileName.startsWith("mods/")) {
auto* mod = new Mod(filePath);
ModDetails d;
d.mod_id = filePath;
mod->setDetails(d);
resources[file.hash.toHex()] = mod;
m_resources[file.hash.toHex()] = mod;
}
if (file.downloads.empty()) {
setError(tr("The file '%1' is missing a download link. This is invalid in the pack format.").arg(fileName));
return nullptr;
emitFailed(tr("The file '%1' is missing a download link. This is invalid in the pack format.").arg(fileName));
return;
}
qDebug() << "Will try to download" << file.downloads.front() << "to" << filePath;
Net::ModrinthDownloadMeta meta{
.reason = m_instance.has_value() ? "update" : "modpack",
.gameVersion = m_minecraft_version,
.reason = m_oldInstance.has_value() ? "update" : "modpack",
.gameVersion = m_minecraftVersion,
.loader = loader,
};
@ -302,14 +296,9 @@ std::unique_ptr<MinecraftInstance> ModrinthCreationTask::createInstance()
}
}
bool endedWell = false;
connect(downloadMods.get(), &NetJob::succeeded, this, [&endedWell]() { endedWell = true; });
connect(downloadMods.get(), &NetJob::failed, [this, &endedWell](const QString& reason) {
endedWell = false;
setError(reason);
});
connect(downloadMods.get(), &NetJob::finished, &loop, &QEventLoop::quit);
connect(downloadMods.get(), &NetJob::succeeded, this, &ModrinthCreationTask::ensureMetaLoop);
connect(downloadMods.get(), &NetJob::failed, this, &ModrinthCreationTask::emitFailed);
connect(downloadMods.get(), &NetJob::aborted, this, &ModrinthCreationTask::emitAborted);
connect(downloadMods.get(), &NetJob::progress, [this](qint64 current, qint64 total) {
setDetails(tr("%1 out of %2 complete").arg(current).arg(total));
setProgress(current, total);
@ -319,57 +308,6 @@ std::unique_ptr<MinecraftInstance> ModrinthCreationTask::createInstance()
setStatus(tr("Downloading mods..."));
downloadMods->start();
m_task = downloadMods;
loop.exec();
if (!endedWell) {
for (auto* resource : resources) {
delete resource;
}
return nullptr;
}
QEventLoop ensureMetaLoop;
QDir folder = FS::PathCombine(instance->modsRoot(), ".index");
auto ensureMetadataTask = makeShared<EnsureMetadataTask>(resources, folder, ModPlatform::ResourceProvider::MODRINTH);
connect(ensureMetadataTask.get(), &Task::succeeded, this, [&endedWell]() { endedWell = true; });
connect(ensureMetadataTask.get(), &Task::finished, &ensureMetaLoop, &QEventLoop::quit);
connect(ensureMetadataTask.get(), &Task::progress, [this](qint64 current, qint64 total) {
setDetails(tr("%1 out of %2 complete").arg(current).arg(total));
setProgress(current, total);
});
connect(ensureMetadataTask.get(), &Task::stepProgress, this, &ModrinthCreationTask::propagateStepProgress);
ensureMetadataTask->start();
m_task = ensureMetadataTask;
ensureMetaLoop.exec();
for (auto* resource : resources) {
delete resource;
}
resources.clear();
// Update information of the already installed instance, if any.
if (m_instance && endedWell) {
setAbortable(false);
auto* inst = m_instance.value();
// Only change the name if it didn't use a custom name, so that the previous custom name
// is preserved, but if we're using the original one, we update the version string.
// NOTE: This needs to come before the copyManagedPack call!
if (inst->name().contains(inst->getManagedPackVersionName()) && inst->name() != instance->name()) {
if (askForChangingInstanceName(m_parent, inst->name(), instance->name()) == InstanceNameChange::ShouldChange) {
inst->setName(instance->name());
}
}
inst->copyManagedPack(*instance);
}
if (endedWell) {
return instance;
}
return nullptr;
}
bool ModrinthCreationTask::parseManifest(const QString& indexPath, std::vector<File>& files, bool setInternalData, bool showOptionalDialog)
@ -385,10 +323,10 @@ bool ModrinthCreationTask::parseManifest(const QString& indexPath, std::vector<F
}
if (setInternalData) {
if (m_managed_version_id.isEmpty()) {
m_managed_version_id = obj["versionId"].toString();
if (m_managedVersionId.isEmpty()) {
m_managedVersionId = obj["versionId"].toString();
}
m_managed_name = obj["name"].toString();
m_managedName = obj["name"].toString();
}
auto jsonFiles = Json::requireIsArrayOf<QJsonObject>(obj, "files", "modrinth.index.json");
@ -470,15 +408,15 @@ bool ModrinthCreationTask::parseManifest(const QString& indexPath, std::vector<F
for (auto it = dependencies.begin(), end = dependencies.end(); it != end; ++it) {
QString name = it.key();
if (name == "minecraft") {
m_minecraft_version = Json::requireString(*it, "Minecraft version");
m_minecraftVersion = Json::requireString(*it, "Minecraft version");
} else if (name == "fabric-loader") {
m_fabric_version = Json::requireString(*it, "Fabric Loader version");
m_fabricVersion = Json::requireString(*it, "Fabric Loader version");
} else if (name == "quilt-loader") {
m_quilt_version = Json::requireString(*it, "Quilt Loader version");
m_quiltVersion = Json::requireString(*it, "Quilt Loader version");
} else if (name == "forge") {
m_forge_version = Json::requireString(*it, "Forge version");
m_forgeVersion = Json::requireString(*it, "Forge version");
} else if (name == "neoforge") {
m_neoForge_version = Json::requireString(*it, "NeoForge version");
m_neoForgeVersion = Json::requireString(*it, "NeoForge version");
} else {
throw JSONValidationError("Unknown dependency type: " + name);
}
@ -489,9 +427,91 @@ bool ModrinthCreationTask::parseManifest(const QString& indexPath, std::vector<F
}
} catch (const JSONValidationError& e) {
setError(tr("Could not understand pack index:\n") + e.cause());
emitFailed(tr("Could not understand pack index:\n") + e.cause());
return false;
}
return true;
}
void ModrinthCreationTask::ensureMetaLoop()
{
QDir folder = FS::PathCombine(m_stagingPath, "minecraft", "jarmods");
auto ensureMetadataTask = makeShared<EnsureMetadataTask>(m_resources, folder, ModPlatform::ResourceProvider::MODRINTH);
connect(ensureMetadataTask.get(), &Task::succeeded, this, &ModrinthCreationTask::finishInstall);
connect(ensureMetadataTask.get(), &Task::failed, this, &ModrinthCreationTask::emitFailed);
connect(ensureMetadataTask.get(), &Task::aborted, this, &ModrinthCreationTask::emitAborted);
connect(ensureMetadataTask.get(), &Task::progress, [this](qint64 current, qint64 total) {
setDetails(tr("%1 out of %2 complete").arg(current).arg(total));
setProgress(current, total);
});
connect(ensureMetadataTask.get(), &Task::stepProgress, this, &ModrinthCreationTask::propagateStepProgress);
ensureMetadataTask->start();
m_task = ensureMetadataTask;
}
ModrinthCreationTask::~ModrinthCreationTask()
{
for (auto* resource : m_resources) {
delete resource;
}
m_resources.clear();
}
void ModrinthCreationTask::setManagedPack(BaseInstance* instance)
{
// Don't add managed info to packs without an ID (most likely imported from ZIP)
if (!m_managedId.isEmpty()) {
instance->setManagedPack("modrinth", m_managedId, m_managedName, m_managedVersionId, version());
} else {
instance->setManagedPack("modrinth", "", name(), "", "");
}
}
void ModrinthCreationTask::finishInstall()
{
// Update information of the already installed instance, if any.
if (m_oldInstance) {
setAbortable(false);
auto* inst = m_oldInstance.value();
// Only change the name if it didn't use a custom name, so that the previous custom name
// is preserved, but if we're using the original one, we update the version string.
// NOTE: This needs to come before the setManagedPack call!
if (inst->name().contains(inst->getManagedPackVersionName()) && inst->name() != name()) {
if (askForChangingInstanceName(m_parent, inst->name(), name()) == InstanceNameChange::ShouldChange) {
inst->setName(name());
}
}
setManagedPack(m_oldInstance.value());
}
if (shouldOverride()) {
bool deleteFailed = false;
setAbortable(false);
setStatus(tr("Removing old conflicting files..."));
qDebug() << "Removing old files";
for (const QString& path : m_filesToRemove) {
if (!QFile::exists(path)) {
continue;
}
qDebug() << "Removing" << path;
if (!QFile::remove(path)) {
qCritical() << "Could not remove" << path;
deleteFailed = true;
}
}
if (deleteFailed) {
emitFailed(tr("Failed to remove old conflicting files."));
return;
}
}
downloadFiles(m_newInstance.get());
}

View file

@ -10,9 +10,11 @@
#include <QVector>
#include "BaseInstance.h"
#include "InstanceCreationTask.h"
#include "InstanceTask.h"
class ModrinthCreationTask final : public InstanceCreationTask {
class Resource;
class ModrinthCreationTask final : public InstanceTask {
Q_OBJECT
struct File {
QString path;
@ -30,32 +32,42 @@ class ModrinthCreationTask final : public InstanceCreationTask {
QString id,
QString versionId = {},
QString originalInstanceId = {})
: m_parent(parent), m_managed_id(std::move(id)), m_managed_version_id(std::move(versionId))
: m_parent(parent), m_managedId(std::move(id)), m_managedVersionId(std::move(versionId))
{
setStagingPath(stagingPath);
setParentSettings(globalSettings);
m_originalInstanceId = std::move(originalInstanceId);
}
~ModrinthCreationTask() override;
bool abort() override;
bool updateInstance() override;
std::unique_ptr<MinecraftInstance> createInstance() override;
void createInstance();
void executeTask() override;
private slots:
void finishInstall();
private:
bool parseManifest(const QString&, std::vector<File>&, bool setInternalData = true, bool showOptionalDialog = true);
void ensureMetaLoop();
void setManagedPack(BaseInstance* instance);
private:
QWidget* m_parent = nullptr;
QString m_minecraft_version, m_fabric_version, m_quilt_version, m_forge_version, m_neoForge_version;
QString m_managed_id, m_managed_version_id, m_managed_name;
QString m_minecraftVersion, m_fabricVersion, m_quiltVersion, m_forgeVersion, m_neoForgeVersion;
QString m_managedId, m_managedVersionId, m_managedName;
std::vector<File> m_files;
Task::Ptr m_task;
std::optional<BaseInstance*> m_instance;
std::optional<BaseInstance*> m_oldInstance;
std::unique_ptr<MinecraftInstance> m_newInstance{};
QString m_root_path = "minecraft";
QString m_rootPath = "minecraft";
QHash<QString, Resource*> m_resources;
};