diff --git a/launcher/modplatform/ModAPI.h b/launcher/modplatform/ModAPI.h index 4d22a63d..8503d7fc 100644 --- a/launcher/modplatform/ModAPI.h +++ b/launcher/modplatform/ModAPI.h @@ -1,21 +1,30 @@ #pragma once +#include #include +namespace ModPlatform { +class ListModel; +} + class ModAPI { - public: - virtual ~ModAPI() = default; + protected: + using CallerType = ModPlatform::ListModel; - // https://docs.curseforge.com/?http#tocS_ModLoaderType - enum ModLoaderType { - Any = 0, - Forge = 1, - Cauldron = 2, - LiteLoader = 3, - Fabric = 4 - }; + public: + virtual ~ModAPI() = default; - inline virtual QString getModSearchURL(int, QString, QString, ModLoaderType, QString) const { return ""; }; - inline virtual QString getVersionsURL(const QString& addonId) const { return ""; }; - inline virtual QString getAuthorURL(const QString& name) const { return ""; }; + // https://docs.curseforge.com/?http#tocS_ModLoaderType + enum ModLoaderType { Any = 0, Forge = 1, Cauldron = 2, LiteLoader = 3, Fabric = 4 }; + + struct SearchArgs { + int offset; + QString search; + QString sorting; + ModLoaderType mod_loader; + QString version; + }; + + inline virtual void searchMods(CallerType* caller, SearchArgs&& args) const {}; + inline virtual void getVersions(CallerType* caller, const QString& addonId, const QString& debugName = "") const {}; }; diff --git a/launcher/modplatform/flame/FlameAPI.h b/launcher/modplatform/flame/FlameAPI.h index b49aeb24..be88df65 100644 --- a/launcher/modplatform/flame/FlameAPI.h +++ b/launcher/modplatform/flame/FlameAPI.h @@ -1,28 +1,93 @@ #pragma once #include "modplatform/ModAPI.h" +#include "ui/pages/modplatform/ModModel.h" + +#include "Application.h" +#include "net/NetJob.h" class FlameAPI : public ModAPI { public: - - inline QString getModSearchURL(int index, QString searchFilter, QString sort, ModLoaderType modLoader, QString version) const override + inline void searchMods(CallerType* caller, SearchArgs&& args) const override { - return QString("https://addons-ecs.forgesvc.net/api/v2/addon/search?" - "gameId=432&" "categoryId=0&" "sectionId=6&" + auto netJob = new NetJob(QString("Flame::Search"), APPLICATION->network()); + auto searchUrl = getModSearchURL(args); - "index=%1&" "pageSize=25&" "searchFilter=%2&" - "sort=%3&" "modLoaderType=%4&" "gameVersion=%5") - .arg(index) - .arg(searchFilter) - .arg(sort) - .arg(modLoader) - .arg(version); + auto response = new QByteArray(); + netJob->addNetAction(Net::Download::makeByteArray(QUrl(searchUrl), response)); + + QObject::connect(netJob, &NetJob::started, caller, [caller, netJob]{ caller->setActiveJob(netJob); }); + QObject::connect(netJob, &NetJob::failed, caller, &CallerType::searchRequestFailed); + QObject::connect(netJob, &NetJob::succeeded, caller, [caller, response] { + QJsonParseError parse_error; + QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error); + if (parse_error.error != QJsonParseError::NoError) { + qWarning() << "Error while parsing JSON response from Modrinth at " << parse_error.offset + << " reason: " << parse_error.errorString(); + qWarning() << *response; + return; + } + + caller->searchRequestFinished(doc); + }); + + netJob->start(); }; - inline QString getVersionsURL(const QString& addonId) const override + inline void getVersions(CallerType* caller, const QString& addonId, const QString& debugName = "Flame") const override + { + auto netJob = new NetJob(QString("%1::ModVersions(%2)").arg(debugName).arg(addonId), APPLICATION->network()); + auto response = new QByteArray(); + + netJob->addNetAction(Net::Download::makeByteArray(getVersionsURL(addonId), response)); + + QObject::connect(netJob, &NetJob::succeeded, caller, [response, debugName, caller, addonId] { + QJsonParseError parse_error; + QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error); + if (parse_error.error != QJsonParseError::NoError) { + qWarning() << "Error while parsing JSON response from " << debugName << " at " << parse_error.offset + << " reason: " << parse_error.errorString(); + qWarning() << *response; + return; + } + + caller->versionRequestSucceeded(doc, addonId); + }); + + QObject::connect(netJob, &NetJob::finished, caller, [response, netJob] { + netJob->deleteLater(); + delete response; + }); + + netJob->start(); + }; + + private: + inline QString getModSearchURL(SearchArgs& args) const + { + return QString( + "https://addons-ecs.forgesvc.net/api/v2/addon/search?" + "gameId=432&" + "categoryId=0&" + "sectionId=6&" + + "index=%1&" + "pageSize=25&" + "searchFilter=%2&" + "sort=%3&" + "modLoaderType=%4&" + "gameVersion=%5") + .arg(args.offset) + .arg(args.search) + .arg(args.sorting) + .arg(args.mod_loader) + .arg(args.version); + }; + + inline QString getVersionsURL(const QString& addonId) const { return QString("https://addons-ecs.forgesvc.net/api/v2/addon/%1/files").arg(addonId); }; - inline QString getAuthorURL(const QString& name) const override { return ""; }; + inline QString getAuthorURL(const QString& name) const { return ""; }; }; diff --git a/launcher/modplatform/modrinth/ModrinthAPI.h b/launcher/modplatform/modrinth/ModrinthAPI.h index 44a362c8..84cc561b 100644 --- a/launcher/modplatform/modrinth/ModrinthAPI.h +++ b/launcher/modplatform/modrinth/ModrinthAPI.h @@ -1,50 +1,111 @@ #pragma once #include "modplatform/ModAPI.h" +#include "ui/pages/modplatform/ModModel.h" + +#include "Application.h" +#include "net/NetJob.h" #include class ModrinthAPI : public ModAPI { public: - inline QString getModSearchURL(int offset, QString query, QString sort, ModLoaderType modLoader, QString version) const override - { - if(!validateModLoader(modLoader)){ + inline void searchMods(CallerType* caller, SearchArgs&& args) const override + { + auto netJob = new NetJob(QString("Modrinth::Search"), APPLICATION->network()); + auto searchUrl = getModSearchURL(args); + + auto response = new QByteArray(); + netJob->addNetAction(Net::Download::makeByteArray(QUrl(searchUrl), response)); + + QObject::connect(netJob, &NetJob::started, caller, [caller, netJob]{ caller->setActiveJob(netJob); }); + QObject::connect(netJob, &NetJob::failed, caller, &CallerType::searchRequestFailed); + QObject::connect(netJob, &NetJob::succeeded, caller, [caller, response] { + QJsonParseError parse_error; + QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error); + if (parse_error.error != QJsonParseError::NoError) { + qWarning() << "Error while parsing JSON response from Modrinth at " << parse_error.offset + << " reason: " << parse_error.errorString(); + qWarning() << *response; + return; + } + + caller->searchRequestFinished(doc); + }); + + netJob->start(); + }; + + inline void getVersions(CallerType* caller, const QString& addonId, const QString& debugName = "Modrinth") const override + { + auto netJob = new NetJob(QString("%1::ModVersions(%2)").arg(debugName).arg(addonId), APPLICATION->network()); + auto response = new QByteArray(); + + netJob->addNetAction(Net::Download::makeByteArray(getVersionsURL(addonId), response)); + + QObject::connect(netJob, &NetJob::succeeded, caller, [response, debugName, caller, addonId] { + QJsonParseError parse_error; + QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error); + if (parse_error.error != QJsonParseError::NoError) { + qWarning() << "Error while parsing JSON response from " << debugName << " at " << parse_error.offset + << " reason: " << parse_error.errorString(); + qWarning() << *response; + return; + } + + caller->versionRequestSucceeded(doc, addonId); + }); + + QObject::connect(netJob, &NetJob::finished, caller, [response, netJob] { + netJob->deleteLater(); + delete response; + }); + + netJob->start(); + }; + + inline QString getAuthorURL(const QString& name) const { return "https://modrinth.com/user/" + name; }; + + private: + inline QString getModSearchURL(SearchArgs& args) const + { + if (!validateModLoader(args.mod_loader)) { qWarning() << "Modrinth only have Forge and Fabric-compatible mods!"; return ""; } - return QString("https://api.modrinth.com/v2/search?" - "offset=%1&" "limit=25&" "query=%2&" "index=%3&" - "facets=[[\"categories:%4\"],[\"versions:%5\"],[\"project_type:mod\"]]") - .arg(offset) - .arg(query) - .arg(sort) - .arg(getModLoaderString(modLoader)) - .arg(version); + return QString( + "https://api.modrinth.com/v2/search?" + "offset=%1&" + "limit=25&" + "query=%2&" + "index=%3&" + "facets=[[\"categories:%4\"],[\"versions:%5\"],[\"project_type:mod\"]]") + .arg(args.offset) + .arg(args.search) + .arg(args.sorting) + .arg(getModLoaderString(args.mod_loader)) + .arg(args.version); }; - inline QString getVersionsURL(const QString& addonId) const override + inline QString getVersionsURL(const QString& addonId) const { return QString("https://api.modrinth.com/v2/project/%1/version").arg(addonId); }; - inline QString getAuthorURL(const QString& name) const override { return "https://modrinth.com/user/" + name; }; + inline bool validateModLoader(ModLoaderType modLoader) const { return modLoader == Any || modLoader == Forge || modLoader == Fabric; } - private: - inline bool validateModLoader(ModLoaderType modLoader) const{ - return modLoader == Any || modLoader == Forge || modLoader == Fabric; - } - - inline QString getModLoaderString(ModLoaderType modLoader) const{ - switch(modLoader){ - case Any: - return "fabric, forge"; - case Forge: - return "forge"; - case Fabric: - return "fabric"; - default: - return ""; + inline QString getModLoaderString(ModLoaderType modLoader) const + { + switch (modLoader) { + case Any: + return "fabric, forge"; + case Forge: + return "forge"; + case Fabric: + return "fabric"; + default: + return ""; } } }; diff --git a/launcher/ui/pages/modplatform/ModModel.cpp b/launcher/ui/pages/modplatform/ModModel.cpp index 5be578c1..705f384e 100644 --- a/launcher/ui/pages/modplatform/ModModel.cpp +++ b/launcher/ui/pages/modplatform/ModModel.cpp @@ -91,30 +91,16 @@ void ListModel::fetchMore(const QModelIndex& parent) void ListModel::getLogo(const QString& logo, const QString& logoUrl, LogoCallback callback) { if (m_logoMap.contains(logo)) { - callback(APPLICATION->metacache()->resolveEntry(m_parent->metaEntryBase(), QString("logos/%1").arg(logo.section(".", 0, 0)))->getFullPath()); + callback(APPLICATION->metacache() + ->resolveEntry(m_parent->metaEntryBase(), QString("logos/%1").arg(logo.section(".", 0, 0))) + ->getFullPath()); } else { requestLogo(logo, logoUrl); } } -void ListModel::requestModVersions(ModPlatform::IndexedPack const& current) -{ - auto netJob = new NetJob(QString("%1::ModVersions(%2)").arg(m_parent->debugName()).arg(current.name), APPLICATION->network()); - auto response = new QByteArray(); - QString addonId = current.addonId.toString(); - - netJob->addNetAction(Net::Download::makeByteArray(m_parent->apiProvider()->getVersionsURL(addonId), response)); - - QObject::connect(netJob, &NetJob::succeeded, this, [this, response, addonId]{ - m_parent->onRequestVersionsSucceeded(m_parent, response, addonId); - }); - - QObject::connect(netJob, &NetJob::finished, this, [response, netJob] { - netJob->deleteLater(); - delete response; - }); - - netJob->start(); +void ListModel::requestModVersions(ModPlatform::IndexedPack const& current) { + m_parent->apiProvider()->getVersions(this, current.addonId.toString(), m_parent->debugName()); } void ListModel::performPaginatedSearch() @@ -124,16 +110,9 @@ void ListModel::performPaginatedSearch() ->getPackProfile() ->getComponentVersion("net.fabricmc.fabric-loader") .isEmpty(); - auto netJob = new NetJob(QString("%1::Search").arg(m_parent->debugName()), APPLICATION->network()); - auto searchUrl = m_parent->apiProvider()->getModSearchURL( - nextSearchOffset, currentSearchTerm, getSorts()[currentSort], hasFabric ? ModAPI::Fabric : ModAPI::Forge, mcVersion); - netJob->addNetAction(Net::Download::makeByteArray(QUrl(searchUrl), &response)); - jobPtr = netJob; - jobPtr->start(); - - QObject::connect(netJob, &NetJob::succeeded, this, &ListModel::searchRequestFinished); - QObject::connect(netJob, &NetJob::failed, this, &ListModel::searchRequestFailed); + m_parent->apiProvider()->searchMods( + this, { nextSearchOffset, currentSearchTerm, getSorts()[currentSort], hasFabric ? ModAPI::Fabric : ModAPI::Forge, mcVersion }); } void ListModel::searchWithTerm(const QString& term, const int sort) @@ -160,9 +139,7 @@ void ListModel::searchRequestFailed(QString reason) if (jobPtr->first()->m_reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() == 409) { // 409 Gone, notify user to update QMessageBox::critical(nullptr, tr("Error"), - QString("%1 %2") - .arg(m_parent->displayName()) - .arg(tr("API version too old!\nPlease update PolyMC!"))); + QString("%1 %2").arg(m_parent->displayName()).arg(tr("API version too old!\nPlease update PolyMC!"))); // self-destruct ((ModDownloadDialog*)((ModPage*)parent())->parentWidget())->reject(); } @@ -180,11 +157,17 @@ void ListModel::searchRequestFailed(QString reason) } } +void ListModel::versionRequestSucceeded(QJsonDocument doc, QString addonId) +{ + m_parent->onRequestVersionsSucceeded(doc, addonId); +} + void ListModel::requestLogo(QString logo, QString url) { if (m_loadingLogos.contains(logo) || m_failedLogos.contains(logo)) { return; } - MetaEntryPtr entry = APPLICATION->metacache()->resolveEntry(m_parent->metaEntryBase(), QString("logos/%1").arg(logo.section(".", 0, 0))); + MetaEntryPtr entry = + APPLICATION->metacache()->resolveEntry(m_parent->metaEntryBase(), QString("logos/%1").arg(logo.section(".", 0, 0))); auto job = new NetJob(QString("%1 Icon Download %2").arg(m_parent->debugName()).arg(logo), APPLICATION->network()); job->addNetAction(Net::Download::makeCached(QUrl(url), entry)); diff --git a/launcher/ui/pages/modplatform/ModModel.h b/launcher/ui/pages/modplatform/ModModel.h index 64c17b4a..28bf05bb 100644 --- a/launcher/ui/pages/modplatform/ModModel.h +++ b/launcher/ui/pages/modplatform/ModModel.h @@ -1,9 +1,10 @@ #pragma once +#include #include -#include "modplatform/ModIndex.h" #include "modplatform/ModAPI.h" +#include "modplatform/ModIndex.h" #include "net/NetJob.h" class ModPage; @@ -26,6 +27,8 @@ class ListModel : public QAbstractListModel { QVariant data(const QModelIndex& index, int role) const override; Qt::ItemFlags flags(const QModelIndex& index) const override; + void setActiveJob(NetJob::Ptr ptr) { jobPtr = ptr; } + bool canFetchMore(const QModelIndex& parent) const override; void fetchMore(const QModelIndex& parent) override; @@ -34,10 +37,14 @@ class ListModel : public QAbstractListModel { virtual void requestModVersions(const ModPlatform::IndexedPack& current); - protected slots: - virtual void searchRequestFinished() = 0; + public slots: + virtual void searchRequestFinished(QJsonDocument& doc) = 0; void searchRequestFailed(QString reason); + void versionRequestSucceeded(QJsonDocument doc, QString addonId); + + protected slots: + void logoFailed(QString logo); void logoLoaded(QString logo, QIcon out); diff --git a/launcher/ui/pages/modplatform/ModPage.h b/launcher/ui/pages/modplatform/ModPage.h index e0d7c95c..aa0e8894 100644 --- a/launcher/ui/pages/modplatform/ModPage.h +++ b/launcher/ui/pages/modplatform/ModPage.h @@ -37,7 +37,7 @@ class ModPage : public QWidget, public BasePage { virtual bool shouldDisplay() const override = 0; const ModAPI* apiProvider() const { return api.get(); }; - virtual void onRequestVersionsSucceeded(ModPage*, QByteArray*, QString) = 0; + virtual void onRequestVersionsSucceeded(QJsonDocument&, QString) = 0; void openedImpl() override; bool eventFilter(QObject* watched, QEvent* event) override; diff --git a/launcher/ui/pages/modplatform/flame/FlameModModel.cpp b/launcher/ui/pages/modplatform/flame/FlameModModel.cpp index 283f9ce7..cff29a79 100644 --- a/launcher/ui/pages/modplatform/flame/FlameModModel.cpp +++ b/launcher/ui/pages/modplatform/flame/FlameModModel.cpp @@ -11,18 +11,10 @@ ListModel::ListModel(FlameModPage* parent) : ModPlatform::ListModel(parent) {} ListModel::~ListModel() {} -void FlameMod::ListModel::searchRequestFinished() +void FlameMod::ListModel::searchRequestFinished(QJsonDocument& doc) { jobPtr.reset(); - QJsonParseError parse_error; - QJsonDocument doc = QJsonDocument::fromJson(response, &parse_error); - if(parse_error.error != QJsonParseError::NoError) { - qWarning() << "Error while parsing JSON response from Flame at " << parse_error.offset << " reason: " << parse_error.errorString(); - qWarning() << response; - return; - } - QList newList; auto packs = doc.array(); for(auto packRaw : packs) { diff --git a/launcher/ui/pages/modplatform/flame/FlameModModel.h b/launcher/ui/pages/modplatform/flame/FlameModModel.h index ae919e63..022ec32e 100644 --- a/launcher/ui/pages/modplatform/flame/FlameModModel.h +++ b/launcher/ui/pages/modplatform/flame/FlameModModel.h @@ -30,7 +30,7 @@ class ListModel : public ModPlatform::ListModel { virtual ~ListModel(); private slots: - void searchRequestFinished() override; + void searchRequestFinished(QJsonDocument& doc) override; private: const char** getSorts() const override; diff --git a/launcher/ui/pages/modplatform/flame/FlameModPage.cpp b/launcher/ui/pages/modplatform/flame/FlameModPage.cpp index 6564265c..19f58280 100644 --- a/launcher/ui/pages/modplatform/flame/FlameModPage.cpp +++ b/launcher/ui/pages/modplatform/flame/FlameModPage.cpp @@ -36,23 +36,17 @@ FlameModPage::FlameModPage(ModDownloadDialog* dialog, BaseInstance* instance) bool FlameModPage::shouldDisplay() const { return true; } -void FlameModPage::onRequestVersionsSucceeded(ModPage* instance, QByteArray* response, QString addonId) +void FlameModPage::onRequestVersionsSucceeded(QJsonDocument& doc, QString addonId) { if (addonId != current.addonId) { return; // wrong request } - QJsonParseError parse_error; - QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error); - if (parse_error.error != QJsonParseError::NoError) { - qWarning() << "Error while parsing JSON response from Flame at " << parse_error.offset << " reason: " << parse_error.errorString(); - qWarning() << *response; - return; - } + QJsonArray arr = doc.array(); try { FlameMod::loadIndexedPackVersions(current, arr, APPLICATION->network(), m_instance); } catch (const JSONValidationError& e) { - qDebug() << *response; + qDebug() << doc; qWarning() << "Error while reading Flame mod version: " << e.cause(); } auto packProfile = ((MinecraftInstance*)m_instance)->getPackProfile(); diff --git a/launcher/ui/pages/modplatform/flame/FlameModPage.h b/launcher/ui/pages/modplatform/flame/FlameModPage.h index 9c6f0e6c..f15b51ec 100644 --- a/launcher/ui/pages/modplatform/flame/FlameModPage.h +++ b/launcher/ui/pages/modplatform/flame/FlameModPage.h @@ -22,5 +22,5 @@ class FlameModPage : public ModPage { bool shouldDisplay() const override; private: - void onRequestVersionsSucceeded(ModPage*, QByteArray*, QString) override; + void onRequestVersionsSucceeded(QJsonDocument&, QString) override; }; diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp index dc3d1469..784b1128 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp @@ -10,19 +10,10 @@ ListModel::ListModel(ModrinthPage* parent) : ModPlatform::ListModel(parent) {} ListModel::~ListModel() {} -void Modrinth::ListModel::searchRequestFinished() +void Modrinth::ListModel::searchRequestFinished(QJsonDocument& doc) { jobPtr.reset(); - - QJsonParseError parse_error; - QJsonDocument doc = QJsonDocument::fromJson(response, &parse_error); - if (parse_error.error != QJsonParseError::NoError) { - qWarning() << "Error while parsing JSON response from Modrinth at " << parse_error.offset - << " reason: " << parse_error.errorString(); - qWarning() << response; - return; - } - + QList newList; auto packs = doc.object().value("hits").toArray(); for (auto packRaw : packs) { diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthModel.h b/launcher/ui/pages/modplatform/modrinth/ModrinthModel.h index b8937b50..d095b18c 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthModel.h +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthModel.h @@ -29,8 +29,8 @@ class ListModel : public ModPlatform::ListModel { ListModel(ModrinthPage* parent); virtual ~ListModel(); - private slots: - void searchRequestFinished() override; + public slots: + void searchRequestFinished(QJsonDocument& doc) override; private: const char** getSorts() const override; diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp index 944f8afb..fa703e38 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp @@ -35,22 +35,15 @@ ModrinthPage::ModrinthPage(ModDownloadDialog* dialog, BaseInstance* instance) bool ModrinthPage::shouldDisplay() const { return true; } -void ModrinthPage::onRequestVersionsSucceeded(ModPage* instance, QByteArray* response, QString addonId) +void ModrinthPage::onRequestVersionsSucceeded(QJsonDocument& response, QString addonId) { if (addonId != current.addonId) { return; } - QJsonParseError parse_error; - QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error); - if (parse_error.error != QJsonParseError::NoError) { - qWarning() << "Error while parsing JSON response from Modrinth at " << parse_error.offset - << " reason: " << parse_error.errorString(); - qWarning() << *response; - return; - } - QJsonArray arr = doc.array(); + + QJsonArray arr = response.array(); try { Modrinth::loadIndexedPackVersions(current, arr, APPLICATION->network(), m_instance); } catch (const JSONValidationError& e) { - qDebug() << *response; + qDebug() << response; qWarning() << "Error while reading Modrinth mod version: " << e.cause(); } auto packProfile = ((MinecraftInstance*)m_instance)->getPackProfile(); diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.h b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.h index 7b1d0a00..f6d1eef0 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.h +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.h @@ -22,5 +22,5 @@ class ModrinthPage : public ModPage { bool shouldDisplay() const override; private: - void onRequestVersionsSucceeded(ModPage*, QByteArray*, QString) override; + void onRequestVersionsSucceeded(QJsonDocument&, QString) override; };