aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--launcher/CMakeLists.txt2
-rw-r--r--launcher/ResourceDownloadTask.cpp11
-rw-r--r--launcher/ResourceDownloadTask.h58
-rw-r--r--launcher/minecraft/mod/tasks/GetModDependenciesTask.cpp179
-rw-r--r--launcher/minecraft/mod/tasks/GetModDependenciesTask.h81
-rw-r--r--launcher/minecraft/mod/tasks/LocalModUpdateTask.cpp33
-rw-r--r--launcher/modplatform/ModIndex.h44
-rw-r--r--launcher/modplatform/ResourceAPI.h24
-rw-r--r--launcher/modplatform/flame/FlameAPI.h15
-rw-r--r--launcher/modplatform/flame/FlameModIndex.cpp78
-rw-r--r--launcher/modplatform/flame/FlameModIndex.h6
-rw-r--r--launcher/modplatform/flame/FlamePackIndex.h10
-rw-r--r--launcher/modplatform/helpers/NetworkResourceAPI.cpp49
-rw-r--r--launcher/modplatform/helpers/NetworkResourceAPI.h2
-rw-r--r--launcher/modplatform/modrinth/ModrinthAPI.h35
-rw-r--r--launcher/modplatform/modrinth/ModrinthPackIndex.cpp41
-rw-r--r--launcher/modplatform/modrinth/ModrinthPackIndex.h3
-rw-r--r--launcher/ui/dialogs/ResourceDownloadDialog.cpp146
-rw-r--r--launcher/ui/dialogs/ResourceDownloadDialog.h21
-rw-r--r--launcher/ui/dialogs/ReviewMessageBox.cpp29
-rw-r--r--launcher/ui/dialogs/ReviewMessageBox.h8
-rw-r--r--launcher/ui/pages/modplatform/ModModel.h1
-rw-r--r--launcher/ui/pages/modplatform/ModPage.cpp23
-rw-r--r--launcher/ui/pages/modplatform/ModPage.h2
-rw-r--r--launcher/ui/pages/modplatform/ResourceModel.cpp47
-rw-r--r--launcher/ui/pages/modplatform/ResourceModel.h12
-rw-r--r--launcher/ui/pages/modplatform/ResourcePage.cpp19
-rw-r--r--launcher/ui/pages/modplatform/ResourcePage.h14
-rw-r--r--launcher/ui/pages/modplatform/ShaderPackPage.cpp16
-rw-r--r--launcher/ui/pages/modplatform/ShaderPackPage.h2
-rw-r--r--launcher/ui/pages/modplatform/flame/FlameResourceModels.cpp7
-rw-r--r--launcher/ui/pages/modplatform/flame/FlameResourceModels.h1
-rw-r--r--launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.cpp11
-rw-r--r--launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.h1
-rw-r--r--launcher/ui/widgets/PageContainer.cpp5
-rw-r--r--launcher/ui/widgets/PageContainer.h1
36 files changed, 824 insertions, 213 deletions
diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt
index 273b5449..f6afc45c 100644
--- a/launcher/CMakeLists.txt
+++ b/launcher/CMakeLists.txt
@@ -362,6 +362,8 @@ set(MINECRAFT_SOURCES
minecraft/mod/tasks/LocalWorldSaveParseTask.cpp
minecraft/mod/tasks/LocalResourceParse.h
minecraft/mod/tasks/LocalResourceParse.cpp
+ minecraft/mod/tasks/GetModDependenciesTask.h
+ minecraft/mod/tasks/GetModDependenciesTask.cpp
# Assets
minecraft/AssetsUtils.h
diff --git a/launcher/ResourceDownloadTask.cpp b/launcher/ResourceDownloadTask.cpp
index 61b918aa..d9ddfa00 100644
--- a/launcher/ResourceDownloadTask.cpp
+++ b/launcher/ResourceDownloadTask.cpp
@@ -27,8 +27,9 @@
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)
+ bool is_indexed,
+ QString custom_target_folder)
+ : m_pack(std::move(pack)), m_pack_version(std::move(version)), m_pack_model(packs), m_custom_target_folder(custom_target_folder)
{
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));
@@ -40,13 +41,13 @@ ResourceDownloadTask::ResourceDownloadTask(ModPlatform::IndexedPack pack,
m_filesNetJob.reset(new NetJob(tr("Resource download"), APPLICATION->network()));
m_filesNetJob->setStatus(tr("Downloading resource:\n%1").arg(m_pack_version.downloadUrl));
- QDir dir { m_pack_model->dir() };
+ QDir dir{ m_pack_model->dir() };
{
// FIXME: Make this more generic. May require adding additional info to IndexedVersion,
// or adquiring a reference to the base instance.
- if (!m_pack_version.custom_target_folder.isEmpty()) {
+ if (!m_custom_target_folder.isEmpty()) {
dir.cdUp();
- dir.cd(m_pack_version.custom_target_folder);
+ dir.cd(m_custom_target_folder);
}
}
diff --git a/launcher/ResourceDownloadTask.h b/launcher/ResourceDownloadTask.h
index 73ad2d07..578e8f45 100644
--- a/launcher/ResourceDownloadTask.h
+++ b/launcher/ResourceDownloadTask.h
@@ -1,44 +1,53 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
-* Prism Launcher - Minecraft Launcher
-* Copyright (c) 2022-2023 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/>.
-*/
+ * Prism Launcher - Minecraft Launcher
+ * Copyright (c) 2022-2023 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/>.
+ */
#pragma once
#include "net/NetJob.h"
#include "tasks/SequentialTask.h"
-#include "modplatform/ModIndex.h"
#include "minecraft/mod/tasks/LocalModUpdateTask.h"
+#include "modplatform/ModIndex.h"
class ResourceFolderModel;
class ResourceDownloadTask : public SequentialTask {
Q_OBJECT
-public:
- explicit ResourceDownloadTask(ModPlatform::IndexedPack pack, ModPlatform::IndexedVersion version, const std::shared_ptr<ResourceFolderModel> packs, bool is_indexed = true);
+ public:
+ explicit ResourceDownloadTask(ModPlatform::IndexedPack pack,
+ ModPlatform::IndexedVersion version,
+ const std::shared_ptr<ResourceFolderModel> packs,
+ bool is_indexed = true,
+ QString custom_target_folder = {});
const QString& getFilename() const { return m_pack_version.fileName; }
- const QString& getCustomPath() const { return m_pack_version.custom_target_folder; }
+ const QString& getCustomPath() const { return m_custom_target_folder; }
const QVariant& getVersionID() const { return m_pack_version.fileId; }
+ const ModPlatform::IndexedVersion& getVersion() const { return m_pack_version; }
+ ModPlatform::IndexedPack& getPack() { return m_pack; }
+ const ModPlatform::ResourceProvider& getProvider() const { return m_pack.provider; }
+ const QString& getName() const { return m_pack.name; }
-private:
+ private:
ModPlatform::IndexedPack m_pack;
ModPlatform::IndexedVersion m_pack_version;
const std::shared_ptr<ResourceFolderModel> m_pack_model;
+ QString m_custom_target_folder;
NetJob::Ptr m_filesNetJob;
LocalModUpdateTask::Ptr m_update_task;
@@ -47,11 +56,8 @@ private:
void downloadFailed(QString reason);
void downloadSucceeded();
- std::tuple<QString, QString> to_delete {"", ""};
+ std::tuple<QString, QString> to_delete{ "", "" };
-private slots:
+ private slots:
void hasOldResource(QString name, QString filename);
};
-
-
-
diff --git a/launcher/minecraft/mod/tasks/GetModDependenciesTask.cpp b/launcher/minecraft/mod/tasks/GetModDependenciesTask.cpp
new file mode 100644
index 00000000..96d343a1
--- /dev/null
+++ b/launcher/minecraft/mod/tasks/GetModDependenciesTask.cpp
@@ -0,0 +1,179 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * Prism Launcher - Minecraft Launcher
+ * Copyright (c) 2023 Trial97 <alexandru.tripon97@gmail.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "GetModDependenciesTask.h"
+
+#include <QDebug>
+#include <algorithm>
+#include <memory>
+#include "Json.h"
+#include "QObjectPtr.h"
+#include "minecraft/mod/MetadataHandler.h"
+#include "modplatform/ModIndex.h"
+#include "modplatform/flame/FlameAPI.h"
+#include "modplatform/modrinth/ModrinthAPI.h"
+#include "tasks/ConcurrentTask.h"
+#include "tasks/SequentialTask.h"
+#include "ui/pages/modplatform/ModModel.h"
+#include "ui/pages/modplatform/flame/FlameResourceModels.h"
+#include "ui/pages/modplatform/modrinth/ModrinthResourceModels.h"
+
+static Version mcVersions(BaseInstance* inst)
+{
+ return static_cast<MinecraftInstance*>(inst)->getPackProfile()->getComponent("net.minecraft")->getVersion();
+}
+
+static ResourceAPI::ModLoaderTypes mcLoaders(BaseInstance* inst)
+{
+ return static_cast<MinecraftInstance*>(inst)->getPackProfile()->getModLoaders().value();
+}
+
+GetModDependenciesTask::GetModDependenciesTask(QObject* parent,
+ BaseInstance* instance,
+ ModFolderModel* folder,
+ QList<std::shared_ptr<PackDependency>> selected)
+ : SequentialTask(parent, "Get dependencies")
+ , m_selected(selected)
+ , m_flame_provider{ ModPlatform::ResourceProvider::FLAME, std::make_shared<ResourceDownload::FlameModModel>(*instance),
+ std::make_shared<FlameAPI>() }
+ , m_modrinth_provider{ ModPlatform::ResourceProvider::MODRINTH, std::make_shared<ResourceDownload::ModrinthModModel>(*instance),
+ std::make_shared<ModrinthAPI>() }
+ , m_version(mcVersions(instance))
+ , m_loaderType(mcLoaders(instance))
+{
+ for (auto mod : folder->allMods())
+ m_mods.append(mod->metadata());
+ prepare();
+};
+
+void GetModDependenciesTask::prepare()
+{
+ for (auto sel : m_selected) {
+ for (auto dep : getDependenciesForVersion(sel->version, sel->pack.provider)) {
+ addTask(prepareDependencyTask(dep, sel->pack.provider, 20));
+ }
+ }
+}
+
+QList<ModPlatform::Dependency> GetModDependenciesTask::getDependenciesForVersion(const ModPlatform::IndexedVersion& version,
+ const ModPlatform::ResourceProvider providerName)
+{
+ auto c_dependencies = QList<ModPlatform::Dependency>();
+ for (auto ver_dep : version.dependencies) {
+ if (ver_dep.type == ModPlatform::DependencyType::REQUIRED) {
+ if (auto dep = std::find_if(c_dependencies.begin(), c_dependencies.end(),
+ [&ver_dep](const ModPlatform::Dependency& i) { return i.addonId == ver_dep.addonId; });
+ dep == c_dependencies.end()) { // check the current dependency list
+ if (auto dep = std::find_if(m_selected.begin(), m_selected.end(),
+ [&ver_dep, providerName](std::shared_ptr<PackDependency> i) {
+ return i->pack.addonId == ver_dep.addonId && i->pack.provider == providerName;
+ });
+ dep == m_selected.end()) { // check the selected versions
+ if (auto dep = std::find_if(m_mods.begin(), m_mods.end(),
+ [&ver_dep, providerName](std::shared_ptr<Metadata::ModStruct> i) {
+ return i->project_id == ver_dep.addonId && i->provider == providerName;
+ });
+ dep == m_mods.end()) { // check the existing mods
+ if (auto dep = std::find_if(m_pack_dependencies.begin(), m_pack_dependencies.end(),
+ [&ver_dep, providerName](std::shared_ptr<PackDependency> i) {
+ return i->pack.addonId == ver_dep.addonId && i->pack.provider == providerName;
+ });
+ dep == m_pack_dependencies.end()) { // check loaded dependencies
+ c_dependencies.append(ver_dep);
+ }
+ }
+ }
+ }
+ }
+ }
+ return c_dependencies;
+};
+
+Task::Ptr GetModDependenciesTask::prepareDependencyTask(const ModPlatform::Dependency& dep,
+ const ModPlatform::ResourceProvider providerName,
+ int level)
+{
+ auto pDep = std::make_shared<PackDependency>();
+ pDep->dependency = dep;
+ pDep->pack = { dep.addonId, providerName };
+ m_pack_dependencies.append(pDep);
+ auto provider = providerName == m_flame_provider.name ? m_flame_provider : m_modrinth_provider;
+
+ auto tasks = makeShared<SequentialTask>(this, QString("DependencyInfo: %1").arg(dep.addonId.toString()));
+
+ auto responseInfo = new QByteArray();
+ auto info = provider.api->getProject(dep.addonId.toString(), responseInfo);
+ QObject::connect(info.get(), &NetJob::succeeded, [responseInfo, provider, pDep] {
+ QJsonParseError parse_error{};
+ QJsonDocument doc = QJsonDocument::fromJson(*responseInfo, &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() << *responseInfo;
+ return;
+ }
+ try {
+ auto obj = provider.name == ModPlatform::ResourceProvider::FLAME ? Json::requireObject(Json::requireObject(doc), "data")
+ : Json::requireObject(doc);
+ provider.mod->loadIndexedPack(pDep->pack, obj);
+ } catch (const JSONValidationError& e) {
+ qDebug() << doc;
+ qWarning() << "Error while reading mod info: " << e.cause();
+ }
+ });
+ tasks->addTask(info);
+
+ ResourceAPI::DependencySearchArgs args = { dep, m_version, m_loaderType };
+ ResourceAPI::DependencySearchCallbacks callbacks;
+
+ callbacks.on_succeed = [dep, provider, pDep, level, this](auto& doc, auto& pack) {
+ try {
+ QJsonArray arr;
+ if (dep.version.length() != 0 && doc.isObject()) {
+ arr.append(doc.object());
+ } else {
+ arr = doc.isObject() ? Json::ensureArray(doc.object(), "data") : doc.array();
+ }
+ pDep->version = provider.mod->loadDependencyVersions(dep, arr);
+ if (!pDep->version.addonId.isValid()) {
+ qWarning() << "Error while reading mod version empty ";
+ qDebug() << doc;
+ return;
+ }
+ pDep->version.is_currently_selected = true;
+ pDep->pack.versions = { pDep->version };
+ pDep->pack.versionsLoaded = true;
+
+ } catch (const JSONValidationError& e) {
+ qDebug() << doc;
+ qWarning() << "Error while reading mod version: " << e.cause();
+ return;
+ }
+ if (level == 0) {
+ qWarning() << "Dependency cycle exeeded";
+ return;
+ }
+ for (auto dep : getDependenciesForVersion(pDep->version, provider.name)) {
+ addTask(prepareDependencyTask(dep, provider.name, level - 1));
+ }
+ };
+
+ auto version = provider.api->getDependencyVersion(std::move(args), std::move(callbacks));
+ tasks->addTask(version);
+ return tasks;
+};
diff --git a/launcher/minecraft/mod/tasks/GetModDependenciesTask.h b/launcher/minecraft/mod/tasks/GetModDependenciesTask.h
new file mode 100644
index 00000000..aca3c004
--- /dev/null
+++ b/launcher/minecraft/mod/tasks/GetModDependenciesTask.h
@@ -0,0 +1,81 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * Prism Launcher - Minecraft Launcher
+ * Copyright (c) 2023 Trial97 <alexandru.tripon97@gmail.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <QDir>
+#include <QEventLoop>
+#include <QList>
+#include <QVariant>
+#include <functional>
+#include <memory>
+
+#include "minecraft/mod/MetadataHandler.h"
+#include "minecraft/mod/ModFolderModel.h"
+#include "modplatform/ModIndex.h"
+#include "modplatform/ResourceAPI.h"
+#include "tasks/SequentialTask.h"
+#include "tasks/Task.h"
+#include "ui/pages/modplatform/ModModel.h"
+
+class GetModDependenciesTask : public SequentialTask {
+ Q_OBJECT
+ public:
+ using Ptr = shared_qobject_ptr<GetModDependenciesTask>;
+
+ struct PackDependency {
+ ModPlatform::Dependency dependency;
+ ModPlatform::IndexedPack pack;
+ ModPlatform::IndexedVersion version;
+ PackDependency(){};
+ PackDependency(const ModPlatform::IndexedPack& p, const ModPlatform::IndexedVersion& v)
+ {
+ pack = p;
+ version = v;
+ }
+ };
+
+ struct Provider {
+ ModPlatform::ResourceProvider name;
+ std::shared_ptr<ResourceDownload::ModModel> mod;
+ std::shared_ptr<ResourceAPI> api;
+ };
+
+ explicit GetModDependenciesTask(QObject* parent,
+ BaseInstance* instance,
+ ModFolderModel* folder,
+ QList<std::shared_ptr<PackDependency>> selected);
+
+ auto getDependecies() const -> QList<std::shared_ptr<PackDependency>> { return m_pack_dependencies; }
+
+ protected slots:
+ Task::Ptr prepareDependencyTask(const ModPlatform::Dependency&, const ModPlatform::ResourceProvider, int);
+ QList<ModPlatform::Dependency> getDependenciesForVersion(const ModPlatform::IndexedVersion&,
+ const ModPlatform::ResourceProvider providerName);
+ void prepare();
+
+ private:
+ QList<std::shared_ptr<PackDependency>> m_pack_dependencies;
+ QList<std::shared_ptr<Metadata::ModStruct>> m_mods;
+ QList<std::shared_ptr<PackDependency>> m_selected;
+ Provider m_flame_provider;
+ Provider m_modrinth_provider;
+
+ Version m_version;
+ ResourceAPI::ModLoaderTypes m_loaderType;
+};
diff --git a/launcher/minecraft/mod/tasks/LocalModUpdateTask.cpp b/launcher/minecraft/mod/tasks/LocalModUpdateTask.cpp
index cc4e252c..4352fad9 100644
--- a/launcher/minecraft/mod/tasks/LocalModUpdateTask.cpp
+++ b/launcher/minecraft/mod/tasks/LocalModUpdateTask.cpp
@@ -1,25 +1,24 @@
// 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/>.
-*/
+ * Prism Launcher - 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 "LocalModUpdateTask.h"
-#include "Application.h"
#include "FileSystem.h"
#include "minecraft/mod/MetadataHandler.h"
diff --git a/launcher/modplatform/ModIndex.h b/launcher/modplatform/ModIndex.h
index 40f1efc4..a19fc0eb 100644
--- a/launcher/modplatform/ModIndex.h
+++ b/launcher/modplatform/ModIndex.h
@@ -1,20 +1,21 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
-* PolyMC - Minecraft Launcher
-* Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
-*
-* This program is free software: you can redistribute it and/or modify
-* it under the terms of the GNU General Public License as published by
-* the Free Software Foundation, version 3.
-*
-* This program is distributed in the hope that it will be useful,
-* but WITHOUT ANY WARRANTY; without even the implied warranty of
-* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-* GNU General Public License for more details.
-*
-* You should have received a copy of the GNU General Public License
-* along with this program. If not, see <https://www.gnu.org/licenses/>.
-*/
+ * Prism Launcher - Minecraft Launcher
+ * Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
+ * Copyright (c) 2023 Trial97 <alexandru.tripon97@gmail.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
#pragma once
@@ -32,6 +33,8 @@ enum class ResourceProvider { MODRINTH, FLAME };
enum class ResourceType { MOD, RESOURCE_PACK, SHADER_PACK };
+enum class DependencyType { REQUIRED, OPTIONAL, INCOMPATIBLE, EMBEDDED, TOOL, INCLUDE };
+
class ProviderCapabilities {
public:
auto name(ResourceProvider) -> const char*;
@@ -51,6 +54,12 @@ struct DonationData {
QString url;
};
+struct Dependency {
+ QVariant addonId;
+ DependencyType type;
+ QString version;
+};
+
struct IndexedVersion {
QVariant addonId;
QVariant fileId;
@@ -65,10 +74,10 @@ struct IndexedVersion {
QString hash;
bool is_preferred = true;
QString changelog;
+ QList<Dependency> dependencies;
// For internal use, not provided by APIs
bool is_currently_selected = false;
- QString custom_target_folder;
};
struct ExtraPackData {
@@ -113,8 +122,7 @@ struct IndexedPack {
if (!versionsLoaded)
return false;
- return std::any_of(versions.constBegin(), versions.constEnd(),
- [](auto const& v) { return v.is_currently_selected; });
+ return std::any_of(versions.constBegin(), versions.constEnd(), [](auto const& v) { return v.is_currently_selected; });
}
};
diff --git a/launcher/modplatform/ResourceAPI.h b/launcher/modplatform/ResourceAPI.h
index 34f33779..c23444b3 100644
--- a/launcher/modplatform/ResourceAPI.h
+++ b/launcher/modplatform/ResourceAPI.h
@@ -111,6 +111,24 @@ class ResourceAPI {
std::function<void(QJsonDocument&, ModPlatform::IndexedPack)> on_succeed;
};
+ struct DependencySearchArgs {
+ ModPlatform::Dependency dependency;
+ Version mcVersion;
+ ModLoaderTypes loader;
+
+ DependencySearchArgs(DependencySearchArgs const&) = default;
+ void operator=(DependencySearchArgs other)
+ {
+ dependency = other.dependency;
+ mcVersion = other.mcVersion;
+ loader = other.loader;
+ }
+ };
+
+ struct DependencySearchCallbacks {
+ std::function<void(QJsonDocument&, const ModPlatform::Dependency&)> on_succeed;
+ };
+
public:
/** Gets a list of available sorting methods for this API. */
[[nodiscard]] virtual auto getSortingMethods() const -> QList<SortingMethod> = 0;
@@ -143,6 +161,12 @@ class ResourceAPI {
return nullptr;
}
+ [[nodiscard]] virtual Task::Ptr getDependencyVersion(DependencySearchArgs&&, DependencySearchCallbacks&&) const
+ {
+ qWarning() << "TODO";
+ return nullptr;
+ }
+
static auto getModLoaderString(ModLoaderType type) -> const QString
{
switch (type) {
diff --git a/launcher/modplatform/flame/FlameAPI.h b/launcher/modplatform/flame/FlameAPI.h
index 5811d717..4ffc36d2 100644
--- a/launcher/modplatform/flame/FlameAPI.h
+++ b/launcher/modplatform/flame/FlameAPI.h
@@ -41,14 +41,15 @@ class FlameAPI : public NetworkResourceAPI {
return 4;
// TODO: remove this once Quilt drops official Fabric support
if (loaders & Quilt) // NOTE: Most if not all Fabric mods should work *currently*
- return 4; // Quilt would probably be 5
+ 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();
+ 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)));
@@ -73,7 +74,7 @@ class FlameAPI : public NetworkResourceAPI {
[[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.pack.addonId.toString())};
+ QString url{ QString("https://api.curseforge.com/v1/mods/%1/files?pageSize=10000&").arg(args.pack.addonId.toString()) };
QStringList get_parameters;
if (args.mcVersions.has_value())
@@ -83,4 +84,12 @@ class FlameAPI : public NetworkResourceAPI {
return url + get_parameters.join('&');
};
+
+ [[nodiscard]] std::optional<QString> getDependencyURL(DependencySearchArgs const& args) const override
+ {
+ return QString("https://api.curseforge.com/v1/mods/%1/files?pageSize=10000&gameVersion=%2&modLoaderType=%3")
+ .arg(args.dependency.addonId.toString())
+ .arg(args.mcVersion.toString())
+ .arg(getMappedModLoader(args.loader));
+ };
};
diff --git a/launcher/modplatform/flame/FlameModIndex.cpp b/launcher/modplatform/flame/FlameModIndex.cpp
index 7498e830..120bfc91 100644
--- a/launcher/modplatform/flame/FlameModIndex.cpp
+++ b/launcher/modplatform/flame/FlameModIndex.cpp
@@ -39,15 +39,15 @@ void FlameMod::loadURLs(ModPlatform::IndexedPack& pack, QJsonObject& obj)
auto links_obj = Json::ensureObject(obj, "links");
pack.extraData.issuesUrl = Json::ensureString(links_obj, "issuesUrl");
- if(pack.extraData.issuesUrl.endsWith('/'))
+ if (pack.extraData.issuesUrl.endsWith('/'))
pack.extraData.issuesUrl.chop(1);
pack.extraData.sourceUrl = Json::ensureString(links_obj, "sourceUrl");
- if(pack.extraData.sourceUrl.endsWith('/'))
+ if (pack.extraData.sourceUrl.endsWith('/'))
pack.extraData.sourceUrl.chop(1);
pack.extraData.wikiUrl = Json::ensureString(links_obj, "wikiUrl");
- if(pack.extraData.wikiUrl.endsWith('/'))
+ if (pack.extraData.wikiUrl.endsWith('/'))
pack.extraData.wikiUrl.chop(1);
if (!pack.extraData.body.isEmpty())
@@ -56,7 +56,7 @@ void FlameMod::loadURLs(ModPlatform::IndexedPack& pack, QJsonObject& obj)
void FlameMod::loadBody(ModPlatform::IndexedPack& pack, QJsonObject& obj)
{
- pack.extraData.body = api.getModDescription(pack.addonId.toInt());
+ pack.extraData.body = api.getModDescription(pack.addonId.toInt());
if (!pack.extraData.issuesUrl.isEmpty() || !pack.extraData.sourceUrl.isEmpty() || !pack.extraData.wikiUrl.isEmpty())
pack.extraDataLoaded = true;
@@ -64,12 +64,12 @@ void FlameMod::loadBody(ModPlatform::IndexedPack& pack, QJsonObject& obj)
static QString enumToString(int hash_algorithm)
{
- switch(hash_algorithm){
- default:
- case 1:
- return "sha1";
- case 2:
- return "md5";
+ switch (hash_algorithm) {
+ default:
+ case 1:
+ return "sha1";
+ case 2:
+ return "md5";
}
}
@@ -84,12 +84,12 @@ void FlameMod::loadIndexedPackVersions(ModPlatform::IndexedPack& pack,
for (auto versionIter : arr) {
auto obj = versionIter.toObject();
-
+
auto file = loadIndexedPackVersion(obj);
- if(!file.addonId.isValid())
+ if (!file.addonId.isValid())
file.addonId = pack.addonId;
- if(file.fileId.isValid()) // Heuristic to check if the returned value is valid
+ if (file.fileId.isValid()) // Heuristic to check if the returned value is valid
unsortedVersions.append(file);
}
@@ -136,8 +136,58 @@ auto FlameMod::loadIndexedPackVersion(QJsonObject& obj, bool load_changelog) ->
}
}
- if(load_changelog)
+ auto dependencies = Json::ensureArray(obj, "dependencies");
+ for (auto d : dependencies) {
+ auto dep = Json::ensureObject(d);
+ ModPlatform::Dependency dependency;
+ dependency.addonId = Json::requireInteger(dep, "modId");
+ switch (Json::requireInteger(dep, "relationType")) {
+ case 1: // EmbeddedLibrary
+ dependency.type = ModPlatform::DependencyType::EMBEDDED;
+ break;
+ case 2: // OptionalDependency
+ dependency.type = ModPlatform::DependencyType::OPTIONAL;
+ break;
+ case 3: // RequiredDependency
+ dependency.type = ModPlatform::DependencyType::REQUIRED;
+ break;
+ case 4: // Tool
+ dependency.type = ModPlatform::DependencyType::TOOL;
+ break;
+ case 5: // Incompatible
+ dependency.type = ModPlatform::DependencyType::INCOMPATIBLE;
+ break;
+ case 6: // Include
+ dependency.type = ModPlatform::DependencyType::INCLUDE;
+ break;
+ }
+ file.dependencies.append(dependency);
+ }
+
+ if (load_changelog)
file.changelog = api.getModFileChangelog(file.addonId.toInt(), file.fileId.toInt());
return file;
}
+
+ModPlatform::IndexedVersion FlameMod::loadDependencyVersions(const ModPlatform::Dependency& m, QJsonArray& arr)
+{
+ QVector<ModPlatform::IndexedVersion> unsortedVersions;
+ for (auto versionIter : arr) {
+ auto obj = versionIter.toObject();
+
+ auto file = loadIndexedPackVersion(obj);
+ if (!file.addonId.isValid())
+ file.addonId = m.addonId;
+
+ if (file.fileId.isValid()) // Heuristic to check if the returned value is valid
+ unsortedVersions.append(file);
+ }
+
+ auto orderSortPredicate = [](const ModPlatform::IndexedVersion& a, const ModPlatform::IndexedVersion& b) -> bool {
+ // dates are in RFC 3339 format
+ return a.date > b.date;
+ };
+ std::sort(unsortedVersions.begin(), unsortedVersions.end(), orderSortPredicate);
+ return unsortedVersions.front();
+};
diff --git a/launcher/modplatform/flame/FlameModIndex.h b/launcher/modplatform/flame/FlameModIndex.h
index 33c4a529..aa0d6f81 100644
--- a/launcher/modplatform/flame/FlameModIndex.h
+++ b/launcher/modplatform/flame/FlameModIndex.h
@@ -6,8 +6,8 @@
#include "modplatform/ModIndex.h"
-#include "BaseInstance.h"
#include <QNetworkAccessManager>
+#include "BaseInstance.h"
namespace FlameMod {
@@ -19,5 +19,5 @@ void loadIndexedPackVersions(ModPlatform::IndexedPack& pack,
const shared_qobject_ptr<QNetworkAccessManager>& network,
const BaseInstance* inst);
auto loadIndexedPackVersion(QJsonObject& obj, bool load_changelog = false) -> ModPlatform::IndexedVersion;
-
-} // namespace FlameMod
+auto loadDependencyVersions(const ModPlatform::Dependency& m, QJsonArray& arr) -> ModPlatform::IndexedVersion;
+} // namespace FlameMod \ No newline at end of file
diff --git a/launcher/modplatform/flame/FlamePackIndex.h b/launcher/modplatform/flame/FlamePackIndex.h
index 1ca0fc0e..b089b722 100644
--- a/launcher/modplatform/flame/FlamePackIndex.h
+++ b/launcher/modplatform/flame/FlamePackIndex.h
@@ -4,6 +4,7 @@
#include <QMetaType>
#include <QString>
#include <QVector>
+#include "modplatform/ModIndex.h"
namespace Flame {
@@ -27,8 +28,7 @@ struct ModpackExtra {
QString sourceUrl;
};
-struct IndexedPack
-{
+struct IndexedPack {
int addonId;
QString name;
QString description;
@@ -43,9 +43,9 @@ struct IndexedPack
ModpackExtra extra;
};
-void loadIndexedPack(IndexedPack & m, QJsonObject & obj);
+void loadIndexedPack(IndexedPack& m, QJsonObject& obj);
void loadIndexedInfo(IndexedPack&, QJsonObject&);
-void loadIndexedPackVersions(IndexedPack & m, QJsonArray & arr);
-}
+void loadIndexedPackVersions(IndexedPack& m, QJsonArray& arr);
+} // namespace Flame
Q_DECLARE_METATYPE(Flame::IndexedPack)
diff --git a/launcher/modplatform/helpers/NetworkResourceAPI.cpp b/launcher/modplatform/helpers/NetworkResourceAPI.cpp
index 010ac15e..9f95cde4 100644
--- a/launcher/modplatform/helpers/NetworkResourceAPI.cpp
+++ b/launcher/modplatform/helpers/NetworkResourceAPI.cpp
@@ -24,7 +24,7 @@ Task::Ptr NetworkResourceAPI::searchProjects(SearchArgs&& args, SearchCallbacks&
netJob->addNetAction(Net::Download::makeByteArray(QUrl(search_url), response));
- QObject::connect(netJob.get(), &NetJob::succeeded, [=]{
+ QObject::connect(netJob.get(), &NetJob::succeeded, [=] {
QJsonParseError parse_error{};
QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error);
if (parse_error.error != QJsonParseError::NoError) {
@@ -40,16 +40,14 @@ Task::Ptr NetworkResourceAPI::searchProjects(SearchArgs&& args, SearchCallbacks&
callbacks.on_succeed(doc);
});
- QObject::connect(netJob.get(), &NetJob::failed, [=](QString reason){
+ QObject::connect(netJob.get(), &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.get(), &NetJob::aborted, [=]{
- callbacks.on_abort();
+ callbacks.on_fail(reason, network_error_code);
});
+ QObject::connect(netJob.get(), &NetJob::aborted, [=] { callbacks.on_abort(); });
return netJob;
}
@@ -101,9 +99,7 @@ Task::Ptr NetworkResourceAPI::getProjectVersions(VersionSearchArgs&& args, Versi
callbacks.on_succeed(doc, args.pack);
});
- QObject::connect(netJob.get(), &NetJob::finished, [response] {
- delete response;
- });
+ QObject::connect(netJob.get(), &NetJob::finished, [response] { delete response; });
return netJob;
}
@@ -120,9 +116,38 @@ Task::Ptr NetworkResourceAPI::getProject(QString addonId, QByteArray* response)
netJob->addNetAction(Net::Download::makeByteArray(QUrl(project_url), response));
- QObject::connect(netJob.get(), &NetJob::finished, [response] {
- delete response;
- });
+ QObject::connect(netJob.get(), &NetJob::finished, [response] { delete response; });
return netJob;
}
+
+Task::Ptr NetworkResourceAPI::getDependencyVersion(DependencySearchArgs&& args, DependencySearchCallbacks&& callbacks) const
+{
+ auto versions_url_optional = getDependencyURL(args);
+ if (!versions_url_optional.has_value())
+ return nullptr;
+
+ auto versions_url = versions_url_optional.value();
+
+ auto netJob = makeShared<NetJob>(QString("%1::Dependency").arg(args.dependency.addonId.toString()), APPLICATION->network());
+ auto response = new QByteArray();
+
+ netJob->addNetAction(Net::Download::makeByteArray(versions_url, response));
+
+ QObject::connect(netJob.get(), &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.dependency);
+ });
+
+ QObject::connect(netJob.get(), &NetJob::finished, [response] { delete response; });
+
+ return netJob;
+};
diff --git a/launcher/modplatform/helpers/NetworkResourceAPI.h b/launcher/modplatform/helpers/NetworkResourceAPI.h
index 94813bec..93865713 100644
--- a/launcher/modplatform/helpers/NetworkResourceAPI.h
+++ b/launcher/modplatform/helpers/NetworkResourceAPI.h
@@ -14,9 +14,11 @@ class NetworkResourceAPI : public ResourceAPI {
Task::Ptr getProjectInfo(ProjectInfoArgs&&, ProjectInfoCallbacks&&) const override;
Task::Ptr getProjectVersions(VersionSearchArgs&&, VersionSearchCallbacks&&) const override;
+ Task::Ptr getDependencyVersion(DependencySearchArgs&&, DependencySearchCallbacks&&) 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;
+ [[nodiscard]] virtual auto getDependencyURL(DependencySearchArgs const& args) const -> std::optional<QString> = 0;
};
diff --git a/launcher/modplatform/modrinth/ModrinthAPI.h b/launcher/modplatform/modrinth/ModrinthAPI.h
index b91ac5c1..95722ccb 100644
--- a/launcher/modplatform/modrinth/ModrinthAPI.h
+++ b/launcher/modplatform/modrinth/ModrinthAPI.h
@@ -12,13 +12,9 @@
class ModrinthAPI : public NetworkResourceAPI {
public:
- auto currentVersion(QString hash,
- QString hash_format,
- QByteArray* response) -> Task::Ptr;
+ auto currentVersion(QString hash, QString hash_format, QByteArray* response) -> Task::Ptr;
- auto currentVersions(const QStringList& hashes,
- QString hash_format,
- QByteArray* response) -> Task::Ptr;
+ auto currentVersions(const QStringList& hashes, QString hash_format, QByteArray* response) -> Task::Ptr;
auto latestVersion(QString hash,
QString hash_format,
@@ -28,8 +24,8 @@ class ModrinthAPI : public NetworkResourceAPI {
auto latestVersions(const QStringList& hashes,
QString hash_format,
- std::optional<std::list<Version>> mcVersions,
- std::optional<ModLoaderTypes> loaders,
+ std::optional<std::list<Version>> mcVersions,
+ std::optional<ModLoaderTypes> loaders,
QByteArray* response) -> Task::Ptr;
Task::Ptr getProjects(QStringList addonIds, QByteArray* response) const override;
@@ -42,7 +38,7 @@ class ModrinthAPI : public NetworkResourceAPI {
static auto getModLoaderStrings(const ModLoaderTypes types) -> const QStringList
{
QStringList l;
- for (auto loader : {Forge, Fabric, Quilt}) {
+ for (auto loader : { Forge, Fabric, Quilt }) {
if (types & loader) {
l << getModLoaderString(loader);
}
@@ -55,8 +51,7 @@ class ModrinthAPI : public NetworkResourceAPI {
static auto getModLoaderFilters(ModLoaderTypes types) -> const QString
{
QStringList l;
- for (auto loader : getModLoaderStrings(types))
- {
+ for (auto loader : getModLoaderStrings(types)) {
l << QString("\"categories:%1\"").arg(loader);
}
return l.join(',');
@@ -139,16 +134,22 @@ class ModrinthAPI : public NetworkResourceAPI {
auto getGameVersionsArray(std::list<Version> mcVersions) const -> QString
{
QString s;
- for(auto& ver : mcVersions){
+ for (auto& ver : mcVersions) {
s += QString("\"versions:%1\",").arg(ver.toString());
}
- s.remove(s.length() - 1, 1); //remove last comma
+ s.remove(s.length() - 1, 1); // remove last comma
return s.isEmpty() ? QString() : s;
}
- inline auto validateModLoaders(ModLoaderTypes loaders) const -> bool
- {
- return loaders & (Forge | Fabric | Quilt);
- }
+ inline auto validateModLoaders(ModLoaderTypes loaders) const -> bool { return loaders & (Forge | Fabric | Quilt); }
+ [[nodiscard]] std::optional<QString> getDependencyURL(DependencySearchArgs const& args) const override
+ {
+ return args.dependency.version.length() != 0 ? QString("%1/version/%2").arg(BuildConfig.MODRINTH_PROD_URL, args.dependency.version)
+ : QString("%1/project/%2/version?game_versions=[\"%3\"]&loaders=[\"%4\"]")
+ .arg(BuildConfig.MODRINTH_PROD_URL)
+ .arg(args.dependency.addonId.toString())
+ .arg(args.mcVersion.toString())
+ .arg(getModLoaderStrings(args.loader).join("\",\""));
+ };
};
diff --git a/launcher/modplatform/modrinth/ModrinthPackIndex.cpp b/launcher/modplatform/modrinth/ModrinthPackIndex.cpp
index 7ade131e..879260a3 100644
--- a/launcher/modplatform/modrinth/ModrinthPackIndex.cpp
+++ b/launcher/modplatform/modrinth/ModrinthPackIndex.cpp
@@ -22,7 +22,7 @@
#include "Json.h"
#include "minecraft/MinecraftInstance.h"
#include "minecraft/PackProfile.h"
-#include "net/NetJob.h"
+#include "modplatform/ModIndex.h"
static ModrinthAPI api;
static ModPlatform::ProviderCapabilities ProviderCaps;
@@ -140,6 +140,26 @@ auto Modrinth::loadIndexedPackVersion(QJsonObject& obj, QString preferred_hash_t
file.version_number = Json::requireString(obj, "version_number");
file.changelog = Json::requireString(obj, "changelog");
+ auto dependencies = Json::ensureArray(obj, "dependencies");
+ for (auto d : dependencies) {
+ auto dep = Json::ensureObject(d);
+ ModPlatform::Dependency dependency;
+ dependency.addonId = Json::requireString(dep, "project_id");
+ dependency.version = Json::ensureString(dep, "version_id");
+ auto depType = Json::requireString(dep, "dependency_type");
+
+ if (depType == "required")
+ dependency.type = ModPlatform::DependencyType::REQUIRED;
+ else if (depType == "optional")
+ dependency.type = ModPlatform::DependencyType::OPTIONAL;
+ else if (depType == "incompatible")
+ dependency.type = ModPlatform::DependencyType::INCOMPATIBLE;
+ else if (depType == "embedded")
+ dependency.type = ModPlatform::DependencyType::EMBEDDED;
+
+ file.dependencies.append(dependency);
+ }
+
auto files = Json::requireArray(obj, "files");
int i = 0;
@@ -195,3 +215,22 @@ auto Modrinth::loadIndexedPackVersion(QJsonObject& obj, QString preferred_hash_t
return {};
}
+
+auto Modrinth::loadDependencyVersions(const ModPlatform::Dependency& m, QJsonArray& arr) -> ModPlatform::IndexedVersion
+{
+ QVector<ModPlatform::IndexedVersion> unsortedVersions;
+
+ for (auto versionIter : arr) {
+ auto obj = versionIter.toObject();
+ auto file = loadIndexedPackVersion(obj);
+
+ if (file.fileId.isValid()) // Heuristic to check if the returned value is valid
+ unsortedVersions.append(file);
+ }
+ auto orderSortPredicate = [](const ModPlatform::IndexedVersion& a, const ModPlatform::IndexedVersion& b) -> bool {
+ // dates are in RFC 3339 format
+ return a.date > b.date;
+ };
+ std::sort(unsortedVersions.begin(), unsortedVersions.end(), orderSortPredicate);
+ return unsortedVersions.length() != 0 ? unsortedVersions.front() : ModPlatform::IndexedVersion();
+} \ No newline at end of file
diff --git a/launcher/modplatform/modrinth/ModrinthPackIndex.h b/launcher/modplatform/modrinth/ModrinthPackIndex.h
index e73e4b18..a8d986c5 100644
--- a/launcher/modplatform/modrinth/ModrinthPackIndex.h
+++ b/launcher/modplatform/modrinth/ModrinthPackIndex.h
@@ -19,8 +19,8 @@
#include "modplatform/ModIndex.h"
-#include "BaseInstance.h"
#include <QNetworkAccessManager>
+#include "BaseInstance.h"
namespace Modrinth {
@@ -31,5 +31,6 @@ void loadIndexedPackVersions(ModPlatform::IndexedPack& pack,
const shared_qobject_ptr<QNetworkAccessManager>& network,
const BaseInstance* inst);
auto loadIndexedPackVersion(QJsonObject& obj, QString hash_type = "sha512", QString filename_prefer = "") -> ModPlatform::IndexedVersion;
+auto loadDependencyVersions(const ModPlatform::Dependency& m, QJsonArray& arr) -> ModPlatform::IndexedVersion;
} // namespace Modrinth
diff --git a/launcher/ui/dialogs/ResourceDownloadDialog.cpp b/launcher/ui/dialogs/ResourceDownloadDialog.cpp
index edb7d063..18106701 100644
--- a/launcher/ui/dialogs/ResourceDownloadDialog.cpp
+++ b/launcher/ui/dialogs/ResourceDownloadDialog.cpp
@@ -18,17 +18,24 @@
*/
#include "ResourceDownloadDialog.h"
+#include <QEventLoop>
+#include <QList>
#include <QPushButton>
+#include <algorithm>
#include "Application.h"
#include "ResourceDownloadTask.h"
#include "minecraft/mod/ModFolderModel.h"
#include "minecraft/mod/ResourcePackFolderModel.h"
-#include "minecraft/mod/TexturePackFolderModel.h"
#include "minecraft/mod/ShaderPackFolderModel.h"
+#include "minecraft/mod/TexturePackFolderModel.h"
+#include "minecraft/mod/tasks/GetModDependenciesTask.h"
+#include "modplatform/ModIndex.h"
+#include "ui/dialogs/CustomMessageBox.h"
+#include "ui/dialogs/ProgressDialog.h"
#include "ui/dialogs/ReviewMessageBox.h"
#include "ui/pages/modplatform/ResourcePage.h"
@@ -41,7 +48,10 @@
namespace ResourceDownload {
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)
+ : QDialog(parent)
+ , m_base_model(base_model)
+ , m_buttons(QDialogButtonBox::Help | QDialogButtonBox::Ok | QDialogButtonBox::Cancel)
+ , m_vertical_layout(this)
{
setObjectName(QStringLiteral("ResourceDownloadDialog"));
@@ -102,7 +112,8 @@ void ResourceDownloadDialog::initializeContainer()
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(resourcesString()));
+ OkButton->setToolTip(
+ tr("Opens a new popup to review your selected %1 and confirm your selection. Shortcut: Ctrl+Return").arg(resourcesString()));
connect(OkButton, &QPushButton::clicked, this, &ResourceDownloadDialog::confirm);
auto CancelButton = m_buttons.button(QDialogButtonBox::Cancel);
@@ -112,23 +123,71 @@ void ResourceDownloadDialog::connectButtons()
connect(HelpButton, &QPushButton::clicked, m_container, &PageContainer::help);
}
-void ResourceDownloadDialog::confirm()
+static ModPlatform::ProviderCapabilities ProviderCaps;
+
+QStringList getReqiredBy(QList<ResourceDownloadDialog::DownloadTaskPtr> tasks, QVariant addonId)
{
- auto keys = m_selected.keys();
- keys.sort(Qt::CaseInsensitive);
+ auto req = QStringList();
+ for (auto& task : tasks) {
+ auto deps = task->getVersion().dependencies;
+ if (auto dep = std::find_if(deps.begin(), deps.end(),
+ [addonId](const ModPlatform::Dependency& d) {
+ return d.addonId == addonId && d.type == ModPlatform::DependencyType::REQUIRED;
+ });
+ dep != deps.end()) {
+ req.append(task->getName());
+ }
+ }
+ return req;
+}
+void ResourceDownloadDialog::confirm()
+{
auto confirm_dialog = ReviewMessageBox::create(this, tr("Confirm %1 to download").arg(resourcesString()));
confirm_dialog->retranslateUi(resourcesString());
- for (auto& task : keys) {
- auto selected = m_selected.constFind(task).value();
- confirm_dialog->appendResource({ task, selected->getFilename(), selected->getCustomPath() });
+ if (auto task = getModDependenciesTask(); task) {
+ connect(task.get(), &Task::failed, this,
+ [&](QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->exec(); });
+
+ connect(task.get(), &Task::succeeded, this, [&]() {
+ QStringList warnings = task->warnings();
+ if (warnings.count()) {
+ CustomMessageBox::selectable(this, tr("Warnings"), warnings.join('\n'), QMessageBox::Warning)->exec();
+ }
+ });
+
+ // Check for updates
+ ProgressDialog progress_dialog(this);
+ progress_dialog.setSkipButton(true, tr("Abort"));
+ progress_dialog.setWindowTitle(tr("Checking for dependencies..."));
+ auto ret = progress_dialog.execWithTask(task.get());
+
+ // If the dialog was skipped / some download error happened
+ if (ret == QDialog::DialogCode::Rejected) {
+ QMetaObject::invokeMethod(this, "reject", Qt::QueuedConnection);
+ return;
+ } else
+ for (auto dep : task->getDependecies()) {
+ addResource(dep->pack, dep->version);
+ }
+ }
+
+ auto selected = getTasks();
+ std::sort(selected.begin(), selected.end(), [](const DownloadTaskPtr& a, const DownloadTaskPtr& b) {
+ return QString::compare(a->getName(), b->getName(), Qt::CaseInsensitive) < 0;
+ });
+ for (auto& task : selected) {
+ confirm_dialog->appendResource({ task->getName(), task->getFilename(), task->getCustomPath(),
+ ProviderCaps.name(task->getProvider()), getReqiredBy(selected, task->getPack().addonId) });
}
if (confirm_dialog->exec()) {
auto deselected = confirm_dialog->deselectedResources();
- for (auto name : deselected) {
- m_selected.remove(name);
+ for (auto page : m_container->getPages()) {
+ auto res = static_cast<ResourcePage*>(page);
+ for (auto name : deselected)
+ res->removeResourceFromPage(name);
}
this->accept();
@@ -145,46 +204,39 @@ ResourcePage* ResourceDownloadDialog::getSelectedPage()
return m_selectedPage;
}
-void ResourceDownloadDialog::addResource(ModPlatform::IndexedPack& pack, ModPlatform::IndexedVersion& ver, bool is_indexed)
+void ResourceDownloadDialog::addResource(ModPlatform::IndexedPack& pack, ModPlatform::IndexedVersion& ver)
{
- removeResource(pack, ver);
-
- ver.is_currently_selected = true;
- m_selected.insert(pack.name, makeShared<ResourceDownloadTask>(pack, ver, getBaseModel(), is_indexed));
-
- m_buttons.button(QDialogButtonBox::Ok)->setEnabled(!m_selected.isEmpty());
+ removeResource(pack.name);
+ m_selectedPage->addResourceToPage(pack, ver, getBaseModel());
+ setButtonStatus();
}
-static ModPlatform::IndexedVersion& getVersionWithID(ModPlatform::IndexedPack& pack, QVariant id)
+void ResourceDownloadDialog::removeResource(const QString& pack_name)
{
- Q_ASSERT(pack.versionsLoaded);
- auto it = std::find_if(pack.versions.begin(), pack.versions.end(), [id](auto const& v) { return v.fileId == id; });
- Q_ASSERT(it != pack.versions.end());
- return *it;
+ for (auto page : m_container->getPages()) {
+ static_cast<ResourcePage*>(page)->removeResourceFromPage(pack_name);
+ }
+ setButtonStatus();
}
-void ResourceDownloadDialog::removeResource(ModPlatform::IndexedPack& pack, ModPlatform::IndexedVersion& ver)
+void ResourceDownloadDialog::setButtonStatus()
{
- if (auto selected_task_it = m_selected.find(pack.name); selected_task_it != m_selected.end()) {
- auto selected_task = *selected_task_it;
- auto old_version_id = selected_task->getVersionID();
-
- // If the new and old version IDs don't match, search for the old one and deselect it.
- if (ver.fileId != old_version_id)
- getVersionWithID(pack, old_version_id).is_currently_selected = false;
+ auto selected = false;
+ for (auto page : m_container->getPages()) {
+ auto res = static_cast<ResourcePage*>(page);
+ selected = selected || res->hasSelectedPacks();
}
-
- // Deselect the new version too, since all versions of that pack got removed.
- ver.is_currently_selected = false;
-
- m_selected.remove(pack.name);
-
- m_buttons.button(QDialogButtonBox::Ok)->setEnabled(!m_selected.isEmpty());
+ m_buttons.button(QDialogButtonBox::Ok)->setEnabled(selected);
}
const QList<ResourceDownloadDialog::DownloadTaskPtr> ResourceDownloadDialog::getTasks()
{
- return m_selected.values();
+ QList<DownloadTaskPtr> selected;
+ for (auto page : m_container->getPages()) {
+ auto res = static_cast<ResourcePage*>(page);
+ selected.append(res->selectedPacks());
+ }
+ return selected;
}
void ResourceDownloadDialog::selectedPageChanged(BasePage* previous, BasePage* selected)
@@ -205,8 +257,6 @@ void ResourceDownloadDialog::selectedPageChanged(BasePage* previous, BasePage* s
m_selectedPage->setSearchTerm(prev_page->getSearchTerm());
}
-
-
ModDownloadDialog::ModDownloadDialog(QWidget* parent, const std::shared_ptr<ModFolderModel>& mods, BaseInstance* instance)
: ResourceDownloadDialog(parent, mods), m_instance(instance)
{
@@ -232,6 +282,18 @@ QList<BasePage*> ModDownloadDialog::getPages()
return pages;
}
+GetModDependenciesTask::Ptr ModDownloadDialog::getModDependenciesTask()
+{
+ if (auto model = dynamic_cast<ModFolderModel*>(getBaseModel().get()); model) {
+ QList<std::shared_ptr<GetModDependenciesTask::PackDependency>> selectedVers;
+ for (auto& selected : getTasks()) {
+ selectedVers.append(std::make_shared<GetModDependenciesTask::PackDependency>(selected->getPack(), selected->getVersion()));
+ }
+
+ return makeShared<GetModDependenciesTask>(this, m_instance, model, selectedVers);
+ }
+ return nullptr;
+};
ResourcePackDownloadDialog::ResourcePackDownloadDialog(QWidget* parent,
const std::shared_ptr<ResourcePackFolderModel>& resource_packs,
@@ -258,7 +320,6 @@ QList<BasePage*> ResourcePackDownloadDialog::getPages()
return pages;
}
-
TexturePackDownloadDialog::TexturePackDownloadDialog(QWidget* parent,
const std::shared_ptr<TexturePackFolderModel>& resource_packs,
BaseInstance* instance)
@@ -284,7 +345,6 @@ QList<BasePage*> TexturePackDownloadDialog::getPages()
return pages;
}
-
ShaderPackDownloadDialog::ShaderPackDownloadDialog(QWidget* parent,
const std::shared_ptr<ShaderPackFolderModel>& shaders,
BaseInstance* instance)
diff --git a/launcher/ui/dialogs/ResourceDownloadDialog.h b/launcher/ui/dialogs/ResourceDownloadDialog.h
index 5678dc8b..08436a9f 100644
--- a/launcher/ui/dialogs/ResourceDownloadDialog.h
+++ b/launcher/ui/dialogs/ResourceDownloadDialog.h
@@ -25,6 +25,7 @@
#include <QLayout>
#include "QObjectPtr.h"
+#include "minecraft/mod/tasks/GetModDependenciesTask.h"
#include "modplatform/ModIndex.h"
#include "ui/pages/BasePageProvider.h"
@@ -62,8 +63,8 @@ class ResourceDownloadDialog : public QDialog, public BasePageProvider {
bool selectPage(QString pageId);
ResourcePage* getSelectedPage();
- void addResource(ModPlatform::IndexedPack&, ModPlatform::IndexedVersion&, bool is_indexed = false);
- void removeResource(ModPlatform::IndexedPack&, ModPlatform::IndexedVersion&);
+ void addResource(ModPlatform::IndexedPack&, ModPlatform::IndexedVersion&);
+ void removeResource(const QString&);
const QList<DownloadTaskPtr> getTasks();
[[nodiscard]] const std::shared_ptr<ResourceFolderModel> getBaseModel() const { return m_base_model; }
@@ -79,6 +80,9 @@ class ResourceDownloadDialog : public QDialog, public BasePageProvider {
protected:
[[nodiscard]] virtual QString geometrySaveKey() const { return ""; }
+ void setButtonStatus();
+
+ [[nodiscard]] virtual GetModDependenciesTask::Ptr getModDependenciesTask() { return nullptr; }
protected:
const std::shared_ptr<ResourceFolderModel> m_base_model;
@@ -88,12 +92,8 @@ class ResourceDownloadDialog : public QDialog, public BasePageProvider {
QDialogButtonBox m_buttons;
QVBoxLayout m_vertical_layout;
-
- QHash<QString, DownloadTaskPtr> m_selected;
};
-
-
class ModDownloadDialog final : public ResourceDownloadDialog {
Q_OBJECT
@@ -106,6 +106,7 @@ class ModDownloadDialog final : public ResourceDownloadDialog {
[[nodiscard]] QString geometrySaveKey() const override { return "ModDownloadGeometry"; }
QList<BasePage*> getPages() override;
+ GetModDependenciesTask::Ptr getModDependenciesTask() override;
private:
BaseInstance* m_instance;
@@ -135,8 +136,8 @@ class TexturePackDownloadDialog final : public ResourceDownloadDialog {
public:
explicit TexturePackDownloadDialog(QWidget* parent,
- const std::shared_ptr<TexturePackFolderModel>& resource_packs,
- BaseInstance* instance);
+ const std::shared_ptr<TexturePackFolderModel>& resource_packs,
+ BaseInstance* instance);
~TexturePackDownloadDialog() override = default;
//: String that gets appended to the texture pack download dialog title ("Download " + resourcesString())
@@ -153,9 +154,7 @@ class ShaderPackDownloadDialog final : public ResourceDownloadDialog {
Q_OBJECT
public:
- explicit ShaderPackDownloadDialog(QWidget* parent,
- const std::shared_ptr<ShaderPackFolderModel>& shader_packs,
- BaseInstance* instance);
+ explicit ShaderPackDownloadDialog(QWidget* parent, const std::shared_ptr<ShaderPackFolderModel>& shader_packs, BaseInstance* instance);
~ShaderPackDownloadDialog() override = default;
//: String that gets appended to the shader pack download dialog title ("Download " + resourcesString())
diff --git a/launcher/ui/dialogs/ReviewMessageBox.cpp b/launcher/ui/dialogs/ReviewMessageBox.cpp
index 7b2df278..7b33765f 100644
--- a/launcher/ui/dialogs/ReviewMessageBox.cpp
+++ b/launcher/ui/dialogs/ReviewMessageBox.cpp
@@ -40,7 +40,8 @@ void ReviewMessageBox::appendResource(ResourceInformation&& info)
auto filenameItem = new QTreeWidgetItem(itemTop);
filenameItem->setText(0, tr("Filename: %1").arg(info.filename));
- itemTop->insertChildren(0, { filenameItem });
+ auto childIndx = 0;
+ itemTop->insertChildren(childIndx++, { filenameItem });
if (!info.custom_file_path.isEmpty()) {
auto customPathItem = new QTreeWidgetItem(itemTop);
@@ -49,7 +50,31 @@ void ReviewMessageBox::appendResource(ResourceInformation&& info)
itemTop->insertChildren(1, { customPathItem });
itemTop->setIcon(1, QIcon(APPLICATION->getThemedIcon("status-yellow")));
- itemTop->setToolTip(1, tr("This file will be downloaded to a folder location different from the default, possibly due to its loader requiring it."));
+ itemTop->setToolTip(
+ childIndx++,
+ tr("This file will be downloaded to a folder location different from the default, possibly due to its loader requiring it."));
+ }
+
+ auto providerItem = new QTreeWidgetItem(itemTop);
+ providerItem->setText(0, tr("Provider: %1").arg(info.provider));
+
+ itemTop->insertChildren(childIndx++, { providerItem });
+
+ if (!info.required_by.isEmpty()) {
+ auto requiredByItem = new QTreeWidgetItem(itemTop);
+ if (info.required_by.length() == 1) {
+ requiredByItem->setText(0, tr("Required by: %1").arg(info.required_by.back()));
+ } else {
+ requiredByItem->setText(0, tr("Required by:"));
+ auto i = 0;
+ for (auto req : info.required_by) {
+ auto reqItem = new QTreeWidgetItem(requiredByItem);
+ reqItem->setText(0, req);
+ reqItem->insertChildren(i++, { reqItem });
+ }
+ }
+
+ itemTop->insertChildren(childIndx++, { requiredByItem });
}
ui->modTreeWidget->addTopLevelItem(itemTop);
diff --git a/launcher/ui/dialogs/ReviewMessageBox.h b/launcher/ui/dialogs/ReviewMessageBox.h
index 5ec2bc23..a520cc2a 100644
--- a/launcher/ui/dialogs/ReviewMessageBox.h
+++ b/launcher/ui/dialogs/ReviewMessageBox.h
@@ -13,9 +13,11 @@ class ReviewMessageBox : public QDialog {
static auto create(QWidget* parent, QString&& title, QString&& icon = "") -> ReviewMessageBox*;
using ResourceInformation = struct res_info {
- QString name;
- QString filename;
- QString custom_file_path {};
+ QString name;
+ QString filename;
+ QString custom_file_path{};
+ QString provider;
+ QStringList required_by;
};
void appendResource(ResourceInformation&& info);
diff --git a/launcher/ui/pages/modplatform/ModModel.h b/launcher/ui/pages/modplatform/ModModel.h
index 5d4a7785..f7563171 100644
--- a/launcher/ui/pages/modplatform/ModModel.h
+++ b/launcher/ui/pages/modplatform/ModModel.h
@@ -32,6 +32,7 @@ class ModModel : public ResourceModel {
void loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj) override = 0;
void loadExtraPackInfo(ModPlatform::IndexedPack& m, QJsonObject& obj) override = 0;
void loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr) override = 0;
+ virtual ModPlatform::IndexedVersion loadDependencyVersions(const ModPlatform::Dependency& m, QJsonArray& arr) = 0;
void setFilter(std::shared_ptr<ModFilterWidget::Filter> filter) { m_filter = filter; }
diff --git a/launcher/ui/pages/modplatform/ModPage.cpp b/launcher/ui/pages/modplatform/ModPage.cpp
index 04be43ad..a6186d89 100644
--- a/launcher/ui/pages/modplatform/ModPage.cpp
+++ b/launcher/ui/pages/modplatform/ModPage.cpp
@@ -55,8 +55,7 @@
namespace ResourceDownload {
-ModPage::ModPage(ModDownloadDialog* dialog, BaseInstance& instance)
- : ResourcePage(dialog, instance)
+ModPage::ModPage(ModDownloadDialog* dialog, BaseInstance& instance) : ResourcePage(dialog, instance)
{
connect(m_ui->searchButton, &QPushButton::clicked, this, &ModPage::triggerSearch);
connect(m_ui->resourceFilterButton, &QPushButton::clicked, this, &ModPage::filterMods);
@@ -75,12 +74,10 @@ void ModPage::setFilterWidget(unique_qobject_ptr<ModFilterWidget>& widget)
m_filter_widget->setInstance(&static_cast<MinecraftInstance&>(m_base_instance));
m_filter = m_filter_widget->getFilter();
- connect(m_filter_widget.get(), &ModFilterWidget::filterChanged, this, [&]{
- m_ui->searchButton->setStyleSheet("text-decoration: underline");
- });
- connect(m_filter_widget.get(), &ModFilterWidget::filterUnchanged, this, [&]{
- m_ui->searchButton->setStyleSheet("text-decoration: none");
- });
+ connect(m_filter_widget.get(), &ModFilterWidget::filterChanged, this,
+ [&] { m_ui->searchButton->setStyleSheet("text-decoration: underline"); });
+ connect(m_filter_widget.get(), &ModFilterWidget::filterUnchanged, this,
+ [&] { m_ui->searchButton->setStyleSheet("text-decoration: none"); });
}
/******** Callbacks to events in the UI (set up in the derived classes) ********/
@@ -128,8 +125,8 @@ void ModPage::updateVersionList()
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.
+ for (auto& mcVer : m_filter->versions) {
+ // NOTE: Flame doesn't care about loader, so passing it changes nothing.
if (validateVersion(version, mcVer.toString(), packProfile->getModLoaders())) {
valid = true;
break;
@@ -148,10 +145,12 @@ void ModPage::updateVersionList()
updateSelectionButton();
}
-void ModPage::addResourceToDialog(ModPlatform::IndexedPack& pack, ModPlatform::IndexedVersion& version)
+void ModPage::addResourceToPage(ModPlatform::IndexedPack& pack,
+ ModPlatform::IndexedVersion& version,
+ const std::shared_ptr<ResourceFolderModel> base_model)
{
bool is_indexed = !APPLICATION->settings()->get("ModMetadataDisabled").toBool();
- m_parent_dialog->addResource(pack, version, is_indexed);
+ m_model->addPack(pack, version, base_model, is_indexed);
}
} // namespace ResourceDownload
diff --git a/launcher/ui/pages/modplatform/ModPage.h b/launcher/ui/pages/modplatform/ModPage.h
index c3b58cd6..6ecf8a94 100644
--- a/launcher/ui/pages/modplatform/ModPage.h
+++ b/launcher/ui/pages/modplatform/ModPage.h
@@ -50,7 +50,7 @@ class ModPage : public ResourcePage {
[[nodiscard]] QMap<QString, QString> urlHandlers() const override;
- void addResourceToDialog(ModPlatform::IndexedPack&, ModPlatform::IndexedVersion&) override;
+ void addResourceToPage(ModPlatform::IndexedPack&, ModPlatform::IndexedVersion&, const std::shared_ptr<ResourceFolderModel>) override;
virtual auto validateVersion(ModPlatform::IndexedVersion& ver, QString mineVer, std::optional<ResourceAPI::ModLoaderTypes> loaders = {}) const -> bool = 0;
diff --git a/launcher/ui/pages/modplatform/ResourceModel.cpp b/launcher/ui/pages/modplatform/ResourceModel.cpp
index db7d26f8..056b28cc 100644
--- a/launcher/ui/pages/modplatform/ResourceModel.cpp
+++ b/launcher/ui/pages/modplatform/ResourceModel.cpp
@@ -6,9 +6,11 @@
#include <QCryptographicHash>
#include <QIcon>
+#include <QList>
#include <QMessageBox>
#include <QPixmapCache>
#include <QUrl>
+#include <algorithm>
#include "Application.h"
#include "BuildConfig.h"
@@ -335,7 +337,15 @@ void ResourceModel::searchRequestSucceeded(QJsonDocument& doc)
ModPlatform::IndexedPack pack;
try {
loadIndexedPack(pack, packObj);
- newList.append(pack);
+ if (auto sel = std::find_if(m_selected.begin(), m_selected.end(),
+ [&pack](const DownloadTaskPtr i) {
+ const auto ipack = i->getPack();
+ return ipack.provider == pack.provider && ipack.addonId == pack.addonId;
+ });
+ sel != m_selected.end()) {
+ newList.append(sel->get()->getPack());
+ } else
+ newList.append(pack);
} catch (const JSONValidationError& e) {
qWarning() << "Error while loading resource from " << debugName() << ": " << e.cause();
continue;
@@ -441,4 +451,39 @@ void ResourceModel::infoRequestSucceeded(QJsonDocument& doc, ModPlatform::Indexe
emit projectInfoUpdated();
}
+void ResourceModel::addPack(ModPlatform::IndexedPack& pack,
+ ModPlatform::IndexedVersion& version,
+ const std::shared_ptr<ResourceFolderModel> packs,
+ bool is_indexed,
+ QString custom_target_folder)
+{
+ version.is_currently_selected = true;
+ m_selected.append(makeShared<ResourceDownloadTask>(pack, version, packs, is_indexed, custom_target_folder));
+}
+
+void ResourceModel::removePack(const QString& rem)
+{
+ auto pred = [&rem](const DownloadTaskPtr i) { return rem == i->getName(); };
+#if QT_VERSION >= QT_VERSION_CHECK(6, 1, 0)
+ m_selected.removeIf(pred);
+#else
+ {
+ for (auto it = m_selected.begin(); it != m_selected.end();)
+ if (pred(*it))
+ it = m_selected.erase(it);
+ else
+ ++it;
+ }
+#endif
+ auto pack = std::find_if(m_packs.begin(), m_packs.end(), [&rem](const ModPlatform::IndexedPack& i) { return rem == i.name; });
+ if (pack == m_packs.end()) { // ignore it if is not in the current search
+ return;
+ }
+ if (!pack->versionsLoaded) {
+ return;
+ }
+ for (auto& ver : pack->versions)
+ ver.is_currently_selected = false;
+}
+
} // namespace ResourceDownload
diff --git a/launcher/ui/pages/modplatform/ResourceModel.h b/launcher/ui/pages/modplatform/ResourceModel.h
index 46a02d6e..735d1687 100644
--- a/launcher/ui/pages/modplatform/ResourceModel.h
+++ b/launcher/ui/pages/modplatform/ResourceModel.h
@@ -10,6 +10,7 @@
#include "QObjectPtr.h"
+#include "ResourceDownloadTask.h"
#include "modplatform/ResourceAPI.h"
#include "tasks/ConcurrentTask.h"
@@ -29,6 +30,8 @@ class ResourceModel : public QAbstractListModel {
Q_PROPERTY(QString search_term MEMBER m_search_term WRITE setSearchTerm)
public:
+ using DownloadTaskPtr = shared_qobject_ptr<ResourceDownloadTask>;
+
ResourceModel(ResourceAPI* api);
~ResourceModel() override;
@@ -80,6 +83,14 @@ class ResourceModel : public QAbstractListModel {
/** 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&);
+ void addPack(ModPlatform::IndexedPack& pack,
+ ModPlatform::IndexedVersion& version,
+ const std::shared_ptr<ResourceFolderModel> packs,
+ bool is_indexed = false,
+ QString custom_target_folder = {});
+ void removePack(const QString& rem);
+ QList<DownloadTaskPtr> selectedPacks() { return m_selected; }
+
protected:
/** Resets the model's data. */
void clearData();
@@ -124,6 +135,7 @@ class ResourceModel : public QAbstractListModel {
QSet<QUrl> m_failed_icon_actions;
QList<ModPlatform::IndexedPack> m_packs;
+ QList<DownloadTaskPtr> m_selected;
// 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?
diff --git a/launcher/ui/pages/modplatform/ResourcePage.cpp b/launcher/ui/pages/modplatform/ResourcePage.cpp
index bbd465bc..4ebdea56 100644
--- a/launcher/ui/pages/modplatform/ResourcePage.cpp
+++ b/launcher/ui/pages/modplatform/ResourcePage.cpp
@@ -37,6 +37,7 @@
*/
#include "ResourcePage.h"
+#include "modplatform/ModIndex.h"
#include "ui_ResourcePage.h"
#include <QDesktopServices>
@@ -311,9 +312,21 @@ void ResourcePage::addResourceToDialog(ModPlatform::IndexedPack& pack, ModPlatfo
m_parent_dialog->addResource(pack, version);
}
-void ResourcePage::removeResourceFromDialog(ModPlatform::IndexedPack& pack, ModPlatform::IndexedVersion& version)
+void ResourcePage::removeResourceFromDialog(const QString& pack_name)
{
- m_parent_dialog->removeResource(pack, version);
+ m_parent_dialog->removeResource(pack_name);
+}
+
+void ResourcePage::addResourceToPage(ModPlatform::IndexedPack& pack,
+ ModPlatform::IndexedVersion& ver,
+ const std::shared_ptr<ResourceFolderModel> base_model)
+{
+ m_model->addPack(pack, ver, base_model);
+}
+
+void ResourcePage::removeResourceFromPage(const QString& name)
+{
+ m_model->removePack(name);
}
void ResourcePage::onResourceSelected()
@@ -327,7 +340,7 @@ void ResourcePage::onResourceSelected()
auto& version = current_pack.versions[m_selected_version_index];
if (version.is_currently_selected)
- removeResourceFromDialog(current_pack, version);
+ removeResourceFromDialog(current_pack.name);
else
addResourceToDialog(current_pack, version);
diff --git a/launcher/ui/pages/modplatform/ResourcePage.h b/launcher/ui/pages/modplatform/ResourcePage.h
index 1896d53e..df68e6fd 100644
--- a/launcher/ui/pages/modplatform/ResourcePage.h
+++ b/launcher/ui/pages/modplatform/ResourcePage.h
@@ -7,10 +7,12 @@
#include <QTimer>
#include <QWidget>
+#include "ResourceDownloadTask.h"
#include "modplatform/ModIndex.h"
#include "modplatform/ResourceAPI.h"
#include "ui/pages/BasePage.h"
+#include "ui/pages/modplatform/ResourceModel.h"
#include "ui/widgets/ProgressWidget.h"
namespace Ui {
@@ -27,6 +29,7 @@ class ResourceModel;
class ResourcePage : public QWidget, public BasePage {
Q_OBJECT
public:
+ using DownloadTaskPtr = shared_qobject_ptr<ResourceDownloadTask>;
~ResourcePage() override;
/* Affects what the user sees */
@@ -72,12 +75,17 @@ class ResourcePage : public QWidget, public BasePage {
virtual void updateSelectionButton();
virtual void updateVersionList();
- virtual void addResourceToDialog(ModPlatform::IndexedPack&, ModPlatform::IndexedVersion&);
- virtual void removeResourceFromDialog(ModPlatform::IndexedPack&, ModPlatform::IndexedVersion&);
+ void addResourceToDialog(ModPlatform::IndexedPack&, ModPlatform::IndexedVersion&);
+ void removeResourceFromDialog(const QString& pack_name);
+ virtual void removeResourceFromPage(const QString& name);
+ virtual void addResourceToPage(ModPlatform::IndexedPack&, ModPlatform::IndexedVersion&, const std::shared_ptr<ResourceFolderModel>);
+
+ QList<DownloadTaskPtr> selectedPacks() { return m_model->selectedPacks(); }
+ bool hasSelectedPacks() { return !(m_model->selectedPacks().isEmpty()); }
protected slots:
virtual void triggerSearch() {}
-
+
void onSelectionChanged(QModelIndex first, QModelIndex second);
void onVersionSelectionChanged(QString data);
void onResourceSelected();
diff --git a/launcher/ui/pages/modplatform/ShaderPackPage.cpp b/launcher/ui/pages/modplatform/ShaderPackPage.cpp
index 251c07e7..c7a69418 100644
--- a/launcher/ui/pages/modplatform/ShaderPackPage.cpp
+++ b/launcher/ui/pages/modplatform/ShaderPackPage.cpp
@@ -13,8 +13,7 @@
namespace ResourceDownload {
-ShaderPackResourcePage::ShaderPackResourcePage(ShaderPackDownloadDialog* dialog, BaseInstance& instance)
- : ResourcePage(dialog, instance)
+ShaderPackResourcePage::ShaderPackResourcePage(ShaderPackDownloadDialog* dialog, BaseInstance& instance) : ResourcePage(dialog, instance)
{
connect(m_ui->searchButton, &QPushButton::clicked, this, &ShaderPackResourcePage::triggerSearch);
connect(m_ui->packView, &QListView::doubleClicked, this, &ShaderPackResourcePage::onResourceSelected);
@@ -38,17 +37,20 @@ QMap<QString, QString> ShaderPackResourcePage::urlHandlers() const
{
QMap<QString, QString> map;
map.insert(QRegularExpression::anchoredPattern("(?:www\\.)?modrinth\\.com\\/shaders\\/([^\\/]+)\\/?"), "modrinth");
- map.insert(QRegularExpression::anchoredPattern("(?:www\\.)?curseforge\\.com\\/minecraft\\/customization\\/([^\\/]+)\\/?"), "curseforge");
+ map.insert(QRegularExpression::anchoredPattern("(?:www\\.)?curseforge\\.com\\/minecraft\\/customization\\/([^\\/]+)\\/?"),
+ "curseforge");
map.insert(QRegularExpression::anchoredPattern("minecraft\\.curseforge\\.com\\/projects\\/([^\\/]+)\\/?"), "curseforge");
return map;
}
-void ShaderPackResourcePage::addResourceToDialog(ModPlatform::IndexedPack& pack, ModPlatform::IndexedVersion& version)
+void ShaderPackResourcePage::addResourceToPage(ModPlatform::IndexedPack& pack,
+ ModPlatform::IndexedVersion& version,
+ const std::shared_ptr<ResourceFolderModel> base_model)
{
+ QString custom_target_folder;
if (version.loaders.contains(QStringLiteral("canvas")))
- version.custom_target_folder = QStringLiteral("resourcepacks");
-
- m_parent_dialog->addResource(pack, version);
+ custom_target_folder = QStringLiteral("resourcepacks");
+ m_model->addPack(pack, version, base_model, false, custom_target_folder);
}
} // namespace ResourceDownload
diff --git a/launcher/ui/pages/modplatform/ShaderPackPage.h b/launcher/ui/pages/modplatform/ShaderPackPage.h
index 972419a8..8a293f74 100644
--- a/launcher/ui/pages/modplatform/ShaderPackPage.h
+++ b/launcher/ui/pages/modplatform/ShaderPackPage.h
@@ -40,7 +40,7 @@ class ShaderPackResourcePage : public ResourcePage {
[[nodiscard]] bool supportsFiltering() const override { return false; };
- void addResourceToDialog(ModPlatform::IndexedPack&, ModPlatform::IndexedVersion&) override;
+ void addResourceToPage(ModPlatform::IndexedPack&, ModPlatform::IndexedVersion&, const std::shared_ptr<ResourceFolderModel>) override;
[[nodiscard]] QMap<QString, QString> urlHandlers() const override;
diff --git a/launcher/ui/pages/modplatform/flame/FlameResourceModels.cpp b/launcher/ui/pages/modplatform/flame/FlameResourceModels.cpp
index e3d0bc14..b233a845 100644
--- a/launcher/ui/pages/modplatform/flame/FlameResourceModels.cpp
+++ b/launcher/ui/pages/modplatform/flame/FlameResourceModels.cpp
@@ -29,6 +29,11 @@ void FlameModModel::loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonAr
FlameMod::loadIndexedPackVersions(m, arr, APPLICATION->network(), &m_base_instance);
}
+auto FlameModModel::loadDependencyVersions(const ModPlatform::Dependency& m, QJsonArray& arr) -> ModPlatform::IndexedVersion
+{
+ return FlameMod::loadDependencyVersions(m, arr);
+};
+
auto FlameModModel::documentToArray(QJsonDocument& obj) const -> QJsonArray
{
return Json::ensureArray(obj.object(), "data");
@@ -81,7 +86,7 @@ void FlameTexturePackModel::loadIndexedPackVersions(ModPlatform::IndexedPack& m,
auto const& mc_versions = version.mcVersion;
if (std::any_of(mc_versions.constBegin(), mc_versions.constEnd(),
- [this](auto const& mc_version){ return Version(mc_version) <= maximumTexturePackVersion(); }))
+ [this](auto const& mc_version) { return Version(mc_version) <= maximumTexturePackVersion(); }))
filtered_versions.push_back(version);
}
diff --git a/launcher/ui/pages/modplatform/flame/FlameResourceModels.h b/launcher/ui/pages/modplatform/flame/FlameResourceModels.h
index 0252ac40..f3ef918a 100644
--- a/launcher/ui/pages/modplatform/flame/FlameResourceModels.h
+++ b/launcher/ui/pages/modplatform/flame/FlameResourceModels.h
@@ -24,6 +24,7 @@ class FlameModModel : public ModModel {
void loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj) override;
void loadExtraPackInfo(ModPlatform::IndexedPack& m, QJsonObject& obj) override;
void loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr) override;
+ auto loadDependencyVersions(const ModPlatform::Dependency& m, QJsonArray& arr) -> ModPlatform::IndexedVersion override;
auto documentToArray(QJsonDocument& obj) const -> QJsonArray override;
};
diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.cpp
index f5d1cc28..e9f09387 100644
--- a/launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.cpp
+++ b/launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.cpp
@@ -42,12 +42,17 @@ void ModrinthModModel::loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJso
::Modrinth::loadIndexedPackVersions(m, arr, APPLICATION->network(), &m_base_instance);
}
+auto ModrinthModModel::loadDependencyVersions(const ModPlatform::Dependency& m, QJsonArray& arr) -> ModPlatform::IndexedVersion
+{
+ return ::Modrinth::loadDependencyVersions(m, arr);
+};
+
auto ModrinthModModel::documentToArray(QJsonDocument& obj) const -> QJsonArray
{
return obj.object().value("hits").toArray();
}
-ModrinthResourcePackModel::ModrinthResourcePackModel(const BaseInstance& base) : ResourcePackResourceModel(base, new ModrinthAPI){}
+ModrinthResourcePackModel::ModrinthResourcePackModel(const BaseInstance& base) : ResourcePackResourceModel(base, new ModrinthAPI) {}
void ModrinthResourcePackModel::loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj)
{
@@ -69,7 +74,7 @@ auto ModrinthResourcePackModel::documentToArray(QJsonDocument& obj) const -> QJs
return obj.object().value("hits").toArray();
}
-ModrinthTexturePackModel::ModrinthTexturePackModel(const BaseInstance& base) : TexturePackResourceModel(base, new ModrinthAPI){}
+ModrinthTexturePackModel::ModrinthTexturePackModel(const BaseInstance& base) : TexturePackResourceModel(base, new ModrinthAPI) {}
void ModrinthTexturePackModel::loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj)
{
@@ -91,7 +96,7 @@ auto ModrinthTexturePackModel::documentToArray(QJsonDocument& obj) const -> QJso
return obj.object().value("hits").toArray();
}
-ModrinthShaderPackModel::ModrinthShaderPackModel(const BaseInstance& base) : ShaderPackResourceModel(base, new ModrinthAPI){}
+ModrinthShaderPackModel::ModrinthShaderPackModel(const BaseInstance& base) : ShaderPackResourceModel(base, new ModrinthAPI) {}
void ModrinthShaderPackModel::loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj)
{
diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.h b/launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.h
index b351b19b..6cd19c41 100644
--- a/launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.h
+++ b/launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.h
@@ -40,6 +40,7 @@ class ModrinthModModel : public ModModel {
void loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj) override;
void loadExtraPackInfo(ModPlatform::IndexedPack& m, QJsonObject& obj) override;
void loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr) override;
+ auto loadDependencyVersions(const ModPlatform::Dependency& m, QJsonArray& arr) -> ModPlatform::IndexedVersion override;
auto documentToArray(QJsonDocument& obj) const -> QJsonArray override;
};
diff --git a/launcher/ui/widgets/PageContainer.cpp b/launcher/ui/widgets/PageContainer.cpp
index 0a06a351..84a4e0de 100644
--- a/launcher/ui/widgets/PageContainer.cpp
+++ b/launcher/ui/widgets/PageContainer.cpp
@@ -135,6 +135,11 @@ BasePage* PageContainer::getPage(QString pageId)
return m_model->findPageEntryById(pageId);
}
+const QList<BasePage*> PageContainer::getPages() const
+{
+ return m_model->pages();
+}
+
void PageContainer::refreshContainer()
{
m_proxyModel->invalidate();
diff --git a/launcher/ui/widgets/PageContainer.h b/launcher/ui/widgets/PageContainer.h
index 97e294dc..ad74d43a 100644
--- a/launcher/ui/widgets/PageContainer.h
+++ b/launcher/ui/widgets/PageContainer.h
@@ -80,6 +80,7 @@ public:
virtual bool selectPage(QString pageId) override;
BasePage* getPage(QString pageId) override;
+ const QList<BasePage*> getPages() const;
void refreshContainer() override;
virtual void setParentContainer(BasePageContainer * container)