aboutsummaryrefslogtreecommitdiff
path: root/launcher
diff options
context:
space:
mode:
Diffstat (limited to 'launcher')
-rw-r--r--launcher/CMakeLists.txt37
-rw-r--r--launcher/ModDownloadTask.cpp72
-rw-r--r--launcher/ResourceDownloadTask.cpp80
-rw-r--r--launcher/ResourceDownloadTask.h (renamed from launcher/ModDownloadTask.h)16
-rw-r--r--launcher/minecraft/PackProfile.cpp28
-rw-r--r--launcher/minecraft/PackProfile.h4
-rw-r--r--launcher/modplatform/CheckUpdateTask.h14
-rw-r--r--launcher/modplatform/EnsureMetadataTask.cpp14
-rw-r--r--launcher/modplatform/EnsureMetadataTask.h6
-rw-r--r--launcher/modplatform/ModAPI.h118
-rw-r--r--launcher/modplatform/ModIndex.cpp24
-rw-r--r--launcher/modplatform/ModIndex.h19
-rw-r--r--launcher/modplatform/ResourceAPI.h149
-rw-r--r--launcher/modplatform/flame/FlameAPI.cpp16
-rw-r--r--launcher/modplatform/flame/FlameAPI.h99
-rw-r--r--launcher/modplatform/flame/FlameCheckUpdate.cpp11
-rw-r--r--launcher/modplatform/flame/FlameCheckUpdate.h2
-rw-r--r--launcher/modplatform/flame/FlameInstanceCreationTask.cpp4
-rw-r--r--launcher/modplatform/flame/FlameInstanceCreationTask.h2
-rw-r--r--launcher/modplatform/flame/FlameModIndex.cpp4
-rw-r--r--launcher/modplatform/helpers/HashUtils.cpp16
-rw-r--r--launcher/modplatform/helpers/HashUtils.h10
-rw-r--r--launcher/modplatform/helpers/NetworkModAPI.cpp97
-rw-r--r--launcher/modplatform/helpers/NetworkModAPI.h17
-rw-r--r--launcher/modplatform/helpers/NetworkResourceAPI.cpp124
-rw-r--r--launcher/modplatform/helpers/NetworkResourceAPI.h18
-rw-r--r--launcher/modplatform/modrinth/ModrinthAPI.cpp36
-rw-r--r--launcher/modplatform/modrinth/ModrinthAPI.h106
-rw-r--r--launcher/modplatform/modrinth/ModrinthCheckUpdate.cpp25
-rw-r--r--launcher/modplatform/modrinth/ModrinthCheckUpdate.h2
-rw-r--r--launcher/modplatform/modrinth/ModrinthPackIndex.cpp4
-rw-r--r--launcher/modplatform/packwiz/Packwiz.cpp8
-rw-r--r--launcher/modplatform/packwiz/Packwiz.h2
-rw-r--r--launcher/net/NetAction.h4
-rw-r--r--launcher/net/NetJob.cpp5
-rw-r--r--launcher/ui/dialogs/BlockedModsDialog.cpp2
-rw-r--r--launcher/ui/dialogs/ChooseProviderDialog.cpp6
-rw-r--r--launcher/ui/dialogs/ChooseProviderDialog.h6
-rw-r--r--launcher/ui/dialogs/ModDownloadDialog.cpp165
-rw-r--r--launcher/ui/dialogs/ModDownloadDialog.h43
-rw-r--r--launcher/ui/dialogs/ModUpdateDialog.cpp44
-rw-r--r--launcher/ui/dialogs/ModUpdateDialog.h8
-rw-r--r--launcher/ui/dialogs/ResourceDownloadDialog.cpp152
-rw-r--r--launcher/ui/dialogs/ResourceDownloadDialog.h55
-rw-r--r--launcher/ui/dialogs/ReviewMessageBox.cpp4
-rw-r--r--launcher/ui/dialogs/ReviewMessageBox.h8
-rw-r--r--launcher/ui/pages/instance/ModFolderPage.cpp6
-rw-r--r--launcher/ui/pages/instance/ResourcePackPage.h1
-rw-r--r--launcher/ui/pages/modplatform/ModModel.cpp274
-rw-r--r--launcher/ui/pages/modplatform/ModModel.h64
-rw-r--r--launcher/ui/pages/modplatform/ModPage.cpp357
-rw-r--r--launcher/ui/pages/modplatform/ModPage.h78
-rw-r--r--launcher/ui/pages/modplatform/ResourceModel.cpp258
-rw-r--r--launcher/ui/pages/modplatform/ResourceModel.h101
-rw-r--r--launcher/ui/pages/modplatform/ResourcePage.cpp347
-rw-r--r--launcher/ui/pages/modplatform/ResourcePage.h95
-rw-r--r--launcher/ui/pages/modplatform/ResourcePage.ui (renamed from launcher/ui/pages/modplatform/ModPage.ui)12
-rw-r--r--launcher/ui/pages/modplatform/flame/FlameResourceModels.cpp (renamed from launcher/ui/pages/modplatform/flame/FlameModModel.cpp)4
-rw-r--r--launcher/ui/pages/modplatform/flame/FlameResourceModels.h (renamed from launcher/ui/pages/modplatform/flame/FlameModModel.h)4
-rw-r--r--launcher/ui/pages/modplatform/flame/FlameResourcePages.cpp (renamed from launcher/ui/pages/modplatform/flame/FlameModPage.cpp)38
-rw-r--r--launcher/ui/pages/modplatform/flame/FlameResourcePages.h (renamed from launcher/ui/pages/modplatform/flame/FlameModPage.h)13
-rw-r--r--launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.cpp (renamed from launcher/ui/pages/modplatform/modrinth/ModrinthModModel.cpp)9
-rw-r--r--launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.h (renamed from launcher/ui/pages/modplatform/modrinth/ModrinthModModel.h)9
-rw-r--r--launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.cpp (renamed from launcher/ui/pages/modplatform/modrinth/ModrinthModPage.cpp)55
-rw-r--r--launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.h (renamed from launcher/ui/pages/modplatform/modrinth/ModrinthModPage.h)32
-rw-r--r--launcher/ui/widgets/ProgressWidget.cpp6
-rw-r--r--launcher/ui/widgets/ProgressWidget.h6
67 files changed, 1950 insertions, 1505 deletions
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 <flowlnlnln@gmail.com>
-* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
-*
-* 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 <https://www.gnu.org/licenses/>.
-*/
-
-#include "ModDownloadTask.h"
-
-#include "Application.h"
-#include "minecraft/mod/ModFolderModel.h"
-
-ModDownloadTask::ModDownloadTask(ModPlatform::IndexedPack mod, ModPlatform::IndexedVersion version, const std::shared_ptr<ModFolderModel> 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/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 <flowlnlnln@gmail.com>
+* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
+*
+* 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 <https://www.gnu.org/licenses/>.
+*/
+
+#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<ResourceFolderModel> packs,
+ bool is_indexed)
+ : m_pack(std::move(pack)), m_pack_version(std::move(version)), m_pack_model(packs)
+{
+ if (auto model = dynamic_cast<ModFolderModel*>(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<ModFolderModel*>(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/ModDownloadTask.h b/launcher/ResourceDownloadTask.h
index 95020470..350c2edd 100644
--- a/launcher/ModDownloadTask.h
+++ b/launcher/ResourceDownloadTask.h
@@ -25,18 +25,18 @@
#include "modplatform/ModIndex.h"
#include "minecraft/mod/tasks/LocalModUpdateTask.h"
-class ModFolderModel;
+class ResourceFolderModel;
-class ModDownloadTask : public SequentialTask {
+class ResourceDownloadTask : public SequentialTask {
Q_OBJECT
public:
- explicit ModDownloadTask(ModPlatform::IndexedPack mod, ModPlatform::IndexedVersion version, const std::shared_ptr<ModFolderModel> mods, bool is_indexed = true);
- const QString& getFilename() const { return m_mod_version.fileName; }
+ explicit ResourceDownloadTask(ModPlatform::IndexedPack pack, ModPlatform::IndexedVersion version, const std::shared_ptr<ResourceFolderModel> packs, bool is_indexed = true);
+ const QString& getFilename() const { return m_pack_version.fileName; }
private:
- ModPlatform::IndexedPack m_mod;
- ModPlatform::IndexedVersion m_mod_version;
- const std::shared_ptr<ModFolderModel> mods;
+ ModPlatform::IndexedPack m_pack;
+ ModPlatform::IndexedVersion m_pack_version;
+ const std::shared_ptr<ResourceFolderModel> m_pack_model;
NetJob::Ptr m_filesNetJob;
LocalModUpdateTask::Ptr m_update_task;
@@ -50,7 +50,7 @@ private:
std::tuple<QString, QString> to_delete {"", ""};
private slots:
- void hasOldMod(QString name, QString filename);
+ 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<QString, ModAPI::ModLoaderType> modloaderMapping{
- {"net.minecraftforge", ModAPI::Forge},
- {"net.fabricmc.fabric-loader", ModAPI::Fabric},
- {"org.quiltmc.quilt-loader", ModAPI::Quilt}
+static const QMap<QString, ResourceAPI::ModLoaderType> 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<ResourceAPI::ModLoaderTypes> PackProfile::getModLoaders()
{
- ModAPI::ModLoaderTypes result = ModAPI::Unspecified;
+ ResourceAPI::ModLoaderTypes result;
+ bool has_any_loader = false;
- QMapIterator<QString, ModAPI::ModLoaderType> i(modloaderMapping);
+ QMapIterator<QString, ResourceAPI::ModLoaderType> 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<ResourceAPI::ModLoaderTypes> 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<Mod*>& mods, std::list<Version>& mcVersions, ModAPI::ModLoaderTypes loaders, std::shared_ptr<ModFolderModel> mods_folder)
+ CheckUpdateTask(QList<Mod*>& mods, std::list<Version>& mcVersions, std::optional<ResourceAPI::ModLoaderTypes> loaders, std::shared_ptr<ModFolderModel> 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<Mod*>& m_mods;
std::list<Version>& m_game_versions;
- ModAPI::ModLoaderTypes m_loaders;
+ std::optional<ResourceAPI::ModLoaderTypes> m_loaders;
std::shared_ptr<ModFolderModel> m_mods_folder;
std::vector<UpdatableMod> 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<Mod*>& mods, QDir dir, ModPlatform::Provider prov)
+EnsureMetadataTask::EnsureMetadataTask(QList<Mod*>& 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<Mod*>&, QDir, ModPlatform::Provider = ModPlatform::Provider::MODRINTH);
+ EnsureMetadataTask(Mod*, QDir, ModPlatform::ResourceProvider = ModPlatform::ResourceProvider::MODRINTH);
+ EnsureMetadataTask(QList<Mod*>&, QDir, ModPlatform::ResourceProvider = ModPlatform::ResourceProvider::MODRINTH);
~EnsureMetadataTask() = default;
@@ -57,7 +57,7 @@ class EnsureMetadataTask : public Task {
private:
QHash<QString, Mod*> m_mods;
QDir m_index_dir;
- ModPlatform::Provider m_provider;
+ ModPlatform::ResourceProvider m_provider;
QHash<QString, ModPlatform::IndexedVersion> 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 <contact@scrumplex.net>
- *
- * 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 <https://www.gnu.org/licenses/>.
- *
- * 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 <QString>
-#include <QList>
-#include <list>
-
-#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<Version> versions;
- };
-
- virtual void searchMods(CallerType* caller, SearchArgs&& args) const = 0;
- virtual void getModInfo(ModPlatform::IndexedPack& pack, std::function<void(QJsonDocument&, ModPlatform::IndexedPack&)> 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<Version> mcVersions;
- ModLoaderTypes loaders;
- };
-
- virtual void getVersions(VersionSearchArgs&& args, std::function<void(QJsonDocument&, QString)> 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<Version> 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 <contact@scrumplex.net>
+ *
+ * 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 <https://www.gnu.org/licenses/>.
+ *
+ * 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 <QList>
+#include <QString>
+
+#include <list>
+
+#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<QString> search;
+ std::optional<QString> sorting;
+ std::optional<ModLoaderTypes> loaders;
+ std::optional<std::list<Version> > versions;
+ };
+ struct SearchCallbacks {
+ std::function<void(QJsonDocument&)> on_succeed;
+ std::function<void(QString const& reason, int network_error_code)> on_fail;
+ std::function<void()> on_abort;
+ };
+
+ struct VersionSearchArgs {
+ QString addonId;
+
+ std::optional<std::list<Version> > mcVersions;
+ std::optional<ModLoaderTypes> loaders;
+ };
+ struct VersionSearchCallbacks {
+ std::function<void(QJsonDocument&, QString)> on_succeed;
+ };
+
+ struct ProjectInfoArgs {
+ ModPlatform::IndexedPack& pack;
+
+ void operator=(ProjectInfoArgs other) { pack = other.pack; }
+ };
+ struct ProjectInfoCallbacks {
+ std::function<void(QJsonDocument&, ModPlatform::IndexedPack&)> 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<Version> 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<uint>& 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<uint>& 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<QString> 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<QString> getInfoURL(QString const& id) const override
+ {
+ return QString("https://api.curseforge.com/v1/mods/%1").arg(id);
+ };
+
+ [[nodiscard]] std::optional<QString> 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<Mod*>& mods, std::list<Version>& mcVersions, ModAPI::ModLoaderTypes loaders, std::shared_ptr<ModFolderModel> mods_folder)
+ FlameCheckUpdate(QList<Mod*>& mods, std::list<Version>& mcVersions, std::optional<ResourceAPI::ModLoaderTypes> loaders, std::shared_ptr<ModFolderModel> 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<void(QJsonDocument&, ModPlatform::IndexedPack&)> 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<void(QJsonDocument&, QString)> 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<void(QJsonDocument&, ModPlatform::IndexedPack&)> callback) override;
- void getVersions(VersionSearchArgs&& args, std::function<void(QJsonDocument&, QString)> 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<QString> = 0;
+ [[nodiscard]] virtual auto getInfoURL(QString const& id) const -> std::optional<QString> = 0;
+ [[nodiscard]] virtual auto getVersionsURL(VersionSearchArgs const& args) const -> std::optional<QString> = 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<Version> mcVersions,
- ModLoaderTypes loaders,
+ std::optional<std::list<Version>> mcVersions,
+ std::optional<ModLoaderTypes> 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<Version> mcVersions,
- ModLoaderTypes loaders,
+ std::optional<std::list<Version>> mcVersions,
+ std::optional<ModLoaderTypes> 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 <QDebug>
-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<Version> mcVersions,
- ModLoaderTypes loaders,
+ std::optional<std::list<Version>> mcVersions,
+ std::optional<ModLoaderTypes> loaders,
QByteArray* response) -> NetJob::Ptr;
auto latestVersions(const QStringList& hashes,
QString hash_format,
- std::list<Version> mcVersions,
- ModLoaderTypes loaders,
+ std::optional<std::list<Version>> mcVersions,
+ std::optional<ModLoaderTypes> 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<QString> 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<QString> 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<QString> 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<Version> 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<Mod*>& mods, std::list<Version>& mcVersions, ModAPI::ModLoaderTypes loaders, std::shared_ptr<ModFolderModel> mods_folder)
+ ModrinthCheckUpdate(QList<Mod*>& mods, std::list<Version>& mcVersions, std::optional<ResourceAPI::ModLoaderTypes> loaders, std::shared_ptr<ModFolderModel> 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<QNetworkAccessManager> network) { m_network = network; }
@@ -75,9 +74,6 @@ class NetAction : public Task {
public:
shared_qobject_ptr<QNetworkAccessManager> m_network;
- /// index within the parent job, FIXME: nuke
- int m_index_within_job = 0;
-
/// the network reply
unique_qobject_ptr<QNetworkReply> 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 <BaseVersion.h>
-#include <InstanceList.h>
-#include <icons/IconList.h>
-
#include "Application.h"
-#include "ReviewMessageBox.h"
-
-#include <QDialogButtonBox>
-#include <QLayout>
-#include <QPushButton>
-#include <QValidator>
-#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<ModFolderModel>& 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<ModFolderModel>& 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<BasePage*> ModDownloadDialog::getPages()
{
QList<BasePage*> 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<ModPage*>(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<ModDownloadTask*> ModDownloadDialog::getTasks()
-{
- return modTask.values();
-}
-
-void ModDownloadDialog::selectedPageChanged(BasePage* previous, BasePage* selected)
-{
- auto* prev_page = dynamic_cast<ModPage*>(previous);
- if (!prev_page) {
- qCritical() << "Page '" << previous->displayName() << "' in ModDownloadDialog is not a ModPage!";
- return;
- }
-
- m_selectedPage = dynamic_cast<ModPage*>(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 <QDialog>
-#include <QVBoxLayout>
-
-#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<ModFolderModel>& mods, QWidget* parent, BaseInstance* instance);
+ explicit ModDownloadDialog(QWidget* parent, const std::shared_ptr<ModFolderModel>& mods, BaseInstance* instance);
~ModDownloadDialog() override = default;
- QString dialogTitle() override;
- QList<BasePage*> 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<ModDownloadTask*> getTasks();
- const std::shared_ptr<ModFolderModel>& mods;
-
- bool selectPage(QString pageId);
- ModPage* getSelectedPage();
+ QList<BasePage*> 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<QString, ModDownloadTask*> 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 <QTextBrowser>
#include <QTreeWidgetItem>
+#include <optional>
+
static ModPlatform::ProviderCapabilities ProviderCaps;
static std::list<Version> mcVersions(BaseInstance* inst)
@@ -28,7 +30,7 @@ static std::list<Version> mcVersions(BaseInstance* inst)
return { static_cast<MinecraftInstance*>(inst)->getPackProfile()->getComponent("net.minecraft")->getVersion() };
}
-static ModAPI::ModLoaderTypes mcLoaders(BaseInstance* inst)
+static std::optional<ResourceAPI::ModLoaderTypes> mcLoaders(BaseInstance* inst)
{
return { static_cast<MinecraftInstance*>(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<ModDownloadTask*>
+auto ModUpdateDialog::getTasks() -> const QList<ResourceDownloadTask*>
{
- QList<ModDownloadTask*> list;
+ QList<ResourceDownloadTask*> 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<ModDownloadTask*> getTasks();
+ const QList<ResourceDownloadTask*> 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<std::tuple<Mod*, QString>> m_failed_metadata;
QList<std::tuple<Mod*, QString, QUrl>> m_failed_check_update;
- QHash<QString, ModDownloadTask*> m_tasks;
+ QHash<QString, ResourceDownloadTask*> 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 <QPushButton>
+
+#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<ResourceFolderModel> 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<ResourceDownloadTask*> ResourceDownloadDialog::getTasks()
+{
+ return m_selected.values();
+}
+
+void ResourceDownloadDialog::selectedPageChanged(BasePage* previous, BasePage* selected)
+{
+ auto* prev_page = dynamic_cast<ResourcePage*>(previous);
+ if (!prev_page) {
+ qCritical() << "Page '" << previous->displayName() << "' in ResourceDownloadDialog is not a ResourcePage!";
+ return;
+ }
+
+ m_selectedPage = dynamic_cast<ResourcePage*>(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 <QDialog>
+#include <QDialogButtonBox>
+#include <QLayout>
+
+#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<ResourceFolderModel> 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<ResourceDownloadTask*> getTasks();
+ [[nodiscard]] const std::shared_ptr<ResourceFolderModel> getBaseModel() const { return m_base_model; }
+
+ protected slots:
+ void selectedPageChanged(BasePage* previous, BasePage* selected);
+
+ virtual void confirm();
+
+ protected:
+ const std::shared_ptr<ResourceFolderModel> m_base_model;
+
+ PageContainer* m_container = nullptr;
+ ResourcePage* m_selectedPage = nullptr;
+
+ QDialogButtonBox m_buttons;
+ QVBoxLayout m_vertical_layout;
+
+ QHash<QString, ResourceDownloadTask*> 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<MinecraftInstance*>(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 <QMessageBox>
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<ListModel*, bool> 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<MinecraftInstance&>(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("<br>")).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<MinecraftInstance&>(m_associated_page->m_base_instance).getPackProfile();
- modpacks[pos] = value.value<ModPlatform::IndexedPack>();
-
- 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<MinecraftInstance*>((dynamic_cast<ModPage*>(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<MinecraftInstance*>((dynamic_cast<ModPage*>(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<ModPlatform::IndexedPack> 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<Version>
+#define MOD_PAGE(x) static_cast<ModPage*>(x)
+
+auto ModPlatform::ListModel::getMineVersions() const -> std::optional<std::list<Version>>
{
- 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 <QAbstractListModel>
#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<QString, QIcon>;
-using LogoCallback = std::function<void (QString)>;
-
-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<Version>;
+ inline auto getMineVersions() const -> std::optional<std::list<Version>>;
protected:
- ModPage* m_parent;
-
- QList<ModPlatform::IndexedPack> modpacks;
-
- LogoMap m_logoMap;
- QMap<QString, LogoCallback> 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 <QDesktopServices>
#include <QKeyEvent>
#include <QRegularExpression>
+
#include <memory>
+#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<ModFilterWidget>& widget)
@@ -97,59 +68,19 @@ void ModPage::setFilterWidget(unique_qobject_ptr<ModFilterWidget>& 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<MinecraftInstance*>(m_instance));
+ m_filter_widget->setInstance(&static_cast<MinecraftInstance&>(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<QKeyEvent*>(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<QKeyEvent*>(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<ModPlatform::ListModel*>(m_model)->searchWithTerm(getSearchTerm(), m_ui->sortByBox->currentIndex(), changed);
+ m_fetch_progress.watch(&m_model->activeJob());
}
-void ModPage::onSelectionChanged(QModelIndex curr, QModelIndex prev)
+QMap<QString, QString> ModPage::urlHandlers() const
{
- ui->versionSelectionBox->clear();
-
- if (!curr.isValid()) { return; }
-
- current = listModel->data(curr, Qt::UserRole).value<ModPlatform::IndexedPack>();
-
- 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<ModPlatform::IndexedPack>();
-
- 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<QString, QString> 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<MinecraftInstance*>(m_instance))->getPackProfile();
+ m_ui->versionSelectionBox->clear();
+ auto packProfile = (dynamic_cast<MinecraftInstance&>(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 = "<a href=\"" + current.websiteUrl + "\">" + name + "</a>";
-
- if (!current.authors.empty()) {
- auto authorToStr = [](ModPlatform::ModpackAuthor& author) -> QString {
- if (author.url.isEmpty()) { return author.name; }
- return QString("<a href=\"%1\">%2</a>").arg(author.url, author.name);
- };
- QStringList authorStrs;
- for (auto& author : current.authors) {
- authorStrs.push_back(authorToStr(author));
- }
- text += "<br>" + tr(" by ") + authorStrs.join(", ");
- }
-
- if (current.extraDataLoaded) {
- if (!current.extraData.donate.isEmpty()) {
- text += "<br><br>" + tr("Donate information: ");
- auto donateToStr = [](ModPlatform::DonationData& donate) -> QString {
- return QString("<a href=\"%1\">%2</a>").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 += "<br><br>" + tr("External links:") + "<br>";
- }
-
- if (!current.extraData.issuesUrl.isEmpty())
- text += "- " + tr("Issues: <a href=%1>%1</a>").arg(current.extraData.issuesUrl) + "<br>";
- if (!current.extraData.wikiUrl.isEmpty())
- text += "- " + tr("Wiki: <a href=%1>%1</a>").arg(current.extraData.wikiUrl) + "<br>";
- if (!current.extraData.sourceUrl.isEmpty())
- text += "- " + tr("Source code: <a href=%1>%1</a>").arg(current.extraData.sourceUrl) + "<br>";
- if (!current.extraData.discordUrl.isEmpty())
- text += "- " + tr("Discord: <a href=%1>%1</a>").arg(current.extraData.discordUrl) + "<br>";
- }
-
- text += "<hr>";
-
- 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 <QWidget>
-#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<typename T>
- 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<MinecraftInstance*>(instance)->getPackProfile()->getComponentVersion("net.minecraft"), page);
+ auto filter_widget = ModFilterWidget::create(static_cast<MinecraftInstance&>(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<QString, QString> 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<ResourceAPI::ModLoaderTypes> 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<ModFilterWidget::Filter> { 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<ModFilterWidget>&);
- 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<ModFilterWidget> m_filter_widget;
std::shared_ptr<ModFilterWidget::Filter> m_filter;
-
- ProgressWidget m_fetch_progress;
-
- ModPlatform::ListModel* listModel = nullptr;
- ModPlatform::IndexedPack current;
-
- std::unique_ptr<ModAPI> 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/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 <QCryptographicHash>
+#include <QIcon>
+#include <QMessageBox>
+#include <QPixmapCache>
+#include <QUrl>
+
+#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*, bool> 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("<br>")).left(edit.lastIndexOf(" ")).append("...");
+ return edit;
+ }
+ return pack.description;
+ }
+ case Qt::DecorationRole: {
+ if (auto icon_or_none = const_cast<ResourceModel*>(this)->getIcon(const_cast<QModelIndex&>(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<ModPlatform::IndexedPack>();
+
+ 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<QIcon> 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 <optional>
+
+#include <QAbstractListModel>
+
+#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<QIcon> 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<ResourceAPI> m_api;
+
+ ConcurrentTask m_current_job;
+
+ shared_qobject_ptr<NetJob> m_current_icon_job;
+ QSet<QUrl> m_currently_running_icon_actions;
+ QSet<QUrl> m_failed_icon_actions;
+
+ ResourcePage* m_associated_page = nullptr;
+
+ QList<ModPlatform::IndexedPack> 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<ResourceModel*, bool> 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 <QDesktopServices>
+#include <QKeyEvent>
+
+#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<QKeyEvent*>(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<ModPlatform::IndexedPack>();
+}
+
+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 = "<a href=\"" + current_pack.websiteUrl + "\">" + name + "</a>";
+
+ if (!current_pack.authors.empty()) {
+ auto authorToStr = [](ModPlatform::ModpackAuthor& author) -> QString {
+ if (author.url.isEmpty()) {
+ return author.name;
+ }
+ return QString("<a href=\"%1\">%2</a>").arg(author.url, author.name);
+ };
+ QStringList authorStrs;
+ for (auto& author : current_pack.authors) {
+ authorStrs.push_back(authorToStr(author));
+ }
+ text += "<br>" + tr(" by ") + authorStrs.join(", ");
+ }
+
+ if (current_pack.extraDataLoaded) {
+ if (!current_pack.extraData.donate.isEmpty()) {
+ text += "<br><br>" + tr("Donate information: ");
+ auto donateToStr = [](ModPlatform::DonationData& donate) -> QString {
+ return QString("<a href=\"%1\">%2</a>").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 += "<br><br>" + tr("External links:") + "<br>";
+ }
+
+ if (!current_pack.extraData.issuesUrl.isEmpty())
+ text += "- " + tr("Issues: <a href=%1>%1</a>").arg(current_pack.extraData.issuesUrl) + "<br>";
+ if (!current_pack.extraData.wikiUrl.isEmpty())
+ text += "- " + tr("Wiki: <a href=%1>%1</a>").arg(current_pack.extraData.wikiUrl) + "<br>";
+ if (!current_pack.extraData.sourceUrl.isEmpty())
+ text += "- " + tr("Source code: <a href=%1>%1</a>").arg(current_pack.extraData.sourceUrl) + "<br>";
+ if (!current_pack.extraData.discordUrl.isEmpty())
+ text += "- " + tr("Discord: <a href=%1>%1</a>").arg(current_pack.extraData.discordUrl) + "<br>";
+ }
+
+ text += "<hr>";
+
+ 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<ModPlatform::IndexedPack>();
+
+ 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 <QTimer>
+#include <QWidget>
+
+#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<QString, QString> 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/ModPage.ui b/launcher/ui/pages/modplatform/ResourcePage.ui
index 94365aa5..8fe1d613 100644
--- a/launcher/ui/pages/modplatform/ModPage.ui
+++ b/launcher/ui/pages/modplatform/ResourcePage.ui
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
- <class>ModPage</class>
- <widget class="QWidget" name="ModPage">
+ <class>ResourcePage</class>
+ <widget class="QWidget" name="ResourcePage">
<property name="geometry">
<rect>
<x>0</x>
@@ -51,7 +51,7 @@
<item row="0" column="0">
<widget class="QLineEdit" name="searchEdit">
<property name="placeholderText">
- <string>Search for mods...</string>
+ <string>Search for resources...</string>
</property>
</widget>
</item>
@@ -74,16 +74,16 @@
<widget class="QComboBox" name="sortByBox"/>
</item>
<item row="1" column="2">
- <widget class="QPushButton" name="modSelectionButton">
+ <widget class="QPushButton" name="resourceSelectionButton">
<property name="text">
- <string>Select mod for download</string>
+ <string>Select resource for download</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="0" column="1">
- <widget class="QPushButton" name="modFilterButton">
+ <widget class="QPushButton" name="resourceFilterButton">
<property name="text">
<string>Filter options</string>
</property>
diff --git a/launcher/ui/pages/modplatform/flame/FlameModModel.cpp b/launcher/ui/pages/modplatform/flame/FlameResourceModels.cpp
index bc2c686c..b602dfac 100644
--- a/launcher/ui/pages/modplatform/flame/FlameModModel.cpp
+++ b/launcher/ui/pages/modplatform/flame/FlameResourceModels.cpp
@@ -1,4 +1,4 @@
-#include "FlameModModel.h"
+#include "FlameResourceModels.h"
#include "Json.h"
#include "modplatform/flame/FlameModIndex.h"
@@ -20,7 +20,7 @@ void ListModel::loadExtraPackInfo(ModPlatform::IndexedPack& m, QJsonObject& obj)
void ListModel::loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr)
{
- FlameMod::loadIndexedPackVersions(m, arr, APPLICATION->network(), m_parent->m_instance);
+ FlameMod::loadIndexedPackVersions(m, arr, APPLICATION->network(), &m_associated_page->m_base_instance);
}
auto ListModel::documentToArray(QJsonDocument& obj) const -> QJsonArray
diff --git a/launcher/ui/pages/modplatform/flame/FlameModModel.h b/launcher/ui/pages/modplatform/flame/FlameResourceModels.h
index 6a6aef2e..b94377d3 100644
--- a/launcher/ui/pages/modplatform/flame/FlameModModel.h
+++ b/launcher/ui/pages/modplatform/flame/FlameResourceModels.h
@@ -1,6 +1,6 @@
#pragma once
-#include "FlameModPage.h"
+#include "modplatform/flame/FlameAPI.h"
namespace FlameMod {
@@ -8,7 +8,7 @@ class ListModel : public ModPlatform::ListModel {
Q_OBJECT
public:
- ListModel(FlameModPage* parent) : ModPlatform::ListModel(parent) {}
+ ListModel(FlameModPage* parent) : ModPlatform::ListModel(parent, new FlameAPI) {}
~ListModel() override = default;
private:
diff --git a/launcher/ui/pages/modplatform/flame/FlameModPage.cpp b/launcher/ui/pages/modplatform/flame/FlameResourcePages.cpp
index bad78c97..490578ad 100644
--- a/launcher/ui/pages/modplatform/flame/FlameModPage.cpp
+++ b/launcher/ui/pages/modplatform/flame/FlameResourcePages.cpp
@@ -34,37 +34,37 @@
* limitations under the License.
*/
-#include "FlameModPage.h"
-#include "ui_ModPage.h"
+#include "FlameResourcePages.h"
+#include "ui_ResourcePage.h"
-#include "FlameModModel.h"
+#include "FlameResourceModels.h"
#include "ui/dialogs/ModDownloadDialog.h"
-FlameModPage::FlameModPage(ModDownloadDialog* dialog, BaseInstance* instance)
- : ModPage(dialog, instance, new FlameAPI())
+FlameModPage::FlameModPage(ModDownloadDialog* dialog, BaseInstance& instance)
+ : ModPage(dialog, instance)
{
- listModel = new FlameMod::ListModel(this);
- ui->packView->setModel(listModel);
+ m_model = new FlameMod::ListModel(this);
+ m_ui->packView->setModel(m_model);
// 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"));
+ 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(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);
+ 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);
- ui->packDescription->setMetaEntry(metaEntryBase());
+ m_ui->packDescription->setMetaEntry(metaEntryBase());
}
-auto FlameModPage::validateVersion(ModPlatform::IndexedVersion& ver, QString mineVer, ModAPI::ModLoaderTypes loaders) const -> bool
+auto FlameModPage::validateVersion(ModPlatform::IndexedVersion& ver, QString mineVer, std::optional<ResourceAPI::ModLoaderTypes> loaders) const -> bool
{
Q_UNUSED(loaders);
return ver.mcVersion.contains(mineVer) && !ver.downloadUrl.isEmpty();
diff --git a/launcher/ui/pages/modplatform/flame/FlameModPage.h b/launcher/ui/pages/modplatform/flame/FlameResourcePages.h
index 58479ab9..597a0c25 100644
--- a/launcher/ui/pages/modplatform/flame/FlameModPage.h
+++ b/launcher/ui/pages/modplatform/flame/FlameResourcePages.h
@@ -36,21 +36,22 @@
#pragma once
-#include "modplatform/ModAPI.h"
-#include "ui/pages/modplatform/ModPage.h"
+#include "Application.h"
+
+#include "modplatform/ResourceAPI.h"
-#include "modplatform/flame/FlameAPI.h"
+#include "ui/pages/modplatform/ModPage.h"
class FlameModPage : public ModPage {
Q_OBJECT
public:
- static FlameModPage* create(ModDownloadDialog* dialog, BaseInstance* instance)
+ static FlameModPage* create(ModDownloadDialog* dialog, BaseInstance& instance)
{
return ModPage::create<FlameModPage>(dialog, instance);
}
- FlameModPage(ModDownloadDialog* dialog, BaseInstance* instance);
+ FlameModPage(ModDownloadDialog* dialog, BaseInstance& instance);
~FlameModPage() override = default;
inline auto displayName() const -> QString override { return "CurseForge"; }
@@ -61,7 +62,7 @@ class FlameModPage : public ModPage {
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;
+ auto validateVersion(ModPlatform::IndexedVersion& ver, QString mineVer, std::optional<ResourceAPI::ModLoaderTypes> loaders = {}) const -> bool override;
bool optedOut(ModPlatform::IndexedVersion& ver) const override;
auto shouldDisplay() const -> bool override;
diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthModModel.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.cpp
index af92e63e..51278546 100644
--- a/launcher/ui/pages/modplatform/modrinth/ModrinthModModel.cpp
+++ b/launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.cpp
@@ -16,8 +16,11 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
-#include "ModrinthModModel.h"
+#include "ModrinthResourceModels.h"
+#include "ui/pages/modplatform/modrinth/ModrinthResourcePages.h"
+
+#include "modplatform/modrinth/ModrinthAPI.h"
#include "modplatform/modrinth/ModrinthPackIndex.h"
namespace Modrinth {
@@ -37,7 +40,7 @@ void ListModel::loadExtraPackInfo(ModPlatform::IndexedPack& m, QJsonObject& obj)
void ListModel::loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr)
{
- Modrinth::loadIndexedPackVersions(m, arr, APPLICATION->network(), m_parent->m_instance);
+ Modrinth::loadIndexedPackVersions(m, arr, APPLICATION->network(), &m_associated_page->m_base_instance);
}
auto ListModel::documentToArray(QJsonDocument& obj) const -> QJsonArray
@@ -46,3 +49,5 @@ auto ListModel::documentToArray(QJsonDocument& obj) const -> QJsonArray
}
} // namespace Modrinth
+
+
diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthModModel.h b/launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.h
index 386897fd..bf62d22f 100644
--- a/launcher/ui/pages/modplatform/modrinth/ModrinthModModel.h
+++ b/launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.h
@@ -18,7 +18,11 @@
#pragma once
-#include "ModrinthModPage.h"
+#include "ui/pages/modplatform/ModModel.h"
+
+#include "ui/pages/modplatform/modrinth/ModrinthResourcePages.h"
+
+#include "modplatform/modrinth/ModrinthAPI.h"
namespace Modrinth {
@@ -26,7 +30,7 @@ class ListModel : public ModPlatform::ListModel {
Q_OBJECT
public:
- ListModel(ModrinthModPage* parent) : ModPlatform::ListModel(parent){};
+ ListModel(ModrinthModPage* parent) : ModPlatform::ListModel(parent, new ModrinthAPI){};
~ListModel() override = default;
private:
@@ -42,3 +46,4 @@ class ListModel : public ModPlatform::ListModel {
};
} // namespace Modrinth
+
diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthModPage.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.cpp
index c531ea90..17f0bc93 100644
--- a/launcher/ui/pages/modplatform/modrinth/ModrinthModPage.cpp
+++ b/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.cpp
@@ -33,48 +33,52 @@
* limitations under the License.
*/
-#include "ModrinthModPage.h"
+#include "ModrinthResourcePages.h"
+#include "ui_ResourcePage.h"
+
#include "modplatform/modrinth/ModrinthAPI.h"
-#include "ui_ModPage.h"
-#include "ModrinthModModel.h"
+#include "ModrinthResourceModels.h"
#include "ui/dialogs/ModDownloadDialog.h"
-ModrinthModPage::ModrinthModPage(ModDownloadDialog* dialog, BaseInstance* instance)
- : ModPage(dialog, instance, new ModrinthAPI())
+ModrinthModPage::ModrinthModPage(ModDownloadDialog* dialog, BaseInstance& instance)
+ : ModPage(dialog, instance)
{
- listModel = new Modrinth::ListModel(this);
- ui->packView->setModel(listModel);
+ m_model = new Modrinth::ListModel(this);
+ m_ui->packView->setModel(m_model);
// 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"));
+ 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(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);
+ 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);
- ui->packDescription->setMetaEntry(metaEntryBase());
+ m_ui->packDescription->setMetaEntry(metaEntryBase());
}
-auto ModrinthModPage::validateVersion(ModPlatform::IndexedVersion& ver, QString mineVer, ModAPI::ModLoaderTypes loaders) const -> bool
+auto ModrinthModPage::validateVersion(ModPlatform::IndexedVersion& ver, QString mineVer, std::optional<ResourceAPI::ModLoaderTypes> loaders) const -> bool
{
- auto loaderStrings = ModrinthAPI::getModLoaderStrings(loaders);
+ auto loaderCompatible = !loaders.has_value();
- auto loaderCompatible = false;
- for (auto remoteLoader : ver.loaders)
- {
- if (loaderStrings.contains(remoteLoader)) {
- loaderCompatible = true;
- break;
+ 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;
}
@@ -82,3 +86,4 @@ auto ModrinthModPage::validateVersion(ModPlatform::IndexedVersion& ver, QString
// 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/ModrinthResourcePages.h
index 40d82e6f..6f816cfd 100644
--- a/launcher/ui/pages/modplatform/modrinth/ModrinthModPage.h
+++ b/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.h
@@ -35,32 +35,38 @@
#pragma once
-#include "modplatform/ModAPI.h"
+#include "Application.h"
+
+#include "modplatform/ResourceAPI.h"
+
#include "ui/pages/modplatform/ModPage.h"
-#include "modplatform/modrinth/ModrinthAPI.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)
+ static ModrinthModPage* create(ModDownloadDialog* dialog, BaseInstance& instance)
{
return ModPage::create<ModrinthModPage>(dialog, instance);
}
- ModrinthModPage(ModDownloadDialog* dialog, BaseInstance* 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"; }
+ [[nodiscard]] bool shouldDisplay() const override;
- 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;
+ [[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 shouldDisplay() const -> bool override;
+ auto validateVersion(ModPlatform::IndexedVersion& ver, QString mineVer, std::optional<ResourceAPI::ModLoaderTypes> 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<Task*>(m_task), "start", Qt::QueuedConnection);
}
bool ProgressWidget::exec(std::shared_ptr<Task> 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> 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;
};