diff options
| author | Sefa Eyeoglu <contact@scrumplex.net> | 2022-05-17 16:09:22 +0200 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2022-05-17 16:09:22 +0200 |
| commit | cc13310083fe8d0c3bb423fa3e31db7b1f7b25e1 (patch) | |
| tree | 19e4613e56c44f0b755a36435f44f3b1d7636058 /launcher/ui/pages | |
| parent | 1012912272d14d8cc8eb1e009579a997b7a35b7f (diff) | |
| parent | ddc3b5eb0bbd17edd60d96f5094d65dbdb922765 (diff) | |
| download | PrismLauncher-cc13310083fe8d0c3bb423fa3e31db7b1f7b25e1.tar.gz PrismLauncher-cc13310083fe8d0c3bb423fa3e31db7b1f7b25e1.tar.bz2 PrismLauncher-cc13310083fe8d0c3bb423fa3e31db7b1f7b25e1.zip | |
Merge pull request #565 from Scrumplex/modrinth-packs
Initial Modrinth modpack support
Diffstat (limited to 'launcher/ui/pages')
11 files changed, 1062 insertions, 80 deletions
diff --git a/launcher/ui/pages/modplatform/ImportPage.cpp b/launcher/ui/pages/modplatform/ImportPage.cpp index 1b53dd40..c7bc13d8 100644 --- a/launcher/ui/pages/modplatform/ImportPage.cpp +++ b/launcher/ui/pages/modplatform/ImportPage.cpp @@ -2,6 +2,7 @@ /* * PolyMC - Minecraft Launcher * Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org> + * 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 @@ -109,7 +110,11 @@ void ImportPage::updateState() { // FIXME: actually do some validation of what's inside here... this is fake AF QFileInfo fi(input); - if(fi.exists() && fi.suffix() == "zip") + // mrpack is a modrinth pack + + // Allow non-latin people to use ZIP files! + auto zip = QMimeDatabase().mimeTypeForUrl(url).suffixes().contains("zip"); + if(fi.exists() && (zip || fi.suffix() == "mrpack")) { QFileInfo fi(url.fileName()); dialog->setSuggestedPack(fi.completeBaseName(), new InstanceImportTask(url)); @@ -144,6 +149,7 @@ void ImportPage::setUrl(const QString& url) void ImportPage::on_modpackBtn_clicked() { auto filter = QMimeDatabase().mimeTypeForName("application/zip").filterString(); + filter += ";;" + tr("Modrinth pack (*.mrpack)"); const QUrl url = QFileDialog::getOpenFileUrl(this, tr("Choose modpack"), modpackUrl(), filter); if (url.isValid()) { diff --git a/launcher/ui/pages/modplatform/ImportPage.ui b/launcher/ui/pages/modplatform/ImportPage.ui index eb63cbe9..77bc5da5 100644 --- a/launcher/ui/pages/modplatform/ImportPage.ui +++ b/launcher/ui/pages/modplatform/ImportPage.ui @@ -11,28 +11,75 @@ </rect> </property> <layout class="QGridLayout" name="gridLayout"> - <item row="1" column="1"> - <widget class="QPushButton" name="modpackBtn"> - <property name="text"> - <string>Browse</string> - </property> - </widget> - </item> - <item row="1" column="0"> + <item row="2" column="0"> <widget class="QLineEdit" name="modpackEdit"> <property name="placeholderText"> <string notr="true">http://</string> </property> </widget> </item> - <item row="0" column="0" colspan="2"> - <widget class="QLabel" name="modpackLabel"> + <item row="2" column="1"> + <widget class="QPushButton" name="modpackBtn"> <property name="text"> - <string>Local file or link to a direct download:</string> + <string>Browse</string> </property> </widget> </item> - <item row="2" column="0" colspan="2"> + <item row="3" column="0" rowspan="2" colspan="2"> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <widget class="QLabel" name="label"> + <property name="text"> + <string>The following file types are implemented (both for local files and URLs):</string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="label_5"> + <property name="text"> + <string>- Curseforge modpacks (ZIP)</string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="label_3"> + <property name="text"> + <string>- Modrinth modpacks (ZIP and mrpack)</string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="label_2"> + <property name="text"> + <string>- PolyMC / MultiMC exported instances (ZIP)</string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="label_4"> + <property name="text"> + <string>- Technic modpacks (ZIP)</string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + </widget> + </item> + </layout> + </item> + <item row="5" column="0" colspan="2"> <spacer name="verticalSpacer"> <property name="orientation"> <enum>Qt::Vertical</enum> @@ -45,6 +92,13 @@ </property> </spacer> </item> + <item row="0" column="0" colspan="2"> + <widget class="QLabel" name="modpackLabel"> + <property name="text"> + <string>Local file or link to a direct download:</string> + </property> + </widget> + </item> </layout> </widget> <resources/> diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthModModel.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthModModel.cpp new file mode 100644 index 00000000..1d9f4d60 --- /dev/null +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthModModel.cpp @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#include "ModrinthModModel.h" + +#include "modplatform/modrinth/ModrinthPackIndex.h" + +namespace Modrinth { + +// NOLINTNEXTLINE(modernize-avoid-c-arrays) +const char* ListModel::sorts[5]{ "relevance", "downloads", "follows", "updated", "newest" }; + +void ListModel::loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj) +{ + Modrinth::loadIndexedPack(m, obj); +} + +void ListModel::loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr) +{ + Modrinth::loadIndexedPackVersions(m, arr, APPLICATION->network(), m_parent->m_instance); +} + +auto ListModel::documentToArray(QJsonDocument& obj) const -> QJsonArray +{ + return obj.object().value("hits").toArray(); +} + +} // namespace Modrinth diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthModModel.h b/launcher/ui/pages/modplatform/modrinth/ModrinthModModel.h new file mode 100644 index 00000000..ae7b0bdd --- /dev/null +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthModModel.h @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#pragma once + +#include "ModrinthModPage.h" + +namespace Modrinth { + +class ListModel : public ModPlatform::ListModel { + Q_OBJECT + + public: + ListModel(ModrinthModPage* parent) : ModPlatform::ListModel(parent){}; + ~ListModel() override = default; + + private: + void loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj) override; + void loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr) override; + + auto documentToArray(QJsonDocument& obj) const -> QJsonArray override; + + // NOLINTNEXTLINE(modernize-avoid-c-arrays) + static const char* sorts[5]; + inline auto getSorts() const -> const char** override { return sorts; }; +}; + +} // namespace Modrinth diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthModPage.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthModPage.cpp new file mode 100644 index 00000000..d3a1f859 --- /dev/null +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthModPage.cpp @@ -0,0 +1,82 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (c) 2022 Sefa Eyeoglu <contact@scrumplex.net> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + * + * This file incorporates work covered by the following copyright and + * permission notice: + * + * Copyright 2013-2021 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "ModrinthModPage.h" +#include "modplatform/modrinth/ModrinthAPI.h" +#include "ui_ModPage.h" + +#include "ModrinthModModel.h" +#include "ui/dialogs/ModDownloadDialog.h" + +ModrinthModPage::ModrinthModPage(ModDownloadDialog* dialog, BaseInstance* instance) + : ModPage(dialog, instance, new ModrinthAPI()) +{ + listModel = new Modrinth::ListModel(this); + ui->packView->setModel(listModel); + + // index is used to set the sorting with the modrinth api + ui->sortByBox->addItem(tr("Sort by Relevance")); + ui->sortByBox->addItem(tr("Sort by Downloads")); + ui->sortByBox->addItem(tr("Sort by Follows")); + ui->sortByBox->addItem(tr("Sort by Last Updated")); + ui->sortByBox->addItem(tr("Sort by Newest")); + + // sometimes Qt just ignores virtual slots and doesn't work as intended it seems, + // so it's best not to connect them in the parent's constructor... + connect(ui->sortByBox, SIGNAL(currentIndexChanged(int)), this, SLOT(triggerSearch())); + connect(ui->packView->selectionModel(), &QItemSelectionModel::currentChanged, this, &ModrinthModPage::onSelectionChanged); + connect(ui->versionSelectionBox, &QComboBox::currentTextChanged, this, &ModrinthModPage::onVersionSelectionChanged); + connect(ui->modSelectionButton, &QPushButton::clicked, this, &ModrinthModPage::onModSelected); +} + +auto ModrinthModPage::validateVersion(ModPlatform::IndexedVersion& ver, QString mineVer, ModAPI::ModLoaderType loader) const -> bool +{ + auto loaderStrings = ModrinthAPI::getModLoaderStrings(loader); + + auto loaderCompatible = false; + for (auto remoteLoader : ver.loaders) + { + if (loaderStrings.contains(remoteLoader)) { + loaderCompatible = true; + break; + } + } + return ver.mcVersion.contains(mineVer) && loaderCompatible; +} + +// I don't know why, but doing this on the parent class makes it so that +// other mod providers start loading before being selected, at least with +// my Qt, so we need to implement this in every derived class... +auto ModrinthModPage::shouldDisplay() const -> bool { return true; } diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthModPage.h b/launcher/ui/pages/modplatform/modrinth/ModrinthModPage.h new file mode 100644 index 00000000..b1e72bfe --- /dev/null +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthModPage.h @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (c) 2022 Sefa Eyeoglu <contact@scrumplex.net> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + * + * This file incorporates work covered by the following copyright and + * permission notice: + * + * Copyright 2013-2021 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "modplatform/ModAPI.h" +#include "ui/pages/modplatform/ModPage.h" + +#include "modplatform/modrinth/ModrinthAPI.h" + +class ModrinthModPage : public ModPage { + Q_OBJECT + + public: + explicit ModrinthModPage(ModDownloadDialog* dialog, BaseInstance* instance); + ~ModrinthModPage() override = default; + + inline auto displayName() const -> QString override { return "Modrinth"; } + inline auto icon() const -> QIcon override { return APPLICATION->getThemedIcon("modrinth"); } + inline auto id() const -> QString override { return "modrinth"; } + inline auto helpPage() const -> QString override { return "Mod-platform"; } + + inline auto debugName() const -> QString override { return "Modrinth"; } + inline auto metaEntryBase() const -> QString override { return "ModrinthPacks"; }; + + auto validateVersion(ModPlatform::IndexedVersion& ver, QString mineVer, ModAPI::ModLoaderType loader = ModAPI::Unspecified) const -> bool override; + + auto shouldDisplay() const -> bool override; +}; diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp index b788860a..7cacf37a 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-3.0-only /* * PolyMC - Minecraft Launcher - * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net> + * 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 @@ -14,30 +14,301 @@ * * You should have received a copy of the GNU General Public License * along with this program. If not, see <https://www.gnu.org/licenses/>. + * + * This file incorporates work covered by the following copyright and + * permission notice: + * + * Copyright 2013-2021 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ #include "ModrinthModel.h" -#include "modplatform/modrinth/ModrinthPackIndex.h" +#include "BuildConfig.h" +#include "Json.h" +#include "minecraft/MinecraftInstance.h" +#include "minecraft/PackProfile.h" +#include "ui/dialogs/ModDownloadDialog.h" + +#include <QMessageBox> namespace Modrinth { -// NOLINTNEXTLINE(modernize-avoid-c-arrays) -const char* ListModel::sorts[5]{ "relevance", "downloads", "follows", "updated", "newest" }; +ModpackListModel::ModpackListModel(ModrinthPage* parent) : QAbstractListModel(parent), m_parent(parent) {} + +auto ModpackListModel::debugName() const -> QString +{ + return m_parent->debugName(); +} + +/******** Make data requests ********/ + +void ModpackListModel::fetchMore(const QModelIndex& parent) +{ + if (parent.isValid()) + return; + if (nextSearchOffset == 0) { + qWarning() << "fetchMore with 0 offset is wrong..."; + return; + } + performPaginatedSearch(); +} + +auto ModpackListModel::data(const QModelIndex& index, int role) const -> QVariant +{ + int pos = index.row(); + if (pos >= modpacks.size() || pos < 0 || !index.isValid()) { + return QString("INVALID INDEX %1").arg(pos); + } + + Modrinth::Modpack pack = modpacks.at(pos); + if (role == Qt::DisplayRole) { + return pack.name; + } else if (role == Qt::ToolTipRole) { + if (pack.description.length() > 100) { + // some magic to prevent to long tooltips and replace html linebreaks + QString edit = pack.description.left(97); + edit = edit.left(edit.lastIndexOf("<br>")).left(edit.lastIndexOf(" ")).append("..."); + return edit; + } + return pack.description; + } else if (role == Qt::DecorationRole) { + if (m_logoMap.contains(pack.iconName)) { + auto icon = m_logoMap.value(pack.iconName); + auto icon_scaled = QIcon(icon.pixmap(48, 48).scaledToWidth(48)); -void ListModel::loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj) + return icon_scaled; + } + QIcon icon = APPLICATION->getThemedIcon("screenshot-placeholder"); + ((ModpackListModel*)this)->requestLogo(pack.iconName, pack.iconUrl.toString()); + return icon; + } else if (role == Qt::UserRole) { + QVariant v; + v.setValue(pack); + return v; + } + + return {}; +} + +void ModpackListModel::performPaginatedSearch() +{ + // TODO: Move to standalone API + NetJob* netJob = new NetJob("Modrinth::SearchModpack", APPLICATION->network()); + auto searchAllUrl = QString(BuildConfig.MODRINTH_PROD_URL + + "/search?" + "offset=%1&" + "limit=%2&" + "query=%3&" + "index=%4&" + "facets=[[\"project_type:modpack\"]]") + .arg(nextSearchOffset) + .arg(m_modpacks_per_page) + .arg(currentSearchTerm) + .arg(currentSort); + + netJob->addNetAction(Net::Download::makeByteArray(QUrl(searchAllUrl), &m_all_response)); + + QObject::connect(netJob, &NetJob::succeeded, this, [this] { + QJsonParseError parse_error_all{}; + + QJsonDocument doc_all = QJsonDocument::fromJson(m_all_response, &parse_error_all); + if (parse_error_all.error != QJsonParseError::NoError) { + qWarning() << "Error while parsing JSON response from " << debugName() << " at " << parse_error_all.offset + << " reason: " << parse_error_all.errorString(); + qWarning() << m_all_response; + return; + } + + searchRequestFinished(doc_all); + }); + QObject::connect(netJob, &NetJob::failed, this, &ModpackListModel::searchRequestFailed); + + jobPtr = netJob; + jobPtr->start(); +} + +void ModpackListModel::refresh() { - Modrinth::loadIndexedPack(m, obj); + if (jobPtr) { + jobPtr->abort(); + searchState = ResetRequested; + return; + } else { + beginResetModel(); + modpacks.clear(); + endResetModel(); + searchState = None; + } + nextSearchOffset = 0; + performPaginatedSearch(); } -void ListModel::loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr) +static auto sortFromIndex(int index) -> QString { - Modrinth::loadIndexedPackVersions(m, arr, APPLICATION->network(), m_parent->m_instance); + switch(index){ + default: + case 1: + return "relevance"; + case 2: + return "downloads"; + case 3: + return "follows"; + case 4: + return "newest"; + case 5: + return "updated"; + } + + return {}; } -auto ListModel::documentToArray(QJsonDocument& obj) const -> QJsonArray +void ModpackListModel::searchWithTerm(const QString& term, const int sort) { - return obj.object().value("hits").toArray(); + if(sort > 5 || sort < 0) + return; + + auto sort_str = sortFromIndex(sort); + + if (currentSearchTerm == term && currentSearchTerm.isNull() == term.isNull() && currentSort == sort_str) { + return; + } + + currentSearchTerm = term; + currentSort = sort_str; + + refresh(); +} + +void ModpackListModel::getLogo(const QString& logo, const QString& logoUrl, LogoCallback callback) +{ + if (m_logoMap.contains(logo)) { + callback(APPLICATION->metacache() + ->resolveEntry("ModrinthPacks", QString("logos/%1").arg(logo.section(".", 0, 0))) + ->getFullPath()); + } else { + requestLogo(logo, logoUrl); + } +} + +void ModpackListModel::requestLogo(QString logo, QString url) +{ + if (m_loadingLogos.contains(logo) || m_failedLogos.contains(logo)) { + return; + } + + MetaEntryPtr entry = + APPLICATION->metacache()->resolveEntry("ModrinthPacks", QString("logos/%1").arg(logo.section(".", 0, 0))); + auto job = new NetJob(QString("%1 Icon Download %2").arg(m_parent->debugName()).arg(logo), APPLICATION->network()); + job->addNetAction(Net::Download::makeCached(QUrl(url), entry)); + + auto fullPath = entry->getFullPath(); + QObject::connect(job, &NetJob::succeeded, this, [this, logo, fullPath, job] { + job->deleteLater(); + emit logoLoaded(logo, QIcon(fullPath)); + if (waitingCallbacks.contains(logo)) { + waitingCallbacks.value(logo)(fullPath); + } + }); + + QObject::connect(job, &NetJob::failed, this, [this, logo, job] { + job->deleteLater(); + emit logoFailed(logo); + }); + + job->start(); + m_loadingLogos.append(logo); +} + +/******** Request callbacks ********/ + +void ModpackListModel::logoLoaded(QString logo, QIcon out) +{ + m_loadingLogos.removeAll(logo); + m_logoMap.insert(logo, out); + for (int i = 0; i < modpacks.size(); i++) { + if (modpacks[i].iconName == logo) { + emit dataChanged(createIndex(i, 0), createIndex(i, 0), { Qt::DecorationRole }); + } + } +} + +void ModpackListModel::logoFailed(QString logo) +{ + m_failedLogos.append(logo); + m_loadingLogos.removeAll(logo); +} + +void ModpackListModel::searchRequestFinished(QJsonDocument& doc_all) +{ + jobPtr.reset(); + + QList<Modrinth::Modpack> newList; + + auto packs_all = doc_all.object().value("hits").toArray(); + for (auto packRaw : packs_all) { + auto packObj = packRaw.toObject(); + + Modrinth::Modpack pack; + try { + Modrinth::loadIndexedPack(pack, packObj); + newList.append(pack); + } catch (const JSONValidationError& e) { + qWarning() << "Error while loading mod from " << m_parent->debugName() << ": " << e.cause(); + continue; + } + } + + if (packs_all.size() < m_modpacks_per_page) { + searchState = Finished; + } else { + nextSearchOffset += m_modpacks_per_page; + searchState = CanPossiblyFetchMore; + } + + beginInsertRows(QModelIndex(), modpacks.size(), modpacks.size() + newList.size() - 1); + modpacks.append(newList); + endInsertRows(); +} + +void ModpackListModel::searchRequestFailed(QString reason) +{ + if (!jobPtr->first()->m_reply) { + // Network error + QMessageBox::critical(nullptr, tr("Error"), tr("A network error occurred. Could not load modpacks.")); + } else if (jobPtr->first()->m_reply && jobPtr->first()->m_reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() == 409) { + // 409 Gone, notify user to update + QMessageBox::critical(nullptr, tr("Error"), + //: %1 refers to the launcher itself + QString("%1 %2") + .arg(m_parent->displayName()) + .arg(tr("API version too old!\nPlease update %1!").arg(BuildConfig.LAUNCHER_NAME))); + } + jobPtr.reset(); + + if (searchState == ResetRequested) { + beginResetModel(); + modpacks.clear(); + endResetModel(); + + nextSearchOffset = 0; + performPaginatedSearch(); + } else { + searchState = Finished; + } } } // namespace Modrinth + +/******** Helpers ********/ diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthModel.h b/launcher/ui/pages/modplatform/modrinth/ModrinthModel.h index 45a6090a..14aa6747 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthModel.h +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthModel.h @@ -1,25 +1,116 @@ +// 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/>. + * + * This file incorporates work covered by the following copyright and + * permission notice: + * + * Copyright 2013-2021 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + #pragma once -#include "ModrinthPage.h" +#include <QAbstractListModel> + +#include "modplatform/modrinth/ModrinthPackManifest.h" +#include "ui/pages/modplatform/modrinth/ModrinthPage.h" + +class ModPage; +class Version; namespace Modrinth { -class ListModel : public ModPlatform::ListModel { +using LogoMap = QMap<QString, QIcon>; +using LogoCallback = std::function<void (QString)>; + +class ModpackListModel : public QAbstractListModel { Q_OBJECT public: - ListModel(ModrinthPage* parent) : ModPlatform::ListModel(parent){}; - ~ListModel() override = default; - - private: - void loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj) override; - void loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr) override; - - auto documentToArray(QJsonDocument& obj) const -> QJsonArray override; - - // NOLINTNEXTLINE(modernize-avoid-c-arrays) - static const char* sorts[5]; - inline auto getSorts() const -> const char** override { return sorts; }; -}; + ModpackListModel(ModrinthPage* parent); + ~ModpackListModel() override = default; + + inline auto rowCount(const QModelIndex& parent) const -> int override { return modpacks.size(); }; + inline auto columnCount(const QModelIndex& parent) const -> int override { return 1; }; + inline auto flags(const QModelIndex& index) const -> Qt::ItemFlags override { return QAbstractListModel::flags(index); }; + + auto debugName() const -> QString; + + /* Retrieve information from the model at a given index with the given role */ + auto data(const QModelIndex& index, int role) const -> QVariant override; + + inline void setActiveJob(NetJob::Ptr ptr) { jobPtr = ptr; } + + /* Ask the API for more information */ + void fetchMore(const QModelIndex& parent) override; + void refresh(); + void searchWithTerm(const QString& term, const int sort); + + void getLogo(const QString& logo, const QString& logoUrl, LogoCallback callback); + + inline auto canFetchMore(const QModelIndex& parent) const -> bool override { return searchState == CanPossiblyFetchMore; }; + + public slots: + void searchRequestFinished(QJsonDocument& doc_all); + void searchRequestFailed(QString reason); -} // namespace Modrinth + protected slots: + + void logoFailed(QString logo); + void logoLoaded(QString logo, QIcon out); + + void performPaginatedSearch(); + + protected: + void requestLogo(QString file, QString url); + + inline auto getMineVersions() const -> std::list<Version>; + + protected: + ModrinthPage* m_parent; + + QList<Modrinth::Modpack> modpacks; + + LogoMap m_logoMap; + QMap<QString, LogoCallback> waitingCallbacks; + QStringList m_failedLogos; + QStringList m_loadingLogos; + + QString currentSearchTerm; + QString currentSort; + int nextSearchOffset = 0; + enum SearchState { None, CanPossiblyFetchMore, ResetRequested, Finished } searchState = None; + + NetJob::Ptr jobPtr; + + QByteArray m_all_response; + QByteArray m_specific_response; + + int m_modpacks_per_page = 20; +}; +} // namespace ModPlatform diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp index 98bde0ae..9bd24b57 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp +++ b/ |
