diff --git a/launcher/ResourceDownloadTask.h b/launcher/ResourceDownloadTask.h index 350c2edd..275ddbe1 100644 --- a/launcher/ResourceDownloadTask.h +++ b/launcher/ResourceDownloadTask.h @@ -32,6 +32,7 @@ class ResourceDownloadTask : public SequentialTask { public: explicit ResourceDownloadTask(ModPlatform::IndexedPack pack, ModPlatform::IndexedVersion version, const std::shared_ptr packs, bool is_indexed = true); const QString& getFilename() const { return m_pack_version.fileName; } + const QVariant& getVersionID() const { return m_pack_version.fileId; } private: ModPlatform::IndexedPack m_pack; diff --git a/launcher/modplatform/ModIndex.h b/launcher/modplatform/ModIndex.h index f65a6a4b..cd40a6ba 100644 --- a/launcher/modplatform/ModIndex.h +++ b/launcher/modplatform/ModIndex.h @@ -65,6 +65,9 @@ struct IndexedVersion { QString hash; bool is_preferred = true; QString changelog; + + // For internal use, not provided by APIs + bool is_currently_selected = false; }; struct ExtraPackData { @@ -95,6 +98,23 @@ struct IndexedPack { // Don't load by default, since some modplatform don't have that info bool extraDataLoaded = true; ExtraPackData extraData; + + // For internal use, not provided by APIs + [[nodiscard]] bool isVersionSelected(size_t index) const + { + if (!versionsLoaded) + return false; + + return versions.at(index).is_currently_selected; + } + [[nodiscard]] bool isAnyVersionSelected() const + { + if (!versionsLoaded) + return false; + + return std::any_of(versions.constBegin(), versions.constEnd(), + [](auto const& v) { return v.is_currently_selected; }); + } }; } // namespace ModPlatform diff --git a/launcher/modplatform/ResourceAPI.h b/launcher/modplatform/ResourceAPI.h index d18a2caa..49aac712 100644 --- a/launcher/modplatform/ResourceAPI.h +++ b/launcher/modplatform/ResourceAPI.h @@ -69,13 +69,20 @@ class ResourceAPI { }; struct VersionSearchArgs { - QString addonId; + ModPlatform::IndexedPack& pack; std::optional > mcVersions; std::optional loaders; + + void operator=(VersionSearchArgs other) + { + pack = other.pack; + mcVersions = other.mcVersions; + loaders = other.loaders; + } }; struct VersionSearchCallbacks { - std::function on_succeed; + std::function on_succeed; }; struct ProjectInfoArgs { diff --git a/launcher/modplatform/flame/FlameAPI.cpp b/launcher/modplatform/flame/FlameAPI.cpp index ae401399..89249c41 100644 --- a/launcher/modplatform/flame/FlameAPI.cpp +++ b/launcher/modplatform/flame/FlameAPI.cpp @@ -114,7 +114,7 @@ auto FlameAPI::getLatestVersion(VersionSearchArgs&& args) -> ModPlatform::Indexe QEventLoop loop; - auto netJob = new NetJob(QString("Flame::GetLatestVersion(%1)").arg(args.addonId), APPLICATION->network()); + auto netJob = new NetJob(QString("Flame::GetLatestVersion(%1)").arg(args.pack.name), APPLICATION->network()); auto response = new QByteArray(); ModPlatform::IndexedVersion ver; diff --git a/launcher/modplatform/flame/FlameAPI.h b/launcher/modplatform/flame/FlameAPI.h index 114a2716..f3cc0bbf 100644 --- a/launcher/modplatform/flame/FlameAPI.h +++ b/launcher/modplatform/flame/FlameAPI.h @@ -78,7 +78,7 @@ class FlameAPI : public NetworkResourceAPI { [[nodiscard]] std::optional getVersionsURL(VersionSearchArgs const& args) const override { - QString url{QString("https://api.curseforge.com/v1/mods/%1/files?pageSize=10000&").arg(args.addonId)}; + QString url{QString("https://api.curseforge.com/v1/mods/%1/files?pageSize=10000&").arg(args.pack.addonId.toString())}; QStringList get_parameters; if (args.mcVersions.has_value()) diff --git a/launcher/modplatform/flame/FlameCheckUpdate.cpp b/launcher/modplatform/flame/FlameCheckUpdate.cpp index 285fa49f..7aee4f4c 100644 --- a/launcher/modplatform/flame/FlameCheckUpdate.cpp +++ b/launcher/modplatform/flame/FlameCheckUpdate.cpp @@ -129,7 +129,8 @@ void FlameCheckUpdate::executeTask() setStatus(tr("Getting API response from CurseForge for '%1'...").arg(mod->name())); setProgress(i++, m_mods.size()); - auto latest_ver = api.getLatestVersion({ mod->metadata()->project_id.toString(), m_game_versions, m_loaders }); + ModPlatform::IndexedPack pack{ mod->metadata()->project_id.toString() }; + auto latest_ver = api.getLatestVersion({ pack, m_game_versions, m_loaders }); // Check if we were aborted while getting the latest version if (m_was_aborted) { diff --git a/launcher/modplatform/flame/FlameModIndex.cpp b/launcher/modplatform/flame/FlameModIndex.cpp index 617b98ce..7498e830 100644 --- a/launcher/modplatform/flame/FlameModIndex.cpp +++ b/launcher/modplatform/flame/FlameModIndex.cpp @@ -76,10 +76,10 @@ static QString enumToString(int hash_algorithm) void FlameMod::loadIndexedPackVersions(ModPlatform::IndexedPack& pack, QJsonArray& arr, const shared_qobject_ptr& network, - BaseInstance* inst) + const BaseInstance* inst) { QVector unsortedVersions; - auto profile = (dynamic_cast(inst))->getPackProfile(); + auto profile = (dynamic_cast(inst))->getPackProfile(); QString mcVersion = profile->getComponentVersion("net.minecraft"); for (auto versionIter : arr) { diff --git a/launcher/modplatform/flame/FlameModIndex.h b/launcher/modplatform/flame/FlameModIndex.h index db63cdbb..33c4a529 100644 --- a/launcher/modplatform/flame/FlameModIndex.h +++ b/launcher/modplatform/flame/FlameModIndex.h @@ -17,7 +17,7 @@ void loadBody(ModPlatform::IndexedPack& m, QJsonObject& obj); void loadIndexedPackVersions(ModPlatform::IndexedPack& pack, QJsonArray& arr, const shared_qobject_ptr& network, - BaseInstance* inst); + const BaseInstance* inst); auto loadIndexedPackVersion(QJsonObject& obj, bool load_changelog = false) -> ModPlatform::IndexedVersion; } // namespace FlameMod diff --git a/launcher/modplatform/helpers/NetworkResourceAPI.cpp b/launcher/modplatform/helpers/NetworkResourceAPI.cpp index eb17008c..77b085c0 100644 --- a/launcher/modplatform/helpers/NetworkResourceAPI.cpp +++ b/launcher/modplatform/helpers/NetworkResourceAPI.cpp @@ -79,7 +79,7 @@ NetJob::Ptr NetworkResourceAPI::getProjectVersions(VersionSearchArgs&& args, Ver auto versions_url = versions_url_optional.value(); - auto netJob = new NetJob(QString("%1::Versions").arg(args.addonId), APPLICATION->network()); + auto netJob = new NetJob(QString("%1::Versions").arg(args.pack.name), APPLICATION->network()); auto response = new QByteArray(); netJob->addNetAction(Net::Download::makeByteArray(versions_url, response)); @@ -94,7 +94,7 @@ NetJob::Ptr NetworkResourceAPI::getProjectVersions(VersionSearchArgs&& args, Ver return; } - callbacks.on_succeed(doc, args.addonId); + callbacks.on_succeed(doc, args.pack); }); QObject::connect(netJob, &NetJob::finished, [response] { diff --git a/launcher/modplatform/modrinth/ModrinthAPI.h b/launcher/modplatform/modrinth/ModrinthAPI.h index bd84fb54..ec38d9ee 100644 --- a/launcher/modplatform/modrinth/ModrinthAPI.h +++ b/launcher/modplatform/modrinth/ModrinthAPI.h @@ -141,7 +141,7 @@ class ModrinthAPI : public NetworkResourceAPI { get_arguments.append(QString("loaders=[\"%1\"]").arg(getModLoaderStrings(args.loaders.value()).join("\",\""))); return QString("%1/project/%2/version%3%4") - .arg(BuildConfig.MODRINTH_PROD_URL, args.addonId, get_arguments.isEmpty() ? "" : "?", get_arguments.join('&')); + .arg(BuildConfig.MODRINTH_PROD_URL, args.pack.addonId.toString(), get_arguments.isEmpty() ? "" : "?", get_arguments.join('&')); }; auto getGameVersionsArray(std::list mcVersions) const -> QString diff --git a/launcher/modplatform/modrinth/ModrinthPackIndex.cpp b/launcher/modplatform/modrinth/ModrinthPackIndex.cpp index a0161089..f270f470 100644 --- a/launcher/modplatform/modrinth/ModrinthPackIndex.cpp +++ b/launcher/modplatform/modrinth/ModrinthPackIndex.cpp @@ -95,10 +95,10 @@ void Modrinth::loadExtraPackData(ModPlatform::IndexedPack& pack, QJsonObject& ob void Modrinth::loadIndexedPackVersions(ModPlatform::IndexedPack& pack, QJsonArray& arr, const shared_qobject_ptr& network, - BaseInstance* inst) + const BaseInstance* inst) { QVector unsortedVersions; - QString mcVersion = (static_cast(inst))->getPackProfile()->getComponentVersion("net.minecraft"); + QString mcVersion = (static_cast(inst))->getPackProfile()->getComponentVersion("net.minecraft"); for (auto versionIter : arr) { auto obj = versionIter.toObject(); diff --git a/launcher/modplatform/modrinth/ModrinthPackIndex.h b/launcher/modplatform/modrinth/ModrinthPackIndex.h index 31881414..e73e4b18 100644 --- a/launcher/modplatform/modrinth/ModrinthPackIndex.h +++ b/launcher/modplatform/modrinth/ModrinthPackIndex.h @@ -29,7 +29,7 @@ void loadExtraPackData(ModPlatform::IndexedPack& m, QJsonObject& obj); void loadIndexedPackVersions(ModPlatform::IndexedPack& pack, QJsonArray& arr, const shared_qobject_ptr& network, - BaseInstance* inst); + const BaseInstance* inst); auto loadIndexedPackVersion(QJsonObject& obj, QString hash_type = "sha512", QString filename_prefer = "") -> ModPlatform::IndexedVersion; } // namespace Modrinth diff --git a/launcher/ui/dialogs/ResourceDownloadDialog.cpp b/launcher/ui/dialogs/ResourceDownloadDialog.cpp index 523a1636..2eb85928 100644 --- a/launcher/ui/dialogs/ResourceDownloadDialog.cpp +++ b/launcher/ui/dialogs/ResourceDownloadDialog.cpp @@ -141,38 +141,44 @@ ResourcePage* ResourceDownloadDialog::getSelectedPage() return m_selectedPage; } -void ResourceDownloadDialog::addResource(QString name, ResourceDownloadTask* task) +void ResourceDownloadDialog::addResource(ModPlatform::IndexedPack& pack, ModPlatform::IndexedVersion& ver, bool is_indexed) { - removeResource(name); - m_selected.insert(name, task); + removeResource(pack, ver); + + ver.is_currently_selected = true; + m_selected.insert(pack.name, new ResourceDownloadTask(pack, ver, getBaseModel(), is_indexed)); m_buttons.button(QDialogButtonBox::Ok)->setEnabled(!m_selected.isEmpty()); } -void ResourceDownloadDialog::removeResource(QString name) +static ModPlatform::IndexedVersion& getVersionWithID(ModPlatform::IndexedPack& pack, QVariant id) { - if (m_selected.contains(name)) - m_selected.find(name).value()->deleteLater(); - m_selected.remove(name); + 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; +} + +void ResourceDownloadDialog::removeResource(ModPlatform::IndexedPack& pack, ModPlatform::IndexedVersion& ver) +{ + 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; + } + + // 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()); } -bool ResourceDownloadDialog::isSelected(QString name, QString filename) const -{ - auto iter = m_selected.constFind(name); - if (iter == m_selected.constEnd()) - return false; - - // FIXME: Is there a way to check for versions without checking the filename - // as a heuristic, other than adding such info to ResourceDownloadTask itself? - if (!filename.isEmpty()) - return iter.value()->getFilename() == filename; - - return true; -} - -const QList ResourceDownloadDialog::getTasks() +const QList ResourceDownloadDialog::getTasks() { return m_selected.values(); } diff --git a/launcher/ui/dialogs/ResourceDownloadDialog.h b/launcher/ui/dialogs/ResourceDownloadDialog.h index 29813493..95a5e628 100644 --- a/launcher/ui/dialogs/ResourceDownloadDialog.h +++ b/launcher/ui/dialogs/ResourceDownloadDialog.h @@ -23,6 +23,8 @@ #include #include +#include "QObjectPtr.h" +#include "modplatform/ModIndex.h" #include "ui/pages/BasePageProvider.h" class BaseInstance; @@ -41,6 +43,8 @@ class ResourceDownloadDialog : public QDialog, public BasePageProvider { Q_OBJECT public: + using DownloadTaskPtr = shared_qobject_ptr; + ResourceDownloadDialog(QWidget* parent, const std::shared_ptr base_model); void initializeContainer(); @@ -54,11 +58,10 @@ class ResourceDownloadDialog : public QDialog, public BasePageProvider { bool selectPage(QString pageId); ResourcePage* getSelectedPage(); - void addResource(QString name, ResourceDownloadTask* task); - void removeResource(QString name); - [[nodiscard]] bool isSelected(QString name, QString filename = "") const; + void addResource(ModPlatform::IndexedPack&, ModPlatform::IndexedVersion&, bool is_indexed = false); + void removeResource(ModPlatform::IndexedPack&, ModPlatform::IndexedVersion&); - const QList getTasks(); + const QList getTasks(); [[nodiscard]] const std::shared_ptr getBaseModel() const { return m_base_model; } public slots: @@ -82,7 +85,7 @@ class ResourceDownloadDialog : public QDialog, public BasePageProvider { QDialogButtonBox m_buttons; QVBoxLayout m_vertical_layout; - QHash m_selected; + QHash m_selected; }; diff --git a/launcher/ui/pages/modplatform/ModModel.cpp b/launcher/ui/pages/modplatform/ModModel.cpp index 59399c59..c9dee449 100644 --- a/launcher/ui/pages/modplatform/ModModel.cpp +++ b/launcher/ui/pages/modplatform/ModModel.cpp @@ -1,7 +1,7 @@ #include "ModModel.h" #include "Json.h" -#include "ModPage.h" + #include "minecraft/MinecraftInstance.h" #include "minecraft/PackProfile.h" @@ -9,15 +9,23 @@ namespace ResourceDownload { -ModModel::ModModel(ModPage* parent, ResourceAPI* api) : ResourceModel(parent, api) {} +ModModel::ModModel(BaseInstance const& base_inst, ResourceAPI* api) : ResourceModel(base_inst, api) {} /******** Make data requests ********/ ResourceAPI::SearchArgs ModModel::createSearchArguments() { - auto profile = static_cast(m_associated_page->m_base_instance).getPackProfile(); + auto profile = static_cast(m_base_instance).getPackProfile(); + + Q_ASSERT(profile); + Q_ASSERT(m_filter); + + std::optional> versions {}; + if (!m_filter->versions.empty()) + versions = m_filter->versions; + return { ModPlatform::ResourceType::MOD, m_next_search_offset, m_search_term, - getSorts()[currentSort], profile->getModLoaders(), getMineVersions() }; + getSorts()[currentSort], profile->getModLoaders(), versions }; } ResourceAPI::SearchCallbacks ModModel::createSearchCallbacks() { @@ -30,19 +38,24 @@ ResourceAPI::SearchCallbacks ModModel::createSearchCallbacks() ResourceAPI::VersionSearchArgs ModModel::createVersionsArguments(QModelIndex& entry) { - auto const& pack = m_packs[entry.row()]; - auto profile = static_cast(m_associated_page->m_base_instance).getPackProfile(); + auto& pack = m_packs[entry.row()]; + auto profile = static_cast(m_base_instance).getPackProfile(); - return { pack.addonId.toString(), getMineVersions(), profile->getModLoaders() }; + Q_ASSERT(profile); + Q_ASSERT(m_filter); + + std::optional> versions {}; + if (!m_filter->versions.empty()) + versions = m_filter->versions; + + return { pack, versions, profile->getModLoaders() }; } ResourceAPI::VersionSearchCallbacks ModModel::createVersionsCallbacks(QModelIndex& entry) { - auto const& pack = m_packs[entry.row()]; - - return { [this, pack, entry](auto& doc, auto addonId) { + return { [this, entry](auto& doc, auto& pack) { if (!s_running_models.constFind(this).value()) return; - versionRequestSucceeded(doc, addonId, entry); + versionRequestSucceeded(doc, pack, entry); } }; } @@ -87,7 +100,7 @@ void ModModel::searchRequestFinished(QJsonDocument& doc) loadIndexedPack(pack, packObj); newList.append(pack); } catch (const JSONValidationError& e) { - qWarning() << "Error while loading mod from " << m_associated_page->debugName() << ": " << e.cause(); + qWarning() << "Error while loading mod from " << debugName() << ": " << e.cause(); continue; } } @@ -127,48 +140,36 @@ void ModModel::infoRequestFinished(QJsonDocument& doc, ModPlatform::IndexedPack& new_pack.setValue(pack); if (!setData(index, new_pack, Qt::UserRole)) { qWarning() << "Failed to cache mod info!"; + return; } + + emit projectInfoUpdated(); } - - m_associated_page->updateUi(); } -void ModModel::versionRequestSucceeded(QJsonDocument doc, QString addonId, const QModelIndex& index) +void ModModel::versionRequestSucceeded(QJsonDocument doc, ModPlatform::IndexedPack& pack, const QModelIndex& index) { - auto current = m_associated_page->getCurrentPack(); - if (addonId != current.addonId) { - return; - } - auto arr = doc.isObject() ? Json::ensureArray(doc.object(), "data") : doc.array(); try { - loadIndexedPackVersions(current, arr); + loadIndexedPackVersions(pack, arr); } catch (const JSONValidationError& e) { qDebug() << doc; qWarning() << "Error while reading " << debugName() << " mod version: " << e.cause(); } - // Cache info :^) - QVariant new_pack; - new_pack.setValue(current); - if (!setData(index, new_pack, Qt::UserRole)) { - qWarning() << "Failed to cache mod versions!"; + // Check if the index is still valid for this mod or not + if (pack.addonId == data(index, Qt::UserRole).value().addonId) { + // Cache info :^) + QVariant new_pack; + new_pack.setValue(pack); + if (!setData(index, new_pack, Qt::UserRole)) { + qWarning() << "Failed to cache mod versions!"; + return; + } + + emit versionListUpdated(); } - - m_associated_page->updateVersionList(); -} - -/******** Helpers ********/ - -#define MOD_PAGE(x) static_cast(x) - -auto ModModel::getMineVersions() const -> std::optional> -{ - auto versions = MOD_PAGE(m_associated_page)->getFilter()->versions; - if (!versions.empty()) - return versions; - return {}; } } // namespace ResourceDownload diff --git a/launcher/ui/pages/modplatform/ModModel.h b/launcher/ui/pages/modplatform/ModModel.h index e3d760a2..39d062f9 100644 --- a/launcher/ui/pages/modplatform/ModModel.h +++ b/launcher/ui/pages/modplatform/ModModel.h @@ -6,6 +6,7 @@ #include "modplatform/ResourceAPI.h" #include "ui/pages/modplatform/ResourceModel.h" +#include "ui/widgets/ModFilterWidget.h" class Version; @@ -17,7 +18,7 @@ class ModModel : public ResourceModel { Q_OBJECT public: - ModModel(ModPage* parent, ResourceAPI* api); + ModModel(const BaseInstance&, ResourceAPI* api); /* Ask the API for more information */ void searchWithTerm(const QString& term, const int sort, const bool filter_changed); @@ -26,12 +27,12 @@ class ModModel : public ResourceModel { virtual void loadExtraPackInfo(ModPlatform::IndexedPack& m, QJsonObject& obj) = 0; virtual void loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr) = 0; + void setFilter(std::shared_ptr filter) { m_filter = filter; } + public slots: void searchRequestFinished(QJsonDocument& doc); - void infoRequestFinished(QJsonDocument& doc, ModPlatform::IndexedPack& pack, const QModelIndex& index); - - void versionRequestSucceeded(QJsonDocument doc, QString addonId, const QModelIndex& index); + void versionRequestSucceeded(QJsonDocument doc, ModPlatform::IndexedPack& pack, const QModelIndex& index); public slots: ResourceAPI::SearchArgs createSearchArguments() override; @@ -47,10 +48,10 @@ class ModModel : public ResourceModel { virtual auto documentToArray(QJsonDocument& obj) const -> QJsonArray = 0; virtual auto getSorts() const -> const char** = 0; - inline auto getMineVersions() const -> std::optional>; - protected: int currentSort = 0; + + std::shared_ptr m_filter = nullptr; }; } // namespace ResourceDownload diff --git a/launcher/ui/pages/modplatform/ModPage.cpp b/launcher/ui/pages/modplatform/ModPage.cpp index 8d441546..556bd642 100644 --- a/launcher/ui/pages/modplatform/ModPage.cpp +++ b/launcher/ui/pages/modplatform/ModPage.cpp @@ -51,8 +51,6 @@ #include "ui/dialogs/ResourceDownloadDialog.h" -#include "ui/pages/modplatform/ModModel.h" - namespace ResourceDownload { ModPage::ModPage(ModDownloadDialog* dialog, BaseInstance& instance) @@ -151,7 +149,7 @@ void ModPage::updateVersionList() void ModPage::addResourceToDialog(ModPlatform::IndexedPack& pack, ModPlatform::IndexedVersion& version) { bool is_indexed = !APPLICATION->settings()->get("ModMetadataDisabled").toBool(); - m_parent_dialog->addResource(pack.name, new ResourceDownloadTask(pack, version, m_parent_dialog->getBaseModel(), is_indexed)); + m_parent_dialog->addResource(pack, version, is_indexed); } } // namespace ResourceDownload diff --git a/launcher/ui/pages/modplatform/ModPage.h b/launcher/ui/pages/modplatform/ModPage.h index 137a6046..2fda3b68 100644 --- a/launcher/ui/pages/modplatform/ModPage.h +++ b/launcher/ui/pages/modplatform/ModPage.h @@ -5,6 +5,7 @@ #include "modplatform/ModIndex.h" #include "ui/pages/modplatform/ResourcePage.h" +#include "ui/pages/modplatform/ModModel.h" #include "ui/widgets/ModFilterWidget.h" namespace Ui { @@ -24,9 +25,14 @@ class ModPage : public ResourcePage { static T* create(ModDownloadDialog* dialog, BaseInstance& instance) { auto page = new T(dialog, instance); + auto model = static_cast(page->getModel()); auto filter_widget = ModFilterWidget::create(static_cast(instance).getPackProfile()->getComponentVersion("net.minecraft"), page); page->setFilterWidget(filter_widget); + model->setFilter(page->getFilter()); + + connect(model, &ResourceModel::versionListUpdated, page, &ResourcePage::updateVersionList); + connect(model, &ResourceModel::projectInfoUpdated, page, &ResourcePage::updateUi); return page; } diff --git a/launcher/ui/pages/modplatform/ResourceModel.cpp b/launcher/ui/pages/modplatform/ResourceModel.cpp index e8af0e7a..cf40fef2 100644 --- a/launcher/ui/pages/modplatform/ResourceModel.cpp +++ b/launcher/ui/pages/modplatform/ResourceModel.cpp @@ -17,14 +17,13 @@ #include "modplatform/ModIndex.h" -#include "ui/pages/modplatform/ResourcePage.h" #include "ui/widgets/ProjectItem.h" namespace ResourceDownload { QHash ResourceModel::s_running_models; -ResourceModel::ResourceModel(ResourcePage* parent, ResourceAPI* api) : QAbstractListModel(), m_api(api), m_associated_page(parent) +ResourceModel::ResourceModel(BaseInstance const& base_inst, ResourceAPI* api) : QAbstractListModel(), m_base_instance(base_inst), m_api(api) { s_running_models.insert(this, true); } @@ -72,7 +71,7 @@ auto ResourceModel::data(const QModelIndex& index, int role) const -> QVariant case UserDataTypes::DESCRIPTION: return pack.description; case UserDataTypes::SELECTED: - return isPackSelected(pack); + return pack.isAnyVersionSelected(); default: break; } @@ -87,13 +86,14 @@ bool ResourceModel::setData(const QModelIndex& index, const QVariant& value, int return false; m_packs[pos] = value.value(); + emit dataChanged(index, index); return true; } QString ResourceModel::debugName() const { - return m_associated_page->debugName() + " (Model)"; + return "ResourceDownload (Model)"; } void ResourceModel::fetchMore(const QModelIndex& parent) @@ -195,7 +195,7 @@ std::optional ResourceModel::getIcon(QModelIndex& index, const QUrl& url) return {}; auto cache_entry = APPLICATION->metacache()->resolveEntry( - m_associated_page->metaEntryBase(), + metaEntryBase(), QString("logos/%1").arg(QString(QCryptographicHash::hash(url.toEncoded(), QCryptographicHash::Algorithm::Sha1).toHex()))); auto icon_fetch_action = Net::Download::makeCached(url, cache_entry); @@ -222,11 +222,6 @@ std::optional ResourceModel::getIcon(QModelIndex& index, const QUrl& url) return {}; } -bool ResourceModel::isPackSelected(const ModPlatform::IndexedPack& pack) const -{ - return m_associated_page->isPackSelected(pack); -} - void ResourceModel::searchRequestFailed(QString reason, int network_error_code) { switch (network_error_code) { @@ -237,9 +232,7 @@ void ResourceModel::searchRequestFailed(QString reason, int network_error_code) case 409: // 409 Gone, notify user to update QMessageBox::critical(nullptr, tr("Error"), - //: %1 refers to the launcher itself - QString("%1 %2") - .arg(m_associated_page->displayName()) + QString("%1") .arg(tr("API version too old!\nPlease update %1!").arg(BuildConfig.LAUNCHER_DISPLAYNAME))); break; } diff --git a/launcher/ui/pages/modplatform/ResourceModel.h b/launcher/ui/pages/modplatform/ResourceModel.h index 6a94c399..af33bf55 100644 --- a/launcher/ui/pages/modplatform/ResourceModel.h +++ b/launcher/ui/pages/modplatform/ResourceModel.h @@ -5,6 +5,7 @@ #include #include "QObjectPtr.h" +#include "BaseInstance.h" #include "modplatform/ResourceAPI.h" #include "tasks/ConcurrentTask.h" @@ -17,19 +18,18 @@ struct IndexedPack; namespace ResourceDownload { -class ResourcePage; - class ResourceModel : public QAbstractListModel { Q_OBJECT public: - ResourceModel(ResourcePage* parent, ResourceAPI* api); + ResourceModel(BaseInstance const&, ResourceAPI* api); ~ResourceModel() override; [[nodiscard]] auto data(const QModelIndex&, int role) const -> QVariant override; bool setData(const QModelIndex& index, const QVariant& value, int role) override; - [[nodiscard]] auto debugName() const -> QString; + [[nodiscard]] virtual auto debugName() const -> QString; + [[nodiscard]] virtual auto metaEntryBase() const -> QString = 0; [[nodiscard]] inline int rowCount(const QModelIndex& parent) const override { return parent.isValid() ? 0 : m_packs.size(); } [[nodiscard]] inline int columnCount(const QModelIndex& parent) const override { return parent.isValid() ? 0 : 1; }; @@ -38,6 +38,10 @@ class ResourceModel : public QAbstractListModel { inline void addActiveJob(Task::Ptr ptr) { m_current_job.addTask(ptr); if (!m_current_job.isRunning()) m_current_job.start(); } inline Task const& activeJob() { return m_current_job; } + signals: + void versionListUpdated(); + void projectInfoUpdated(); + public slots: void fetchMore(const QModelIndex& parent) override; [[nodiscard]] inline bool canFetchMore(const QModelIndex& parent) const override @@ -72,9 +76,9 @@ class ResourceModel : public QAbstractListModel { /** Resets the model's data. */ void clearData(); - [[nodiscard]] bool isPackSelected(const ModPlatform::IndexedPack&) const; - protected: + const BaseInstance& m_base_instance; + /* Basic search parameters */ enum class SearchState { None, CanFetchMore, ResetRequested, Finished } m_search_state = SearchState::None; int m_next_search_offset = 0; @@ -88,8 +92,6 @@ class ResourceModel : public QAbstractListModel { QSet m_currently_running_icon_actions; QSet m_failed_icon_actions; - ResourcePage* m_associated_page = nullptr; - QList m_packs; // HACK: We need this to prevent callbacks from calling the model after it has already been deleted. diff --git a/launcher/ui/pages/modplatform/ResourcePage.cpp b/launcher/ui/pages/modplatform/ResourcePage.cpp index 161b5c22..e04278af 100644 --- a/launcher/ui/pages/modplatform/ResourcePage.cpp +++ b/launcher/ui/pages/modplatform/ResourcePage.cpp @@ -103,19 +103,18 @@ void ResourcePage::setSearchTerm(QString term) m_ui->searchEdit->setText(term); } +bool ResourcePage::setCurrentPack(ModPlatform::IndexedPack pack) +{ + QVariant v; + v.setValue(pack); + return m_model->setData(m_ui->packView->currentIndex(), v, Qt::UserRole); +} + ModPlatform::IndexedPack ResourcePage::getCurrentPack() const { return m_model->data(m_ui->packView->currentIndex(), Qt::UserRole).value(); } -bool ResourcePage::isPackSelected(const ModPlatform::IndexedPack& pack, int version) const -{ - if (version < 0 || !pack.versionsLoaded) - return m_parent_dialog->isSelected(pack.name); - - return m_parent_dialog->isSelected(pack.name, pack.versions[version].fileName); -} - void ResourcePage::updateUi() { auto current_pack = getCurrentPack(); @@ -185,7 +184,7 @@ void ResourcePage::updateSelectionButton() } m_ui->resourceSelectionButton->setEnabled(true); - if (!isPackSelected(getCurrentPack(), m_selected_version_index)) { + if (!getCurrentPack().isVersionSelected(m_selected_version_index)) { m_ui->resourceSelectionButton->setText(tr("Select %1 for download").arg(resourceString())); } else { m_ui->resourceSelectionButton->setText(tr("Deselect %1 for download").arg(resourceString())); @@ -256,12 +255,12 @@ void ResourcePage::onVersionSelectionChanged(QString data) void ResourcePage::addResourceToDialog(ModPlatform::IndexedPack& pack, ModPlatform::IndexedVersion& version) { - m_parent_dialog->addResource(pack.name, new ResourceDownloadTask(pack, version, m_parent_dialog->getBaseModel())); + m_parent_dialog->addResource(pack, version); } -void ResourcePage::removeResourceFromDialog(ModPlatform::IndexedPack& pack, ModPlatform::IndexedVersion&) +void ResourcePage::removeResourceFromDialog(ModPlatform::IndexedPack& pack, ModPlatform::IndexedVersion& version) { - m_parent_dialog->removeResource(pack.name); + m_parent_dialog->removeResource(pack, version); } void ResourcePage::onResourceSelected() @@ -270,13 +269,19 @@ void ResourcePage::onResourceSelected() return; auto current_pack = getCurrentPack(); + if (!current_pack.versionsLoaded) + return; auto& version = current_pack.versions[m_selected_version_index]; - if (m_parent_dialog->isSelected(current_pack.name, version.fileName)) + if (version.is_currently_selected) removeResourceFromDialog(current_pack, version); else addResourceToDialog(current_pack, version); + // Save the modified pack (and prevent warning in release build) + [[maybe_unused]] bool set = setCurrentPack(current_pack); + Q_ASSERT(set); + updateSelectionButton(); /* Force redraw on the resource list when the selection changes */ diff --git a/launcher/ui/pages/modplatform/ResourcePage.h b/launcher/ui/pages/modplatform/ResourcePage.h index f731cf56..b51c7ccb 100644 --- a/launcher/ui/pages/modplatform/ResourcePage.h +++ b/launcher/ui/pages/modplatform/ResourcePage.h @@ -50,11 +50,13 @@ class ResourcePage : public QWidget, public BasePage { /** Programatically set the term in the search bar. */ void setSearchTerm(QString); - [[nodiscard]] bool isPackSelected(const ModPlatform::IndexedPack&, int version = -1) const; + [[nodiscard]] bool setCurrentPack(ModPlatform::IndexedPack); [[nodiscard]] auto getCurrentPack() const -> ModPlatform::IndexedPack; [[nodiscard]] auto getDialog() const -> const ResourceDownloadDialog* { return m_parent_dialog; } + [[nodiscard]] auto getModel() const -> ResourceModel* { return m_model; } + protected: ResourcePage(ResourceDownloadDialog* parent, BaseInstance&); diff --git a/launcher/ui/pages/modplatform/flame/FlameResourceModels.cpp b/launcher/ui/pages/modplatform/flame/FlameResourceModels.cpp index cfe4080a..d0f109de 100644 --- a/launcher/ui/pages/modplatform/flame/FlameResourceModels.cpp +++ b/launcher/ui/pages/modplatform/flame/FlameResourceModels.cpp @@ -2,6 +2,7 @@ #include "Json.h" +#include "modplatform/flame/FlameAPI.h" #include "modplatform/flame/FlameModIndex.h" namespace ResourceDownload { @@ -9,7 +10,7 @@ namespace ResourceDownload { // NOLINTNEXTLINE(modernize-avoid-c-arrays) const char* FlameModModel::sorts[6]{ "Featured", "Popularity", "LastUpdated", "Name", "Author", "TotalDownloads" }; -FlameModModel::FlameModModel(FlameModPage* parent) : ModModel(parent, new FlameAPI) {} +FlameModModel::FlameModModel(BaseInstance const& base) : ModModel(base, new FlameAPI) {} void FlameModModel::loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj) { @@ -24,7 +25,7 @@ void FlameModModel::loadExtraPackInfo(ModPlatform::IndexedPack& m, QJsonObject& void FlameModModel::loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr) { - FlameMod::loadIndexedPackVersions(m, arr, APPLICATION->network(), &m_associated_page->m_base_instance); + FlameMod::loadIndexedPackVersions(m, arr, APPLICATION->network(), &m_base_instance); } auto FlameModModel::documentToArray(QJsonDocument& obj) const -> QJsonArray diff --git a/launcher/ui/pages/modplatform/flame/FlameResourceModels.h b/launcher/ui/pages/modplatform/flame/FlameResourceModels.h index 501937e2..7b253dce 100644 --- a/launcher/ui/pages/modplatform/flame/FlameResourceModels.h +++ b/launcher/ui/pages/modplatform/flame/FlameResourceModels.h @@ -1,9 +1,6 @@ #pragma once -#include "modplatform/flame/FlameAPI.h" - #include "ui/pages/modplatform/ModModel.h" - #include "ui/pages/modplatform/flame/FlameResourcePages.h" namespace ResourceDownload { @@ -12,10 +9,13 @@ class FlameModModel : public ModModel { Q_OBJECT public: - FlameModModel(FlameModPage* parent); + FlameModModel(const BaseInstance&); ~FlameModModel() override = default; private: + [[nodiscard]] QString debugName() const override { return Flame::debugName() + " (Model)"; } + [[nodiscard]] QString metaEntryBase() const override { return Flame::metaEntryBase(); } + void loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj) override; void loadExtraPackInfo(ModPlatform::IndexedPack& m, QJsonObject& obj) override; void loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr) override; diff --git a/launcher/ui/pages/modplatform/flame/FlameResourcePages.cpp b/launcher/ui/pages/modplatform/flame/FlameResourcePages.cpp index 2a8ab526..67737a76 100644 --- a/launcher/ui/pages/modplatform/flame/FlameResourcePages.cpp +++ b/launcher/ui/pages/modplatform/flame/FlameResourcePages.cpp @@ -45,7 +45,7 @@ namespace ResourceDownload { FlameModPage::FlameModPage(ModDownloadDialog* dialog, BaseInstance& instance) : ModPage(dialog, instance) { - m_model = new FlameModModel(this); + m_model = new FlameModModel(instance); m_ui->packView->setModel(m_model); // index is used to set the sorting with the flame api diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.cpp index ee96f0de..9d26ae05 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.cpp +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.cpp @@ -18,8 +18,6 @@ #include "ModrinthResourceModels.h" -#include "ui/pages/modplatform/modrinth/ModrinthResourcePages.h" - #include "modplatform/modrinth/ModrinthAPI.h" #include "modplatform/modrinth/ModrinthPackIndex.h" @@ -28,7 +26,7 @@ namespace ResourceDownload { // NOLINTNEXTLINE(modernize-avoid-c-arrays) const char* ModrinthModModel::sorts[5]{ "relevance", "downloads", "follows", "updated", "newest" }; -ModrinthModModel::ModrinthModModel(ModrinthModPage* parent) : ModModel(parent, new ModrinthAPI){}; +ModrinthModModel::ModrinthModModel(BaseInstance const& base) : ModModel(base, new ModrinthAPI){}; void ModrinthModModel::loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj) { @@ -42,7 +40,7 @@ void ModrinthModModel::loadExtraPackInfo(ModPlatform::IndexedPack& m, QJsonObjec void ModrinthModModel::loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr) { - ::Modrinth::loadIndexedPackVersions(m, arr, APPLICATION->network(), &m_associated_page->m_base_instance); + ::Modrinth::loadIndexedPackVersions(m, arr, APPLICATION->network(), &m_base_instance); } auto ModrinthModModel::documentToArray(QJsonDocument& obj) const -> QJsonArray diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.h b/launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.h index b0088a73..798a70e6 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.h +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.h @@ -19,6 +19,7 @@ #pragma once #include "ui/pages/modplatform/ModModel.h" +#include "ui/pages/modplatform/modrinth/ModrinthResourcePages.h" namespace ResourceDownload { @@ -28,10 +29,13 @@ class ModrinthModModel : public ModModel { Q_OBJECT public: - ModrinthModModel(ModrinthModPage* parent); + ModrinthModModel(const BaseInstance&); ~ModrinthModModel() override = default; private: + [[nodiscard]] QString debugName() const override { return Modrinth::debugName() + " (Model)"; } + [[nodiscard]] QString metaEntryBase() const override { return Modrinth::metaEntryBase(); } + void loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj) override; void loadExtraPackInfo(ModPlatform::IndexedPack& m, QJsonObject& obj) override; void loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr) override; diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.cpp index 1352e2f6..88621e05 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.cpp +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.cpp @@ -47,7 +47,7 @@ namespace ResourceDownload { ModrinthModPage::ModrinthModPage(ModDownloadDialog* dialog, BaseInstance& instance) : ModPage(dialog, instance) { - m_model = new ModrinthModModel(this); + m_model = new ModrinthModModel(instance); m_ui->packView->setModel(m_model); // index is used to set the sorting with the modrinth api