From 5450e0edf305090c2cab81e335c8d5366d7f1f13 Mon Sep 17 00:00:00 2001 From: flow Date: Tue, 13 Dec 2022 13:43:27 -0300 Subject: fix(Inst.Import): don't set managed pack info from imported ZIPs This prevents the Managed Pack page from showing up even though there's no way for it to work correctly. Signed-off-by: flow --- launcher/modplatform/flame/FlameInstanceCreationTask.cpp | 4 +++- launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) (limited to 'launcher/modplatform') diff --git a/launcher/modplatform/flame/FlameInstanceCreationTask.cpp b/launcher/modplatform/flame/FlameInstanceCreationTask.cpp index 729268d7..1d441f09 100644 --- a/launcher/modplatform/flame/FlameInstanceCreationTask.cpp +++ b/launcher/modplatform/flame/FlameInstanceCreationTask.cpp @@ -361,7 +361,9 @@ bool FlameCreationTask::createInstance() FS::deletePath(jarmodsPath); } - instance.setManagedPack("flame", m_managed_id, m_pack.name, m_managed_version_id, m_pack.version); + // Don't add managed info to packs without an ID (most likely imported from ZIP) + if (!m_managed_id.isEmpty()) + instance.setManagedPack("flame", m_managed_id, m_pack.name, m_managed_version_id, m_pack.version); instance.setName(name()); m_mod_id_resolver = new Flame::FileResolvingTask(APPLICATION->network(), m_pack); diff --git a/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp b/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp index 1c0e8979..5632f6a3 100644 --- a/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp +++ b/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp @@ -217,7 +217,9 @@ bool ModrinthCreationTask::createInstance() instance.setIconKey("modrinth"); } - instance.setManagedPack("modrinth", m_managed_id, m_managed_name, m_managed_version_id, version()); + // 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()); instance.setName(name()); instance.saveNow(); -- cgit From 22aebc22159196a83f7f1447a951717b4359f528 Mon Sep 17 00:00:00 2001 From: flow Date: Sat, 17 Dec 2022 12:34:27 -0300 Subject: fix(Inst. Import): correctly set component versions when updating This makes it so that the later call to parse the old manifest doesn't change the class data, so that the new data con continue there and be reflected on the component list later. Co-authored-by: Sefa Eyeoglu Signed-off-by: flow --- .../modrinth/ModrinthInstanceCreationTask.cpp | 32 ++++++++++++---------- .../modrinth/ModrinthInstanceCreationTask.h | 2 +- 2 files changed, 18 insertions(+), 16 deletions(-) (limited to 'launcher/modplatform') diff --git a/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp b/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp index 5632f6a3..4f63ca82 100644 --- a/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp +++ b/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp @@ -279,7 +279,7 @@ bool ModrinthCreationTask::createInstance() return ended_well; } -bool ModrinthCreationTask::parseManifest(const QString& index_path, std::vector& files, bool set_managed_info, bool show_optional_dialog) +bool ModrinthCreationTask::parseManifest(const QString& index_path, std::vector& files, bool set_internal_data, bool show_optional_dialog) { try { auto doc = Json::requireDocument(index_path); @@ -291,7 +291,7 @@ bool ModrinthCreationTask::parseManifest(const QString& index_path, std::vector< throw JSONValidationError("Unknown game: " + game); } - if (set_managed_info) { + if (set_internal_data) { if (m_managed_version_id.isEmpty()) m_managed_version_id = Json::ensureString(obj, "versionId", {}, "Managed ID"); m_managed_name = Json::ensureString(obj, "name", {}, "Managed Name"); @@ -367,19 +367,21 @@ bool ModrinthCreationTask::parseManifest(const QString& index_path, std::vector< files.push_back(file); } - auto dependencies = Json::requireObject(obj, "dependencies", "modrinth.index.json"); - for (auto it = dependencies.begin(), end = dependencies.end(); it != end; ++it) { - QString name = it.key(); - if (name == "minecraft") { - minecraftVersion = Json::requireString(*it, "Minecraft version"); - } else if (name == "fabric-loader") { - fabricVersion = Json::requireString(*it, "Fabric Loader version"); - } else if (name == "quilt-loader") { - quiltVersion = Json::requireString(*it, "Quilt Loader version"); - } else if (name == "forge") { - forgeVersion = Json::requireString(*it, "Forge version"); - } else { - throw JSONValidationError("Unknown dependency type: " + name); + if (set_internal_data) { + auto dependencies = Json::requireObject(obj, "dependencies", "modrinth.index.json"); + for (auto it = dependencies.begin(), end = dependencies.end(); it != end; ++it) { + QString name = it.key(); + if (name == "minecraft") { + minecraftVersion = Json::requireString(*it, "Minecraft version"); + } else if (name == "fabric-loader") { + fabricVersion = Json::requireString(*it, "Fabric Loader version"); + } else if (name == "quilt-loader") { + quiltVersion = Json::requireString(*it, "Quilt Loader version"); + } else if (name == "forge") { + forgeVersion = Json::requireString(*it, "Forge version"); + } else { + throw JSONValidationError("Unknown dependency type: " + name); + } } } } else { diff --git a/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.h b/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.h index 122fc5ce..5cf295f4 100644 --- a/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.h +++ b/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.h @@ -37,7 +37,7 @@ class ModrinthCreationTask final : public InstanceCreationTask { bool createInstance() override; private: - bool parseManifest(const QString&, std::vector&, bool set_managed_info = true, bool show_optional_dialog = true); + bool parseManifest(const QString&, std::vector&, bool set_internal_data = true, bool show_optional_dialog = true); private: QWidget* m_parent = nullptr; -- cgit From e3f8d9908747863b9e5870b9a3d9c79f3407997f Mon Sep 17 00:00:00 2001 From: flow Date: Sat, 17 Dec 2022 12:41:10 -0300 Subject: refactor(Inst. Import): use m_* for member variables in MR components Makes it clearer what is being changed when. Signed-off-by: flow --- .../modrinth/ModrinthInstanceCreationTask.cpp | 22 +++++++++++----------- .../modrinth/ModrinthInstanceCreationTask.h | 2 +- 2 files changed, 12 insertions(+), 12 deletions(-) (limited to 'launcher/modplatform') diff --git a/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp b/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp index 4f63ca82..c5a27c9d 100644 --- a/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp +++ b/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp @@ -202,14 +202,14 @@ bool ModrinthCreationTask::createInstance() auto components = instance.getPackProfile(); components->buildingFromScratch(); - components->setComponentVersion("net.minecraft", minecraftVersion, true); + components->setComponentVersion("net.minecraft", m_minecraft_version, true); - if (!fabricVersion.isEmpty()) - components->setComponentVersion("net.fabricmc.fabric-loader", fabricVersion); - if (!quiltVersion.isEmpty()) - components->setComponentVersion("org.quiltmc.quilt-loader", quiltVersion); - if (!forgeVersion.isEmpty()) - components->setComponentVersion("net.minecraftforge", forgeVersion); + if (!m_fabric_version.isEmpty()) + components->setComponentVersion("net.fabricmc.fabric-loader", m_fabric_version); + if (!m_quilt_version.isEmpty()) + components->setComponentVersion("org.quiltmc.quilt-loader", m_quilt_version); + if (!m_forge_version.isEmpty()) + components->setComponentVersion("net.minecraftforge", m_forge_version); if (m_instIcon != "default") { instance.setIconKey(m_instIcon); @@ -372,13 +372,13 @@ bool ModrinthCreationTask::parseManifest(const QString& index_path, std::vector< for (auto it = dependencies.begin(), end = dependencies.end(); it != end; ++it) { QString name = it.key(); if (name == "minecraft") { - minecraftVersion = Json::requireString(*it, "Minecraft version"); + m_minecraft_version = Json::requireString(*it, "Minecraft version"); } else if (name == "fabric-loader") { - fabricVersion = Json::requireString(*it, "Fabric Loader version"); + m_fabric_version = Json::requireString(*it, "Fabric Loader version"); } else if (name == "quilt-loader") { - quiltVersion = Json::requireString(*it, "Quilt Loader version"); + m_quilt_version = Json::requireString(*it, "Quilt Loader version"); } else if (name == "forge") { - forgeVersion = Json::requireString(*it, "Forge version"); + m_forge_version = Json::requireString(*it, "Forge version"); } else { throw JSONValidationError("Unknown dependency type: " + name); } diff --git a/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.h b/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.h index 5cf295f4..6de24fd4 100644 --- a/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.h +++ b/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.h @@ -42,7 +42,7 @@ class ModrinthCreationTask final : public InstanceCreationTask { private: QWidget* m_parent = nullptr; - QString minecraftVersion, fabricVersion, quiltVersion, forgeVersion; + QString m_minecraft_version, m_fabric_version, m_quilt_version, m_forge_version; QString m_managed_id, m_managed_version_id, m_managed_name; std::vector m_files; -- cgit From 8422e3ac01c861125fd6aea441714a2fb38e5ff9 Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Sat, 24 Dec 2022 20:38:29 -0700 Subject: feat: zip resource validation check for flame Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- launcher/minecraft/mod/DataPack.cpp | 2 +- launcher/minecraft/mod/ResourcePack.cpp | 2 +- .../flame/FlameInstanceCreationTask.cpp | 147 ++++++++++++++++----- .../modplatform/flame/FlameInstanceCreationTask.h | 3 + 4 files changed, 120 insertions(+), 34 deletions(-) (limited to 'launcher/modplatform') diff --git a/launcher/minecraft/mod/DataPack.cpp b/launcher/minecraft/mod/DataPack.cpp index 6c333285..ea1d097b 100644 --- a/launcher/minecraft/mod/DataPack.cpp +++ b/launcher/minecraft/mod/DataPack.cpp @@ -41,7 +41,7 @@ void DataPack::setPackFormat(int new_format_id) QMutexLocker locker(&m_data_lock); if (!s_pack_format_versions.contains(new_format_id)) { - qWarning() << "Pack format '%1' is not a recognized resource pack id!"; + qWarning() << "Pack format '" << new_format_id << "' is not a recognized data pack id!"; } m_pack_format = new_format_id; diff --git a/launcher/minecraft/mod/ResourcePack.cpp b/launcher/minecraft/mod/ResourcePack.cpp index 47da4fea..87995215 100644 --- a/launcher/minecraft/mod/ResourcePack.cpp +++ b/launcher/minecraft/mod/ResourcePack.cpp @@ -27,7 +27,7 @@ void ResourcePack::setPackFormat(int new_format_id) QMutexLocker locker(&m_data_lock); if (!s_pack_format_versions.contains(new_format_id)) { - qWarning() << "Pack format '%1' is not a recognized resource pack id!"; + qWarning() << "Pack format '" << new_format_id << "' is not a recognized resource pack id!"; } m_pack_format = new_format_id; diff --git a/launcher/modplatform/flame/FlameInstanceCreationTask.cpp b/launcher/modplatform/flame/FlameInstanceCreationTask.cpp index 1d441f09..2b1bc8d0 100644 --- a/launcher/modplatform/flame/FlameInstanceCreationTask.cpp +++ b/launcher/modplatform/flame/FlameInstanceCreationTask.cpp @@ -53,6 +53,13 @@ #include "ui/dialogs/BlockedModsDialog.h" #include "ui/dialogs/CustomMessageBox.h" +#include +#include +#include +#include +#include +#include + const static QMap forgemap = { { "1.2.5", "3.4.9.171" }, { "1.4.2", "6.0.1.355" }, { "1.4.7", "6.6.2.534" }, @@ -401,6 +408,11 @@ void FlameCreationTask::idResolverSucceeded(QEventLoop& loop) QList blocked_mods; auto anyBlocked = false; for (const auto& result : results.files.values()) { + + if(result.fileName.endsWith(".zip")) { + m_ZIP_resources.append(std::make_pair(result.fileName, result.targetFolder)); + } + if (!result.resolved || result.url.isEmpty()) { BlockedMod blocked_mod; blocked_mod.name = result.fileName; @@ -439,37 +451,6 @@ void FlameCreationTask::idResolverSucceeded(QEventLoop& loop) } } -/// @brief copy the matched blocked mods to the instance staging area -/// @param blocked_mods list of the blocked mods and their matched paths -void FlameCreationTask::copyBlockedMods(QList const& blocked_mods) -{ - setStatus(tr("Copying Blocked Mods...")); - setAbortable(false); - int i = 0; - int total = blocked_mods.length(); - setProgress(i, total); - for (auto const& mod : blocked_mods) { - if (!mod.matched) { - qDebug() << mod.name << "was not matched to a local file, skipping copy"; - continue; - } - - auto dest_path = FS::PathCombine(m_stagingPath, "minecraft", mod.targetFolder, mod.name); - - setStatus(tr("Copying Blocked Mods (%1 out of %2 are done)").arg(QString::number(i), QString::number(total))); - - qDebug() << "Will try to copy" << mod.localPath << "to" << dest_path; - - if (!FS::copy(mod.localPath, dest_path)()) { - qDebug() << "Copy of" << mod.localPath << "to" << dest_path << "Failed"; - } - - i++; - setProgress(i, total); - } - - setAbortable(true); -} void FlameCreationTask::setupDownloadJob(QEventLoop& loop) { @@ -509,7 +490,10 @@ void FlameCreationTask::setupDownloadJob(QEventLoop& loop) } m_mod_id_resolver.reset(); - connect(m_files_job.get(), &NetJob::succeeded, this, [&]() { m_files_job.reset(); }); + connect(m_files_job.get(), &NetJob::succeeded, this, [&]() { + m_files_job.reset(); + validateZIPResouces(); + }); connect(m_files_job.get(), &NetJob::failed, [&](QString reason) { m_files_job.reset(); setError(reason); @@ -520,3 +504,102 @@ void FlameCreationTask::setupDownloadJob(QEventLoop& loop) setStatus(tr("Downloading mods...")); m_files_job->start(); } + +/// @brief copy the matched blocked mods to the instance staging area +/// @param blocked_mods list of the blocked mods and their matched paths +void FlameCreationTask::copyBlockedMods(QList const& blocked_mods) +{ + setStatus(tr("Copying Blocked Mods...")); + setAbortable(false); + int i = 0; + int total = blocked_mods.length(); + setProgress(i, total); + for (auto const& mod : blocked_mods) { + if (!mod.matched) { + qDebug() << mod.name << "was not matched to a local file, skipping copy"; + continue; + } + + auto destPath = FS::PathCombine(m_stagingPath, "minecraft", mod.targetFolder, mod.name); + + setStatus(tr("Copying Blocked Mods (%1 out of %2 are done)").arg(QString::number(i), QString::number(total))); + + qDebug() << "Will try to copy" << mod.localPath << "to" << destPath; + + if (!FS::copy(mod.localPath, destPath)()) { + qDebug() << "Copy of" << mod.localPath << "to" << destPath << "Failed"; + } + + i++; + setProgress(i, total); + } + + setAbortable(true); +} + +static bool moveFile(QString src, QString dst) +{ + if (!FS::copy(src, dst)()) { // copy + qDebug() << "Copy of" << src << "to" << dst << "Failed!"; + return false; + } else { + if (!FS::deletePath(src)) { // remove origonal + qDebug() << "Deleation of" << src << "Failed!"; + return false; + }; + } + return true; +} + +void FlameCreationTask::validateZIPResouces() +{ + qDebug() << "Validating resoucres stored as .zip are in the right place"; + for (auto [fileName, targetFolder] : m_ZIP_resources) { + qDebug() << "Checking" << fileName << "..."; + auto localPath = FS::PathCombine(m_stagingPath, "minecraft", targetFolder, fileName); + QFileInfo localFileInfo(localPath); + if (localFileInfo.exists() && localFileInfo.isFile()) { + if (ResourcePackUtils::validate(localFileInfo)) { + if (targetFolder != "resourcepacks") { + qDebug() << "Target folder of" << fileName << "is incorrect, it's a resource pack."; + auto destPath = FS::PathCombine(m_stagingPath, "minecraft", "resourcepacks", fileName); + qDebug() << "Moveing" << localPath << "to" << destPath; + moveFile(localPath, destPath); + } else { + qDebug() << fileName << "is in the right place :" << targetFolder; + } + } else if (TexturePackUtils::validate(localFileInfo)) { + if (targetFolder != "texturepacks") { + qDebug() << "Target folder of" << fileName << "is incorrect, it's a pre 1.6 texture pack."; + auto destPath = FS::PathCombine(m_stagingPath, "minecraft", "texturepacks", fileName); + qDebug() << "Moveing" << localPath << "to" << destPath; + moveFile(localPath, destPath); + } else { + qDebug() << fileName << "is in the right place :" << targetFolder; + } + } else if (DataPackUtils::validate(localFileInfo)) { + if (targetFolder != "datapacks") { + qDebug() << "Target folder of" << fileName << "is incorrect, it's a data pack."; + auto destPath = FS::PathCombine(m_stagingPath, "minecraft", "datapacks", fileName); + qDebug() << "Moveing" << localPath << "to" << destPath; + moveFile(localPath, destPath); + } else { + qDebug() << fileName << "is in the right place :" << targetFolder; + } + } else if (ModUtils::validate(localFileInfo)) { + if (targetFolder != "mods") { + qDebug() << "Target folder of" << fileName << "is incorrect, it's a mod."; + auto destPath = FS::PathCombine(m_stagingPath, "minecraft", "mods", fileName); + qDebug() << "Moveing" << localPath << "to" << destPath; + moveFile(localPath, destPath); + } else { + qDebug() << fileName << "is in the right place :" << targetFolder; + } + } else { + qDebug() << "Can't Identify" << fileName << "at" << localPath << ", leaving it where it is."; + } + } else { + qDebug() << "Can't find" << localPath << "to validate it, ignoreing"; + } + } +} diff --git a/launcher/modplatform/flame/FlameInstanceCreationTask.h b/launcher/modplatform/flame/FlameInstanceCreationTask.h index 3a1c729f..498e1d6e 100644 --- a/launcher/modplatform/flame/FlameInstanceCreationTask.h +++ b/launcher/modplatform/flame/FlameInstanceCreationTask.h @@ -77,6 +77,7 @@ class FlameCreationTask final : public InstanceCreationTask { void idResolverSucceeded(QEventLoop&); void setupDownloadJob(QEventLoop&); void copyBlockedMods(QList const& blocked_mods); + void validateZIPResouces(); private: QWidget* m_parent = nullptr; @@ -90,5 +91,7 @@ class FlameCreationTask final : public InstanceCreationTask { QString m_managed_id, m_managed_version_id; + QList> m_ZIP_resources; + std::optional m_instance; }; -- cgit From 78984eea3aa398451dc511712ccb7ec55f93194c Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Sun, 25 Dec 2022 16:49:56 -0700 Subject: feat: support installing worlds during flame pack import. Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- .../flame/FlameInstanceCreationTask.cpp | 72 ++++++++++++---------- 1 file changed, 39 insertions(+), 33 deletions(-) (limited to 'launcher/modplatform') diff --git a/launcher/modplatform/flame/FlameInstanceCreationTask.cpp b/launcher/modplatform/flame/FlameInstanceCreationTask.cpp index 2b1bc8d0..204d5c1f 100644 --- a/launcher/modplatform/flame/FlameInstanceCreationTask.cpp +++ b/launcher/modplatform/flame/FlameInstanceCreationTask.cpp @@ -57,6 +57,9 @@ #include #include #include +#include +#include +#include #include #include @@ -537,13 +540,13 @@ void FlameCreationTask::copyBlockedMods(QList const& blocked_mods) setAbortable(true); } -static bool moveFile(QString src, QString dst) +bool moveFile(QString src, QString dst) { if (!FS::copy(src, dst)()) { // copy qDebug() << "Copy of" << src << "to" << dst << "Failed!"; return false; } else { - if (!FS::deletePath(src)) { // remove origonal + if (!FS::deletePath(src)) { // remove original qDebug() << "Deleation of" << src << "Failed!"; return false; }; @@ -551,50 +554,53 @@ static bool moveFile(QString src, QString dst) return true; } + void FlameCreationTask::validateZIPResouces() { qDebug() << "Validating resoucres stored as .zip are in the right place"; for (auto [fileName, targetFolder] : m_ZIP_resources) { + qDebug() << "Checking" << fileName << "..."; auto localPath = FS::PathCombine(m_stagingPath, "minecraft", targetFolder, fileName); + + auto validatePath = [&localPath, this](QString fileName, QString targetFolder, QString realTarget) { + if (targetFolder != "resourcepacks") { + qDebug() << "Target folder of" << fileName << "is incorrect, it's a resource pack."; + auto destPath = FS::PathCombine(m_stagingPath, "minecraft", "resourcepacks", fileName); + qDebug() << "Moving" << localPath << "to" << destPath; + if (moveFile(localPath, destPath)) { + return destPath; + } + } else { + qDebug() << fileName << "is in the right place :" << targetFolder; + } + return localPath; + }; + QFileInfo localFileInfo(localPath); if (localFileInfo.exists() && localFileInfo.isFile()) { if (ResourcePackUtils::validate(localFileInfo)) { - if (targetFolder != "resourcepacks") { - qDebug() << "Target folder of" << fileName << "is incorrect, it's a resource pack."; - auto destPath = FS::PathCombine(m_stagingPath, "minecraft", "resourcepacks", fileName); - qDebug() << "Moveing" << localPath << "to" << destPath; - moveFile(localPath, destPath); - } else { - qDebug() << fileName << "is in the right place :" << targetFolder; - } + validatePath(fileName, targetFolder, "resourcepacks"); } else if (TexturePackUtils::validate(localFileInfo)) { - if (targetFolder != "texturepacks") { - qDebug() << "Target folder of" << fileName << "is incorrect, it's a pre 1.6 texture pack."; - auto destPath = FS::PathCombine(m_stagingPath, "minecraft", "texturepacks", fileName); - qDebug() << "Moveing" << localPath << "to" << destPath; - moveFile(localPath, destPath); - } else { - qDebug() << fileName << "is in the right place :" << targetFolder; - } + validatePath(fileName, targetFolder, "texturepacks"); } else if (DataPackUtils::validate(localFileInfo)) { - if (targetFolder != "datapacks") { - qDebug() << "Target folder of" << fileName << "is incorrect, it's a data pack."; - auto destPath = FS::PathCombine(m_stagingPath, "minecraft", "datapacks", fileName); - qDebug() << "Moveing" << localPath << "to" << destPath; - moveFile(localPath, destPath); - } else { - qDebug() << fileName << "is in the right place :" << targetFolder; - } + validatePath(fileName, targetFolder, "datapacks"); } else if (ModUtils::validate(localFileInfo)) { - if (targetFolder != "mods") { - qDebug() << "Target folder of" << fileName << "is incorrect, it's a mod."; - auto destPath = FS::PathCombine(m_stagingPath, "minecraft", "mods", fileName); - qDebug() << "Moveing" << localPath << "to" << destPath; - moveFile(localPath, destPath); + validatePath(fileName, targetFolder, "mods"); + } else if (WorldSaveUtils::validate(localFileInfo)) { + QString worldPath = validatePath(fileName, targetFolder, "saves"); + + qDebug() << "Installing World from" << worldPath; + World w(worldPath); + if (!w.isValid()) { + qDebug() << "World at" << worldPath << "is not valid, skipping install."; } else { - qDebug() << fileName << "is in the right place :" << targetFolder; - } + w.install(FS::PathCombine(m_stagingPath, "minecraft", "saves")); + } + } else if (ShaderPackUtils::validate(localFileInfo)) { + // in theroy flame API can't do this but who knows, that *may* change ? + // better to handle it if it *does* occure in the future + validatePath(fileName, targetFolder, "shaderpacks"); } else { qDebug() << "Can't Identify" << fileName << "at" << localPath << ", leaving it where it is."; } -- cgit From b2082bfde7149a5596fe8a467659699ad569f932 Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Sun, 25 Dec 2022 17:16:26 -0700 Subject: fix: explicit QFileInfo converison for qt6 fix: validatePath in validateZIPResouces Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- .../modplatform/flame/FlameInstanceCreationTask.cpp | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) (limited to 'launcher/modplatform') diff --git a/launcher/modplatform/flame/FlameInstanceCreationTask.cpp b/launcher/modplatform/flame/FlameInstanceCreationTask.cpp index 204d5c1f..b62d05ab 100644 --- a/launcher/modplatform/flame/FlameInstanceCreationTask.cpp +++ b/launcher/modplatform/flame/FlameInstanceCreationTask.cpp @@ -564,15 +564,13 @@ void FlameCreationTask::validateZIPResouces() auto localPath = FS::PathCombine(m_stagingPath, "minecraft", targetFolder, fileName); auto validatePath = [&localPath, this](QString fileName, QString targetFolder, QString realTarget) { - if (targetFolder != "resourcepacks") { - qDebug() << "Target folder of" << fileName << "is incorrect, it's a resource pack."; - auto destPath = FS::PathCombine(m_stagingPath, "minecraft", "resourcepacks", fileName); + if (targetFolder != realTarget) { + qDebug() << "Target folder of" << fileName << "is incorrect, it belongs in" << realTarget; + auto destPath = FS::PathCombine(m_stagingPath, "minecraft", realTarget, fileName); qDebug() << "Moving" << localPath << "to" << destPath; if (moveFile(localPath, destPath)) { return destPath; } - } else { - qDebug() << fileName << "is in the right place :" << targetFolder; } return localPath; }; @@ -580,18 +578,24 @@ void FlameCreationTask::validateZIPResouces() QFileInfo localFileInfo(localPath); if (localFileInfo.exists() && localFileInfo.isFile()) { if (ResourcePackUtils::validate(localFileInfo)) { + qDebug() << fileName << "is a resource pack"; validatePath(fileName, targetFolder, "resourcepacks"); } else if (TexturePackUtils::validate(localFileInfo)) { + qDebug() << fileName << "is a pre 1.6 texture pack"; validatePath(fileName, targetFolder, "texturepacks"); } else if (DataPackUtils::validate(localFileInfo)) { + qDebug() << fileName << "is a data pack"; validatePath(fileName, targetFolder, "datapacks"); } else if (ModUtils::validate(localFileInfo)) { + qDebug() << fileName << "is a mod"; validatePath(fileName, targetFolder, "mods"); } else if (WorldSaveUtils::validate(localFileInfo)) { + qDebug() << fileName << "is a world save"; QString worldPath = validatePath(fileName, targetFolder, "saves"); qDebug() << "Installing World from" << worldPath; - World w(worldPath); + QFileInfo worldFileInfo(worldPath); + World w(worldFileInfo); if (!w.isValid()) { qDebug() << "World at" << worldPath << "is not valid, skipping install."; } else { @@ -600,6 +604,7 @@ void FlameCreationTask::validateZIPResouces() } else if (ShaderPackUtils::validate(localFileInfo)) { // in theroy flame API can't do this but who knows, that *may* change ? // better to handle it if it *does* occure in the future + qDebug() << fileName << "is a shader pack"; validatePath(fileName, targetFolder, "shaderpacks"); } else { qDebug() << "Can't Identify" << fileName << "at" << localPath << ", leaving it where it is."; -- cgit From 3691f3a2963c77dbd7b469b4b90ca79b61014d43 Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Mon, 26 Dec 2022 14:29:13 -0700 Subject: fix: cleanup and suggested changes Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- launcher/minecraft/mod/DataPack.cpp | 6 +-- launcher/minecraft/mod/DataPack.h | 8 +-- launcher/minecraft/mod/Mod.cpp | 2 +- launcher/minecraft/mod/ResourcePack.cpp | 11 ++-- launcher/minecraft/mod/ShaderPack.cpp | 2 - launcher/minecraft/mod/ShaderPack.h | 24 ++++----- launcher/minecraft/mod/WorldSave.cpp | 6 +-- launcher/minecraft/mod/WorldSave.h | 14 ++--- .../minecraft/mod/tasks/LocalDataPackParseTask.cpp | 46 ++++++++++------- .../minecraft/mod/tasks/LocalDataPackParseTask.h | 2 +- launcher/minecraft/mod/tasks/LocalModParseTask.cpp | 36 ++++++------- launcher/minecraft/mod/tasks/LocalModParseTask.h | 15 +++--- .../mod/tasks/LocalResourcePackParseTask.cpp | 60 ++++++++++++++-------- .../mod/tasks/LocalShaderPackParseTask.cpp | 23 ++++----- .../minecraft/mod/tasks/LocalShaderPackParseTask.h | 3 +- .../mod/tasks/LocalWorldSaveParseTask.cpp | 56 +++++++++++--------- .../minecraft/mod/tasks/LocalWorldSaveParseTask.h | 2 +- .../flame/FlameInstanceCreationTask.cpp | 43 ++++++++-------- tests/ResourcePackParse_test.cpp | 2 +- 19 files changed, 187 insertions(+), 174 deletions(-) (limited to 'launcher/modplatform') diff --git a/launcher/minecraft/mod/DataPack.cpp b/launcher/minecraft/mod/DataPack.cpp index ea1d097b..5c58f6b2 100644 --- a/launcher/minecraft/mod/DataPack.cpp +++ b/launcher/minecraft/mod/DataPack.cpp @@ -30,9 +30,9 @@ // Values taken from: // https://minecraft.fandom.com/wiki/Tutorials/Creating_a_data_pack#%22pack_format%22 static const QMap> s_pack_format_versions = { - { 4, { Version("1.13"), Version("1.14.4") } }, { 5, { Version("1.15"), Version("1.16.1") } }, - { 6, { Version("1.16.2"), Version("1.16.5") } }, { 7, { Version("1.17"), Version("1.17.1") } }, - { 8, { Version("1.18"), Version("1.18.1") } }, { 9, { Version("1.18.2"), Version("1.18.2") } }, + { 4, { Version("1.13"), Version("1.14.4") } }, { 5, { Version("1.15"), Version("1.16.1") } }, + { 6, { Version("1.16.2"), Version("1.16.5") } }, { 7, { Version("1.17"), Version("1.17.1") } }, + { 8, { Version("1.18"), Version("1.18.1") } }, { 9, { Version("1.18.2"), Version("1.18.2") } }, { 10, { Version("1.19"), Version("1.19.3") } }, }; diff --git a/launcher/minecraft/mod/DataPack.h b/launcher/minecraft/mod/DataPack.h index 17d9b65e..fc2703c7 100644 --- a/launcher/minecraft/mod/DataPack.h +++ b/launcher/minecraft/mod/DataPack.h @@ -45,7 +45,7 @@ class DataPack : public Resource { /** Gets, respectively, the lower and upper versions supported by the set pack format. */ [[nodiscard]] std::pair compatibleVersions() const; - /** Gets the description of the resource pack. */ + /** Gets the description of the data pack. */ [[nodiscard]] QString description() const { return m_description; } /** Thread-safe. */ @@ -62,12 +62,12 @@ class DataPack : public Resource { protected: mutable QMutex m_data_lock; - /* The 'version' of a resource pack, as defined in the pack.mcmeta file. - * See https://minecraft.fandom.com/wiki/Tutorials/Creating_a_resource_pack#Formatting_pack.mcmeta + /* The 'version' of a data pack, as defined in the pack.mcmeta file. + * See https://minecraft.fandom.com/wiki/Data_pack#pack.mcmeta */ int m_pack_format = 0; - /** The resource pack's description, as defined in the pack.mcmeta file. + /** The data pack's description, as defined in the pack.mcmeta file. */ QString m_description; }; diff --git a/launcher/minecraft/mod/Mod.cpp b/launcher/minecraft/mod/Mod.cpp index 8b00354d..3439b6ee 100644 --- a/launcher/minecraft/mod/Mod.cpp +++ b/launcher/minecraft/mod/Mod.cpp @@ -199,4 +199,4 @@ void Mod::finishResolvingWithDetails(ModDetails&& details) bool Mod::valid() const { return !m_local_details.mod_id.isEmpty(); -} \ No newline at end of file +} diff --git a/launcher/minecraft/mod/ResourcePack.cpp b/launcher/minecraft/mod/ResourcePack.cpp index 87995215..876d5c3e 100644 --- a/launcher/minecraft/mod/ResourcePack.cpp +++ b/launcher/minecraft/mod/ResourcePack.cpp @@ -13,12 +13,11 @@ // Values taken from: // https://minecraft.fandom.com/wiki/Tutorials/Creating_a_resource_pack#Formatting_pack.mcmeta static const QMap> s_pack_format_versions = { - { 1, { Version("1.6.1"), Version("1.8.9") } }, { 2, { Version("1.9"), Version("1.10.2") } }, - { 3, { Version("1.11"), Version("1.12.2") } }, { 4, { Version("1.13"), Version("1.14.4") } }, - { 5, { Version("1.15"), Version("1.16.1") } }, { 6, { Version("1.16.2"), Version("1.16.5") } }, - { 7, { Version("1.17"), Version("1.17.1") } }, { 8, { Version("1.18"), Version("1.18.2") } }, - { 9, { Version("1.19"), Version("1.19.2") } }, - // { 11, { Version("22w42a"), Version("22w44a") } } + { 1, { Version("1.6.1"), Version("1.8.9") } }, { 2, { Version("1.9"), Version("1.10.2") } }, + { 3, { Version("1.11"), Version("1.12.2") } }, { 4, { Version("1.13"), Version("1.14.4") } }, + { 5, { Version("1.15"), Version("1.16.1") } }, { 6, { Version("1.16.2"), Version("1.16.5") } }, + { 7, { Version("1.17"), Version("1.17.1") } }, { 8, { Version("1.18"), Version("1.18.2") } }, + { 9, { Version("1.19"), Version("1.19.2") } }, { 11, { Version("22w42a"), Version("22w44a") } }, { 12, { Version("1.19.3"), Version("1.19.3") } }, }; diff --git a/launcher/minecraft/mod/ShaderPack.cpp b/launcher/minecraft/mod/ShaderPack.cpp index b8d427c7..6a9641de 100644 --- a/launcher/minecraft/mod/ShaderPack.cpp +++ b/launcher/minecraft/mod/ShaderPack.cpp @@ -24,12 +24,10 @@ #include "minecraft/mod/tasks/LocalShaderPackParseTask.h" - void ShaderPack::setPackFormat(ShaderPackFormat new_format) { QMutexLocker locker(&m_data_lock); - m_pack_format = new_format; } diff --git a/launcher/minecraft/mod/ShaderPack.h b/launcher/minecraft/mod/ShaderPack.h index a0dad7a1..ec0f9404 100644 --- a/launcher/minecraft/mod/ShaderPack.h +++ b/launcher/minecraft/mod/ShaderPack.h @@ -24,31 +24,27 @@ #include "Resource.h" /* Info: - * Currently For Optifine / Iris shader packs, - * could be expanded to support others should they exsist? + * Currently For Optifine / Iris shader packs, + * could be expanded to support others should they exist? * - * This class and enum are mostly here as placeholders for validating - * that a shaderpack exsists and is in the right format, + * This class and enum are mostly here as placeholders for validating + * that a shaderpack exists and is in the right format, * namely that they contain a folder named 'shaders'. * - * In the technical sense it would be possible to parse files like `shaders/shaders.properties` - * to get information like the availble profiles but this is not all that usefull without more knoledge of the - * shader mod used to be able to change settings - * + * In the technical sense it would be possible to parse files like `shaders/shaders.properties` + * to get information like the available profiles but this is not all that useful without more knowledge of the + * shader mod used to be able to change settings. */ #include -enum class ShaderPackFormat { - VALID, - INVALID -}; +enum class ShaderPackFormat { VALID, INVALID }; class ShaderPack : public Resource { Q_OBJECT public: using Ptr = shared_qobject_ptr; - + [[nodiscard]] ShaderPackFormat packFormat() const { return m_pack_format; } ShaderPack(QObject* parent = nullptr) : Resource(parent) {} @@ -62,5 +58,5 @@ class ShaderPack : public Resource { protected: mutable QMutex m_data_lock; - ShaderPackFormat m_pack_format = ShaderPackFormat::INVALID; + ShaderPackFormat m_pack_format = ShaderPackFormat::INVALID; }; diff --git a/launcher/minecraft/mod/WorldSave.cpp b/launcher/minecraft/mod/WorldSave.cpp index 9a626fc1..7123f512 100644 --- a/launcher/minecraft/mod/WorldSave.cpp +++ b/launcher/minecraft/mod/WorldSave.cpp @@ -27,7 +27,6 @@ void WorldSave::setSaveFormat(WorldSaveFormat new_save_format) { QMutexLocker locker(&m_data_lock); - m_save_format = new_save_format; } @@ -35,11 +34,10 @@ void WorldSave::setSaveDirName(QString dir_name) { QMutexLocker locker(&m_data_lock); - m_save_dir_name = dir_name; } bool WorldSave::valid() const { - return m_save_format != WorldSaveFormat::INVALID; -} \ No newline at end of file + return m_save_format != WorldSaveFormat::INVALID; +} diff --git a/launcher/minecraft/mod/WorldSave.h b/launcher/minecraft/mod/WorldSave.h index f703f34c..5985fc8a 100644 --- a/launcher/minecraft/mod/WorldSave.h +++ b/launcher/minecraft/mod/WorldSave.h @@ -27,11 +27,7 @@ class Version; -enum class WorldSaveFormat { - SINGLE, - MULTI, - INVALID -}; +enum class WorldSaveFormat { SINGLE, MULTI, INVALID }; class WorldSave : public Resource { Q_OBJECT @@ -53,15 +49,13 @@ class WorldSave : public Resource { bool valid() const override; - protected: mutable QMutex m_data_lock; - /* The 'version' of a resource pack, as defined in the pack.mcmeta file. - * See https://minecraft.fandom.com/wiki/Tutorials/Creating_a_resource_pack#Formatting_pack.mcmeta + /** The format in which the save file is in. + * Since saves can be distributed in various slightly different ways, this allows us to treat them separately. */ WorldSaveFormat m_save_format = WorldSaveFormat::INVALID; - QString m_save_dir_name; - + QString m_save_dir_name; }; diff --git a/launcher/minecraft/mod/tasks/LocalDataPackParseTask.cpp b/launcher/minecraft/mod/tasks/LocalDataPackParseTask.cpp index 8bc8278b..3fcb2110 100644 --- a/launcher/minecraft/mod/tasks/LocalDataPackParseTask.cpp +++ b/launcher/minecraft/mod/tasks/LocalDataPackParseTask.cpp @@ -25,8 +25,8 @@ #include "Json.h" #include -#include #include +#include #include @@ -40,7 +40,7 @@ bool process(DataPack& pack, ProcessingLevel level) case ResourceType::ZIPFILE: return DataPackUtils::processZIP(pack, level); default: - qWarning() << "Invalid type for resource pack parse task!"; + qWarning() << "Invalid type for data pack parse task!"; return false; } } @@ -49,11 +49,16 @@ bool processFolder(DataPack& pack, ProcessingLevel level) { Q_ASSERT(pack.type() == ResourceType::FOLDER); + auto mcmeta_invalid = [&pack]() { + qWarning() << "Resource pack at" << pack.fileinfo().filePath() << "does not have a valid pack.mcmeta"; + return false; // the mcmeta is not optional + }; + QFileInfo mcmeta_file_info(FS::PathCombine(pack.fileinfo().filePath(), "pack.mcmeta")); if (mcmeta_file_info.exists() && mcmeta_file_info.isFile()) { QFile mcmeta_file(mcmeta_file_info.filePath()); if (!mcmeta_file.open(QIODevice::ReadOnly)) - return false; // can't open mcmeta file + return mcmeta_invalid(); // can't open mcmeta file auto data = mcmeta_file.readAll(); @@ -61,22 +66,22 @@ bool processFolder(DataPack& pack, ProcessingLevel level) mcmeta_file.close(); if (!mcmeta_result) { - return false; // mcmeta invalid + return mcmeta_invalid(); // mcmeta invalid } } else { - return false; // mcmeta file isn't a valid file + return mcmeta_invalid(); // mcmeta file isn't a valid file } QFileInfo data_dir_info(FS::PathCombine(pack.fileinfo().filePath(), "data")); if (!data_dir_info.exists() || !data_dir_info.isDir()) { - return false; // data dir does not exists or isn't valid + return false; // data dir does not exists or isn't valid } if (level == ProcessingLevel::BasicInfoOnly) { - return true; // only need basic info already checked + return true; // only need basic info already checked } - return true; // all tests passed + return true; // all tests passed } bool processZIP(DataPack& pack, ProcessingLevel level) @@ -85,15 +90,20 @@ bool processZIP(DataPack& pack, ProcessingLevel level) QuaZip zip(pack.fileinfo().filePath()); if (!zip.open(QuaZip::mdUnzip)) - return false; // can't open zip file + return false; // can't open zip file QuaZipFile file(&zip); + auto mcmeta_invalid = [&pack]() { + qWarning() << "Resource pack at" << pack.fileinfo().filePath() << "does not have a valid pack.mcmeta"; + return false; // the mcmeta is not optional + }; + if (zip.setCurrentFile("pack.mcmeta")) { if (!file.open(QIODevice::ReadOnly)) { qCritical() << "Failed to open file in zip."; zip.close(); - return false; + return mcmeta_invalid(); } auto data = file.readAll(); @@ -102,20 +112,20 @@ bool processZIP(DataPack& pack, ProcessingLevel level) file.close(); if (!mcmeta_result) { - return false; // mcmeta invalid + return mcmeta_invalid(); // mcmeta invalid } } else { - return false; // could not set pack.mcmeta as current file. + return mcmeta_invalid(); // could not set pack.mcmeta as current file. } QuaZipDir zipDir(&zip); if (!zipDir.exists("/data")) { - return false; // data dir does not exists at zip root + return false; // data dir does not exists at zip root } if (level == ProcessingLevel::BasicInfoOnly) { zip.close(); - return true; // only need basic info already checked + return true; // only need basic info already checked } zip.close(); @@ -123,7 +133,7 @@ bool processZIP(DataPack& pack, ProcessingLevel level) return true; } -// https://minecraft.fandom.com/wiki/Tutorials/Creating_a_resource_pack#Formatting_pack.mcmeta +// https://minecraft.fandom.com/wiki/Data_pack#pack.mcmeta bool processMCMeta(DataPack& pack, QByteArray&& raw_data) { try { @@ -147,9 +157,7 @@ bool validate(QFileInfo file) } // namespace DataPackUtils -LocalDataPackParseTask::LocalDataPackParseTask(int token, DataPack& dp) - : Task(nullptr, false), m_token(token), m_resource_pack(dp) -{} +LocalDataPackParseTask::LocalDataPackParseTask(int token, DataPack& dp) : Task(nullptr, false), m_token(token), m_data_pack(dp) {} bool LocalDataPackParseTask::abort() { @@ -159,7 +167,7 @@ bool LocalDataPackParseTask::abort() void LocalDataPackParseTask::executeTask() { - if (!DataPackUtils::process(m_resource_pack)) + if (!DataPackUtils::process(m_data_pack)) return; if (m_aborted) diff --git a/launcher/minecraft/mod/tasks/LocalDataPackParseTask.h b/launcher/minecraft/mod/tasks/LocalDataPackParseTask.h index 54e3d398..12fd8c82 100644 --- a/launcher/minecraft/mod/tasks/LocalDataPackParseTask.h +++ b/launcher/minecraft/mod/tasks/LocalDataPackParseTask.h @@ -59,7 +59,7 @@ class LocalDataPackParseTask : public Task { private: int m_token; - DataPack& m_resource_pack; + DataPack& m_data_pack; bool m_aborted = false; }; diff --git a/launcher/minecraft/mod/tasks/LocalModParseTask.cpp b/launcher/minecraft/mod/tasks/LocalModParseTask.cpp index e8fd39b6..8bfe2c84 100644 --- a/launcher/minecraft/mod/tasks/LocalModParseTask.cpp +++ b/launcher/minecraft/mod/tasks/LocalModParseTask.cpp @@ -284,7 +284,8 @@ ModDetails ReadLiteModInfo(QByteArray contents) return details; } -bool process(Mod& mod, ProcessingLevel level) { +bool process(Mod& mod, ProcessingLevel level) +{ switch (mod.type()) { case ResourceType::FOLDER: return processFolder(mod, level); @@ -293,13 +294,13 @@ bool process(Mod& mod, ProcessingLevel level) { case ResourceType::LITEMOD: return processLitemod(mod); default: - qWarning() << "Invalid type for resource pack parse task!"; + qWarning() << "Invalid type for mod parse task!"; return false; } } -bool processZIP(Mod& mod, ProcessingLevel level) { - +bool processZIP(Mod& mod, ProcessingLevel level) +{ ModDetails details; QuaZip zip(mod.fileinfo().filePath()); @@ -316,7 +317,7 @@ bool processZIP(Mod& mod, ProcessingLevel level) { details = ReadMCModTOML(file.readAll()); file.close(); - + // to replace ${file.jarVersion} with the actual version, as needed if (details.version == "${file.jarVersion}") { if (zip.setCurrentFile("META-INF/MANIFEST.MF")) { @@ -347,7 +348,6 @@ bool processZIP(Mod& mod, ProcessingLevel level) { } } - zip.close(); mod.setDetails(details); @@ -403,11 +403,11 @@ bool processZIP(Mod& mod, ProcessingLevel level) { } zip.close(); - return false; // no valid mod found in archive + return false; // no valid mod found in archive } -bool processFolder(Mod& mod, ProcessingLevel level) { - +bool processFolder(Mod& mod, ProcessingLevel level) +{ ModDetails details; QFileInfo mcmod_info(FS::PathCombine(mod.fileinfo().filePath(), "mcmod.info")); @@ -424,13 +424,13 @@ bool processFolder(Mod& mod, ProcessingLevel level) { return true; } - return false; // no valid mcmod.info file found + return false; // no valid mcmod.info file found } -bool processLitemod(Mod& mod, ProcessingLevel level) { - +bool processLitemod(Mod& mod, ProcessingLevel level) +{ ModDetails details; - + QuaZip zip(mod.fileinfo().filePath()); if (!zip.open(QuaZip::mdUnzip)) return false; @@ -451,24 +451,22 @@ bool processLitemod(Mod& mod, ProcessingLevel level) { } zip.close(); - return false; // no valid litemod.json found in archive + return false; // no valid litemod.json found in archive } /** Checks whether a file is valid as a mod or not. */ -bool validate(QFileInfo file) { - +bool validate(QFileInfo file) +{ Mod mod{ file }; return ModUtils::process(mod, ProcessingLevel::BasicInfoOnly) && mod.valid(); } } // namespace ModUtils - LocalModParseTask::LocalModParseTask(int token, ResourceType type, const QFileInfo& modFile) : Task(nullptr, false), m_token(token), m_type(type), m_modFile(modFile), m_result(new Result()) {} - bool LocalModParseTask::abort() { m_aborted.store(true); @@ -476,7 +474,7 @@ bool LocalModParseTask::abort() } void LocalModParseTask::executeTask() -{ +{ Mod mod{ m_modFile }; ModUtils::process(mod, ModUtils::ProcessingLevel::Full); diff --git a/launcher/minecraft/mod/tasks/LocalModParseTask.h b/launcher/minecraft/mod/tasks/LocalModParseTask.h index c9512166..38dae135 100644 --- a/launcher/minecraft/mod/tasks/LocalModParseTask.h +++ b/launcher/minecraft/mod/tasks/LocalModParseTask.h @@ -27,32 +27,29 @@ bool processLitemod(Mod& mod, ProcessingLevel level = ProcessingLevel::Full); bool validate(QFileInfo file); } // namespace ModUtils -class LocalModParseTask : public Task -{ +class LocalModParseTask : public Task { Q_OBJECT -public: + public: struct Result { ModDetails details; }; using ResultPtr = std::shared_ptr; - ResultPtr result() const { - return m_result; - } + ResultPtr result() const { return m_result; } [[nodiscard]] bool canAbort() const override { return true; } bool abort() override; - LocalModParseTask(int token, ResourceType type, const QFileInfo & modFile); + LocalModParseTask(int token, ResourceType type, const QFileInfo& modFile); void executeTask() override; [[nodiscard]] int token() const { return m_token; } -private: + private: void processAsZip(); void processAsFolder(); void processAsLitemod(); -private: + private: int m_token; ResourceType m_type; QFileInfo m_modFile; diff --git a/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp b/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp index 2c41c9ae..4bf0b80d 100644 --- a/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp +++ b/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp @@ -22,8 +22,8 @@ #include "Json.h" #include -#include #include +#include #include @@ -46,11 +46,16 @@ bool processFolder(ResourcePack& pack, ProcessingLevel level) { Q_ASSERT(pack.type() == ResourceType::FOLDER); + auto mcmeta_invalid = [&pack]() { + qWarning() << "Resource pack at" << pack.fileinfo().filePath() << "does not have a valid pack.mcmeta"; + return false; // the mcmeta is not optional + }; + QFileInfo mcmeta_file_info(FS::PathCombine(pack.fileinfo().filePath(), "pack.mcmeta")); if (mcmeta_file_info.exists() && mcmeta_file_info.isFile()) { QFile mcmeta_file(mcmeta_file_info.filePath()); if (!mcmeta_file.open(QIODevice::ReadOnly)) - return false; // can't open mcmeta file + return mcmeta_invalid(); // can't open mcmeta file auto data = mcmeta_file.readAll(); @@ -58,26 +63,31 @@ bool processFolder(ResourcePack& pack, ProcessingLevel level) mcmeta_file.close(); if (!mcmeta_result) { - return false; // mcmeta invalid + return mcmeta_invalid(); // mcmeta invalid } } else { - return false; // mcmeta file isn't a valid file + return mcmeta_invalid(); // mcmeta file isn't a valid file } QFileInfo assets_dir_info(FS::PathCombine(pack.fileinfo().filePath(), "assets")); if (!assets_dir_info.exists() || !assets_dir_info.isDir()) { - return false; // assets dir does not exists or isn't valid + return false; // assets dir does not exists or isn't valid } if (level == ProcessingLevel::BasicInfoOnly) { - return true; // only need basic info already checked + return true; // only need basic info already checked } - + + auto png_invalid = [&pack]() { + qWarning() << "Resource pack at" << pack.fileinfo().filePath() << "does not have a valid pack.png"; + return true; // the png is optional + }; + QFileInfo image_file_info(FS::PathCombine(pack.fileinfo().filePath(), "pack.png")); if (image_file_info.exists() && image_file_info.isFile()) { QFile pack_png_file(image_file_info.filePath()); if (!pack_png_file.open(QIODevice::ReadOnly)) - return false; // can't open pack.png file + return png_invalid(); // can't open pack.png file auto data = pack_png_file.readAll(); @@ -85,13 +95,13 @@ bool processFolder(ResourcePack& pack, ProcessingLevel level) pack_png_file.close(); if (!pack_png_result) { - return false; // pack.png invalid + return png_invalid(); // pack.png invalid } } else { - return false; // pack.png does not exists or is not a valid file. + return png_invalid(); // pack.png does not exists or is not a valid file. } - return true; // all tests passed + return true; // all tests passed } bool processZIP(ResourcePack& pack, ProcessingLevel level) @@ -100,15 +110,20 @@ bool processZIP(ResourcePack& pack, ProcessingLevel level) QuaZip zip(pack.fileinfo().filePath()); if (!zip.open(QuaZip::mdUnzip)) - return false; // can't open zip file + return false; // can't open zip file QuaZipFile file(&zip); + auto mcmeta_invalid = [&pack]() { + qWarning() << "Resource pack at" << pack.fileinfo().filePath() << "does not have a valid pack.mcmeta"; + return false; // the mcmeta is not optional + }; + if (zip.setCurrentFile("pack.mcmeta")) { if (!file.open(QIODevice::ReadOnly)) { qCritical() << "Failed to open file in zip."; zip.close(); - return false; + return mcmeta_invalid(); } auto data = file.readAll(); @@ -117,27 +132,32 @@ bool processZIP(ResourcePack& pack, ProcessingLevel level) file.close(); if (!mcmeta_result) { - return false; // mcmeta invalid + return mcmeta_invalid(); // mcmeta invalid } } else { - return false; // could not set pack.mcmeta as current file. + return mcmeta_invalid(); // could not set pack.mcmeta as current file. } QuaZipDir zipDir(&zip); if (!zipDir.exists("/assets")) { - return false; // assets dir does not exists at zip root + return false; // assets dir does not exists at zip root } if (level == ProcessingLevel::BasicInfoOnly) { zip.close(); - return true; // only need basic info already checked + return true; // only need basic info already checked } + auto png_invalid = [&pack]() { + qWarning() << "Resource pack at" << pack.fileinfo().filePath() << "does not have a valid pack.png"; + return true; // the png is optional + }; + if (zip.setCurrentFile("pack.png")) { if (!file.open(QIODevice::ReadOnly)) { qCritical() << "Failed to open file in zip."; zip.close(); - return false; + return png_invalid(); } auto data = file.readAll(); @@ -146,10 +166,10 @@ bool processZIP(ResourcePack& pack, ProcessingLevel level) file.close(); if (!pack_png_result) { - return false; // pack.png invalid + return png_invalid(); // pack.png invalid } } else { - return false; // could not set pack.mcmeta as current file. + return png_invalid(); // could not set pack.mcmeta as current file. } zip.close(); diff --git a/launcher/minecraft/mod/tasks/LocalShaderPackParseTask.cpp b/launcher/minecraft/mod/tasks/LocalShaderPackParseTask.cpp index 088853b9..a9949735 100644 --- a/launcher/minecraft/mod/tasks/LocalShaderPackParseTask.cpp +++ b/launcher/minecraft/mod/tasks/LocalShaderPackParseTask.cpp @@ -24,8 +24,8 @@ #include "FileSystem.h" #include -#include #include +#include namespace ShaderPackUtils { @@ -45,18 +45,18 @@ bool process(ShaderPack& pack, ProcessingLevel level) bool processFolder(ShaderPack& pack, ProcessingLevel level) { Q_ASSERT(pack.type() == ResourceType::FOLDER); - + QFileInfo shaders_dir_info(FS::PathCombine(pack.fileinfo().filePath(), "shaders")); if (!shaders_dir_info.exists() || !shaders_dir_info.isDir()) { - return false; // assets dir does not exists or isn't valid + return false; // assets dir does not exists or isn't valid } pack.setPackFormat(ShaderPackFormat::VALID); if (level == ProcessingLevel::BasicInfoOnly) { - return true; // only need basic info already checked + return true; // only need basic info already checked } - - return true; // all tests passed + + return true; // all tests passed } bool processZIP(ShaderPack& pack, ProcessingLevel level) @@ -65,19 +65,19 @@ bool processZIP(ShaderPack& pack, ProcessingLevel level) QuaZip zip(pack.fileinfo().filePath()); if (!zip.open(QuaZip::mdUnzip)) - return false; // can't open zip file + return false; // can't open zip file QuaZipFile file(&zip); QuaZipDir zipDir(&zip); if (!zipDir.exists("/shaders")) { - return false; // assets dir does not exists at zip root + return false; // assets dir does not exists at zip root } pack.setPackFormat(ShaderPackFormat::VALID); if (level == ProcessingLevel::BasicInfoOnly) { zip.close(); - return true; // only need basic info already checked + return true; // only need basic info already checked } zip.close(); @@ -85,7 +85,6 @@ bool processZIP(ShaderPack& pack, ProcessingLevel level) return true; } - bool validate(QFileInfo file) { ShaderPack sp{ file }; @@ -94,9 +93,7 @@ bool validate(QFileInfo file) } // namespace ShaderPackUtils -LocalShaderPackParseTask::LocalShaderPackParseTask(int token, ShaderPack& sp) - : Task(nullptr, false), m_token(token), m_shader_pack(sp) -{} +LocalShaderPackParseTask::LocalShaderPackParseTask(int token, ShaderPack& sp) : Task(nullptr, false), m_token(token), m_shader_pack(sp) {} bool LocalShaderPackParseTask::abort() { diff --git a/launcher/minecraft/mod/tasks/LocalShaderPackParseTask.h b/launcher/minecraft/mod/tasks/LocalShaderPackParseTask.h index 5d113508..6be2183c 100644 --- a/launcher/minecraft/mod/tasks/LocalShaderPackParseTask.h +++ b/launcher/minecraft/mod/tasks/LocalShaderPackParseTask.h @@ -19,7 +19,6 @@ * along with this program. If not, see . */ - #pragma once #include @@ -38,7 +37,7 @@ bool process(ShaderPack& pack, ProcessingLevel level = ProcessingLevel::Full); bool processZIP(ShaderPack& pack, ProcessingLevel level = ProcessingLevel::Full); bool processFolder(ShaderPack& pack, ProcessingLevel level = ProcessingLevel::Full); -/** Checks whether a file is valid as a resource pack or not. */ +/** Checks whether a file is valid as a shader pack or not. */ bool validate(QFileInfo file); } // namespace ShaderPackUtils diff --git a/launcher/minecraft/mod/tasks/LocalWorldSaveParseTask.cpp b/launcher/minecraft/mod/tasks/LocalWorldSaveParseTask.cpp index b7f2420a..cbc8f8ce 100644 --- a/launcher/minecraft/mod/tasks/LocalWorldSaveParseTask.cpp +++ b/launcher/minecraft/mod/tasks/LocalWorldSaveParseTask.cpp @@ -24,12 +24,12 @@ #include "FileSystem.h" -#include -#include #include -#include #include -#include +#include + +#include +#include namespace WorldSaveUtils { @@ -41,15 +41,22 @@ bool process(WorldSave& pack, ProcessingLevel level) case ResourceType::ZIPFILE: return WorldSaveUtils::processZIP(pack, level); default: - qWarning() << "Invalid type for shader pack parse task!"; + qWarning() << "Invalid type for world save parse task!"; return false; } } - +/// @brief checks a folder structure to see if it contains a level.dat +/// @param dir the path to check +/// @param saves used in recursive call if a "saves" dir was found +/// @return std::tuple of ( +/// bool , +/// QString , +/// bool +/// ) static std::tuple contains_level_dat(QDir dir, bool saves = false) { - for(auto const& entry : dir.entryInfoList()) { + for (auto const& entry : dir.entryInfoList()) { if (!entry.isDir()) { continue; } @@ -64,12 +71,11 @@ static std::tuple contains_level_dat(QDir dir, bool saves = return std::make_tuple(false, "", saves); } - bool processFolder(WorldSave& save, ProcessingLevel level) { Q_ASSERT(save.type() == ResourceType::FOLDER); - auto [ found, save_dir_name, found_saves_dir ] = contains_level_dat(QDir(save.fileinfo().filePath())); + auto [found, save_dir_name, found_saves_dir] = contains_level_dat(QDir(save.fileinfo().filePath())); if (!found) { return false; @@ -84,14 +90,21 @@ bool processFolder(WorldSave& save, ProcessingLevel level) } if (level == ProcessingLevel::BasicInfoOnly) { - return true; // only need basic info already checked + return true; // only need basic info already checked } - // resurved for more intensive processing - - return true; // all tests passed + // reserved for more intensive processing + + return true; // all tests passed } +/// @brief checks a folder structure to see if it contains a level.dat +/// @param zip the zip file to check +/// @return std::tuple of ( +/// bool , +/// QString , +/// bool +/// ) static std::tuple contains_level_dat(QuaZip& zip) { bool saves = false; @@ -100,7 +113,7 @@ static std::tuple contains_level_dat(QuaZip& zip) saves = true; zipDir.cd("/saves"); } - + for (auto const& entry : zipDir.entryList()) { zipDir.cd(entry); if (zipDir.exists("level.dat")) { @@ -117,14 +130,14 @@ bool processZIP(WorldSave& save, ProcessingLevel level) QuaZip zip(save.fileinfo().filePath()); if (!zip.open(QuaZip::mdUnzip)) - return false; // can't open zip file + return false; // can't open zip file - auto [ found, save_dir_name, found_saves_dir ] = contains_level_dat(zip); + auto [found, save_dir_name, found_saves_dir] = contains_level_dat(zip); if (save_dir_name.endsWith("/")) { save_dir_name.chop(1); } - + if (!found) { return false; } @@ -139,17 +152,16 @@ bool processZIP(WorldSave& save, ProcessingLevel level) if (level == ProcessingLevel::BasicInfoOnly) { zip.close(); - return true; // only need basic info already checked + return true; // only need basic info already checked } - // resurved for more intensive processing + // reserved for more intensive processing zip.close(); return true; } - bool validate(QFileInfo file) { WorldSave sp{ file }; @@ -158,9 +170,7 @@ bool validate(QFileInfo file) } // namespace WorldSaveUtils -LocalWorldSaveParseTask::LocalWorldSaveParseTask(int token, WorldSave& save) - : Task(nullptr, false), m_token(token), m_save(save) -{} +LocalWorldSaveParseTask::LocalWorldSaveParseTask(int token, WorldSave& save) : Task(nullptr, false), m_token(token), m_save(save) {} bool LocalWorldSaveParseTask::abort() { diff --git a/launcher/minecraft/mod/tasks/LocalWorldSaveParseTask.h b/launcher/minecraft/mod/tasks/LocalWorldSaveParseTask.h index aa5db0c2..9dcdca2b 100644 --- a/launcher/minecraft/mod/tasks/LocalWorldSaveParseTask.h +++ b/launcher/minecraft/mod/tasks/LocalWorldSaveParseTask.h @@ -59,4 +59,4 @@ class LocalWorldSaveParseTask : public Task { WorldSave& m_save; bool m_aborted = false; -}; \ No newline at end of file +}; diff --git a/launcher/modplatform/flame/FlameInstanceCreationTask.cpp b/launcher/modplatform/flame/FlameInstanceCreationTask.cpp index b62d05ab..79104e17 100644 --- a/launcher/modplatform/flame/FlameInstanceCreationTask.cpp +++ b/launcher/modplatform/flame/FlameInstanceCreationTask.cpp @@ -53,15 +53,16 @@ #include "ui/dialogs/BlockedModsDialog.h" #include "ui/dialogs/CustomMessageBox.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include + +#include "minecraft/World.h" +#include "minecraft/mod/tasks/LocalDataPackParseTask.h" +#include "minecraft/mod/tasks/LocalModParseTask.h" +#include "minecraft/mod/tasks/LocalResourcePackParseTask.h" +#include "minecraft/mod/tasks/LocalShaderPackParseTask.h" +#include "minecraft/mod/tasks/LocalTexturePackParseTask.h" +#include "minecraft/mod/tasks/LocalWorldSaveParseTask.h" const static QMap forgemap = { { "1.2.5", "3.4.9.171" }, { "1.4.2", "6.0.1.355" }, @@ -411,8 +412,7 @@ void FlameCreationTask::idResolverSucceeded(QEventLoop& loop) QList blocked_mods; auto anyBlocked = false; for (const auto& result : results.files.values()) { - - if(result.fileName.endsWith(".zip")) { + if (result.fileName.endsWith(".zip")) { m_ZIP_resources.append(std::make_pair(result.fileName, result.targetFolder)); } @@ -454,7 +454,6 @@ void FlameCreationTask::idResolverSucceeded(QEventLoop& loop) } } - void FlameCreationTask::setupDownloadJob(QEventLoop& loop) { m_files_job = new NetJob(tr("Mod download"), APPLICATION->network()); @@ -493,8 +492,8 @@ void FlameCreationTask::setupDownloadJob(QEventLoop& loop) } m_mod_id_resolver.reset(); - connect(m_files_job.get(), &NetJob::succeeded, this, [&]() { - m_files_job.reset(); + connect(m_files_job.get(), &NetJob::succeeded, this, [&]() { + m_files_job.reset(); validateZIPResouces(); }); connect(m_files_job.get(), &NetJob::failed, [&](QString reason) { @@ -543,26 +542,26 @@ void FlameCreationTask::copyBlockedMods(QList const& blocked_mods) bool moveFile(QString src, QString dst) { if (!FS::copy(src, dst)()) { // copy - qDebug() << "Copy of" << src << "to" << dst << "Failed!"; + qDebug() << "Copy of" << src << "to" << dst << "failed!"; return false; } else { if (!FS::deletePath(src)) { // remove original - qDebug() << "Deleation of" << src << "Failed!"; + qDebug() << "Deletion of" << src << "failed!"; return false; }; } return true; } - void FlameCreationTask::validateZIPResouces() { - qDebug() << "Validating resoucres stored as .zip are in the right place"; + qDebug() << "Validating whether resources stored as .zip are in the right place"; for (auto [fileName, targetFolder] : m_ZIP_resources) { - qDebug() << "Checking" << fileName << "..."; auto localPath = FS::PathCombine(m_stagingPath, "minecraft", targetFolder, fileName); + /// @brief check the target and move the the file + /// @return path where file can now be found auto validatePath = [&localPath, this](QString fileName, QString targetFolder, QString realTarget) { if (targetFolder != realTarget) { qDebug() << "Target folder of" << fileName << "is incorrect, it belongs in" << realTarget; @@ -589,7 +588,7 @@ void FlameCreationTask::validateZIPResouces() } else if (ModUtils::validate(localFileInfo)) { qDebug() << fileName << "is a mod"; validatePath(fileName, targetFolder, "mods"); - } else if (WorldSaveUtils::validate(localFileInfo)) { + } else if (WorldSaveUtils::validate(localFileInfo)) { qDebug() << fileName << "is a world save"; QString worldPath = validatePath(fileName, targetFolder, "saves"); @@ -600,7 +599,7 @@ void FlameCreationTask::validateZIPResouces() qDebug() << "World at" << worldPath << "is not valid, skipping install."; } else { w.install(FS::PathCombine(m_stagingPath, "minecraft", "saves")); - } + } } else if (ShaderPackUtils::validate(localFileInfo)) { // in theroy flame API can't do this but who knows, that *may* change ? // better to handle it if it *does* occure in the future @@ -610,7 +609,7 @@ void FlameCreationTask::validateZIPResouces() qDebug() << "Can't Identify" << fileName << "at" << localPath << ", leaving it where it is."; } } else { - qDebug() << "Can't find" << localPath << "to validate it, ignoreing"; + qDebug() << "Can't find" << localPath << "to validate it, ignoring"; } } } diff --git a/tests/ResourcePackParse_test.cpp b/tests/ResourcePackParse_test.cpp index 4192da31..7f2f86bf 100644 --- a/tests/ResourcePackParse_test.cpp +++ b/tests/ResourcePackParse_test.cpp @@ -67,7 +67,7 @@ class ResourcePackParseTest : public QObject { QVERIFY(pack.packFormat() == 6); QVERIFY(pack.description() == "o quartel pegou fogo, policia deu sinal, acode acode acode a bandeira nacional"); - QVERIFY(valid == false); + QVERIFY(valid == false); // no assets dir } }; -- cgit From c470f05abf090232b27faac6014f9e1cbe9dab9b Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Thu, 29 Dec 2022 17:21:54 -0700 Subject: refactor: use std::filesystem::rename insted of copy and then moving. Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- launcher/FileSystem.cpp | 16 ++++++++++++++++ launcher/FileSystem.h | 8 ++++++++ launcher/modplatform/flame/FlameInstanceCreationTask.cpp | 15 +-------------- 3 files changed, 25 insertions(+), 14 deletions(-) (limited to 'launcher/modplatform') diff --git a/launcher/FileSystem.cpp b/launcher/FileSystem.cpp index 3e8e10a5..4390eed9 100644 --- a/launcher/FileSystem.cpp +++ b/launcher/FileSystem.cpp @@ -213,6 +213,22 @@ bool copy::operator()(const QString& offset, bool dryRun) return err.value() == 0; } +bool move(const QString& source, const QString& dest) +{ + std::error_code err; + + ensureFilePathExists(dest); + fs::rename(StringUtils::toStdString(source), StringUtils::toStdString(dest), err); + + if (err) { + qWarning() << "Failed to move file:" << QString::fromStdString(err.message()); + qDebug() << "Source file:" << source; + qDebug() << "Destination file:" << dest; + } + + return err.value() == 0; +} + bool deletePath(QString path) { std::error_code err; diff --git a/launcher/FileSystem.h b/launcher/FileSystem.h index ac893725..1e3a60d9 100644 --- a/launcher/FileSystem.h +++ b/launcher/FileSystem.h @@ -121,6 +121,14 @@ class copy : public QObject { int m_copied; }; +/** + * @brief moves a file by renaming it + * @param source source file path + * @param dest destination filepath + * + */ +bool move(const QString& source, const QString& dest); + /** * Delete a folder recursively */ diff --git a/launcher/modplatform/flame/FlameInstanceCreationTask.cpp b/launcher/modplatform/flame/FlameInstanceCreationTask.cpp index 79104e17..0a91879d 100644 --- a/launcher/modplatform/flame/FlameInstanceCreationTask.cpp +++ b/launcher/modplatform/flame/FlameInstanceCreationTask.cpp @@ -539,19 +539,6 @@ void FlameCreationTask::copyBlockedMods(QList const& blocked_mods) setAbortable(true); } -bool moveFile(QString src, QString dst) -{ - if (!FS::copy(src, dst)()) { // copy - qDebug() << "Copy of" << src << "to" << dst << "failed!"; - return false; - } else { - if (!FS::deletePath(src)) { // remove original - qDebug() << "Deletion of" << src << "failed!"; - return false; - }; - } - return true; -} void FlameCreationTask::validateZIPResouces() { @@ -567,7 +554,7 @@ void FlameCreationTask::validateZIPResouces() qDebug() << "Target folder of" << fileName << "is incorrect, it belongs in" << realTarget; auto destPath = FS::PathCombine(m_stagingPath, "minecraft", realTarget, fileName); qDebug() << "Moving" << localPath << "to" << destPath; - if (moveFile(localPath, destPath)) { + if (FS::move(localPath, destPath)) { return destPath; } } -- cgit From 7f438425aa84db51211123b47622a828be0aeb96 Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Thu, 29 Dec 2022 19:47:19 -0700 Subject: refactor: add an `identify` function to make easy to reuse Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- launcher/CMakeLists.txt | 2 + .../minecraft/mod/tasks/LocalResourceParse.cpp | 60 +++++++++++++++++++ launcher/minecraft/mod/tasks/LocalResourceParse.h | 31 ++++++++++ .../flame/FlameInstanceCreationTask.cpp | 68 +++++++++++----------- 4 files changed, 128 insertions(+), 33 deletions(-) create mode 100644 launcher/minecraft/mod/tasks/LocalResourceParse.cpp create mode 100644 launcher/minecraft/mod/tasks/LocalResourceParse.h (limited to 'launcher/modplatform') diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index 853e1c03..9826d543 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -363,6 +363,8 @@ set(MINECRAFT_SOURCES minecraft/mod/tasks/LocalShaderPackParseTask.cpp minecraft/mod/tasks/LocalWorldSaveParseTask.h minecraft/mod/tasks/LocalWorldSaveParseTask.cpp + minecraft/mod/tasks/LocalResourceParse.h + minecraft/mod/tasks/LocalResourceParse.cpp # Assets minecraft/AssetsUtils.h diff --git a/launcher/minecraft/mod/tasks/LocalResourceParse.cpp b/launcher/minecraft/mod/tasks/LocalResourceParse.cpp new file mode 100644 index 00000000..244b2f54 --- /dev/null +++ b/launcher/minecraft/mod/tasks/LocalResourceParse.cpp @@ -0,0 +1,60 @@ +// SPDX-FileCopyrightText: 2022 Rachel Powers <508861+Ryex@users.noreply.github.com> +// +// SPDX-License-Identifier: GPL-3.0-only + +/* + * Prism Launcher - Minecraft Launcher + * Copyright (C) 2022 Rachel Powers <508861+Ryex@users.noreply.github.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "LocalResourceParse.h" + +#include "LocalDataPackParseTask.h" +#include "LocalModParseTask.h" +#include "LocalResourcePackParseTask.h" +#include "LocalShaderPackParseTask.h" +#include "LocalTexturePackParseTask.h" +#include "LocalWorldSaveParseTask.h" + +namespace ResourceUtils { +PackedResourceType identify(QFileInfo file){ + if (file.exists() && file.isFile()) { + if (ResourcePackUtils::validate(file)) { + qDebug() << file.fileName() << "is a resource pack"; + return PackedResourceType::ResourcePack; + } else if (TexturePackUtils::validate(file)) { + qDebug() << file.fileName() << "is a pre 1.6 texture pack"; + return PackedResourceType::TexturePack; + } else if (DataPackUtils::validate(file)) { + qDebug() << file.fileName() << "is a data pack"; + return PackedResourceType::DataPack; + } else if (ModUtils::validate(file)) { + qDebug() << file.fileName() << "is a mod"; + return PackedResourceType::Mod; + } else if (WorldSaveUtils::validate(file)) { + qDebug() << file.fileName() << "is a world save"; + return PackedResourceType::WorldSave; + } else if (ShaderPackUtils::validate(file)) { + qDebug() << file.fileName() << "is a shader pack"; + return PackedResourceType::ShaderPack; + } else { + qDebug() << "Can't Identify" << file.fileName() ; + } + } else { + qDebug() << "Can't find" << file.absolutePath(); + } + return PackedResourceType::UNKNOWN; +} +} \ No newline at end of file diff --git a/launcher/minecraft/mod/tasks/LocalResourceParse.h b/launcher/minecraft/mod/tasks/LocalResourceParse.h new file mode 100644 index 00000000..b3e2829d --- /dev/null +++ b/launcher/minecraft/mod/tasks/LocalResourceParse.h @@ -0,0 +1,31 @@ +// SPDX-FileCopyrightText: 2022 Rachel Powers <508861+Ryex@users.noreply.github.com> +// +// SPDX-License-Identifier: GPL-3.0-only + +/* + * Prism Launcher - Minecraft Launcher + * Copyright (C) 2022 Rachel Powers <508861+Ryex@users.noreply.github.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once + +#include +#include +#include + +enum class PackedResourceType { DataPack, ResourcePack, TexturePack, ShaderPack, WorldSave, Mod, UNKNOWN }; +namespace ResourceUtils { +PackedResourceType identify(QFileInfo file); +} // namespace ResourceUtils \ No newline at end of file diff --git a/launcher/modplatform/flame/FlameInstanceCreationTask.cpp b/launcher/modplatform/flame/FlameInstanceCreationTask.cpp index 0a91879d..dc69769a 100644 --- a/launcher/modplatform/flame/FlameInstanceCreationTask.cpp +++ b/launcher/modplatform/flame/FlameInstanceCreationTask.cpp @@ -57,12 +57,8 @@ #include #include "minecraft/World.h" -#include "minecraft/mod/tasks/LocalDataPackParseTask.h" -#include "minecraft/mod/tasks/LocalModParseTask.h" -#include "minecraft/mod/tasks/LocalResourcePackParseTask.h" -#include "minecraft/mod/tasks/LocalShaderPackParseTask.h" -#include "minecraft/mod/tasks/LocalTexturePackParseTask.h" -#include "minecraft/mod/tasks/LocalWorldSaveParseTask.h" +#include "minecraft/mod/tasks/LocalResourceParse.h" + const static QMap forgemap = { { "1.2.5", "3.4.9.171" }, { "1.4.2", "6.0.1.355" }, @@ -561,42 +557,48 @@ void FlameCreationTask::validateZIPResouces() return localPath; }; + auto installWorld = [this](QString worldPath){ + qDebug() << "Installing World from" << worldPath; + QFileInfo worldFileInfo(worldPath); + World w(worldFileInfo); + if (!w.isValid()) { + qDebug() << "World at" << worldPath << "is not valid, skipping install."; + } else { + w.install(FS::PathCombine(m_stagingPath, "minecraft", "saves")); + } + }; + QFileInfo localFileInfo(localPath); - if (localFileInfo.exists() && localFileInfo.isFile()) { - if (ResourcePackUtils::validate(localFileInfo)) { - qDebug() << fileName << "is a resource pack"; + auto type = ResourceUtils::identify(localFileInfo); + + QString worldPath; + + switch (type) { + case PackedResourceType::ResourcePack : validatePath(fileName, targetFolder, "resourcepacks"); - } else if (TexturePackUtils::validate(localFileInfo)) { - qDebug() << fileName << "is a pre 1.6 texture pack"; + break; + case PackedResourceType::TexturePack : validatePath(fileName, targetFolder, "texturepacks"); - } else if (DataPackUtils::validate(localFileInfo)) { - qDebug() << fileName << "is a data pack"; + break; + case PackedResourceType::DataPack : validatePath(fileName, targetFolder, "datapacks"); - } else if (ModUtils::validate(localFileInfo)) { - qDebug() << fileName << "is a mod"; + break; + case PackedResourceType::Mod : validatePath(fileName, targetFolder, "mods"); - } else if (WorldSaveUtils::validate(localFileInfo)) { - qDebug() << fileName << "is a world save"; - QString worldPath = validatePath(fileName, targetFolder, "saves"); - - qDebug() << "Installing World from" << worldPath; - QFileInfo worldFileInfo(worldPath); - World w(worldFileInfo); - if (!w.isValid()) { - qDebug() << "World at" << worldPath << "is not valid, skipping install."; - } else { - w.install(FS::PathCombine(m_stagingPath, "minecraft", "saves")); - } - } else if (ShaderPackUtils::validate(localFileInfo)) { + break; + case PackedResourceType::ShaderPack : // in theroy flame API can't do this but who knows, that *may* change ? // better to handle it if it *does* occure in the future - qDebug() << fileName << "is a shader pack"; validatePath(fileName, targetFolder, "shaderpacks"); - } else { + break; + case PackedResourceType::WorldSave : + worldPath = validatePath(fileName, targetFolder, "saves"); + installWorld(worldPath); + break; + case PackedResourceType::UNKNOWN : + default : qDebug() << "Can't Identify" << fileName << "at" << localPath << ", leaving it where it is."; - } - } else { - qDebug() << "Can't find" << localPath << "to validate it, ignoring"; + break; } } } -- cgit From 2faf8332ee8523a5c097c744dc8e12e86d304230 Mon Sep 17 00:00:00 2001 From: byquanton <32410361+byquanton@users.noreply.github.com> Date: Thu, 5 Jan 2023 17:08:41 +0100 Subject: fix: Add 1.16+ Forge library prefix in TechnicPackProcessor.cpp Signed-off-by: byquanton <32410361+byquanton@users.noreply.github.com> --- launcher/modplatform/technic/TechnicPackProcessor.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'launcher/modplatform') diff --git a/launcher/modplatform/technic/TechnicPackProcessor.cpp b/launcher/modplatform/technic/TechnicPackProcessor.cpp index 95feb4b2..df713a72 100644 --- a/launcher/modplatform/technic/TechnicPackProcessor.cpp +++ b/launcher/modplatform/technic/TechnicPackProcessor.cpp @@ -172,7 +172,7 @@ void Technic::TechnicPackProcessor::run(SettingsObjectPtr globalSettings, const auto libraryObject = Json::ensureObject(library, {}, ""); auto libraryName = Json::ensureString(libraryObject, "name", "", ""); - if (libraryName.startsWith("net.minecraftforge:forge:") && libraryName.contains('-')) + if ((libraryName.startsWith("net.minecraftforge:forge:") || libraryName.startsWith("net.minecraftforge:fmlloader:")) && libraryName.contains('-')) { QString libraryVersion = libraryName.section(':', 2); if (!libraryVersion.startsWith("1.7.10-")) -- cgit From 807da6a0358c99cea907b51fb389654c969e27da Mon Sep 17 00:00:00 2001 From: Joshua Goins Date: Sat, 7 Jan 2023 15:38:16 -0500 Subject: fix: Remove extra line breaks for modrinth descriptions Signed-off-by: Joshua Goins --- launcher/modplatform/modrinth/ModrinthPackIndex.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'launcher/modplatform') diff --git a/launcher/modplatform/modrinth/ModrinthPackIndex.cpp b/launcher/modplatform/modrinth/ModrinthPackIndex.cpp index ae45e096..aec45a73 100644 --- a/launcher/modplatform/modrinth/ModrinthPackIndex.cpp +++ b/launcher/modplatform/modrinth/ModrinthPackIndex.cpp @@ -87,7 +87,7 @@ void Modrinth::loadExtraPackData(ModPlatform::IndexedPack& pack, QJsonObject& ob pack.extraData.donate.append(donate); } - pack.extraData.body = Json::ensureString(obj, "body"); + pack.extraData.body = Json::ensureString(obj, "body").remove("
"); pack.extraDataLoaded = true; } -- cgit From 6a1807995390b2a2cbe074ee1f47d3791e0e3f10 Mon Sep 17 00:00:00 2001 From: flow Date: Fri, 25 Nov 2022 09:23:46 -0300 Subject: refactor: generalize mod models and APIs to resources Firstly, this abstract away behavior in the mod download models that can also be applied to other types of resources into a superclass, allowing other resource types to be implemented without so much code duplication. For that, this also generalizes the APIs used (currently, ModrinthAPI and FlameAPI) to be able to make requests to other types of resources. It also does a general cleanup of both of those. In particular, this makes use of std::optional instead of invalid values for errors and, well, optional values :p This is a squash of some commits that were becoming too interlaced together to be cleanly separated. Signed-off-by: flow --- launcher/CMakeLists.txt | 37 ++- launcher/ModDownloadTask.cpp | 72 ----- launcher/ModDownloadTask.h | 57 ---- launcher/ResourceDownloadTask.cpp | 80 +++++ launcher/ResourceDownloadTask.h | 57 ++++ launcher/minecraft/PackProfile.cpp | 28 +- launcher/minecraft/PackProfile.h | 4 +- launcher/modplatform/CheckUpdateTask.h | 14 +- launcher/modplatform/EnsureMetadataTask.cpp | 14 +- launcher/modplatform/EnsureMetadataTask.h | 6 +- launcher/modplatform/ModAPI.h | 118 ------- launcher/modplatform/ModIndex.cpp | 24 +- launcher/modplatform/ModIndex.h | 19 +- launcher/modplatform/ResourceAPI.h | 149 +++++++++ launcher/modplatform/flame/FlameAPI.cpp | 16 +- launcher/modplatform/flame/FlameAPI.h | 99 +++--- launcher/modplatform/flame/FlameCheckUpdate.cpp | 11 +- launcher/modplatform/flame/FlameCheckUpdate.h | 2 +- .../flame/FlameInstanceCreationTask.cpp | 4 +- .../modplatform/flame/FlameInstanceCreationTask.h | 2 +- launcher/modplatform/flame/FlameModIndex.cpp | 4 +- launcher/modplatform/helpers/HashUtils.cpp | 16 +- launcher/modplatform/helpers/HashUtils.h | 10 +- launcher/modplatform/helpers/NetworkModAPI.cpp | 97 ------ launcher/modplatform/helpers/NetworkModAPI.h | 17 - .../modplatform/helpers/NetworkResourceAPI.cpp | 124 +++++++ launcher/modplatform/helpers/NetworkResourceAPI.h | 18 ++ launcher/modplatform/modrinth/ModrinthAPI.cpp | 36 ++- launcher/modplatform/modrinth/ModrinthAPI.h | 106 +++--- .../modplatform/modrinth/ModrinthCheckUpdate.cpp | 25 +- .../modplatform/modrinth/ModrinthCheckUpdate.h | 2 +- .../modplatform/modrinth/ModrinthPackIndex.cpp | 4 +- launcher/modplatform/packwiz/Packwiz.cpp | 8 +- launcher/modplatform/packwiz/Packwiz.h | 2 +- launcher/net/NetAction.h | 4 - launcher/net/NetJob.cpp | 5 +- launcher/ui/dialogs/BlockedModsDialog.cpp | 2 +- launcher/ui/dialogs/ChooseProviderDialog.cpp | 6 +- launcher/ui/dialogs/ChooseProviderDialog.h | 6 +- launcher/ui/dialogs/ModDownloadDialog.cpp | 165 +--------- launcher/ui/dialogs/ModDownloadDialog.h | 43 +-- launcher/ui/dialogs/ModUpdateDialog.cpp | 44 +-- launcher/ui/dialogs/ModUpdateDialog.h | 8 +- launcher/ui/dialogs/ResourceDownloadDialog.cpp | 152 +++++++++ launcher/ui/dialogs/ResourceDownloadDialog.h | 55 ++++ launcher/ui/dialogs/ReviewMessageBox.cpp | 4 +- launcher/ui/dialogs/ReviewMessageBox.h | 8 +- launcher/ui/pages/instance/ModFolderPage.cpp | 6 +- launcher/ui/pages/instance/ResourcePackPage.h | 1 + launcher/ui/pages/modplatform/ModModel.cpp | 274 +++------------- launcher/ui/pages/modplatform/ModModel.h | 64 +--- launcher/ui/pages/modplatform/ModPage.cpp | 357 +++------------------ launcher/ui/pages/modplatform/ModPage.h | 78 +---- launcher/ui/pages/modplatform/ModPage.ui | 118 ------- launcher/ui/pages/modplatform/ResourceModel.cpp | 258 +++++++++++++++ launcher/ui/pages/modplatform/ResourceModel.h | 101 ++++++ launcher/ui/pages/modplatform/ResourcePage.cpp | 347 ++++++++++++++++++++ launcher/ui/pages/modplatform/ResourcePage.h | 95 ++++++ launcher/ui/pages/modplatform/ResourcePage.ui | 118 +++++++ .../ui/pages/modplatform/flame/FlameModModel.cpp | 31 -- .../ui/pages/modplatform/flame/FlameModModel.h | 26 -- .../ui/pages/modplatform/flame/FlameModPage.cpp | 97 ------ launcher/ui/pages/modplatform/flame/FlameModPage.h | 70 ---- .../modplatform/flame/FlameResourceModels.cpp | 31 ++ .../pages/modplatform/flame/FlameResourceModels.h | 26 ++ .../pages/modplatform/flame/FlameResourcePages.cpp | 97 ++++++ .../pages/modplatform/flame/FlameResourcePages.h | 71 ++++ .../modplatform/modrinth/ModrinthModModel.cpp | 48 --- .../pages/modplatform/modrinth/ModrinthModModel.h | 44 --- .../pages/modplatform/modrinth/ModrinthModPage.cpp | 84 ----- .../pages/modplatform/modrinth/ModrinthModPage.h | 66 ---- .../modrinth/ModrinthResourceModels.cpp | 53 +++ .../modplatform/modrinth/ModrinthResourceModels.h | 49 +++ .../modplatform/modrinth/ModrinthResourcePages.cpp | 89 +++++ .../modplatform/modrinth/ModrinthResourcePages.h | 72 +++++ launcher/ui/widgets/ProgressWidget.cpp | 6 +- launcher/ui/widgets/ProgressWidget.h | 6 +- tests/Packwiz_test.cpp | 4 +- 78 files changed, 2508 insertions(+), 2063 deletions(-) delete mode 100644 launcher/ModDownloadTask.cpp delete mode 100644 launcher/ModDownloadTask.h create mode 100644 launcher/ResourceDownloadTask.cpp create mode 100644 launcher/ResourceDownloadTask.h delete mode 100644 launcher/modplatform/ModAPI.h create mode 100644 launcher/modplatform/ResourceAPI.h delete mode 100644 launcher/modplatform/helpers/NetworkModAPI.cpp delete mode 100644 launcher/modplatform/helpers/NetworkModAPI.h create mode 100644 launcher/modplatform/helpers/NetworkResourceAPI.cpp create mode 100644 launcher/modplatform/helpers/NetworkResourceAPI.h create mode 100644 launcher/ui/dialogs/ResourceDownloadDialog.cpp create mode 100644 launcher/ui/dialogs/ResourceDownloadDialog.h delete mode 100644 launcher/ui/pages/modplatform/ModPage.ui create mode 100644 launcher/ui/pages/modplatform/ResourceModel.cpp create mode 100644 launcher/ui/pages/modplatform/ResourceModel.h create mode 100644 launcher/ui/pages/modplatform/ResourcePage.cpp create mode 100644 launcher/ui/pages/modplatform/ResourcePage.h create mode 100644 launcher/ui/pages/modplatform/ResourcePage.ui delete mode 100644 launcher/ui/pages/modplatform/flame/FlameModModel.cpp delete mode 100644 launcher/ui/pages/modplatform/flame/FlameModModel.h delete mode 100644 launcher/ui/pages/modplatform/flame/FlameModPage.cpp delete mode 100644 launcher/ui/pages/modplatform/flame/FlameModPage.h create mode 100644 launcher/ui/pages/modplatform/flame/FlameResourceModels.cpp create mode 100644 launcher/ui/pages/modplatform/flame/FlameResourceModels.h create mode 100644 launcher/ui/pages/modplatform/flame/FlameResourcePages.cpp create mode 100644 launcher/ui/pages/modplatform/flame/FlameResourcePages.h delete mode 100644 launcher/ui/pages/modplatform/modrinth/ModrinthModModel.cpp delete mode 100644 launcher/ui/pages/modplatform/modrinth/ModrinthModModel.h delete mode 100644 launcher/ui/pages/modplatform/modrinth/ModrinthModPage.cpp delete mode 100644 launcher/ui/pages/modplatform/modrinth/ModrinthModPage.h create mode 100644 launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.cpp create mode 100644 launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.h create mode 100644 launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.cpp create mode 100644 launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.h (limited to 'launcher/modplatform') diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index eec6c787..a1a68f5b 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -38,9 +38,9 @@ set(CORE_SOURCES InstanceImportTask.h InstanceImportTask.cpp - # Mod downloading task - ModDownloadTask.h - ModDownloadTask.cpp + # Resource downloading task + ResourceDownloadTask.h + ResourceDownloadTask.cpp # Use tracking separate from memory management Usable.h @@ -473,7 +473,7 @@ set(API_SOURCES modplatform/ModIndex.h modplatform/ModIndex.cpp - modplatform/ModAPI.h + modplatform/ResourceAPI.h modplatform/EnsureMetadataTask.h modplatform/EnsureMetadataTask.cpp @@ -484,8 +484,8 @@ set(API_SOURCES modplatform/flame/FlameAPI.cpp modplatform/modrinth/ModrinthAPI.h modplatform/modrinth/ModrinthAPI.cpp - modplatform/helpers/NetworkModAPI.h - modplatform/helpers/NetworkModAPI.cpp + modplatform/helpers/NetworkResourceAPI.h + modplatform/helpers/NetworkResourceAPI.cpp modplatform/helpers/HashUtils.h modplatform/helpers/HashUtils.cpp modplatform/helpers/OverrideUtils.h @@ -771,6 +771,11 @@ SET(LAUNCHER_SOURCES ui/pages/modplatform/VanillaPage.cpp ui/pages/modplatform/VanillaPage.h + ui/pages/modplatform/ResourcePage.cpp + ui/pages/modplatform/ResourcePage.h + ui/pages/modplatform/ResourceModel.cpp + ui/pages/modplatform/ResourceModel.h + ui/pages/modplatform/ModPage.cpp ui/pages/modplatform/ModPage.h ui/pages/modplatform/ModModel.cpp @@ -803,10 +808,10 @@ SET(LAUNCHER_SOURCES ui/pages/modplatform/flame/FlameModel.h ui/pages/modplatform/flame/FlamePage.cpp ui/pages/modplatform/flame/FlamePage.h - ui/pages/modplatform/flame/FlameModModel.cpp - ui/pages/modplatform/flame/FlameModModel.h - ui/pages/modplatform/flame/FlameModPage.cpp - ui/pages/modplatform/flame/FlameModPage.h + ui/pages/modplatform/flame/FlameResourceModels.cpp + ui/pages/modplatform/flame/FlameResourceModels.h + ui/pages/modplatform/flame/FlameResourcePages.cpp + ui/pages/modplatform/flame/FlameResourcePages.h ui/pages/modplatform/modrinth/ModrinthPage.cpp ui/pages/modplatform/modrinth/ModrinthPage.h @@ -821,10 +826,10 @@ SET(LAUNCHER_SOURCES ui/pages/modplatform/ImportPage.cpp ui/pages/modplatform/ImportPage.h - ui/pages/modplatform/modrinth/ModrinthModModel.cpp - ui/pages/modplatform/modrinth/ModrinthModModel.h - ui/pages/modplatform/modrinth/ModrinthModPage.cpp - ui/pages/modplatform/modrinth/ModrinthModPage.h + ui/pages/modplatform/modrinth/ModrinthResourceModels.cpp + ui/pages/modplatform/modrinth/ModrinthResourceModels.h + ui/pages/modplatform/modrinth/ModrinthResourcePages.cpp + ui/pages/modplatform/modrinth/ModrinthResourcePages.h # GUI - dialogs ui/dialogs/AboutDialog.cpp @@ -869,6 +874,8 @@ SET(LAUNCHER_SOURCES ui/dialogs/VersionSelectDialog.h ui/dialogs/SkinUploadDialog.cpp ui/dialogs/SkinUploadDialog.h + ui/dialogs/ResourceDownloadDialog.cpp + ui/dialogs/ResourceDownloadDialog.h ui/dialogs/ModDownloadDialog.cpp ui/dialogs/ModDownloadDialog.h ui/dialogs/ScrollMessageBox.cpp @@ -965,7 +972,7 @@ qt_wrap_ui(LAUNCHER_UI ui/pages/modplatform/atlauncher/AtlOptionalModDialog.ui ui/pages/modplatform/atlauncher/AtlPage.ui ui/pages/modplatform/VanillaPage.ui - ui/pages/modplatform/ModPage.ui + ui/pages/modplatform/ResourcePage.ui ui/pages/modplatform/flame/FlamePage.ui ui/pages/modplatform/legacy_ftb/Page.ui ui/pages/modplatform/ImportPage.ui diff --git a/launcher/ModDownloadTask.cpp b/launcher/ModDownloadTask.cpp deleted file mode 100644 index 2b0343f4..00000000 --- a/launcher/ModDownloadTask.cpp +++ /dev/null @@ -1,72 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-only -/* -* PolyMC - Minecraft Launcher -* Copyright (c) 2022 flowln -* Copyright (C) 2022 Sefa Eyeoglu -* -* This program is free software: you can redistribute it and/or modify -* it under the terms of the GNU General Public License as published by -* the Free Software Foundation, version 3. -* -* This program is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU General Public License for more details. -* -* You should have received a copy of the GNU General Public License -* along with this program. If not, see . -*/ - -#include "ModDownloadTask.h" - -#include "Application.h" -#include "minecraft/mod/ModFolderModel.h" - -ModDownloadTask::ModDownloadTask(ModPlatform::IndexedPack mod, ModPlatform::IndexedVersion version, const std::shared_ptr mods, bool is_indexed) - : m_mod(mod), m_mod_version(version), mods(mods) -{ - if (is_indexed) { - m_update_task.reset(new LocalModUpdateTask(mods->indexDir(), m_mod, m_mod_version)); - connect(m_update_task.get(), &LocalModUpdateTask::hasOldMod, this, &ModDownloadTask::hasOldMod); - - addTask(m_update_task); - } - - m_filesNetJob.reset(new NetJob(tr("Mod download"), APPLICATION->network())); - m_filesNetJob->setStatus(tr("Downloading mod:\n%1").arg(m_mod_version.downloadUrl)); - - m_filesNetJob->addNetAction(Net::Download::makeFile(m_mod_version.downloadUrl, mods->dir().absoluteFilePath(getFilename()))); - connect(m_filesNetJob.get(), &NetJob::succeeded, this, &ModDownloadTask::downloadSucceeded); - connect(m_filesNetJob.get(), &NetJob::progress, this, &ModDownloadTask::downloadProgressChanged); - connect(m_filesNetJob.get(), &NetJob::failed, this, &ModDownloadTask::downloadFailed); - - addTask(m_filesNetJob); -} - -void ModDownloadTask::downloadSucceeded() -{ - m_filesNetJob.reset(); - auto name = std::get<0>(to_delete); - auto filename = std::get<1>(to_delete); - if (!name.isEmpty() && filename != m_mod_version.fileName) { - mods->uninstallMod(filename, true); - } -} - -void ModDownloadTask::downloadFailed(QString reason) -{ - emitFailed(reason); - m_filesNetJob.reset(); -} - -void ModDownloadTask::downloadProgressChanged(qint64 current, qint64 total) -{ - emit progress(current, total); -} - -// This indirection is done so that we don't delete a mod before being sure it was -// downloaded successfully! -void ModDownloadTask::hasOldMod(QString name, QString filename) -{ - to_delete = {name, filename}; -} diff --git a/launcher/ModDownloadTask.h b/launcher/ModDownloadTask.h deleted file mode 100644 index 95020470..00000000 --- a/launcher/ModDownloadTask.h +++ /dev/null @@ -1,57 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-only -/* -* PolyMC - Minecraft Launcher -* Copyright (c) 2022 flowln -* Copyright (C) 2022 Sefa Eyeoglu -* -* This program is free software: you can redistribute it and/or modify -* it under the terms of the GNU General Public License as published by -* the Free Software Foundation, version 3. -* -* This program is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU General Public License for more details. -* -* You should have received a copy of the GNU General Public License -* along with this program. If not, see . -*/ - -#pragma once - -#include "net/NetJob.h" -#include "tasks/SequentialTask.h" - -#include "modplatform/ModIndex.h" -#include "minecraft/mod/tasks/LocalModUpdateTask.h" - -class ModFolderModel; - -class ModDownloadTask : public SequentialTask { - Q_OBJECT -public: - explicit ModDownloadTask(ModPlatform::IndexedPack mod, ModPlatform::IndexedVersion version, const std::shared_ptr mods, bool is_indexed = true); - const QString& getFilename() const { return m_mod_version.fileName; } - -private: - ModPlatform::IndexedPack m_mod; - ModPlatform::IndexedVersion m_mod_version; - const std::shared_ptr mods; - - NetJob::Ptr m_filesNetJob; - LocalModUpdateTask::Ptr m_update_task; - - void downloadProgressChanged(qint64 current, qint64 total); - - void downloadFailed(QString reason); - - void downloadSucceeded(); - - std::tuple to_delete {"", ""}; - -private slots: - void hasOldMod(QString name, QString filename); -}; - - - diff --git a/launcher/ResourceDownloadTask.cpp b/launcher/ResourceDownloadTask.cpp new file mode 100644 index 00000000..687eaf51 --- /dev/null +++ b/launcher/ResourceDownloadTask.cpp @@ -0,0 +1,80 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* +* PolyMC - Minecraft Launcher +* Copyright (c) 2022 flowln +* Copyright (C) 2022 Sefa Eyeoglu +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, version 3. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see . +*/ + +#include "ResourceDownloadTask.h" + +#include "Application.h" + +#include "minecraft/mod/ModFolderModel.h" +#include "minecraft/mod/ResourceFolderModel.h" + +ResourceDownloadTask::ResourceDownloadTask(ModPlatform::IndexedPack pack, + ModPlatform::IndexedVersion version, + const std::shared_ptr packs, + bool is_indexed) + : m_pack(std::move(pack)), m_pack_version(std::move(version)), m_pack_model(packs) +{ + if (auto model = dynamic_cast(m_pack_model.get()); model && is_indexed) { + m_update_task.reset(new LocalModUpdateTask(model->indexDir(), m_pack, m_pack_version)); + connect(m_update_task.get(), &LocalModUpdateTask::hasOldMod, this, &ResourceDownloadTask::hasOldResource); + + addTask(m_update_task); + } + + m_filesNetJob.reset(new NetJob(tr("Resource download"), APPLICATION->network())); + m_filesNetJob->setStatus(tr("Downloading resource:\n%1").arg(m_pack_version.downloadUrl)); + + m_filesNetJob->addNetAction(Net::Download::makeFile(m_pack_version.downloadUrl, m_pack_model->dir().absoluteFilePath(getFilename()))); + connect(m_filesNetJob.get(), &NetJob::succeeded, this, &ResourceDownloadTask::downloadSucceeded); + connect(m_filesNetJob.get(), &NetJob::progress, this, &ResourceDownloadTask::downloadProgressChanged); + connect(m_filesNetJob.get(), &NetJob::failed, this, &ResourceDownloadTask::downloadFailed); + + addTask(m_filesNetJob); +} + +void ResourceDownloadTask::downloadSucceeded() +{ + m_filesNetJob.reset(); + auto name = std::get<0>(to_delete); + auto filename = std::get<1>(to_delete); + if (!name.isEmpty() && filename != m_pack_version.fileName) { + if (auto model = dynamic_cast(m_pack_model.get()); model) + model->uninstallMod(filename, true); + else + m_pack_model->uninstallResource(filename); + } +} + +void ResourceDownloadTask::downloadFailed(QString reason) +{ + emitFailed(reason); + m_filesNetJob.reset(); +} + +void ResourceDownloadTask::downloadProgressChanged(qint64 current, qint64 total) +{ + emit progress(current, total); +} + +// This indirection is done so that we don't delete a mod before being sure it was +// downloaded successfully! +void ResourceDownloadTask::hasOldResource(QString name, QString filename) +{ + to_delete = { name, filename }; +} diff --git a/launcher/ResourceDownloadTask.h b/launcher/ResourceDownloadTask.h new file mode 100644 index 00000000..350c2edd --- /dev/null +++ b/launcher/ResourceDownloadTask.h @@ -0,0 +1,57 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* +* PolyMC - Minecraft Launcher +* Copyright (c) 2022 flowln +* Copyright (C) 2022 Sefa Eyeoglu +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, version 3. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see . +*/ + +#pragma once + +#include "net/NetJob.h" +#include "tasks/SequentialTask.h" + +#include "modplatform/ModIndex.h" +#include "minecraft/mod/tasks/LocalModUpdateTask.h" + +class ResourceFolderModel; + +class ResourceDownloadTask : public SequentialTask { + Q_OBJECT +public: + explicit ResourceDownloadTask(ModPlatform::IndexedPack pack, ModPlatform::IndexedVersion version, const std::shared_ptr packs, bool is_indexed = true); + const QString& getFilename() const { return m_pack_version.fileName; } + +private: + ModPlatform::IndexedPack m_pack; + ModPlatform::IndexedVersion m_pack_version; + const std::shared_ptr m_pack_model; + + NetJob::Ptr m_filesNetJob; + LocalModUpdateTask::Ptr m_update_task; + + void downloadProgressChanged(qint64 current, qint64 total); + + void downloadFailed(QString reason); + + void downloadSucceeded(); + + std::tuple to_delete {"", ""}; + +private slots: + void hasOldResource(QString name, QString filename); +}; + + + diff --git a/launcher/minecraft/PackProfile.cpp b/launcher/minecraft/PackProfile.cpp index 43fa3f8d..42021b3c 100644 --- a/launcher/minecraft/PackProfile.cpp +++ b/launcher/minecraft/PackProfile.cpp @@ -55,12 +55,13 @@ #include "PackProfile_p.h" #include "ComponentUpdateTask.h" -#include "modplatform/ModAPI.h" +#include "Application.h" +#include "modplatform/ResourceAPI.h" -static const QMap modloaderMapping{ - {"net.minecraftforge", ModAPI::Forge}, - {"net.fabricmc.fabric-loader", ModAPI::Fabric}, - {"org.quiltmc.quilt-loader", ModAPI::Quilt} +static const QMap modloaderMapping{ + {"net.minecraftforge", ResourceAPI::Forge}, + {"net.fabricmc.fabric-loader", ResourceAPI::Fabric}, + {"org.quiltmc.quilt-loader", ResourceAPI::Quilt} }; PackProfile::PackProfile(MinecraftInstance * instance) @@ -1066,19 +1067,22 @@ void PackProfile::disableInteraction(bool disable) } } -ModAPI::ModLoaderTypes PackProfile::getModLoaders() +std::optional PackProfile::getModLoaders() { - ModAPI::ModLoaderTypes result = ModAPI::Unspecified; + ResourceAPI::ModLoaderTypes result; + bool has_any_loader = false; - QMapIterator i(modloaderMapping); + QMapIterator i(modloaderMapping); - while (i.hasNext()) - { + while (i.hasNext()) { i.next(); - Component* c = getComponent(i.key()); - if (c != nullptr && c->isEnabled()) { + if (auto c = getComponent(i.key()); c != nullptr && c->isEnabled()) { result |= i.value(); + has_any_loader = true; } } + + if (!has_any_loader) + return {}; return result; } diff --git a/launcher/minecraft/PackProfile.h b/launcher/minecraft/PackProfile.h index 2330cca1..67b418f4 100644 --- a/launcher/minecraft/PackProfile.h +++ b/launcher/minecraft/PackProfile.h @@ -49,7 +49,7 @@ #include "BaseVersion.h" #include "MojangDownloadInfo.h" #include "net/Mode.h" -#include "modplatform/ModAPI.h" +#include "modplatform/ResourceAPI.h" class MinecraftInstance; struct PackProfileData; @@ -145,7 +145,7 @@ public: // todo(merged): is this the best approach void appendComponent(ComponentPtr component); - ModAPI::ModLoaderTypes getModLoaders(); + std::optional getModLoaders(); private: void scheduleSave(); diff --git a/launcher/modplatform/CheckUpdateTask.h b/launcher/modplatform/CheckUpdateTask.h index 91922034..932a62d9 100644 --- a/launcher/modplatform/CheckUpdateTask.h +++ b/launcher/modplatform/CheckUpdateTask.h @@ -1,18 +1,18 @@ #pragma once #include "minecraft/mod/Mod.h" -#include "modplatform/ModAPI.h" +#include "modplatform/ResourceAPI.h" #include "modplatform/ModIndex.h" #include "tasks/Task.h" -class ModDownloadTask; +class ResourceDownloadTask; class ModFolderModel; class CheckUpdateTask : public Task { Q_OBJECT public: - CheckUpdateTask(QList& mods, std::list& mcVersions, ModAPI::ModLoaderTypes loaders, std::shared_ptr mods_folder) + CheckUpdateTask(QList& mods, std::list& mcVersions, std::optional loaders, std::shared_ptr mods_folder) : Task(nullptr), m_mods(mods), m_game_versions(mcVersions), m_loaders(loaders), m_mods_folder(mods_folder) {}; struct UpdatableMod { @@ -21,11 +21,11 @@ class CheckUpdateTask : public Task { QString old_version; QString new_version; QString changelog; - ModPlatform::Provider provider; - ModDownloadTask* download; + ModPlatform::ResourceProvider provider; + ResourceDownloadTask* download; public: - UpdatableMod(QString name, QString old_h, QString old_v, QString new_v, QString changelog, ModPlatform::Provider p, ModDownloadTask* t) + UpdatableMod(QString name, QString old_h, QString old_v, QString new_v, QString changelog, ModPlatform::ResourceProvider p, ResourceDownloadTask* t) : name(name), old_hash(old_h), old_version(old_v), new_version(new_v), changelog(changelog), provider(p), download(t) {} }; @@ -44,7 +44,7 @@ class CheckUpdateTask : public Task { protected: QList& m_mods; std::list& m_game_versions; - ModAPI::ModLoaderTypes m_loaders; + std::optional m_loaders; std::shared_ptr m_mods_folder; std::vector m_updatable; diff --git a/launcher/modplatform/EnsureMetadataTask.cpp b/launcher/modplatform/EnsureMetadataTask.cpp index 234330a7..9bf81338 100644 --- a/launcher/modplatform/EnsureMetadataTask.cpp +++ b/launcher/modplatform/EnsureMetadataTask.cpp @@ -20,7 +20,7 @@ static ModPlatform::ProviderCapabilities ProviderCaps; static ModrinthAPI modrinth_api; static FlameAPI flame_api; -EnsureMetadataTask::EnsureMetadataTask(Mod* mod, QDir dir, ModPlatform::Provider prov) +EnsureMetadataTask::EnsureMetadataTask(Mod* mod, QDir dir, ModPlatform::ResourceProvider prov) : Task(nullptr), m_index_dir(dir), m_provider(prov), m_hashing_task(nullptr), m_current_task(nullptr) { auto hash_task = createNewHash(mod); @@ -31,7 +31,7 @@ EnsureMetadataTask::EnsureMetadataTask(Mod* mod, QDir dir, ModPlatform::Provider hash_task->start(); } -EnsureMetadataTask::EnsureMetadataTask(QList& mods, QDir dir, ModPlatform::Provider prov) +EnsureMetadataTask::EnsureMetadataTask(QList& mods, QDir dir, ModPlatform::ResourceProvider prov) : Task(nullptr), m_index_dir(dir), m_provider(prov), m_current_task(nullptr) { m_hashing_task = new ConcurrentTask(this, "MakeHashesTask", 10); @@ -110,10 +110,10 @@ void EnsureMetadataTask::executeTask() NetJob::Ptr version_task; switch (m_provider) { - case (ModPlatform::Provider::MODRINTH): + case (ModPlatform::ResourceProvider::MODRINTH): version_task = modrinthVersionsTask(); break; - case (ModPlatform::Provider::FLAME): + case (ModPlatform::ResourceProvider::FLAME): version_task = flameVersionsTask(); break; } @@ -130,10 +130,10 @@ void EnsureMetadataTask::executeTask() NetJob::Ptr project_task; switch (m_provider) { - case (ModPlatform::Provider::MODRINTH): + case (ModPlatform::ResourceProvider::MODRINTH): project_task = modrinthProjectsTask(); break; - case (ModPlatform::Provider::FLAME): + case (ModPlatform::ResourceProvider::FLAME): project_task = flameProjectsTask(); break; } @@ -212,7 +212,7 @@ void EnsureMetadataTask::emitFail(Mod* m, QString key, RemoveFromList remove) NetJob::Ptr EnsureMetadataTask::modrinthVersionsTask() { - auto hash_type = ProviderCaps.hashType(ModPlatform::Provider::MODRINTH).first(); + auto hash_type = ProviderCaps.hashType(ModPlatform::ResourceProvider::MODRINTH).first(); auto* response = new QByteArray(); auto ver_task = modrinth_api.currentVersions(m_mods.keys(), hash_type, response); diff --git a/launcher/modplatform/EnsureMetadataTask.h b/launcher/modplatform/EnsureMetadataTask.h index a8b0851e..a79e5861 100644 --- a/launcher/modplatform/EnsureMetadataTask.h +++ b/launcher/modplatform/EnsureMetadataTask.h @@ -14,8 +14,8 @@ class EnsureMetadataTask : public Task { Q_OBJECT public: - EnsureMetadataTask(Mod*, QDir, ModPlatform::Provider = ModPlatform::Provider::MODRINTH); - EnsureMetadataTask(QList&, QDir, ModPlatform::Provider = ModPlatform::Provider::MODRINTH); + EnsureMetadataTask(Mod*, QDir, ModPlatform::ResourceProvider = ModPlatform::ResourceProvider::MODRINTH); + EnsureMetadataTask(QList&, QDir, ModPlatform::ResourceProvider = ModPlatform::ResourceProvider::MODRINTH); ~EnsureMetadataTask() = default; @@ -57,7 +57,7 @@ class EnsureMetadataTask : public Task { private: QHash m_mods; QDir m_index_dir; - ModPlatform::Provider m_provider; + ModPlatform::ResourceProvider m_provider; QHash m_temp_versions; ConcurrentTask* m_hashing_task; diff --git a/launcher/modplatform/ModAPI.h b/launcher/modplatform/ModAPI.h deleted file mode 100644 index 703de143..00000000 --- a/launcher/modplatform/ModAPI.h +++ /dev/null @@ -1,118 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-only -/* - * PolyMC - Minecraft Launcher - * Copyright (C) 2022 Sefa Eyeoglu - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, version 3. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * This file incorporates work covered by the following copyright and - * permission notice: - * - * Copyright 2013-2021 MultiMC Contributors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include -#include -#include - -#include "../Version.h" -#include "net/NetJob.h" - -namespace ModPlatform { -class ListModel; -struct IndexedPack; -} - -class ModAPI { - protected: - using CallerType = ModPlatform::ListModel; - - public: - virtual ~ModAPI() = default; - - enum ModLoaderType { - Unspecified = 0, - Forge = 1 << 0, - Cauldron = 1 << 1, - LiteLoader = 1 << 2, - Fabric = 1 << 3, - Quilt = 1 << 4 - }; - Q_DECLARE_FLAGS(ModLoaderTypes, ModLoaderType) - - struct SearchArgs { - int offset; - QString search; - QString sorting; - ModLoaderTypes loaders; - std::list versions; - }; - - virtual void searchMods(CallerType* caller, SearchArgs&& args) const = 0; - virtual void getModInfo(ModPlatform::IndexedPack& pack, std::function callback) = 0; - - virtual auto getProject(QString addonId, QByteArray* response) const -> NetJob* = 0; - virtual auto getProjects(QStringList addonIds, QByteArray* response) const -> NetJob* = 0; - - - struct VersionSearchArgs { - QString addonId; - std::list mcVersions; - ModLoaderTypes loaders; - }; - - virtual void getVersions(VersionSearchArgs&& args, std::function callback) const = 0; - - static auto getModLoaderString(ModLoaderType type) -> const QString { - switch (type) { - case Unspecified: - break; - case Forge: - return "forge"; - case Cauldron: - return "cauldron"; - case LiteLoader: - return "liteloader"; - case Fabric: - return "fabric"; - case Quilt: - return "quilt"; - } - return ""; - } - - protected: - inline auto getGameVersionsString(std::list mcVersions) const -> QString - { - QString s; - for(auto& ver : mcVersions){ - s += QString("\"%1\",").arg(ver.toString()); - } - s.remove(s.length() - 1, 1); //remove last comma - return s; - } -}; diff --git a/launcher/modplatform/ModIndex.cpp b/launcher/modplatform/ModIndex.cpp index 34fd9f30..6a507caf 100644 --- a/launcher/modplatform/ModIndex.cpp +++ b/launcher/modplatform/ModIndex.cpp @@ -24,47 +24,47 @@ namespace ModPlatform { -auto ProviderCapabilities::name(Provider p) -> const char* +auto ProviderCapabilities::name(ResourceProvider p) -> const char* { switch (p) { - case Provider::MODRINTH: + case ResourceProvider::MODRINTH: return "modrinth"; - case Provider::FLAME: + case ResourceProvider::FLAME: return "curseforge"; } return {}; } -auto ProviderCapabilities::readableName(Provider p) -> QString +auto ProviderCapabilities::readableName(ResourceProvider p) -> QString { switch (p) { - case Provider::MODRINTH: + case ResourceProvider::MODRINTH: return "Modrinth"; - case Provider::FLAME: + case ResourceProvider::FLAME: return "CurseForge"; } return {}; } -auto ProviderCapabilities::hashType(Provider p) -> QStringList +auto ProviderCapabilities::hashType(ResourceProvider p) -> QStringList { switch (p) { - case Provider::MODRINTH: + case ResourceProvider::MODRINTH: return { "sha512", "sha1" }; - case Provider::FLAME: + case ResourceProvider::FLAME: // Try newer formats first, fall back to old format return { "sha1", "md5", "murmur2" }; } return {}; } -auto ProviderCapabilities::hash(Provider p, QIODevice* device, QString type) -> QString +auto ProviderCapabilities::hash(ResourceProvider p, QIODevice* device, QString type) -> QString { QCryptographicHash::Algorithm algo = QCryptographicHash::Sha1; switch (p) { - case Provider::MODRINTH: { + case ResourceProvider::MODRINTH: { algo = (type == "sha1") ? QCryptographicHash::Sha1 : QCryptographicHash::Sha512; break; } - case Provider::FLAME: + case ResourceProvider::FLAME: algo = (type == "sha1") ? QCryptographicHash::Sha1 : QCryptographicHash::Md5; break; } diff --git a/launcher/modplatform/ModIndex.h b/launcher/modplatform/ModIndex.h index 518fed7c..f65a6a4b 100644 --- a/launcher/modplatform/ModIndex.h +++ b/launcher/modplatform/ModIndex.h @@ -28,17 +28,16 @@ class QIODevice; namespace ModPlatform { -enum class Provider { - MODRINTH, - FLAME -}; +enum class ResourceProvider { MODRINTH, FLAME }; + +enum class ResourceType { MOD, RESOURCE_PACK }; class ProviderCapabilities { public: - auto name(Provider) -> const char*; - auto readableName(Provider) -> QString; - auto hashType(Provider) -> QStringList; - auto hash(Provider, QIODevice*, QString type = "") -> QString; + auto name(ResourceProvider) -> const char*; + auto readableName(ResourceProvider) -> QString; + auto hashType(ResourceProvider) -> QStringList; + auto hash(ResourceProvider, QIODevice*, QString type = "") -> QString; }; struct ModpackAuthor { @@ -81,7 +80,7 @@ struct ExtraPackData { struct IndexedPack { QVariant addonId; - Provider provider; + ResourceProvider provider; QString name; QString slug; QString description; @@ -101,4 +100,4 @@ struct IndexedPack { } // namespace ModPlatform Q_DECLARE_METATYPE(ModPlatform::IndexedPack) -Q_DECLARE_METATYPE(ModPlatform::Provider) +Q_DECLARE_METATYPE(ModPlatform::ResourceProvider) diff --git a/launcher/modplatform/ResourceAPI.h b/launcher/modplatform/ResourceAPI.h new file mode 100644 index 00000000..d18a2caa --- /dev/null +++ b/launcher/modplatform/ResourceAPI.h @@ -0,0 +1,149 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (C) 2022 Sefa Eyeoglu + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * This file incorporates work covered by the following copyright and + * permission notice: + * + * Copyright 2013-2021 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include + +#include + +#include "../Version.h" + +#include "modplatform/ModIndex.h" +#include "net/NetJob.h" + +/* Simple class with a common interface for interacting with APIs */ +class ResourceAPI { + public: + virtual ~ResourceAPI() = default; + + enum ModLoaderType { Forge = 1 << 0, Cauldron = 1 << 1, LiteLoader = 1 << 2, Fabric = 1 << 3, Quilt = 1 << 4 }; + Q_DECLARE_FLAGS(ModLoaderTypes, ModLoaderType) + + struct SearchArgs { + ModPlatform::ResourceType type{}; + int offset = 0; + + std::optional search; + std::optional sorting; + std::optional loaders; + std::optional > versions; + }; + struct SearchCallbacks { + std::function on_succeed; + std::function on_fail; + std::function on_abort; + }; + + struct VersionSearchArgs { + QString addonId; + + std::optional > mcVersions; + std::optional loaders; + }; + struct VersionSearchCallbacks { + std::function on_succeed; + }; + + struct ProjectInfoArgs { + ModPlatform::IndexedPack& pack; + + void operator=(ProjectInfoArgs other) { pack = other.pack; } + }; + struct ProjectInfoCallbacks { + std::function on_succeed; + }; + + public slots: + [[nodiscard]] virtual NetJob::Ptr searchProjects(SearchArgs&&, SearchCallbacks&&) const + { + qWarning() << "TODO"; + return nullptr; + } + [[nodiscard]] virtual NetJob::Ptr getProject(QString addonId, QByteArray* response) const + { + qWarning() << "TODO"; + return nullptr; + } + [[nodiscard]] virtual NetJob::Ptr getProjects(QStringList addonIds, QByteArray* response) const + { + qWarning() << "TODO"; + return nullptr; + } + + [[nodiscard]] virtual NetJob::Ptr getProjectInfo(ProjectInfoArgs&&, ProjectInfoCallbacks&&) const + { + qWarning() << "TODO"; + return nullptr; + } + [[nodiscard]] virtual NetJob::Ptr getProjectVersions(VersionSearchArgs&&, VersionSearchCallbacks&&) const + { + qWarning() << "TODO"; + return nullptr; + } + + static auto getModLoaderString(ModLoaderType type) -> const QString + { + switch (type) { + case Forge: + return "forge"; + case Cauldron: + return "cauldron"; + case LiteLoader: + return "liteloader"; + case Fabric: + return "fabric"; + case Quilt: + return "quilt"; + default: + break; + } + return ""; + } + + protected: + [[nodiscard]] inline QString debugName() const { return "External resource API"; } + + [[nodiscard]] inline auto getGameVersionsString(std::list mcVersions) const -> QString + { + QString s; + for (auto& ver : mcVersions) { + s += QString("\"%1\",").arg(ver.toString()); + } + s.remove(s.length() - 1, 1); // remove last comma + return s; + } +}; diff --git a/launcher/modplatform/flame/FlameAPI.cpp b/launcher/modplatform/flame/FlameAPI.cpp index 4d71da21..ae401399 100644 --- a/launcher/modplatform/flame/FlameAPI.cpp +++ b/launcher/modplatform/flame/FlameAPI.cpp @@ -106,13 +106,19 @@ auto FlameAPI::getModDescription(int modId) -> QString auto FlameAPI::getLatestVersion(VersionSearchArgs&& args) -> ModPlatform::IndexedVersion { + auto versions_url_optional = getVersionsURL(args); + if (!versions_url_optional.has_value()) + return {}; + + auto versions_url = versions_url_optional.value(); + QEventLoop loop; auto netJob = new NetJob(QString("Flame::GetLatestVersion(%1)").arg(args.addonId), APPLICATION->network()); auto response = new QByteArray(); ModPlatform::IndexedVersion ver; - netJob->addNetAction(Net::Download::makeByteArray(getVersionsURL(args), response)); + netJob->addNetAction(Net::Download::makeByteArray(versions_url, response)); QObject::connect(netJob, &NetJob::succeeded, [response, args, &ver] { QJsonParseError parse_error{}; @@ -161,7 +167,7 @@ auto FlameAPI::getLatestVersion(VersionSearchArgs&& args) -> ModPlatform::Indexe return ver; } -auto FlameAPI::getProjects(QStringList addonIds, QByteArray* response) const -> NetJob* +NetJob::Ptr FlameAPI::getProjects(QStringList addonIds, QByteArray* response) const { auto* netJob = new NetJob(QString("Flame::GetProjects"), APPLICATION->network()); @@ -178,13 +184,13 @@ auto FlameAPI::getProjects(QStringList addonIds, QByteArray* response) const -> netJob->addNetAction(Net::Upload::makeByteArray(QString("https://api.curseforge.com/v1/mods"), response, body_raw)); - QObject::connect(netJob, &NetJob::finished, [response, netJob] { delete response; netJob->deleteLater(); }); + QObject::connect(netJob, &NetJob::finished, [response] { delete response; }); QObject::connect(netJob, &NetJob::failed, [body_raw] { qDebug() << body_raw; }); return netJob; } -auto FlameAPI::getFiles(const QStringList& fileIds, QByteArray* response) const -> NetJob* +NetJob::Ptr FlameAPI::getFiles(const QStringList& fileIds, QByteArray* response) const { auto* netJob = new NetJob(QString("Flame::GetFiles"), APPLICATION->network()); @@ -201,7 +207,7 @@ auto FlameAPI::getFiles(const QStringList& fileIds, QByteArray* response) const netJob->addNetAction(Net::Upload::makeByteArray(QString("https://api.curseforge.com/v1/mods/files"), response, body_raw)); - QObject::connect(netJob, &NetJob::finished, [response, netJob] { delete response; netJob->deleteLater(); }); + QObject::connect(netJob, &NetJob::finished, [response] { delete response; }); QObject::connect(netJob, &NetJob::failed, [body_raw] { qDebug() << body_raw; }); return netJob; diff --git a/launcher/modplatform/flame/FlameAPI.h b/launcher/modplatform/flame/FlameAPI.h index 4c6ca64c..114a2716 100644 --- a/launcher/modplatform/flame/FlameAPI.h +++ b/launcher/modplatform/flame/FlameAPI.h @@ -1,21 +1,21 @@ #pragma once #include "modplatform/ModIndex.h" -#include "modplatform/helpers/NetworkModAPI.h" +#include "modplatform/helpers/NetworkResourceAPI.h" -class FlameAPI : public NetworkModAPI { +class FlameAPI : public NetworkResourceAPI { public: - auto matchFingerprints(const QList& fingerprints, QByteArray* response) -> NetJob::Ptr; auto getModFileChangelog(int modId, int fileId) -> QString; auto getModDescription(int modId) -> QString; auto getLatestVersion(VersionSearchArgs&& args) -> ModPlatform::IndexedVersion; - auto getProjects(QStringList addonIds, QByteArray* response) const -> NetJob* override; - auto getFiles(const QStringList& fileIds, QByteArray* response) const -> NetJob*; + NetJob::Ptr getProjects(QStringList addonIds, QByteArray* response) const override; + NetJob::Ptr matchFingerprints(const QList& fingerprints, QByteArray* response); + NetJob::Ptr getFiles(const QStringList& fileIds, QByteArray* response) const; private: - inline auto getSortFieldInt(QString sortString) const -> int + static int getSortFieldInt(QString const& sortString) { return sortString == "Featured" ? 1 : sortString == "Popularity" ? 2 @@ -28,48 +28,16 @@ class FlameAPI : public NetworkModAPI { : 1; } - private: - inline auto getModSearchURL(SearchArgs& args) const -> QString override - { - auto gameVersionStr = args.versions.size() != 0 ? QString("gameVersion=%1").arg(args.versions.front().toString()) : QString(); - - return QString( - "https://api.curseforge.com/v1/mods/search?" - "gameId=432&" - "classId=6&" - - "index=%1&" - "pageSize=25&" - "searchFilter=%2&" - "sortField=%3&" - "sortOrder=desc&" - "modLoaderType=%4&" - "%5") - .arg(args.offset) - .arg(args.search) - .arg(getSortFieldInt(args.sorting)) - .arg(getMappedModLoader(args.loaders)) - .arg(gameVersionStr); - }; - - inline auto getModInfoURL(QString& id) const -> QString override + static int getClassId(ModPlatform::ResourceType type) { - return QString("https://api.curseforge.com/v1/mods/%1").arg(id); - }; - - inline auto getVersionsURL(VersionSearchArgs& args) const -> QString override - { - QString gameVersionQuery = args.mcVersions.size() == 1 ? QString("gameVersion=%1&").arg(args.mcVersions.front().toString()) : ""; - QString modLoaderQuery = QString("modLoaderType=%1&").arg(getMappedModLoader(args.loaders)); - - return QString("https://api.curseforge.com/v1/mods/%1/files?pageSize=10000&%2%3") - .arg(args.addonId) - .arg(gameVersionQuery) - .arg(modLoaderQuery); - }; + switch (type) { + default: + case ModPlatform::ResourceType::MOD: + return 6; + } + } - public: - static auto getMappedModLoader(const ModLoaderTypes loaders) -> int + static int getMappedModLoader(ModLoaderTypes loaders) { // https://docs.curseforge.com/?http#tocS_ModLoaderType if (loaders & Forge) @@ -81,4 +49,43 @@ class FlameAPI : public NetworkModAPI { return 4; // Quilt would probably be 5 return 0; } + + private: + [[nodiscard]] std::optional getSearchURL(SearchArgs const& args) const override + { + auto gameVersionStr = args.versions.has_value() ? QString("gameVersion=%1").arg(args.versions.value().front().toString()) : QString(); + + QStringList get_arguments; + get_arguments.append(QString("classId=%1").arg(getClassId(args.type))); + get_arguments.append(QString("index=%1").arg(args.offset)); + get_arguments.append("pageSize=25"); + if (args.search.has_value()) + get_arguments.append(QString("searchFilter=%1").arg(args.search.value())); + if (args.sorting.has_value()) + get_arguments.append(QString("sortField=%1").arg(getSortFieldInt(args.sorting.value()))); + get_arguments.append("sortOrder=desc"); + if (args.loaders.has_value()) + get_arguments.append(QString("modLoaderType=%1").arg(getMappedModLoader(args.loaders.value()))); + get_arguments.append(gameVersionStr); + + return "https://api.curseforge.com/v1/mods/search?gameId=432&" + get_arguments.join('&'); + }; + + [[nodiscard]] std::optional getInfoURL(QString const& id) const override + { + return QString("https://api.curseforge.com/v1/mods/%1").arg(id); + }; + + [[nodiscard]] std::optional getVersionsURL(VersionSearchArgs const& args) const override + { + QString url{QString("https://api.curseforge.com/v1/mods/%1/files?pageSize=10000&").arg(args.addonId)}; + + QStringList get_parameters; + if (args.mcVersions.has_value()) + get_parameters.append(QString("gameVersion=%1").arg(args.mcVersions.value().front().toString())); + if (args.loaders.has_value()) + get_parameters.append(QString("modLoaderType=%1").arg(getMappedModLoader(args.loaders.value()))); + + return url + get_parameters.join('&'); + }; }; diff --git a/launcher/modplatform/flame/FlameCheckUpdate.cpp b/launcher/modplatform/flame/FlameCheckUpdate.cpp index 8dd3a846..285fa49f 100644 --- a/launcher/modplatform/flame/FlameCheckUpdate.cpp +++ b/launcher/modplatform/flame/FlameCheckUpdate.cpp @@ -7,7 +7,10 @@ #include "FileSystem.h" #include "Json.h" -#include "ModDownloadTask.h" +#include "ResourceDownloadTask.h" + +#include "minecraft/mod/ModFolderModel.h" +#include "minecraft/mod/ResourceFolderModel.h" static FlameAPI api; @@ -160,7 +163,7 @@ void FlameCheckUpdate::executeTask() for (auto& author : mod->authors()) pack.authors.append({ author }); pack.description = mod->description(); - pack.provider = ModPlatform::Provider::FLAME; + pack.provider = ModPlatform::ResourceProvider::FLAME; auto old_version = mod->version(); if (old_version.isEmpty() && mod->status() != ModStatus::NotInstalled) { @@ -168,10 +171,10 @@ void FlameCheckUpdate::executeTask() old_version = current_ver.version; } - auto download_task = new ModDownloadTask(pack, latest_ver, m_mods_folder); + auto download_task = new ResourceDownloadTask(pack, latest_ver, m_mods_folder); m_updatable.emplace_back(pack.name, mod->metadata()->hash, old_version, latest_ver.version, api.getModFileChangelog(latest_ver.addonId.toInt(), latest_ver.fileId.toInt()), - ModPlatform::Provider::FLAME, download_task); + ModPlatform::ResourceProvider::FLAME, download_task); } } diff --git a/launcher/modplatform/flame/FlameCheckUpdate.h b/launcher/modplatform/flame/FlameCheckUpdate.h index 163c706c..4a98d684 100644 --- a/launcher/modplatform/flame/FlameCheckUpdate.h +++ b/launcher/modplatform/flame/FlameCheckUpdate.h @@ -8,7 +8,7 @@ class FlameCheckUpdate : public CheckUpdateTask { Q_OBJECT public: - FlameCheckUpdate(QList& mods, std::list& mcVersions, ModAPI::ModLoaderTypes loaders, std::shared_ptr mods_folder) + FlameCheckUpdate(QList& mods, std::list& mcVersions, std::optional loaders, std::shared_ptr mods_folder) : CheckUpdateTask(mods, mcVersions, loaders, mods_folder) {} diff --git a/launcher/modplatform/flame/FlameInstanceCreationTask.cpp b/launcher/modplatform/flame/FlameInstanceCreationTask.cpp index dc69769a..fb6f78e8 100644 --- a/launcher/modplatform/flame/FlameInstanceCreationTask.cpp +++ b/launcher/modplatform/flame/FlameInstanceCreationTask.cpp @@ -183,7 +183,7 @@ bool FlameCreationTask::updateInstance() QEventLoop loop; - connect(job, &NetJob::succeeded, this, [this, raw_response, fileIds, old_inst_dir, &old_files, old_minecraft_dir] { + connect(job.get(), &NetJob::succeeded, this, [this, raw_response, fileIds, old_inst_dir, &old_files, old_minecraft_dir] { // Parse the API response QJsonParseError parse_error{}; auto doc = QJsonDocument::fromJson(*raw_response, &parse_error); @@ -225,7 +225,7 @@ bool FlameCreationTask::updateInstance() m_files_to_remove.append(old_minecraft_dir.absoluteFilePath(relative_path)); } }); - connect(job, &NetJob::finished, &loop, &QEventLoop::quit); + connect(job.get(), &NetJob::finished, &loop, &QEventLoop::quit); m_process_update_file_info_job = job; job->start(); diff --git a/launcher/modplatform/flame/FlameInstanceCreationTask.h b/launcher/modplatform/flame/FlameInstanceCreationTask.h index 498e1d6e..36b62e3e 100644 --- a/launcher/modplatform/flame/FlameInstanceCreationTask.h +++ b/launcher/modplatform/flame/FlameInstanceCreationTask.h @@ -86,7 +86,7 @@ class FlameCreationTask final : public InstanceCreationTask { Flame::Manifest m_pack; // Handle to allow aborting - NetJob* m_process_update_file_info_job = nullptr; + NetJob::Ptr m_process_update_file_info_job = nullptr; NetJob::Ptr m_files_job = nullptr; QString m_managed_id, m_managed_version_id; diff --git a/launcher/modplatform/flame/FlameModIndex.cpp b/launcher/modplatform/flame/FlameModIndex.cpp index 32aa4bdb..617b98ce 100644 --- a/launcher/modplatform/flame/FlameModIndex.cpp +++ b/launcher/modplatform/flame/FlameModIndex.cpp @@ -11,7 +11,7 @@ static ModPlatform::ProviderCapabilities ProviderCaps; void FlameMod::loadIndexedPack(ModPlatform::IndexedPack& pack, QJsonObject& obj) { pack.addonId = Json::requireInteger(obj, "id"); - pack.provider = ModPlatform::Provider::FLAME; + pack.provider = ModPlatform::ResourceProvider::FLAME; pack.name = Json::requireString(obj, "name"); pack.slug = Json::requireString(obj, "slug"); pack.websiteUrl = Json::ensureString(Json::ensureObject(obj, "links"), "websiteUrl", ""); @@ -127,7 +127,7 @@ auto FlameMod::loadIndexedPackVersion(QJsonObject& obj, bool load_changelog) -> auto hash_list = Json::ensureArray(obj, "hashes"); for (auto h : hash_list) { auto hash_entry = Json::ensureObject(h); - auto hash_types = ProviderCaps.hashType(ModPlatform::Provider::FLAME); + auto hash_types = ProviderCaps.hashType(ModPlatform::ResourceProvider::FLAME); auto hash_algo = enumToString(Json::ensureInteger(hash_entry, "algo", 1, "algorithm")); if (hash_types.contains(hash_algo)) { file.hash = Json::requireString(hash_entry, "value"); diff --git a/launcher/modplatform/helpers/HashUtils.cpp b/launcher/modplatform/helpers/HashUtils.cpp index f1e4759e..af484be0 100644 --- a/launcher/modplatform/helpers/HashUtils.cpp +++ b/launcher/modplatform/helpers/HashUtils.cpp @@ -12,12 +12,12 @@ namespace Hashing { static ModPlatform::ProviderCapabilities ProviderCaps; -Hasher::Ptr createHasher(QString file_path, ModPlatform::Provider provider) +Hasher::Ptr createHasher(QString file_path, ModPlatform::ResourceProvider provider) { switch (provider) { - case ModPlatform::Provider::MODRINTH: + case ModPlatform::ResourceProvider::MODRINTH: return createModrinthHasher(file_path); - case ModPlatform::Provider::FLAME: + case ModPlatform::ResourceProvider::FLAME: return createFlameHasher(file_path); default: qCritical() << "[Hashing]" @@ -36,12 +36,12 @@ Hasher::Ptr createFlameHasher(QString file_path) return new FlameHasher(file_path); } -Hasher::Ptr createBlockedModHasher(QString file_path, ModPlatform::Provider provider) +Hasher::Ptr createBlockedModHasher(QString file_path, ModPlatform::ResourceProvider provider) { return new BlockedModHasher(file_path, provider); } -Hasher::Ptr createBlockedModHasher(QString file_path, ModPlatform::Provider provider, QString type) +Hasher::Ptr createBlockedModHasher(QString file_path, ModPlatform::ResourceProvider provider, QString type) { auto hasher = new BlockedModHasher(file_path, provider); hasher->useHashType(type); @@ -62,8 +62,8 @@ void ModrinthHasher::executeTask() return; } - auto hash_type = ProviderCaps.hashType(ModPlatform::Provider::MODRINTH).first(); - m_hash = ProviderCaps.hash(ModPlatform::Provider::MODRINTH, &file, hash_type); + auto hash_type = ProviderCaps.hashType(ModPlatform::ResourceProvider::MODRINTH).first(); + m_hash = ProviderCaps.hash(ModPlatform::ResourceProvider::MODRINTH, &file, hash_type); file.close(); @@ -92,7 +92,7 @@ void FlameHasher::executeTask() } -BlockedModHasher::BlockedModHasher(QString file_path, ModPlatform::Provider provider) +BlockedModHasher::BlockedModHasher(QString file_path, ModPlatform::ResourceProvider provider) : Hasher(file_path), provider(provider) { setObjectName(QString("BlockedModHasher: %1").arg(file_path)); hash_type = ProviderCaps.hashType(provider).first(); diff --git a/launcher/modplatform/helpers/HashUtils.h b/launcher/modplatform/helpers/HashUtils.h index fa3244f6..91146a52 100644 --- a/launcher/modplatform/helpers/HashUtils.h +++ b/launcher/modplatform/helpers/HashUtils.h @@ -42,21 +42,21 @@ class ModrinthHasher : public Hasher { class BlockedModHasher : public Hasher { public: - BlockedModHasher(QString file_path, ModPlatform::Provider provider); + BlockedModHasher(QString file_path, ModPlatform::ResourceProvider provider); void executeTask() override; QStringList getHashTypes(); bool useHashType(QString type); private: - ModPlatform::Provider provider; + ModPlatform::ResourceProvider provider; QString hash_type; }; -Hasher::Ptr createHasher(QString file_path, ModPlatform::Provider provider); +Hasher::Ptr createHasher(QString file_path, ModPlatform::ResourceProvider provider); Hasher::Ptr createFlameHasher(QString file_path); Hasher::Ptr createModrinthHasher(QString file_path); -Hasher::Ptr createBlockedModHasher(QString file_path, ModPlatform::Provider provider); -Hasher::Ptr createBlockedModHasher(QString file_path, ModPlatform::Provider provider, QString type); +Hasher::Ptr createBlockedModHasher(QString file_path, ModPlatform::ResourceProvider provider); +Hasher::Ptr createBlockedModHasher(QString file_path, ModPlatform::ResourceProvider provider, QString type); } // namespace Hashing diff --git a/launcher/modplatform/helpers/NetworkModAPI.cpp b/launcher/modplatform/helpers/NetworkModAPI.cpp deleted file mode 100644 index 7633030e..00000000 --- a/launcher/modplatform/helpers/NetworkModAPI.cpp +++ /dev/null @@ -1,97 +0,0 @@ -#include "NetworkModAPI.h" - -#include "ui/pages/modplatform/ModModel.h" - -#include "Application.h" -#include "net/NetJob.h" - -void NetworkModAPI::searchMods(CallerType* caller, SearchArgs&& args) const -{ - auto netJob = new NetJob(QString("%1::Search").arg(caller->debugName()), APPLICATION->network()); - auto searchUrl = getModSearchURL(args); - - auto response = new QByteArray(); - netJob->addNetAction(Net::Download::makeByteArray(QUrl(searchUrl), response)); - - QObject::connect(netJob, &NetJob::started, caller, [caller, netJob] { caller->setActiveJob(netJob); }); - QObject::connect(netJob, &NetJob::failed, caller, &CallerType::searchRequestFailed); - QObject::connect(netJob, &NetJob::aborted, caller, &CallerType::searchRequestAborted); - QObject::connect(netJob, &NetJob::succeeded, caller, [caller, response] { - QJsonParseError parse_error{}; - QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error); - if (parse_error.error != QJsonParseError::NoError) { - qWarning() << "Error while parsing JSON response from " << caller->debugName() << " at " << parse_error.offset - << " reason: " << parse_error.errorString(); - qWarning() << *response; - return; - } - - caller->searchRequestFinished(doc); - }); - - netJob->start(); -} - -void NetworkModAPI::getModInfo(ModPlatform::IndexedPack& pack, std::function callback) -{ - auto response = new QByteArray(); - auto job = getProject(pack.addonId.toString(), response); - - QObject::connect(job, &NetJob::succeeded, [callback, &pack, response] { - QJsonParseError parse_error{}; - QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error); - if (parse_error.error != QJsonParseError::NoError) { - qWarning() << "Error while parsing JSON response for mod info at " << parse_error.offset - << " reason: " << parse_error.errorString(); - qWarning() << *response; - return; - } - - callback(doc, pack); - }); - - job->start(); -} - -void NetworkModAPI::getVersions(VersionSearchArgs&& args, std::function callback) const -{ - auto netJob = new NetJob(QString("ModVersions(%2)").arg(args.addonId), APPLICATION->network()); - auto response = new QByteArray(); - - netJob->addNetAction(Net::Download::makeByteArray(getVersionsURL(args), response)); - - QObject::connect(netJob, &NetJob::succeeded, [response, callback, args] { - QJsonParseError parse_error{}; - QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error); - if (parse_error.error != QJsonParseError::NoError) { - qWarning() << "Error while parsing JSON response for getting versions at " << parse_error.offset - << " reason: " << parse_error.errorString(); - qWarning() << *response; - return; - } - - callback(doc, args.addonId); - }); - - QObject::connect(netJob, &NetJob::finished, [response, netJob] { - netJob->deleteLater(); - delete response; - }); - - netJob->start(); -} - -auto NetworkModAPI::getProject(QString addonId, QByteArray* response) const -> NetJob* -{ - auto netJob = new NetJob(QString("%1::GetProject").arg(addonId), APPLICATION->network()); - auto searchUrl = getModInfoURL(addonId); - - netJob->addNetAction(Net::Download::makeByteArray(QUrl(searchUrl), response)); - - QObject::connect(netJob, &NetJob::finished, [response, netJob] { - netJob->deleteLater(); - delete response; - }); - - return netJob; -} diff --git a/launcher/modplatform/helpers/NetworkModAPI.h b/launcher/modplatform/helpers/NetworkModAPI.h deleted file mode 100644 index b8af22c7..00000000 --- a/launcher/modplatform/helpers/NetworkModAPI.h +++ /dev/null @@ -1,17 +0,0 @@ -#pragma once - -#include "modplatform/ModAPI.h" - -class NetworkModAPI : public ModAPI { - public: - void searchMods(CallerType* caller, SearchArgs&& args) const override; - void getModInfo(ModPlatform::IndexedPack& pack, std::function callback) override; - void getVersions(VersionSearchArgs&& args, std::function callback) const override; - - auto getProject(QString addonId, QByteArray* response) const -> NetJob* override; - - protected: - virtual auto getModSearchURL(SearchArgs& args) const -> QString = 0; - virtual auto getModInfoURL(QString& id) const -> QString = 0; - virtual auto getVersionsURL(VersionSearchArgs& args) const -> QString = 0; -}; diff --git a/launcher/modplatform/helpers/NetworkResourceAPI.cpp b/launcher/modplatform/helpers/NetworkResourceAPI.cpp new file mode 100644 index 00000000..eb17008c --- /dev/null +++ b/launcher/modplatform/helpers/NetworkResourceAPI.cpp @@ -0,0 +1,124 @@ +#include "NetworkResourceAPI.h" + +#include "Application.h" +#include "net/NetJob.h" + +#include "modplatform/ModIndex.h" + +NetJob::Ptr NetworkResourceAPI::searchProjects(SearchArgs&& args, SearchCallbacks&& callbacks) const +{ + auto search_url_optional = getSearchURL(args); + if (!search_url_optional.has_value()) { + callbacks.on_fail("Failed to create search URL", -1); + return nullptr; + } + + auto search_url = search_url_optional.value(); + + auto response = new QByteArray(); + auto netJob = new NetJob(QString("%1::Search").arg(debugName()), APPLICATION->network()); + + netJob->addNetAction(Net::Download::makeByteArray(QUrl(search_url), response)); + + QObject::connect(netJob, &NetJob::succeeded, [=]{ + QJsonParseError parse_error{}; + QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error); + if (parse_error.error != QJsonParseError::NoError) { + qWarning() << "Error while parsing JSON response from " << debugName() << " at " << parse_error.offset + << " reason: " << parse_error.errorString(); + qWarning() << *response; + + callbacks.on_fail(parse_error.errorString(), -1); + + return; + } + + callbacks.on_succeed(doc); + }); + + QObject::connect(netJob, &NetJob::failed, [=](QString reason){ + int network_error_code = -1; + if (auto* failed_action = netJob->getFailedActions().at(0); failed_action && failed_action->m_reply) + network_error_code = failed_action->m_reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + + callbacks.on_fail(reason, network_error_code); + }); + QObject::connect(netJob, &NetJob::aborted, [=]{ + callbacks.on_abort(); + }); + + return netJob; +} + +NetJob::Ptr NetworkResourceAPI::getProjectInfo(ProjectInfoArgs&& args, ProjectInfoCallbacks&& callbacks) const +{ + auto response = new QByteArray(); + auto job = getProject(args.pack.addonId.toString(), response); + + QObject::connect(job.get(), &NetJob::succeeded, [response, callbacks, args] { + QJsonParseError parse_error{}; + QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error); + if (parse_error.error != QJsonParseError::NoError) { + qWarning() << "Error while parsing JSON response for mod info at " << parse_error.offset + << " reason: " << parse_error.errorString(); + qWarning() << *response; + return; + } + + callbacks.on_succeed(doc, args.pack); + }); + + return job; +} + +NetJob::Ptr NetworkResourceAPI::getProjectVersions(VersionSearchArgs&& args, VersionSearchCallbacks&& callbacks) const +{ + auto versions_url_optional = getVersionsURL(args); + if (!versions_url_optional.has_value()) + return nullptr; + + auto versions_url = versions_url_optional.value(); + + auto netJob = new NetJob(QString("%1::Versions").arg(args.addonId), APPLICATION->network()); + auto response = new QByteArray(); + + netJob->addNetAction(Net::Download::makeByteArray(versions_url, response)); + + QObject::connect(netJob, &NetJob::succeeded, [=] { + QJsonParseError parse_error{}; + QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error); + if (parse_error.error != QJsonParseError::NoError) { + qWarning() << "Error while parsing JSON response for getting versions at " << parse_error.offset + << " reason: " << parse_error.errorString(); + qWarning() << *response; + return; + } + + callbacks.on_succeed(doc, args.addonId); + }); + + QObject::connect(netJob, &NetJob::finished, [response] { + delete response; + }); + + return netJob; +} + +NetJob::Ptr NetworkResourceAPI::getProject(QString addonId, QByteArray* response) const +{ + auto project_url_optional = getInfoURL(addonId); + if (!project_url_optional.has_value()) + return nullptr; + + auto project_url = project_url_optional.value(); + + auto netJob = new NetJob(QString("%1::GetProject").arg(addonId), APPLICATION->network()); + + netJob->addNetAction(Net::Download::makeByteArray(QUrl(project_url), response)); + + QObject::connect(netJob, &NetJob::finished, [response] { + delete response; + }); + + return netJob; +} diff --git a/launcher/modplatform/helpers/NetworkResourceAPI.h b/launcher/modplatform/helpers/NetworkResourceAPI.h new file mode 100644 index 00000000..834f274a --- /dev/null +++ b/launcher/modplatform/helpers/NetworkResourceAPI.h @@ -0,0 +1,18 @@ +#pragma once + +#include "modplatform/ResourceAPI.h" + +class NetworkResourceAPI : public ResourceAPI { + public: + NetJob::Ptr searchProjects(SearchArgs&&, SearchCallbacks&&) const override; + + NetJob::Ptr getProject(QString addonId, QByteArray* response) const override; + + NetJob::Ptr getProjectInfo(ProjectInfoArgs&&, ProjectInfoCallbacks&&) const override; + NetJob::Ptr getProjectVersions(VersionSearchArgs&&, VersionSearchCallbacks&&) const override; + + protected: + [[nodiscard]] virtual auto getSearchURL(SearchArgs const& args) const -> std::optional = 0; + [[nodiscard]] virtual auto getInfoURL(QString const& id) const -> std::optional = 0; + [[nodiscard]] virtual auto getVersionsURL(VersionSearchArgs const& args) const -> std::optional = 0; +}; diff --git a/launcher/modplatform/modrinth/ModrinthAPI.cpp b/launcher/modplatform/modrinth/ModrinthAPI.cpp index 747cf4c3..8e64be09 100644 --- a/launcher/modplatform/modrinth/ModrinthAPI.cpp +++ b/launcher/modplatform/modrinth/ModrinthAPI.cpp @@ -37,21 +37,24 @@ auto ModrinthAPI::currentVersions(const QStringList& hashes, QString hash_format auto ModrinthAPI::latestVersion(QString hash, QString hash_format, - std::list mcVersions, - ModLoaderTypes loaders, + std::optional> mcVersions, + std::optional loaders, QByteArray* response) -> NetJob::Ptr { auto* netJob = new NetJob(QString("Modrinth::GetLatestVersion"), APPLICATION->network()); QJsonObject body_obj; - Json::writeStringList(body_obj, "loaders", getModLoaderStrings(loaders)); + if (loaders.has_value()) + Json::writeStringList(body_obj, "loaders", getModLoaderStrings(loaders.value())); - QStringList game_versions; - for (auto& ver : mcVersions) { - game_versions.append(ver.toString()); + if (mcVersions.has_value()) { + QStringList game_versions; + for (auto& ver : mcVersions.value()) { + game_versions.append(ver.toString()); + } + Json::writeStringList(body_obj, "game_versions", game_versions); } - Json::writeStringList(body_obj, "game_versions", game_versions); QJsonDocument body(body_obj); auto body_raw = body.toJson(); @@ -66,8 +69,8 @@ auto ModrinthAPI::latestVersion(QString hash, auto ModrinthAPI::latestVersions(const QStringList& hashes, QString hash_format, - std::list mcVersions, - ModLoaderTypes loaders, + std::optional> mcVersions, + std::optional loaders, QByteArray* response) -> NetJob::Ptr { auto* netJob = new NetJob(QString("Modrinth::GetLatestVersions"), APPLICATION->network()); @@ -77,13 +80,16 @@ auto ModrinthAPI::latestVersions(const QStringList& hashes, Json::writeStringList(body_obj, "hashes", hashes); Json::writeString(body_obj, "algorithm", hash_format); - Json::writeStringList(body_obj, "loaders", getModLoaderStrings(loaders)); + if (loaders.has_value()) + Json::writeStringList(body_obj, "loaders", getModLoaderStrings(loaders.value())); - QStringList game_versions; - for (auto& ver : mcVersions) { - game_versions.append(ver.toString()); + if (mcVersions.has_value()) { + QStringList game_versions; + for (auto& ver : mcVersions.value()) { + game_versions.append(ver.toString()); + } + Json::writeStringList(body_obj, "game_versions", game_versions); } - Json::writeStringList(body_obj, "game_versions", game_versions); QJsonDocument body(body_obj); auto body_raw = body.toJson(); @@ -95,7 +101,7 @@ auto ModrinthAPI::latestVersions(const QStringList& hashes, return netJob; } -auto ModrinthAPI::getProjects(QStringList addonIds, QByteArray* response) const -> NetJob* +NetJob::Ptr ModrinthAPI::getProjects(QStringList addonIds, QByteArray* response) const { auto netJob = new NetJob(QString("Modrinth::GetProjects"), APPLICATION->network()); auto searchUrl = getMultipleModInfoURL(addonIds); diff --git a/launcher/modplatform/modrinth/ModrinthAPI.h b/launcher/modplatform/modrinth/ModrinthAPI.h index e1a18681..bd84fb54 100644 --- a/launcher/modplatform/modrinth/ModrinthAPI.h +++ b/launcher/modplatform/modrinth/ModrinthAPI.h @@ -19,13 +19,12 @@ #pragma once #include "BuildConfig.h" -#include "modplatform/ModAPI.h" #include "modplatform/ModIndex.h" -#include "modplatform/helpers/NetworkModAPI.h" +#include "modplatform/helpers/NetworkResourceAPI.h" #include -class ModrinthAPI : public NetworkModAPI { +class ModrinthAPI : public NetworkResourceAPI { public: auto currentVersion(QString hash, QString hash_format, @@ -37,17 +36,17 @@ class ModrinthAPI : public NetworkModAPI { auto latestVersion(QString hash, QString hash_format, - std::list mcVersions, - ModLoaderTypes loaders, + std::optional> mcVersions, + std::optional loaders, QByteArray* response) -> NetJob::Ptr; auto latestVersions(const QStringList& hashes, QString hash_format, - std::list mcVersions, - ModLoaderTypes loaders, + std::optional> mcVersions, + std::optional loaders, QByteArray* response) -> NetJob::Ptr; - auto getProjects(QStringList addonIds, QByteArray* response) const -> NetJob* override; + NetJob::Ptr getProjects(QStringList addonIds, QByteArray* response) const override; public: inline auto getAuthorURL(const QString& name) const -> QString { return "https://modrinth.com/user/" + name; }; @@ -55,15 +54,13 @@ class ModrinthAPI : public NetworkModAPI { static auto getModLoaderStrings(const ModLoaderTypes types) -> const QStringList { QStringList l; - for (auto loader : {Forge, Fabric, Quilt}) - { - if ((types & loader) || types == Unspecified) - { - l << ModAPI::getModLoaderString(loader); + for (auto loader : {Forge, Fabric, Quilt}) { + if (types & loader) { + l << getModLoaderString(loader); } } if ((types & Quilt) && (~types & Fabric)) // Add Fabric if Quilt is in use, if Fabric isn't already there - l << ModAPI::getModLoaderString(Fabric); + l << getModLoaderString(Fabric); return l; } @@ -78,28 +75,54 @@ class ModrinthAPI : public NetworkModAPI { } private: - inline auto getModSearchURL(SearchArgs& args) const -> QString override + [[nodiscard]] static QString resourceTypeParameter(ModPlatform::ResourceType type) { - if (!validateModLoaders(args.loaders)) { - qWarning() << "Modrinth only have Forge and Fabric-compatible mods!"; - return ""; + switch (type) { + case ModPlatform::ResourceType::MOD: + return "mod"; + default: + qWarning() << "Invalid resource type for Modrinth API!"; + break; } - return QString(BuildConfig.MODRINTH_PROD_URL + - "/search?" - "offset=%1&" - "limit=25&" - "query=%2&" - "index=%3&" - "facets=[[%4],%5[\"project_type:mod\"]]") - .arg(args.offset) - .arg(args.search) - .arg(args.sorting) - .arg(getModLoaderFilters(args.loaders)) - .arg(getGameVersionsArray(args.versions)); + return ""; + } + [[nodiscard]] QString createFacets(SearchArgs const& args) const + { + QStringList facets_list; + + if (args.loaders.has_value()) + facets_list.append(QString("[%1]").arg(getModLoaderFilters(args.loaders.value()))); + if (args.versions.has_value()) + facets_list.append(QString("[%1]").arg(getGameVersionsArray(args.versions.value()))); + facets_list.append(QString("[\"project_type:%1\"]").arg(resourceTypeParameter(args.type))); + + return QString("[%1]").arg(facets_list.join(',')); + } + + public: + [[nodiscard]] inline auto getSearchURL(SearchArgs const& args) const -> std::optional override + { + if (args.loaders.has_value()) { + if (!validateModLoaders(args.loaders.value())) { + qWarning() << "Modrinth only have Forge and Fabric-compatible mods!"; + return {}; + } + } + + QStringList get_arguments; + get_arguments.append(QString("offset=%1").arg(args.offset)); + get_arguments.append(QString("limit=25")); + if (args.search.has_value()) + get_arguments.append(QString("query=%1").arg(args.search.value())); + if (args.sorting.has_value()) + get_arguments.append(QString("index=%1").arg(args.sorting.value())); + get_arguments.append(QString("facets=%1").arg(createFacets(args))); + + return BuildConfig.MODRINTH_PROD_URL + "/search?" + get_arguments.join('&'); }; - inline auto getModInfoURL(QString& id) const -> QString override + inline auto getInfoURL(QString const& id) const -> std::optional override { return BuildConfig.MODRINTH_PROD_URL + "/project/" + id; }; @@ -109,15 +132,16 @@ class ModrinthAPI : public NetworkModAPI { return BuildConfig.MODRINTH_PROD_URL + QString("/projects?ids=[\"%1\"]").arg(ids.join("\",\"")); }; - inline auto getVersionsURL(VersionSearchArgs& args) const -> QString override + inline auto getVersionsURL(VersionSearchArgs const& args) const -> std::optional override { - return QString(BuildConfig.MODRINTH_PROD_URL + - "/project/%1/version?" - "game_versions=[%2]&" - "loaders=[\"%3\"]") - .arg(args.addonId, - getGameVersionsString(args.mcVersions), - getModLoaderStrings(args.loaders).join("\",\"")); + QStringList get_arguments; + if (args.mcVersions.has_value()) + get_arguments.append(QString("game_versions=[%1]").arg(getGameVersionsString(args.mcVersions.value()))); + if (args.loaders.has_value()) + get_arguments.append(QString("loaders=[\"%1\"]").arg(getModLoaderStrings(args.loaders.value()).join("\",\""))); + + return QString("%1/project/%2/version%3%4") + .arg(BuildConfig.MODRINTH_PROD_URL, args.addonId, get_arguments.isEmpty() ? "" : "?", get_arguments.join('&')); }; auto getGameVersionsArray(std::list mcVersions) const -> QString @@ -127,12 +151,12 @@ class ModrinthAPI : public NetworkModAPI { s += QString("\"versions:%1\",").arg(ver.toString()); } s.remove(s.length() - 1, 1); //remove last comma - return s.isEmpty() ? QString() : QString("[%1],").arg(s); + return s.isEmpty() ? QString() : s; } inline auto validateModLoaders(ModLoaderTypes loaders) const -> bool { - return (loaders == Unspecified) || (loaders & (Forge | Fabric | Quilt)); + return loaders & (Forge | Fabric | Quilt); } }; diff --git a/launcher/modplatform/modrinth/ModrinthCheckUpdate.cpp b/launcher/modplatform/modrinth/ModrinthCheckUpdate.cpp index e2d27547..7826b33d 100644 --- a/launcher/modplatform/modrinth/ModrinthCheckUpdate.cpp +++ b/launcher/modplatform/modrinth/ModrinthCheckUpdate.cpp @@ -4,12 +4,15 @@ #include "Json.h" -#include "ModDownloadTask.h" +#include "ResourceDownloadTask.h" #include "modplatform/helpers/HashUtils.h" #include "tasks/ConcurrentTask.h" +#include "minecraft/mod/ModFolderModel.h" +#include "minecraft/mod/ResourceFolderModel.h" + static ModrinthAPI api; static ModPlatform::ProviderCapabilities ProviderCaps; @@ -34,7 +37,7 @@ void ModrinthCheckUpdate::executeTask() // Create all hashes QStringList hashes; - auto best_hash_type = ProviderCaps.hashType(ModPlatform::Provider::MODRINTH).first(); + auto best_hash_type = ProviderCaps.hashType(ModPlatform::ResourceProvider::MODRINTH).first(); ConcurrentTask hashing_task(this, "MakeModrinthHashesTask", 10); for (auto* mod : m_mods) { @@ -108,11 +111,13 @@ void ModrinthCheckUpdate::executeTask() // Sometimes a version may have multiple files, one with "forge" and one with "fabric", // so we may want to filter it QString loader_filter; - static auto flags = { ModAPI::ModLoaderType::Forge, ModAPI::ModLoaderType::Fabric, ModAPI::ModLoaderType::Quilt }; - for (auto flag : flags) { - if (m_loaders.testFlag(flag)) { - loader_filter = api.getModLoaderString(flag); - break; + if (m_loaders.has_value()) { + static auto flags = { ResourceAPI::ModLoaderType::Forge, ResourceAPI::ModLoaderType::Fabric, ResourceAPI::ModLoaderType::Quilt }; + for (auto flag : flags) { + if (m_loaders.value().testFlag(flag)) { + loader_filter = api.getModLoaderString(flag); + break; + } } } @@ -152,12 +157,12 @@ void ModrinthCheckUpdate::executeTask() for (auto& author : mod->authors()) pack.authors.append({ author }); pack.description = mod->description(); - pack.provider = ModPlatform::Provider::MODRINTH; + pack.provider = ModPlatform::ResourceProvider::MODRINTH; - auto download_task = new ModDownloadTask(pack, project_ver, m_mods_folder); + auto download_task = new ResourceDownloadTask(pack, project_ver, m_mods_folder); m_updatable.emplace_back(pack.name, hash, mod->version(), project_ver.version_number, project_ver.changelog, - ModPlatform::Provider::MODRINTH, download_task); + ModPlatform::ResourceProvider::MODRINTH, download_task); } } } catch (Json::JsonException& e) { diff --git a/launcher/modplatform/modrinth/ModrinthCheckUpdate.h b/launcher/modplatform/modrinth/ModrinthCheckUpdate.h index abf8ada1..177ce516 100644 --- a/launcher/modplatform/modrinth/ModrinthCheckUpdate.h +++ b/launcher/modplatform/modrinth/ModrinthCheckUpdate.h @@ -8,7 +8,7 @@ class ModrinthCheckUpdate : public CheckUpdateTask { Q_OBJECT public: - ModrinthCheckUpdate(QList& mods, std::list& mcVersions, ModAPI::ModLoaderTypes loaders, std::shared_ptr mods_folder) + ModrinthCheckUpdate(QList& mods, std::list& mcVersions, std::optional loaders, std::shared_ptr mods_folder) : CheckUpdateTask(mods, mcVersions, loaders, mods_folder) {} diff --git a/launcher/modplatform/modrinth/ModrinthPackIndex.cpp b/launcher/modplatform/modrinth/ModrinthPackIndex.cpp index aec45a73..a0161089 100644 --- a/launcher/modplatform/modrinth/ModrinthPackIndex.cpp +++ b/launcher/modplatform/modrinth/ModrinthPackIndex.cpp @@ -33,7 +33,7 @@ void Modrinth::loadIndexedPack(ModPlatform::IndexedPack& pack, QJsonObject& obj) if (pack.addonId.toString().isEmpty()) pack.addonId = Json::requireString(obj, "id"); - pack.provider = ModPlatform::Provider::MODRINTH; + pack.provider = ModPlatform::ResourceProvider::MODRINTH; pack.name = Json::requireString(obj, "title"); pack.slug = Json::ensureString(obj, "slug", ""); @@ -179,7 +179,7 @@ auto Modrinth::loadIndexedPackVersion(QJsonObject& obj, QString preferred_hash_t file.hash = Json::requireString(hash_list, preferred_hash_type); file.hash_type = preferred_hash_type; } else { - auto hash_types = ProviderCaps.hashType(ModPlatform::Provider::MODRINTH); + auto hash_types = ProviderCaps.hashType(ModPlatform::ResourceProvider::MODRINTH); for (auto& hash_type : hash_types) { if (hash_list.contains(hash_type)) { file.hash = Json::requireString(hash_list, hash_type); diff --git a/launcher/modplatform/packwiz/Packwiz.cpp b/launcher/modplatform/packwiz/Packwiz.cpp index 0ed29311..510c7309 100644 --- a/launcher/modplatform/packwiz/Packwiz.cpp +++ b/launcher/modplatform/packwiz/Packwiz.cpp @@ -97,7 +97,7 @@ auto V1::createModFormat(QDir& index_dir, ModPlatform::IndexedPack& mod_pack, Mo mod.name = mod_pack.name; mod.filename = mod_version.fileName; - if (mod_pack.provider == ModPlatform::Provider::FLAME) { + if (mod_pack.provider == ModPlatform::ResourceProvider::FLAME) { mod.mode = "metadata:curseforge"; } else { mod.mode = "url"; @@ -176,11 +176,11 @@ void V1::updateModIndex(QDir& index_dir, Mod& mod) in_stream << QString("\n[update]\n"); in_stream << QString("[update.%1]\n").arg(ProviderCaps.name(mod.provider)); switch (mod.provider) { - case (ModPlatform::Provider::FLAME): + case (ModPlatform::ResourceProvider::FLAME): in_stream << QString("file-id = %1\n").arg(mod.file_id.toString()); in_stream << QString("project-id = %1\n").arg(mod.project_id.toString()); break; - case (ModPlatform::Provider::MODRINTH): + case (ModPlatform::ResourceProvider::MODRINTH): addToStream("mod-id", mod.mod_id().toString()); addToStream("version", mod.version().toString()); break; @@ -273,7 +273,7 @@ auto V1::getIndexForMod(QDir& index_dir, QString slug) -> Mod } { // [update] info - using Provider = ModPlatform::Provider; + using Provider = ModPlatform::ResourceProvider; auto update_table = table["update"]; if (!update_table || !update_table.is_table()) { diff --git a/launcher/modplatform/packwiz/Packwiz.h b/launcher/modplatform/packwiz/Packwiz.h index 9754e5c4..4b096eec 100644 --- a/launcher/modplatform/packwiz/Packwiz.h +++ b/launcher/modplatform/packwiz/Packwiz.h @@ -49,7 +49,7 @@ class V1 { QString hash {}; // [update] - ModPlatform::Provider provider {}; + ModPlatform::ResourceProvider provider {}; QVariant file_id {}; QVariant project_id {}; diff --git a/launcher/net/NetAction.h b/launcher/net/NetAction.h index d9c4fadc..38fe058b 100644 --- a/launcher/net/NetAction.h +++ b/launcher/net/NetAction.h @@ -52,7 +52,6 @@ class NetAction : public Task { virtual ~NetAction() = default; QUrl url() { return m_url; } - auto index() -> int { return m_index_within_job; } void setNetwork(shared_qobject_ptr network) { m_network = network; } @@ -75,9 +74,6 @@ class NetAction : public Task { public: shared_qobject_ptr m_network; - /// index within the parent job, FIXME: nuke - int m_index_within_job = 0; - /// the network reply unique_qobject_ptr m_reply; diff --git a/launcher/net/NetJob.cpp b/launcher/net/NetJob.cpp index 9b5d4f1b..4bcd40b5 100644 --- a/launcher/net/NetJob.cpp +++ b/launcher/net/NetJob.cpp @@ -38,11 +38,10 @@ auto NetJob::addNetAction(NetAction::Ptr action) -> bool { - action->m_index_within_job = m_queue.size(); - m_queue.append(action); - action->setNetwork(m_network); + addTask(action); + return true; } diff --git a/launcher/ui/dialogs/BlockedModsDialog.cpp b/launcher/ui/dialogs/BlockedModsDialog.cpp index 8b49bd1a..5977fd10 100644 --- a/launcher/ui/dialogs/BlockedModsDialog.cpp +++ b/launcher/ui/dialogs/BlockedModsDialog.cpp @@ -230,7 +230,7 @@ void BlockedModsDialog::addHashTask(QString path) /// @param path the path to the local file being hashed void BlockedModsDialog::buildHashTask(QString path) { - auto hash_task = Hashing::createBlockedModHasher(path, ModPlatform::Provider::FLAME, "sha1"); + auto hash_task = Hashing::createBlockedModHasher(path, ModPlatform::ResourceProvider::FLAME, "sha1"); qDebug() << "[Blocked Mods Dialog] Creating Hash task for path: " << path; diff --git a/launcher/ui/dialogs/ChooseProviderDialog.cpp b/launcher/ui/dialogs/ChooseProviderDialog.cpp index 89935d9a..83748e1e 100644 --- a/launcher/ui/dialogs/ChooseProviderDialog.cpp +++ b/launcher/ui/dialogs/ChooseProviderDialog.cpp @@ -67,9 +67,9 @@ void ChooseProviderDialog::confirmAll() accept(); } -auto ChooseProviderDialog::getSelectedProvider() const -> ModPlatform::Provider +auto ChooseProviderDialog::getSelectedProvider() const -> ModPlatform::ResourceProvider { - return ModPlatform::Provider(m_providers.checkedId()); + return ModPlatform::ResourceProvider(m_providers.checkedId()); } void ChooseProviderDialog::addProviders() @@ -77,7 +77,7 @@ void ChooseProviderDialog::addProviders() int btn_index = 0; QRadioButton* btn; - for (auto& provider : { ModPlatform::Provider::MODRINTH, ModPlatform::Provider::FLAME }) { + for (auto& provider : { ModPlatform::ResourceProvider::MODRINTH, ModPlatform::ResourceProvider::FLAME }) { btn = new QRadioButton(ProviderCaps.readableName(provider), this); m_providers.addButton(btn, btn_index++); ui->providersLayout->addWidget(btn); diff --git a/launcher/ui/dialogs/ChooseProviderDialog.h b/launcher/ui/dialogs/ChooseProviderDialog.h index 4a3b9f29..be9735b5 100644 --- a/launcher/ui/dialogs/ChooseProviderDialog.h +++ b/launcher/ui/dialogs/ChooseProviderDialog.h @@ -8,7 +8,7 @@ class ChooseProviderDialog; } namespace ModPlatform { -enum class Provider; +enum class ResourceProvider; } class Mod; @@ -24,7 +24,7 @@ class ChooseProviderDialog : public QDialog { bool try_others = false; - ModPlatform::Provider chosen; + ModPlatform::ResourceProvider chosen; }; public: @@ -45,7 +45,7 @@ class ChooseProviderDialog : public QDialog { void addProviders(); void disableInput(); - auto getSelectedProvider() const -> ModPlatform::Provider; + auto getSelectedProvider() const -> ModPlatform::ResourceProvider; private: Ui::ChooseProviderDialog* ui; diff --git a/launcher/ui/dialogs/ModDownloadDialog.cpp b/launcher/ui/dialogs/ModDownloadDialog.cpp index 24d23ba9..8a77ef7f 100644 --- a/launcher/ui/dialogs/ModDownloadDialog.cpp +++ b/launcher/ui/dialogs/ModDownloadDialog.cpp @@ -19,76 +19,24 @@ #include "ModDownloadDialog.h" -#include -#include -#include - #include "Application.h" -#include "ReviewMessageBox.h" - -#include -#include -#include -#include -#include "ModDownloadTask.h" -#include "ui/pages/modplatform/flame/FlameModPage.h" -#include "ui/pages/modplatform/modrinth/ModrinthModPage.h" -#include "ui/widgets/PageContainer.h" +#include "ui/pages/modplatform/flame/FlameResourcePages.h" +#include "ui/pages/modplatform/modrinth/ModrinthResourcePages.h" -ModDownloadDialog::ModDownloadDialog(const std::shared_ptr& mods, QWidget* parent, BaseInstance* instance) - : QDialog(parent), mods(mods), m_verticalLayout(new QVBoxLayout(this)), m_instance(instance) +ModDownloadDialog::ModDownloadDialog(QWidget* parent, const std::shared_ptr& mods, BaseInstance* instance) + : ResourceDownloadDialog(parent, mods), m_instance(instance) { - setObjectName(QStringLiteral("ModDownloadDialog")); - m_verticalLayout->setObjectName(QStringLiteral("verticalLayout")); - - resize(std::max(0.5 * parent->width(), 400.0), std::max(0.75 * parent->height(), 400.0)); - - setWindowIcon(APPLICATION->getThemedIcon("new")); - // NOTE: m_buttons must be initialized before PageContainer, because it indirectly accesses m_buttons through setSuggestedPack! Do not - // move this below. - m_buttons = new QDialogButtonBox(QDialogButtonBox::Help | QDialogButtonBox::Ok | QDialogButtonBox::Cancel); - - m_container = new PageContainer(this); - m_container->setSizePolicy(QSizePolicy::Policy::Preferred, QSizePolicy::Policy::Expanding); - m_container->layout()->setContentsMargins(0, 0, 0, 0); - m_verticalLayout->addWidget(m_container); - - m_container->addButtons(m_buttons); - - connect(m_container, &PageContainer::selectedPageChanged, this, &ModDownloadDialog::selectedPageChanged); - - // Bonk Qt over its stupid head and make sure it understands which button is the default one... - // See: https://stackoverflow.com/questions/24556831/qbuttonbox-set-default-button - auto OkButton = m_buttons->button(QDialogButtonBox::Ok); - OkButton->setEnabled(false); - OkButton->setDefault(true); - OkButton->setAutoDefault(true); - OkButton->setText(tr("Review and confirm")); - OkButton->setShortcut(tr("Ctrl+Return")); - OkButton->setToolTip(tr("Opens a new popup to review your selected mods and confirm your selection. Shortcut: Ctrl+Return")); - connect(OkButton, &QPushButton::clicked, this, &ModDownloadDialog::confirm); - - auto CancelButton = m_buttons->button(QDialogButtonBox::Cancel); - CancelButton->setDefault(false); - CancelButton->setAutoDefault(false); - connect(CancelButton, &QPushButton::clicked, this, &ModDownloadDialog::reject); - - auto HelpButton = m_buttons->button(QDialogButtonBox::Help); - HelpButton->setDefault(false); - HelpButton->setAutoDefault(false); - connect(HelpButton, &QPushButton::clicked, m_container, &PageContainer::help); - - QMetaObject::connectSlotsByName(this); - setWindowModality(Qt::WindowModal); - setWindowTitle(dialogTitle()); + initializeContainer(); + connectButtons(); restoreGeometry(QByteArray::fromBase64(APPLICATION->settings()->get("ModDownloadGeometry").toByteArray())); } -QString ModDownloadDialog::dialogTitle() +void ModDownloadDialog::accept() { - return tr("Download mods"); + APPLICATION->settings()->set("ModDownloadGeometry", saveGeometry().toBase64()); + QDialog::accept(); } void ModDownloadDialog::reject() @@ -97,106 +45,15 @@ void ModDownloadDialog::reject() QDialog::reject(); } -void ModDownloadDialog::confirm() -{ - auto keys = modTask.keys(); - keys.sort(Qt::CaseInsensitive); - - auto confirm_dialog = ReviewMessageBox::create(this, tr("Confirm mods to download")); - - for (auto& task : keys) { - confirm_dialog->appendMod({ task, modTask.find(task).value()->getFilename() }); - } - - if (confirm_dialog->exec()) { - auto deselected = confirm_dialog->deselectedMods(); - for (auto name : deselected) { - modTask.remove(name); - } - - this->accept(); - } -} - -void ModDownloadDialog::accept() -{ - APPLICATION->settings()->set("ModDownloadGeometry", saveGeometry().toBase64()); - QDialog::accept(); -} - QList ModDownloadDialog::getPages() { QList pages; - pages.append(ModrinthModPage::create(this, m_instance)); + pages.append(ModrinthModPage::create(this, *m_instance)); if (APPLICATION->capabilities() & Application::SupportsFlame) - pages.append(FlameModPage::create(this, m_instance)); + pages.append(FlameModPage::create(this, *m_instance)); m_selectedPage = dynamic_cast(pages[0]); return pages; } - -void ModDownloadDialog::addSelectedMod(QString name, ModDownloadTask* task) -{ - removeSelectedMod(name); - modTask.insert(name, task); - - m_buttons->button(QDialogButtonBox::Ok)->setEnabled(!modTask.isEmpty()); -} - -void ModDownloadDialog::removeSelectedMod(QString name) -{ - if (modTask.contains(name)) - delete modTask.find(name).value(); - modTask.remove(name); - - m_buttons->button(QDialogButtonBox::Ok)->setEnabled(!modTask.isEmpty()); -} - -bool ModDownloadDialog::isModSelected(QString name, QString filename) const -{ - // FIXME: Is there a way to check for versions without checking the filename - // as a heuristic, other than adding such info to ModDownloadTask itself? - auto iter = modTask.find(name); - return iter != modTask.end() && (iter.value()->getFilename() == filename); -} - -bool ModDownloadDialog::isModSelected(QString name) const -{ - auto iter = modTask.find(name); - return iter != modTask.end(); -} - -const QList ModDownloadDialog::getTasks() -{ - return modTask.values(); -} - -void ModDownloadDialog::selectedPageChanged(BasePage* previous, BasePage* selected) -{ - auto* prev_page = dynamic_cast(previous); - if (!prev_page) { - qCritical() << "Page '" << previous->displayName() << "' in ModDownloadDialog is not a ModPage!"; - return; - } - - m_selectedPage = dynamic_cast(selected); - if (!m_selectedPage) { - qCritical() << "Page '" << selected->displayName() << "' in ModDownloadDialog is not a ModPage!"; - return; - } - - // Same effect as having a global search bar - m_selectedPage->setSearchTerm(prev_page->getSearchTerm()); -} - -bool ModDownloadDialog::selectPage(QString pageId) -{ - return m_container->selectPage(pageId); -} - -ModPage* ModDownloadDialog::getSelectedPage() -{ - return m_selectedPage; -} diff --git a/launcher/ui/dialogs/ModDownloadDialog.h b/launcher/ui/dialogs/ModDownloadDialog.h index fcf6f4fc..19036042 100644 --- a/launcher/ui/dialogs/ModDownloadDialog.h +++ b/launcher/ui/dialogs/ModDownloadDialog.h @@ -19,60 +19,29 @@ #pragma once -#include -#include - -#include "ModDownloadTask.h" #include "minecraft/mod/ModFolderModel.h" -#include "ui/pages/BasePageProvider.h" -namespace Ui -{ -class ModDownloadDialog; -} +#include "ui/dialogs/ResourceDownloadDialog.h" -class PageContainer; class QDialogButtonBox; -class ModPage; -class ModrinthModPage; -class ModDownloadDialog final : public QDialog, public BasePageProvider +class ModDownloadDialog final : public ResourceDownloadDialog { Q_OBJECT public: - explicit ModDownloadDialog(const std::shared_ptr& mods, QWidget* parent, BaseInstance* instance); + explicit ModDownloadDialog(QWidget* parent, const std::shared_ptr& mods, BaseInstance* instance); ~ModDownloadDialog() override = default; - QString dialogTitle() override; - QList getPages() override; - - void addSelectedMod(QString name = QString(), ModDownloadTask* task = nullptr); - void removeSelectedMod(QString name = QString()); - bool isModSelected(QString name, QString filename) const; - bool isModSelected(QString name) const; + //: String that gets appended to the mod download dialog title ("Download " + resourcesString()) + [[nodiscard]] QString resourceString() const override { return tr("mods"); } - const QList getTasks(); - const std::shared_ptr& mods; - - bool selectPage(QString pageId); - ModPage* getSelectedPage(); + QList getPages() override; public slots: - void confirm(); void accept() override; void reject() override; - private slots: - void selectedPageChanged(BasePage* previous, BasePage* selected); - private: - Ui::ModDownloadDialog* ui = nullptr; - PageContainer* m_container = nullptr; - QDialogButtonBox* m_buttons = nullptr; - QVBoxLayout* m_verticalLayout = nullptr; - ModPage* m_selectedPage = nullptr; - - QHash modTask; BaseInstance* m_instance; }; diff --git a/launcher/ui/dialogs/ModUpdateDialog.cpp b/launcher/ui/dialogs/ModUpdateDialog.cpp index 2704243e..4ef42d6c 100644 --- a/launcher/ui/dialogs/ModUpdateDialog.cpp +++ b/launcher/ui/dialogs/ModUpdateDialog.cpp @@ -21,6 +21,8 @@ #include #include +#include + static ModPlatform::ProviderCapabilities ProviderCaps; static std::list mcVersions(BaseInstance* inst) @@ -28,7 +30,7 @@ static std::list mcVersions(BaseInstance* inst) return { static_cast(inst)->getPackProfile()->getComponent("net.minecraft")->getVersion() }; } -static ModAPI::ModLoaderTypes mcLoaders(BaseInstance* inst) +static std::optional mcLoaders(BaseInstance* inst) { return { static_cast(inst)->getPackProfile()->getModLoaders() }; } @@ -212,14 +214,14 @@ auto ModUpdateDialog::ensureMetadata() -> bool bool confirm_rest = false; bool try_others_rest = false; bool skip_rest = false; - ModPlatform::Provider provider_rest = ModPlatform::Provider::MODRINTH; + ModPlatform::ResourceProvider provider_rest = ModPlatform::ResourceProvider::MODRINTH; - auto addToTmp = [&](Mod* m, ModPlatform::Provider p) { + auto addToTmp = [&](Mod* m, ModPlatform::ResourceProvider p) { switch (p) { - case ModPlatform::Provider::MODRINTH: + case ModPlatform::ResourceProvider::MODRINTH: modrinth_tmp.push_back(m); break; - case ModPlatform::Provider::FLAME: + case ModPlatform::ResourceProvider::FLAME: flame_tmp.push_back(m); break; } @@ -264,10 +266,10 @@ auto ModUpdateDialog::ensureMetadata() -> bool } if (!modrinth_tmp.empty()) { - auto* modrinth_task = new EnsureMetadataTask(modrinth_tmp, index_dir, ModPlatform::Provider::MODRINTH); + auto* modrinth_task = new EnsureMetadataTask(modrinth_tmp, index_dir, ModPlatform::ResourceProvider::MODRINTH); connect(modrinth_task, &EnsureMetadataTask::metadataReady, [this](Mod* candidate) { onMetadataEnsured(candidate); }); connect(modrinth_task, &EnsureMetadataTask::metadataFailed, [this, &should_try_others](Mod* candidate) { - onMetadataFailed(candidate, should_try_others.find(candidate->internal_id()).value(), ModPlatform::Provider::MODRINTH); + onMetadataFailed(candidate, should_try_others.find(candidate->internal_id()).value(), ModPlatform::ResourceProvider::MODRINTH); }); if (modrinth_task->getHashingTask()) @@ -277,10 +279,10 @@ auto ModUpdateDialog::ensureMetadata() -> bool } if (!flame_tmp.empty()) { - auto* flame_task = new EnsureMetadataTask(flame_tmp, index_dir, ModPlatform::Provider::FLAME); + auto* flame_task = new EnsureMetadataTask(flame_tmp, index_dir, ModPlatform::ResourceProvider::FLAME); connect(flame_task, &EnsureMetadataTask::metadataReady, [this](Mod* candidate) { onMetadataEnsured(candidate); }); connect(flame_task, &EnsureMetadataTask::metadataFailed, [this, &should_try_others](Mod* candidate) { - onMetadataFailed(candidate, should_try_others.find(candidate->internal_id()).value(), ModPlatform::Provider::FLAME); + onMetadataFailed(candidate, should_try_others.find(candidate->internal_id()).value(), ModPlatform::ResourceProvider::FLAME); }); if (flame_task->getHashingTask()) @@ -306,28 +308,28 @@ void ModUpdateDialog::onMetadataEnsured(Mod* mod) return; switch (mod->metadata()->provider) { - case ModPlatform::Provider::MODRINTH: + case ModPlatform::ResourceProvider::MODRINTH: m_modrinth_to_update.push_back(mod); break; - case ModPlatform::Provider::FLAME: + case ModPlatform::ResourceProvider::FLAME: m_flame_to_update.push_back(mod); break; } } -ModPlatform::Provider next(ModPlatform::Provider p) +ModPlatform::ResourceProvider next(ModPlatform::ResourceProvider p) { switch (p) { - case ModPlatform::Provider::MODRINTH: - return ModPlatform::Provider::FLAME; - case ModPlatform::Provider::FLAME: - return ModPlatform::Provider::MODRINTH; + case ModPlatform::ResourceProvider::MODRINTH: + return ModPlatform::ResourceProvider::FLAME; + case ModPlatform::ResourceProvider::FLAME: + return ModPlatform::ResourceProvider::MODRINTH; } - return ModPlatform::Provider::FLAME; + return ModPlatform::ResourceProvider::FLAME; } -void ModUpdateDialog::onMetadataFailed(Mod* mod, bool try_others, ModPlatform::Provider first_choice) +void ModUpdateDialog::onMetadataFailed(Mod* mod, bool try_others, ModPlatform::ResourceProvider first_choice) { if (try_others) { auto index_dir = indexDir(); @@ -368,7 +370,7 @@ void ModUpdateDialog::appendMod(CheckUpdateTask::UpdatableMod const& info) QString text = info.changelog; switch (info.provider) { - case ModPlatform::Provider::MODRINTH: { + case ModPlatform::ResourceProvider::MODRINTH: { text = markdownToHTML(info.changelog.toUtf8()); break; } @@ -386,9 +388,9 @@ void ModUpdateDialog::appendMod(CheckUpdateTask::UpdatableMod const& info) ui->modTreeWidget->addTopLevelItem(item_top); } -auto ModUpdateDialog::getTasks() -> const QList +auto ModUpdateDialog::getTasks() -> const QList { - QList list; + QList list; auto* item = ui->modTreeWidget->topLevelItem(0); diff --git a/launcher/ui/dialogs/ModUpdateDialog.h b/launcher/ui/dialogs/ModUpdateDialog.h index bd486f0d..3e3dd90d 100644 --- a/launcher/ui/dialogs/ModUpdateDialog.h +++ b/launcher/ui/dialogs/ModUpdateDialog.h @@ -1,7 +1,7 @@ #pragma once #include "BaseInstance.h" -#include "ModDownloadTask.h" +#include "ResourceDownloadTask.h" #include "ReviewMessageBox.h" #include "minecraft/mod/ModFolderModel.h" @@ -25,7 +25,7 @@ class ModUpdateDialog final : public ReviewMessageBox { void appendMod(const CheckUpdateTask::UpdatableMod& info); - const QList getTasks(); + const QList getTasks(); auto indexDir() const -> QDir { return m_mod_model->indexDir(); } auto noUpdates() const -> bool { return m_no_updates; }; @@ -36,7 +36,7 @@ class ModUpdateDialog final : public ReviewMessageBox { private slots: void onMetadataEnsured(Mod*); - void onMetadataFailed(Mod*, bool try_others = false, ModPlatform::Provider first_choice = ModPlatform::Provider::MODRINTH); + void onMetadataFailed(Mod*, bool try_others = false, ModPlatform::ResourceProvider first_choice = ModPlatform::ResourceProvider::MODRINTH); private: QWidget* m_parent; @@ -54,7 +54,7 @@ class ModUpdateDialog final : public ReviewMessageBox { QList> m_failed_metadata; QList> m_failed_check_update; - QHash m_tasks; + QHash m_tasks; BaseInstance* m_instance; bool m_no_updates = false; diff --git a/launcher/ui/dialogs/ResourceDownloadDialog.cpp b/launcher/ui/dialogs/ResourceDownloadDialog.cpp new file mode 100644 index 00000000..7367548f --- /dev/null +++ b/launcher/ui/dialogs/ResourceDownloadDialog.cpp @@ -0,0 +1,152 @@ +#include "ResourceDownloadDialog.h" + +#include + +#include "Application.h" +#include "ResourceDownloadTask.h" + +#include "ui/dialogs/ReviewMessageBox.h" +#include "ui/pages/modplatform/ResourcePage.h" +#include "ui/widgets/PageContainer.h" + +ResourceDownloadDialog::ResourceDownloadDialog(QWidget* parent, const std::shared_ptr base_model) + : QDialog(parent), m_base_model(base_model), m_buttons(QDialogButtonBox::Help | QDialogButtonBox::Ok | QDialogButtonBox::Cancel), m_vertical_layout(this) +{ + setObjectName(QStringLiteral("ResourceDownloadDialog")); + + resize(std::max(0.5 * parent->width(), 400.0), std::max(0.75 * parent->height(), 400.0)); + + setWindowIcon(APPLICATION->getThemedIcon("new")); + + // Bonk Qt over its stupid head and make sure it understands which button is the default one... + // See: https://stackoverflow.com/questions/24556831/qbuttonbox-set-default-button + auto OkButton = m_buttons.button(QDialogButtonBox::Ok); + OkButton->setEnabled(false); + OkButton->setDefault(true); + OkButton->setAutoDefault(true); + OkButton->setText(tr("Review and confirm")); + OkButton->setShortcut(tr("Ctrl+Return")); + + auto CancelButton = m_buttons.button(QDialogButtonBox::Cancel); + CancelButton->setDefault(false); + CancelButton->setAutoDefault(false); + + auto HelpButton = m_buttons.button(QDialogButtonBox::Help); + HelpButton->setDefault(false); + HelpButton->setAutoDefault(false); + + setWindowModality(Qt::WindowModal); + setWindowTitle(dialogTitle()); +} + +// NOTE: We can't have this in the ctor because PageContainer calls a virtual function, and so +// won't work with subclasses if we put it in this ctor. +void ResourceDownloadDialog::initializeContainer() +{ + m_container = new PageContainer(this); + m_container->setSizePolicy(QSizePolicy::Policy::Preferred, QSizePolicy::Policy::Expanding); + m_container->layout()->setContentsMargins(0, 0, 0, 0); + m_vertical_layout.addWidget(m_container); + + m_container->addButtons(&m_buttons); + + connect(m_container, &PageContainer::selectedPageChanged, this, &ResourceDownloadDialog::selectedPageChanged); +} + +void ResourceDownloadDialog::connectButtons() +{ + auto OkButton = m_buttons.button(QDialogButtonBox::Ok); + OkButton->setToolTip(tr("Opens a new popup to review your selected %1 and confirm your selection. Shortcut: Ctrl+Return").arg(resourceString())); + connect(OkButton, &QPushButton::clicked, this, &ResourceDownloadDialog::confirm); + + auto CancelButton = m_buttons.button(QDialogButtonBox::Cancel); + connect(CancelButton, &QPushButton::clicked, this, &ResourceDownloadDialog::reject); + + auto HelpButton = m_buttons.button(QDialogButtonBox::Help); + connect(HelpButton, &QPushButton::clicked, m_container, &PageContainer::help); +} + +void ResourceDownloadDialog::confirm() +{ + auto keys = m_selected.keys(); + keys.sort(Qt::CaseInsensitive); + + auto confirm_dialog = ReviewMessageBox::create(this, tr("Confirm %1 to download").arg(resourceString())); + + for (auto& task : keys) { + confirm_dialog->appendResource({ task, m_selected.find(task).value()->getFilename() }); + } + + if (confirm_dialog->exec()) { + auto deselected = confirm_dialog->deselectedResources(); + for (auto name : deselected) { + m_selected.remove(name); + } + + this->accept(); + } +} + +bool ResourceDownloadDialog::selectPage(QString pageId) +{ + return m_container->selectPage(pageId); +} + +ResourcePage* ResourceDownloadDialog::getSelectedPage() +{ + return m_selectedPage; +} + +void ResourceDownloadDialog::addResource(QString name, ResourceDownloadTask* task) +{ + removeResource(name); + m_selected.insert(name, task); + + m_buttons.button(QDialogButtonBox::Ok)->setEnabled(!m_selected.isEmpty()); +} + +void ResourceDownloadDialog::removeResource(QString name) +{ + if (m_selected.contains(name)) + m_selected.find(name).value()->deleteLater(); + m_selected.remove(name); + + m_buttons.button(QDialogButtonBox::Ok)->setEnabled(!m_selected.isEmpty()); +} + +bool ResourceDownloadDialog::isSelected(QString name, QString filename) const +{ + auto iter = m_selected.constFind(name); + if (iter == m_selected.constEnd()) + return false; + + // FIXME: Is there a way to check for versions without checking the filename + // as a heuristic, other than adding such info to ResourceDownloadTask itself? + if (!filename.isEmpty()) + return iter.value()->getFilename() == filename; + + return true; +} + +const QList ResourceDownloadDialog::getTasks() +{ + return m_selected.values(); +} + +void ResourceDownloadDialog::selectedPageChanged(BasePage* previous, BasePage* selected) +{ + auto* prev_page = dynamic_cast(previous); + if (!prev_page) { + qCritical() << "Page '" << previous->displayName() << "' in ResourceDownloadDialog is not a ResourcePage!"; + return; + } + + m_selectedPage = dynamic_cast(selected); + if (!m_selectedPage) { + qCritical() << "Page '" << selected->displayName() << "' in ResourceDownloadDialog is not a ResourcePage!"; + return; + } + + // Same effect as having a global search bar + m_selectedPage->setSearchTerm(prev_page->getSearchTerm()); +} diff --git a/launcher/ui/dialogs/ResourceDownloadDialog.h b/launcher/ui/dialogs/ResourceDownloadDialog.h new file mode 100644 index 00000000..d6b3938b --- /dev/null +++ b/launcher/ui/dialogs/ResourceDownloadDialog.h @@ -0,0 +1,55 @@ +#pragma once + +#include +#include +#include + +#include "ui/pages/BasePageProvider.h" + +class ResourceDownloadTask; +class ResourcePage; +class ResourceFolderModel; +class PageContainer; +class QVBoxLayout; +class QDialogButtonBox; + +class ResourceDownloadDialog : public QDialog, public BasePageProvider { + Q_OBJECT + + public: + ResourceDownloadDialog(QWidget* parent, const std::shared_ptr base_model); + + void initializeContainer(); + void connectButtons(); + + //: String that gets appended to the download dialog title ("Download " + resourcesString()) + [[nodiscard]] virtual QString resourceString() const { return tr("resources"); } + + QString dialogTitle() override { return tr("Download %1").arg(resourceString()); }; + + bool selectPage(QString pageId); + ResourcePage* getSelectedPage(); + + void addResource(QString name, ResourceDownloadTask* task); + void removeResource(QString name); + [[nodiscard]] bool isSelected(QString name, QString filename = "") const; + + const QList getTasks(); + [[nodiscard]] const std::shared_ptr getBaseModel() const { return m_base_model; } + + protected slots: + void selectedPageChanged(BasePage* previous, BasePage* selected); + + virtual void confirm(); + + protected: + const std::shared_ptr m_base_model; + + PageContainer* m_container = nullptr; + ResourcePage* m_selectedPage = nullptr; + + QDialogButtonBox m_buttons; + QVBoxLayout m_vertical_layout; + + QHash m_selected; +}; diff --git a/launcher/ui/dialogs/ReviewMessageBox.cpp b/launcher/ui/dialogs/ReviewMessageBox.cpp index 7c25c91c..f45a9c4a 100644 --- a/launcher/ui/dialogs/ReviewMessageBox.cpp +++ b/launcher/ui/dialogs/ReviewMessageBox.cpp @@ -25,7 +25,7 @@ auto ReviewMessageBox::create(QWidget* parent, QString&& title, QString&& icon) return new ReviewMessageBox(parent, title, icon); } -void ReviewMessageBox::appendMod(ModInformation&& info) +void ReviewMessageBox::appendResource(ResourceInformation&& info) { auto itemTop = new QTreeWidgetItem(ui->modTreeWidget); itemTop->setCheckState(0, Qt::CheckState::Checked); @@ -39,7 +39,7 @@ void ReviewMessageBox::appendMod(ModInformation&& info) ui->modTreeWidget->addTopLevelItem(itemTop); } -auto ReviewMessageBox::deselectedMods() -> QStringList +auto ReviewMessageBox::deselectedResources() -> QStringList { QStringList list; diff --git a/launcher/ui/dialogs/ReviewMessageBox.h b/launcher/ui/dialogs/ReviewMessageBox.h index 9cfa679a..e2d0ce37 100644 --- a/launcher/ui/dialogs/ReviewMessageBox.h +++ b/launcher/ui/dialogs/ReviewMessageBox.h @@ -12,15 +12,15 @@ class ReviewMessageBox : public QDialog { public: static auto create(QWidget* parent, QString&& title, QString&& icon = "") -> ReviewMessageBox*; - using ModInformation = struct { + using ResourceInformation = struct { QString name; QString filename; }; - void appendMod(ModInformation&& info); - auto deselectedMods() -> QStringList; + void appendResource(ResourceInformation&& info); + auto deselectedResources() -> QStringList; - ~ReviewMessageBox(); + ~ReviewMessageBox() override; protected: ReviewMessageBox(QWidget* parent, const QString& title, const QString& icon); diff --git a/launcher/ui/pages/instance/ModFolderPage.cpp b/launcher/ui/pages/instance/ModFolderPage.cpp index 627e71e5..1bce3c0d 100644 --- a/launcher/ui/pages/instance/ModFolderPage.cpp +++ b/launcher/ui/pages/instance/ModFolderPage.cpp @@ -59,7 +59,7 @@ #include "minecraft/mod/Mod.h" #include "minecraft/mod/ModFolderModel.h" -#include "modplatform/ModAPI.h" +#include "modplatform/ResourceAPI.h" #include "Version.h" #include "tasks/ConcurrentTask.h" @@ -153,12 +153,12 @@ void ModFolderPage::installMods() return; // this is a null instance or a legacy instance auto profile = static_cast(m_instance)->getPackProfile(); - if (profile->getModLoaders() == ModAPI::Unspecified) { + if (!profile->getModLoaders().has_value()) { QMessageBox::critical(this, tr("Error"), tr("Please install a mod loader first!")); return; } - ModDownloadDialog mdownload(m_model, this, m_instance); + ModDownloadDialog mdownload(this, m_model, m_instance); if (mdownload.exec()) { ConcurrentTask* tasks = new ConcurrentTask(this); connect(tasks, &Task::failed, [this, tasks](QString reason) { diff --git a/launcher/ui/pages/instance/ResourcePackPage.h b/launcher/ui/pages/instance/ResourcePackPage.h index 9633e3b4..db8af0c5 100644 --- a/launcher/ui/pages/instance/ResourcePackPage.h +++ b/launcher/ui/pages/instance/ResourcePackPage.h @@ -73,3 +73,4 @@ public: return true; } }; + diff --git a/launcher/ui/pages/modplatform/ModModel.cpp b/launcher/ui/pages/modplatform/ModModel.cpp index ed58eb32..31aae746 100644 --- a/launcher/ui/pages/modplatform/ModModel.cpp +++ b/launcher/ui/pages/modplatform/ModModel.cpp @@ -1,226 +1,81 @@ #include "ModModel.h" -#include "BuildConfig.h" #include "Json.h" #include "ModPage.h" #include "minecraft/MinecraftInstance.h" #include "minecraft/PackProfile.h" -#include "ui/dialogs/ModDownloadDialog.h" - -#include "ui/widgets/ProjectItem.h" #include namespace ModPlatform { -// HACK: We need this to prevent callbacks from calling the ListModel after it has already been deleted. -// This leaks a tiny bit of memory per time the user has opened the mod dialog. How to make this better? -static QHash s_running; - -ListModel::ListModel(ModPage* parent) : QAbstractListModel(parent), m_parent(parent) { s_running.insert(this, true); } - -ListModel::~ListModel() -{ - s_running.find(this).value() = false; -} - -auto ListModel::debugName() const -> QString -{ - return m_parent->debugName(); -} +ListModel::ListModel(ModPage* parent, ResourceAPI* api) : ResourceModel(parent, api) {} /******** Make data requests ********/ -void ListModel::fetchMore(const QModelIndex& parent) +ResourceAPI::SearchArgs ListModel::createSearchArguments() { - if (parent.isValid()) - return; - if (nextSearchOffset == 0) { - qWarning() << "fetchMore with 0 offset is wrong..."; - return; - } - performPaginatedSearch(); + auto profile = static_cast(m_associated_page->m_base_instance).getPackProfile(); + return { ModPlatform::ResourceType::MOD, m_next_search_offset, m_search_term, + getSorts()[currentSort], profile->getModLoaders(), getMineVersions() }; } - -auto ListModel::data(const QModelIndex& index, int role) const -> QVariant +ResourceAPI::SearchCallbacks ListModel::createSearchCallbacks() { - int pos = index.row(); - if (pos >= modpacks.size() || pos < 0 || !index.isValid()) { - return QString("INVALID INDEX %1").arg(pos); - } - - ModPlatform::IndexedPack pack = modpacks.at(pos); - switch (role) { - case Qt::ToolTipRole: { - if (pack.description.length() > 100) { - // some magic to prevent to long tooltips and replace html linebreaks - QString edit = pack.description.left(97); - edit = edit.left(edit.lastIndexOf("
")).left(edit.lastIndexOf(" ")).append("..."); - return edit; - } - return pack.description; - } - case Qt::DecorationRole: { - if (m_logoMap.contains(pack.logoName)) { - return m_logoMap.value(pack.logoName); - } - QIcon icon = APPLICATION->getThemedIcon("screenshot-placeholder"); - // un-const-ify this - ((ListModel*)this)->requestLogo(pack.logoName, pack.logoUrl); - return icon; - } - case Qt::SizeHintRole: - return QSize(0, 58); - case Qt::UserRole: { - QVariant v; - v.setValue(pack); - return v; - } - // Custom data - case UserDataTypes::TITLE: - return pack.name; - case UserDataTypes::DESCRIPTION: - return pack.description; - case UserDataTypes::SELECTED: - return m_parent->getDialog()->isModSelected(pack.name); - default: - break; - } - - return {}; + return { [this](auto& doc) { + if (!s_running_models.constFind(this).value()) + return; + searchRequestFinished(doc); + } }; } -bool ListModel::setData(const QModelIndex &index, const QVariant &value, int role) +ResourceAPI::VersionSearchArgs ListModel::createVersionsArguments(QModelIndex& entry) { - int pos = index.row(); - if (pos >= modpacks.size() || pos < 0 || !index.isValid()) - return false; + auto const& pack = m_packs[entry.row()]; + auto profile = static_cast(m_associated_page->m_base_instance).getPackProfile(); - modpacks[pos] = value.value(); - - return true; + return { pack.addonId.toString(), getMineVersions(), profile->getModLoaders() }; } - -void ListModel::requestModVersions(ModPlatform::IndexedPack const& current, QModelIndex index) +ResourceAPI::VersionSearchCallbacks ListModel::createVersionsCallbacks(QModelIndex& entry) { - auto profile = (dynamic_cast((dynamic_cast(parent()))->m_instance))->getPackProfile(); - - m_parent->apiProvider()->getVersions({ current.addonId.toString(), getMineVersions(), profile->getModLoaders() }, - [this, current, index](QJsonDocument& doc, QString addonId) { - if (!s_running.constFind(this).value()) - return; - versionRequestSucceeded(doc, addonId, index); - }); -} - -void ListModel::performPaginatedSearch() -{ - auto profile = (dynamic_cast((dynamic_cast(parent()))->m_instance))->getPackProfile(); + auto const& pack = m_packs[entry.row()]; - m_parent->apiProvider()->searchMods( - this, { nextSearchOffset, currentSearchTerm, getSorts()[currentSort], profile->getModLoaders(), getMineVersions() }); + return { [this, pack, entry](auto& doc, auto addonId) { + if (!s_running_models.constFind(this).value()) + return; + versionRequestSucceeded(doc, addonId, entry); + } }; } -void ListModel::requestModInfo(ModPlatform::IndexedPack& current, QModelIndex index) +ResourceAPI::ProjectInfoArgs ListModel::createInfoArguments(QModelIndex& entry) { - m_parent->apiProvider()->getModInfo(current, [this, index](QJsonDocument& doc, ModPlatform::IndexedPack& pack) { - if (!s_running.constFind(this).value()) - return; - infoRequestFinished(doc, pack, index); - }); + auto& pack = m_packs[entry.row()]; + return { pack }; } - -void ListModel::refresh() +ResourceAPI::ProjectInfoCallbacks ListModel::createInfoCallbacks(QModelIndex& entry) { - if (jobPtr) { - jobPtr->abort(); - searchState = ResetRequested; - return; - } else { - beginResetModel(); - modpacks.clear(); - endResetModel(); - searchState = None; - } - nextSearchOffset = 0; - performPaginatedSearch(); + return { [this, entry](auto& doc, auto& pack) { + if (!s_running_models.constFind(this).value()) + return; + infoRequestFinished(doc, pack, entry); + } }; } void ListModel::searchWithTerm(const QString& term, const int sort, const bool filter_changed) { - if (currentSearchTerm == term && currentSearchTerm.isNull() == term.isNull() && currentSort == sort && !filter_changed) { + if (m_search_term == term && m_search_term.isNull() == term.isNull() && currentSort == sort && !filter_changed) { return; } - currentSearchTerm = term; + setSearchTerm(term); currentSort = sort; refresh(); } -void ListModel::getLogo(const QString& logo, const QString& logoUrl, LogoCallback callback) -{ - if (m_logoMap.contains(logo)) { - callback(APPLICATION->metacache() - ->resolveEntry(m_parent->metaEntryBase(), QString("logos/%1").arg(logo.section(".", 0, 0))) - ->getFullPath()); - } else { - requestLogo(logo, logoUrl); - } -} - -void ListModel::requestLogo(QString logo, QString url) -{ - if (m_loadingLogos.contains(logo) || m_failedLogos.contains(logo) || url.isEmpty()) { - return; - } - - MetaEntryPtr entry = - APPLICATION->metacache()->resolveEntry(m_parent->metaEntryBase(), QString("logos/%1").arg(logo.section(".", 0, 0))); - auto job = new NetJob(QString("%1 Icon Download %2").arg(m_parent->debugName()).arg(logo), APPLICATION->network()); - job->addNetAction(Net::Download::makeCached(QUrl(url), entry)); - - auto fullPath = entry->getFullPath(); - QObject::connect(job, &NetJob::succeeded, this, [this, logo, fullPath, job] { - job->deleteLater(); - emit logoLoaded(logo, QIcon(fullPath)); - if (waitingCallbacks.contains(logo)) { - waitingCallbacks.value(logo)(fullPath); - } - }); - - QObject::connect(job, &NetJob::failed, this, [this, logo, job] { - job->deleteLater(); - emit logoFailed(logo); - }); - - job->start(); - m_loadingLogos.append(logo); -} - /******** Request callbacks ********/ -void ListModel::logoLoaded(QString logo, QIcon out) -{ - m_loadingLogos.removeAll(logo); - m_logoMap.insert(logo, out); - for (int i = 0; i < modpacks.size(); i++) { - if (modpacks[i].logoName == logo) { - emit dataChanged(createIndex(i, 0), createIndex(i, 0), { Qt::DecorationRole }); - } - } -} - -void ListModel::logoFailed(QString logo) -{ - m_failedLogos.append(logo); - m_loadingLogos.removeAll(logo); -} - void ListModel::searchRequestFinished(QJsonDocument& doc) { - jobPtr.reset(); - QList newList; auto packs = documentToArray(doc); @@ -232,62 +87,27 @@ void ListModel::searchRequestFinished(QJsonDocument& doc) loadIndexedPack(pack, packObj); newList.append(pack); } catch (const JSONValidationError& e) { - qWarning() << "Error while loading mod from " << m_parent->debugName() << ": " << e.cause(); + qWarning() << "Error while loading mod from " << m_associated_page->debugName() << ": " << e.cause(); continue; } } if (packs.size() < 25) { - searchState = Finished; + m_search_state = SearchState::Finished; } else { - nextSearchOffset += 25; - searchState = CanPossiblyFetchMore; + m_next_search_offset += 25; + m_search_state = SearchState::CanFetchMore; } // When you have a Qt build with assertions turned on, proceeding here will abort the application if (newList.size() == 0) return; - beginInsertRows(QModelIndex(), modpacks.size(), modpacks.size() + newList.size() - 1); - modpacks.append(newList); + beginInsertRows(QModelIndex(), m_packs.size(), m_packs.size() + newList.size() - 1); + m_packs.append(newList); endInsertRows(); } -void ListModel::searchRequestFailed(QString reason) -{ - auto failed_action = jobPtr->getFailedActions().at(0); - if (!failed_action->m_reply) { - // Network error - QMessageBox::critical(nullptr, tr("Error"), tr("A network error occurred. Could not load mods.")); - } else if (failed_action->m_reply && failed_action->m_reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() == 409) { - // 409 Gone, notify user to update - QMessageBox::critical(nullptr, tr("Error"), - //: %1 refers to the launcher itself - QString("%1 %2") - .arg(m_parent->displayName()) - .arg(tr("API version too old!\nPlease update %1!").arg(BuildConfig.LAUNCHER_DISPLAYNAME))); - } - - jobPtr.reset(); - searchState = Finished; -} - -void ListModel::searchRequestAborted() -{ - if (searchState != ResetRequested) - qCritical() << "Search task in ModModel aborted by an unknown reason!"; - - // Retry fetching - jobPtr.reset(); - - beginResetModel(); - modpacks.clear(); - endResetModel(); - - nextSearchOffset = 0; - performPaginatedSearch(); -} - void ListModel::infoRequestFinished(QJsonDocument& doc, ModPlatform::IndexedPack& pack, const QModelIndex& index) { qDebug() << "Loading mod info"; @@ -310,12 +130,12 @@ void ListModel::infoRequestFinished(QJsonDocument& doc, ModPlatform::IndexedPack } } - m_parent->updateUi(); + m_associated_page->updateUi(); } void ListModel::versionRequestSucceeded(QJsonDocument doc, QString addonId, const QModelIndex& index) { - auto& current = m_parent->getCurrent(); + auto current = m_associated_page->getCurrentPack(); if (addonId != current.addonId) { return; } @@ -336,15 +156,19 @@ void ListModel::versionRequestSucceeded(QJsonDocument doc, QString addonId, cons qWarning() << "Failed to cache mod versions!"; } - - m_parent->updateModVersions(); + m_associated_page->updateVersionList(); } } // namespace ModPlatform /******** Helpers ********/ -auto ModPlatform::ListModel::getMineVersions() const -> std::list +#define MOD_PAGE(x) static_cast(x) + +auto ModPlatform::ListModel::getMineVersions() const -> std::optional> { - return m_parent->getFilter()->versions; + auto versions = MOD_PAGE(m_associated_page)->getFilter()->versions; + if (!versions.empty()) + return versions; + return {}; } diff --git a/launcher/ui/pages/modplatform/ModModel.h b/launcher/ui/pages/modplatform/ModModel.h index 36840649..7c735d90 100644 --- a/launcher/ui/pages/modplatform/ModModel.h +++ b/launcher/ui/pages/modplatform/ModModel.h @@ -3,90 +3,52 @@ #include #include "modplatform/ModIndex.h" -#include "net/NetJob.h" +#include "modplatform/ResourceAPI.h" + +#include "ui/pages/modplatform/ResourceModel.h" class ModPage; class Version; namespace ModPlatform { -using LogoMap = QMap; -using LogoCallback = std::function; - -class ListModel : public QAbstractListModel { +class ListModel : public ResourceModel { Q_OBJECT public: - ListModel(ModPage* parent); - ~ListModel() override; - - inline auto rowCount(const QModelIndex& parent) const -> int override { return parent.isValid() ? 0 : modpacks.size(); }; - inline auto columnCount(const QModelIndex& parent) const -> int override { return parent.isValid() ? 0 : 1; }; - inline auto flags(const QModelIndex& index) const -> Qt::ItemFlags override { return QAbstractListModel::flags(index); }; - - auto debugName() const -> QString; - - /* Retrieve information from the model at a given index with the given role */ - auto data(const QModelIndex& index, int role) const -> QVariant override; - bool setData(const QModelIndex &index, const QVariant &value, int role) override; - - inline void setActiveJob(NetJob::Ptr ptr) { jobPtr = ptr; } - inline NetJob* activeJob() { return jobPtr.get(); } + ListModel(ModPage* parent, ResourceAPI* api); /* Ask the API for more information */ - void fetchMore(const QModelIndex& parent) override; - void refresh(); void searchWithTerm(const QString& term, const int sort, const bool filter_changed); - void requestModInfo(ModPlatform::IndexedPack& current, QModelIndex index); - void requestModVersions(const ModPlatform::IndexedPack& current, QModelIndex index); virtual void loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj) = 0; virtual void loadExtraPackInfo(ModPlatform::IndexedPack& m, QJsonObject& obj) = 0; virtual void loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr) = 0; - void getLogo(const QString& logo, const QString& logoUrl, LogoCallback callback); - - inline auto canFetchMore(const QModelIndex& parent) const -> bool override { return parent.isValid() ? false : searchState == CanPossiblyFetchMore; }; - public slots: void searchRequestFinished(QJsonDocument& doc); - void searchRequestFailed(QString reason); - void searchRequestAborted(); void infoRequestFinished(QJsonDocument& doc, ModPlatform::IndexedPack& pack, const QModelIndex& index); void versionRequestSucceeded(QJsonDocument doc, QString addonId, const QModelIndex& index); - protected slots: + public slots: + ResourceAPI::SearchArgs createSearchArguments() override; + ResourceAPI::SearchCallbacks createSearchCallbacks() override; - void logoFailed(QString logo); - void logoLoaded(QString logo, QIcon out); + ResourceAPI::VersionSearchArgs createVersionsArguments(QModelIndex&) override; + ResourceAPI::VersionSearchCallbacks createVersionsCallbacks(QModelIndex&) override; - void performPaginatedSearch(); + ResourceAPI::ProjectInfoArgs createInfoArguments(QModelIndex&) override; + ResourceAPI::ProjectInfoCallbacks createInfoCallbacks(QModelIndex&) override; protected: virtual auto documentToArray(QJsonDocument& obj) const -> QJsonArray = 0; virtual auto getSorts() const -> const char** = 0; - void requestLogo(QString file, QString url); - - inline auto getMineVersions() const -> std::list; + inline auto getMineVersions() const -> std::optional>; protected: - ModPage* m_parent; - - QList modpacks; - - LogoMap m_logoMap; - QMap waitingCallbacks; - QStringList m_failedLogos; - QStringList m_loadingLogos; - - QString currentSearchTerm; int currentSort = 0; - int nextSearchOffset = 0; - enum SearchState { None, CanPossiblyFetchMore, ResetRequested, Finished } searchState = None; - - NetJob::Ptr jobPtr; }; } // namespace ModPlatform diff --git a/launcher/ui/pages/modplatform/ModPage.cpp b/launcher/ui/pages/modplatform/ModPage.cpp index 0f30689e..853f2c54 100644 --- a/launcher/ui/pages/modplatform/ModPage.cpp +++ b/launcher/ui/pages/modplatform/ModPage.cpp @@ -35,59 +35,30 @@ */ #include "ModPage.h" -#include "Application.h" -#include "ui_ModPage.h" +#include "ui_ResourcePage.h" #include #include #include + #include +#include "Application.h" +#include "ResourceDownloadTask.h" + #include "minecraft/MinecraftInstance.h" #include "minecraft/PackProfile.h" -#include "ui/dialogs/ModDownloadDialog.h" -#include "ui/widgets/ProjectItem.h" -#include "Markdown.h" - -ModPage::ModPage(ModDownloadDialog* dialog, BaseInstance* instance, ModAPI* api) - : QWidget(dialog) - , m_instance(instance) - , ui(new Ui::ModPage) - , dialog(dialog) - , m_fetch_progress(this, false) - , api(api) -{ - ui->setupUi(this); - - connect(ui->searchButton, &QPushButton::clicked, this, &ModPage::triggerSearch); - connect(ui->modFilterButton, &QPushButton::clicked, this, &ModPage::filterMods); - connect(ui->packView, &QListView::doubleClicked, this, &ModPage::onModSelected); - - m_search_timer.setTimerType(Qt::TimerType::CoarseTimer); - m_search_timer.setSingleShot(true); - - connect(&m_search_timer, &QTimer::timeout, this, &ModPage::triggerSearch); - - ui->searchEdit->installEventFilter(this); - - ui->versionSelectionBox->view()->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); - ui->versionSelectionBox->view()->parentWidget()->setMaximumHeight(300); - - m_fetch_progress.hideIfInactive(true); - m_fetch_progress.setFixedHeight(24); - m_fetch_progress.progressFormat(""); - - ui->gridLayout_3->addWidget(&m_fetch_progress, 0, 0, 1, ui->gridLayout_3->columnCount()); - ui->packView->setItemDelegate(new ProjectItemDelegate(this)); - ui->packView->installEventFilter(this); +#include "ui/dialogs/ModDownloadDialog.h" - connect(ui->packDescription, &QTextBrowser::anchorClicked, this, &ModPage::openUrl); -} +#include "ui/pages/modplatform/ModModel.h" -ModPage::~ModPage() +ModPage::ModPage(ModDownloadDialog* dialog, BaseInstance& instance) + : ResourcePage(dialog, instance) { - delete ui; + connect(m_ui->searchButton, &QPushButton::clicked, this, &ModPage::triggerSearch); + connect(m_ui->resourceFilterButton, &QPushButton::clicked, this, &ModPage::filterMods); + connect(m_ui->packView, &QListView::doubleClicked, this, &ModPage::onResourceSelected); } void ModPage::setFilterWidget(unique_qobject_ptr& widget) @@ -97,59 +68,19 @@ void ModPage::setFilterWidget(unique_qobject_ptr& widget) m_filter_widget.swap(widget); - ui->gridLayout_3->addWidget(m_filter_widget.get(), 0, 0, 1, ui->gridLayout_3->columnCount()); + m_ui->gridLayout_3->addWidget(m_filter_widget.get(), 0, 0, 1, m_ui->gridLayout_3->columnCount()); - m_filter_widget->setInstance(static_cast(m_instance)); + m_filter_widget->setInstance(&static_cast(m_base_instance)); m_filter = m_filter_widget->getFilter(); connect(m_filter_widget.get(), &ModFilterWidget::filterChanged, this, [&]{ - ui->searchButton->setStyleSheet("text-decoration: underline"); + m_ui->searchButton->setStyleSheet("text-decoration: underline"); }); connect(m_filter_widget.get(), &ModFilterWidget::filterUnchanged, this, [&]{ - ui->searchButton->setStyleSheet("text-decoration: none"); + m_ui->searchButton->setStyleSheet("text-decoration: none"); }); } - -/******** Qt things ********/ - -void ModPage::openedImpl() -{ - updateSelectionButton(); - triggerSearch(); -} - -auto ModPage::eventFilter(QObject* watched, QEvent* event) -> bool -{ - if (watched == ui->searchEdit && event->type() == QEvent::KeyPress) { - auto* keyEvent = dynamic_cast(event); - if (keyEvent->key() == Qt::Key_Return) { - triggerSearch(); - keyEvent->accept(); - return true; - } else { - if (m_search_timer.isActive()) - m_search_timer.stop(); - - m_search_timer.start(350); - } - } else if (watched == ui->packView && event->type() == QEvent::KeyPress) { - auto* keyEvent = dynamic_cast(event); - if (keyEvent->key() == Qt::Key_Return) { - onModSelected(); - - // To have the 'select mod' button outlined instead of the 'review and confirm' one - ui->modSelectionButton->setFocus(Qt::FocusReason::ShortcutFocusReason); - ui->packView->setFocus(Qt::FocusReason::NoFocusReason); - - keyEvent->accept(); - return true; - } - } - return QWidget::eventFilter(watched, event); -} - - /******** Callbacks to events in the UI (set up in the derived classes) ********/ void ModPage::filterMods() @@ -163,176 +94,37 @@ void ModPage::triggerSearch() m_filter = m_filter_widget->getFilter(); if (changed) { - ui->packView->clearSelection(); - ui->packDescription->clear(); - ui->versionSelectionBox->clear(); + m_ui->packView->clearSelection(); + m_ui->packDescription->clear(); + m_ui->versionSelectionBox->clear(); updateSelectionButton(); } - listModel->searchWithTerm(getSearchTerm(), ui->sortByBox->currentIndex(), changed); - m_fetch_progress.watch(listModel->activeJob()); -} - -QString ModPage::getSearchTerm() const -{ - return ui->searchEdit->text(); -} -void ModPage::setSearchTerm(QString term) -{ - ui->searchEdit->setText(term); + static_cast(m_model)->searchWithTerm(getSearchTerm(), m_ui->sortByBox->currentIndex(), changed); + m_fetch_progress.watch(&m_model->activeJob()); } -void ModPage::onSelectionChanged(QModelIndex curr, QModelIndex prev) +QMap ModPage::urlHandlers() const { - ui->versionSelectionBox->clear(); - - if (!curr.isValid()) { return; } - - current = listModel->data(curr, Qt::UserRole).value(); - - if (!current.versionsLoaded) { - qDebug() << QString("Loading %1 mod versions").arg(debugName()); - - ui->modSelectionButton->setText(tr("Loading versions...")); - ui->modSelectionButton->setEnabled(false); - - listModel->requestModVersions(current, curr); - } else { - for (int i = 0; i < current.versions.size(); i++) { - ui->versionSelectionBox->addItem(current.versions[i].version, QVariant(i)); - } - if (ui->versionSelectionBox->count() == 0) { ui->versionSelectionBox->addItem(tr("No valid version found."), QVariant(-1)); } - - updateSelectionButton(); - } - - if(!current.extraDataLoaded){ - qDebug() << QString("Loading %1 mod info").arg(debugName()); - - listModel->requestModInfo(current, curr); - } - - updateUi(); -} - -void ModPage::onVersionSelectionChanged(QString data) -{ - if (data.isNull() || data.isEmpty()) { - selectedVersion = -1; - return; - } - selectedVersion = ui->versionSelectionBox->currentData().toInt(); - updateSelectionButton(); -} - -void ModPage::onModSelected() -{ - if (selectedVersion < 0) - return; - - auto& version = current.versions[selectedVersion]; - if (dialog->isModSelected(current.name, version.fileName)) { - dialog->removeSelectedMod(current.name); - } else { - bool is_indexed = !APPLICATION->settings()->get("ModMetadataDisabled").toBool(); - dialog->addSelectedMod(current.name, new ModDownloadTask(current, version, dialog->mods, is_indexed)); - } - - updateSelectionButton(); - - /* Force redraw on the mods list when the selection changes */ - ui->packView->adjustSize(); -} - -static const QRegularExpression modrinth(QRegularExpression::anchoredPattern("(?:www\\.)?modrinth\\.com\\/mod\\/([^\\/]+)\\/?")); -static const QRegularExpression curseForge(QRegularExpression::anchoredPattern("(?:www\\.)?curseforge\\.com\\/minecraft\\/mc-mods\\/([^\\/]+)\\/?")); -static const QRegularExpression curseForgeOld(QRegularExpression::anchoredPattern("minecraft\\.curseforge\\.com\\/projects\\/([^\\/]+)\\/?")); - -void ModPage::openUrl(const QUrl& url) -{ - // do not allow other url schemes for security reasons - if (!(url.scheme() == "http" || url.scheme() == "https")) { - qWarning() << "Unsupported scheme" << url.scheme(); - return; - } - - // detect mod URLs and search instead - - const QString address = url.host() + url.path(); - QRegularExpressionMatch match; - QString page; - - match = modrinth.match(address); - if (match.hasMatch()) - page = "modrinth"; - else if (APPLICATION->capabilities() & Application::SupportsFlame) { - match = curseForge.match(address); - if (!match.hasMatch()) - match = curseForgeOld.match(address); - - if (match.hasMatch()) - page = "curseforge"; - } - - if (!page.isNull()) { - const QString slug = match.captured(1); - - // ensure the user isn't opening the same mod - if (slug != current.slug) { - dialog->selectPage(page); - - ModPage* newPage = dialog->getSelectedPage(); - - QLineEdit* searchEdit = newPage->ui->searchEdit; - ModPlatform::ListModel* model = newPage->listModel; - QListView* view = newPage->ui->packView; - - auto jump = [url, slug, model, view] { - for (int row = 0; row < model->rowCount({}); row++) { - const QModelIndex index = model->index(row); - const auto pack = model->data(index, Qt::UserRole).value(); - - if (pack.slug == slug) { - view->setCurrentIndex(index); - return; - } - } - - // The final fallback. - QDesktopServices::openUrl(url); - }; - - searchEdit->setText(slug); - newPage->triggerSearch(); - - if (model->activeJob()) - connect(model->activeJob(), &Task::finished, jump); - else - jump(); - - return; - } - } - - // open in the user's web browser - QDesktopServices::openUrl(url); + QMap map; + map.insert(QRegularExpression::anchoredPattern("(?:www\\.)?modrinth\\.com\\/mod\\/([^\\/]+)\\/?"), "modrinth"); + map.insert(QRegularExpression::anchoredPattern("(?:www\\.)?curseforge\\.com\\/minecraft\\/mc-mods\\/([^\\/]+)\\/?"), "curseforge"); + map.insert(QRegularExpression::anchoredPattern("minecraft\\.curseforge\\.com\\/projects\\/([^\\/]+)\\/?"), "curseforge"); + return map; } /******** Make changes to the UI ********/ -void ModPage::retranslate() -{ - ui->retranslateUi(this); -} - -void ModPage::updateModVersions(int prev_count) +void ModPage::updateVersionList() { - auto packProfile = (dynamic_cast(m_instance))->getPackProfile(); + m_ui->versionSelectionBox->clear(); + auto packProfile = (dynamic_cast(m_base_instance)).getPackProfile(); QString mcVersion = packProfile->getComponentVersion("net.minecraft"); - for (int i = 0; i < current.versions.size(); i++) { - auto version = current.versions[i]; + auto current_pack = getCurrentPack(); + for (int i = 0; i < current_pack.versions.size(); i++) { + auto version = current_pack.versions[i]; bool valid = false; for(auto& mcVer : m_filter->versions){ //NOTE: Flame doesn't care about loader, so passing it changes nothing. @@ -344,87 +136,18 @@ void ModPage::updateModVersions(int prev_count) // Only add the version if it's valid or using the 'Any' filter, but never if the version is opted out if ((valid || m_filter->versions.empty()) && !optedOut(version)) - ui->versionSelectionBox->addItem(version.version, QVariant(i)); + m_ui->versionSelectionBox->addItem(version.version, QVariant(i)); } - if (ui->versionSelectionBox->count() == 0 && prev_count != 0) { - ui->versionSelectionBox->addItem(tr("No valid version found!"), QVariant(-1)); - ui->modSelectionButton->setText(tr("Cannot select invalid version :(")); + if (m_ui->versionSelectionBox->count() == 0) { + m_ui->versionSelectionBox->addItem(tr("No valid version found!"), QVariant(-1)); + m_ui->resourceSelectionButton->setText(tr("Cannot select invalid version :(")); } updateSelectionButton(); } - -void ModPage::updateSelectionButton() -{ - if (!isOpened || selectedVersion < 0) { - ui->modSelectionButton->setEnabled(false); - return; - } - - ui->modSelectionButton->setEnabled(true); - auto& version = current.versions[selectedVersion]; - if (!dialog->isModSelected(current.name, version.fileName)) { - ui->modSelectionButton->setText(tr("Select mod for download")); - } else { - ui->modSelectionButton->setText(tr("Deselect mod for download")); - } -} - -void ModPage::updateUi() +void ModPage::addResourceToDialog(ModPlatform::IndexedPack& pack, ModPlatform::IndexedVersion& version) { - QString text = ""; - QString name = current.name; - - if (current.websiteUrl.isEmpty()) - text = name; - else - text = "" + name + ""; - - if (!current.authors.empty()) { - auto authorToStr = [](ModPlatform::ModpackAuthor& author) -> QString { - if (author.url.isEmpty()) { return author.name; } - return QString("%2").arg(author.url, author.name); - }; - QStringList authorStrs; - for (auto& author : current.authors) { - authorStrs.push_back(authorToStr(author)); - } - text += "
" + tr(" by ") + authorStrs.join(", "); - } - - if (current.extraDataLoaded) { - if (!current.extraData.donate.isEmpty()) { - text += "

" + tr("Donate information: "); - auto donateToStr = [](ModPlatform::DonationData& donate) -> QString { - return QString("%2").arg(donate.url, donate.platform); - }; - QStringList donates; - for (auto& donate : current.extraData.donate) { - donates.append(donateToStr(donate)); - } - text += donates.join(", "); - } - - if (!current.extraData.issuesUrl.isEmpty() - || !current.extraData.sourceUrl.isEmpty() - || !current.extraData.wikiUrl.isEmpty() - || !current.extraData.discordUrl.isEmpty()) { - text += "

" + tr("External links:") + "
"; - } - - if (!current.extraData.issuesUrl.isEmpty()) - text += "- " + tr("Issues: %1").arg(current.extraData.issuesUrl) + "
"; - if (!current.extraData.wikiUrl.isEmpty()) - text += "- " + tr("Wiki: %1").arg(current.extraData.wikiUrl) + "
"; - if (!current.extraData.sourceUrl.isEmpty()) - text += "- " + tr("Source code: %1").arg(current.extraData.sourceUrl) + "
"; - if (!current.extraData.discordUrl.isEmpty()) - text += "- " + tr("Discord: %1").arg(current.extraData.discordUrl) + "
"; - } - - text += "
"; - - ui->packDescription->setHtml(text + (current.extraData.body.isEmpty() ? current.description : markdownToHTML(current.extraData.body))); - ui->packDescription->flush(); + bool is_indexed = !APPLICATION->settings()->get("ModMetadataDisabled").toBool(); + m_parent_dialog->addResource(pack.name, new ResourceDownloadTask(pack, version, m_parent_dialog->getBaseModel(), is_indexed)); } diff --git a/launcher/ui/pages/modplatform/ModPage.h b/launcher/ui/pages/modplatform/ModPage.h index c9ccbaf2..8c1fec84 100644 --- a/launcher/ui/pages/modplatform/ModPage.h +++ b/launcher/ui/pages/modplatform/ModPage.h @@ -2,104 +2,58 @@ #include -#include "Application.h" -#include "modplatform/ModAPI.h" #include "modplatform/ModIndex.h" -#include "ui/pages/BasePage.h" -#include "ui/pages/modplatform/ModModel.h" + +#include "ui/pages/modplatform/ResourcePage.h" #include "ui/widgets/ModFilterWidget.h" -#include "ui/widgets/ProgressWidget.h" class ModDownloadDialog; namespace Ui { -class ModPage; +class ResourcePage; } /* This page handles most logic related to browsing and selecting mods to download. */ -class ModPage : public QWidget, public BasePage { +class ModPage : public ResourcePage { Q_OBJECT public: template - static T* create(ModDownloadDialog* dialog, BaseInstance* instance) + static T* create(ModDownloadDialog* dialog, BaseInstance& instance) { auto page = new T(dialog, instance); - auto filter_widget = ModFilterWidget::create(static_cast(instance)->getPackProfile()->getComponentVersion("net.minecraft"), page); + auto filter_widget = ModFilterWidget::create(static_cast(instance).getPackProfile()->getComponentVersion("net.minecraft"), page); page->setFilterWidget(filter_widget); return page; } - ~ModPage() override; - - /* Affects what the user sees */ - auto displayName() const -> QString override = 0; - auto icon() const -> QIcon override = 0; - auto id() const -> QString override = 0; - auto helpPage() const -> QString override = 0; + ~ModPage() override = default; - /* Used internally */ - virtual auto metaEntryBase() const -> QString = 0; - virtual auto debugName() const -> QString = 0; + [[nodiscard]] inline QString resourceString() const override { return tr("mod"); } + [[nodiscard]] QMap urlHandlers() const override; - void retranslate() override; + void addResourceToDialog(ModPlatform::IndexedPack&, ModPlatform::IndexedVersion&) override; - void updateUi(); + virtual auto validateVersion(ModPlatform::IndexedVersion& ver, QString mineVer, std::optional loaders = {}) const -> bool = 0; - auto shouldDisplay() const -> bool override = 0; - virtual auto validateVersion(ModPlatform::IndexedVersion& ver, QString mineVer, ModAPI::ModLoaderTypes loaders = ModAPI::Unspecified) const -> bool = 0; - virtual bool optedOut(ModPlatform::IndexedVersion& ver) const { return false; }; - - auto apiProvider() -> ModAPI* { return api.get(); }; + [[nodiscard]] bool supportsFiltering() const override { return true; }; auto getFilter() const -> const std::shared_ptr { return m_filter; } - auto getDialog() const -> const ModDownloadDialog* { return dialog; } - - /** Get the current term in the search bar. */ - auto getSearchTerm() const -> QString; - /** Programatically set the term in the search bar. */ - void setSearchTerm(QString); - void setFilterWidget(unique_qobject_ptr&); - auto getCurrent() -> ModPlatform::IndexedPack& { return current; } - void updateModVersions(int prev_count = -1); - - void openedImpl() override; - auto eventFilter(QObject* watched, QEvent* event) -> bool override; - - BaseInstance* m_instance; + public slots: + void updateVersionList() override; protected: - ModPage(ModDownloadDialog* dialog, BaseInstance* instance, ModAPI* api); - void updateSelectionButton(); + ModPage(ModDownloadDialog* dialog, BaseInstance& instance); protected slots: virtual void filterMods(); - void triggerSearch(); - void onSelectionChanged(QModelIndex first, QModelIndex second); - void onVersionSelectionChanged(QString data); - void onModSelected(); - virtual void openUrl(const QUrl& url); + void triggerSearch() override; protected: - Ui::ModPage* ui = nullptr; - ModDownloadDialog* dialog = nullptr; - unique_qobject_ptr m_filter_widget; std::shared_ptr m_filter; - - ProgressWidget m_fetch_progress; - - ModPlatform::ListModel* listModel = nullptr; - ModPlatform::IndexedPack current; - - std::unique_ptr api; - - int selectedVersion = -1; - - // Used to do instant searching with a delay to cache quick changes - QTimer m_search_timer; }; diff --git a/launcher/ui/pages/modplatform/ModPage.ui b/launcher/ui/pages/modplatform/ModPage.ui deleted file mode 100644 index 94365aa5..00000000 --- a/launcher/ui/pages/modplatform/ModPage.ui +++ /dev/null @@ -1,118 +0,0 @@ - - - ModPage - - - - 0 - 0 - 837 - 685 - - - - - - - - - false - - - false - - - - - - - Qt::ScrollBarAlwaysOff - - - true - - - - 48 - 48 - - - - - - - - - - Search - - - - - - - Search for mods... - - - - - - - - - - - - Version selected: - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - - - - Select mod for download - - - - - - - - - Filter options - - - - - - - Qt::Vertical - - - - - - - - ProjectDescriptionPage - QTextBrowser -
ui/widgets/ProjectDescriptionPage.h
-
-
- - searchEdit - searchButton - packView - packDescription - sortByBox - versionSelectionBox - - - -
diff --git a/launcher/ui/pages/modplatform/ResourceModel.cpp b/launcher/ui/pages/modplatform/ResourceModel.cpp new file mode 100644 index 00000000..d672a2ac --- /dev/null +++ b/launcher/ui/pages/modplatform/ResourceModel.cpp @@ -0,0 +1,258 @@ +#include "ResourceModel.h" + +#include +#include +#include +#include +#include + +#include "Application.h" +#include "BuildConfig.h" + +#include "net/Download.h" +#include "net/NetJob.h" + +#include "minecraft/MinecraftInstance.h" +#include "minecraft/PackProfile.h" + +#include "modplatform/ModIndex.h" + +#include "ui/pages/modplatform/ResourcePage.h" +#include "ui/widgets/ProjectItem.h" + +QHash ResourceModel::s_running_models; + +ResourceModel::ResourceModel(ResourcePage* parent, ResourceAPI* api) : QAbstractListModel(), m_api(api), m_associated_page(parent) +{ + s_running_models.insert(this, true); +} + +ResourceModel::~ResourceModel() +{ + s_running_models.find(this).value() = false; +} + +auto ResourceModel::data(const QModelIndex& index, int role) const -> QVariant +{ + int pos = index.row(); + if (pos >= m_packs.size() || pos < 0 || !index.isValid()) { + return QString("INVALID INDEX %1").arg(pos); + } + + auto pack = m_packs.at(pos); + switch (role) { + case Qt::ToolTipRole: { + if (pack.description.length() > 100) { + // some magic to prevent to long tooltips and replace html linebreaks + QString edit = pack.description.left(97); + edit = edit.left(edit.lastIndexOf("
")).left(edit.lastIndexOf(" ")).append("..."); + return edit; + } + return pack.description; + } + case Qt::DecorationRole: { + if (auto icon_or_none = const_cast(this)->getIcon(const_cast(index), pack.logoUrl); + icon_or_none.has_value()) + return icon_or_none.value(); + + return APPLICATION->getThemedIcon("screenshot-placeholder"); + } + case Qt::SizeHintRole: + return QSize(0, 58); + case Qt::UserRole: { + QVariant v; + v.setValue(pack); + return v; + } + // Custom data + case UserDataTypes::TITLE: + return pack.name; + case UserDataTypes::DESCRIPTION: + return pack.description; + case UserDataTypes::SELECTED: + return isPackSelected(pack); + default: + break; + } + + return {}; +} + +bool ResourceModel::setData(const QModelIndex& index, const QVariant& value, int role) +{ + int pos = index.row(); + if (pos >= m_packs.size() || pos < 0 || !index.isValid()) + return false; + + m_packs[pos] = value.value(); + + return true; +} + +QString ResourceModel::debugName() const +{ + return m_associated_page->debugName() + " (Model)"; +} + +void ResourceModel::fetchMore(const QModelIndex& parent) +{ + if (parent.isValid()) + return; + + Q_ASSERT(m_next_search_offset != 0); + + search(); +} + +void ResourceModel::search() +{ + if (!m_current_job.isRunning()) + m_current_job.clear(); + + auto args{ createSearchArguments() }; + + auto callbacks{ createSearchCallbacks() }; + Q_ASSERT(callbacks.on_succeed); + + // Use defaults if no callbacks are set + if (!callbacks.on_fail) + callbacks.on_fail = [this](QString reason, int network_error_code) { + if (!s_running_models.constFind(this).value()) + return; + searchRequestFailed(reason, network_error_code); + }; + if (!callbacks.on_abort) + callbacks.on_abort = [this] { + if (!s_running_models.constFind(this).value()) + return; + searchRequestAborted(); + }; + + if (auto job = m_api->searchProjects(std::move(args), std::move(callbacks)); job) + addActiveJob(job); +} + +void ResourceModel::loadEntry(QModelIndex& entry) +{ + auto const& pack = m_packs[entry.row()]; + + if (!m_current_job.isRunning()) + m_current_job.clear(); + + if (!pack.versionsLoaded) { + auto args{ createVersionsArguments(entry) }; + auto callbacks{ createVersionsCallbacks(entry) }; + + if (auto job = m_api->getProjectVersions(std::move(args), std::move(callbacks)); job) + addActiveJob(job); + } + + if (!pack.extraDataLoaded) { + auto args{ createInfoArguments(entry) }; + auto callbacks{ createInfoCallbacks(entry) }; + + if (auto job = m_api->getProjectInfo(std::move(args), std::move(callbacks)); job) + addActiveJob(job); + } +} + +void ResourceModel::refresh() +{ + if (m_current_job.isRunning()) { + m_current_job.abort(); + m_search_state = SearchState::ResetRequested; + return; + } + + clearData(); + m_search_state = SearchState::None; + + m_next_search_offset = 0; + search(); +} + +void ResourceModel::clearData() +{ + beginResetModel(); + m_packs.clear(); + endResetModel(); +} + +std::optional ResourceModel::getIcon(QModelIndex& index, const QUrl& url) +{ + QPixmap pixmap; + if (QPixmapCache::find(url.toString(), &pixmap)) + return { pixmap }; + + if (!m_current_icon_job) + m_current_icon_job = new NetJob("IconJob", APPLICATION->network()); + + if (m_currently_running_icon_actions.contains(url)) + return {}; + if (m_failed_icon_actions.contains(url)) + return {}; + + auto cache_entry = APPLICATION->metacache()->resolveEntry( + m_associated_page->metaEntryBase(), + QString("logos/%1").arg(QString(QCryptographicHash::hash(url.toEncoded(), QCryptographicHash::Algorithm::Sha1).toHex()))); + auto icon_fetch_action = Net::Download::makeCached(url, cache_entry); + + auto full_file_path = cache_entry->getFullPath(); + connect(icon_fetch_action.get(), &NetAction::succeeded, this, [=] { + auto icon = QIcon(full_file_path); + QPixmapCache::insert(url.toString(), icon.pixmap(icon.actualSize({ 64, 64 }))); + + m_currently_running_icon_actions.remove(url); + + emit dataChanged(index, index, { Qt::DecorationRole }); + }); + connect(icon_fetch_action.get(), &NetAction::failed, this, [=] { + m_currently_running_icon_actions.remove(url); + m_failed_icon_actions.insert(url); + }); + + m_currently_running_icon_actions.insert(url); + + m_current_icon_job->addNetAction(icon_fetch_action); + if (!m_current_icon_job->isRunning()) + QMetaObject::invokeMethod(m_current_icon_job.get(), &NetJob::start); + + return {}; +} + +bool ResourceModel::isPackSelected(const ModPlatform::IndexedPack& pack) const +{ + return m_associated_page->isPackSelected(pack); +} + +void ResourceModel::searchRequestFailed(QString reason, int network_error_code) +{ + switch (network_error_code) { + default: + // Network error + QMessageBox::critical(nullptr, tr("Error"), tr("A network error occurred. Could not load mods.")); + break; + case 409: + // 409 Gone, notify user to update + QMessageBox::critical(nullptr, tr("Error"), + //: %1 refers to the launcher itself + QString("%1 %2") + .arg(m_associated_page->displayName()) + .arg(tr("API version too old!\nPlease update %1!").arg(BuildConfig.LAUNCHER_DISPLAYNAME))); + break; + } + + m_search_state = SearchState::Finished; +} + +void ResourceModel::searchRequestAborted() +{ + if (m_search_state != SearchState::ResetRequested) + qCritical() << "Search task in" << debugName() << "aborted by an unknown reason!"; + + // Retry fetching + clearData(); + + m_next_search_offset = 0; + search(); +} diff --git a/launcher/ui/pages/modplatform/ResourceModel.h b/launcher/ui/pages/modplatform/ResourceModel.h new file mode 100644 index 00000000..af0e9f55 --- /dev/null +++ b/launcher/ui/pages/modplatform/ResourceModel.h @@ -0,0 +1,101 @@ +#pragma once + +#include + +#include + +#include "QObjectPtr.h" +#include "modplatform/ResourceAPI.h" +#include "tasks/ConcurrentTask.h" + +class NetJob; +class ResourcePage; +class ResourceAPI; + +namespace ModPlatform { +struct IndexedPack; +} + + +class ResourceModel : public QAbstractListModel { + Q_OBJECT + + public: + ResourceModel(ResourcePage* parent, ResourceAPI* api); + ~ResourceModel() override; + + [[nodiscard]] auto data(const QModelIndex&, int role) const -> QVariant override; + bool setData(const QModelIndex& index, const QVariant& value, int role) override; + + [[nodiscard]] auto debugName() const -> QString; + + [[nodiscard]] inline int rowCount(const QModelIndex& parent) const override { return parent.isValid() ? 0 : m_packs.size(); } + [[nodiscard]] inline int columnCount(const QModelIndex& parent) const override { return parent.isValid() ? 0 : 1; }; + [[nodiscard]] inline auto flags(const QModelIndex& index) const -> Qt::ItemFlags override { return QAbstractListModel::flags(index); }; + + inline void addActiveJob(Task::Ptr ptr) { m_current_job.addTask(ptr); if (!m_current_job.isRunning()) m_current_job.start(); } + inline Task const& activeJob() { return m_current_job; } + + public slots: + void fetchMore(const QModelIndex& parent) override; + [[nodiscard]] inline bool canFetchMore(const QModelIndex& parent) const override + { + return parent.isValid() ? false : m_search_state == SearchState::CanFetchMore; + } + + void setSearchTerm(QString term) { m_search_term = term; } + + virtual ResourceAPI::SearchArgs createSearchArguments() = 0; + virtual ResourceAPI::SearchCallbacks createSearchCallbacks() = 0; + + virtual ResourceAPI::VersionSearchArgs createVersionsArguments(QModelIndex&) = 0; + virtual ResourceAPI::VersionSearchCallbacks createVersionsCallbacks(QModelIndex&) = 0; + + virtual ResourceAPI::ProjectInfoArgs createInfoArguments(QModelIndex&) = 0; + virtual ResourceAPI::ProjectInfoCallbacks createInfoCallbacks(QModelIndex&) = 0; + + /** Requests the API for more entries. */ + virtual void search(); + + /** Applies any processing / extra requests needed to fully load the specified entry's information. */ + virtual void loadEntry(QModelIndex&); + + /** Schedule a refresh, clearing the current state. */ + void refresh(); + + /** Gets the icon at the URL for the given index. If it's not fetched yet, fetch it and update when fisinhed. */ + std::optional getIcon(QModelIndex&, const QUrl&); + + protected: + /** Resets the model's data. */ + void clearData(); + + [[nodiscard]] bool isPackSelected(const ModPlatform::IndexedPack&) const; + + protected: + /* Basic search parameters */ + enum class SearchState { None, CanFetchMore, ResetRequested, Finished } m_search_state = SearchState::None; + int m_next_search_offset = 0; + QString m_search_term; + + std::unique_ptr m_api; + + ConcurrentTask m_current_job; + + shared_qobject_ptr m_current_icon_job; + QSet m_currently_running_icon_actions; + QSet m_failed_icon_actions; + + ResourcePage* m_associated_page = nullptr; + + QList m_packs; + + // HACK: We need this to prevent callbacks from calling the model after it has already been deleted. + // This leaks a tiny bit of memory per time the user has opened a resource dialog. How to make this better? + static QHash s_running_models; + + private: + /* Default search request callbacks */ + void searchRequestFailed(QString reason, int network_error_code); + void searchRequestAborted(); +}; diff --git a/launcher/ui/pages/modplatform/ResourcePage.cpp b/launcher/ui/pages/modplatform/ResourcePage.cpp new file mode 100644 index 00000000..3b382d20 --- /dev/null +++ b/launcher/ui/pages/modplatform/ResourcePage.cpp @@ -0,0 +1,347 @@ +#include "ResourcePage.h" +#include "ui_ResourcePage.h" + +#include +#include + +#include "Markdown.h" +#include "ResourceDownloadTask.h" + +#include "minecraft/MinecraftInstance.h" + +#include "ui/dialogs/ResourceDownloadDialog.h" +#include "ui/pages/modplatform/ResourceModel.h" +#include "ui/widgets/ProjectItem.h" + +ResourcePage::ResourcePage(ResourceDownloadDialog* parent, BaseInstance& base_instance) + : QWidget(parent), m_base_instance(base_instance), m_ui(new Ui::ResourcePage), m_parent_dialog(parent), m_fetch_progress(this, false) +{ + m_ui->setupUi(this); + + m_ui->searchEdit->installEventFilter(this); + + m_ui->versionSelectionBox->view()->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); + m_ui->versionSelectionBox->view()->parentWidget()->setMaximumHeight(300); + + m_search_timer.setTimerType(Qt::TimerType::CoarseTimer); + m_search_timer.setSingleShot(true); + + connect(&m_search_timer, &QTimer::timeout, this, &ResourcePage::triggerSearch); + + m_fetch_progress.hideIfInactive(true); + m_fetch_progress.setFixedHeight(24); + m_fetch_progress.progressFormat(""); + + m_ui->gridLayout_3->addWidget(&m_fetch_progress, 0, 0, 1, m_ui->gridLayout_3->columnCount()); + + m_ui->packView->setItemDelegate(new ProjectItemDelegate(this)); + m_ui->packView->installEventFilter(this); + + connect(m_ui->packDescription, &QTextBrowser::anchorClicked, this, &ResourcePage::openUrl); +} + +ResourcePage::~ResourcePage() +{ + delete m_ui; +} + +void ResourcePage::retranslate() +{ + m_ui->retranslateUi(this); +} + +void ResourcePage::openedImpl() +{ + if (!supportsFiltering()) + m_ui->resourceFilterButton->setVisible(false); + + updateSelectionButton(); + triggerSearch(); +} + +auto ResourcePage::eventFilter(QObject* watched, QEvent* event) -> bool +{ + if (event->type() == QEvent::KeyPress) { + auto* keyEvent = static_cast(event); + if (watched == m_ui->searchEdit) { + if (keyEvent->key() == Qt::Key_Return) { + triggerSearch(); + keyEvent->accept(); + return true; + } else { + if (m_search_timer.isActive()) + m_search_timer.stop(); + + m_search_timer.start(350); + } + } else if (watched == m_ui->packView) { + if (keyEvent->key() == Qt::Key_Return) { + onResourceSelected(); + + // To have the 'select mod' button outlined instead of the 'review and confirm' one + m_ui->resourceSelectionButton->setFocus(Qt::FocusReason::ShortcutFocusReason); + m_ui->packView->setFocus(Qt::FocusReason::NoFocusReason); + + keyEvent->accept(); + return true; + } + } + } + + return QWidget::eventFilter(watched, event); +} + +QString ResourcePage::getSearchTerm() const +{ + return m_ui->searchEdit->text(); +} + +void ResourcePage::setSearchTerm(QString term) +{ + m_ui->searchEdit->setText(term); +} + +ModPlatform::IndexedPack ResourcePage::getCurrentPack() const +{ + return m_model->data(m_ui->packView->currentIndex(), Qt::UserRole).value(); +} + +bool ResourcePage::isPackSelected(const ModPlatform::IndexedPack& pack, int version) const +{ + if (version < 0 || !pack.versionsLoaded) + return m_parent_dialog->isSelected(pack.name); + + return m_parent_dialog->isSelected(pack.name, pack.versions[version].fileName); +} + +void ResourcePage::updateUi() +{ + auto current_pack = getCurrentPack(); + + QString text = ""; + QString name = current_pack.name; + + if (current_pack.websiteUrl.isEmpty()) + text = name; + else + text = "" + name + ""; + + if (!current_pack.authors.empty()) { + auto authorToStr = [](ModPlatform::ModpackAuthor& author) -> QString { + if (author.url.isEmpty()) { + return author.name; + } + return QString("%2").arg(author.url, author.name); + }; + QStringList authorStrs; + for (auto& author : current_pack.authors) { + authorStrs.push_back(authorToStr(author)); + } + text += "
" + tr(" by ") + authorStrs.join(", "); + } + + if (current_pack.extraDataLoaded) { + if (!current_pack.extraData.donate.isEmpty()) { + text += "

" + tr("Donate information: "); + auto donateToStr = [](ModPlatform::DonationData& donate) -> QString { + return QString("%2").arg(donate.url, donate.platform); + }; + QStringList donates; + for (auto& donate : current_pack.extraData.donate) { + donates.append(donateToStr(donate)); + } + text += donates.join(", "); + } + + if (!current_pack.extraData.issuesUrl.isEmpty() || !current_pack.extraData.sourceUrl.isEmpty() || + !current_pack.extraData.wikiUrl.isEmpty() || !current_pack.extraData.discordUrl.isEmpty()) { + text += "

" + tr("External links:") + "
"; + } + + if (!current_pack.extraData.issuesUrl.isEmpty()) + text += "- " + tr("Issues: %1").arg(current_pack.extraData.issuesUrl) + "
"; + if (!current_pack.extraData.wikiUrl.isEmpty()) + text += "- " + tr("Wiki: %1").arg(current_pack.extraData.wikiUrl) + "
"; + if (!current_pack.extraData.sourceUrl.isEmpty()) + text += "- " + tr("Source code: %1").arg(current_pack.extraData.sourceUrl) + "
"; + if (!current_pack.extraData.discordUrl.isEmpty()) + text += "- " + tr("Discord: %1").arg(current_pack.extraData.discordUrl) + "
"; + } + + text += "
"; + + m_ui->packDescription->setHtml( + text + (current_pack.extraData.body.isEmpty() ? current_pack.description : markdownToHTML(current_pack.extraData.body))); + m_ui->packDescription->flush(); +} + +void ResourcePage::updateSelectionButton() +{ + if (!isOpened || m_selected_version_index < 0) { + m_ui->resourceSelectionButton->setEnabled(false); + return; + } + + m_ui->resourceSelectionButton->setEnabled(true); + if (!isPackSelected(getCurrentPack(), m_selected_version_index)) { + m_ui->resourceSelectionButton->setText(tr("Select %1 for download").arg(resourceString())); + } else { + m_ui->resourceSelectionButton->setText(tr("Deselect %1 for download").arg(resourceString())); + } +} + +void ResourcePage::updateVersionList() +{ + auto current_pack = getCurrentPack(); + + m_ui->versionSelectionBox->blockSignals(true); + m_ui->versionSelectionBox->clear(); + m_ui->versionSelectionBox->blockSignals(false); + + for (int i = 0; i < current_pack.versions.size(); i++) { + auto& version = current_pack.versions[i]; + if (optedOut(version)) + continue; + + m_ui->versionSelectionBox->addItem(current_pack.versions[i].version, QVariant(i)); + } + + if (m_ui->versionSelectionBox->count() == 0) { + m_ui->versionSelectionBox->addItem(tr("No valid version found."), QVariant(-1)); + m_ui->resourceSelectionButton->setText(tr("Cannot select invalid version :(")); + } + + updateSelectionButton(); +} + +void ResourcePage::onSelectionChanged(QModelIndex curr, QModelIndex prev) +{ + if (!curr.isValid()) { + return; + } + + auto current_pack = getCurrentPack(); + + bool request_load = false; + if (!current_pack.versionsLoaded) { + m_ui->resourceSelectionButton->setText(tr("Loading versions...")); + m_ui->resourceSelectionButton->setEnabled(false); + + request_load = true; + } else { + updateVersionList(); + } + + if (!current_pack.extraDataLoaded) + request_load = true; + + if (request_load) + m_model->loadEntry(curr); + + updateUi(); +} + +void ResourcePage::onVersionSelectionChanged(QString data) +{ + if (data.isNull() || data.isEmpty()) { + m_selected_version_index = -1; + return; + } + + m_selected_version_index = m_ui->versionSelectionBox->currentData().toInt(); + updateSelectionButton(); +} + +void ResourcePage::addResourceToDialog(ModPlatform::IndexedPack& pack, ModPlatform::IndexedVersion& version) +{ + m_parent_dialog->addResource(pack.name, new ResourceDownloadTask(pack, version, m_parent_dialog->getBaseModel())); +} + +void ResourcePage::removeResourceFromDialog(ModPlatform::IndexedPack& pack, ModPlatform::IndexedVersion&) +{ + m_parent_dialog->removeResource(pack.name); +} + +void ResourcePage::onResourceSelected() +{ + if (m_selected_version_index < 0) + return; + + auto current_pack = getCurrentPack(); + + auto& version = current_pack.versions[m_selected_version_index]; + if (m_parent_dialog->isSelected(current_pack.name, version.fileName)) + removeResourceFromDialog(current_pack, version); + else + addResourceToDialog(current_pack, version); + + updateSelectionButton(); + + /* Force redraw on the resource list when the selection changes */ + m_ui->packView->adjustSize(); +} + +void ResourcePage::openUrl(const QUrl& url) +{ + // do not allow other url schemes for security reasons + if (!(url.scheme() == "http" || url.scheme() == "https")) { + qWarning() << "Unsupported scheme" << url.scheme(); + return; + } + + // detect URLs and search instead + + const QString address = url.host() + url.path(); + QRegularExpressionMatch match; + QString page; + + for (auto&& [regex, candidate] : urlHandlers().asKeyValueRange()) { + if (match = QRegularExpression(regex).match(address); match.hasMatch()) { + page = candidate; + break; + } + } + + if (!page.isNull()) { + const QString slug = match.captured(1); + + // ensure the user isn't opening the same mod + if (slug != getCurrentPack().slug) { + m_parent_dialog->selectPage(page); + + auto newPage = m_parent_dialog->getSelectedPage(); + + QLineEdit* searchEdit = newPage->m_ui->searchEdit; + auto model = newPage->m_model; + QListView* view = newPage->m_ui->packView; + + auto jump = [url, slug, model, view] { + for (int row = 0; row < model->rowCount({}); row++) { + const QModelIndex index = model->index(row); + const auto pack = model->data(index, Qt::UserRole).value(); + + if (pack.slug == slug) { + view->setCurrentIndex(index); + return; + } + } + + // The final fallback. + QDesktopServices::openUrl(url); + }; + + searchEdit->setText(slug); + newPage->triggerSearch(); + + if (model->activeJob().isRunning()) + connect(&model->activeJob(), &Task::finished, jump); + else + jump(); + + return; + } + } + + // open in the user's web browser + QDesktopServices::openUrl(url); +} diff --git a/launcher/ui/pages/modplatform/ResourcePage.h b/launcher/ui/pages/modplatform/ResourcePage.h new file mode 100644 index 00000000..32aad3d9 --- /dev/null +++ b/launcher/ui/pages/modplatform/ResourcePage.h @@ -0,0 +1,95 @@ +#pragma once + +#include +#include + +#include "modplatform/ModIndex.h" +#include "modplatform/ResourceAPI.h" + +#include "ui/pages/BasePage.h" +#include "ui/widgets/ProgressWidget.h" + +namespace Ui { +class ResourcePage; +} + +class BaseInstance; +class ResourceModel; +class ResourceDownloadDialog; + +class ResourcePage : public QWidget, public BasePage { + Q_OBJECT + public: + ~ResourcePage() override; + + /* Affects what the user sees */ + [[nodiscard]] auto displayName() const -> QString override = 0; + [[nodiscard]] auto icon() const -> QIcon override = 0; + [[nodiscard]] auto id() const -> QString override = 0; + [[nodiscard]] auto helpPage() const -> QString override = 0; + [[nodiscard]] bool shouldDisplay() const override = 0; + + /* Used internally */ + [[nodiscard]] virtual auto metaEntryBase() const -> QString = 0; + [[nodiscard]] virtual auto debugName() const -> QString = 0; + + [[nodiscard]] virtual inline QString resourceString() const { return tr("resource"); } + + /* Features this resource's page supports */ + [[nodiscard]] virtual bool supportsFiltering() const = 0; + + void retranslate() override; + void openedImpl() override; + auto eventFilter(QObject* watched, QEvent* event) -> bool override; + + /** Get the current term in the search bar. */ + [[nodiscard]] auto getSearchTerm() const -> QString; + /** Programatically set the term in the search bar. */ + void setSearchTerm(QString); + + [[nodiscard]] bool isPackSelected(const ModPlatform::IndexedPack&, int version = -1) const; + [[nodiscard]] auto getCurrentPack() const -> ModPlatform::IndexedPack; + + [[nodiscard]] auto getDialog() const -> const ResourceDownloadDialog* { return m_parent_dialog; } + + protected: + ResourcePage(ResourceDownloadDialog* parent, BaseInstance&); + + public slots: + virtual void updateUi(); + virtual void updateSelectionButton(); + virtual void updateVersionList(); + + virtual void addResourceToDialog(ModPlatform::IndexedPack&, ModPlatform::IndexedVersion&); + virtual void removeResourceFromDialog(ModPlatform::IndexedPack&, ModPlatform::IndexedVersion&); + + protected slots: + virtual void triggerSearch() {} + + void onSelectionChanged(QModelIndex first, QModelIndex second); + void onVersionSelectionChanged(QString data); + void onResourceSelected(); + + /** Associates regex expressions to pages in the order they're given in the map. */ + [[nodiscard]] virtual QMap urlHandlers() const = 0; + virtual void openUrl(const QUrl&); + + /** Whether the version is opted out or not. Currently only makes sense in CF. */ + virtual bool optedOut(ModPlatform::IndexedVersion& ver) const { return false; }; + + public: + BaseInstance& m_base_instance; + + protected: + Ui::ResourcePage* m_ui; + + ResourceDownloadDialog* m_parent_dialog = nullptr; + ResourceModel* m_model = nullptr; + + int m_selected_version_index = -1; + + ProgressWidget m_fetch_progress; + + // Used to do instant searching with a delay to cache quick changes + QTimer m_search_timer; +}; diff --git a/launcher/ui/pages/modplatform/ResourcePage.ui b/launcher/ui/pages/modplatform/ResourcePage.ui new file mode 100644 index 00000000..8fe1d613 --- /dev/null +++ b/launcher/ui/pages/modplatform/ResourcePage.ui @@ -0,0 +1,118 @@ + + + ResourcePage + + + + 0 + 0 + 837 + 685 + + + + + + + + + false + + + false + + + + + + + Qt::ScrollBarAlwaysOff + + + true + + + + 48 + 48 + + + + + + + + + + Search + + + + + + + Search for resources... + + + + + + + + + + + + Version selected: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + + Select resource for download + + + + + + + + + Filter options + + + + + + + Qt::Vertical + + + + + + + + ProjectDescriptionPage + QTextBrowser +
ui/widgets/ProjectDescriptionPage.h
+
+
+ + searchEdit + searchButton + packView + packDescription + sortByBox + versionSelectionBox + + + +
diff --git a/launcher/ui/pages/modplatform/flame/FlameModModel.cpp b/launcher/ui/pages/modplatform/flame/FlameModModel.cpp deleted file mode 100644 index bc2c686c..00000000 --- a/launcher/ui/pages/modplatform/flame/FlameModModel.cpp +++ /dev/null @@ -1,31 +0,0 @@ -#include "FlameModModel.h" -#include "Json.h" -#include "modplatform/flame/FlameModIndex.h" - -namespace FlameMod { - -// NOLINTNEXTLINE(modernize-avoid-c-arrays) -const char* ListModel::sorts[6]{ "Featured", "Popularity", "LastUpdated", "Name", "Author", "TotalDownloads" }; - -void ListModel::loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj) -{ - FlameMod::loadIndexedPack(m, obj); -} - -// We already deal with the URLs when initializing the pack, due to the API response's structure -void ListModel::loadExtraPackInfo(ModPlatform::IndexedPack& m, QJsonObject& obj) -{ - FlameMod::loadBody(m, obj); -} - -void ListModel::loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr) -{ - FlameMod::loadIndexedPackVersions(m, arr, APPLICATION->network(), m_parent->m_instance); -} - -auto ListModel::documentToArray(QJsonDocument& obj) const -> QJsonArray -{ - return Json::ensureArray(obj.object(), "data"); -} - -} // namespace FlameMod diff --git a/launcher/ui/pages/modplatform/flame/FlameModModel.h b/launcher/ui/pages/modplatform/flame/FlameModModel.h deleted file mode 100644 index 6a6aef2e..00000000 --- a/launcher/ui/pages/modplatform/flame/FlameModModel.h +++ /dev/null @@ -1,26 +0,0 @@ -#pragma once - -#include "FlameModPage.h" - -namespace FlameMod { - -class ListModel : public ModPlatform::ListModel { - Q_OBJECT - - public: - ListModel(FlameModPage* parent) : ModPlatform::ListModel(parent) {} - ~ListModel() override = default; - - private: - void loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj) override; - void loadExtraPackInfo(ModPlatform::IndexedPack& m, QJsonObject& obj) override; - void loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr) override; - - auto documentToArray(QJsonDocument& obj) const -> QJsonArray override; - - // NOLINTNEXTLINE(modernize-avoid-c-arrays) - static const char* sorts[6]; - inline auto getSorts() const -> const char** override { return sorts; }; -}; - -} // namespace FlameMod diff --git a/launcher/ui/pages/modplatform/flame/FlameModPage.cpp b/launcher/ui/pages/modplatform/flame/FlameModPage.cpp deleted file mode 100644 index bad78c97..00000000 --- a/launcher/ui/pages/modplatform/flame/FlameModPage.cpp +++ /dev/null @@ -1,97 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-only -/* - * Prism Launcher - Minecraft Launcher - * Copyright (C) 2022 Sefa Eyeoglu - * Copyright (C) 2022 TheKodeToad - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, version 3. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * This file incorporates work covered by the following copyright and - * permission notice: - * - * Copyright 2013-2021 MultiMC Contributors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "FlameModPage.h" -#include "ui_ModPage.h" - -#include "FlameModModel.h" -#include "ui/dialogs/ModDownloadDialog.h" - -FlameModPage::FlameModPage(ModDownloadDialog* dialog, BaseInstance* instance) - : ModPage(dialog, instance, new FlameAPI()) -{ - listModel = new FlameMod::ListModel(this); - ui->packView->setModel(listModel); - - // index is used to set the sorting with the flame api - ui->sortByBox->addItem(tr("Sort by Featured")); - ui->sortByBox->addItem(tr("Sort by Popularity")); - ui->sortByBox->addItem(tr("Sort by Last Updated")); - ui->sortByBox->addItem(tr("Sort by Name")); - ui->sortByBox->addItem(tr("Sort by Author")); - ui->sortByBox->addItem(tr("Sort by Downloads")); - - // sometimes Qt just ignores virtual slots and doesn't work as intended it seems, - // so it's best not to connect them in the parent's contructor... - connect(ui->sortByBox, SIGNAL(currentIndexChanged(int)), this, SLOT(triggerSearch())); - connect(ui->packView->selectionModel(), &QItemSelectionModel::currentChanged, this, &FlameModPage::onSelectionChanged); - connect(ui->versionSelectionBox, &QComboBox::currentTextChanged, this, &FlameModPage::onVersionSelectionChanged); - connect(ui->modSelectionButton, &QPushButton::clicked, this, &FlameModPage::onModSelected); - - ui->packDescription->setMetaEntry(metaEntryBase()); -} - -auto FlameModPage::validateVersion(ModPlatform::IndexedVersion& ver, QString mineVer, ModAPI::ModLoaderTypes loaders) const -> bool -{ - Q_UNUSED(loaders); - return ver.mcVersion.contains(mineVer) && !ver.downloadUrl.isEmpty(); -} - -bool FlameModPage::optedOut(ModPlatform::IndexedVersion& ver) const -{ - return ver.downloadUrl.isEmpty(); -} - -// I don't know why, but doing this on the parent class makes it so that -// other mod providers start loading before being selected, at least with -// my Qt, so we need to implement this in every derived class... -auto FlameModPage::shouldDisplay() const -> bool { return true; } - -void FlameModPage::openUrl(const QUrl& url) -{ - if (url.scheme().isEmpty()) { - QString query = url.query(QUrl::FullyDecoded); - - if (query.startsWith("remoteUrl=")) { - // attempt to resolve url from warning page - query.remove(0, 10); - ModPage::openUrl({QUrl::fromPercentEncoding(query.toUtf8())}); // double decoding is necessary - return; - } - } - - ModPage::openUrl(url); -} diff --git a/launcher/ui/pages/modplatform/flame/FlameModPage.h b/launcher/ui/pages/modplatform/flame/FlameModPage.h deleted file mode 100644 index 58479ab9..00000000 --- a/launcher/ui/pages/modplatform/flame/FlameModPage.h +++ /dev/null @@ -1,70 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-only -/* - * Prism Launcher - Minecraft Launcher - * Copyright (C) 2022 Sefa Eyeoglu - * Copyright (C) 2022 TheKodeToad - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, version 3. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * This file incorporates work covered by the following copyright and - * permission notice: - * - * Copyright 2013-2021 MultiMC Contributors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include "modplatform/ModAPI.h" -#include "ui/pages/modplatform/ModPage.h" - -#include "modplatform/flame/FlameAPI.h" - -class FlameModPage : public ModPage { - Q_OBJECT - - public: - static FlameModPage* create(ModDownloadDialog* dialog, BaseInstance* instance) - { - return ModPage::create(dialog, instance); - } - - FlameModPage(ModDownloadDialog* dialog, BaseInstance* instance); - ~FlameModPage() override = default; - - inline auto displayName() const -> QString override { return "CurseForge"; } - inline auto icon() const -> QIcon override { return APPLICATION->getThemedIcon("flame"); } - inline auto id() const -> QString override { return "curseforge"; } - inline auto helpPage() const -> QString override { return "Mod-platform"; } - - inline auto debugName() const -> QString override { return "Flame"; } - inline auto metaEntryBase() const -> QString override { return "FlameMods"; }; - - auto validateVersion(ModPlatform::IndexedVersion& ver, QString mineVer, ModAPI::ModLoaderTypes loaders = ModAPI::Unspecified) const -> bool override; - bool optedOut(ModPlatform::IndexedVersion& ver) const override; - - auto shouldDisplay() const -> bool override; - - void openUrl(const QUrl& url) override; -}; diff --git a/launcher/ui/pages/modplatform/flame/FlameResourceModels.cpp b/launcher/ui/pages/modplatform/flame/FlameResourceModels.cpp new file mode 100644 index 00000000..b602dfac --- /dev/null +++ b/launcher/ui/pages/modplatform/flame/FlameResourceModels.cpp @@ -0,0 +1,31 @@ +#include "FlameResourceModels.h" +#include "Json.h" +#include "modplatform/flame/FlameModIndex.h" + +namespace FlameMod { + +// NOLINTNEXTLINE(modernize-avoid-c-arrays) +const char* ListModel::sorts[6]{ "Featured", "Popularity", "LastUpdated", "Name", "Author", "TotalDownloads" }; + +void ListModel::loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj) +{ + FlameMod::loadIndexedPack(m, obj); +} + +// We already deal with the URLs when initializing the pack, due to the API response's structure +void ListModel::loadExtraPackInfo(ModPlatform::IndexedPack& m, QJsonObject& obj) +{ + FlameMod::loadBody(m, obj); +} + +void ListModel::loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr) +{ + FlameMod::loadIndexedPackVersions(m, arr, APPLICATION->network(), &m_associated_page->m_base_instance); +} + +auto ListModel::documentToArray(QJsonDocument& obj) const -> QJsonArray +{ + return Json::ensureArray(obj.object(), "data"); +} + +} // namespace FlameMod diff --git a/launcher/ui/pages/modplatform/flame/FlameResourceModels.h b/launcher/ui/pages/modplatform/flame/FlameResourceModels.h new file mode 100644 index 00000000..b94377d3 --- /dev/null +++ b/launcher/ui/pages/modplatform/flame/FlameResourceModels.h @@ -0,0 +1,26 @@ +#pragma once + +#include "modplatform/flame/FlameAPI.h" + +namespace FlameMod { + +class ListModel : public ModPlatform::ListModel { + Q_OBJECT + + public: + ListModel(FlameModPage* parent) : ModPlatform::ListModel(parent, new FlameAPI) {} + ~ListModel() override = default; + + private: + void loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj) override; + void loadExtraPackInfo(ModPlatform::IndexedPack& m, QJsonObject& obj) override; + void loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr) override; + + auto documentToArray(QJsonDocument& obj) const -> QJsonArray override; + + // NOLINTNEXTLINE(modernize-avoid-c-arrays) + static const char* sorts[6]; + inline auto getSorts() const -> const char** override { return sorts; }; +}; + +} // namespace FlameMod diff --git a/launcher/ui/pages/modplatform/flame/FlameResourcePages.cpp b/launcher/ui/pages/modplatform/flame/FlameResourcePages.cpp new file mode 100644 index 00000000..490578ad --- /dev/null +++ b/launcher/ui/pages/modplatform/flame/FlameResourcePages.cpp @@ -0,0 +1,97 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (C) 2022 Sefa Eyeoglu + * Copyright (C) 2022 TheKodeToad + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * This file incorporates work covered by the following copyright and + * permission notice: + * + * Copyright 2013-2021 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "FlameResourcePages.h" +#include "ui_ResourcePage.h" + +#include "FlameResourceModels.h" +#include "ui/dialogs/ModDownloadDialog.h" + +FlameModPage::FlameModPage(ModDownloadDialog* dialog, BaseInstance& instance) + : ModPage(dialog, instance) +{ + m_model = new FlameMod::ListModel(this); + m_ui->packView->setModel(m_model); + + // index is used to set the sorting with the flame api + m_ui->sortByBox->addItem(tr("Sort by Featured")); + m_ui->sortByBox->addItem(tr("Sort by Popularity")); + m_ui->sortByBox->addItem(tr("Sort by Last Updated")); + m_ui->sortByBox->addItem(tr("Sort by Name")); + m_ui->sortByBox->addItem(tr("Sort by Author")); + m_ui->sortByBox->addItem(tr("Sort by Downloads")); + + // sometimes Qt just ignores virtual slots and doesn't work as intended it seems, + // so it's best not to connect them in the parent's contructor... + connect(m_ui->sortByBox, SIGNAL(currentIndexChanged(int)), this, SLOT(triggerSearch())); + connect(m_ui->packView->selectionModel(), &QItemSelectionModel::currentChanged, this, &FlameModPage::onSelectionChanged); + connect(m_ui->versionSelectionBox, &QComboBox::currentTextChanged, this, &FlameModPage::onVersionSelectionChanged); + connect(m_ui->resourceSelectionButton, &QPushButton::clicked, this, &FlameModPage::onResourceSelected); + + m_ui->packDescription->setMetaEntry(metaEntryBase()); +} + +auto FlameModPage::validateVersion(ModPlatform::IndexedVersion& ver, QString mineVer, std::optional loaders) const -> bool +{ + Q_UNUSED(loaders); + return ver.mcVersion.contains(mineVer) && !ver.downloadUrl.isEmpty(); +} + +bool FlameModPage::optedOut(ModPlatform::IndexedVersion& ver) const +{ + return ver.downloadUrl.isEmpty(); +} + +// I don't know why, but doing this on the parent class makes it so that +// other mod providers start loading before being selected, at least with +// my Qt, so we need to implement this in every derived class... +auto FlameModPage::shouldDisplay() const -> bool { return true; } + +void FlameModPage::openUrl(const QUrl& url) +{ + if (url.scheme().isEmpty()) { + QString query = url.query(QUrl::FullyDecoded); + + if (query.startsWith("remoteUrl=")) { + // attempt to resolve url from warning page + query.remove(0, 10); + ModPage::openUrl({QUrl::fromPercentEncoding(query.toUtf8())}); // double decoding is necessary + return; + } + } + + ModPage::openUrl(url); +} diff --git a/launcher/ui/pages/modplatform/flame/FlameResourcePages.h b/launcher/ui/pages/modplatform/flame/FlameResourcePages.h new file mode 100644 index 00000000..597a0c25 --- /dev/null +++ b/launcher/ui/pages/modplatform/flame/FlameResourcePages.h @@ -0,0 +1,71 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (C) 2022 Sefa Eyeoglu + * Copyright (C) 2022 TheKodeToad + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * This file incorporates work covered by the following copyright and + * permission notice: + * + * Copyright 2013-2021 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "Application.h" + +#include "modplatform/ResourceAPI.h" + +#include "ui/pages/modplatform/ModPage.h" + +class FlameModPage : public ModPage { + Q_OBJECT + + public: + static FlameModPage* create(ModDownloadDialog* dialog, BaseInstance& instance) + { + return ModPage::create(dialog, instance); + } + + FlameModPage(ModDownloadDialog* dialog, BaseInstance& instance); + ~FlameModPage() override = default; + + inline auto displayName() const -> QString override { return "CurseForge"; } + inline auto icon() const -> QIcon override { return APPLICATION->getThemedIcon("flame"); } + inline auto id() const -> QString override { return "curseforge"; } + inline auto helpPage() const -> QString override { return "Mod-platform"; } + + inline auto debugName() const -> QString override { return "Flame"; } + inline auto metaEntryBase() const -> QString override { return "FlameMods"; }; + + auto validateVersion(ModPlatform::IndexedVersion& ver, QString mineVer, std::optional loaders = {}) const -> bool override; + bool optedOut(ModPlatform::IndexedVersion& ver) const override; + + auto shouldDisplay() const -> bool override; + + void openUrl(const QUrl& url) override; +}; diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthModModel.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthModModel.cpp deleted file mode 100644 index af92e63e..00000000 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthModModel.cpp +++ /dev/null @@ -1,48 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-only -/* - * PolyMC - Minecraft Launcher - * Copyright (C) 2022 Sefa Eyeoglu - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, version 3. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#include "ModrinthModModel.h" - -#include "modplatform/modrinth/ModrinthPackIndex.h" - -namespace Modrinth { - -// NOLINTNEXTLINE(modernize-avoid-c-arrays) -const char* ListModel::sorts[5]{ "relevance", "downloads", "follows", "updated", "newest" }; - -void ListModel::loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj) -{ - Modrinth::loadIndexedPack(m, obj); -} - -void ListModel::loadExtraPackInfo(ModPlatform::IndexedPack& m, QJsonObject& obj) -{ - Modrinth::loadExtraPackData(m, obj); -} - -void ListModel::loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr) -{ - Modrinth::loadIndexedPackVersions(m, arr, APPLICATION->network(), m_parent->m_instance); -} - -auto ListModel::documentToArray(QJsonDocument& obj) const -> QJsonArray -{ - return obj.object().value("hits").toArray(); -} - -} // namespace Modrinth diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthModModel.h b/launcher/ui/pages/modplatform/modrinth/ModrinthModModel.h deleted file mode 100644 index 386897fd..00000000 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthModModel.h +++ /dev/null @@ -1,44 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-only -/* - * PolyMC - Minecraft Launcher - * Copyright (C) 2022 Sefa Eyeoglu - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, version 3. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#pragma once - -#include "ModrinthModPage.h" - -namespace Modrinth { - -class ListModel : public ModPlatform::ListModel { - Q_OBJECT - - public: - ListModel(ModrinthModPage* parent) : ModPlatform::ListModel(parent){}; - ~ListModel() override = default; - - private: - void loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj) override; - void loadExtraPackInfo(ModPlatform::IndexedPack& m, QJsonObject& obj) override; - void loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr) override; - - auto documentToArray(QJsonDocument& obj) const -> QJsonArray override; - - // NOLINTNEXTLINE(modernize-avoid-c-arrays) - static const char* sorts[5]; - inline auto getSorts() const -> const char** override { return sorts; }; -}; - -} // namespace Modrinth diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthModPage.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthModPage.cpp deleted file mode 100644 index c531ea90..00000000 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthModPage.cpp +++ /dev/null @@ -1,84 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-only -/* - * PolyMC - Minecraft Launcher - * Copyright (C) 2022 Sefa Eyeoglu - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, version 3. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * This file incorporates work covered by the following copyright and - * permission notice: - * - * Copyright 2013-2021 MultiMC Contributors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "ModrinthModPage.h" -#include "modplatform/modrinth/ModrinthAPI.h" -#include "ui_ModPage.h" - -#include "ModrinthModModel.h" -#include "ui/dialogs/ModDownloadDialog.h" - -ModrinthModPage::ModrinthModPage(ModDownloadDialog* dialog, BaseInstance* instance) - : ModPage(dialog, instance, new ModrinthAPI()) -{ - listModel = new Modrinth::ListModel(this); - ui->packView->setModel(listModel); - - // index is used to set the sorting with the modrinth api - ui->sortByBox->addItem(tr("Sort by Relevance")); - ui->sortByBox->addItem(tr("Sort by Downloads")); - ui->sortByBox->addItem(tr("Sort by Follows")); - ui->sortByBox->addItem(tr("Sort by Last Updated")); - ui->sortByBox->addItem(tr("Sort by Newest")); - - // sometimes Qt just ignores virtual slots and doesn't work as intended it seems, - // so it's best not to connect them in the parent's constructor... - connect(ui->sortByBox, SIGNAL(currentIndexChanged(int)), this, SLOT(triggerSearch())); - connect(ui->packView->selectionModel(), &QItemSelectionModel::currentChanged, this, &ModrinthModPage::onSelectionChanged); - connect(ui->versionSelectionBox, &QComboBox::currentTextChanged, this, &ModrinthModPage::onVersionSelectionChanged); - connect(ui->modSelectionButton, &QPushButton::clicked, this, &ModrinthModPage::onModSelected); - - ui->packDescription->setMetaEntry(metaEntryBase()); -} - -auto ModrinthModPage::validateVersion(ModPlatform::IndexedVersion& ver, QString mineVer, ModAPI::ModLoaderTypes loaders) const -> bool -{ - auto loaderStrings = ModrinthAPI::getModLoaderStrings(loaders); - - auto loaderCompatible = false; - for (auto remoteLoader : ver.loaders) - { - if (loaderStrings.contains(remoteLoader)) { - loaderCompatible = true; - break; - } - } - return ver.mcVersion.contains(mineVer) && loaderCompatible; -} - -// I don't know why, but doing this on the parent class makes it so that -// other mod providers start loading before being selected, at least with -// my Qt, so we need to implement this in every derived class... -auto ModrinthModPage::shouldDisplay() const -> bool { return true; } diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthModPage.h b/launcher/ui/pages/modplatform/modrinth/ModrinthModPage.h deleted file mode 100644 index 40d82e6f..00000000 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthModPage.h +++ /dev/null @@ -1,66 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-only -/* - * PolyMC - Minecraft Launcher - * Copyright (C) 2022 Sefa Eyeoglu - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, version 3. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * This file incorporates work covered by the following copyright and - * permission notice: - * - * Copyright 2013-2021 MultiMC Contributors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include "modplatform/ModAPI.h" -#include "ui/pages/modplatform/ModPage.h" - -#include "modplatform/modrinth/ModrinthAPI.h" - -class ModrinthModPage : public ModPage { - Q_OBJECT - - public: - static ModrinthModPage* create(ModDownloadDialog* dialog, BaseInstance* instance) - { - return ModPage::create(dialog, instance); - } - - ModrinthModPage(ModDownloadDialog* dialog, BaseInstance* instance); - ~ModrinthModPage() override = default; - - inline auto displayName() const -> QString override { return "Modrinth"; } - inline auto icon() const -> QIcon override { return APPLICATION->getThemedIcon("modrinth"); } - inline auto id() const -> QString override { return "modrinth"; } - inline auto helpPage() const -> QString override { return "Mod-platform"; } - - inline auto debugName() const -> QString override { return "Modrinth"; } - inline auto metaEntryBase() const -> QString override { return "ModrinthPacks"; }; - - auto validateVersion(ModPlatform::IndexedVersion& ver, QString mineVer, ModAPI::ModLoaderTypes loaders = ModAPI::Unspecified) const -> bool override; - - auto shouldDisplay() const -> bool override; -}; diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.cpp new file mode 100644 index 00000000..51278546 --- /dev/null +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.cpp @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (C) 2022 Sefa Eyeoglu + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "ModrinthResourceModels.h" + +#include "ui/pages/modplatform/modrinth/ModrinthResourcePages.h" + +#include "modplatform/modrinth/ModrinthAPI.h" +#include "modplatform/modrinth/ModrinthPackIndex.h" + +namespace Modrinth { + +// NOLINTNEXTLINE(modernize-avoid-c-arrays) +const char* ListModel::sorts[5]{ "relevance", "downloads", "follows", "updated", "newest" }; + +void ListModel::loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj) +{ + Modrinth::loadIndexedPack(m, obj); +} + +void ListModel::loadExtraPackInfo(ModPlatform::IndexedPack& m, QJsonObject& obj) +{ + Modrinth::loadExtraPackData(m, obj); +} + +void ListModel::loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr) +{ + Modrinth::loadIndexedPackVersions(m, arr, APPLICATION->network(), &m_associated_page->m_base_instance); +} + +auto ListModel::documentToArray(QJsonDocument& obj) const -> QJsonArray +{ + return obj.object().value("hits").toArray(); +} + +} // namespace Modrinth + + diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.h b/launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.h new file mode 100644 index 00000000..bf62d22f --- /dev/null +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.h @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (C) 2022 Sefa Eyeoglu + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once + +#include "ui/pages/modplatform/ModModel.h" + +#include "ui/pages/modplatform/modrinth/ModrinthResourcePages.h" + +#include "modplatform/modrinth/ModrinthAPI.h" + +namespace Modrinth { + +class ListModel : public ModPlatform::ListModel { + Q_OBJECT + + public: + ListModel(ModrinthModPage* parent) : ModPlatform::ListModel(parent, new ModrinthAPI){}; + ~ListModel() override = default; + + private: + void loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj) override; + void loadExtraPackInfo(ModPlatform::IndexedPack& m, QJsonObject& obj) override; + void loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr) override; + + auto documentToArray(QJsonDocument& obj) const -> QJsonArray override; + + // NOLINTNEXTLINE(modernize-avoid-c-arrays) + static const char* sorts[5]; + inline auto getSorts() const -> const char** override { return sorts; }; +}; + +} // namespace Modrinth + diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.cpp new file mode 100644 index 00000000..17f0bc93 --- /dev/null +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.cpp @@ -0,0 +1,89 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (C) 2022 Sefa Eyeoglu + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * This file incorporates work covered by the following copyright and + * permission notice: + * + * Copyright 2013-2021 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "ModrinthResourcePages.h" +#include "ui_ResourcePage.h" + +#include "modplatform/modrinth/ModrinthAPI.h" + +#include "ModrinthResourceModels.h" +#include "ui/dialogs/ModDownloadDialog.h" + +ModrinthModPage::ModrinthModPage(ModDownloadDialog* dialog, BaseInstance& instance) + : ModPage(dialog, instance) +{ + m_model = new Modrinth::ListModel(this); + m_ui->packView->setModel(m_model); + + // index is used to set the sorting with the modrinth api + m_ui->sortByBox->addItem(tr("Sort by Relevance")); + m_ui->sortByBox->addItem(tr("Sort by Downloads")); + m_ui->sortByBox->addItem(tr("Sort by Follows")); + m_ui->sortByBox->addItem(tr("Sort by Last Updated")); + m_ui->sortByBox->addItem(tr("Sort by Newest")); + + // sometimes Qt just ignores virtual slots and doesn't work as intended it seems, + // so it's best not to connect them in the parent's constructor... + connect(m_ui->sortByBox, SIGNAL(currentIndexChanged(int)), this, SLOT(triggerSearch())); + connect(m_ui->packView->selectionModel(), &QItemSelectionModel::currentChanged, this, &ModrinthModPage::onSelectionChanged); + connect(m_ui->versionSelectionBox, &QComboBox::currentTextChanged, this, &ModrinthModPage::onVersionSelectionChanged); + connect(m_ui->resourceSelectionButton, &QPushButton::clicked, this, &ModrinthModPage::onResourceSelected); + + m_ui->packDescription->setMetaEntry(metaEntryBase()); +} + +auto ModrinthModPage::validateVersion(ModPlatform::IndexedVersion& ver, QString mineVer, std::optional loaders) const -> bool +{ + auto loaderCompatible = !loaders.has_value(); + + if (!loaderCompatible) { + auto loaderStrings = ModrinthAPI::getModLoaderStrings(loaders.value()); + for (auto remoteLoader : ver.loaders) + { + if (loaderStrings.contains(remoteLoader)) { + loaderCompatible = true; + break; + } + } + } + + return ver.mcVersion.contains(mineVer) && loaderCompatible; +} + +// I don't know why, but doing this on the parent class makes it so that +// other mod providers start loading before being selected, at least with +// my Qt, so we need to implement this in every derived class... +auto ModrinthModPage::shouldDisplay() const -> bool { return true; } + diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.h b/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.h new file mode 100644 index 00000000..6f816cfd --- /dev/null +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.h @@ -0,0 +1,72 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (C) 2022 Sefa Eyeoglu + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * This file incorporates work covered by the following copyright and + * permission notice: + * + * Copyright 2013-2021 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "Application.h" + +#include "modplatform/ResourceAPI.h" + +#include "ui/pages/modplatform/ModPage.h" + +static inline QString displayName() { return "Modrinth"; } +static inline QIcon icon() { return APPLICATION->getThemedIcon("modrinth"); } +static inline QString id() { return "modrinth"; } +static inline QString debugName() { return "Modrinth"; } +static inline QString metaEntryBase() { return "ModrinthPacks"; }; + +class ModrinthModPage : public ModPage { + Q_OBJECT + + public: + static ModrinthModPage* create(ModDownloadDialog* dialog, BaseInstance& instance) + { + return ModPage::create(dialog, instance); + } + + ModrinthModPage(ModDownloadDialog* dialog, BaseInstance& instance); + ~ModrinthModPage() override = default; + + [[nodiscard]] bool shouldDisplay() const override; + + [[nodiscard]] inline auto displayName() const -> QString override { return ::displayName(); } \ + [[nodiscard]] inline auto icon() const -> QIcon override { return ::icon(); } \ + [[nodiscard]] inline auto id() const -> QString override { return ::id(); } \ + [[nodiscard]] inline auto debugName() const -> QString override { return ::debugName(); } \ + [[nodiscard]] inline auto metaEntryBase() const -> QString override { return ::metaEntryBase(); } + inline auto helpPage() const -> QString override { return "Mod-platform"; } + + auto validateVersion(ModPlatform::IndexedVersion& ver, QString mineVer, std::optional loaders = {}) const -> bool override; +}; diff --git a/launcher/ui/widgets/ProgressWidget.cpp b/launcher/ui/widgets/ProgressWidget.cpp index b60d9a7a..18b51fc3 100644 --- a/launcher/ui/widgets/ProgressWidget.cpp +++ b/launcher/ui/widgets/ProgressWidget.cpp @@ -39,7 +39,7 @@ void ProgressWidget::progressFormat(QString format) m_bar->setFormat(format); } -void ProgressWidget::watch(Task* task) +void ProgressWidget::watch(const Task* task) { if (!task) return; @@ -57,11 +57,11 @@ void ProgressWidget::watch(Task* task) show(); } -void ProgressWidget::start(Task* task) +void ProgressWidget::start(const Task* task) { watch(task); if (!m_task->isRunning()) - QMetaObject::invokeMethod(m_task, "start", Qt::QueuedConnection); + QMetaObject::invokeMethod(const_cast(m_task), "start", Qt::QueuedConnection); } bool ProgressWidget::exec(std::shared_ptr task) diff --git a/launcher/ui/widgets/ProgressWidget.h b/launcher/ui/widgets/ProgressWidget.h index 4d9097b8..b0458f33 100644 --- a/launcher/ui/widgets/ProgressWidget.h +++ b/launcher/ui/widgets/ProgressWidget.h @@ -27,10 +27,10 @@ class ProgressWidget : public QWidget { public slots: /** Watch the progress of a task. */ - void watch(Task* task); + void watch(const Task* task); /** Watch the progress of a task, and start it if needed */ - void start(Task* task); + void start(const Task* task); /** Blocking way of waiting for a task to finish. */ bool exec(std::shared_ptr task); @@ -50,7 +50,7 @@ class ProgressWidget : public QWidget { private: QLabel* m_label = nullptr; QProgressBar* m_bar = nullptr; - Task* m_task = nullptr; + const Task* m_task = nullptr; bool m_hide_if_inactive = false; }; diff --git a/tests/Packwiz_test.cpp b/tests/Packwiz_test.cpp index 098e8f89..29289469 100644 --- a/tests/Packwiz_test.cpp +++ b/tests/Packwiz_test.cpp @@ -48,7 +48,7 @@ class PackwizTest : public QObject { QCOMPARE(metadata.hash_format, "sha512"); QCOMPARE(metadata.hash, "c8fe6e15ddea32668822dddb26e1851e5f03834be4bcb2eff9c0da7fdc086a9b6cead78e31a44d3bc66335cba11144ee0337c6d5346f1ba63623064499b3188d"); - QCOMPARE(metadata.provider, ModPlatform::Provider::MODRINTH); + QCOMPARE(metadata.provider, ModPlatform::ResourceProvider::MODRINTH); QCOMPARE(metadata.version(), "ug2qKTPR"); QCOMPARE(metadata.mod_id(), "kYq5qkSL"); } @@ -76,7 +76,7 @@ class PackwizTest : public QObject { QCOMPARE(metadata.hash_format, "murmur2"); QCOMPARE(metadata.hash, "1781245820"); - QCOMPARE(metadata.provider, ModPlatform::Provider::FLAME); + QCOMPARE(metadata.provider, ModPlatform::ResourceProvider::FLAME); QCOMPARE(metadata.file_id, 3509043); QCOMPARE(metadata.project_id, 327154); } -- cgit From 45d1319891ce87cc1546a316ad550f892d411633 Mon Sep 17 00:00:00 2001 From: flow Date: Sun, 18 Dec 2022 15:41:46 -0300 Subject: refactor(RD): decouple ResourceModels from ResourcePages This makes it so that we don't need a reference to the parent page in the model. It will be useful once we change the page from a widget-based one to a QML page. It also makes tasks be created in the dialog instead of the page, so that the dialog can also have the necessary information to mark versions as selected / deselected easily. It also makes the task pointers into smart pointers. Signed-off-by: flow --- launcher/ResourceDownloadTask.h | 1 + launcher/modplatform/ModIndex.h | 20 ++++++ launcher/modplatform/ResourceAPI.h | 11 ++- launcher/modplatform/flame/FlameAPI.cpp | 2 +- launcher/modplatform/flame/FlameAPI.h | 2 +- launcher/modplatform/flame/FlameCheckUpdate.cpp | 3 +- launcher/modplatform/flame/FlameModIndex.cpp | 4 +- launcher/modplatform/flame/FlameModIndex.h | 2 +- .../modplatform/helpers/NetworkResourceAPI.cpp | 4 +- launcher/modplatform/modrinth/ModrinthAPI.h | 2 +- .../modplatform/modrinth/ModrinthPackIndex.cpp | 4 +- launcher/modplatform/modrinth/ModrinthPackIndex.h | 2 +- launcher/ui/dialogs/ResourceDownloadDialog.cpp | 44 +++++++----- launcher/ui/dialogs/ResourceDownloadDialog.h | 13 ++-- launcher/ui/pages/modplatform/ModModel.cpp | 81 +++++++++++----------- launcher/ui/pages/modplatform/ModModel.h | 13 ++-- launcher/ui/pages/modplatform/ModPage.cpp | 4 +- launcher/ui/pages/modplatform/ModPage.h | 6 ++ launcher/ui/pages/modplatform/ResourceModel.cpp | 19 ++--- launcher/ui/pages/modplatform/ResourceModel.h | 18 ++--- launcher/ui/pages/modplatform/ResourcePage.cpp | 29 ++++---- launcher/ui/pages/modplatform/ResourcePage.h | 4 +- .../modplatform/flame/FlameResourceModels.cpp | 5 +- .../pages/modplatform/flame/FlameResourceModels.h | 8 +-- .../pages/modplatform/flame/FlameResourcePages.cpp | 2 +- .../modrinth/ModrinthResourceModels.cpp | 6 +- .../modplatform/modrinth/ModrinthResourceModels.h | 6 +- .../modplatform/modrinth/ModrinthResourcePages.cpp | 2 +- 28 files changed, 183 insertions(+), 134 deletions(-) (limited to 'launcher/modplatform') diff --git a/launcher/ResourceDownloadTask.h b/launcher/ResourceDownloadTask.h index 350c2edd..275ddbe1 100644 --- a/launcher/ResourceDownloadTask.h +++ b/launcher/ResourceDownloadTask.h @@ -32,6 +32,7 @@ class ResourceDownloadTask : public SequentialTask { public: explicit ResourceDownloadTask(ModPlatform::IndexedPack pack, ModPlatform::IndexedVersion version, const std::shared_ptr packs, bool is_indexed = true); const QString& getFilename() const { return m_pack_version.fileName; } + const QVariant& getVersionID() const { return m_pack_version.fileId; } private: ModPlatform::IndexedPack m_pack; diff --git a/launcher/modplatform/ModIndex.h b/launcher/modplatform/ModIndex.h index f65a6a4b..cd40a6ba 100644 --- a/launcher/modplatform/ModIndex.h +++ b/launcher/modplatform/ModIndex.h @@ -65,6 +65,9 @@ struct IndexedVersion { QString hash; bool is_preferred = true; QString changelog; + + // For internal use, not provided by APIs + bool is_currently_selected = false; }; struct ExtraPackData { @@ -95,6 +98,23 @@ struct IndexedPack { // Don't load by default, since some modplatform don't have that info bool extraDataLoaded = true; ExtraPackData extraData; + + // For internal use, not provided by APIs + [[nodiscard]] bool isVersionSelected(size_t index) const + { + if (!versionsLoaded) + return false; + + return versions.at(index).is_currently_selected; + } + [[nodiscard]] bool isAnyVersionSelected() const + { + if (!versionsLoaded) + return false; + + return std::any_of(versions.constBegin(), versions.constEnd(), + [](auto const& v) { return v.is_currently_selected; }); + } }; } // namespace ModPlatform diff --git a/launcher/modplatform/ResourceAPI.h b/launcher/modplatform/ResourceAPI.h index d18a2caa..49aac712 100644 --- a/launcher/modplatform/ResourceAPI.h +++ b/launcher/modplatform/ResourceAPI.h @@ -69,13 +69,20 @@ class ResourceAPI { }; struct VersionSearchArgs { - QString addonId; + ModPlatform::IndexedPack& pack; std::optional > mcVersions; std::optional loaders; + + void operator=(VersionSearchArgs other) + { + pack = other.pack; + mcVersions = other.mcVersions; + loaders = other.loaders; + } }; struct VersionSearchCallbacks { - std::function on_succeed; + std::function on_succeed; }; struct ProjectInfoArgs { diff --git a/launcher/modplatform/flame/FlameAPI.cpp b/launcher/modplatform/flame/FlameAPI.cpp index ae401399..89249c41 100644 --- a/launcher/modplatform/flame/FlameAPI.cpp +++ b/launcher/modplatform/flame/FlameAPI.cpp @@ -114,7 +114,7 @@ auto FlameAPI::getLatestVersion(VersionSearchArgs&& args) -> ModPlatform::Indexe QEventLoop loop; - auto netJob = new NetJob(QString("Flame::GetLatestVersion(%1)").arg(args.addonId), APPLICATION->network()); + auto netJob = new NetJob(QString("Flame::GetLatestVersion(%1)").arg(args.pack.name), APPLICATION->network()); auto response = new QByteArray(); ModPlatform::IndexedVersion ver; diff --git a/launcher/modplatform/flame/FlameAPI.h b/launcher/modplatform/flame/FlameAPI.h index 114a2716..f3cc0bbf 100644 --- a/launcher/modplatform/flame/FlameAPI.h +++ b/launcher/modplatform/flame/FlameAPI.h @@ -78,7 +78,7 @@ class FlameAPI : public NetworkResourceAPI { [[nodiscard]] std::optional getVersionsURL(VersionSearchArgs const& args) const override { - QString url{QString("https://api.curseforge.com/v1/mods/%1/files?pageSize=10000&").arg(args.addonId)}; + QString url{QString("https://api.curseforge.com/v1/mods/%1/files?pageSize=10000&").arg(args.pack.addonId.toString())}; QStringList get_parameters; if (args.mcVersions.has_value()) diff --git a/launcher/modplatform/flame/FlameCheckUpdate.cpp b/launcher/modplatform/flame/FlameCheckUpdate.cpp index 285fa49f..7aee4f4c 100644 --- a/launcher/modplatform/flame/FlameCheckUpdate.cpp +++ b/launcher/modplatform/flame/FlameCheckUpdate.cpp @@ -129,7 +129,8 @@ void FlameCheckUpdate::executeTask() setStatus(tr("Getting API response from CurseForge for '%1'...").arg(mod->name())); setProgress(i++, m_mods.size()); - auto latest_ver = api.getLatestVersion({ mod->metadata()->project_id.toString(), m_game_versions, m_loaders }); + ModPlatform::IndexedPack pack{ mod->metadata()->project_id.toString() }; + auto latest_ver = api.getLatestVersion({ pack, m_game_versions, m_loaders }); // Check if we were aborted while getting the latest version if (m_was_aborted) { diff --git a/launcher/modplatform/flame/FlameModIndex.cpp b/launcher/modplatform/flame/FlameModIndex.cpp index 617b98ce..7498e830 100644 --- a/launcher/modplatform/flame/FlameModIndex.cpp +++ b/launcher/modplatform/flame/FlameModIndex.cpp @@ -76,10 +76,10 @@ static QString enumToString(int hash_algorithm) void FlameMod::loadIndexedPackVersions(ModPlatform::IndexedPack& pack, QJsonArray& arr, const shared_qobject_ptr& network, - BaseInstance* inst) + const BaseInstance* inst) { QVector unsortedVersions; - auto profile = (dynamic_cast(inst))->getPackProfile(); + auto profile = (dynamic_cast(inst))->getPackProfile(); QString mcVersion = profile->getComponentVersion("net.minecraft"); for (auto versionIter : arr) { diff --git a/launcher/modplatform/flame/FlameModIndex.h b/launcher/modplatform/flame/FlameModIndex.h index db63cdbb..33c4a529 100644 --- a/launcher/modplatform/flame/FlameModIndex.h +++ b/launcher/modplatform/flame/FlameModIndex.h @@ -17,7 +17,7 @@ void loadBody(ModPlatform::IndexedPack& m, QJsonObject& obj); void loadIndexedPackVersions(ModPlatform::IndexedPack& pack, QJsonArray& arr, const shared_qobject_ptr& network, - BaseInstance* inst); + const BaseInstance* inst); auto loadIndexedPackVersion(QJsonObject& obj, bool load_changelog = false) -> ModPlatform::IndexedVersion; } // namespace FlameMod diff --git a/launcher/modplatform/helpers/NetworkResourceAPI.cpp b/launcher/modplatform/helpers/NetworkResourceAPI.cpp index eb17008c..77b085c0 100644 --- a/launcher/modplatform/helpers/NetworkResourceAPI.cpp +++ b/launcher/modplatform/helpers/NetworkResourceAPI.cpp @@ -79,7 +79,7 @@ NetJob::Ptr NetworkResourceAPI::getProjectVersions(VersionSearchArgs&& args, Ver auto versions_url = versions_url_optional.value(); - auto netJob = new NetJob(QString("%1::Versions").arg(args.addonId), APPLICATION->network()); + auto netJob = new NetJob(QString("%1::Versions").arg(args.pack.name), APPLICATION->network()); auto response = new QByteArray(); netJob->addNetAction(Net::Download::makeByteArray(versions_url, response)); @@ -94,7 +94,7 @@ NetJob::Ptr NetworkResourceAPI::getProjectVersions(VersionSearchArgs&& args, Ver return; } - callbacks.on_succeed(doc, args.addonId); + callbacks.on_succeed(doc, args.pack); }); QObject::connect(netJob, &NetJob::finished, [response] { diff --git a/launcher/modplatform/modrinth/ModrinthAPI.h b/launcher/modplatform/modrinth/ModrinthAPI.h index bd84fb54..ec38d9ee 100644 --- a/launcher/modplatform/modrinth/ModrinthAPI.h +++ b/launcher/modplatform/modrinth/ModrinthAPI.h @@ -141,7 +141,7 @@ class ModrinthAPI : public NetworkResourceAPI { get_arguments.append(QString("loaders=[\"%1\"]").arg(getModLoaderStrings(args.loaders.value()).join("\",\""))); return QString("%1/project/%2/version%3%4") - .arg(BuildConfig.MODRINTH_PROD_URL, args.addonId, get_arguments.isEmpty() ? "" : "?", get_arguments.join('&')); + .arg(BuildConfig.MODRINTH_PROD_URL, args.pack.addonId.toString(), get_arguments.isEmpty() ? "" : "?", get_arguments.join('&')); }; auto getGameVersionsArray(std::list mcVersions) const -> QString diff --git a/launcher/modplatform/modrinth/ModrinthPackIndex.cpp b/launcher/modplatform/modrinth/ModrinthPackIndex.cpp index a0161089..f270f470 100644 --- a/launcher/modplatform/modrinth/ModrinthPackIndex.cpp +++ b/launcher/modplatform/modrinth/ModrinthPackIndex.cpp @@ -95,10 +95,10 @@ void Modrinth::loadExtraPackData(ModPlatform::IndexedPack& pack, QJsonObject& ob void Modrinth::loadIndexedPackVersions(ModPlatform::IndexedPack& pack, QJsonArray& arr, const shared_qobject_ptr& network, - BaseInstance* inst) + const BaseInstance* inst) { QVector unsortedVersions; - QString mcVersion = (static_cast(inst))->getPackProfile()->getComponentVersion("net.minecraft"); + QString mcVersion = (static_cast(inst))->getPackProfile()->getComponentVersion("net.minecraft"); for (auto versionIter : arr) { auto obj = versionIter.toObject(); diff --git a/launcher/modplatform/modrinth/ModrinthPackIndex.h b/launcher/modplatform/modrinth/ModrinthPackIndex.h index 31881414..e73e4b18 100644 --- a/launcher/modplatform/modrinth/ModrinthPackIndex.h +++ b/launcher/modplatform/modrinth/ModrinthPackIndex.h @@ -29,7 +29,7 @@ void loadExtraPackData(ModPlatform::IndexedPack& m, QJsonObject& obj); void loadIndexedPackVersions(ModPlatform::IndexedPack& pack, QJsonArray& arr, const shared_qobject_ptr& network, - BaseInstance* inst); + const BaseInstance* inst); auto loadIndexedPackVersion(QJsonObject& obj, QString hash_type = "sha512", QString filename_prefer = "") -> ModPlatform::IndexedVersion; } // namespace Modrinth diff --git a/launcher/ui/dialogs/ResourceDownloadDialog.cpp b/launcher/ui/dialogs/ResourceDownloadDialog.cpp index 523a1636..2eb85928 100644 --- a/launcher/ui/dialogs/ResourceDownloadDialog.cpp +++ b/launcher/ui/dialogs/ResourceDownloadDialog.cpp @@ -141,38 +141,44 @@ ResourcePage* ResourceDownloadDialog::getSelectedPage() return m_selectedPage; } -void ResourceDownloadDialog::addResource(QString name, ResourceDownloadTask* task) +void ResourceDownloadDialog::addResource(ModPlatform::IndexedPack& pack, ModPlatform::IndexedVersion& ver, bool is_indexed) { - removeResource(name); - m_selected.insert(name, task); + removeResource(pack, ver); + + ver.is_currently_selected = true; + m_selected.insert(pack.name, new ResourceDownloadTask(pack, ver, getBaseModel(), is_indexed)); m_buttons.button(QDialogButtonBox::Ok)->setEnabled(!m_selected.isEmpty()); } -void ResourceDownloadDialog::removeResource(QString name) +static ModPlatform::IndexedVersion& getVersionWithID(ModPlatform::IndexedPack& pack, QVariant id) { - if (m_selected.contains(name)) - m_selected.find(name).value()->deleteLater(); - m_selected.remove(name); - - m_buttons.button(QDialogButtonBox::Ok)->setEnabled(!m_selected.isEmpty()); + Q_ASSERT(pack.versionsLoaded); + auto it = std::find_if(pack.versions.begin(), pack.versions.end(), [id](auto const& v) { return v.fileId == id; }); + Q_ASSERT(it != pack.versions.end()); + return *it; } -bool ResourceDownloadDialog::isSelected(QString name, QString filename) const +void ResourceDownloadDialog::removeResource(ModPlatform::IndexedPack& pack, ModPlatform::IndexedVersion& ver) { - auto iter = m_selected.constFind(name); - if (iter == m_selected.constEnd()) - return false; + if (auto selected_task_it = m_selected.find(pack.name); selected_task_it != m_selected.end()) { + auto selected_task = *selected_task_it; + auto old_version_id = selected_task->getVersionID(); - // FIXME: Is there a way to check for versions without checking the filename - // as a heuristic, other than adding such info to ResourceDownloadTask itself? - if (!filename.isEmpty()) - return iter.value()->getFilename() == filename; + // If the new and old version IDs don't match, search for the old one and deselect it. + if (ver.fileId != old_version_id) + getVersionWithID(pack, old_version_id).is_currently_selected = false; + } - return true; + // Deselect the new version too, since all versions of that pack got removed. + ver.is_currently_selected = false; + + m_selected.remove(pack.name); + + m_buttons.button(QDialogButtonBox::Ok)->setEnabled(!m_selected.isEmpty()); } -const QList ResourceDownloadDialog::getTasks() +const QList ResourceDownloadDialog::getTasks() { return m_selected.values(); } diff --git a/launcher/ui/dialogs/ResourceDownloadDialog.h b/launcher/ui/dialogs/ResourceDownloadDialog.h index 29813493..95a5e628 100644 --- a/launcher/ui/dialogs/ResourceDownloadDialog.h +++ b/launcher/ui/dialogs/ResourceDownloadDialog.h @@ -23,6 +23,8 @@ #include #include +#include "QObjectPtr.h" +#include "modplatform/ModIndex.h" #include "ui/pages/BasePageProvider.h" class BaseInstance; @@ -41,6 +43,8 @@ class ResourceDownloadDialog : public QDialog, public BasePageProvider { Q_OBJECT public: + using DownloadTaskPtr = shared_qobject_ptr; + ResourceDownloadDialog(QWidget* parent, const std::shared_ptr base_model); void initializeContainer(); @@ -54,11 +58,10 @@ class ResourceDownloadDialog : public QDialog, public BasePageProvider { bool selectPage(QString pageId); ResourcePage* getSelectedPage(); - void addResource(QString name, ResourceDownloadTask* task); - void removeResource(QString name); - [[nodiscard]] bool isSelected(QString name, QString filename = "") const; + void addResource(ModPlatform::IndexedPack&, ModPlatform::IndexedVersion&, bool is_indexed = false); + void removeResource(ModPlatform::IndexedPack&, ModPlatform::IndexedVersion&); - const QList getTasks(); + const QList getTasks(); [[nodiscard]] const std::shared_ptr getBaseModel() const { return m_base_model; } public slots: @@ -82,7 +85,7 @@ class ResourceDownloadDialog : public QDialog, public BasePageProvider { QDialogButtonBox m_buttons; QVBoxLayout m_vertical_layout; - QHash m_selected; + QHash m_selected; }; diff --git a/launcher/ui/pages/modplatform/ModModel.cpp b/launcher/ui/pages/modplatform/ModModel.cpp index 59399c59..c9dee449 100644 --- a/launcher/ui/pages/modplatform/ModModel.cpp +++ b/launcher/ui/pages/modplatform/ModModel.cpp @@ -1,7 +1,7 @@ #include "ModModel.h" #include "Json.h" -#include "ModPage.h" + #include "minecraft/MinecraftInstance.h" #include "minecraft/PackProfile.h" @@ -9,15 +9,23 @@ namespace ResourceDownload { -ModModel::ModModel(ModPage* parent, ResourceAPI* api) : ResourceModel(parent, api) {} +ModModel::ModModel(BaseInstance const& base_inst, ResourceAPI* api) : ResourceModel(base_inst, api) {} /******** Make data requests ********/ ResourceAPI::SearchArgs ModModel::createSearchArguments() { - auto profile = static_cast(m_associated_page->m_base_instance).getPackProfile(); + auto profile = static_cast(m_base_instance).getPackProfile(); + + Q_ASSERT(profile); + Q_ASSERT(m_filter); + + std::optional> versions {}; + if (!m_filter->versions.empty()) + versions = m_filter->versions; + return { ModPlatform::ResourceType::MOD, m_next_search_offset, m_search_term, - getSorts()[currentSort], profile->getModLoaders(), getMineVersions() }; + getSorts()[currentSort], profile->getModLoaders(), versions }; } ResourceAPI::SearchCallbacks ModModel::createSearchCallbacks() { @@ -30,19 +38,24 @@ ResourceAPI::SearchCallbacks ModModel::createSearchCallbacks() ResourceAPI::VersionSearchArgs ModModel::createVersionsArguments(QModelIndex& entry) { - auto const& pack = m_packs[entry.row()]; - auto profile = static_cast(m_associated_page->m_base_instance).getPackProfile(); + auto& pack = m_packs[entry.row()]; + auto profile = static_cast(m_base_instance).getPackProfile(); + + Q_ASSERT(profile); + Q_ASSERT(m_filter); - return { pack.addonId.toString(), getMineVersions(), profile->getModLoaders() }; + std::optional> versions {}; + if (!m_filter->versions.empty()) + versions = m_filter->versions; + + return { pack, versions, profile->getModLoaders() }; } ResourceAPI::VersionSearchCallbacks ModModel::createVersionsCallbacks(QModelIndex& entry) { - auto const& pack = m_packs[entry.row()]; - - return { [this, pack, entry](auto& doc, auto addonId) { + return { [this, entry](auto& doc, auto& pack) { if (!s_running_models.constFind(this).value()) return; - versionRequestSucceeded(doc, addonId, entry); + versionRequestSucceeded(doc, pack, entry); } }; } @@ -87,7 +100,7 @@ void ModModel::searchRequestFinished(QJsonDocument& doc) loadIndexedPack(pack, packObj); newList.append(pack); } catch (const JSONValidationError& e) { - qWarning() << "Error while loading mod from " << m_associated_page->debugName() << ": " << e.cause(); + qWarning() << "Error while loading mod from " << debugName() << ": " << e.cause(); continue; } } @@ -127,48 +140,36 @@ void ModModel::infoRequestFinished(QJsonDocument& doc, ModPlatform::IndexedPack& new_pack.setValue(pack); if (!setData(index, new_pack, Qt::UserRole)) { qWarning() << "Failed to cache mod info!"; + return; } + + emit projectInfoUpdated(); } - - m_associated_page->updateUi(); } -void ModModel::versionRequestSucceeded(QJsonDocument doc, QString addonId, const QModelIndex& index) +void ModModel::versionRequestSucceeded(QJsonDocument doc, ModPlatform::IndexedPack& pack, const QModelIndex& index) { - auto current = m_associated_page->getCurrentPack(); - if (addonId != current.addonId) { - return; - } - auto arr = doc.isObject() ? Json::ensureArray(doc.object(), "data") : doc.array(); try { - loadIndexedPackVersions(current, arr); + loadIndexedPackVersions(pack, arr); } catch (const JSONValidationError& e) { qDebug() << doc; qWarning() << "Error while reading " << debugName() << " mod version: " << e.cause(); } - // Cache info :^) - QVariant new_pack; - new_pack.setValue(current); - if (!setData(index, new_pack, Qt::UserRole)) { - qWarning() << "Failed to cache mod versions!"; - } - - m_associated_page->updateVersionList(); -} - -/******** Helpers ********/ - -#define MOD_PAGE(x) static_cast(x) + // Check if the index is still valid for this mod or not + if (pack.addonId == data(index, Qt::UserRole).value().addonId) { + // Cache info :^) + QVariant new_pack; + new_pack.setValue(pack); + if (!setData(index, new_pack, Qt::UserRole)) { + qWarning() << "Failed to cache mod versions!"; + return; + } -auto ModModel::getMineVersions() const -> std::optional> -{ - auto versions = MOD_PAGE(m_associated_page)->getFilter()->versions; - if (!versions.empty()) - return versions; - return {}; + emit versionListUpdated(); + } } } // namespace ResourceDownload diff --git a/launcher/ui/pages/modplatform/ModModel.h b/launcher/ui/pages/modplatform/ModModel.h index e3d760a2..39d062f9 100644 --- a/launcher/ui/pages/modplatform/ModModel.h +++ b/launcher/ui/pages/modplatform/ModModel.h @@ -6,6 +6,7 @@ #include "modplatform/ResourceAPI.h" #include "ui/pages/modplatform/ResourceModel.h" +#include "ui/widgets/ModFilterWidget.h" class Version; @@ -17,7 +18,7 @@ class ModModel : public ResourceModel { Q_OBJECT public: - ModModel(ModPage* parent, ResourceAPI* api); + ModModel(const BaseInstance&, ResourceAPI* api); /* Ask the API for more information */ void searchWithTerm(const QString& term, const int sort, const bool filter_changed); @@ -26,12 +27,12 @@ class ModModel : public ResourceModel { virtual void loadExtraPackInfo(ModPlatform::IndexedPack& m, QJsonObject& obj) = 0; virtual void loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr) = 0; + void setFilter(std::shared_ptr filter) { m_filter = filter; } + public slots: void searchRequestFinished(QJsonDocument& doc); - void infoRequestFinished(QJsonDocument& doc, ModPlatform::IndexedPack& pack, const QModelIndex& index); - - void versionRequestSucceeded(QJsonDocument doc, QString addonId, const QModelIndex& index); + void versionRequestSucceeded(QJsonDocument doc, ModPlatform::IndexedPack& pack, const QModelIndex& index); public slots: ResourceAPI::SearchArgs createSearchArguments() override; @@ -47,10 +48,10 @@ class ModModel : public ResourceModel { virtual auto documentToArray(QJsonDocument& obj) const -> QJsonArray = 0; virtual auto getSorts() const -> const char** = 0; - inline auto getMineVersions() const -> std::optional>; - protected: int currentSort = 0; + + std::shared_ptr m_filter = nullptr; }; } // namespace ResourceDownload diff --git a/launcher/ui/pages/modplatform/ModPage.cpp b/launcher/ui/pages/modplatform/ModPage.cpp index 8d441546..556bd642 100644 --- a/launcher/ui/pages/modplatform/ModPage.cpp +++ b/launcher/ui/pages/modplatform/ModPage.cpp @@ -51,8 +51,6 @@ #include "ui/dialogs/ResourceDownloadDialog.h" -#include "ui/pages/modplatform/ModModel.h" - namespace ResourceDownload { ModPage::ModPage(ModDownloadDialog* dialog, BaseInstance& instance) @@ -151,7 +149,7 @@ void ModPage::updateVersionList() void ModPage::addResourceToDialog(ModPlatform::IndexedPack& pack, ModPlatform::IndexedVersion& version) { bool is_indexed = !APPLICATION->settings()->get("ModMetadataDisabled").toBool(); - m_parent_dialog->addResource(pack.name, new ResourceDownloadTask(pack, version, m_parent_dialog->getBaseModel(), is_indexed)); + m_parent_dialog->addResource(pack, version, is_indexed); } } // namespace ResourceDownload diff --git a/launcher/ui/pages/modplatform/ModPage.h b/launcher/ui/pages/modplatform/ModPage.h index 137a6046..2fda3b68 100644 --- a/launcher/ui/pages/modplatform/ModPage.h +++ b/launcher/ui/pages/modplatform/ModPage.h @@ -5,6 +5,7 @@ #include "modplatform/ModIndex.h" #include "ui/pages/modplatform/ResourcePage.h" +#include "ui/pages/modplatform/ModModel.h" #include "ui/widgets/ModFilterWidget.h" namespace Ui { @@ -24,9 +25,14 @@ class ModPage : public ResourcePage { static T* create(ModDownloadDialog* dialog, BaseInstance& instance) { auto page = new T(dialog, instance); + auto model = static_cast(page->getModel()); auto filter_widget = ModFilterWidget::create(static_cast(instance).getPackProfile()->getComponentVersion("net.minecraft"), page); page->setFilterWidget(filter_widget); + model->setFilter(page->getFilter()); + + connect(model, &ResourceModel::versionListUpdated, page, &ResourcePage::updateVersionList); + connect(model, &ResourceModel::projectInfoUpdated, page, &ResourcePage::updateUi); return page; } diff --git a/launcher/ui/pages/modplatform/ResourceModel.cpp b/launcher/ui/pages/modplatform/ResourceModel.cpp index e8af0e7a..cf40fef2 100644 --- a/launcher/ui/pages/modplatform/ResourceModel.cpp +++ b/launcher/ui/pages/modplatform/ResourceModel.cpp @@ -17,14 +17,13 @@ #include "modplatform/ModIndex.h" -#include "ui/pages/modplatform/ResourcePage.h" #include "ui/widgets/ProjectItem.h" namespace ResourceDownload { QHash ResourceModel::s_running_models; -ResourceModel::ResourceModel(ResourcePage* parent, ResourceAPI* api) : QAbstractListModel(), m_api(api), m_associated_page(parent) +ResourceModel::ResourceModel(BaseInstance const& base_inst, ResourceAPI* api) : QAbstractListModel(), m_base_instance(base_inst), m_api(api) { s_running_models.insert(this, true); } @@ -72,7 +71,7 @@ auto ResourceModel::data(const QModelIndex& index, int role) const -> QVariant case UserDataTypes::DESCRIPTION: return pack.description; case UserDataTypes::SELECTED: - return isPackSelected(pack); + return pack.isAnyVersionSelected(); default: break; } @@ -87,13 +86,14 @@ bool ResourceModel::setData(const QModelIndex& index, const QVariant& value, int return false; m_packs[pos] = value.value(); + emit dataChanged(index, index); return true; } QString ResourceModel::debugName() const { - return m_associated_page->debugName() + " (Model)"; + return "ResourceDownload (Model)"; } void ResourceModel::fetchMore(const QModelIndex& parent) @@ -195,7 +195,7 @@ std::optional ResourceModel::getIcon(QModelIndex& index, const QUrl& url) return {}; auto cache_entry = APPLICATION->metacache()->resolveEntry( - m_associated_page->metaEntryBase(), + metaEntryBase(), QString("logos/%1").arg(QString(QCryptographicHash::hash(url.toEncoded(), QCryptographicHash::Algorithm::Sha1).toHex()))); auto icon_fetch_action = Net::Download::makeCached(url, cache_entry); @@ -222,11 +222,6 @@ std::optional ResourceModel::getIcon(QModelIndex& index, const QUrl& url) return {}; } -bool ResourceModel::isPackSelected(const ModPlatform::IndexedPack& pack) const -{ - return m_associated_page->isPackSelected(pack); -} - void ResourceModel::searchRequestFailed(QString reason, int network_error_code) { switch (network_error_code) { @@ -237,9 +232,7 @@ void ResourceModel::searchRequestFailed(QString reason, int network_error_code) case 409: // 409 Gone, notify user to update QMessageBox::critical(nullptr, tr("Error"), - //: %1 refers to the launcher itself - QString("%1 %2") - .arg(m_associated_page->displayName()) + QString("%1") .arg(tr("API version too old!\nPlease update %1!").arg(BuildConfig.LAUNCHER_DISPLAYNAME))); break; } diff --git a/launcher/ui/pages/modplatform/ResourceModel.h b/launcher/ui/pages/modplatform/ResourceModel.h index 6a94c399..af33bf55 100644 --- a/launcher/ui/pages/modplatform/ResourceModel.h +++ b/launcher/ui/pages/modplatform/ResourceModel.h @@ -5,6 +5,7 @@ #include #include "QObjectPtr.h" +#include "BaseInstance.h" #include "modplatform/ResourceAPI.h" #include "tasks/ConcurrentTask.h" @@ -17,19 +18,18 @@ struct IndexedPack; namespace ResourceDownload { -class ResourcePage; - class ResourceModel : public QAbstractListModel { Q_OBJECT public: - ResourceModel(ResourcePage* parent, ResourceAPI* api); + ResourceModel(BaseInstance const&, ResourceAPI* api); ~ResourceModel() override; [[nodiscard]] auto data(const QModelIndex&, int role) const -> QVariant override; bool setData(const QModelIndex& index, const QVariant& value, int role) override; - [[nodiscard]] auto debugName() const -> QString; + [[nodiscard]] virtual auto debugName() const -> QString; + [[nodiscard]] virtual auto metaEntryBase() const -> QString = 0; [[nodiscard]] inline int rowCount(const QModelIndex& parent) const override { return parent.isValid() ? 0 : m_packs.size(); } [[nodiscard]] inline int columnCount(const QModelIndex& parent) const override { return parent.isValid() ? 0 : 1; }; @@ -38,6 +38,10 @@ class ResourceModel : public QAbstractListModel { inline void addActiveJob(Task::Ptr ptr) { m_current_job.addTask(ptr); if (!m_current_job.isRunning()) m_current_job.start(); } inline Task const& activeJob() { return m_current_job; } + signals: + void versionListUpdated(); + void projectInfoUpdated(); + public slots: void fetchMore(const QModelIndex& parent) override; [[nodiscard]] inline bool canFetchMore(const QModelIndex& parent) const override @@ -72,9 +76,9 @@ class ResourceModel : public QAbstractListModel { /** Resets the model's data. */ void clearData(); - [[nodiscard]] bool isPackSelected(const ModPlatform::IndexedPack&) const; - protected: + const BaseInstance& m_base_instance; + /* Basic search parameters */ enum class SearchState { None, CanFetchMore, ResetRequested, Finished } m_search_state = SearchState::None; int m_next_search_offset = 0; @@ -88,8 +92,6 @@ class ResourceModel : public QAbstractListModel { QSet m_currently_running_icon_actions; QSet m_failed_icon_actions; - ResourcePage* m_associated_page = nullptr; - QList m_packs; // HACK: We need this to prevent callbacks from calling the model after it has already been deleted. diff --git a/launcher/ui/pages/modplatform/ResourcePage.cpp b/launcher/ui/pages/modplatform/ResourcePage.cpp index 161b5c22..e04278af 100644 --- a/launcher/ui/pages/modplatform/ResourcePage.cpp +++ b/launcher/ui/pages/modplatform/ResourcePage.cpp @@ -103,17 +103,16 @@ void ResourcePage::setSearchTerm(QString term) m_ui->searchEdit->setText(term); } -ModPlatform::IndexedPack ResourcePage::getCurrentPack() const +bool ResourcePage::setCurrentPack(ModPlatform::IndexedPack pack) { - return m_model->data(m_ui->packView->currentIndex(), Qt::UserRole).value(); + QVariant v; + v.setValue(pack); + return m_model->setData(m_ui->packView->currentIndex(), v, Qt::UserRole); } -bool ResourcePage::isPackSelected(const ModPlatform::IndexedPack& pack, int version) const +ModPlatform::IndexedPack ResourcePage::getCurrentPack() const { - if (version < 0 || !pack.versionsLoaded) - return m_parent_dialog->isSelected(pack.name); - - return m_parent_dialog->isSelected(pack.name, pack.versions[version].fileName); + return m_model->data(m_ui->packView->currentIndex(), Qt::UserRole).value(); } void ResourcePage::updateUi() @@ -185,7 +184,7 @@ void ResourcePage::updateSelectionButton() } m_ui->resourceSelectionButton->setEnabled(true); - if (!isPackSelected(getCurrentPack(), m_selected_version_index)) { + if (!getCurrentPack().isVersionSelected(m_selected_version_index)) { m_ui->resourceSelectionButton->setText(tr("Select %1 for download").arg(resourceString())); } else { m_ui->resourceSelectionButton->setText(tr("Deselect %1 for download").arg(resourceString())); @@ -256,12 +255,12 @@ void ResourcePage::onVersionSelectionChanged(QString data) void ResourcePage::addResourceToDialog(ModPlatform::IndexedPack& pack, ModPlatform::IndexedVersion& version) { - m_parent_dialog->addResource(pack.name, new ResourceDownloadTask(pack, version, m_parent_dialog->getBaseModel())); + m_parent_dialog->addResource(pack, version); } -void ResourcePage::removeResourceFromDialog(ModPlatform::IndexedPack& pack, ModPlatform::IndexedVersion&) +void ResourcePage::removeResourceFromDialog(ModPlatform::IndexedPack& pack, ModPlatform::IndexedVersion& version) { - m_parent_dialog->removeResource(pack.name); + m_parent_dialog->removeResource(pack, version); } void ResourcePage::onResourceSelected() @@ -270,13 +269,19 @@ void ResourcePage::onResourceSelected() return; auto current_pack = getCurrentPack(); + if (!current_pack.versionsLoaded) + return; auto& version = current_pack.versions[m_selected_version_index]; - if (m_parent_dialog->isSelected(current_pack.name, version.fileName)) + if (version.is_currently_selected) removeResourceFromDialog(current_pack, version); else addResourceToDialog(current_pack, version); + // Save the modified pack (and prevent warning in release build) + [[maybe_unused]] bool set = setCurrentPack(current_pack); + Q_ASSERT(set); + updateSelectionButton(); /* Force redraw on the resource list when the selection changes */ diff --git a/launcher/ui/pages/modplatform/ResourcePage.h b/launcher/ui/pages/modplatform/ResourcePage.h index f731cf56..b51c7ccb 100644 --- a/launcher/ui/pages/modplatform/ResourcePage.h +++ b/launcher/ui/pages/modplatform/ResourcePage.h @@ -50,11 +50,13 @@ class ResourcePage : public QWidget, public BasePage { /** Programatically set the term in the search bar. */ void setSearchTerm(QString); - [[nodiscard]] bool isPackSelected(const ModPlatform::IndexedPack&, int version = -1) const; + [[nodiscard]] bool setCurrentPack(ModPlatform::IndexedPack); [[nodiscard]] auto getCurrentPack() const -> ModPlatform::IndexedPack; [[nodiscard]] auto getDialog() const -> const ResourceDownloadDialog* { return m_parent_dialog; } + [[nodiscard]] auto getModel() const -> ResourceModel* { return m_model; } + protected: ResourcePage(ResourceDownloadDialog* parent, BaseInstance&); diff --git a/launcher/ui/pages/modplatform/flame/FlameResourceModels.cpp b/launcher/ui/pages/modplatform/flame/FlameResourceModels.cpp index cfe4080a..d0f109de 100644 --- a/launcher/ui/pages/modplatform/flame/FlameResourceModels.cpp +++ b/launcher/ui/pages/modplatform/flame/FlameResourceModels.cpp @@ -2,6 +2,7 @@ #include "Json.h" +#include "modplatform/flame/FlameAPI.h" #include "modplatform/flame/FlameModIndex.h" namespace ResourceDownload { @@ -9,7 +10,7 @@ namespace ResourceDownload { // NOLINTNEXTLINE(modernize-avoid-c-arrays) const char* FlameModModel::sorts[6]{ "Featured", "Popularity", "LastUpdated", "Name", "Author", "TotalDownloads" }; -FlameModModel::FlameModModel(FlameModPage* parent) : ModModel(parent, new FlameAPI) {} +FlameModModel::FlameModModel(BaseInstance const& base) : ModModel(base, new FlameAPI) {} void FlameModModel::loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj) { @@ -24,7 +25,7 @@ void FlameModModel::loadExtraPackInfo(ModPlatform::IndexedPack& m, QJsonObject& void FlameModModel::loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr) { - FlameMod::loadIndexedPackVersions(m, arr, APPLICATION->network(), &m_associated_page->m_base_instance); + FlameMod::loadIndexedPackVersions(m, arr, APPLICATION->network(), &m_base_instance); } auto FlameModModel::documentToArray(QJsonDocument& obj) const -> QJsonArray diff --git a/launcher/ui/pages/modplatform/flame/FlameResourceModels.h b/launcher/ui/pages/modplatform/flame/FlameResourceModels.h index 501937e2..7b253dce 100644 --- a/launcher/ui/pages/modplatform/flame/FlameResourceModels.h +++ b/launcher/ui/pages/modplatform/flame/FlameResourceModels.h @@ -1,9 +1,6 @@ #pragma once -#include "modplatform/flame/FlameAPI.h" - #include "ui/pages/modplatform/ModModel.h" - #include "ui/pages/modplatform/flame/FlameResourcePages.h" namespace ResourceDownload { @@ -12,10 +9,13 @@ class FlameModModel : public ModModel { Q_OBJECT public: - FlameModModel(FlameModPage* parent); + FlameModModel(const BaseInstance&); ~FlameModModel() override = default; private: + [[nodiscard]] QString debugName() const override { return Flame::debugName() + " (Model)"; } + [[nodiscard]] QString metaEntryBase() const override { return Flame::metaEntryBase(); } + void loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj) override; void loadExtraPackInfo(ModPlatform::IndexedPack& m, QJsonObject& obj) override; void loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr) override; diff --git a/launcher/ui/pages/modplatform/flame/FlameResourcePages.cpp b/launcher/ui/pages/modplatform/flame/FlameResourcePages.cpp index 2a8ab526..67737a76 100644 --- a/launcher/ui/pages/modplatform/flame/FlameResourcePages.cpp +++ b/launcher/ui/pages/modplatform/flame/FlameResourcePages.cpp @@ -45,7 +45,7 @@ namespace ResourceDownload { FlameModPage::FlameModPage(ModDownloadDialog* dialog, BaseInstance& instance) : ModPage(dialog, instance) { - m_model = new FlameModModel(this); + m_model = new FlameModModel(instance); m_ui->packView->setModel(m_model); // index is used to set the sorting with the flame api diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.cpp index ee96f0de..9d26ae05 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.cpp +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.cpp @@ -18,8 +18,6 @@ #include "ModrinthResourceModels.h" -#include "ui/pages/modplatform/modrinth/ModrinthResourcePages.h" - #include "modplatform/modrinth/ModrinthAPI.h" #include "modplatform/modrinth/ModrinthPackIndex.h" @@ -28,7 +26,7 @@ namespace ResourceDownload { // NOLINTNEXTLINE(modernize-avoid-c-arrays) const char* ModrinthModModel::sorts[5]{ "relevance", "downloads", "follows", "updated", "newest" }; -ModrinthModModel::ModrinthModModel(ModrinthModPage* parent) : ModModel(parent, new ModrinthAPI){}; +ModrinthModModel::ModrinthModModel(BaseInstance const& base) : ModModel(base, new ModrinthAPI){}; void ModrinthModModel::loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj) { @@ -42,7 +40,7 @@ void ModrinthModModel::loadExtraPackInfo(ModPlatform::IndexedPack& m, QJsonObjec void ModrinthModModel::loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr) { - ::Modrinth::loadIndexedPackVersions(m, arr, APPLICATION->network(), &m_associated_page->m_base_instance); + ::Modrinth::loadIndexedPackVersions(m, arr, APPLICATION->network(), &m_base_instance); } auto ModrinthModModel::documentToArray(QJsonDocument& obj) const -> QJsonArray diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.h b/launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.h index b0088a73..798a70e6 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.h +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.h @@ -19,6 +19,7 @@ #pragma once #include "ui/pages/modplatform/ModModel.h" +#include "ui/pages/modplatform/modrinth/ModrinthResourcePages.h" namespace ResourceDownload { @@ -28,10 +29,13 @@ class ModrinthModModel : public ModModel { Q_OBJECT public: - ModrinthModModel(ModrinthModPage* parent); + ModrinthModModel(const BaseInstance&); ~ModrinthModModel() override = default; private: + [[nodiscard]] QString debugName() const override { return Modrinth::debugName() + " (Model)"; } + [[nodiscard]] QString metaEntryBase() const override { return Modrinth::metaEntryBase(); } + void loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj) override; void loadExtraPackInfo(ModPlatform::IndexedPack& m, QJsonObject& obj) override; void loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr) override; diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.cpp index 1352e2f6..88621e05 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.cpp +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.cpp @@ -47,7 +47,7 @@ namespace ResourceDownload { ModrinthModPage::ModrinthModPage(ModDownloadDialog* dialog, BaseInstance& instance) : ModPage(dialog, instance) { - m_model = new ModrinthModModel(this); + m_model = new ModrinthModModel(instance); m_ui->packView->setModel(m_model); // index is used to set the sorting with the modrinth api -- cgit From c8eca4fb8508a22b9d4819d57627dd684f8d98c5 Mon Sep 17 00:00:00 2001 From: flow Date: Sun, 18 Dec 2022 17:03:39 -0300 Subject: fix: build with qt5.12 on Linux and pedantic flag Signed-off-by: flow --- launcher/modplatform/ResourceAPI.h | 1 + launcher/ui/dialogs/ResourceDownloadDialog.h | 1 + launcher/ui/pages/modplatform/ResourceModel.h | 15 ++++++++------- launcher/ui/pages/modplatform/ResourcePage.cpp | 4 +++- launcher/ui/pages/modplatform/ResourcePage.h | 4 +++- launcher/ui/pages/modplatform/flame/FlameResourcePages.h | 2 +- .../pages/modplatform/modrinth/ModrinthResourceModels.cpp | 2 +- .../ui/pages/modplatform/modrinth/ModrinthResourcePages.h | 2 +- 8 files changed, 19 insertions(+), 12 deletions(-) (limited to 'launcher/modplatform') diff --git a/launcher/modplatform/ResourceAPI.h b/launcher/modplatform/ResourceAPI.h index 49aac712..78441c34 100644 --- a/launcher/modplatform/ResourceAPI.h +++ b/launcher/modplatform/ResourceAPI.h @@ -39,6 +39,7 @@ #include #include +#include #include "../Version.h" diff --git a/launcher/ui/dialogs/ResourceDownloadDialog.h b/launcher/ui/dialogs/ResourceDownloadDialog.h index 95a5e628..34120350 100644 --- a/launcher/ui/dialogs/ResourceDownloadDialog.h +++ b/launcher/ui/dialogs/ResourceDownloadDialog.h @@ -21,6 +21,7 @@ #include #include +#include #include #include "QObjectPtr.h" diff --git a/launcher/ui/pages/modplatform/ResourceModel.h b/launcher/ui/pages/modplatform/ResourceModel.h index 45af33a2..d0b9234b 100644 --- a/launcher/ui/pages/modplatform/ResourceModel.h +++ b/launcher/ui/pages/modplatform/ResourceModel.h @@ -35,19 +35,16 @@ class ResourceModel : public QAbstractListModel { [[nodiscard]] virtual auto metaEntryBase() const -> QString = 0; [[nodiscard]] inline int rowCount(const QModelIndex& parent) const override { return parent.isValid() ? 0 : m_packs.size(); } - [[nodiscard]] inline int columnCount(const QModelIndex& parent) const override { return parent.isValid() ? 0 : 1; }; - [[nodiscard]] inline auto flags(const QModelIndex& index) const -> Qt::ItemFlags override { return QAbstractListModel::flags(index); }; + [[nodiscard]] inline int columnCount(const QModelIndex& parent) const override { return parent.isValid() ? 0 : 1; } + [[nodiscard]] inline auto flags(const QModelIndex& index) const -> Qt::ItemFlags override { return QAbstractListModel::flags(index); } inline void addActiveJob(Task::Ptr ptr) { m_current_job.addTask(ptr); if (!m_current_job.isRunning()) m_current_job.start(); } inline Task const& activeJob() { return m_current_job; } - signals: - void versionListUpdated(); - void projectInfoUpdated(); - public slots: void fetchMore(const QModelIndex& parent) override; - [[nodiscard]] inline bool canFetchMore(const QModelIndex& parent) const override + // NOTE: Can't use [[nodiscard]] here because of https://bugreports.qt.io/browse/QTBUG-58628 on Qt 5.12 + inline bool canFetchMore(const QModelIndex& parent) const override { return parent.isValid() ? false : m_search_state == SearchState::CanFetchMore; } @@ -105,6 +102,10 @@ class ResourceModel : public QAbstractListModel { /* Default search request callbacks */ void searchRequestFailed(QString reason, int network_error_code); void searchRequestAborted(); + + signals: + void versionListUpdated(); + void projectInfoUpdated(); }; } // namespace ResourceDownload diff --git a/launcher/ui/pages/modplatform/ResourcePage.cpp b/launcher/ui/pages/modplatform/ResourcePage.cpp index e04278af..6e6868c5 100644 --- a/launcher/ui/pages/modplatform/ResourcePage.cpp +++ b/launcher/ui/pages/modplatform/ResourcePage.cpp @@ -302,7 +302,9 @@ void ResourcePage::openUrl(const QUrl& url) QRegularExpressionMatch match; QString page; - for (auto&& [regex, candidate] : urlHandlers().asKeyValueRange()) { + auto handlers = urlHandlers(); + for (auto it = handlers.constKeyValueBegin(); it != handlers.constKeyValueEnd(); it++) { + auto&& [regex, candidate] = *it; if (match = QRegularExpression(regex).match(address); match.hasMatch()) { page = candidate; break; diff --git a/launcher/ui/pages/modplatform/ResourcePage.h b/launcher/ui/pages/modplatform/ResourcePage.h index b51c7ccb..b95c5a40 100644 --- a/launcher/ui/pages/modplatform/ResourcePage.h +++ b/launcher/ui/pages/modplatform/ResourcePage.h @@ -75,8 +75,10 @@ class ResourcePage : public QWidget, public BasePage { void onVersionSelectionChanged(QString data); void onResourceSelected(); + // NOTE: Can't use [[nodiscard]] here because of https://bugreports.qt.io/browse/QTBUG-58628 on Qt 5.12 + /** Associates regex expressions to pages in the order they're given in the map. */ - [[nodiscard]] virtual QMap urlHandlers() const = 0; + virtual QMap urlHandlers() const = 0; virtual void openUrl(const QUrl&); /** Whether the version is opted out or not. Currently only makes sense in CF. */ diff --git a/launcher/ui/pages/modplatform/flame/FlameResourcePages.h b/launcher/ui/pages/modplatform/flame/FlameResourcePages.h index 6c7d0247..12b51aa9 100644 --- a/launcher/ui/pages/modplatform/flame/FlameResourcePages.h +++ b/launcher/ui/pages/modplatform/flame/FlameResourcePages.h @@ -49,7 +49,7 @@ static inline QString displayName() { return "CurseForge"; } static inline QIcon icon() { return APPLICATION->getThemedIcon("flame"); } static inline QString id() { return "curseforge"; } static inline QString debugName() { return "Flame"; } -static inline QString metaEntryBase() { return "FlameMods"; }; +static inline QString metaEntryBase() { return "FlameMods"; } } class FlameModPage : public ModPage { diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.cpp index 9d26ae05..895e23fd 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.cpp +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.cpp @@ -26,7 +26,7 @@ namespace ResourceDownload { // NOLINTNEXTLINE(modernize-avoid-c-arrays) const char* ModrinthModModel::sorts[5]{ "relevance", "downloads", "follows", "updated", "newest" }; -ModrinthModModel::ModrinthModModel(BaseInstance const& base) : ModModel(base, new ModrinthAPI){}; +ModrinthModModel::ModrinthModModel(BaseInstance const& base) : ModModel(base, new ModrinthAPI) {} void ModrinthModModel::loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj) { diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.h b/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.h index 07b32c0c..a263bd44 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.h +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.h @@ -48,7 +48,7 @@ static inline QString displayName() { return "Modrinth"; } static inline QIcon icon() { return APPLICATION->getThemedIcon("modrinth"); } static inline QString id() { return "modrinth"; } static inline QString debugName() { return "Modrinth"; } -static inline QString metaEntryBase() { return "ModrinthPacks"; }; +static inline QString metaEntryBase() { return "ModrinthPacks"; } } class ModrinthModPage : public ModPage { -- cgit From 36571c5e2237c98e194cff326480ebe3e661c586 Mon Sep 17 00:00:00 2001 From: flow Date: Tue, 20 Dec 2022 12:15:17 -0300 Subject: refactor(RD): clear up sorting methods This refactors the sorting methods to join every bit of it into a single list, easing maintanance. It also removes the weird index contraint on the list of methods by adding an index field to the DS that holds the method. Lastly, it puts the available methods on their respective API, so other resources on the same API can re-use them later on. Signed-off-by: flow --- launcher/modplatform/ResourceAPI.h | 17 ++++++++++- launcher/modplatform/flame/FlameAPI.cpp | 15 ++++++++++ launcher/modplatform/flame/FlameAPI.h | 17 ++--------- launcher/modplatform/modrinth/ModrinthAPI.cpp | 12 ++++++++ launcher/modplatform/modrinth/ModrinthAPI.h | 4 ++- launcher/ui/pages/modplatform/ModModel.cpp | 33 ++++++++++++++-------- launcher/ui/pages/modplatform/ModModel.h | 5 +--- launcher/ui/pages/modplatform/ModPage.cpp | 2 +- launcher/ui/pages/modplatform/ResourceModel.h | 5 ++++ launcher/ui/pages/modplatform/ResourcePage.cpp | 11 ++++++++ launcher/ui/pages/modplatform/ResourcePage.h | 4 +-- .../modplatform/flame/FlameResourceModels.cpp | 3 -- .../pages/modplatform/flame/FlameResourceModels.h | 4 --- .../pages/modplatform/flame/FlameResourcePages.cpp | 8 +----- .../modrinth/ModrinthResourceModels.cpp | 3 -- .../modplatform/modrinth/ModrinthResourceModels.h | 4 --- .../modplatform/modrinth/ModrinthResourcePages.cpp | 7 +---- 17 files changed, 93 insertions(+), 61 deletions(-) (limited to 'launcher/modplatform') diff --git a/launcher/modplatform/ResourceAPI.h b/launcher/modplatform/ResourceAPI.h index 78441c34..a2078b94 100644 --- a/launcher/modplatform/ResourceAPI.h +++ b/launcher/modplatform/ResourceAPI.h @@ -54,12 +54,23 @@ class ResourceAPI { enum ModLoaderType { Forge = 1 << 0, Cauldron = 1 << 1, LiteLoader = 1 << 2, Fabric = 1 << 3, Quilt = 1 << 4 }; Q_DECLARE_FLAGS(ModLoaderTypes, ModLoaderType) + struct SortingMethod { + // The index of the sorting method. Used to allow for arbitrary ordering in the list of methods. + // Used by Flame in the API request. + unsigned int index; + // The real name of the sorting, as used in the respective API specification. + // Used by Modrinth in the API request. + QString name; + // The human-readable name of the sorting, used for display in the UI. + QString readable_name; + }; + struct SearchArgs { ModPlatform::ResourceType type{}; int offset = 0; std::optional search; - std::optional sorting; + std::optional sorting; std::optional loaders; std::optional > versions; }; @@ -95,6 +106,10 @@ class ResourceAPI { std::function on_succeed; }; + public: + /** Gets a list of available sorting methods for this API. */ + [[nodiscard]] virtual auto getSortingMethods() const -> QList = 0; + public slots: [[nodiscard]] virtual NetJob::Ptr searchProjects(SearchArgs&&, SearchCallbacks&&) const { diff --git a/launcher/modplatform/flame/FlameAPI.cpp b/launcher/modplatform/flame/FlameAPI.cpp index 89249c41..32729a14 100644 --- a/launcher/modplatform/flame/FlameAPI.cpp +++ b/launcher/modplatform/flame/FlameAPI.cpp @@ -212,3 +212,18 @@ NetJob::Ptr FlameAPI::getFiles(const QStringList& fileIds, QByteArray* response) return netJob; } + +// https://docs.curseforge.com/?python#tocS_ModsSearchSortField +static QList s_sorts = { { 1, "Featured", QObject::tr("Sort by Featured") }, + { 2, "Popularity", QObject::tr("Sort by Popularity") }, + { 3, "LastUpdated", QObject::tr("Sort by Last Updated") }, + { 4, "Name", QObject::tr("Sort by Name") }, + { 5, "Author", QObject::tr("Sort by Author") }, + { 6, "TotalDownloads", QObject::tr("Sort by Downloads") }, + { 7, "Category", QObject::tr("Sort by Category") }, + { 8, "GameVersion", QObject::tr("Sort by Game Version") } }; + +QList FlameAPI::getSortingMethods() const +{ + return s_sorts; +} diff --git a/launcher/modplatform/flame/FlameAPI.h b/launcher/modplatform/flame/FlameAPI.h index f3cc0bbf..2b288564 100644 --- a/launcher/modplatform/flame/FlameAPI.h +++ b/launcher/modplatform/flame/FlameAPI.h @@ -14,20 +14,9 @@ class FlameAPI : public NetworkResourceAPI { NetJob::Ptr matchFingerprints(const QList& fingerprints, QByteArray* response); NetJob::Ptr getFiles(const QStringList& fileIds, QByteArray* response) const; - private: - static int getSortFieldInt(QString const& sortString) - { - return sortString == "Featured" ? 1 - : sortString == "Popularity" ? 2 - : sortString == "LastUpdated" ? 3 - : sortString == "Name" ? 4 - : sortString == "Author" ? 5 - : sortString == "TotalDownloads" ? 6 - : sortString == "Category" ? 7 - : sortString == "GameVersion" ? 8 - : 1; - } + [[nodiscard]] auto getSortingMethods() const -> QList override; + private: static int getClassId(ModPlatform::ResourceType type) { switch (type) { @@ -62,7 +51,7 @@ class FlameAPI : public NetworkResourceAPI { if (args.search.has_value()) get_arguments.append(QString("searchFilter=%1").arg(args.search.value())); if (args.sorting.has_value()) - get_arguments.append(QString("sortField=%1").arg(getSortFieldInt(args.sorting.value()))); + get_arguments.append(QString("sortField=%1").arg(args.sorting.value().index)); get_arguments.append("sortOrder=desc"); if (args.loaders.has_value()) get_arguments.append(QString("modLoaderType=%1").arg(getMappedModLoader(args.loaders.value()))); diff --git a/launcher/modplatform/modrinth/ModrinthAPI.cpp b/launcher/modplatform/modrinth/ModrinthAPI.cpp index 8e64be09..8d7e3acf 100644 --- a/launcher/modplatform/modrinth/ModrinthAPI.cpp +++ b/launcher/modplatform/modrinth/ModrinthAPI.cpp @@ -112,3 +112,15 @@ NetJob::Ptr ModrinthAPI::getProjects(QStringList addonIds, QByteArray* response) return netJob; } + +// https://docs.modrinth.com/api-spec/#tag/projects/operation/searchProjects +static QList s_sorts = { { 1, "relevance", QObject::tr("Sort by Relevance") }, + { 2, "downloads", QObject::tr("Sort by Downloads") }, + { 3, "follows", QObject::tr("Sort by Follows") }, + { 4, "newest", QObject::tr("Sort by Last Updated") }, + { 5, "updated", QObject::tr("Sort by Newest") } }; + +QList ModrinthAPI::getSortingMethods() const +{ + return s_sorts; +} diff --git a/launcher/modplatform/modrinth/ModrinthAPI.h b/launcher/modplatform/modrinth/ModrinthAPI.h index ec38d9ee..949fc46e 100644 --- a/launcher/modplatform/modrinth/ModrinthAPI.h +++ b/launcher/modplatform/modrinth/ModrinthAPI.h @@ -49,6 +49,8 @@ class ModrinthAPI : public NetworkResourceAPI { NetJob::Ptr getProjects(QStringList addonIds, QByteArray* response) const override; public: + [[nodiscard]] auto getSortingMethods() const -> QList override; + inline auto getAuthorURL(const QString& name) const -> QString { return "https://modrinth.com/user/" + name; }; static auto getModLoaderStrings(const ModLoaderTypes types) -> const QStringList @@ -116,7 +118,7 @@ class ModrinthAPI : public NetworkResourceAPI { if (args.search.has_value()) get_arguments.append(QString("query=%1").arg(args.search.value())); if (args.sorting.has_value()) - get_arguments.append(QString("index=%1").arg(args.sorting.value())); + get_arguments.append(QString("index=%1").arg(args.sorting.value().name)); get_arguments.append(QString("facets=%1").arg(createFacets(args))); return BuildConfig.MODRINTH_PROD_URL + "/search?" + get_arguments.join('&'); diff --git a/launcher/ui/pages/modplatform/ModModel.cpp b/launcher/ui/pages/modplatform/ModModel.cpp index c9dee449..5eeac5d5 100644 --- a/launcher/ui/pages/modplatform/ModModel.cpp +++ b/launcher/ui/pages/modplatform/ModModel.cpp @@ -20,12 +20,23 @@ ResourceAPI::SearchArgs ModModel::createSearchArguments() Q_ASSERT(profile); Q_ASSERT(m_filter); - std::optional> versions {}; - if (!m_filter->versions.empty()) - versions = m_filter->versions; + std::optional> versions{}; + std::optional sort{}; + + { // Version filter + if (!m_filter->versions.empty()) + versions = m_filter->versions; + } - return { ModPlatform::ResourceType::MOD, m_next_search_offset, m_search_term, - getSorts()[currentSort], profile->getModLoaders(), versions }; + { // Sorting method + auto sorting_methods = getSortingMethods(); + auto method = std::find_if(sorting_methods.begin(), sorting_methods.end(), + [this](auto const& e) { return m_current_sort_index == e.index; }); + if (method != sorting_methods.end()) + sort = *method; + } + + return { ModPlatform::ResourceType::MOD, m_next_search_offset, m_search_term, sort, profile->getModLoaders(), versions }; } ResourceAPI::SearchCallbacks ModModel::createSearchCallbacks() { @@ -44,8 +55,8 @@ ResourceAPI::VersionSearchArgs ModModel::createVersionsArguments(QModelIndex& en Q_ASSERT(profile); Q_ASSERT(m_filter); - std::optional> versions {}; - if (!m_filter->versions.empty()) + std::optional> versions{}; + if (!m_filter->versions.empty()) versions = m_filter->versions; return { pack, versions, profile->getModLoaders() }; @@ -73,14 +84,14 @@ ResourceAPI::ProjectInfoCallbacks ModModel::createInfoCallbacks(QModelIndex& ent } }; } -void ModModel::searchWithTerm(const QString& term, const int sort, const bool filter_changed) +void ModModel::searchWithTerm(const QString& term, unsigned int sort, bool filter_changed) { - if (m_search_term == term && m_search_term.isNull() == term.isNull() && currentSort == sort && !filter_changed) { + if (m_search_term == term && m_search_term.isNull() == term.isNull() && m_current_sort_index == sort && !filter_changed) { return; } setSearchTerm(term); - currentSort = sort; + m_current_sort_index = sort; refresh(); } @@ -142,7 +153,7 @@ void ModModel::infoRequestFinished(QJsonDocument& doc, ModPlatform::IndexedPack& qWarning() << "Failed to cache mod info!"; return; } - + emit projectInfoUpdated(); } } diff --git a/launcher/ui/pages/modplatform/ModModel.h b/launcher/ui/pages/modplatform/ModModel.h index 39d062f9..3aeba3ef 100644 --- a/launcher/ui/pages/modplatform/ModModel.h +++ b/launcher/ui/pages/modplatform/ModModel.h @@ -21,7 +21,7 @@ class ModModel : public ResourceModel { ModModel(const BaseInstance&, ResourceAPI* api); /* Ask the API for more information */ - void searchWithTerm(const QString& term, const int sort, const bool filter_changed); + void searchWithTerm(const QString& term, unsigned int sort, bool filter_changed); virtual void loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj) = 0; virtual void loadExtraPackInfo(ModPlatform::IndexedPack& m, QJsonObject& obj) = 0; @@ -46,11 +46,8 @@ class ModModel : public ResourceModel { protected: virtual auto documentToArray(QJsonDocument& obj) const -> QJsonArray = 0; - virtual auto getSorts() const -> const char** = 0; protected: - int currentSort = 0; - std::shared_ptr m_filter = nullptr; }; diff --git a/launcher/ui/pages/modplatform/ModPage.cpp b/launcher/ui/pages/modplatform/ModPage.cpp index 556bd642..04cbddcb 100644 --- a/launcher/ui/pages/modplatform/ModPage.cpp +++ b/launcher/ui/pages/modplatform/ModPage.cpp @@ -100,7 +100,7 @@ void ModPage::triggerSearch() updateSelectionButton(); } - static_cast(m_model)->searchWithTerm(getSearchTerm(), m_ui->sortByBox->currentIndex(), changed); + static_cast(m_model)->searchWithTerm(getSearchTerm(), m_ui->sortByBox->currentData().toUInt(), changed); m_fetch_progress.watch(&m_model->activeJob()); } diff --git a/launcher/ui/pages/modplatform/ResourceModel.h b/launcher/ui/pages/modplatform/ResourceModel.h index d0b9234b..facff91d 100644 --- a/launcher/ui/pages/modplatform/ResourceModel.h +++ b/launcher/ui/pages/modplatform/ResourceModel.h @@ -6,7 +6,9 @@ #include "QObjectPtr.h" #include "BaseInstance.h" + #include "modplatform/ResourceAPI.h" + #include "tasks/ConcurrentTask.h" class NetJob; @@ -41,6 +43,8 @@ class ResourceModel : public QAbstractListModel { inline void addActiveJob(Task::Ptr ptr) { m_current_job.addTask(ptr); if (!m_current_job.isRunning()) m_current_job.start(); } inline Task const& activeJob() { return m_current_job; } + [[nodiscard]] auto getSortingMethods() const { return m_api->getSortingMethods(); } + public slots: void fetchMore(const QModelIndex& parent) override; // NOTE: Can't use [[nodiscard]] here because of https://bugreports.qt.io/browse/QTBUG-58628 on Qt 5.12 @@ -83,6 +87,7 @@ class ResourceModel : public QAbstractListModel { enum class SearchState { None, CanFetchMore, ResetRequested, Finished } m_search_state = SearchState::None; int m_next_search_offset = 0; QString m_search_term; + unsigned int m_current_sort_index = 0; std::unique_ptr m_api; diff --git a/launcher/ui/pages/modplatform/ResourcePage.cpp b/launcher/ui/pages/modplatform/ResourcePage.cpp index 6e6868c5..43b77207 100644 --- a/launcher/ui/pages/modplatform/ResourcePage.cpp +++ b/launcher/ui/pages/modplatform/ResourcePage.cpp @@ -103,6 +103,17 @@ void ResourcePage::setSearchTerm(QString term) m_ui->searchEdit->setText(term); } +void ResourcePage::addSortings() +{ + Q_ASSERT(m_model); + + auto sorts = m_model->getSortingMethods(); + std::sort(sorts.begin(), sorts.end(), [](auto const& l, auto const& r) { return l.index < r.index; }); + + for (auto&& sorting : sorts) + m_ui->sortByBox->addItem(sorting.readable_name, QVariant(sorting.index)); +} + bool ResourcePage::setCurrentPack(ModPlatform::IndexedPack pack) { QVariant v; diff --git a/launcher/ui/pages/modplatform/ResourcePage.h b/launcher/ui/pages/modplatform/ResourcePage.h index b95c5a40..547c4056 100644 --- a/launcher/ui/pages/modplatform/ResourcePage.h +++ b/launcher/ui/pages/modplatform/ResourcePage.h @@ -52,14 +52,14 @@ class ResourcePage : public QWidget, public BasePage { [[nodiscard]] bool setCurrentPack(ModPlatform::IndexedPack); [[nodiscard]] auto getCurrentPack() const -> ModPlatform::IndexedPack; - [[nodiscard]] auto getDialog() const -> const ResourceDownloadDialog* { return m_parent_dialog; } - [[nodiscard]] auto getModel() const -> ResourceModel* { return m_model; } protected: ResourcePage(ResourceDownloadDialog* parent, BaseInstance&); + void addSortings(); + public slots: virtual void updateUi(); virtual void updateSelectionButton(); diff --git a/launcher/ui/pages/modplatform/flame/FlameResourceModels.cpp b/launcher/ui/pages/modplatform/flame/FlameResourceModels.cpp index d0f109de..a1cd1f26 100644 --- a/launcher/ui/pages/modplatform/flame/FlameResourceModels.cpp +++ b/launcher/ui/pages/modplatform/flame/FlameResourceModels.cpp @@ -7,9 +7,6 @@ namespace ResourceDownload { -// NOLINTNEXTLINE(modernize-avoid-c-arrays) -const char* FlameModModel::sorts[6]{ "Featured", "Popularity", "LastUpdated", "Name", "Author", "TotalDownloads" }; - FlameModModel::FlameModModel(BaseInstance const& base) : ModModel(base, new FlameAPI) {} void FlameModModel::loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj) diff --git a/launcher/ui/pages/modplatform/flame/FlameResourceModels.h b/launcher/ui/pages/modplatform/flame/FlameResourceModels.h index 7b253dce..47fbbe1a 100644 --- a/launcher/ui/pages/modplatform/flame/FlameResourceModels.h +++ b/launcher/ui/pages/modplatform/flame/FlameResourceModels.h @@ -21,10 +21,6 @@ class FlameModModel : public ModModel { void loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr) override; auto documentToArray(QJsonDocument& obj) const -> QJsonArray override; - - // NOLINTNEXTLINE(modernize-avoid-c-arrays) - static const char* sorts[6]; - inline auto getSorts() const -> const char** override { return sorts; }; }; } // namespace ResourceDownload diff --git a/launcher/ui/pages/modplatform/flame/FlameResourcePages.cpp b/launcher/ui/pages/modplatform/flame/FlameResourcePages.cpp index 67737a76..e34be7fd 100644 --- a/launcher/ui/pages/modplatform/flame/FlameResourcePages.cpp +++ b/launcher/ui/pages/modplatform/flame/FlameResourcePages.cpp @@ -48,13 +48,7 @@ FlameModPage::FlameModPage(ModDownloadDialog* dialog, BaseInstance& instance) m_model = new FlameModModel(instance); m_ui->packView->setModel(m_model); - // index is used to set the sorting with the flame api - m_ui->sortByBox->addItem(tr("Sort by Featured")); - m_ui->sortByBox->addItem(tr("Sort by Popularity")); - m_ui->sortByBox->addItem(tr("Sort by Last Updated")); - m_ui->sortByBox->addItem(tr("Sort by Name")); - m_ui->sortByBox->addItem(tr("Sort by Author")); - m_ui->sortByBox->addItem(tr("Sort by Downloads")); + addSortings(); // sometimes Qt just ignores virtual slots and doesn't work as intended it seems, // so it's best not to connect them in the parent's contructor... diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.cpp index 895e23fd..06b72fd0 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.cpp +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.cpp @@ -23,9 +23,6 @@ namespace ResourceDownload { -// NOLINTNEXTLINE(modernize-avoid-c-arrays) -const char* ModrinthModModel::sorts[5]{ "relevance", "downloads", "follows", "updated", "newest" }; - ModrinthModModel::ModrinthModModel(BaseInstance const& base) : ModModel(base, new ModrinthAPI) {} void ModrinthModModel::loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj) diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.h b/launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.h index 798a70e6..2511f5e5 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.h +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.h @@ -41,10 +41,6 @@ class ModrinthModModel : public ModModel { void loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr) override; auto documentToArray(QJsonDocument& obj) const -> QJsonArray override; - - // NOLINTNEXTLINE(modernize-avoid-c-arrays) - static const char* sorts[5]; - inline auto getSorts() const -> const char** override { return sorts; }; }; } // namespace ResourceDownload diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.cpp index 88621e05..45902d16 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.cpp +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.cpp @@ -50,12 +50,7 @@ ModrinthModPage::ModrinthModPage(ModDownloadDialog* dialog, BaseInstance& instan m_model = new ModrinthModModel(instance); m_ui->packView->setModel(m_model); - // index is used to set the sorting with the modrinth api - m_ui->sortByBox->addItem(tr("Sort by Relevance")); - m_ui->sortByBox->addItem(tr("Sort by Downloads")); - m_ui->sortByBox->addItem(tr("Sort by Follows")); - m_ui->sortByBox->addItem(tr("Sort by Last Updated")); - m_ui->sortByBox->addItem(tr("Sort by Newest")); + addSortings(); // sometimes Qt just ignores virtual slots and doesn't work as intended it seems, // so it's best not to connect them in the parent's constructor... -- cgit From 38e20eb1486928e10f4d3c128f3e9a6c697d872a Mon Sep 17 00:00:00 2001 From: flow Date: Tue, 20 Dec 2022 16:27:15 -0300 Subject: fix(RD): pass copy of IndexedPack to callbacks instead of ref. This prevents a crash in which the pack list gets updated in a search request meanwhile a versions / extra info request is being processed. Previously, this situation would cause the reference in the latter callbacks to be invalidated by an internal relocation of the pack list. Signed-off-by: flow --- launcher/modplatform/ResourceAPI.h | 8 ++--- launcher/ui/pages/modplatform/ModModel.cpp | 58 ++++++++++++++++-------------- 2 files changed, 36 insertions(+), 30 deletions(-) (limited to 'launcher/modplatform') diff --git a/launcher/modplatform/ResourceAPI.h b/launcher/modplatform/ResourceAPI.h index a2078b94..5f4e1832 100644 --- a/launcher/modplatform/ResourceAPI.h +++ b/launcher/modplatform/ResourceAPI.h @@ -81,7 +81,7 @@ class ResourceAPI { }; struct VersionSearchArgs { - ModPlatform::IndexedPack& pack; + ModPlatform::IndexedPack pack; std::optional > mcVersions; std::optional loaders; @@ -94,16 +94,16 @@ class ResourceAPI { } }; struct VersionSearchCallbacks { - std::function on_succeed; + std::function on_succeed; }; struct ProjectInfoArgs { - ModPlatform::IndexedPack& pack; + ModPlatform::IndexedPack pack; void operator=(ProjectInfoArgs other) { pack = other.pack; } }; struct ProjectInfoCallbacks { - std::function on_succeed; + std::function on_succeed; }; public: diff --git a/launcher/ui/pages/modplatform/ModModel.cpp b/launcher/ui/pages/modplatform/ModModel.cpp index 5eeac5d5..29cb2132 100644 --- a/launcher/ui/pages/modplatform/ModModel.cpp +++ b/launcher/ui/pages/modplatform/ModModel.cpp @@ -63,7 +63,7 @@ ResourceAPI::VersionSearchArgs ModModel::createVersionsArguments(QModelIndex& en } ResourceAPI::VersionSearchCallbacks ModModel::createVersionsCallbacks(QModelIndex& entry) { - return { [this, entry](auto& doc, auto& pack) { + return { [this, entry](auto& doc, auto pack) { if (!s_running_models.constFind(this).value()) return; versionRequestSucceeded(doc, pack, entry); @@ -77,7 +77,7 @@ ResourceAPI::ProjectInfoArgs ModModel::createInfoArguments(QModelIndex& entry) } ResourceAPI::ProjectInfoCallbacks ModModel::createInfoCallbacks(QModelIndex& entry) { - return { [this, entry](auto& doc, auto& pack) { + return { [this, entry](auto& doc, auto pack) { if (!s_running_models.constFind(this).value()) return; infoRequestFinished(doc, pack, entry); @@ -136,51 +136,57 @@ void ModModel::infoRequestFinished(QJsonDocument& doc, ModPlatform::IndexedPack& { qDebug() << "Loading mod info"; + auto current_pack = data(index, Qt::UserRole).value(); + + // Check if the index is still valid for this mod or not + if (pack.addonId != current_pack.addonId) + return; + try { auto obj = Json::requireObject(doc); - loadExtraPackInfo(pack, obj); + loadExtraPackInfo(current_pack, obj); } catch (const JSONValidationError& e) { qDebug() << doc; qWarning() << "Error while reading " << debugName() << " mod info: " << e.cause(); } - // Check if the index is still valid for this mod or not - if (pack.addonId == data(index, Qt::UserRole).value().addonId) { - // Cache info :^) - QVariant new_pack; - new_pack.setValue(pack); - if (!setData(index, new_pack, Qt::UserRole)) { - qWarning() << "Failed to cache mod info!"; - return; - } - - emit projectInfoUpdated(); + // Cache info :^) + QVariant new_pack; + new_pack.setValue(current_pack); + if (!setData(index, new_pack, Qt::UserRole)) { + qWarning() << "Failed to cache mod info!"; + return; } + + emit projectInfoUpdated(); } void ModModel::versionRequestSucceeded(QJsonDocument doc, ModPlatform::IndexedPack& pack, const QModelIndex& index) { auto arr = doc.isObject() ? Json::ensureArray(doc.object(), "data") : doc.array(); + auto current_pack = data(index, Qt::UserRole).value(); + + // Check if the index is still valid for this mod or not + if (pack.addonId != current_pack.addonId) + return; + try { - loadIndexedPackVersions(pack, arr); + loadIndexedPackVersions(current_pack, arr); } catch (const JSONValidationError& e) { qDebug() << doc; qWarning() << "Error while reading " << debugName() << " mod version: " << e.cause(); } - // Check if the index is still valid for this mod or not - if (pack.addonId == data(index, Qt::UserRole).value().addonId) { - // Cache info :^) - QVariant new_pack; - new_pack.setValue(pack); - if (!setData(index, new_pack, Qt::UserRole)) { - qWarning() << "Failed to cache mod versions!"; - return; - } - - emit versionListUpdated(); + // Cache info :^) + QVariant new_pack; + new_pack.setValue(current_pack); + if (!setData(index, new_pack, Qt::UserRole)) { + qWarning() << "Failed to cache mod versions!"; + return; } + + emit versionListUpdated(); } } // namespace ResourceDownload -- cgit From 7d128c79a3cceb5e88157ead72009642ee0e4a07 Mon Sep 17 00:00:00 2001 From: flow Date: Wed, 28 Dec 2022 15:19:20 -0300 Subject: fix: CodeQL warnings about the rule of two shush Signed-off-by: flow --- launcher/modplatform/ResourceAPI.h | 2 ++ 1 file changed, 2 insertions(+) (limited to 'launcher/modplatform') diff --git a/launcher/modplatform/ResourceAPI.h b/launcher/modplatform/ResourceAPI.h index 5f4e1832..8f794955 100644 --- a/launcher/modplatform/ResourceAPI.h +++ b/launcher/modplatform/ResourceAPI.h @@ -86,6 +86,7 @@ class ResourceAPI { std::optional > mcVersions; std::optional loaders; + VersionSearchArgs(VersionSearchArgs const&) = default; void operator=(VersionSearchArgs other) { pack = other.pack; @@ -100,6 +101,7 @@ class ResourceAPI { struct ProjectInfoArgs { ModPlatform::IndexedPack pack; + ProjectInfoArgs(ProjectInfoArgs const&) = default; void operator=(ProjectInfoArgs other) { pack = other.pack; } }; struct ProjectInfoCallbacks { -- cgit From ba677a8cb76dd6cde4a08ff4b6f142f7be1bdb29 Mon Sep 17 00:00:00 2001 From: flow Date: Tue, 3 Jan 2023 13:58:27 -0300 Subject: refactor: change some ResourceAPI from NetJob to Task This makes it easier to create resource apis that aren't network-based. Signed-off-by: flow --- launcher/QObjectPtr.h | 4 +++ launcher/modplatform/EnsureMetadataTask.cpp | 28 +++++++++---------- launcher/modplatform/EnsureMetadataTask.h | 10 +++---- launcher/modplatform/ResourceAPI.h | 13 ++++----- launcher/modplatform/flame/FlameAPI.cpp | 6 ++--- launcher/modplatform/flame/FlameAPI.h | 6 ++--- .../flame/FlameInstanceCreationTask.cpp | 4 +-- .../modplatform/flame/FlameInstanceCreationTask.h | 2 +- .../modplatform/helpers/NetworkResourceAPI.cpp | 8 +++--- launcher/modplatform/helpers/NetworkResourceAPI.h | 8 +++--- launcher/modplatform/modrinth/ModrinthAPI.cpp | 31 ++++++++++++---------- launcher/modplatform/modrinth/ModrinthAPI.h | 10 +++---- .../modplatform/modrinth/ModrinthCheckUpdate.cpp | 2 +- .../modplatform/modrinth/ModrinthCheckUpdate.h | 2 +- launcher/ui/pages/instance/ManagedPackPage.h | 2 ++ launcher/ui/pages/modplatform/ResourceModel.cpp | 2 +- launcher/ui/pages/modplatform/ResourceModel.h | 4 +-- 17 files changed, 75 insertions(+), 67 deletions(-) (limited to 'launcher/modplatform') diff --git a/launcher/QObjectPtr.h b/launcher/QObjectPtr.h index b1ef1c8d..ec466096 100644 --- a/launcher/QObjectPtr.h +++ b/launcher/QObjectPtr.h @@ -28,6 +28,10 @@ class shared_qobject_ptr : public QSharedPointer { constexpr shared_qobject_ptr(const shared_qobject_ptr& other) : QSharedPointer(other) {} + template + constexpr shared_qobject_ptr(const QSharedPointer& other) : QSharedPointer(other) + {} + void reset() { QSharedPointer::reset(); } void reset(const shared_qobject_ptr& other) { diff --git a/launcher/modplatform/EnsureMetadataTask.cpp b/launcher/modplatform/EnsureMetadataTask.cpp index 9bf81338..fb451938 100644 --- a/launcher/modplatform/EnsureMetadataTask.cpp +++ b/launcher/modplatform/EnsureMetadataTask.cpp @@ -13,8 +13,6 @@ #include "modplatform/modrinth/ModrinthAPI.h" #include "modplatform/modrinth/ModrinthPackIndex.h" -#include "net/NetJob.h" - static ModPlatform::ProviderCapabilities ProviderCaps; static ModrinthAPI modrinth_api; @@ -107,7 +105,7 @@ void EnsureMetadataTask::executeTask() } } - NetJob::Ptr version_task; + Task::Ptr version_task; switch (m_provider) { case (ModPlatform::ResourceProvider::MODRINTH): @@ -127,7 +125,7 @@ void EnsureMetadataTask::executeTask() }; connect(version_task.get(), &Task::finished, this, [this, invalidade_leftover] { - NetJob::Ptr project_task; + Task::Ptr project_task; switch (m_provider) { case (ModPlatform::ResourceProvider::MODRINTH): @@ -149,7 +147,7 @@ void EnsureMetadataTask::executeTask() m_current_task = nullptr; }); - m_current_task = project_task.get(); + m_current_task = project_task; project_task->start(); }); @@ -164,7 +162,7 @@ void EnsureMetadataTask::executeTask() setStatus(tr("Requesting metadata information from %1 for '%2'...") .arg(ProviderCaps.readableName(m_provider), m_mods.begin().value()->name())); - m_current_task = version_task.get(); + m_current_task = version_task; version_task->start(); } @@ -210,7 +208,7 @@ void EnsureMetadataTask::emitFail(Mod* m, QString key, RemoveFromList remove) // Modrinth -NetJob::Ptr EnsureMetadataTask::modrinthVersionsTask() +Task::Ptr EnsureMetadataTask::modrinthVersionsTask() { auto hash_type = ProviderCaps.hashType(ModPlatform::ResourceProvider::MODRINTH).first(); @@ -221,7 +219,7 @@ NetJob::Ptr EnsureMetadataTask::modrinthVersionsTask() if (!ver_task) return {}; - connect(ver_task.get(), &NetJob::succeeded, this, [this, response] { + connect(ver_task.get(), &Task::succeeded, this, [this, response] { QJsonParseError parse_error{}; QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error); if (parse_error.error != QJsonParseError::NoError) { @@ -260,14 +258,14 @@ NetJob::Ptr EnsureMetadataTask::modrinthVersionsTask() return ver_task; } -NetJob::Ptr EnsureMetadataTask::modrinthProjectsTask() +Task::Ptr EnsureMetadataTask::modrinthProjectsTask() { QHash addonIds; for (auto const& data : m_temp_versions) addonIds.insert(data.addonId.toString(), data.hash); auto response = new QByteArray(); - NetJob::Ptr proj_task; + Task::Ptr proj_task; if (addonIds.isEmpty()) { qWarning() << "No addonId found!"; @@ -281,7 +279,7 @@ NetJob::Ptr EnsureMetadataTask::modrinthProjectsTask() if (!proj_task) return {}; - connect(proj_task.get(), &NetJob::succeeded, this, [this, response, addonIds] { + connect(proj_task.get(), &Task::succeeded, this, [this, response, addonIds] { QJsonParseError parse_error{}; auto doc = QJsonDocument::fromJson(*response, &parse_error); if (parse_error.error != QJsonParseError::NoError) { @@ -335,7 +333,7 @@ NetJob::Ptr EnsureMetadataTask::modrinthProjectsTask() } // Flame -NetJob::Ptr EnsureMetadataTask::flameVersionsTask() +Task::Ptr EnsureMetadataTask::flameVersionsTask() { auto* response = new QByteArray(); @@ -400,7 +398,7 @@ NetJob::Ptr EnsureMetadataTask::flameVersionsTask() return ver_task; } -NetJob::Ptr EnsureMetadataTask::flameProjectsTask() +Task::Ptr EnsureMetadataTask::flameProjectsTask() { QHash addonIds; for (auto const& hash : m_mods.keys()) { @@ -414,7 +412,7 @@ NetJob::Ptr EnsureMetadataTask::flameProjectsTask() } auto response = new QByteArray(); - NetJob::Ptr proj_task; + Task::Ptr proj_task; if (addonIds.isEmpty()) { qWarning() << "No addonId found!"; @@ -428,7 +426,7 @@ NetJob::Ptr EnsureMetadataTask::flameProjectsTask() if (!proj_task) return {}; - connect(proj_task.get(), &NetJob::succeeded, this, [this, response, addonIds] { + connect(proj_task.get(), &Task::succeeded, this, [this, response, addonIds] { QJsonParseError parse_error{}; auto doc = QJsonDocument::fromJson(*response, &parse_error); if (parse_error.error != QJsonParseError::NoError) { diff --git a/launcher/modplatform/EnsureMetadataTask.h b/launcher/modplatform/EnsureMetadataTask.h index a79e5861..635f4a2b 100644 --- a/launcher/modplatform/EnsureMetadataTask.h +++ b/launcher/modplatform/EnsureMetadataTask.h @@ -28,11 +28,11 @@ class EnsureMetadataTask : public Task { private: // FIXME: Move to their own namespace - auto modrinthVersionsTask() -> NetJob::Ptr; - auto modrinthProjectsTask() -> NetJob::Ptr; + auto modrinthVersionsTask() -> Task::Ptr; + auto modrinthProjectsTask() -> Task::Ptr; - auto flameVersionsTask() -> NetJob::Ptr; - auto flameProjectsTask() -> NetJob::Ptr; + auto flameVersionsTask() -> Task::Ptr; + auto flameProjectsTask() -> Task::Ptr; // Helpers enum class RemoveFromList { @@ -61,5 +61,5 @@ class EnsureMetadataTask : public Task { QHash m_temp_versions; ConcurrentTask* m_hashing_task; - NetJob* m_current_task; + Task::Ptr m_current_task; }; diff --git a/launcher/modplatform/ResourceAPI.h b/launcher/modplatform/ResourceAPI.h index 8f794955..dfb3652c 100644 --- a/launcher/modplatform/ResourceAPI.h +++ b/launcher/modplatform/ResourceAPI.h @@ -35,6 +35,7 @@ #pragma once +#include #include #include @@ -44,7 +45,7 @@ #include "../Version.h" #include "modplatform/ModIndex.h" -#include "net/NetJob.h" +#include "tasks/Task.h" /* Simple class with a common interface for interacting with APIs */ class ResourceAPI { @@ -113,28 +114,28 @@ class ResourceAPI { [[nodiscard]] virtual auto getSortingMethods() const -> QList = 0; public slots: - [[nodiscard]] virtual NetJob::Ptr searchProjects(SearchArgs&&, SearchCallbacks&&) const + [[nodiscard]] virtual Task::Ptr searchProjects(SearchArgs&&, SearchCallbacks&&) const { qWarning() << "TODO"; return nullptr; } - [[nodiscard]] virtual NetJob::Ptr getProject(QString addonId, QByteArray* response) const + [[nodiscard]] virtual Task::Ptr getProject(QString addonId, QByteArray* response) const { qWarning() << "TODO"; return nullptr; } - [[nodiscard]] virtual NetJob::Ptr getProjects(QStringList addonIds, QByteArray* response) const + [[nodiscard]] virtual Task::Ptr getProjects(QStringList addonIds, QByteArray* response) const { qWarning() << "TODO"; return nullptr; } - [[nodiscard]] virtual NetJob::Ptr getProjectInfo(ProjectInfoArgs&&, ProjectInfoCallbacks&&) const + [[nodiscard]] virtual Task::Ptr getProjectInfo(ProjectInfoArgs&&, ProjectInfoCallbacks&&) const { qWarning() << "TODO"; return nullptr; } - [[nodiscard]] virtual NetJob::Ptr getProjectVersions(VersionSearchArgs&&, VersionSearchCallbacks&&) const + [[nodiscard]] virtual Task::Ptr getProjectVersions(VersionSearchArgs&&, VersionSearchCallbacks&&) const { qWarning() << "TODO"; return nullptr; diff --git a/launcher/modplatform/flame/FlameAPI.cpp b/launcher/modplatform/flame/FlameAPI.cpp index 32729a14..c8981585 100644 --- a/launcher/modplatform/flame/FlameAPI.cpp +++ b/launcher/modplatform/flame/FlameAPI.cpp @@ -7,7 +7,7 @@ #include "net/Upload.h" -auto FlameAPI::matchFingerprints(const QList& fingerprints, QByteArray* response) -> NetJob::Ptr +Task::Ptr FlameAPI::matchFingerprints(const QList& fingerprints, QByteArray* response) { auto* netJob = new NetJob(QString("Flame::MatchFingerprints"), APPLICATION->network()); @@ -167,7 +167,7 @@ auto FlameAPI::getLatestVersion(VersionSearchArgs&& args) -> ModPlatform::Indexe return ver; } -NetJob::Ptr FlameAPI::getProjects(QStringList addonIds, QByteArray* response) const +Task::Ptr FlameAPI::getProjects(QStringList addonIds, QByteArray* response) const { auto* netJob = new NetJob(QString("Flame::GetProjects"), APPLICATION->network()); @@ -190,7 +190,7 @@ NetJob::Ptr FlameAPI::getProjects(QStringList addonIds, QByteArray* response) co return netJob; } -NetJob::Ptr FlameAPI::getFiles(const QStringList& fileIds, QByteArray* response) const +Task::Ptr FlameAPI::getFiles(const QStringList& fileIds, QByteArray* response) const { auto* netJob = new NetJob(QString("Flame::GetFiles"), APPLICATION->network()); diff --git a/launcher/modplatform/flame/FlameAPI.h b/launcher/modplatform/flame/FlameAPI.h index 2b288564..8e7ed727 100644 --- a/launcher/modplatform/flame/FlameAPI.h +++ b/launcher/modplatform/flame/FlameAPI.h @@ -10,9 +10,9 @@ class FlameAPI : public NetworkResourceAPI { auto getLatestVersion(VersionSearchArgs&& args) -> ModPlatform::IndexedVersion; - NetJob::Ptr getProjects(QStringList addonIds, QByteArray* response) const override; - NetJob::Ptr matchFingerprints(const QList& fingerprints, QByteArray* response); - NetJob::Ptr getFiles(const QStringList& fileIds, QByteArray* response) const; + Task::Ptr getProjects(QStringList addonIds, QByteArray* response) const override; + Task::Ptr matchFingerprints(const QList& fingerprints, QByteArray* response); + Task::Ptr getFiles(const QStringList& fileIds, QByteArray* response) const; [[nodiscard]] auto getSortingMethods() const -> QList override; diff --git a/launcher/modplatform/flame/FlameInstanceCreationTask.cpp b/launcher/modplatform/flame/FlameInstanceCreationTask.cpp index fb6f78e8..890bff48 100644 --- a/launcher/modplatform/flame/FlameInstanceCreationTask.cpp +++ b/launcher/modplatform/flame/FlameInstanceCreationTask.cpp @@ -183,7 +183,7 @@ bool FlameCreationTask::updateInstance() QEventLoop loop; - connect(job.get(), &NetJob::succeeded, this, [this, raw_response, fileIds, old_inst_dir, &old_files, old_minecraft_dir] { + connect(job.get(), &Task::succeeded, this, [this, raw_response, fileIds, old_inst_dir, &old_files, old_minecraft_dir] { // Parse the API response QJsonParseError parse_error{}; auto doc = QJsonDocument::fromJson(*raw_response, &parse_error); @@ -225,7 +225,7 @@ bool FlameCreationTask::updateInstance() m_files_to_remove.append(old_minecraft_dir.absoluteFilePath(relative_path)); } }); - connect(job.get(), &NetJob::finished, &loop, &QEventLoop::quit); + connect(job.get(), &Task::finished, &loop, &QEventLoop::quit); m_process_update_file_info_job = job; job->start(); diff --git a/launcher/modplatform/flame/FlameInstanceCreationTask.h b/launcher/modplatform/flame/FlameInstanceCreationTask.h index 36b62e3e..0ae4735b 100644 --- a/launcher/modplatform/flame/FlameInstanceCreationTask.h +++ b/launcher/modplatform/flame/FlameInstanceCreationTask.h @@ -86,7 +86,7 @@ class FlameCreationTask final : public InstanceCreationTask { Flame::Manifest m_pack; // Handle to allow aborting - NetJob::Ptr m_process_update_file_info_job = nullptr; + Task::Ptr m_process_update_file_info_job = nullptr; NetJob::Ptr m_files_job = nullptr; QString m_managed_id, m_managed_version_id; diff --git a/launcher/modplatform/helpers/NetworkResourceAPI.cpp b/launcher/modplatform/helpers/NetworkResourceAPI.cpp index 77b085c0..88bbc045 100644 --- a/launcher/modplatform/helpers/NetworkResourceAPI.cpp +++ b/launcher/modplatform/helpers/NetworkResourceAPI.cpp @@ -5,7 +5,7 @@ #include "modplatform/ModIndex.h" -NetJob::Ptr NetworkResourceAPI::searchProjects(SearchArgs&& args, SearchCallbacks&& callbacks) const +Task::Ptr NetworkResourceAPI::searchProjects(SearchArgs&& args, SearchCallbacks&& callbacks) const { auto search_url_optional = getSearchURL(args); if (!search_url_optional.has_value()) { @@ -50,7 +50,7 @@ NetJob::Ptr NetworkResourceAPI::searchProjects(SearchArgs&& args, SearchCallback return netJob; } -NetJob::Ptr NetworkResourceAPI::getProjectInfo(ProjectInfoArgs&& args, ProjectInfoCallbacks&& callbacks) const +Task::Ptr NetworkResourceAPI::getProjectInfo(ProjectInfoArgs&& args, ProjectInfoCallbacks&& callbacks) const { auto response = new QByteArray(); auto job = getProject(args.pack.addonId.toString(), response); @@ -71,7 +71,7 @@ NetJob::Ptr NetworkResourceAPI::getProjectInfo(ProjectInfoArgs&& args, ProjectIn return job; } -NetJob::Ptr NetworkResourceAPI::getProjectVersions(VersionSearchArgs&& args, VersionSearchCallbacks&& callbacks) const +Task::Ptr NetworkResourceAPI::getProjectVersions(VersionSearchArgs&& args, VersionSearchCallbacks&& callbacks) const { auto versions_url_optional = getVersionsURL(args); if (!versions_url_optional.has_value()) @@ -104,7 +104,7 @@ NetJob::Ptr NetworkResourceAPI::getProjectVersions(VersionSearchArgs&& args, Ver return netJob; } -NetJob::Ptr NetworkResourceAPI::getProject(QString addonId, QByteArray* response) const +Task::Ptr NetworkResourceAPI::getProject(QString addonId, QByteArray* response) const { auto project_url_optional = getInfoURL(addonId); if (!project_url_optional.has_value()) diff --git a/launcher/modplatform/helpers/NetworkResourceAPI.h b/launcher/modplatform/helpers/NetworkResourceAPI.h index 834f274a..ab5586fd 100644 --- a/launcher/modplatform/helpers/NetworkResourceAPI.h +++ b/launcher/modplatform/helpers/NetworkResourceAPI.h @@ -4,12 +4,12 @@ class NetworkResourceAPI : public ResourceAPI { public: - NetJob::Ptr searchProjects(SearchArgs&&, SearchCallbacks&&) const override; + Task::Ptr searchProjects(SearchArgs&&, SearchCallbacks&&) const override; - NetJob::Ptr getProject(QString addonId, QByteArray* response) const override; + Task::Ptr getProject(QString addonId, QByteArray* response) const override; - NetJob::Ptr getProjectInfo(ProjectInfoArgs&&, ProjectInfoCallbacks&&) const override; - NetJob::Ptr getProjectVersions(VersionSearchArgs&&, VersionSearchCallbacks&&) const override; + Task::Ptr getProjectInfo(ProjectInfoArgs&&, ProjectInfoCallbacks&&) const override; + Task::Ptr getProjectVersions(VersionSearchArgs&&, VersionSearchCallbacks&&) const override; protected: [[nodiscard]] virtual auto getSearchURL(SearchArgs const& args) const -> std::optional = 0; diff --git a/launcher/modplatform/modrinth/ModrinthAPI.cpp b/launcher/modplatform/modrinth/ModrinthAPI.cpp index 8d7e3acf..028480a9 100644 --- a/launcher/modplatform/modrinth/ModrinthAPI.cpp +++ b/launcher/modplatform/modrinth/ModrinthAPI.cpp @@ -4,7 +4,7 @@ #include "Json.h" #include "net/Upload.h" -auto ModrinthAPI::currentVersion(QString hash, QString hash_format, QByteArray* response) -> NetJob::Ptr +Task::Ptr ModrinthAPI::currentVersion(QString hash, QString hash_format, QByteArray* response) { auto* netJob = new NetJob(QString("Modrinth::GetCurrentVersion"), APPLICATION->network()); @@ -16,7 +16,7 @@ auto ModrinthAPI::currentVersion(QString hash, QString hash_format, QByteArray* return netJob; } -auto ModrinthAPI::currentVersions(const QStringList& hashes, QString hash_format, QByteArray* response) -> NetJob::Ptr +Task::Ptr ModrinthAPI::currentVersions(const QStringList& hashes, QString hash_format, QByteArray* response) { auto* netJob = new NetJob(QString("Modrinth::GetCurrentVersions"), APPLICATION->network()); @@ -35,11 +35,11 @@ auto ModrinthAPI::currentVersions(const QStringList& hashes, QString hash_format return netJob; } -auto ModrinthAPI::latestVersion(QString hash, - QString hash_format, - std::optional> mcVersions, - std::optional loaders, - QByteArray* response) -> NetJob::Ptr +Task::Ptr ModrinthAPI::latestVersion(QString hash, + QString hash_format, + std::optional> mcVersions, + std::optional loaders, + QByteArray* response) { auto* netJob = new NetJob(QString("Modrinth::GetLatestVersion"), APPLICATION->network()); @@ -67,11 +67,11 @@ auto ModrinthAPI::latestVersion(QString hash, return netJob; } -auto ModrinthAPI::latestVersions(const QStringList& hashes, - QString hash_format, - std::optional> mcVersions, - std::optional loaders, - QByteArray* response) -> NetJob::Ptr +Task::Ptr ModrinthAPI::latestVersions(const QStringList& hashes, + QString hash_format, + std::optional> mcVersions, + std::optional loaders, + QByteArray* response) { auto* netJob = new NetJob(QString("Modrinth::GetLatestVersions"), APPLICATION->network()); @@ -101,14 +101,17 @@ auto ModrinthAPI::latestVersions(const QStringList& hashes, return netJob; } -NetJob::Ptr ModrinthAPI::getProjects(QStringList addonIds, QByteArray* response) const +Task::Ptr ModrinthAPI::getProjects(QStringList addonIds, QByteArray* response) const { auto netJob = new NetJob(QString("Modrinth::GetProjects"), APPLICATION->network()); auto searchUrl = getMultipleModInfoURL(addonIds); netJob->addNetAction(Net::Download::makeByteArray(QUrl(searchUrl), response)); - QObject::connect(netJob, &NetJob::finished, [response, netJob] { delete response; netJob->deleteLater(); }); + QObject::connect(netJob, &NetJob::finished, [response, netJob] { + delete response; + netJob->deleteLater(); + }); return netJob; } diff --git a/launcher/modplatform/modrinth/ModrinthAPI.h b/launcher/modplatform/modrinth/ModrinthAPI.h index 949fc46e..cba3afc8 100644 --- a/launcher/modplatform/modrinth/ModrinthAPI.h +++ b/launcher/modplatform/modrinth/ModrinthAPI.h @@ -28,25 +28,25 @@ class ModrinthAPI : public NetworkResourceAPI { public: auto currentVersion(QString hash, QString hash_format, - QByteArray* response) -> NetJob::Ptr; + QByteArray* response) -> Task::Ptr; auto currentVersions(const QStringList& hashes, QString hash_format, - QByteArray* response) -> NetJob::Ptr; + QByteArray* response) -> Task::Ptr; auto latestVersion(QString hash, QString hash_format, std::optional> mcVersions, std::optional loaders, - QByteArray* response) -> NetJob::Ptr; + QByteArray* response) -> Task::Ptr; auto latestVersions(const QStringList& hashes, QString hash_format, std::optional> mcVersions, std::optional loaders, - QByteArray* response) -> NetJob::Ptr; + QByteArray* response) -> Task::Ptr; - NetJob::Ptr getProjects(QStringList addonIds, QByteArray* response) const override; + Task::Ptr getProjects(QStringList addonIds, QByteArray* response) const override; public: [[nodiscard]] auto getSortingMethods() const -> QList override; diff --git a/launcher/modplatform/modrinth/ModrinthCheckUpdate.cpp b/launcher/modplatform/modrinth/ModrinthCheckUpdate.cpp index 7826b33d..daca68d7 100644 --- a/launcher/modplatform/modrinth/ModrinthCheckUpdate.cpp +++ b/launcher/modplatform/modrinth/ModrinthCheckUpdate.cpp @@ -175,7 +175,7 @@ void ModrinthCheckUpdate::executeTask() setStatus(tr("Waiting for the API response from Modrinth...")); setProgress(1, 3); - m_net_job = job.get(); + m_net_job = qSharedPointerObjectCast(job); job->start(); lock.exec(); diff --git a/launcher/modplatform/modrinth/ModrinthCheckUpdate.h b/launcher/modplatform/modrinth/ModrinthCheckUpdate.h index 177ce516..88e1a675 100644 --- a/launcher/modplatform/modrinth/ModrinthCheckUpdate.h +++ b/launcher/modplatform/modrinth/ModrinthCheckUpdate.h @@ -19,5 +19,5 @@ class ModrinthCheckUpdate : public CheckUpdateTask { void executeTask() override; private: - NetJob* m_net_job = nullptr; + NetJob::Ptr m_net_job = nullptr; }; diff --git a/launcher/ui/pages/instance/ManagedPackPage.h b/launcher/ui/pages/instance/ManagedPackPage.h index d29a5e88..55782ba7 100644 --- a/launcher/ui/pages/instance/ManagedPackPage.h +++ b/launcher/ui/pages/instance/ManagedPackPage.h @@ -12,6 +12,8 @@ #include "modplatform/flame/FlameAPI.h" #include "modplatform/flame/FlamePackIndex.h" +#include "net/NetJob.h" + #include "ui/pages/BasePage.h" #include diff --git a/launcher/ui/pages/modplatform/ResourceModel.cpp b/launcher/ui/pages/modplatform/ResourceModel.cpp index 202aa29a..be5ead90 100644 --- a/launcher/ui/pages/modplatform/ResourceModel.cpp +++ b/launcher/ui/pages/modplatform/ResourceModel.cpp @@ -225,7 +225,7 @@ void ResourceModel::clearData() endResetModel(); } -void ResourceModel::runSearchJob(NetJob::Ptr ptr) +void ResourceModel::runSearchJob(Task::Ptr ptr) { m_current_search_job = ptr; m_current_search_job->start(); diff --git a/launcher/ui/pages/modplatform/ResourceModel.h b/launcher/ui/pages/modplatform/ResourceModel.h index 02014fd6..7e813373 100644 --- a/launcher/ui/pages/modplatform/ResourceModel.h +++ b/launcher/ui/pages/modplatform/ResourceModel.h @@ -80,7 +80,7 @@ class ResourceModel : public QAbstractListModel { /** Resets the model's data. */ void clearData(); - void runSearchJob(NetJob::Ptr); + void runSearchJob(Task::Ptr); void runInfoJob(Task::Ptr); [[nodiscard]] auto getCurrentSortingMethodByIndex() const -> std::optional; @@ -111,7 +111,7 @@ class ResourceModel : public QAbstractListModel { std::unique_ptr m_api; // Job for searching for new entries - shared_qobject_ptr m_current_search_job; + shared_qobject_ptr m_current_search_job; // Job for fetching versions and extra info on existing entries ConcurrentTask m_current_info_job; -- cgit From c294c2d1df57c3d599fdea65bab9bb97b1fd699f Mon Sep 17 00:00:00 2001 From: flow Date: Sun, 8 Jan 2023 12:33:10 -0300 Subject: refactor(RD): allow setting custom folder target for downloaded resources Signed-off-by: flow --- launcher/ResourceDownloadTask.cpp | 12 +++++++++++- launcher/ResourceDownloadTask.h | 3 +-- launcher/modplatform/ModIndex.h | 1 + launcher/ui/dialogs/ResourceDownloadDialog.cpp | 3 ++- launcher/ui/dialogs/ReviewMessageBox.cpp | 16 ++++++++++++++++ launcher/ui/dialogs/ReviewMessageBox.h | 3 ++- 6 files changed, 33 insertions(+), 5 deletions(-) (limited to 'launcher/modplatform') diff --git a/launcher/ResourceDownloadTask.cpp b/launcher/ResourceDownloadTask.cpp index 687eaf51..8c9dae6f 100644 --- a/launcher/ResourceDownloadTask.cpp +++ b/launcher/ResourceDownloadTask.cpp @@ -40,7 +40,17 @@ ResourceDownloadTask::ResourceDownloadTask(ModPlatform::IndexedPack pack, m_filesNetJob.reset(new NetJob(tr("Resource download"), APPLICATION->network())); m_filesNetJob->setStatus(tr("Downloading resource:\n%1").arg(m_pack_version.downloadUrl)); - m_filesNetJob->addNetAction(Net::Download::makeFile(m_pack_version.downloadUrl, m_pack_model->dir().absoluteFilePath(getFilename()))); + QDir dir { m_pack_model->dir() }; + { + // FIXME: Make this more generic. May require adding additional info to IndexedVersion, + // or adquiring a reference to the base instance. + if (!m_pack_version.custom_target_folder.isEmpty()) { + dir.cdUp(); + dir.cd(m_pack_version.custom_target_folder); + } + } + + m_filesNetJob->addNetAction(Net::Download::makeFile(m_pack_version.downloadUrl, dir.absoluteFilePath(getFilename()))); connect(m_filesNetJob.get(), &NetJob::succeeded, this, &ResourceDownloadTask::downloadSucceeded); connect(m_filesNetJob.get(), &NetJob::progress, this, &ResourceDownloadTask::downloadProgressChanged); connect(m_filesNetJob.get(), &NetJob::failed, this, &ResourceDownloadTask::downloadFailed); diff --git a/launcher/ResourceDownloadTask.h b/launcher/ResourceDownloadTask.h index 275ddbe1..5ce39d69 100644 --- a/launcher/ResourceDownloadTask.h +++ b/launcher/ResourceDownloadTask.h @@ -32,6 +32,7 @@ class ResourceDownloadTask : public SequentialTask { public: explicit ResourceDownloadTask(ModPlatform::IndexedPack pack, ModPlatform::IndexedVersion version, const std::shared_ptr packs, bool is_indexed = true); const QString& getFilename() const { return m_pack_version.fileName; } + const QString& getCustomPath() const { return m_pack_version.custom_target_folder; } const QVariant& getVersionID() const { return m_pack_version.fileId; } private: @@ -43,9 +44,7 @@ private: LocalModUpdateTask::Ptr m_update_task; void downloadProgressChanged(qint64 current, qint64 total); - void downloadFailed(QString reason); - void downloadSucceeded(); std::tuple to_delete {"", ""}; diff --git a/launcher/modplatform/ModIndex.h b/launcher/modplatform/ModIndex.h index cd40a6ba..b1f8050d 100644 --- a/launcher/modplatform/ModIndex.h +++ b/launcher/modplatform/ModIndex.h @@ -68,6 +68,7 @@ struct IndexedVersion { // For internal use, not provided by APIs bool is_currently_selected = false; + QString custom_target_folder; }; struct ExtraPackData { diff --git a/launcher/ui/dialogs/ResourceDownloadDialog.cpp b/launcher/ui/dialogs/ResourceDownloadDialog.cpp index 147373c9..b9367c16 100644 --- a/launcher/ui/dialogs/ResourceDownloadDialog.cpp +++ b/launcher/ui/dialogs/ResourceDownloadDialog.cpp @@ -118,7 +118,8 @@ void ResourceDownloadDialog::confirm() confirm_dialog->retranslateUi(resourcesString()); for (auto& task : keys) { - confirm_dialog->appendResource({ task, m_selected.find(task).value()->getFilename() }); + auto selected = m_selected.constFind(task).value(); + confirm_dialog->appendResource({ task, selected->getFilename(), selected->getCustomPath() }); } if (confirm_dialog->exec()) { diff --git a/launcher/ui/dialogs/ReviewMessageBox.cpp b/launcher/ui/dialogs/ReviewMessageBox.cpp index 9c638d1f..7b2df278 100644 --- a/launcher/ui/dialogs/ReviewMessageBox.cpp +++ b/launcher/ui/dialogs/ReviewMessageBox.cpp @@ -1,6 +1,8 @@ #include "ReviewMessageBox.h" #include "ui_ReviewMessageBox.h" +#include "Application.h" + #include ReviewMessageBox::ReviewMessageBox(QWidget* parent, QString const& title, QString const& icon) @@ -11,6 +13,10 @@ ReviewMessageBox::ReviewMessageBox(QWidget* parent, QString const& title, QStrin auto back_button = ui->buttonBox->button(QDialogButtonBox::Cancel); back_button->setText(tr("Back")); + ui->modTreeWidget->header()->setSectionResizeMode(0, QHeaderView::Stretch); + ui->modTreeWidget->header()->setStretchLastSection(false); + ui->modTreeWidget->header()->setSectionResizeMode(1, QHeaderView::ResizeToContents); + connect(ui->buttonBox, &QDialogButtonBox::accepted, this, &ReviewMessageBox::accept); connect(ui->buttonBox, &QDialogButtonBox::rejected, this, &ReviewMessageBox::reject); } @@ -36,6 +42,16 @@ void ReviewMessageBox::appendResource(ResourceInformation&& info) itemTop->insertChildren(0, { filenameItem }); + if (!info.custom_file_path.isEmpty()) { + auto customPathItem = new QTreeWidgetItem(itemTop); + customPathItem->setText(0, tr("This download will be placed in: %1").arg(info.custom_file_path)); + + itemTop->insertChildren(1, { customPathItem }); + + itemTop->setIcon(1, QIcon(APPLICATION->getThemedIcon("status-yellow"))); + itemTop->setToolTip(1, tr("This file will be downloaded to a folder location different from the default, possibly due to its loader requiring it.")); + } + ui->modTreeWidget->addTopLevelItem(itemTop); } diff --git a/launcher/ui/dialogs/ReviewMessageBox.h b/launcher/ui/dialogs/ReviewMessageBox.h index 7ee0d65d..5ec2bc23 100644 --- a/launcher/ui/dialogs/ReviewMessageBox.h +++ b/launcher/ui/dialogs/ReviewMessageBox.h @@ -12,9 +12,10 @@ class ReviewMessageBox : public QDialog { public: static auto create(QWidget* parent, QString&& title, QString&& icon = "") -> ReviewMessageBox*; - using ResourceInformation = struct { + using ResourceInformation = struct res_info { QString name; QString filename; + QString custom_file_path {}; }; void appendResource(ResourceInformation&& info); -- cgit From 9407596b12df8cc45ddc53d3c08e495a2674199c Mon Sep 17 00:00:00 2001 From: flow Date: Fri, 13 Jan 2023 16:49:21 -0300 Subject: fix(ModUpdater): fail mods individually when there's errors in the JSON Prevents a single problematic mod from invalidating all the API response. Signed-off-by: flow --- launcher/modplatform/EnsureMetadataTask.cpp | 52 +++++++++++++++++------------ 1 file changed, 31 insertions(+), 21 deletions(-) (limited to 'launcher/modplatform') diff --git a/launcher/modplatform/EnsureMetadataTask.cpp b/launcher/modplatform/EnsureMetadataTask.cpp index fb451938..d9523052 100644 --- a/launcher/modplatform/EnsureMetadataTask.cpp +++ b/launcher/modplatform/EnsureMetadataTask.cpp @@ -289,43 +289,53 @@ Task::Ptr EnsureMetadataTask::modrinthProjectsTask() return; } + QJsonArray entries; + try { - QJsonArray entries; if (addonIds.size() == 1) entries = { doc.object() }; else entries = Json::requireArray(doc); + } catch (Json::JsonException& e) { + qDebug() << e.cause(); + qDebug() << doc; + } - for (auto entry : entries) { + for (auto entry : entries) { + ModPlatform::IndexedPack pack; + + try { auto entry_obj = Json::requireObject(entry); - ModPlatform::IndexedPack pack; Modrinth::loadIndexedPack(pack, entry_obj); + } catch (Json::JsonException& e) { + qDebug() << e.cause(); + qDebug() << doc; - auto hash = addonIds.find(pack.addonId.toString()).value(); + // Skip this entry, since it has problems + continue; + } - auto mod_iter = m_mods.find(hash); - if (mod_iter == m_mods.end()) { - qWarning() << "Invalid project id from the API response."; - continue; - } + auto hash = addonIds.find(pack.addonId.toString()).value(); - auto* mod = mod_iter.value(); + auto mod_iter = m_mods.find(hash); + if (mod_iter == m_mods.end()) { + qWarning() << "Invalid project id from the API response."; + continue; + } - try { - setStatus(tr("Parsing API response from Modrinth for '%1'...").arg(mod->name())); + auto* mod = mod_iter.value(); - modrinthCallback(pack, m_temp_versions.find(hash).value(), mod); - } catch (Json::JsonException& e) { - qDebug() << e.cause(); - qDebug() << entries; + try { + setStatus(tr("Parsing API response from Modrinth for '%1'...").arg(mod->name())); - emitFail(mod); - } + modrinthCallback(pack, m_temp_versions.find(hash).value(), mod); + } catch (Json::JsonException& e) { + qDebug() << e.cause(); + qDebug() << entries; + + emitFail(mod); } - } catch (Json::JsonException& e) { - qDebug() << e.cause(); - qDebug() << doc; } }); -- cgit From c95c81d42f63a2807889740b89be924fd0b59083 Mon Sep 17 00:00:00 2001 From: flow Date: Fri, 13 Jan 2023 16:59:37 -0300 Subject: fix(ModUpdater): ensure instead of require icon_url The spec says that this can be null, and indeed some mods have it set to null, and should still be considered as valid. Signed-off-by: flow --- launcher/modplatform/modrinth/ModrinthPackIndex.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'launcher/modplatform') diff --git a/launcher/modplatform/modrinth/ModrinthPackIndex.cpp b/launcher/modplatform/modrinth/ModrinthPackIndex.cpp index f270f470..7ade131e 100644 --- a/launcher/modplatform/modrinth/ModrinthPackIndex.cpp +++ b/launcher/modplatform/modrinth/ModrinthPackIndex.cpp @@ -27,6 +27,7 @@ static ModrinthAPI api; static ModPlatform::ProviderCapabilities ProviderCaps; +// https://docs.modrinth.com/api-spec/#tag/projects/operation/getProject void Modrinth::loadIndexedPack(ModPlatform::IndexedPack& pack, QJsonObject& obj) { pack.addonId = Json::ensureString(obj, "project_id"); @@ -44,7 +45,7 @@ void Modrinth::loadIndexedPack(ModPlatform::IndexedPack& pack, QJsonObject& obj) pack.description = Json::ensureString(obj, "description", ""); - pack.logoUrl = Json::requireString(obj, "icon_url"); + pack.logoUrl = Json::ensureString(obj, "icon_url", ""); pack.logoName = pack.addonId.toString(); ModPlatform::ModpackAuthor modAuthor; -- cgit From ea5020e188d7cb6d4c8dcf7f953161759ed17899 Mon Sep 17 00:00:00 2001 From: flow Date: Mon, 23 Jan 2023 11:03:55 -0300 Subject: fix(license): add/fix my copyright/license headers *sobbing in messy legal stuff i know nothing about* Signed-off-by: flow --- launcher/ResourceDownloadTask.cpp | 4 +-- launcher/ResourceDownloadTask.h | 4 +-- launcher/modplatform/ResourceAPI.h | 4 ++- launcher/modplatform/flame/FlameAPI.cpp | 4 +++ launcher/modplatform/flame/FlameAPI.h | 4 +++ .../modplatform/helpers/NetworkResourceAPI.cpp | 4 +++ launcher/modplatform/helpers/NetworkResourceAPI.h | 4 +++ launcher/modplatform/modrinth/ModrinthAPI.cpp | 4 +++ launcher/modplatform/modrinth/ModrinthAPI.h | 18 ++-------- launcher/ui/pages/instance/ManagedPackPage.cpp | 2 +- launcher/ui/pages/instance/ManagedPackPage.h | 2 +- launcher/ui/pages/modplatform/ModModel.cpp | 4 +++ launcher/ui/pages/modplatform/ModModel.h | 4 +++ launcher/ui/pages/modplatform/ModPage.cpp | 4 ++- launcher/ui/pages/modplatform/ModPage.h | 4 +++ launcher/ui/pages/modplatform/ResourceModel.cpp | 4 +++ launcher/ui/pages/modplatform/ResourceModel.h | 4 +++ launcher/ui/pages/modplatform/ResourcePage.cpp | 38 ++++++++++++++++++++++ launcher/ui/pages/modplatform/ResourcePage.h | 4 +++ .../modplatform/flame/FlameResourceModels.cpp | 4 +++ .../pages/modplatform/flame/FlameResourceModels.h | 4 +++ .../pages/modplatform/flame/FlameResourcePages.cpp | 4 ++- .../pages/modplatform/flame/FlameResourcePages.h | 4 ++- .../modrinth/ModrinthResourceModels.cpp | 2 ++ .../modplatform/modrinth/ModrinthResourceModels.h | 2 ++ .../modplatform/modrinth/ModrinthResourcePages.cpp | 2 ++ .../modplatform/modrinth/ModrinthResourcePages.h | 4 ++- 27 files changed, 119 insertions(+), 27 deletions(-) (limited to 'launcher/modplatform') diff --git a/launcher/ResourceDownloadTask.cpp b/launcher/ResourceDownloadTask.cpp index 8c9dae6f..98bcf259 100644 --- a/launcher/ResourceDownloadTask.cpp +++ b/launcher/ResourceDownloadTask.cpp @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-3.0-only /* -* PolyMC - Minecraft Launcher -* Copyright (c) 2022 flowln +* Prism Launcher - Minecraft Launcher +* Copyright (c) 2022-2023 flowln * Copyright (C) 2022 Sefa Eyeoglu * * This program is free software: you can redistribute it and/or modify diff --git a/launcher/ResourceDownloadTask.h b/launcher/ResourceDownloadTask.h index 5ce39d69..73ad2d07 100644 --- a/launcher/ResourceDownloadTask.h +++ b/launcher/ResourceDownloadTask.h @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-3.0-only /* -* PolyMC - Minecraft Launcher -* Copyright (c) 2022 flowln +* Prism Launcher - Minecraft Launcher +* Copyright (c) 2022-2023 flowln * Copyright (C) 2022 Sefa Eyeoglu * * This program is free software: you can redistribute it and/or modify diff --git a/launcher/modplatform/ResourceAPI.h b/launcher/modplatform/ResourceAPI.h index dfb3652c..34f33779 100644 --- a/launcher/modplatform/ResourceAPI.h +++ b/launcher/modplatform/ResourceAPI.h @@ -1,4 +1,6 @@ -// SPDX-License-Identifier: GPL-3.0-only +// SPDX-FileCopyrightText: 2023 flowln +// +// SPDX-License-Identifier: GPL-3.0-only AND Apache-2.0 /* * PolyMC - Minecraft Launcher * Copyright (C) 2022 Sefa Eyeoglu diff --git a/launcher/modplatform/flame/FlameAPI.cpp b/launcher/modplatform/flame/FlameAPI.cpp index c8981585..57f70047 100644 --- a/launcher/modplatform/flame/FlameAPI.cpp +++ b/launcher/modplatform/flame/FlameAPI.cpp @@ -1,3 +1,7 @@ +// SPDX-FileCopyrightText: 2023 flowln +// +// SPDX-License-Identifier: GPL-3.0-only + #include "FlameAPI.h" #include "FlameModIndex.h" diff --git a/launcher/modplatform/flame/FlameAPI.h b/launcher/modplatform/flame/FlameAPI.h index 8e7ed727..06d749e6 100644 --- a/launcher/modplatform/flame/FlameAPI.h +++ b/launcher/modplatform/flame/FlameAPI.h @@ -1,3 +1,7 @@ +// SPDX-FileCopyrightText: 2023 flowln +// +// SPDX-License-Identifier: GPL-3.0-only + #pragma once #include "modplatform/ModIndex.h" diff --git a/launcher/modplatform/helpers/NetworkResourceAPI.cpp b/launcher/modplatform/helpers/NetworkResourceAPI.cpp index 88bbc045..ac994c31 100644 --- a/launcher/modplatform/helpers/NetworkResourceAPI.cpp +++ b/launcher/modplatform/helpers/NetworkResourceAPI.cpp @@ -1,3 +1,7 @@ +// SPDX-FileCopyrightText: 2023 flowln +// +// SPDX-License-Identifier: GPL-3.0-only + #include "NetworkResourceAPI.h" #include "Application.h" diff --git a/launcher/modplatform/helpers/NetworkResourceAPI.h b/launcher/modplatform/helpers/NetworkResourceAPI.h index ab5586fd..94813bec 100644 --- a/launcher/modplatform/helpers/NetworkResourceAPI.h +++ b/launcher/modplatform/helpers/NetworkResourceAPI.h @@ -1,3 +1,7 @@ +// SPDX-FileCopyrightText: 2023 flowln +// +// SPDX-License-Identifier: GPL-3.0-only + #pragma once #include "modplatform/ResourceAPI.h" diff --git a/launcher/modplatform/modrinth/ModrinthAPI.cpp b/launcher/modplatform/modrinth/ModrinthAPI.cpp index 028480a9..0c601d22 100644 --- a/launcher/modplatform/modrinth/ModrinthAPI.cpp +++ b/launcher/modplatform/modrinth/ModrinthAPI.cpp @@ -1,3 +1,7 @@ +// SPDX-FileCopyrightText: 2023 flowln +// +// SPDX-License-Identifier: GPL-3.0-only + #include "ModrinthAPI.h" #include "Application.h" diff --git a/launcher/modplatform/modrinth/ModrinthAPI.h b/launcher/modplatform/modrinth/ModrinthAPI.h index cba3afc8..dda27303 100644 --- a/launcher/modplatform/modrinth/ModrinthAPI.h +++ b/launcher/modplatform/modrinth/ModrinthAPI.h @@ -1,20 +1,6 @@ +// SPDX-FileCopyrightText: 2022-2023 flowln +// // SPDX-License-Identifier: GPL-3.0-only -/* - * PolyMC - Minecraft Launcher - * Copyright (c) 2022 flowln - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, version 3. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ #pragma once diff --git a/launcher/ui/pages/instance/ManagedPackPage.cpp b/launcher/ui/pages/instance/ManagedPackPage.cpp index 8d56d894..dc983d9a 100644 --- a/launcher/ui/pages/instance/ManagedPackPage.cpp +++ b/launcher/ui/pages/instance/ManagedPackPage.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2022 flow +// SPDX-FileCopyrightText: 2022 flowln // // SPDX-License-Identifier: GPL-3.0-only diff --git a/launcher/ui/pages/instance/ManagedPackPage.h b/launcher/ui/pages/instance/ManagedPackPage.h index 55782ba7..1ac6fc03 100644 --- a/launcher/ui/pages/instance/ManagedPackPage.h +++ b/launcher/ui/pages/instance/ManagedPackPage.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2022 flow +// SPDX-FileCopyrightText: 2022 flowln // // SPDX-License-Identifier: GPL-3.0-only diff --git a/launcher/ui/pages/modplatform/ModModel.cpp b/launcher/ui/pages/modplatform/ModModel.cpp index 433c7b10..3ffe6cb0 100644 --- a/launcher/ui/pages/modplatform/ModModel.cpp +++ b/launcher/ui/pages/modplatform/ModModel.cpp @@ -1,3 +1,7 @@ +// SPDX-FileCopyrightText: 2023 flowln +// +// SPDX-License-Identifier: GPL-3.0-only + #include "ModModel.h" #include "minecraft/MinecraftInstance.h" diff --git a/launcher/ui/pages/modplatform/ModModel.h b/launcher/ui/pages/modplatform/ModModel.h index 1fac9040..5d4a7785 100644 --- a/launcher/ui/pages/modplatform/ModModel.h +++ b/launcher/ui/pages/modplatform/ModModel.h @@ -1,3 +1,7 @@ +// SPDX-FileCopyrightText: 2023 flowln +// +// SPDX-License-Identifier: GPL-3.0-only + #pragma once #include diff --git a/launcher/ui/pages/modplatform/ModPage.cpp b/launcher/ui/pages/modplatform/ModPage.cpp index d57e748b..04be43ad 100644 --- a/launcher/ui/pages/modplatform/ModPage.cpp +++ b/launcher/ui/pages/modplatform/ModPage.cpp @@ -1,4 +1,6 @@ -// SPDX-License-Identifier: GPL-3.0-only +// SPDX-FileCopyrightText: 2023 flowln +// +// SPDX-License-Identifier: GPL-3.0-only AND Apache-2.0 /* * Prism Launcher - Minecraft Launcher * Copyright (C) 2022 Sefa Eyeoglu diff --git a/launcher/ui/pages/modplatform/ModPage.h b/launcher/ui/pages/modplatform/ModPage.h index a3aab1de..c3b58cd6 100644 --- a/launcher/ui/pages/modplatform/ModPage.h +++ b/launcher/ui/pages/modplatform/ModPage.h @@ -1,3 +1,7 @@ +// SPDX-FileCopyrightText: 2023 flowln +// +// SPDX-License-Identifier: GPL-3.0-only + #pragma once #include diff --git a/launcher/ui/pages/modplatform/ResourceModel.cpp b/launcher/ui/pages/modplatform/ResourceModel.cpp index eb723159..8af70104 100644 --- a/launcher/ui/pages/modplatform/ResourceModel.cpp +++ b/launcher/ui/pages/modplatform/ResourceModel.cpp @@ -1,3 +1,7 @@ +// SPDX-FileCopyrightText: 2023 flowln +// +// SPDX-License-Identifier: GPL-3.0-only + #include "ResourceModel.h" #include diff --git a/launcher/ui/pages/modplatform/ResourceModel.h b/launcher/ui/pages/modplatform/ResourceModel.h index 7e813373..610b631c 100644 --- a/launcher/ui/pages/modplatform/ResourceModel.h +++ b/launcher/ui/pages/modplatform/ResourceModel.h @@ -1,3 +1,7 @@ +// SPDX-FileCopyrightText: 2023 flowln +// +// SPDX-License-Identifier: GPL-3.0-only + #pragma once #include diff --git a/launcher/ui/pages/modplatform/ResourcePage.cpp b/launcher/ui/pages/modplatform/ResourcePage.cpp index bfa7e33d..bbd465bc 100644 --- a/launcher/ui/pages/modplatform/ResourcePage.cpp +++ b/launcher/ui/pages/modplatform/ResourcePage.cpp @@ -1,3 +1,41 @@ +// SPDX-FileCopyrightText: 2023 flowln +// +// SPDX-License-Identifier: GPL-3.0-only AND Apache-2.0 +/* + * Prism Launcher - Minecraft Launcher + * Copyright (C) 2022 Sefa Eyeoglu + * Copyright (C) 2022 TheKodeToad + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * This file incorporates work covered by the following copyright and + * permission notice: + * + * Copyright 2013-2021 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + #include "ResourcePage.h" #include "ui_ResourcePage.h" diff --git a/launcher/ui/pages/modplatform/ResourcePage.h b/launcher/ui/pages/modplatform/ResourcePage.h index 71fc6593..1896d53e 100644 --- a/launcher/ui/pages/modplatform/ResourcePage.h +++ b/launcher/ui/pages/modplatform/ResourcePage.h @@ -1,3 +1,7 @@ +// SPDX-FileCopyrightText: 2023 flowln +// +// SPDX-License-Identifier: GPL-3.0-only + #pragma once #include diff --git a/launcher/ui/pages/modplatform/flame/FlameResourceModels.cpp b/launcher/ui/pages/modplatform/flame/FlameResourceModels.cpp index a1cd1f26..de1f2122 100644 --- a/launcher/ui/pages/modplatform/flame/FlameResourceModels.cpp +++ b/launcher/ui/pages/modplatform/flame/FlameResourceModels.cpp @@ -1,3 +1,7 @@ +// SPDX-FileCopyrightText: 2023 flowln +// +// SPDX-License-Identifier: GPL-3.0-only + #include "FlameResourceModels.h" #include "Json.h" diff --git a/launcher/ui/pages/modplatform/flame/FlameResourceModels.h b/launcher/ui/pages/modplatform/flame/FlameResourceModels.h index 47fbbe1a..625a2a7d 100644 --- a/launcher/ui/pages/modplatform/flame/FlameResourceModels.h +++ b/launcher/ui/pages/modplatform/flame/FlameResourceModels.h @@ -1,3 +1,7 @@ +// SPDX-FileCopyrightText: 2023 flowln +// +// SPDX-License-Identifier: GPL-3.0-only + #pragma once #include "ui/pages/modplatform/ModModel.h" diff --git a/launcher/ui/pages/modplatform/flame/FlameResourcePages.cpp b/launcher/ui/pages/modplatform/flame/FlameResourcePages.cpp index e34be7fd..485431a7 100644 --- a/launcher/ui/pages/modplatform/flame/FlameResourcePages.cpp +++ b/launcher/ui/pages/modplatform/flame/FlameResourcePages.cpp @@ -1,4 +1,6 @@ -// SPDX-License-Identifier: GPL-3.0-only +// SPDX-FileCopyrightText: 2023 flowln +// +// SPDX-License-Identifier: GPL-3.0-only AND Apache-2.0 /* * Prism Launcher - Minecraft Launcher * Copyright (C) 2022 Sefa Eyeoglu diff --git a/launcher/ui/pages/modplatform/flame/FlameResourcePages.h b/launcher/ui/pages/modplatform/flame/FlameResourcePages.h index 12b51aa9..b21a53ad 100644 --- a/launcher/ui/pages/modplatform/flame/FlameResourcePages.h +++ b/launcher/ui/pages/modplatform/flame/FlameResourcePages.h @@ -1,4 +1,6 @@ -// SPDX-License-Identifier: GPL-3.0-only +// SPDX-FileCopyrightText: 2023 flowln +// +// SPDX-License-Identifier: GPL-3.0-only AND Apache-2.0 /* * Prism Launcher - Minecraft Launcher * Copyright (C) 2022 Sefa Eyeoglu diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.cpp index 06b72fd0..73d55133 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.cpp +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.cpp @@ -1,3 +1,5 @@ +// SPDX-FileCopyrightText: 2023 flowln +// // SPDX-License-Identifier: GPL-3.0-only /* * PolyMC - Minecraft Launcher diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.h b/launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.h index 2511f5e5..56cab146 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.h +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.h @@ -1,3 +1,5 @@ +// SPDX-FileCopyrightText: 2023 flowln +// // SPDX-License-Identifier: GPL-3.0-only /* * PolyMC - Minecraft Launcher diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.cpp index 45902d16..b82f800e 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.cpp +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.cpp @@ -1,3 +1,5 @@ +// SPDX-FileCopyrightText: 2023 flowln +// // SPDX-License-Identifier: GPL-3.0-only /* * PolyMC - Minecraft Launcher diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.h b/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.h index a263bd44..be38eff1 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.h +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.h @@ -1,4 +1,6 @@ -// SPDX-License-Identifier: GPL-3.0-only +// SPDX-FileCopyrightText: 2023 flowln +// +// SPDX-License-Identifier: GPL-3.0-only AND Apache-2.0 /* * PolyMC - Minecraft Launcher * Copyright (C) 2022 Sefa Eyeoglu -- cgit