diff --git a/launcher/ui/pages/modplatform/ModModel.cpp b/launcher/ui/pages/modplatform/ModModel.cpp index beb8aec1..d52a430e 100644 --- a/launcher/ui/pages/modplatform/ModModel.cpp +++ b/launcher/ui/pages/modplatform/ModModel.cpp @@ -1,7 +1,5 @@ #include "ModModel.h" -#include "Json.h" - #include "minecraft/MinecraftInstance.h" #include "minecraft/PackProfile.h" @@ -31,14 +29,6 @@ ResourceAPI::SearchArgs ModModel::createSearchArguments() return { ModPlatform::ResourceType::MOD, m_next_search_offset, m_search_term, sort, profile->getModLoaders(), versions }; } -ResourceAPI::SearchCallbacks ModModel::createSearchCallbacks() -{ - return { [this](auto& doc) { - if (!s_running_models.constFind(this).value()) - return; - searchRequestFinished(doc); - } }; -} ResourceAPI::VersionSearchArgs ModModel::createVersionsArguments(QModelIndex& entry) { @@ -54,28 +44,12 @@ ResourceAPI::VersionSearchArgs ModModel::createVersionsArguments(QModelIndex& en return { pack, versions, profile->getModLoaders() }; } -ResourceAPI::VersionSearchCallbacks ModModel::createVersionsCallbacks(QModelIndex& entry) -{ - return { [this, entry](auto& doc, auto pack) { - if (!s_running_models.constFind(this).value()) - return; - versionRequestSucceeded(doc, pack, entry); - } }; -} ResourceAPI::ProjectInfoArgs ModModel::createInfoArguments(QModelIndex& entry) { auto& pack = m_packs[entry.row()]; return { pack }; } -ResourceAPI::ProjectInfoCallbacks ModModel::createInfoCallbacks(QModelIndex& entry) -{ - return { [this, entry](auto& doc, auto pack) { - if (!s_running_models.constFind(this).value()) - return; - infoRequestFinished(doc, pack, entry); - } }; -} void ModModel::searchWithTerm(const QString& term, unsigned int sort, bool filter_changed) { @@ -89,97 +63,4 @@ void ModModel::searchWithTerm(const QString& term, unsigned int sort, bool filte refresh(); } -/******** Request callbacks ********/ - -void ModModel::searchRequestFinished(QJsonDocument& doc) -{ - QList newList; - auto packs = documentToArray(doc); - - for (auto packRaw : packs) { - auto packObj = packRaw.toObject(); - - ModPlatform::IndexedPack pack; - try { - loadIndexedPack(pack, packObj); - newList.append(pack); - } catch (const JSONValidationError& e) { - qWarning() << "Error while loading mod from " << debugName() << ": " << e.cause(); - continue; - } - } - - if (packs.size() < 25) { - m_search_state = SearchState::Finished; - } else { - m_next_search_offset += 25; - m_search_state = SearchState::CanFetchMore; - } - - // When you have a Qt build with assertions turned on, proceeding here will abort the application - if (newList.size() == 0) - return; - - beginInsertRows(QModelIndex(), m_packs.size(), m_packs.size() + newList.size() - 1); - m_packs.append(newList); - endInsertRows(); -} - -void ModModel::infoRequestFinished(QJsonDocument& doc, ModPlatform::IndexedPack& pack, const QModelIndex& index) -{ - qDebug() << "Loading mod info"; - - auto current_pack = data(index, Qt::UserRole).value(); - - // Check if the index is still valid for this mod or not - if (pack.addonId != current_pack.addonId) - return; - - try { - auto obj = Json::requireObject(doc); - loadExtraPackInfo(current_pack, obj); - } catch (const JSONValidationError& e) { - qDebug() << doc; - qWarning() << "Error while reading " << debugName() << " mod info: " << e.cause(); - } - - // Cache info :^) - QVariant new_pack; - new_pack.setValue(current_pack); - if (!setData(index, new_pack, Qt::UserRole)) { - qWarning() << "Failed to cache mod info!"; - return; - } - - emit projectInfoUpdated(); -} - -void ModModel::versionRequestSucceeded(QJsonDocument doc, ModPlatform::IndexedPack& pack, const QModelIndex& index) -{ - auto arr = doc.isObject() ? Json::ensureArray(doc.object(), "data") : doc.array(); - - auto current_pack = data(index, Qt::UserRole).value(); - - // Check if the index is still valid for this mod or not - if (pack.addonId != current_pack.addonId) - return; - - try { - loadIndexedPackVersions(current_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_pack); - if (!setData(index, new_pack, Qt::UserRole)) { - qWarning() << "Failed to cache mod versions!"; - return; - } - - emit versionListUpdated(); -} - } // namespace ResourceDownload diff --git a/launcher/ui/pages/modplatform/ModModel.h b/launcher/ui/pages/modplatform/ModModel.h index 3aeba3ef..c705371a 100644 --- a/launcher/ui/pages/modplatform/ModModel.h +++ b/launcher/ui/pages/modplatform/ModModel.h @@ -23,29 +23,19 @@ class ModModel : public ResourceModel { /* Ask the API for more information */ void searchWithTerm(const QString& term, unsigned int sort, bool filter_changed); - virtual void loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj) = 0; - virtual void loadExtraPackInfo(ModPlatform::IndexedPack& m, QJsonObject& obj) = 0; - virtual void loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr) = 0; + 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; 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, ModPlatform::IndexedPack& pack, const QModelIndex& index); - public slots: ResourceAPI::SearchArgs createSearchArguments() override; - ResourceAPI::SearchCallbacks createSearchCallbacks() override; - ResourceAPI::VersionSearchArgs createVersionsArguments(QModelIndex&) override; - ResourceAPI::VersionSearchCallbacks createVersionsCallbacks(QModelIndex&) override; - ResourceAPI::ProjectInfoArgs createInfoArguments(QModelIndex&) override; - ResourceAPI::ProjectInfoCallbacks createInfoCallbacks(QModelIndex&) override; protected: - virtual auto documentToArray(QJsonDocument& obj) const -> QJsonArray = 0; + auto documentToArray(QJsonDocument& obj) const -> QJsonArray override = 0; protected: std::shared_ptr m_filter = nullptr; diff --git a/launcher/ui/pages/modplatform/ResourceModel.cpp b/launcher/ui/pages/modplatform/ResourceModel.cpp index d9c30912..05d44ee2 100644 --- a/launcher/ui/pages/modplatform/ResourceModel.cpp +++ b/launcher/ui/pages/modplatform/ResourceModel.cpp @@ -8,13 +8,11 @@ #include "Application.h" #include "BuildConfig.h" +#include "Json.h" #include "net/Download.h" #include "net/NetJob.h" -#include "minecraft/MinecraftInstance.h" -#include "minecraft/PackProfile.h" - #include "modplatform/ModIndex.h" #include "ui/widgets/ProjectItem.h" @@ -129,9 +127,14 @@ void ResourceModel::search() auto args{ createSearchArguments() }; auto callbacks{ createSearchCallbacks() }; - Q_ASSERT(callbacks.on_succeed); // Use defaults if no callbacks are set + if (!callbacks.on_succeed) + callbacks.on_succeed = [this](auto& doc) { + if (!s_running_models.constFind(this).value()) + return; + searchRequestSucceeded(doc); + }; if (!callbacks.on_fail) callbacks.on_fail = [this](QString reason, int network_error_code) { if (!s_running_models.constFind(this).value()) @@ -160,6 +163,14 @@ void ResourceModel::loadEntry(QModelIndex& entry) auto args{ createVersionsArguments(entry) }; auto callbacks{ createVersionsCallbacks(entry) }; + // Use default if no callbacks are set + if (!callbacks.on_succeed) + callbacks.on_succeed = [this, entry](auto& doc, auto pack) { + if (!s_running_models.constFind(this).value()) + return; + versionRequestSucceeded(doc, pack, entry); + }; + if (auto job = m_api->getProjectVersions(std::move(args), std::move(callbacks)); job) runInfoJob(job); } @@ -168,6 +179,14 @@ void ResourceModel::loadEntry(QModelIndex& entry) auto args{ createInfoArguments(entry) }; auto callbacks{ createInfoCallbacks(entry) }; + // Use default if no callbacks are set + if (!callbacks.on_succeed) + callbacks.on_succeed = [this, entry](auto& doc, auto pack) { + if (!s_running_models.constFind(this).value()) + return; + infoRequestSucceeded(doc, pack, entry); + }; + if (auto job = m_api->getProjectInfo(std::move(args), std::move(callbacks)); job) runInfoJob(job); } @@ -226,10 +245,10 @@ std::optional ResourceModel::getCurrentSortingMethod { std::optional sort{}; - { // Find sorting method by ID + { // Find sorting method by ID auto sorting_methods = getSortingMethods(); auto method = std::find_if(sorting_methods.constBegin(), sorting_methods.constEnd(), - [this](auto const& e) { return m_current_sort_index == e.index; }); + [this](auto const& e) { return m_current_sort_index == e.index; }); if (method != sorting_methods.constEnd()) sort = *method; } @@ -279,6 +298,64 @@ std::optional ResourceModel::getIcon(QModelIndex& index, const QUrl& url) return {}; } +// No 'forgor to implement' shall pass here :blobfox_knife: +#define NEED_FOR_CALLBACK_ASSERT(name) \ + Q_ASSERT_X(0 != 0, #name, "You NEED to re-implement this if you intend on using the default callbacks.") + +QJsonArray ResourceModel::documentToArray(QJsonDocument& doc) const +{ + NEED_FOR_CALLBACK_ASSERT("documentToArray"); + return {}; +} +void ResourceModel::loadIndexedPack(ModPlatform::IndexedPack&, QJsonObject&) +{ + NEED_FOR_CALLBACK_ASSERT("loadIndexedPack"); +} +void ResourceModel::loadExtraPackInfo(ModPlatform::IndexedPack&, QJsonObject&) +{ + NEED_FOR_CALLBACK_ASSERT("loadExtraPackInfo"); +} +void ResourceModel::loadIndexedPackVersions(ModPlatform::IndexedPack&, QJsonArray&) +{ + NEED_FOR_CALLBACK_ASSERT("loadIndexedPackVersions"); +} + +/* Default callbacks */ + +void ResourceModel::searchRequestSucceeded(QJsonDocument& doc) +{ + QList newList; + auto packs = documentToArray(doc); + + for (auto packRaw : packs) { + auto packObj = packRaw.toObject(); + + ModPlatform::IndexedPack pack; + try { + loadIndexedPack(pack, packObj); + newList.append(pack); + } catch (const JSONValidationError& e) { + qWarning() << "Error while loading resource from " << debugName() << ": " << e.cause(); + continue; + } + } + + if (packs.size() < 25) { + m_search_state = SearchState::Finished; + } else { + m_next_search_offset += 25; + m_search_state = SearchState::CanFetchMore; + } + + // When you have a Qt build with assertions turned on, proceeding here will abort the application + if (newList.size() == 0) + return; + + beginInsertRows(QModelIndex(), m_packs.size(), m_packs.size() + newList.size() - 1); + m_packs.append(newList); + endInsertRows(); +} + void ResourceModel::searchRequestFailed(QString reason, int network_error_code) { switch (network_error_code) { @@ -289,8 +366,7 @@ void ResourceModel::searchRequestFailed(QString reason, int network_error_code) case 409: // 409 Gone, notify user to update QMessageBox::critical(nullptr, tr("Error"), - QString("%1") - .arg(tr("API version too old!\nPlease update %1!").arg(BuildConfig.LAUNCHER_DISPLAYNAME))); + QString("%1").arg(tr("API version too old!\nPlease update %1!").arg(BuildConfig.LAUNCHER_DISPLAYNAME))); break; } @@ -309,4 +385,58 @@ void ResourceModel::searchRequestAborted() search(); } +void ResourceModel::versionRequestSucceeded(QJsonDocument& doc, ModPlatform::IndexedPack& pack, const QModelIndex& index) +{ + auto current_pack = data(index, Qt::UserRole).value(); + + // Check if the index is still valid for this resource or not + if (pack.addonId != current_pack.addonId) + return; + + try { + auto arr = doc.isObject() ? Json::ensureArray(doc.object(), "data") : doc.array(); + loadIndexedPackVersions(current_pack, arr); + } catch (const JSONValidationError& e) { + qDebug() << doc; + qWarning() << "Error while reading " << debugName() << " resource version: " << e.cause(); + } + + // Cache info :^) + QVariant new_pack; + new_pack.setValue(current_pack); + if (!setData(index, new_pack, Qt::UserRole)) { + qWarning() << "Failed to cache resource versions!"; + return; + } + + emit versionListUpdated(); +} + +void ResourceModel::infoRequestSucceeded(QJsonDocument& doc, ModPlatform::IndexedPack& pack, const QModelIndex& index) +{ + auto current_pack = data(index, Qt::UserRole).value(); + + // Check if the index is still valid for this resource or not + if (pack.addonId != current_pack.addonId) + return; + + try { + auto obj = Json::requireObject(doc); + loadExtraPackInfo(current_pack, obj); + } catch (const JSONValidationError& e) { + qDebug() << doc; + qWarning() << "Error while reading " << debugName() << " resource info: " << e.cause(); + } + + // Cache info :^) + QVariant new_pack; + new_pack.setValue(current_pack); + if (!setData(index, new_pack, Qt::UserRole)) { + qWarning() << "Failed to cache resource info!"; + return; + } + + emit projectInfoUpdated(); +} + } // namespace ResourceDownload diff --git a/launcher/ui/pages/modplatform/ResourceModel.h b/launcher/ui/pages/modplatform/ResourceModel.h index 05aa6a94..d8be3b6b 100644 --- a/launcher/ui/pages/modplatform/ResourceModel.h +++ b/launcher/ui/pages/modplatform/ResourceModel.h @@ -57,13 +57,13 @@ class ResourceModel : public QAbstractListModel { void setSearchTerm(QString term) { m_search_term = term; } virtual ResourceAPI::SearchArgs createSearchArguments() = 0; - virtual ResourceAPI::SearchCallbacks createSearchCallbacks() = 0; + virtual ResourceAPI::SearchCallbacks createSearchCallbacks() { return {}; } virtual ResourceAPI::VersionSearchArgs createVersionsArguments(QModelIndex&) = 0; - virtual ResourceAPI::VersionSearchCallbacks createVersionsCallbacks(QModelIndex&) = 0; + virtual ResourceAPI::VersionSearchCallbacks createVersionsCallbacks(QModelIndex&) { return {}; } virtual ResourceAPI::ProjectInfoArgs createInfoArguments(QModelIndex&) = 0; - virtual ResourceAPI::ProjectInfoCallbacks createInfoCallbacks(QModelIndex&) = 0; + virtual ResourceAPI::ProjectInfoCallbacks createInfoCallbacks(QModelIndex&) { return {}; } /** Requests the API for more entries. */ virtual void search(); @@ -86,6 +86,22 @@ class ResourceModel : public QAbstractListModel { [[nodiscard]] auto getCurrentSortingMethodByIndex() const -> std::optional; + /** Converts a JSON document to a common array format. + * + * This is needed so that different providers, with different JSON structures, can be parsed + * uniformally. You NEED to re-implement this if you intend on using the default callbacks. + */ + [[nodiscard]] virtual auto documentToArray(QJsonDocument&) const -> QJsonArray; + + /** Functions to load data into a pack. + * + * Those are needed for the same reason as ddocumentToArray, and NEED to be re-implemented in the same way. + */ + + virtual void loadIndexedPack(ModPlatform::IndexedPack&, QJsonObject&); + virtual void loadExtraPackInfo(ModPlatform::IndexedPack&, QJsonObject&); + virtual void loadIndexedPackVersions(ModPlatform::IndexedPack&, QJsonArray&); + protected: const BaseInstance& m_base_instance; @@ -114,9 +130,14 @@ class ResourceModel : public QAbstractListModel { private: /* Default search request callbacks */ + void searchRequestSucceeded(QJsonDocument&); void searchRequestFailed(QString reason, int network_error_code); void searchRequestAborted(); + void versionRequestSucceeded(QJsonDocument&, ModPlatform::IndexedPack&, const QModelIndex&); + + void infoRequestSucceeded(QJsonDocument&, ModPlatform::IndexedPack&, const QModelIndex&); + signals: void versionListUpdated(); void projectInfoUpdated();