From 4e9039be2d3bc0357e6bfe577c5f2add8313f8d6 Mon Sep 17 00:00:00 2001 From: timoreo Date: Fri, 14 Jan 2022 09:43:42 +0100 Subject: [PATCH 01/17] Start of mod downloading --- launcher/CMakeLists.txt | 18 ++ launcher/ModDownloadTask.cpp | 25 ++ launcher/ModDownloadTask.h | 36 +++ .../modrinth/ModrinthPackIndex.cpp | 51 ++++ .../modplatform/modrinth/ModrinthPackIndex.h | 46 ++++ launcher/ui/dialogs/ModDownloadDialog.cpp | 110 ++++++++ launcher/ui/dialogs/ModDownloadDialog.h | 65 +++++ launcher/ui/pages/instance/ModFolderPage.cpp | 29 ++ launcher/ui/pages/instance/ModFolderPage.h | 1 + launcher/ui/pages/instance/ModFolderPage.ui | 9 + .../modplatform/modrinth/ModrinthModel.cpp | 256 ++++++++++++++++++ .../modplatform/modrinth/ModrinthModel.h | 77 ++++++ .../modplatform/modrinth/ModrinthPage.cpp | 180 ++++++++++++ .../pages/modplatform/modrinth/ModrinthPage.h | 80 ++++++ .../modplatform/modrinth/ModrinthPage.ui | 90 ++++++ 15 files changed, 1073 insertions(+) create mode 100644 launcher/ModDownloadTask.cpp create mode 100644 launcher/ModDownloadTask.h create mode 100644 launcher/modplatform/modrinth/ModrinthPackIndex.cpp create mode 100644 launcher/modplatform/modrinth/ModrinthPackIndex.h create mode 100644 launcher/ui/dialogs/ModDownloadDialog.cpp create mode 100644 launcher/ui/dialogs/ModDownloadDialog.h create mode 100644 launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp create mode 100644 launcher/ui/pages/modplatform/modrinth/ModrinthModel.h create mode 100644 launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp create mode 100644 launcher/ui/pages/modplatform/modrinth/ModrinthPage.h create mode 100644 launcher/ui/pages/modplatform/modrinth/ModrinthPage.ui diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index b5c52afa..12274b70 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -37,6 +37,10 @@ set(CORE_SOURCES InstanceImportTask.h InstanceImportTask.cpp + # Mod downloading task + ModDownloadTask.h + ModDownloadTask.cpp + # Use tracking separate from memory management Usable.h @@ -512,6 +516,11 @@ set(FLAME_SOURCES modplatform/flame/FileResolvingTask.cpp ) +set(MODRINTH_SOURCES + modplatform/modrinth/ModrinthPackIndex.cpp + modplatform/modrinth/ModrinthPackIndex.h +) + set(MODPACKSCH_SOURCES modplatform/modpacksch/FTBPackInstallTask.h modplatform/modpacksch/FTBPackInstallTask.cpp @@ -566,6 +575,7 @@ set(LOGIC_SOURCES ${ICONS_SOURCES} ${FTB_SOURCES} ${FLAME_SOURCES} + ${MODRINTH_SOURCES} ${MODPACKSCH_SOURCES} ${TECHNIC_SOURCES} ${ATLAUNCHER_SOURCES} @@ -748,6 +758,11 @@ SET(LAUNCHER_SOURCES ui/pages/modplatform/ImportPage.cpp ui/pages/modplatform/ImportPage.h + ui/pages/modplatform/modrinth/ModrinthModel.cpp + ui/pages/modplatform/modrinth/ModrinthModel.h + ui/pages/modplatform/modrinth/ModrinthPage.cpp + ui/pages/modplatform/modrinth/ModrinthPage.h + # GUI - dialogs ui/dialogs/AboutDialog.cpp ui/dialogs/AboutDialog.h @@ -785,6 +800,8 @@ SET(LAUNCHER_SOURCES ui/dialogs/VersionSelectDialog.h ui/dialogs/SkinUploadDialog.cpp ui/dialogs/SkinUploadDialog.h + ui/dialogs/ModDownloadDialog.cpp + ui/dialogs/ModDownloadDialog.h # GUI - widgets @@ -865,6 +882,7 @@ qt5_wrap_ui(LAUNCHER_UI ui/pages/modplatform/ImportPage.ui ui/pages/modplatform/ftb/FtbPage.ui ui/pages/modplatform/technic/TechnicPage.ui + ui/pages/modplatform/modrinth/ModrinthPage.ui ui/widgets/InstanceCardWidget.ui ui/widgets/CustomCommands.ui ui/widgets/MCModInfoFrame.ui diff --git a/launcher/ModDownloadTask.cpp b/launcher/ModDownloadTask.cpp new file mode 100644 index 00000000..22955470 --- /dev/null +++ b/launcher/ModDownloadTask.cpp @@ -0,0 +1,25 @@ +/* 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 "ModDownloadTask.h" + +ModDownloadTask::ModDownloadTask(const QUrl sourceUrl) { + m_sourceUrl = sourceUrl; +} + +void ModDownloadTask::executeTask() { + //TODO actually install the mod + emitSucceeded(); +} diff --git a/launcher/ModDownloadTask.h b/launcher/ModDownloadTask.h new file mode 100644 index 00000000..067bd91c --- /dev/null +++ b/launcher/ModDownloadTask.h @@ -0,0 +1,36 @@ +/* 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 "QObjectPtr.h" +#include "tasks/Task.h" +#include + + +class ModDownloadTask : public Task { + Q_OBJECT +public: + explicit ModDownloadTask(const QUrl sourceUrl); + +protected: + //! Entry point for tasks. + void executeTask() override; + +private: + QUrl m_sourceUrl; +}; + + + diff --git a/launcher/modplatform/modrinth/ModrinthPackIndex.cpp b/launcher/modplatform/modrinth/ModrinthPackIndex.cpp new file mode 100644 index 00000000..fa421ab2 --- /dev/null +++ b/launcher/modplatform/modrinth/ModrinthPackIndex.cpp @@ -0,0 +1,51 @@ +#include +#include "ModrinthPackIndex.h" + +#include "Json.h" +#include "net/NetJob.h" + +void Modrinth::loadIndexedPack(Modrinth::IndexedPack & pack, QJsonObject & obj) +{ + pack.addonId = Json::requireString(obj, "mod_id"); + pack.name = Json::requireString(obj, "title"); + pack.websiteUrl = Json::ensureString(obj, "page_url", ""); + pack.description = Json::ensureString(obj, "description", ""); + + pack.logoUrl = Json::requireString(obj, "icon_url"); + pack.logoName = "logoName"; + + Modrinth::ModpackAuthor packAuthor; + packAuthor.name = Json::requireString(obj, "author"); + packAuthor.url = Json::requireString(obj, "author_url"); + pack.authors.append(packAuthor); //TODO delete this ? only one author ever exists +} + +void Modrinth::loadIndexedPackVersions(Modrinth::IndexedPack & pack, QJsonArray & arr, const shared_qobject_ptr& network) +{ + QVector unsortedVersions; + for(auto versionIter: arr) { + auto obj = versionIter.toObject(); + Modrinth::IndexedVersion file; + file.addonId = Json::requireString(obj,"mod_id") ; + file.fileId = Json::requireString(obj, "id"); + file.date = Json::requireString(obj, "date_published"); + auto versionArray = Json::requireArray(obj, "game_versions"); + if (versionArray.empty()) { + continue; + } + // pick the latest version supported + file.mcVersion = versionArray[0].toString(); + file.version = Json::requireString(obj, "name"); + //TODO show all the files ? + file.downloadUrl = Json::requireString(Json::requireArray(obj, "files")[0].toObject(),"url"); + unsortedVersions.append(file); + } + auto orderSortPredicate = [](const IndexedVersion & a, const IndexedVersion & b) -> bool + { + //dates are in RFC 3339 format + return a.date > b.date; + }; + std::sort(unsortedVersions.begin(), unsortedVersions.end(), orderSortPredicate); + pack.versions = unsortedVersions; + pack.versionsLoaded = true; +} diff --git a/launcher/modplatform/modrinth/ModrinthPackIndex.h b/launcher/modplatform/modrinth/ModrinthPackIndex.h new file mode 100644 index 00000000..afc31ff2 --- /dev/null +++ b/launcher/modplatform/modrinth/ModrinthPackIndex.h @@ -0,0 +1,46 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include "net/NetJob.h" + +namespace Modrinth { + +struct ModpackAuthor { + QString name; + QString url; +}; + +struct IndexedVersion { + QString addonId; + QString fileId; + QString version; + QString mcVersion; + QString downloadUrl; + QString date; +}; + +struct IndexedPack +{ + QString addonId; + QString name; + QString description; + QList authors; + QString logoName; + QString logoUrl; + QString websiteUrl; + + bool versionsLoaded = false; + QVector versions; +}; + +void loadIndexedPack(IndexedPack & m, QJsonObject & obj); +void loadIndexedPackVersions(IndexedPack & m, QJsonArray & arr, const shared_qobject_ptr& network); +void versionJobFinished(); +} + +Q_DECLARE_METATYPE(Modrinth::IndexedPack) diff --git a/launcher/ui/dialogs/ModDownloadDialog.cpp b/launcher/ui/dialogs/ModDownloadDialog.cpp new file mode 100644 index 00000000..a40980ef --- /dev/null +++ b/launcher/ui/dialogs/ModDownloadDialog.cpp @@ -0,0 +1,110 @@ +/* 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 "ModDownloadDialog.h" + +#include +#include +#include + +#include "ProgressDialog.h" + +#include +#include +#include +#include + +#include "ui/widgets/PageContainer.h" +#include "ui/pages/modplatform/modrinth/ModrinthPage.h" +#include "ModDownloadTask.h" + + +ModDownloadDialog::ModDownloadDialog(const std::shared_ptr& mods, QWidget *parent) + : QDialog(parent) +{ + setObjectName(QStringLiteral("ModDownloadDialog")); + resize(400, 347); + m_verticalLayout = new QVBoxLayout(this); + m_verticalLayout->setObjectName(QStringLiteral("verticalLayout")); + + setWindowIcon(APPLICATION->getThemedIcon("new")); + // NOTE: m_buttons must be initialized before PageContainer, because it indirectly accesses m_buttons through setSuggestedPack! Do not move this below. + m_buttons = new QDialogButtonBox(QDialogButtonBox::Help | QDialogButtonBox::Ok | QDialogButtonBox::Cancel); + + m_container = new PageContainer(this); + m_container->setSizePolicy(QSizePolicy::Policy::Preferred, QSizePolicy::Policy::Expanding); + m_container->layout()->setContentsMargins(0, 0, 0, 0); + m_verticalLayout->addWidget(m_container); + + m_container->addButtons(m_buttons); + + // Bonk Qt over its stupid head and make sure it understands which button is the default one... + // See: https://stackoverflow.com/questions/24556831/qbuttonbox-set-default-button + auto OkButton = m_buttons->button(QDialogButtonBox::Ok); + OkButton->setDefault(true); + OkButton->setAutoDefault(true); + connect(OkButton, &QPushButton::clicked, this, &ModDownloadDialog::accept); + + auto CancelButton = m_buttons->button(QDialogButtonBox::Cancel); + CancelButton->setDefault(false); + CancelButton->setAutoDefault(false); + connect(CancelButton, &QPushButton::clicked, this, &ModDownloadDialog::reject); + + auto HelpButton = m_buttons->button(QDialogButtonBox::Help); + HelpButton->setDefault(false); + HelpButton->setAutoDefault(false); + connect(HelpButton, &QPushButton::clicked, m_container, &PageContainer::help); + QMetaObject::connectSlotsByName(this); + setWindowModality(Qt::WindowModal); + setWindowTitle("Download mods"); +} + +QString ModDownloadDialog::dialogTitle() +{ + return tr("Download mods"); +} + +void ModDownloadDialog::reject() +{ + QDialog::reject(); +} + +void ModDownloadDialog::accept() +{ + QDialog::accept(); +} + +QList ModDownloadDialog::getPages() +{ + modrinthPage = new ModrinthPage(this); + return + { + modrinthPage + }; +} + +void ModDownloadDialog::setSuggestedMod(const QString& name, ModDownloadTask* task) +{ + modTask.reset(task); + m_buttons->button(QDialogButtonBox::Ok)->setEnabled(task); +} + +ModDownloadDialog::~ModDownloadDialog() +{ +} + +ModDownloadTask *ModDownloadDialog::getTask() { + return modTask.release(); +} diff --git a/launcher/ui/dialogs/ModDownloadDialog.h b/launcher/ui/dialogs/ModDownloadDialog.h new file mode 100644 index 00000000..6ce6ff61 --- /dev/null +++ b/launcher/ui/dialogs/ModDownloadDialog.h @@ -0,0 +1,65 @@ +/* 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 +#include + +#include "BaseVersion.h" +#include "ui/pages/BasePageProvider.h" +#include "minecraft/mod/ModFolderModel.h" +#include "ModDownloadTask.h" + +namespace Ui +{ +class ModDownloadDialog; +} + +class PageContainer; +class QDialogButtonBox; +class ModrinthPage; + +class ModDownloadDialog : public QDialog, public BasePageProvider +{ + Q_OBJECT + +public: + explicit ModDownloadDialog(const std::shared_ptr& mods, QWidget *parent = nullptr); + ~ModDownloadDialog(); + + QString dialogTitle() override; + QList getPages() override; + + void setSuggestedMod(const QString & name = QString(), ModDownloadTask * task = nullptr); + + ModDownloadTask * getTask(); + +public slots: + void accept() override; + void reject() override; + +//private slots: + +private: + Ui::ModDownloadDialog *ui = nullptr; + PageContainer * m_container = nullptr; + QDialogButtonBox * m_buttons = nullptr; + QVBoxLayout *m_verticalLayout = nullptr; + + + ModrinthPage *modrinthPage = nullptr; + std::unique_ptr modTask; +}; diff --git a/launcher/ui/pages/instance/ModFolderPage.cpp b/launcher/ui/pages/instance/ModFolderPage.cpp index e63b1434..d2f5dead 100644 --- a/launcher/ui/pages/instance/ModFolderPage.cpp +++ b/launcher/ui/pages/instance/ModFolderPage.cpp @@ -26,6 +26,7 @@ #include "Application.h" #include "ui/dialogs/CustomMessageBox.h" +#include "ui/dialogs/ModDownloadDialog.h" #include "ui/GuiUtil.h" #include "DesktopServices.h" @@ -36,6 +37,7 @@ #include "minecraft/PackProfile.h" #include "Version.h" +#include "ui/dialogs/ProgressDialog.h" namespace { // FIXME: wasteful @@ -342,6 +344,33 @@ void ModFolderPage::on_actionRemove_triggered() m_mods->deleteMods(selection.indexes()); } +void ModFolderPage::on_actionInstall_mods_triggered() +{ + if(!m_controlsEnabled) { + return; + } + ModDownloadDialog mdownload(m_mods, this); + mdownload.exec(); + ModDownloadTask * task = mdownload.getTask(); + if(task){ + connect(task, &Task::failed, [this](QString reason) + { + CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); + }); + connect(task, &Task::succeeded, [this, task]() + { + QStringList warnings = task->warnings(); + if(warnings.count()) + { + CustomMessageBox::selectable(this, tr("Warnings"), warnings.join('\n'), QMessageBox::Warning)->show(); + } + }); + ProgressDialog loadDialog(this); + loadDialog.setSkipButton(true, tr("Abort")); + loadDialog.execWithTask(task); + } +} + void ModFolderPage::on_actionView_configs_triggered() { DesktopServices::openDirectory(m_inst->instanceConfigFolder(), true); diff --git a/launcher/ui/pages/instance/ModFolderPage.h b/launcher/ui/pages/instance/ModFolderPage.h index 8ef7559b..fbda3cd8 100644 --- a/launcher/ui/pages/instance/ModFolderPage.h +++ b/launcher/ui/pages/instance/ModFolderPage.h @@ -102,6 +102,7 @@ slots: void on_actionRemove_triggered(); void on_actionEnable_triggered(); void on_actionDisable_triggered(); + void on_actionInstall_mods_triggered(); void on_actionView_Folder_triggered(); void on_actionView_configs_triggered(); void ShowContextMenu(const QPoint &pos); diff --git a/launcher/ui/pages/instance/ModFolderPage.ui b/launcher/ui/pages/instance/ModFolderPage.ui index 0fb51e84..b5b4c9b2 100644 --- a/launcher/ui/pages/instance/ModFolderPage.ui +++ b/launcher/ui/pages/instance/ModFolderPage.ui @@ -88,6 +88,7 @@ + @@ -136,6 +137,14 @@ View &Folder + + + Install mods + + + Install mods from Modrinth or Curseforge + + diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp new file mode 100644 index 00000000..3bc70e34 --- /dev/null +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp @@ -0,0 +1,256 @@ +#include "ModrinthModel.h" +#include "Application.h" +#include + +#include +#include + +#include +#include + +#include + +namespace Modrinth { + +ListModel::ListModel(QObject *parent) : QAbstractListModel(parent) +{ +} + +ListModel::~ListModel() +{ +} + +int ListModel::rowCount(const QModelIndex &parent) const +{ + return modpacks.size(); +} + +int ListModel::columnCount(const QModelIndex &parent) const +{ + return 1; +} + +QVariant ListModel::data(const QModelIndex &index, int role) const +{ + int pos = index.row(); + if(pos >= modpacks.size() || pos < 0 || !index.isValid()) + { + return QString("INVALID INDEX %1").arg(pos); + } + + IndexedPack 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("
")).left(edit.lastIndexOf(" ")).append("..."); + return edit; + + } + return pack.description; + } + else if(role == Qt::DecorationRole) + { + if(m_logoMap.contains(pack.logoName)) + { + return (m_logoMap.value(pack.logoName)); + } + QIcon icon = APPLICATION->getThemedIcon("screenshot-placeholder"); + ((ListModel *)this)->requestLogo(pack.logoName, pack.logoUrl); + return icon; + } + else if(role == Qt::UserRole) + { + QVariant v; + v.setValue(pack); + return v; + } + + return QVariant(); +} + +void ListModel::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].logoName == logo) { + emit dataChanged(createIndex(i, 0), createIndex(i, 0), {Qt::DecorationRole}); + } + } +} + +void ListModel::logoFailed(QString logo) +{ + m_failedLogos.append(logo); + m_loadingLogos.removeAll(logo); +} + +void ListModel::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))); + NetJob *job = new NetJob(QString("Modrinth Icon Download %1").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] + { + emit logoLoaded(logo, QIcon(fullPath)); + if(waitingCallbacks.contains(logo)) + { + waitingCallbacks.value(logo)(fullPath); + } + }); + + QObject::connect(job, &NetJob::failed, this, [this, logo] + { + emit logoFailed(logo); + }); + + job->start(); + + m_loadingLogos.append(logo); +} + +void ListModel::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); + } +} + +Qt::ItemFlags ListModel::flags(const QModelIndex &index) const +{ + return QAbstractListModel::flags(index); +} + +bool ListModel::canFetchMore(const QModelIndex& parent) const +{ + return searchState == CanPossiblyFetchMore; +} + +void ListModel::fetchMore(const QModelIndex& parent) +{ + if (parent.isValid()) + return; + if(nextSearchOffset == 0) { + qWarning() << "fetchMore with 0 offset is wrong..."; + return; + } + performPaginatedSearch(); +} +const char* sorts[4]{"relevance","downloads","updated","newest"}; + +void ListModel::performPaginatedSearch() +{ + NetJob *netJob = new NetJob("Modrinth::Search", APPLICATION->network()); + auto searchUrl = QString( + "https://api.modrinth.com/api/v1/mod?" + "offset=%1&" + "limit=25&" + "query=%2&" + "index=%3" + ).arg(nextSearchOffset).arg(currentSearchTerm).arg(sorts[currentSort]); + 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); +} + +void ListModel::searchWithTerm(const QString& term, int sort) +{ + if(currentSearchTerm == term && currentSearchTerm.isNull() == term.isNull() && currentSort == sort) { + return; + } + currentSearchTerm = term; + currentSort = sort; + if(jobPtr) { + jobPtr->abort(); + searchState = ResetRequested; + return; + } + else { + beginResetModel(); + modpacks.clear(); + endResetModel(); + searchState = None; + } + nextSearchOffset = 0; + performPaginatedSearch(); +} + +void Modrinth::ListModel::searchRequestFinished() +{ + 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) { + auto packObj = packRaw.toObject(); + + Modrinth::IndexedPack pack; + try + { + Modrinth::loadIndexedPack(pack, packObj); + newList.append(pack); + } + catch(const JSONValidationError &e) + { + qWarning() << "Error while loading mod from Modrinth: " << e.cause(); + continue; + } + } + if(packs.size() < 25) { + searchState = Finished; + } else { + nextSearchOffset += 25; + searchState = CanPossiblyFetchMore; + } + beginInsertRows(QModelIndex(), modpacks.size(), modpacks.size() + newList.size() - 1); + modpacks.append(newList); + endInsertRows(); +} + +void Modrinth::ListModel::searchRequestFailed(QString reason) +{ + jobPtr.reset(); + + if(searchState == ResetRequested) { + beginResetModel(); + modpacks.clear(); + endResetModel(); + + nextSearchOffset = 0; + performPaginatedSearch(); + } else { + searchState = Finished; + } +} + +} + diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthModel.h b/launcher/ui/pages/modplatform/modrinth/ModrinthModel.h new file mode 100644 index 00000000..7bd06f6a --- /dev/null +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthModel.h @@ -0,0 +1,77 @@ +#pragma once + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include "modplatform/modrinth/ModrinthPackIndex.h" + +namespace Modrinth { + + +typedef QMap LogoMap; +typedef std::function LogoCallback; + +class ListModel : public QAbstractListModel +{ + Q_OBJECT + +public: + ListModel(QObject *parent); + virtual ~ListModel(); + + int rowCount(const QModelIndex &parent) const override; + int columnCount(const QModelIndex &parent) const override; + QVariant data(const QModelIndex &index, int role) const override; + Qt::ItemFlags flags(const QModelIndex &index) const override; + bool canFetchMore(const QModelIndex & parent) const override; + void fetchMore(const QModelIndex & parent) override; + + void getLogo(const QString &logo, const QString &logoUrl, LogoCallback callback); + void searchWithTerm(const QString & term, const int sort); + +private slots: + void performPaginatedSearch(); + + void logoFailed(QString logo); + void logoLoaded(QString logo, QIcon out); + + void searchRequestFinished(); + void searchRequestFailed(QString reason); + +private: + void requestLogo(QString file, QString url); + +private: + QList modpacks; + QStringList m_failedLogos; + QStringList m_loadingLogos; + LogoMap m_logoMap; + QMap waitingCallbacks; + + QString currentSearchTerm; + int currentSort = 0; + int nextSearchOffset = 0; + enum SearchState { + None, + CanPossiblyFetchMore, + ResetRequested, + Finished + } searchState = None; + NetJob::Ptr jobPtr; + QByteArray response; +}; + +} diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp new file mode 100644 index 00000000..ea1800d2 --- /dev/null +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp @@ -0,0 +1,180 @@ +#include "ModrinthPage.h" +#include "ui_ModrinthPage.h" + +#include + +#include "Application.h" +#include "Json.h" +#include "ui/dialogs/ModDownloadDialog.h" +#include "InstanceImportTask.h" +#include "ModrinthModel.h" +#include "ModDownloadTask.h" + +ModrinthPage::ModrinthPage(ModDownloadDialog* dialog, QWidget *parent) + : QWidget(parent), ui(new Ui::ModrinthPage), dialog(dialog) +{ + ui->setupUi(this); + connect(ui->searchButton, &QPushButton::clicked, this, &ModrinthPage::triggerSearch); + ui->searchEdit->installEventFilter(this); + listModel = new Modrinth::ListModel(this); + ui->packView->setModel(listModel); + + ui->versionSelectionBox->view()->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); + ui->versionSelectionBox->view()->parentWidget()->setMaximumHeight(300); + + // index is used to set the sorting with the modrinth api + ui->sortByBox->addItem(tr("Sort by Relevence")); + ui->sortByBox->addItem(tr("Sort by Downloads")); + ui->sortByBox->addItem(tr("Sort by last updated")); + ui->sortByBox->addItem(tr("Sort by newest")); + + connect(ui->sortByBox, SIGNAL(currentIndexChanged(int)), this, SLOT(triggerSearch())); + connect(ui->packView->selectionModel(), &QItemSelectionModel::currentChanged, this, &ModrinthPage::onSelectionChanged); + connect(ui->versionSelectionBox, &QComboBox::currentTextChanged, this, &ModrinthPage::onVersionSelectionChanged); +} + +ModrinthPage::~ModrinthPage() +{ + delete ui; +} + +bool ModrinthPage::eventFilter(QObject* watched, QEvent* event) +{ + if (watched == ui->searchEdit && event->type() == QEvent::KeyPress) { + QKeyEvent* keyEvent = static_cast(event); + if (keyEvent->key() == Qt::Key_Return) { + triggerSearch(); + keyEvent->accept(); + return true; + } + } + return QWidget::eventFilter(watched, event); +} + +bool ModrinthPage::shouldDisplay() const +{ + return true; +} + +void ModrinthPage::openedImpl() +{ + suggestCurrent(); + triggerSearch(); +} + +void ModrinthPage::triggerSearch() +{ + listModel->searchWithTerm(ui->searchEdit->text(), ui->sortByBox->currentIndex()); +} + +void ModrinthPage::onSelectionChanged(QModelIndex first, QModelIndex second) +{ + ui->versionSelectionBox->clear(); + + if(!first.isValid()) + { + if(isOpened) + { + dialog->setSuggestedMod(); + } + return; + } + + current = listModel->data(first, Qt::UserRole).value(); + QString text = ""; + QString name = current.name; + + if (current.websiteUrl.isEmpty()) + text = name; + else + text = "" + name + ""; + if (!current.authors.empty()) { + auto authorToStr = [](Modrinth::ModpackAuthor & author) { + if(author.url.isEmpty()) { + return author.name; + } + return QString("%2").arg(author.url, author.name); + }; + QStringList authorStrs; + for(auto & author: current.authors) { + authorStrs.push_back(authorToStr(author)); + } + text += "
" + tr(" by ") + authorStrs.join(", "); + } + text += "

"; + + ui->packDescription->setHtml(text + current.description); + + if (!current.versionsLoaded) + { + qDebug() << "Loading Modrinth mod versions"; + NetJob *netJob = new NetJob(QString("Modrinth::ModVersions(%1)").arg(current.name), APPLICATION->network()); + std::shared_ptr response = std::make_shared(); + QString addonId = current.addonId; + addonId.remove(0,6); + netJob->addNetAction(Net::Download::makeByteArray(QString("https://api.modrinth.com/api/v1/mod/%1/version").arg(addonId), response.get())); + + QObject::connect(netJob, &NetJob::succeeded, this, [this, 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; + } + QJsonArray arr = doc.array(); + try + { + Modrinth::loadIndexedPackVersions(current, arr, APPLICATION->network()); + } + catch(const JSONValidationError &e) + { + qDebug() << *response; + qWarning() << "Error while reading Modrinth mod version: " << e.cause(); + } + + for(auto version : current.versions) { + ui->versionSelectionBox->addItem(version.version, QVariant(version.downloadUrl)); + } + + suggestCurrent(); + }); + netJob->start(); + } + else + { + for(auto version : current.versions) { + ui->versionSelectionBox->addItem(version.version, QVariant(version.downloadUrl)); + } + + suggestCurrent(); + } +} + +void ModrinthPage::suggestCurrent() +{ + if(!isOpened) + { + return; + } + + if (selectedVersion.isEmpty()) + { + dialog->setSuggestedMod(); + return; + } + + dialog->setSuggestedMod(current.name, new ModDownloadTask(selectedVersion)); +} + +void ModrinthPage::onVersionSelectionChanged(QString data) +{ + if(data.isNull() || data.isEmpty()) + { + selectedVersion = ""; + return; + } + selectedVersion = ui->versionSelectionBox->currentData().toString(); + suggestCurrent(); +} diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.h b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.h new file mode 100644 index 00000000..924bc6ce --- /dev/null +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.h @@ -0,0 +1,80 @@ +/* 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 + +#include "ui/pages/BasePage.h" +#include +#include "tasks/Task.h" +#include "modplatform/modrinth/ModrinthPackIndex.h" + +namespace Ui +{ +class ModrinthPage; +} + +class ModDownloadDialog; + +namespace Modrinth { + class ListModel; +} + +class ModrinthPage : public QWidget, public BasePage +{ + Q_OBJECT + +public: + explicit ModrinthPage(ModDownloadDialog* dialog, QWidget *parent = 0); + virtual ~ModrinthPage(); + virtual QString displayName() const override + { + return tr("Modrinth"); + } + virtual QIcon icon() const override + { + return APPLICATION->getThemedIcon("flame"); + } + virtual QString id() const override + { + return "modrinth"; + } + virtual QString helpPage() const override + { + return "Modrinth-platform"; + } + virtual bool shouldDisplay() const override; + + void openedImpl() override; + + bool eventFilter(QObject * watched, QEvent * event) override; + +private: + void suggestCurrent(); + +private slots: + void triggerSearch(); + void onSelectionChanged(QModelIndex first, QModelIndex second); + void onVersionSelectionChanged(QString data); + +private: + Ui::ModrinthPage *ui = nullptr; + ModDownloadDialog* dialog = nullptr; + Modrinth::ListModel* listModel = nullptr; + Modrinth::IndexedPack current; + + QString selectedVersion; +}; diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.ui b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.ui new file mode 100644 index 00000000..6d183de5 --- /dev/null +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.ui @@ -0,0 +1,90 @@ + + + ModrinthPage + + + + 0 + 0 + 837 + 685 + + + + + + + + + + 48 + 48 + + + + Qt::ScrollBarAlwaysOff + + + true + + + + + + + true + + + true + + + + + + + + + + + + + + Version selected: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + + + + Search + + + + + + + Search and filter ... + + + + + + + searchEdit + searchButton + packView + packDescription + sortByBox + versionSelectionBox + + + + From 4d599eb118fb066c6204b29c34d6e1acbc0e8e06 Mon Sep 17 00:00:00 2001 From: timoreo Date: Fri, 14 Jan 2022 10:51:44 +0100 Subject: [PATCH 02/17] Added modrinth icon --- .../multimc/128x128/instances/modrinth.png | Bin 0 -> 10575 bytes .../multimc/32x32/instances/modrinth.png | Bin 0 -> 1913 bytes launcher/resources/multimc/multimc.qrc | 3 +++ .../pages/modplatform/modrinth/ModrinthPage.h | 2 +- 4 files changed, 4 insertions(+), 1 deletion(-) create mode 100644 launcher/resources/multimc/128x128/instances/modrinth.png create mode 100644 launcher/resources/multimc/32x32/instances/modrinth.png diff --git a/launcher/resources/multimc/128x128/instances/modrinth.png b/launcher/resources/multimc/128x128/instances/modrinth.png new file mode 100644 index 0000000000000000000000000000000000000000..740bc8f02469f108db79d92d05484aff17bf8801 GIT binary patch literal 10575 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 literal 0 HcmV?d00001 diff --git a/launcher/resources/multimc/32x32/instances/modrinth.png b/launcher/resources/multimc/32x32/instances/modrinth.png new file mode 100644 index 0000000000000000000000000000000000000000..025ed06534234458f6de2456868d0a66b4324f3b GIT binary patch literal 1913 zcmV-<2Zs2GP)3&289m><@6E99O-OKpvFD)x&4M7Lr726LBnr*iX251# zY=ak;1huGYRobdkkfK&qNs2(JcO2sdCV_^irMOE#t=fh*Qq(0R_ShaEPN9Gj2W$^B z@7>cMGh@bV32pwh_t(7p&Uepw-}l`+=K}xhLv92d+zYKg4BTbevcLedXgCD~m6EfB zIhIVQqjTQt{A)N2^N9*5teQCmezRUHStCkrzaG{Y#z@wnKk-7#`V;T}w*sW7F@FP; z-XemDnMso8VDNV+ISo+J$gQIIX%QKhg*i*tY;G>?s`-xu1b5CE;~SIQtu!7_FW7Gk zYLPHa$?;upMwY`Hh7Zs2>1hM^DZegLA_XGi13=>;k~lX|64~_b;Kvpa+&O2Ae_ZmA z2o{2*F?iV|+^1X1drl7nhM^D!0Wf=df!6xPhRS!rwm02;Jq z$P4T8?pK!edWM{aJv4aEb4^*Kvik%8P)r^GD3J#(ZU1>??aT!L&>M?>l?Hzh5m8z_ z8?4QlbWH(btEWX|JjoADTs8v0it4HbTm#y8&{xJxU}r$MxJb=PkJprAf~p=GOIipW@!?YqCy z`49kv8l8Pg%ag`1=5%-FXAY|&0V`?(pYe@Uew z7ffzjS@&v}j0*APET3rrY~`;74_jA1O5cFxLRt;~;>RUK^r+A_~e!b>cmcNzeI zcq=7cof?WE4z?6WpS^^KH8m%1@qpMz53G6TJPh7~256+exwzvg04!>9iU8yq!zou} zAQ*(6R{=X1gvK>SWOeimDX*oHf`!5Qz^4G9us*OjC(r4%M_9ieYkRA%6%VF+PA>J{ zkO1GZ!1h_6cP{uIarULsc-RHdn+}*=P@jKWsL^>@gbx9Lh)nj|(jIDbnzbzlM9EF5 z?Tf?dm1n1LJc&z3^(_FPM9yS*he`mTGu0@Z4?-Xyj2wYN0E%lYrJfOyQ9!EeA-G9I z?h%c90Dz=xl3dkV5&g%KFf1i_A~laAsS@RmATUAzh~VujG5{BU0szr+Yhmq-J5xla zEB&=oL}V1nJ~zp4SY9$0l$X-P2^#*w^RTLAb@UYgm^R%hgOb|-z)QH1R7PX*(Szgi z=$gmff#jhK?X?b~CJIMsg)adjX(Afed#a_hdmjJU)cQ(|84w8N8FZPVfZ;l!qcej`D9wqU< zh>TaZs`JO4&Y~A*ZVNT!mlxDK_W=M-1tw{Q9T{?ykT15C#$pd|5BM!xS1TnsqEKiv zKFkvg6VO`TbK1E0UMe@Dgz#5azL9KQ)BSr~^cENouK|E4nIQ^aQ?{Vy6J$;q3wcr14Uvv=jq?@vyhKxMRh2dtj)Z z*_I4|A=?viwKh`zPR}r4IGw@Enya8TFxU308VKeAfH6FrI6JuPYQFGBOliW9ZRR2x zyz}wbFJCNJl{a2%eZS>HB?QYvM3W>8U+Rkwlw8dZUROZoeP~x;y=b-dGFv_fqQiLn zmxzccOoJdGB6b$$obk-&%PrWA3IG5+x#Nz>BgVKJKJLXt$QttrT}+0J;9h8TA#k@P zvVaPAWUgk~QfKIS$6RBM&N|uiw;Q>d{eS!$l(>|MSu=Nq00000NkvXXu0mjfgB*o# literal 0 HcmV?d00001 diff --git a/launcher/resources/multimc/multimc.qrc b/launcher/resources/multimc/multimc.qrc index 58b1d763..ef29cf9b 100644 --- a/launcher/resources/multimc/multimc.qrc +++ b/launcher/resources/multimc/multimc.qrc @@ -268,6 +268,9 @@ 32x32/instances/flame.png 128x128/instances/flame.png + 32x32/instances/modrinth.png + 128x128/instances/modrinth.png + 32x32/instances/gear.png 128x128/instances/gear.png diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.h b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.h index 924bc6ce..1f7b12af 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.h +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.h @@ -46,7 +46,7 @@ public: } virtual QIcon icon() const override { - return APPLICATION->getThemedIcon("flame"); + return APPLICATION->getThemedIcon("modrinth"); } virtual QString id() const override { From 9e6fa8f29aa8bc0f609bfcdb6460c6845b73448a Mon Sep 17 00:00:00 2001 From: timoreo Date: Fri, 14 Jan 2022 12:47:18 +0100 Subject: [PATCH 03/17] Added the downloading of the mods --- launcher/ModDownloadTask.cpp | 37 +++++++++++++++++-- launcher/ModDownloadTask.h | 15 +++++++- .../modrinth/ModrinthPackIndex.cpp | 4 +- .../modplatform/modrinth/ModrinthPackIndex.h | 1 + launcher/ui/dialogs/ModDownloadDialog.cpp | 2 +- launcher/ui/dialogs/ModDownloadDialog.h | 1 + .../modplatform/modrinth/ModrinthPage.cpp | 2 +- 7 files changed, 54 insertions(+), 8 deletions(-) diff --git a/launcher/ModDownloadTask.cpp b/launcher/ModDownloadTask.cpp index 22955470..263aa5d0 100644 --- a/launcher/ModDownloadTask.cpp +++ b/launcher/ModDownloadTask.cpp @@ -14,12 +14,41 @@ */ #include "ModDownloadTask.h" +#include "Application.h" -ModDownloadTask::ModDownloadTask(const QUrl sourceUrl) { - m_sourceUrl = sourceUrl; +ModDownloadTask::ModDownloadTask(const QUrl sourceUrl,const QString filename, const std::shared_ptr mods) +: m_sourceUrl(sourceUrl), mods(mods), filename(filename) { } void ModDownloadTask::executeTask() { - //TODO actually install the mod - emitSucceeded(); + setStatus(tr("Downloading mod:\n%1").arg(m_sourceUrl.toString())); + + m_filesNetJob.reset(new NetJob(tr("Modpack download"), APPLICATION->network())); + m_filesNetJob->addNetAction(Net::Download::makeFile(m_sourceUrl, mods->dir().absoluteFilePath(filename))); + connect(m_filesNetJob.get(), &NetJob::succeeded, this, &ModDownloadTask::downloadSucceeded); + connect(m_filesNetJob.get(), &NetJob::progress, this, &ModDownloadTask::downloadProgressChanged); + connect(m_filesNetJob.get(), &NetJob::failed, this, &ModDownloadTask::downloadFailed); + m_filesNetJob->start(); } + +void ModDownloadTask::downloadSucceeded() +{ + emitSucceeded(); + m_filesNetJob.reset(); +} + +void ModDownloadTask::downloadFailed(QString reason) +{ + emitFailed(reason); + m_filesNetJob.reset(); +} + +void ModDownloadTask::downloadProgressChanged(qint64 current, qint64 total) +{ + emit progress(current, total); +} + +bool ModDownloadTask::abort() { + return m_filesNetJob->abort(); +} + diff --git a/launcher/ModDownloadTask.h b/launcher/ModDownloadTask.h index 067bd91c..8b39917c 100644 --- a/launcher/ModDownloadTask.h +++ b/launcher/ModDownloadTask.h @@ -16,20 +16,33 @@ #pragma once #include "QObjectPtr.h" #include "tasks/Task.h" +#include "minecraft/mod/ModFolderModel.h" +#include "net/NetJob.h" #include class ModDownloadTask : public Task { Q_OBJECT public: - explicit ModDownloadTask(const QUrl sourceUrl); + explicit ModDownloadTask(const QUrl sourceUrl, const QString filename, const std::shared_ptr mods); +public slots: + bool abort() override; protected: //! Entry point for tasks. void executeTask() override; private: QUrl m_sourceUrl; + std::shared_ptr m_filesNetJob; + const std::shared_ptr mods; + const QString filename; + + void downloadProgressChanged(qint64 current, qint64 total); + + void downloadFailed(QString reason); + + void downloadSucceeded(); }; diff --git a/launcher/modplatform/modrinth/ModrinthPackIndex.cpp b/launcher/modplatform/modrinth/ModrinthPackIndex.cpp index fa421ab2..fbfaeac8 100644 --- a/launcher/modplatform/modrinth/ModrinthPackIndex.cpp +++ b/launcher/modplatform/modrinth/ModrinthPackIndex.cpp @@ -37,7 +37,9 @@ void Modrinth::loadIndexedPackVersions(Modrinth::IndexedPack & pack, QJsonArray file.mcVersion = versionArray[0].toString(); file.version = Json::requireString(obj, "name"); //TODO show all the files ? - file.downloadUrl = Json::requireString(Json::requireArray(obj, "files")[0].toObject(),"url"); + auto parent = Json::requireArray(obj, "files")[0].toObject(); + file.downloadUrl = Json::requireString(parent, "url"); + file.fileName = Json::requireString(parent, "filename"); unsortedVersions.append(file); } auto orderSortPredicate = [](const IndexedVersion & a, const IndexedVersion & b) -> bool diff --git a/launcher/modplatform/modrinth/ModrinthPackIndex.h b/launcher/modplatform/modrinth/ModrinthPackIndex.h index afc31ff2..e39b69ab 100644 --- a/launcher/modplatform/modrinth/ModrinthPackIndex.h +++ b/launcher/modplatform/modrinth/ModrinthPackIndex.h @@ -22,6 +22,7 @@ struct IndexedVersion { QString mcVersion; QString downloadUrl; QString date; + QString fileName; }; struct IndexedPack diff --git a/launcher/ui/dialogs/ModDownloadDialog.cpp b/launcher/ui/dialogs/ModDownloadDialog.cpp index a40980ef..3b4e11e5 100644 --- a/launcher/ui/dialogs/ModDownloadDialog.cpp +++ b/launcher/ui/dialogs/ModDownloadDialog.cpp @@ -32,7 +32,7 @@ ModDownloadDialog::ModDownloadDialog(const std::shared_ptr& mods, QWidget *parent) - : QDialog(parent) + : QDialog(parent), mods(mods) { setObjectName(QStringLiteral("ModDownloadDialog")); resize(400, 347); diff --git a/launcher/ui/dialogs/ModDownloadDialog.h b/launcher/ui/dialogs/ModDownloadDialog.h index 6ce6ff61..ac40257d 100644 --- a/launcher/ui/dialogs/ModDownloadDialog.h +++ b/launcher/ui/dialogs/ModDownloadDialog.h @@ -46,6 +46,7 @@ public: void setSuggestedMod(const QString & name = QString(), ModDownloadTask * task = nullptr); ModDownloadTask * getTask(); + const std::shared_ptr &mods; public slots: void accept() override; diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp index ea1800d2..b68597ac 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp @@ -165,7 +165,7 @@ void ModrinthPage::suggestCurrent() return; } - dialog->setSuggestedMod(current.name, new ModDownloadTask(selectedVersion)); + dialog->setSuggestedMod(current.name, new ModDownloadTask(selectedVersion, current.versions.at(0).fileName ,dialog->mods)); } void ModrinthPage::onVersionSelectionChanged(QString data) From 1a8c972aefae75ee91295ea5a926cca71d95140a Mon Sep 17 00:00:00 2001 From: timoreo Date: Fri, 14 Jan 2022 20:22:15 +0100 Subject: [PATCH 04/17] Fixed icons Also having a mod loader is now enforced --- launcher/ModDownloadTask.h | 2 +- .../modrinth/ModrinthPackIndex.cpp | 2 +- launcher/ui/dialogs/ModDownloadDialog.cpp | 7 +-- launcher/ui/dialogs/ModDownloadDialog.h | 3 +- launcher/ui/pages/instance/ModFolderPage.cpp | 47 +++++++++++-------- .../modplatform/modrinth/ModrinthModel.cpp | 3 +- .../modplatform/modrinth/ModrinthPage.cpp | 6 ++- .../pages/modplatform/modrinth/ModrinthPage.h | 3 +- 8 files changed, 43 insertions(+), 30 deletions(-) diff --git a/launcher/ModDownloadTask.h b/launcher/ModDownloadTask.h index 8b39917c..950a4048 100644 --- a/launcher/ModDownloadTask.h +++ b/launcher/ModDownloadTask.h @@ -34,7 +34,7 @@ protected: private: QUrl m_sourceUrl; - std::shared_ptr m_filesNetJob; + NetJob::Ptr m_filesNetJob; const std::shared_ptr mods; const QString filename; diff --git a/launcher/modplatform/modrinth/ModrinthPackIndex.cpp b/launcher/modplatform/modrinth/ModrinthPackIndex.cpp index fbfaeac8..89e827b4 100644 --- a/launcher/modplatform/modrinth/ModrinthPackIndex.cpp +++ b/launcher/modplatform/modrinth/ModrinthPackIndex.cpp @@ -12,7 +12,7 @@ void Modrinth::loadIndexedPack(Modrinth::IndexedPack & pack, QJsonObject & obj) pack.description = Json::ensureString(obj, "description", ""); pack.logoUrl = Json::requireString(obj, "icon_url"); - pack.logoName = "logoName"; + pack.logoName = pack.addonId; Modrinth::ModpackAuthor packAuthor; packAuthor.name = Json::requireString(obj, "author"); diff --git a/launcher/ui/dialogs/ModDownloadDialog.cpp b/launcher/ui/dialogs/ModDownloadDialog.cpp index 3b4e11e5..ac5639e0 100644 --- a/launcher/ui/dialogs/ModDownloadDialog.cpp +++ b/launcher/ui/dialogs/ModDownloadDialog.cpp @@ -31,8 +31,9 @@ #include "ModDownloadTask.h" -ModDownloadDialog::ModDownloadDialog(const std::shared_ptr& mods, QWidget *parent) - : QDialog(parent), mods(mods) +ModDownloadDialog::ModDownloadDialog(const std::shared_ptr &mods, QWidget *parent, + BaseInstance *instance) + : QDialog(parent), mods(mods), m_instance(instance) { setObjectName(QStringLiteral("ModDownloadDialog")); resize(400, 347); @@ -88,7 +89,7 @@ void ModDownloadDialog::accept() QList ModDownloadDialog::getPages() { - modrinthPage = new ModrinthPage(this); + modrinthPage = new ModrinthPage(this, m_instance); return { modrinthPage diff --git a/launcher/ui/dialogs/ModDownloadDialog.h b/launcher/ui/dialogs/ModDownloadDialog.h index ac40257d..7b2d18a0 100644 --- a/launcher/ui/dialogs/ModDownloadDialog.h +++ b/launcher/ui/dialogs/ModDownloadDialog.h @@ -37,7 +37,7 @@ class ModDownloadDialog : public QDialog, public BasePageProvider Q_OBJECT public: - explicit ModDownloadDialog(const std::shared_ptr& mods, QWidget *parent = nullptr); + explicit ModDownloadDialog(const std::shared_ptr &mods, QWidget *parent, BaseInstance *instance); ~ModDownloadDialog(); QString dialogTitle() override; @@ -63,4 +63,5 @@ private: ModrinthPage *modrinthPage = nullptr; std::unique_ptr modTask; + BaseInstance *m_instance; }; diff --git a/launcher/ui/pages/instance/ModFolderPage.cpp b/launcher/ui/pages/instance/ModFolderPage.cpp index d2f5dead..7ebf66f6 100644 --- a/launcher/ui/pages/instance/ModFolderPage.cpp +++ b/launcher/ui/pages/instance/ModFolderPage.cpp @@ -349,25 +349,34 @@ void ModFolderPage::on_actionInstall_mods_triggered() if(!m_controlsEnabled) { return; } - ModDownloadDialog mdownload(m_mods, this); - mdownload.exec(); - ModDownloadTask * task = mdownload.getTask(); - if(task){ - connect(task, &Task::failed, [this](QString reason) - { - CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); - }); - connect(task, &Task::succeeded, [this, task]() - { - QStringList warnings = task->warnings(); - if(warnings.count()) - { - CustomMessageBox::selectable(this, tr("Warnings"), warnings.join('\n'), QMessageBox::Warning)->show(); - } - }); - ProgressDialog loadDialog(this); - loadDialog.setSkipButton(true, tr("Abort")); - loadDialog.execWithTask(task); + if(m_inst->typeName() != "Minecraft"){ + return; //this is a null instance or a legacy instance + } + bool hasFabric = !((MinecraftInstance *)m_inst)->getPackProfile()->getComponentVersion("net.fabricmc.fabric-loader").isEmpty(); + bool hasForge = !((MinecraftInstance *)m_inst)->getPackProfile()->getComponentVersion("net.minecraftforge").isEmpty(); + if (!hasFabric && !hasForge) { + QMessageBox::critical(this,tr("Error"),tr("Please install a mod loader first !")); + return; + } + ModDownloadDialog mdownload(m_mods, this, m_inst); + if(mdownload.exec()) { + ModDownloadTask *task = mdownload.getTask(); + if (task) { + connect(task, &Task::failed, [this](QString reason) { + CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); + }); + connect(task, &Task::succeeded, [this, task]() { + QStringList warnings = task->warnings(); + if (warnings.count()) { + CustomMessageBox::selectable(this, tr("Warnings"), warnings.join('\n'), + QMessageBox::Warning)->show(); + } + }); + ProgressDialog loadDialog(this); + loadDialog.setSkipButton(true, tr("Abort")); + loadDialog.execWithTask(task); + m_mods->update(); + } } } diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp index 3bc70e34..0242465b 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp @@ -100,7 +100,7 @@ void ListModel::requestLogo(QString logo, QString url) } MetaEntryPtr entry = APPLICATION->metacache()->resolveEntry("ModrinthPacks", QString("logos/%1").arg(logo.section(".", 0, 0))); - NetJob *job = new NetJob(QString("Modrinth Icon Download %1").arg(logo), APPLICATION->network()); + auto job = new NetJob(QString("Modrinth Icon Download %1").arg(logo), APPLICATION->network()); job->addNetAction(Net::Download::makeCached(QUrl(url), entry)); auto fullPath = entry->getFullPath(); @@ -119,7 +119,6 @@ void ListModel::requestLogo(QString logo, QString url) }); job->start(); - m_loadingLogos.append(logo); } diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp index b68597ac..e72a57f6 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp @@ -9,9 +9,11 @@ #include "InstanceImportTask.h" #include "ModrinthModel.h" #include "ModDownloadTask.h" +#include "ui/pages/instance/ModFolderPage.h" +#include "minecraft/PackProfile.h" -ModrinthPage::ModrinthPage(ModDownloadDialog* dialog, QWidget *parent) - : QWidget(parent), ui(new Ui::ModrinthPage), dialog(dialog) +ModrinthPage::ModrinthPage(ModDownloadDialog *dialog, BaseInstance *instance) + : QWidget(dialog), ui(new Ui::ModrinthPage), dialog(dialog), m_instance(instance) { ui->setupUi(this); connect(ui->searchButton, &QPushButton::clicked, this, &ModrinthPage::triggerSearch); diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.h b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.h index 1f7b12af..59671d0e 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.h +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.h @@ -38,7 +38,7 @@ class ModrinthPage : public QWidget, public BasePage Q_OBJECT public: - explicit ModrinthPage(ModDownloadDialog* dialog, QWidget *parent = 0); + explicit ModrinthPage(ModDownloadDialog *dialog, BaseInstance *instance); virtual ~ModrinthPage(); virtual QString displayName() const override { @@ -77,4 +77,5 @@ private: Modrinth::IndexedPack current; QString selectedVersion; + BaseInstance *m_instance; }; From 2896f70cd83761a1248d55b28e2b5cc2cf465049 Mon Sep 17 00:00:00 2001 From: timoreo Date: Fri, 14 Jan 2022 22:07:54 +0100 Subject: [PATCH 05/17] Removing copyrights --- launcher/ModDownloadTask.cpp | 15 --------------- launcher/ModDownloadTask.h | 15 --------------- launcher/ui/dialogs/ModDownloadDialog.cpp | 15 --------------- launcher/ui/dialogs/ModDownloadDialog.h | 15 --------------- .../ui/pages/modplatform/modrinth/ModrinthPage.h | 15 --------------- 5 files changed, 75 deletions(-) diff --git a/launcher/ModDownloadTask.cpp b/launcher/ModDownloadTask.cpp index 263aa5d0..6e5463fc 100644 --- a/launcher/ModDownloadTask.cpp +++ b/launcher/ModDownloadTask.cpp @@ -1,18 +1,3 @@ -/* 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 "ModDownloadTask.h" #include "Application.h" diff --git a/launcher/ModDownloadTask.h b/launcher/ModDownloadTask.h index 950a4048..7e4f1b7d 100644 --- a/launcher/ModDownloadTask.h +++ b/launcher/ModDownloadTask.h @@ -1,18 +1,3 @@ -/* 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 "QObjectPtr.h" #include "tasks/Task.h" diff --git a/launcher/ui/dialogs/ModDownloadDialog.cpp b/launcher/ui/dialogs/ModDownloadDialog.cpp index ac5639e0..86ca050b 100644 --- a/launcher/ui/dialogs/ModDownloadDialog.cpp +++ b/launcher/ui/dialogs/ModDownloadDialog.cpp @@ -1,18 +1,3 @@ -/* 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 "ModDownloadDialog.h" #include diff --git a/launcher/ui/dialogs/ModDownloadDialog.h b/launcher/ui/dialogs/ModDownloadDialog.h index 7b2d18a0..12be7295 100644 --- a/launcher/ui/dialogs/ModDownloadDialog.h +++ b/launcher/ui/dialogs/ModDownloadDialog.h @@ -1,18 +1,3 @@ -/* 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 diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.h b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.h index 59671d0e..8ff5cbe4 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.h +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.h @@ -1,18 +1,3 @@ -/* 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 From 4b37c46889cb8973d8eb8c22f0c45fe36fdb81cf Mon Sep 17 00:00:00 2001 From: timoreo Date: Sat, 15 Jan 2022 08:51:47 +0100 Subject: [PATCH 06/17] Filtering per mod loader & mc version --- .../modrinth/ModrinthPackIndex.cpp | 9 ++++++++- .../modplatform/modrinth/ModrinthPackIndex.h | 3 ++- .../modplatform/modrinth/ModrinthModel.cpp | 19 ++++++++++++------- .../modplatform/modrinth/ModrinthModel.h | 6 ++++-- .../modplatform/modrinth/ModrinthPage.cpp | 13 +++++++++---- .../pages/modplatform/modrinth/ModrinthPage.h | 3 ++- 6 files changed, 37 insertions(+), 16 deletions(-) diff --git a/launcher/modplatform/modrinth/ModrinthPackIndex.cpp b/launcher/modplatform/modrinth/ModrinthPackIndex.cpp index 89e827b4..ce408ca0 100644 --- a/launcher/modplatform/modrinth/ModrinthPackIndex.cpp +++ b/launcher/modplatform/modrinth/ModrinthPackIndex.cpp @@ -34,12 +34,19 @@ void Modrinth::loadIndexedPackVersions(Modrinth::IndexedPack & pack, QJsonArray continue; } // pick the latest version supported - file.mcVersion = versionArray[0].toString(); + for(auto mcVer : versionArray){ + file.mcVersion.append(mcVer.toString()); + } + auto loaders = Json::requireArray(obj,"loaders"); + for(auto loader : loaders){ + file.loaders.append(loader.toString()); + } file.version = Json::requireString(obj, "name"); //TODO show all the files ? auto parent = Json::requireArray(obj, "files")[0].toObject(); file.downloadUrl = Json::requireString(parent, "url"); file.fileName = Json::requireString(parent, "filename"); + unsortedVersions.append(file); } auto orderSortPredicate = [](const IndexedVersion & a, const IndexedVersion & b) -> bool diff --git a/launcher/modplatform/modrinth/ModrinthPackIndex.h b/launcher/modplatform/modrinth/ModrinthPackIndex.h index e39b69ab..b3cffc40 100644 --- a/launcher/modplatform/modrinth/ModrinthPackIndex.h +++ b/launcher/modplatform/modrinth/ModrinthPackIndex.h @@ -19,10 +19,11 @@ struct IndexedVersion { QString addonId; QString fileId; QString version; - QString mcVersion; + QVector mcVersion; QString downloadUrl; QString date; QString fileName; + QVector loaders; }; struct IndexedPack diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp index 0242465b..e7f66768 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp @@ -1,18 +1,19 @@ #include "ModrinthModel.h" #include "Application.h" +#include "minecraft/MinecraftInstance.h" +#include "minecraft/PackProfile.h" +#include "ModrinthPage.h" #include #include #include #include -#include -#include namespace Modrinth { -ListModel::ListModel(QObject *parent) : QAbstractListModel(parent) +ListModel::ListModel(ModrinthPage *parent) : QAbstractListModel(parent) { } @@ -158,14 +159,18 @@ const char* sorts[4]{"relevance","downloads","updated","newest"}; void ListModel::performPaginatedSearch() { - NetJob *netJob = new NetJob("Modrinth::Search", APPLICATION->network()); + + QString mcVersion = ((MinecraftInstance *)((ModrinthPage *)parent())->m_instance)->getPackProfile()->getComponentVersion("net.minecraft"); + bool hasFabric = !((MinecraftInstance *)((ModrinthPage *)parent())->m_instance)->getPackProfile()->getComponentVersion("net.fabricmc.fabric-loader").isEmpty(); + auto netJob = new NetJob("Modrinth::Search", APPLICATION->network()); auto searchUrl = QString( "https://api.modrinth.com/api/v1/mod?" "offset=%1&" "limit=25&" "query=%2&" - "index=%3" - ).arg(nextSearchOffset).arg(currentSearchTerm).arg(sorts[currentSort]); + "index=%3&" + "filters=categories=\"%4\" AND versions=\"%5\"" + ).arg(nextSearchOffset).arg(currentSearchTerm).arg(sorts[currentSort]).arg(hasFabric ? "fabric" : "forge").arg(mcVersion); netJob->addNetAction(Net::Download::makeByteArray(QUrl(searchUrl), &response)); jobPtr = netJob; jobPtr->start(); @@ -173,7 +178,7 @@ void ListModel::performPaginatedSearch() QObject::connect(netJob, &NetJob::failed, this, &ListModel::searchRequestFailed); } -void ListModel::searchWithTerm(const QString& term, int sort) +void ListModel::searchWithTerm(const QString &term, const int sort) { if(currentSearchTerm == term && currentSearchTerm.isNull() == term.isNull() && currentSort == sort) { return; diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthModel.h b/launcher/ui/pages/modplatform/modrinth/ModrinthModel.h index 7bd06f6a..53f1f134 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthModel.h +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthModel.h @@ -17,6 +17,8 @@ #include #include "modplatform/modrinth/ModrinthPackIndex.h" +#include "BaseInstance.h" +#include "ModrinthPage.h" namespace Modrinth { @@ -29,7 +31,7 @@ class ListModel : public QAbstractListModel Q_OBJECT public: - ListModel(QObject *parent); + ListModel(ModrinthPage *parent); virtual ~ListModel(); int rowCount(const QModelIndex &parent) const override; @@ -40,7 +42,7 @@ public: void fetchMore(const QModelIndex & parent) override; void getLogo(const QString &logo, const QString &logoUrl, LogoCallback callback); - void searchWithTerm(const QString & term, const int sort); + void searchWithTerm(const QString &term, const int sort); private slots: void performPaginatedSearch(); diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp index e72a57f6..96797062 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp @@ -9,11 +9,11 @@ #include "InstanceImportTask.h" #include "ModrinthModel.h" #include "ModDownloadTask.h" -#include "ui/pages/instance/ModFolderPage.h" +#include "minecraft/MinecraftInstance.h" #include "minecraft/PackProfile.h" ModrinthPage::ModrinthPage(ModDownloadDialog *dialog, BaseInstance *instance) - : QWidget(dialog), ui(new Ui::ModrinthPage), dialog(dialog), m_instance(instance) + : QWidget(dialog), m_instance(instance), ui(new Ui::ModrinthPage), dialog(dialog) { ui->setupUi(this); connect(ui->searchButton, &QPushButton::clicked, this, &ModrinthPage::triggerSearch); @@ -135,8 +135,13 @@ void ModrinthPage::onSelectionChanged(QModelIndex first, QModelIndex second) qDebug() << *response; qWarning() << "Error while reading Modrinth mod version: " << e.cause(); } - - for(auto version : current.versions) { + auto packProfile = ((MinecraftInstance *)m_instance)->getPackProfile(); + QString mcVersion = packProfile->getComponentVersion("net.minecraft"); + QString loaderString = (packProfile->getComponentVersion("net.minecraftforge").isEmpty()) ? "fabric" : "forge"; + for(const auto& version : current.versions) { + if(!version.mcVersion.contains(mcVersion) || !version.loaders.contains(loaderString)){ + continue; + } ui->versionSelectionBox->addItem(version.version, QVariant(version.downloadUrl)); } diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.h b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.h index 8ff5cbe4..3748d836 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.h +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.h @@ -47,6 +47,8 @@ public: bool eventFilter(QObject * watched, QEvent * event) override; + BaseInstance *m_instance; + private: void suggestCurrent(); @@ -62,5 +64,4 @@ private: Modrinth::IndexedPack current; QString selectedVersion; - BaseInstance *m_instance; }; From f6de472da2f4b27c941517c17fb604b63d8e21d2 Mon Sep 17 00:00:00 2001 From: timoreo Date: Sat, 15 Jan 2022 09:06:48 +0100 Subject: [PATCH 07/17] Added a no version message --- launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp index 96797062..f58a884c 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp @@ -144,6 +144,9 @@ void ModrinthPage::onSelectionChanged(QModelIndex first, QModelIndex second) } ui->versionSelectionBox->addItem(version.version, QVariant(version.downloadUrl)); } + if(ui->versionSelectionBox->count() == 0){ + ui->versionSelectionBox->addItem("No Valid Version found !", QVariant("")); + } suggestCurrent(); }); @@ -154,7 +157,9 @@ void ModrinthPage::onSelectionChanged(QModelIndex first, QModelIndex second) for(auto version : current.versions) { ui->versionSelectionBox->addItem(version.version, QVariant(version.downloadUrl)); } - + if(ui->versionSelectionBox->count() == 0){ + ui->versionSelectionBox->addItem("No Valid Version found !", QVariant("")); + } suggestCurrent(); } } From 621e0ba4a887ab4dfdde6a6bba2e1c7b209fb6bc Mon Sep 17 00:00:00 2001 From: timoreo Date: Sat, 15 Jan 2022 10:25:24 +0100 Subject: [PATCH 08/17] Added smart file selection This might fail in a few special cases --- launcher/ModDownloadTask.cpp | 2 +- .../modrinth/ModrinthPackIndex.cpp | 41 +++++++++++++++++-- .../modplatform/modrinth/ModrinthPackIndex.h | 4 +- .../modplatform/modrinth/ModrinthPage.cpp | 6 +-- 4 files changed, 43 insertions(+), 10 deletions(-) diff --git a/launcher/ModDownloadTask.cpp b/launcher/ModDownloadTask.cpp index 6e5463fc..08a02d29 100644 --- a/launcher/ModDownloadTask.cpp +++ b/launcher/ModDownloadTask.cpp @@ -8,7 +8,7 @@ ModDownloadTask::ModDownloadTask(const QUrl sourceUrl,const QString filename, co void ModDownloadTask::executeTask() { setStatus(tr("Downloading mod:\n%1").arg(m_sourceUrl.toString())); - m_filesNetJob.reset(new NetJob(tr("Modpack download"), APPLICATION->network())); + m_filesNetJob.reset(new NetJob(tr("Mod download"), APPLICATION->network())); m_filesNetJob->addNetAction(Net::Download::makeFile(m_sourceUrl, mods->dir().absoluteFilePath(filename))); connect(m_filesNetJob.get(), &NetJob::succeeded, this, &ModDownloadTask::downloadSucceeded); connect(m_filesNetJob.get(), &NetJob::progress, this, &ModDownloadTask::downloadProgressChanged); diff --git a/launcher/modplatform/modrinth/ModrinthPackIndex.cpp b/launcher/modplatform/modrinth/ModrinthPackIndex.cpp index ce408ca0..8d47699b 100644 --- a/launcher/modplatform/modrinth/ModrinthPackIndex.cpp +++ b/launcher/modplatform/modrinth/ModrinthPackIndex.cpp @@ -3,6 +3,10 @@ #include "Json.h" #include "net/NetJob.h" +#include "BaseInstance.h" +#include "minecraft/MinecraftInstance.h" +#include "minecraft/PackProfile.h" + void Modrinth::loadIndexedPack(Modrinth::IndexedPack & pack, QJsonObject & obj) { @@ -20,9 +24,12 @@ void Modrinth::loadIndexedPack(Modrinth::IndexedPack & pack, QJsonObject & obj) pack.authors.append(packAuthor); //TODO delete this ? only one author ever exists } -void Modrinth::loadIndexedPackVersions(Modrinth::IndexedPack & pack, QJsonArray & arr, const shared_qobject_ptr& network) +void Modrinth::loadIndexedPackVersions(Modrinth::IndexedPack & pack, QJsonArray & arr, const shared_qobject_ptr& network, BaseInstance * inst) { QVector unsortedVersions; + bool hasFabric = !((MinecraftInstance *)inst)->getPackProfile()->getComponentVersion("net.fabricmc.fabric-loader").isEmpty(); + QString mcVersion = ((MinecraftInstance *)inst)->getPackProfile()->getComponentVersion("net.minecraft"); + for(auto versionIter: arr) { auto obj = versionIter.toObject(); Modrinth::IndexedVersion file; @@ -33,7 +40,6 @@ void Modrinth::loadIndexedPackVersions(Modrinth::IndexedPack & pack, QJsonArray if (versionArray.empty()) { continue; } - // pick the latest version supported for(auto mcVer : versionArray){ file.mcVersion.append(mcVer.toString()); } @@ -42,8 +48,35 @@ void Modrinth::loadIndexedPackVersions(Modrinth::IndexedPack & pack, QJsonArray file.loaders.append(loader.toString()); } file.version = Json::requireString(obj, "name"); - //TODO show all the files ? - auto parent = Json::requireArray(obj, "files")[0].toObject(); + + auto files = Json::requireArray(obj, "files"); + int i = 0; + while (files.count() > 1 && i < files.count()){ + //try to resolve the correct file + auto parent = files[i].toObject(); + auto fileName = Json::requireString(parent, "filename"); + //avoid grabbing "dev" files + if(fileName.contains("javadocs",Qt::CaseInsensitive) || fileName.contains("sources",Qt::CaseInsensitive)){ + i++; + continue; + } + //grab the correct mod loader + if(fileName.contains("forge",Qt::CaseInsensitive) || fileName.contains("fabric",Qt::CaseInsensitive) ){ + if(hasFabric){ + if(fileName.contains("forge",Qt::CaseInsensitive)){ + i++; + continue; + } + }else{ + if(fileName.contains("fabric",Qt::CaseInsensitive)){ + i++; + continue; + } + } + } + break; + } + auto parent = files[i].toObject(); file.downloadUrl = Json::requireString(parent, "url"); file.fileName = Json::requireString(parent, "filename"); diff --git a/launcher/modplatform/modrinth/ModrinthPackIndex.h b/launcher/modplatform/modrinth/ModrinthPackIndex.h index b3cffc40..01ae4b44 100644 --- a/launcher/modplatform/modrinth/ModrinthPackIndex.h +++ b/launcher/modplatform/modrinth/ModrinthPackIndex.h @@ -7,6 +7,7 @@ #include #include #include "net/NetJob.h" +#include "BaseInstance.h" namespace Modrinth { @@ -41,8 +42,7 @@ struct IndexedPack }; void loadIndexedPack(IndexedPack & m, QJsonObject & obj); -void loadIndexedPackVersions(IndexedPack & m, QJsonArray & arr, const shared_qobject_ptr& network); -void versionJobFinished(); +void loadIndexedPackVersions(IndexedPack &pack, QJsonArray &arr, const shared_qobject_ptr &network, BaseInstance *inst); } Q_DECLARE_METATYPE(Modrinth::IndexedPack) diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp index f58a884c..fe5766dc 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp @@ -128,7 +128,7 @@ void ModrinthPage::onSelectionChanged(QModelIndex first, QModelIndex second) QJsonArray arr = doc.array(); try { - Modrinth::loadIndexedPackVersions(current, arr, APPLICATION->network()); + Modrinth::loadIndexedPackVersions(current, arr, APPLICATION->network(), m_instance); } catch(const JSONValidationError &e) { @@ -145,7 +145,7 @@ void ModrinthPage::onSelectionChanged(QModelIndex first, QModelIndex second) ui->versionSelectionBox->addItem(version.version, QVariant(version.downloadUrl)); } if(ui->versionSelectionBox->count() == 0){ - ui->versionSelectionBox->addItem("No Valid Version found !", QVariant("")); + ui->versionSelectionBox->addItem(tr("No Valid Version found !"), QVariant("")); } suggestCurrent(); @@ -158,7 +158,7 @@ void ModrinthPage::onSelectionChanged(QModelIndex first, QModelIndex second) ui->versionSelectionBox->addItem(version.version, QVariant(version.downloadUrl)); } if(ui->versionSelectionBox->count() == 0){ - ui->versionSelectionBox->addItem("No Valid Version found !", QVariant("")); + ui->versionSelectionBox->addItem(tr("No Valid Version found !"), QVariant("")); } suggestCurrent(); } From 975f77756d7ffeb94fb14355b622ee850e04bd8b Mon Sep 17 00:00:00 2001 From: timoreo Date: Sun, 16 Jan 2022 11:20:21 +0100 Subject: [PATCH 09/17] Added curseforge selection --- launcher/CMakeLists.txt | 7 + launcher/modplatform/flame/FlameModIndex.cpp | 99 +++++++ launcher/modplatform/flame/FlameModIndex.h | 50 ++++ launcher/ui/dialogs/ModDownloadDialog.cpp | 4 +- launcher/ui/dialogs/ModDownloadDialog.h | 2 + .../pages/modplatform/flame/FlameModModel.cpp | 265 ++++++++++++++++++ .../pages/modplatform/flame/FlameModModel.h | 79 ++++++ .../pages/modplatform/flame/FlameModPage.cpp | 193 +++++++++++++ .../ui/pages/modplatform/flame/FlameModPage.h | 67 +++++ .../pages/modplatform/flame/FlameModPage.ui | 90 ++++++ 10 files changed, 855 insertions(+), 1 deletion(-) create mode 100644 launcher/modplatform/flame/FlameModIndex.cpp create mode 100644 launcher/modplatform/flame/FlameModIndex.h create mode 100644 launcher/ui/pages/modplatform/flame/FlameModModel.cpp create mode 100644 launcher/ui/pages/modplatform/flame/FlameModModel.h create mode 100644 launcher/ui/pages/modplatform/flame/FlameModPage.cpp create mode 100644 launcher/ui/pages/modplatform/flame/FlameModPage.h create mode 100644 launcher/ui/pages/modplatform/flame/FlameModPage.ui diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index 12274b70..a1aae524 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -510,6 +510,8 @@ set(FLAME_SOURCES # Flame modplatform/flame/FlamePackIndex.cpp modplatform/flame/FlamePackIndex.h + modplatform/flame/FlameModIndex.cpp + modplatform/flame/FlameModIndex.h modplatform/flame/PackManifest.h modplatform/flame/PackManifest.cpp modplatform/flame/FileResolvingTask.h @@ -749,6 +751,10 @@ SET(LAUNCHER_SOURCES ui/pages/modplatform/flame/FlameModel.h ui/pages/modplatform/flame/FlamePage.cpp ui/pages/modplatform/flame/FlamePage.h + ui/pages/modplatform/flame/FlameModModel.cpp + ui/pages/modplatform/flame/FlameModModel.h + ui/pages/modplatform/flame/FlameModPage.cpp + ui/pages/modplatform/flame/FlameModPage.h ui/pages/modplatform/technic/TechnicModel.cpp ui/pages/modplatform/technic/TechnicModel.h @@ -878,6 +884,7 @@ qt5_wrap_ui(LAUNCHER_UI ui/pages/modplatform/atlauncher/AtlPage.ui ui/pages/modplatform/VanillaPage.ui ui/pages/modplatform/flame/FlamePage.ui + ui/pages/modplatform/flame/FlameModPage.ui ui/pages/modplatform/legacy_ftb/Page.ui ui/pages/modplatform/ImportPage.ui ui/pages/modplatform/ftb/FtbPage.ui diff --git a/launcher/modplatform/flame/FlameModIndex.cpp b/launcher/modplatform/flame/FlameModIndex.cpp new file mode 100644 index 00000000..d298ae83 --- /dev/null +++ b/launcher/modplatform/flame/FlameModIndex.cpp @@ -0,0 +1,99 @@ +#include +#include "FlameModIndex.h" +#include "Json.h" +#include "net/NetJob.h" +#include "BaseInstance.h" +#include "minecraft/MinecraftInstance.h" +#include "minecraft/PackProfile.h" + + +void FlameMod::loadIndexedPack(FlameMod::IndexedPack & pack, QJsonObject & obj) +{ + pack.addonId = Json::requireInteger(obj, "id"); + pack.name = Json::requireString(obj, "name"); + pack.websiteUrl = Json::ensureString(obj, "websiteUrl", ""); + pack.description = Json::ensureString(obj, "summary", ""); + + bool thumbnailFound = false; + auto attachments = Json::requireArray(obj, "attachments"); + for(auto attachmentRaw: attachments) { + auto attachmentObj = Json::requireObject(attachmentRaw); + bool isDefault = attachmentObj.value("isDefault").toBool(false); + if(isDefault) { + thumbnailFound = true; + pack.logoName = Json::requireString(attachmentObj, "title"); + pack.logoUrl = Json::requireString(attachmentObj, "thumbnailUrl"); + break; + } + } + + if(!thumbnailFound) { + throw JSONValidationError(QString("Pack without an icon, skipping: %1").arg(pack.name)); + } + + + auto authors = Json::requireArray(obj, "authors"); + for(auto authorIter: authors) { + auto author = Json::requireObject(authorIter); + FlameMod::ModpackAuthor packAuthor; + packAuthor.name = Json::requireString(author, "name"); + packAuthor.url = Json::requireString(author, "url"); + pack.authors.append(packAuthor); + } +} + +void FlameMod::loadIndexedPackVersions(FlameMod::IndexedPack & pack, QJsonArray & arr, const shared_qobject_ptr& network, BaseInstance * inst) +{ + QVector unsortedVersions; + bool hasFabric = !((MinecraftInstance *)inst)->getPackProfile()->getComponentVersion("net.fabricmc.fabric-loader").isEmpty(); + QString mcVersion = ((MinecraftInstance *)inst)->getPackProfile()->getComponentVersion("net.minecraft"); + + for(auto versionIter: arr) { + auto obj = versionIter.toObject(); + FlameMod::IndexedVersion file; + file.addonId = pack.addonId; + file.fileId = Json::requireInteger(obj, "id"); + file.date = Json::requireString(obj, "fileDate"); + auto versionArray = Json::requireArray(obj, "gameVersion"); + if (versionArray.empty()) { + continue; + } + for(auto mcVer : versionArray){ + file.mcVersion.append(mcVer.toString()); + } + + file.version = Json::requireString(obj, "displayName"); + file.downloadUrl = Json::requireString(obj, "downloadUrl"); + file.fileName = Json::requireString(obj, "fileName"); + + auto modules = Json::requireArray(obj, "modules"); + bool valid = false; + for(auto m : modules){ + auto fname = Json::requireString(m.toObject(),"foldername"); + if(hasFabric){ + if(fname == "fabric.mod.json"){ + valid = true; + break; + } + }else{ + if(fname == "mcmod.info"){ + valid = true; + break; + } + } + } + if(!valid){ + continue; + } + + unsortedVersions.append(file); + } + auto orderSortPredicate = [](const IndexedVersion & a, const IndexedVersion & b) -> bool + { + //dates are in RFC 3339 format + return a.date > b.date; + }; + std::sort(unsortedVersions.begin(), unsortedVersions.end(), orderSortPredicate); + pack.versions = unsortedVersions; + pack.versionsLoaded = true; +} \ No newline at end of file diff --git a/launcher/modplatform/flame/FlameModIndex.h b/launcher/modplatform/flame/FlameModIndex.h new file mode 100644 index 00000000..0293bb23 --- /dev/null +++ b/launcher/modplatform/flame/FlameModIndex.h @@ -0,0 +1,50 @@ +// +// Created by timoreo on 16/01/2022. +// + +#pragma once +#include +#include +#include +#include +#include +#include +#include "net/NetJob.h" +#include "BaseInstance.h" + +namespace FlameMod { + struct ModpackAuthor { + QString name; + QString url; + }; + + struct IndexedVersion { + int addonId; + int fileId; + QString version; + QVector mcVersion; + QString downloadUrl; + QString date; + QString fileName; + }; + + struct IndexedPack + { + int addonId; + QString name; + QString description; + QList authors; + QString logoName; + QString logoUrl; + QString websiteUrl; + + bool versionsLoaded = false; + QVector versions; + }; + + void loadIndexedPack(IndexedPack & m, QJsonObject & obj); + void loadIndexedPackVersions(IndexedPack &pack, QJsonArray &arr, const shared_qobject_ptr &network, BaseInstance *inst); + +} + +Q_DECLARE_METATYPE(FlameMod::IndexedPack) diff --git a/launcher/ui/dialogs/ModDownloadDialog.cpp b/launcher/ui/dialogs/ModDownloadDialog.cpp index 86ca050b..6b807b8c 100644 --- a/launcher/ui/dialogs/ModDownloadDialog.cpp +++ b/launcher/ui/dialogs/ModDownloadDialog.cpp @@ -75,9 +75,11 @@ void ModDownloadDialog::accept() QList ModDownloadDialog::getPages() { modrinthPage = new ModrinthPage(this, m_instance); + flameModPage = new FlameModPage(this, m_instance); return { - modrinthPage + modrinthPage, + flameModPage }; } diff --git a/launcher/ui/dialogs/ModDownloadDialog.h b/launcher/ui/dialogs/ModDownloadDialog.h index 12be7295..ece8e328 100644 --- a/launcher/ui/dialogs/ModDownloadDialog.h +++ b/launcher/ui/dialogs/ModDownloadDialog.h @@ -7,6 +7,7 @@ #include "ui/pages/BasePageProvider.h" #include "minecraft/mod/ModFolderModel.h" #include "ModDownloadTask.h" +#include "ui/pages/modplatform/flame/FlameModPage.h" namespace Ui { @@ -47,6 +48,7 @@ private: ModrinthPage *modrinthPage = nullptr; + FlameModPage *flameModPage = nullptr; std::unique_ptr modTask; BaseInstance *m_instance; }; diff --git a/launcher/ui/pages/modplatform/flame/FlameModModel.cpp b/launcher/ui/pages/modplatform/flame/FlameModModel.cpp new file mode 100644 index 00000000..7deaa36c --- /dev/null +++ b/launcher/ui/pages/modplatform/flame/FlameModModel.cpp @@ -0,0 +1,265 @@ +#include "FlameModModel.h" +#include "Application.h" +#include "minecraft/MinecraftInstance.h" +#include "minecraft/PackProfile.h" +#include "FlameModPage.h" +#include + +#include +#include + +#include + + +namespace FlameMod { + +ListModel::ListModel(FlameModPage *parent) : QAbstractListModel(parent) +{ +} + +ListModel::~ListModel() +{ +} + +int ListModel::rowCount(const QModelIndex &parent) const +{ + return modpacks.size(); +} + +int ListModel::columnCount(const QModelIndex &parent) const +{ + return 1; +} + +QVariant ListModel::data(const QModelIndex &index, int role) const +{ + int pos = index.row(); + if(pos >= modpacks.size() || pos < 0 || !index.isValid()) + { + return QString("INVALID INDEX %1").arg(pos); + } + + IndexedPack 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("
")).left(edit.lastIndexOf(" ")).append("..."); + return edit; + + } + return pack.description; + } + else if(role == Qt::DecorationRole) + { + if(m_logoMap.contains(pack.logoName)) + { + return (m_logoMap.value(pack.logoName)); + } + QIcon icon = APPLICATION->getThemedIcon("screenshot-placeholder"); + ((ListModel *)this)->requestLogo(pack.logoName, pack.logoUrl); + return icon; + } + else if(role == Qt::UserRole) + { + QVariant v; + v.setValue(pack); + return v; + } + + return QVariant(); +} + +void ListModel::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].logoName == logo) { + emit dataChanged(createIndex(i, 0), createIndex(i, 0), {Qt::DecorationRole}); + } + } +} + +void ListModel::logoFailed(QString logo) +{ + m_failedLogos.append(logo); + m_loadingLogos.removeAll(logo); +} + +void ListModel::requestLogo(QString logo, QString url) +{ + if(m_loadingLogos.contains(logo) || m_failedLogos.contains(logo)) + { + return; + } + + MetaEntryPtr entry = APPLICATION->metacache()->resolveEntry("FlameMods", QString("logos/%1").arg(logo.section(".", 0, 0))); + auto job = new NetJob(QString("Flame Icon Download %1").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] + { + emit logoLoaded(logo, QIcon(fullPath)); + if(waitingCallbacks.contains(logo)) + { + waitingCallbacks.value(logo)(fullPath); + } + }); + + QObject::connect(job, &NetJob::failed, this, [this, logo] + { + emit logoFailed(logo); + }); + + job->start(); + m_loadingLogos.append(logo); +} + +void ListModel::getLogo(const QString &logo, const QString &logoUrl, LogoCallback callback) +{ + if(m_logoMap.contains(logo)) + { + callback(APPLICATION->metacache()->resolveEntry("FlameMods", QString("logos/%1").arg(logo.section(".", 0, 0)))->getFullPath()); + } + else + { + requestLogo(logo, logoUrl); + } +} + +Qt::ItemFlags ListModel::flags(const QModelIndex &index) const +{ + return QAbstractListModel::flags(index); +} + +bool ListModel::canFetchMore(const QModelIndex& parent) const +{ + return searchState == CanPossiblyFetchMore; +} + +void ListModel::fetchMore(const QModelIndex& parent) +{ + if (parent.isValid()) + return; + if(nextSearchOffset == 0) { + qWarning() << "fetchMore with 0 offset is wrong..."; + return; + } + performPaginatedSearch(); +} +const char* sorts[6]{"Featured","Popularity","LastUpdated","Name","Author","TotalDownloads"}; + +void ListModel::performPaginatedSearch() +{ + + QString mcVersion = ((MinecraftInstance *)((FlameModPage *)parent())->m_instance)->getPackProfile()->getComponentVersion("net.minecraft"); + bool hasFabric = !((MinecraftInstance *)((FlameModPage *)parent())->m_instance)->getPackProfile()->getComponentVersion("net.fabricmc.fabric-loader").isEmpty(); + auto netJob = new NetJob("Flame::Search", APPLICATION->network()); + auto searchUrl = QString( + "https://addons-ecs.forgesvc.net/api/v2/addon/search?" + "gameId=432&" + "categoryId=0&" + "sectionId=6&" + + "index=%1&" + "pageSize=25&" + "searchFilter=%2&" + "sort=%3&" + "%4" + "gameVersion=%5" + ).arg(nextSearchOffset).arg(currentSearchTerm).arg(sorts[currentSort]).arg(hasFabric ? "modLoaderType=4&" : "").arg(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); +} + +void ListModel::searchWithTerm(const QString &term, const int sort) +{ + if(currentSearchTerm == term && currentSearchTerm.isNull() == term.isNull() && currentSort == sort) { + return; + } + currentSearchTerm = term; + currentSort = sort; + if(jobPtr) { + jobPtr->abort(); + searchState = ResetRequested; + return; + } + else { + beginResetModel(); + modpacks.clear(); + endResetModel(); + searchState = None; + } + nextSearchOffset = 0; + performPaginatedSearch(); +} + +void ListModel::searchRequestFinished() +{ + 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) { + auto packObj = packRaw.toObject(); + + FlameMod::IndexedPack pack; + try + { + FlameMod::loadIndexedPack(pack, packObj); + newList.append(pack); + } + catch(const JSONValidationError &e) + { + qWarning() << "Error while loading mod from Flame: " << e.cause(); + continue; + } + } + if(packs.size() < 25) { + searchState = Finished; + } else { + nextSearchOffset += 25; + searchState = CanPossiblyFetchMore; + } + beginInsertRows(QModelIndex(), modpacks.size(), modpacks.size() + newList.size() - 1); + modpacks.append(newList); + endInsertRows(); +} + +void ListModel::searchRequestFailed(QString reason) +{ + jobPtr.reset(); + + if(searchState == ResetRequested) { + beginResetModel(); + modpacks.clear(); + endResetModel(); + + nextSearchOffset = 0; + performPaginatedSearch(); + } else { + searchState = Finished; + } +} + +} + diff --git a/launcher/ui/pages/modplatform/flame/FlameModModel.h b/launcher/ui/pages/modplatform/flame/FlameModModel.h new file mode 100644 index 00000000..0c1cb95e --- /dev/null +++ b/launcher/ui/pages/modplatform/flame/FlameModModel.h @@ -0,0 +1,79 @@ +#pragma once + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include "modplatform/flame/FlameModIndex.h" +#include "BaseInstance.h" +#include "FlameModPage.h" + +namespace FlameMod { + + +typedef QMap LogoMap; +typedef std::function LogoCallback; + +class ListModel : public QAbstractListModel +{ + Q_OBJECT + +public: + ListModel(FlameModPage *parent); + virtual ~ListModel(); + + int rowCount(const QModelIndex &parent) const override; + int columnCount(const QModelIndex &parent) const override; + QVariant data(const QModelIndex &index, int role) const override; + Qt::ItemFlags flags(const QModelIndex &index) const override; + bool canFetchMore(const QModelIndex & parent) const override; + void fetchMore(const QModelIndex & parent) override; + + void getLogo(const QString &logo, const QString &logoUrl, LogoCallback callback); + void searchWithTerm(const QString &term, const int sort); + +private slots: + void performPaginatedSearch(); + + void logoFailed(QString logo); + void logoLoaded(QString logo, QIcon out); + + void searchRequestFinished(); + void searchRequestFailed(QString reason); + +private: + void requestLogo(QString file, QString url); + +private: + QList modpacks; + QStringList m_failedLogos; + QStringList m_loadingLogos; + LogoMap m_logoMap; + QMap waitingCallbacks; + + QString currentSearchTerm; + int currentSort = 0; + int nextSearchOffset = 0; + enum SearchState { + None, + CanPossiblyFetchMore, + ResetRequested, + Finished + } searchState = None; + NetJob::Ptr jobPtr; + QByteArray response; +}; + +} diff --git a/launcher/ui/pages/modplatform/flame/FlameModPage.cpp b/launcher/ui/pages/modplatform/flame/FlameModPage.cpp new file mode 100644 index 00000000..ffcb7fdb --- /dev/null +++ b/launcher/ui/pages/modplatform/flame/FlameModPage.cpp @@ -0,0 +1,193 @@ +#include "FlameModPage.h" +#include "ui_FlameModPage.h" + +#include + +#include "Application.h" +#include "Json.h" +#include "ui/dialogs/ModDownloadDialog.h" +#include "InstanceImportTask.h" +#include "FlameModModel.h" +#include "ModDownloadTask.h" +#include "minecraft/MinecraftInstance.h" +#include "minecraft/PackProfile.h" + +FlameModPage::FlameModPage(ModDownloadDialog *dialog, BaseInstance *instance) + : QWidget(dialog), m_instance(instance), ui(new Ui::FlameModPage), dialog(dialog) +{ + ui->setupUi(this); + connect(ui->searchButton, &QPushButton::clicked, this, &FlameModPage::triggerSearch); + ui->searchEdit->installEventFilter(this); + listModel = new FlameMod::ListModel(this); + ui->packView->setModel(listModel); + + ui->versionSelectionBox->view()->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); + ui->versionSelectionBox->view()->parentWidget()->setMaximumHeight(300); + + // index is used to set the sorting with the flame api + ui->sortByBox->addItem(tr("Sort by Featured")); + ui->sortByBox->addItem(tr("Sort by Popularity")); + ui->sortByBox->addItem(tr("Sort by last updated")); + ui->sortByBox->addItem(tr("Sort by Name")); + ui->sortByBox->addItem(tr("Sort by Author")); + ui->sortByBox->addItem(tr("Sort by Downloads")); + + connect(ui->sortByBox, SIGNAL(currentIndexChanged(int)), this, SLOT(triggerSearch())); + connect(ui->packView->selectionModel(), &QItemSelectionModel::currentChanged, this, &FlameModPage::onSelectionChanged); + connect(ui->versionSelectionBox, &QComboBox::currentTextChanged, this, &FlameModPage::onVersionSelectionChanged); +} + +FlameModPage::~FlameModPage() +{ + delete ui; +} + +bool FlameModPage::eventFilter(QObject* watched, QEvent* event) +{ + if (watched == ui->searchEdit && event->type() == QEvent::KeyPress) { + QKeyEvent* keyEvent = static_cast(event); + if (keyEvent->key() == Qt::Key_Return) { + triggerSearch(); + keyEvent->accept(); + return true; + } + } + return QWidget::eventFilter(watched, event); +} + +bool FlameModPage::shouldDisplay() const +{ + return true; +} + +void FlameModPage::openedImpl() +{ + suggestCurrent(); + triggerSearch(); +} + +void FlameModPage::triggerSearch() +{ + listModel->searchWithTerm(ui->searchEdit->text(), ui->sortByBox->currentIndex()); +} + +void FlameModPage::onSelectionChanged(QModelIndex first, QModelIndex second) +{ + ui->versionSelectionBox->clear(); + + if(!first.isValid()) + { + if(isOpened) + { + dialog->setSuggestedMod(); + } + return; + } + + current = listModel->data(first, Qt::UserRole).value(); + QString text = ""; + QString name = current.name; + + if (current.websiteUrl.isEmpty()) + text = name; + else + text = "" + name + ""; + if (!current.authors.empty()) { + auto authorToStr = [](FlameMod::ModpackAuthor & author) { + if(author.url.isEmpty()) { + return author.name; + } + return QString("%2").arg(author.url, author.name); + }; + QStringList authorStrs; + for(auto & author: current.authors) { + authorStrs.push_back(authorToStr(author)); + } + text += "
" + tr(" by ") + authorStrs.join(", "); + } + text += "

"; + + ui->packDescription->setHtml(text + current.description); + + if (!current.versionsLoaded) + { + qDebug() << "Loading flame mod versions"; + NetJob *netJob = new NetJob(QString("Flame::ModVersions(%1)").arg(current.name), APPLICATION->network()); + std::shared_ptr response = std::make_shared(); + int addonId = current.addonId; + netJob->addNetAction(Net::Download::makeByteArray(QString("https://addons-ecs.forgesvc.net/api/v2/addon/%1/files").arg(addonId), response.get())); + + QObject::connect(netJob, &NetJob::succeeded, this, [this, response] + { + 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; + qWarning() << "Error while reading Flame mod version: " << e.cause(); + } + auto packProfile = ((MinecraftInstance *)m_instance)->getPackProfile(); + QString mcVersion = packProfile->getComponentVersion("net.minecraft"); + QString loaderString = (packProfile->getComponentVersion("net.minecraftforge").isEmpty()) ? "fabric" : "forge"; + for(const auto& version : current.versions) { + if(!version.mcVersion.contains(mcVersion)){ + continue; + } + ui->versionSelectionBox->addItem(version.version, QVariant(version.downloadUrl)); + } + if(ui->versionSelectionBox->count() == 0){ + ui->versionSelectionBox->addItem(tr("No Valid Version found !"), QVariant("")); + } + + suggestCurrent(); + }); + netJob->start(); + } + else + { + for(auto version : current.versions) { + ui->versionSelectionBox->addItem(version.version, QVariant(version.downloadUrl)); + } + if(ui->versionSelectionBox->count() == 0){ + ui->versionSelectionBox->addItem(tr("No Valid Version found !"), QVariant("")); + } + suggestCurrent(); + } +} + +void FlameModPage::suggestCurrent() +{ + if(!isOpened) + { + return; + } + + if (selectedVersion.isEmpty()) + { + dialog->setSuggestedMod(); + return; + } + + dialog->setSuggestedMod(current.name, new ModDownloadTask(selectedVersion, current.versions.at(0).fileName ,dialog->mods)); +} + +void FlameModPage::onVersionSelectionChanged(QString data) +{ + if(data.isNull() || data.isEmpty()) + { + selectedVersion = ""; + return; + } + selectedVersion = ui->versionSelectionBox->currentData().toString(); + suggestCurrent(); +} diff --git a/launcher/ui/pages/modplatform/flame/FlameModPage.h b/launcher/ui/pages/modplatform/flame/FlameModPage.h new file mode 100644 index 00000000..85c68620 --- /dev/null +++ b/launcher/ui/pages/modplatform/flame/FlameModPage.h @@ -0,0 +1,67 @@ +#pragma once + +#include + +#include "ui/pages/BasePage.h" +#include +#include "tasks/Task.h" +#include "modplatform/flame/FlameModIndex.h" + +namespace Ui +{ +class FlameModPage; +} + +class ModDownloadDialog; + +namespace FlameMod { + class ListModel; +} + +class FlameModPage : public QWidget, public BasePage +{ + Q_OBJECT + +public: + explicit FlameModPage(ModDownloadDialog *dialog, BaseInstance *instance); + virtual ~FlameModPage(); + virtual QString displayName() const override + { + return tr("CurseForge"); + } + virtual QIcon icon() const override + { + return APPLICATION->getThemedIcon("flame"); + } + virtual QString id() const override + { + return "curseforge"; + } + virtual QString helpPage() const override + { + return "Flame-platform"; + } + virtual bool shouldDisplay() const override; + + void openedImpl() override; + + bool eventFilter(QObject * watched, QEvent * event) override; + + BaseInstance *m_instance; + +private: + void suggestCurrent(); + +private slots: + void triggerSearch(); + void onSelectionChanged(QModelIndex first, QModelIndex second); + void onVersionSelectionChanged(QString data); + +private: + Ui::FlameModPage *ui = nullptr; + ModDownloadDialog* dialog = nullptr; + FlameMod::ListModel* listModel = nullptr; + FlameMod::IndexedPack current; + + QString selectedVersion; +}; diff --git a/launcher/ui/pages/modplatform/flame/FlameModPage.ui b/launcher/ui/pages/modplatform/flame/FlameModPage.ui new file mode 100644 index 00000000..7da0bb4a --- /dev/null +++ b/launcher/ui/pages/modplatform/flame/FlameModPage.ui @@ -0,0 +1,90 @@ + + + FlameModPage + + + + 0 + 0 + 837 + 685 + + + + + + + + + + 48 + 48 + + + + Qt::ScrollBarAlwaysOff + + + true + + + + + + + true + + + true + + + + + + + + + + + + + + Version selected: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + + + + Search + + + + + + + Search and filter ... + + + + + + + searchEdit + searchButton + packView + packDescription + sortByBox + versionSelectionBox + + + + From affc2521aaa282a6ba7f051dd02594500add4e6a Mon Sep 17 00:00:00 2001 From: timoreo Date: Tue, 18 Jan 2022 12:28:55 +0100 Subject: [PATCH 10/17] Various fixes --- launcher/modplatform/flame/FlameModIndex.cpp | 3 ++- launcher/ui/pages/instance/ModFolderPage.cpp | 4 +++- launcher/ui/pages/modplatform/flame/FlameModModel.cpp | 6 ++++-- launcher/ui/pages/modplatform/flame/FlameModPage.cpp | 5 +++-- launcher/ui/pages/modplatform/flame/FlameModel.cpp | 11 +++++------ .../ui/pages/modplatform/modrinth/ModrinthModel.cpp | 6 ++++-- .../ui/pages/modplatform/modrinth/ModrinthPage.cpp | 5 +++-- 7 files changed, 24 insertions(+), 16 deletions(-) diff --git a/launcher/modplatform/flame/FlameModIndex.cpp b/launcher/modplatform/flame/FlameModIndex.cpp index d298ae83..ca8f7fd7 100644 --- a/launcher/modplatform/flame/FlameModIndex.cpp +++ b/launcher/modplatform/flame/FlameModIndex.cpp @@ -76,13 +76,14 @@ void FlameMod::loadIndexedPackVersions(FlameMod::IndexedPack & pack, QJsonArray break; } }else{ + //this cannot check for the recent mcmod.toml formats if(fname == "mcmod.info"){ valid = true; break; } } } - if(!valid){ + if(!valid || !hasFabric){ continue; } diff --git a/launcher/ui/pages/instance/ModFolderPage.cpp b/launcher/ui/pages/instance/ModFolderPage.cpp index 7ebf66f6..1d2194f3 100644 --- a/launcher/ui/pages/instance/ModFolderPage.cpp +++ b/launcher/ui/pages/instance/ModFolderPage.cpp @@ -362,7 +362,8 @@ void ModFolderPage::on_actionInstall_mods_triggered() if(mdownload.exec()) { ModDownloadTask *task = mdownload.getTask(); if (task) { - connect(task, &Task::failed, [this](QString reason) { + connect(task, &Task::failed, [this, task](QString reason) { + task->deleteLater(); CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); }); connect(task, &Task::succeeded, [this, task]() { @@ -371,6 +372,7 @@ void ModFolderPage::on_actionInstall_mods_triggered() CustomMessageBox::selectable(this, tr("Warnings"), warnings.join('\n'), QMessageBox::Warning)->show(); } + task->deleteLater(); }); ProgressDialog loadDialog(this); loadDialog.setSkipButton(true, tr("Abort")); diff --git a/launcher/ui/pages/modplatform/flame/FlameModModel.cpp b/launcher/ui/pages/modplatform/flame/FlameModModel.cpp index 7deaa36c..199f9c54 100644 --- a/launcher/ui/pages/modplatform/flame/FlameModModel.cpp +++ b/launcher/ui/pages/modplatform/flame/FlameModModel.cpp @@ -105,8 +105,9 @@ void ListModel::requestLogo(QString logo, QString url) job->addNetAction(Net::Download::makeCached(QUrl(url), entry)); auto fullPath = entry->getFullPath(); - QObject::connect(job, &NetJob::succeeded, this, [this, logo, fullPath] + QObject::connect(job, &NetJob::succeeded, this, [this, logo, fullPath, job] { + job->deleteLater(); emit logoLoaded(logo, QIcon(fullPath)); if(waitingCallbacks.contains(logo)) { @@ -114,8 +115,9 @@ void ListModel::requestLogo(QString logo, QString url) } }); - QObject::connect(job, &NetJob::failed, this, [this, logo] + QObject::connect(job, &NetJob::failed, this, [this, logo, job] { + job->deleteLater(); emit logoFailed(logo); }); diff --git a/launcher/ui/pages/modplatform/flame/FlameModPage.cpp b/launcher/ui/pages/modplatform/flame/FlameModPage.cpp index ffcb7fdb..9978ad7b 100644 --- a/launcher/ui/pages/modplatform/flame/FlameModPage.cpp +++ b/launcher/ui/pages/modplatform/flame/FlameModPage.cpp @@ -112,13 +112,14 @@ void FlameModPage::onSelectionChanged(QModelIndex first, QModelIndex second) if (!current.versionsLoaded) { qDebug() << "Loading flame mod versions"; - NetJob *netJob = new NetJob(QString("Flame::ModVersions(%1)").arg(current.name), APPLICATION->network()); + auto netJob = new NetJob(QString("Flame::ModVersions(%1)").arg(current.name), APPLICATION->network()); std::shared_ptr response = std::make_shared(); int addonId = current.addonId; netJob->addNetAction(Net::Download::makeByteArray(QString("https://addons-ecs.forgesvc.net/api/v2/addon/%1/files").arg(addonId), response.get())); - QObject::connect(netJob, &NetJob::succeeded, this, [this, response] + QObject::connect(netJob, &NetJob::succeeded, this, [this, response, netJob] { + netJob->deleteLater(); QJsonParseError parse_error; QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error); if(parse_error.error != QJsonParseError::NoError) { diff --git a/launcher/ui/pages/modplatform/flame/FlameModel.cpp b/launcher/ui/pages/modplatform/flame/FlameModel.cpp index 891676cf..fe163cae 100644 --- a/launcher/ui/pages/modplatform/flame/FlameModel.cpp +++ b/launcher/ui/pages/modplatform/flame/FlameModel.cpp @@ -6,9 +6,6 @@ #include #include -#include - -#include namespace Flame { @@ -100,12 +97,13 @@ void ListModel::requestLogo(QString logo, QString url) } MetaEntryPtr entry = APPLICATION->metacache()->resolveEntry("FlamePacks", QString("logos/%1").arg(logo.section(".", 0, 0))); - NetJob *job = new NetJob(QString("Flame Icon Download %1").arg(logo), APPLICATION->network()); + auto job = new NetJob(QString("Flame Icon Download %1").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] + QObject::connect(job, &NetJob::succeeded, this, [this, logo, fullPath, job] { + job->deleteLater(); emit logoLoaded(logo, QIcon(fullPath)); if(waitingCallbacks.contains(logo)) { @@ -113,8 +111,9 @@ void ListModel::requestLogo(QString logo, QString url) } }); - QObject::connect(job, &NetJob::failed, this, [this, logo] + QObject::connect(job, &NetJob::failed, this, [this, logo, job] { + job->deleteLater(); emit logoFailed(logo); }); diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp index e7f66768..68a20ecf 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp @@ -105,8 +105,9 @@ void ListModel::requestLogo(QString logo, QString url) job->addNetAction(Net::Download::makeCached(QUrl(url), entry)); auto fullPath = entry->getFullPath(); - QObject::connect(job, &NetJob::succeeded, this, [this, logo, fullPath] + QObject::connect(job, &NetJob::succeeded, this, [this, logo, fullPath, job] { + job->deleteLater(); emit logoLoaded(logo, QIcon(fullPath)); if(waitingCallbacks.contains(logo)) { @@ -114,8 +115,9 @@ void ListModel::requestLogo(QString logo, QString url) } }); - QObject::connect(job, &NetJob::failed, this, [this, logo] + QObject::connect(job, &NetJob::failed, this, [this, logo, job] { + job->deleteLater(); emit logoFailed(logo); }); diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp index fe5766dc..d0319984 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp @@ -110,14 +110,15 @@ void ModrinthPage::onSelectionChanged(QModelIndex first, QModelIndex second) if (!current.versionsLoaded) { qDebug() << "Loading Modrinth mod versions"; - NetJob *netJob = new NetJob(QString("Modrinth::ModVersions(%1)").arg(current.name), APPLICATION->network()); + auto netJob = new NetJob(QString("Modrinth::ModVersions(%1)").arg(current.name), APPLICATION->network()); std::shared_ptr response = std::make_shared(); QString addonId = current.addonId; addonId.remove(0,6); netJob->addNetAction(Net::Download::makeByteArray(QString("https://api.modrinth.com/api/v1/mod/%1/version").arg(addonId), response.get())); - QObject::connect(netJob, &NetJob::succeeded, this, [this, response] + QObject::connect(netJob, &NetJob::succeeded, this, [this, response, netJob] { + netJob->deleteLater(); QJsonParseError parse_error; QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error); if(parse_error.error != QJsonParseError::NoError) { From 6d22794cf97722e3f7d5507fc16c75bf6fe59bf8 Mon Sep 17 00:00:00 2001 From: timoreo Date: Wed, 19 Jan 2022 09:47:09 +0100 Subject: [PATCH 11/17] Reduce spaghettiness --- launcher/ui/pages/modplatform/flame/FlameModModel.cpp | 8 +++++++- launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp | 8 +++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/launcher/ui/pages/modplatform/flame/FlameModModel.cpp b/launcher/ui/pages/modplatform/flame/FlameModModel.cpp index 199f9c54..2cf83261 100644 --- a/launcher/ui/pages/modplatform/flame/FlameModModel.cpp +++ b/launcher/ui/pages/modplatform/flame/FlameModModel.cpp @@ -177,7 +177,13 @@ void ListModel::performPaginatedSearch() "sort=%3&" "%4" "gameVersion=%5" - ).arg(nextSearchOffset).arg(currentSearchTerm).arg(sorts[currentSort]).arg(hasFabric ? "modLoaderType=4&" : "").arg(mcVersion); + ) + .arg(nextSearchOffset) + .arg(currentSearchTerm) + .arg(sorts[currentSort]) + .arg(hasFabric ? "modLoaderType=4&" : "") + .arg(mcVersion); + netJob->addNetAction(Net::Download::makeByteArray(QUrl(searchUrl), &response)); jobPtr = netJob; jobPtr->start(); diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp index 68a20ecf..4f6a491e 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp @@ -172,7 +172,13 @@ void ListModel::performPaginatedSearch() "query=%2&" "index=%3&" "filters=categories=\"%4\" AND versions=\"%5\"" - ).arg(nextSearchOffset).arg(currentSearchTerm).arg(sorts[currentSort]).arg(hasFabric ? "fabric" : "forge").arg(mcVersion); + ) + .arg(nextSearchOffset) + .arg(currentSearchTerm) + .arg(sorts[currentSort]) + .arg(hasFabric ? "fabric" : "forge") + .arg(mcVersion); + netJob->addNetAction(Net::Download::makeByteArray(QUrl(searchUrl), &response)); jobPtr = netJob; jobPtr->start(); From a2d88f6df47c49d29444e8faba6a6e648cbe3a11 Mon Sep 17 00:00:00 2001 From: timoreo Date: Mon, 24 Jan 2022 07:12:19 +0100 Subject: [PATCH 12/17] Fixed spacing --- launcher/ui/pages/instance/ModFolderPage.cpp | 2 +- launcher/ui/pages/modplatform/flame/FlameModPage.cpp | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/launcher/ui/pages/instance/ModFolderPage.cpp b/launcher/ui/pages/instance/ModFolderPage.cpp index 1d2194f3..09b199fb 100644 --- a/launcher/ui/pages/instance/ModFolderPage.cpp +++ b/launcher/ui/pages/instance/ModFolderPage.cpp @@ -355,7 +355,7 @@ void ModFolderPage::on_actionInstall_mods_triggered() bool hasFabric = !((MinecraftInstance *)m_inst)->getPackProfile()->getComponentVersion("net.fabricmc.fabric-loader").isEmpty(); bool hasForge = !((MinecraftInstance *)m_inst)->getPackProfile()->getComponentVersion("net.minecraftforge").isEmpty(); if (!hasFabric && !hasForge) { - QMessageBox::critical(this,tr("Error"),tr("Please install a mod loader first !")); + QMessageBox::critical(this,tr("Error"),tr("Please install a mod loader first!")); return; } ModDownloadDialog mdownload(m_mods, this, m_inst); diff --git a/launcher/ui/pages/modplatform/flame/FlameModPage.cpp b/launcher/ui/pages/modplatform/flame/FlameModPage.cpp index 9978ad7b..80f3de19 100644 --- a/launcher/ui/pages/modplatform/flame/FlameModPage.cpp +++ b/launcher/ui/pages/modplatform/flame/FlameModPage.cpp @@ -147,7 +147,7 @@ void FlameModPage::onSelectionChanged(QModelIndex first, QModelIndex second) ui->versionSelectionBox->addItem(version.version, QVariant(version.downloadUrl)); } if(ui->versionSelectionBox->count() == 0){ - ui->versionSelectionBox->addItem(tr("No Valid Version found !"), QVariant("")); + ui->versionSelectionBox->addItem(tr("No Valid Version found!"), QVariant("")); } suggestCurrent(); @@ -160,7 +160,7 @@ void FlameModPage::onSelectionChanged(QModelIndex first, QModelIndex second) ui->versionSelectionBox->addItem(version.version, QVariant(version.downloadUrl)); } if(ui->versionSelectionBox->count() == 0){ - ui->versionSelectionBox->addItem(tr("No Valid Version found !"), QVariant("")); + ui->versionSelectionBox->addItem(tr("No Valid Version found!"), QVariant("")); } suggestCurrent(); } From 1d0e6bf453bfee0d9201fabf1e979ab0aca90418 Mon Sep 17 00:00:00 2001 From: timoreo Date: Mon, 24 Jan 2022 07:23:01 +0100 Subject: [PATCH 13/17] Changed modrinth author data to not be a list --- .../modplatform/modrinth/ModrinthPackIndex.cpp | 8 ++++---- .../modplatform/modrinth/ModrinthPackIndex.h | 2 +- .../pages/modplatform/modrinth/ModrinthPage.cpp | 16 +--------------- 3 files changed, 6 insertions(+), 20 deletions(-) diff --git a/launcher/modplatform/modrinth/ModrinthPackIndex.cpp b/launcher/modplatform/modrinth/ModrinthPackIndex.cpp index 8d47699b..a546eb7c 100644 --- a/launcher/modplatform/modrinth/ModrinthPackIndex.cpp +++ b/launcher/modplatform/modrinth/ModrinthPackIndex.cpp @@ -18,10 +18,10 @@ void Modrinth::loadIndexedPack(Modrinth::IndexedPack & pack, QJsonObject & obj) pack.logoUrl = Json::requireString(obj, "icon_url"); pack.logoName = pack.addonId; - Modrinth::ModpackAuthor packAuthor; - packAuthor.name = Json::requireString(obj, "author"); - packAuthor.url = Json::requireString(obj, "author_url"); - pack.authors.append(packAuthor); //TODO delete this ? only one author ever exists + Modrinth::ModpackAuthor modAuthor; + modAuthor.name = Json::requireString(obj, "author"); + modAuthor.url = Json::requireString(obj, "author_url"); + pack.author = modAuthor; } void Modrinth::loadIndexedPackVersions(Modrinth::IndexedPack & pack, QJsonArray & arr, const shared_qobject_ptr& network, BaseInstance * inst) diff --git a/launcher/modplatform/modrinth/ModrinthPackIndex.h b/launcher/modplatform/modrinth/ModrinthPackIndex.h index 01ae4b44..3a4cd270 100644 --- a/launcher/modplatform/modrinth/ModrinthPackIndex.h +++ b/launcher/modplatform/modrinth/ModrinthPackIndex.h @@ -32,7 +32,7 @@ struct IndexedPack QString addonId; QString name; QString description; - QList authors; + ModpackAuthor author; QString logoName; QString logoUrl; QString websiteUrl; diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp index d0319984..61912cd7 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp @@ -90,21 +90,7 @@ void ModrinthPage::onSelectionChanged(QModelIndex first, QModelIndex second) text = name; else text = "" + name + ""; - if (!current.authors.empty()) { - auto authorToStr = [](Modrinth::ModpackAuthor & author) { - if(author.url.isEmpty()) { - return author.name; - } - return QString("%2").arg(author.url, author.name); - }; - QStringList authorStrs; - for(auto & author: current.authors) { - authorStrs.push_back(authorToStr(author)); - } - text += "
" + tr(" by ") + authorStrs.join(", "); - } - text += "

"; - + text += "
"+ tr(" by ") + ""+current.author.name+"

"; ui->packDescription->setHtml(text + current.description); if (!current.versionsLoaded) From efc44c56a62def0242353dde9c84452690209465 Mon Sep 17 00:00:00 2001 From: timoreo Date: Fri, 28 Jan 2022 19:32:42 +0100 Subject: [PATCH 14/17] Fix button being present in other pages --- launcher/ui/pages/instance/ModFolderPage.cpp | 5 +++++ launcher/ui/pages/instance/ModFolderPage.ui | 9 --------- launcher/ui/widgets/WideBar.cpp | 14 ++++++++++++++ launcher/ui/widgets/WideBar.h | 1 + 4 files changed, 20 insertions(+), 9 deletions(-) diff --git a/launcher/ui/pages/instance/ModFolderPage.cpp b/launcher/ui/pages/instance/ModFolderPage.cpp index 09b199fb..494d32f0 100644 --- a/launcher/ui/pages/instance/ModFolderPage.cpp +++ b/launcher/ui/pages/instance/ModFolderPage.cpp @@ -143,6 +143,11 @@ ModFolderPage::ModFolderPage( ui(new Ui::ModFolderPage) { ui->setupUi(this); + if(id == "mods") { + auto act = new QAction(tr("Install Mods"), this); + ui->actionsToolbar->insertActionBefore(ui->actionView_configs,act); + connect(act, &QAction::triggered, this, &ModFolderPage::on_actionInstall_mods_triggered); + } ui->actionsToolbar->insertSpacer(ui->actionView_configs); m_inst = inst; diff --git a/launcher/ui/pages/instance/ModFolderPage.ui b/launcher/ui/pages/instance/ModFolderPage.ui index b5b4c9b2..0fb51e84 100644 --- a/launcher/ui/pages/instance/ModFolderPage.ui +++ b/launcher/ui/pages/instance/ModFolderPage.ui @@ -88,7 +88,6 @@ - @@ -137,14 +136,6 @@ View &Folder - - - Install mods - - - Install mods from Modrinth or Curseforge - - diff --git a/launcher/ui/widgets/WideBar.cpp b/launcher/ui/widgets/WideBar.cpp index cbd6c617..8d5bd12d 100644 --- a/launcher/ui/widgets/WideBar.cpp +++ b/launcher/ui/widgets/WideBar.cpp @@ -76,6 +76,20 @@ void WideBar::addSeparator() m_entries.push_back(entry); } +void WideBar::insertActionBefore(QAction* before, QAction* action){ + auto iter = std::find_if(m_entries.begin(), m_entries.end(), [before](BarEntry * entry) { + return entry->wideAction == before; + }); + if(iter == m_entries.end()) { + return; + } + auto entry = new BarEntry(); + entry->qAction = insertWidget((*iter)->qAction, new ActionButton(action, this)); + entry->wideAction = action; + entry->type = BarEntry::Action; + m_entries.insert(iter, entry); +} + void WideBar::insertSpacer(QAction* action) { auto iter = std::find_if(m_entries.begin(), m_entries.end(), [action](BarEntry * entry) { diff --git a/launcher/ui/widgets/WideBar.h b/launcher/ui/widgets/WideBar.h index d1b8cbe7..2b676a8c 100644 --- a/launcher/ui/widgets/WideBar.h +++ b/launcher/ui/widgets/WideBar.h @@ -18,6 +18,7 @@ public: void addAction(QAction *action); void addSeparator(); void insertSpacer(QAction *action); + void insertActionBefore(QAction *before, QAction *action); QMenu *createContextMenu(QWidget *parent = nullptr, const QString & title = QString()); private: From aa2c27bf6984f9ea2d67411c0f28d802d40834af Mon Sep 17 00:00:00 2001 From: timoreo Date: Mon, 31 Jan 2022 17:18:11 +0100 Subject: [PATCH 15/17] Update to Modrinth API V2 --- launcher/modplatform/modrinth/ModrinthPackIndex.cpp | 6 +++--- launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp | 6 +++--- launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/launcher/modplatform/modrinth/ModrinthPackIndex.cpp b/launcher/modplatform/modrinth/ModrinthPackIndex.cpp index a546eb7c..1a31e940 100644 --- a/launcher/modplatform/modrinth/ModrinthPackIndex.cpp +++ b/launcher/modplatform/modrinth/ModrinthPackIndex.cpp @@ -10,7 +10,7 @@ void Modrinth::loadIndexedPack(Modrinth::IndexedPack & pack, QJsonObject & obj) { - pack.addonId = Json::requireString(obj, "mod_id"); + pack.addonId = Json::requireString(obj, "project_id"); pack.name = Json::requireString(obj, "title"); pack.websiteUrl = Json::ensureString(obj, "page_url", ""); pack.description = Json::ensureString(obj, "description", ""); @@ -20,7 +20,7 @@ void Modrinth::loadIndexedPack(Modrinth::IndexedPack & pack, QJsonObject & obj) Modrinth::ModpackAuthor modAuthor; modAuthor.name = Json::requireString(obj, "author"); - modAuthor.url = Json::requireString(obj, "author_url"); + modAuthor.url = "https://modrinth.com/user/"+modAuthor.name; pack.author = modAuthor; } @@ -33,7 +33,7 @@ void Modrinth::loadIndexedPackVersions(Modrinth::IndexedPack & pack, QJsonArray for(auto versionIter: arr) { auto obj = versionIter.toObject(); Modrinth::IndexedVersion file; - file.addonId = Json::requireString(obj,"mod_id") ; + file.addonId = Json::requireString(obj,"project_id") ; file.fileId = Json::requireString(obj, "id"); file.date = Json::requireString(obj, "date_published"); auto versionArray = Json::requireArray(obj, "game_versions"); diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp index 4f6a491e..71574156 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp @@ -157,7 +157,7 @@ void ListModel::fetchMore(const QModelIndex& parent) } performPaginatedSearch(); } -const char* sorts[4]{"relevance","downloads","updated","newest"}; +const char* sorts[5]{"relevance","downloads","follows","updated","newest"}; void ListModel::performPaginatedSearch() { @@ -166,12 +166,12 @@ void ListModel::performPaginatedSearch() bool hasFabric = !((MinecraftInstance *)((ModrinthPage *)parent())->m_instance)->getPackProfile()->getComponentVersion("net.fabricmc.fabric-loader").isEmpty(); auto netJob = new NetJob("Modrinth::Search", APPLICATION->network()); auto searchUrl = QString( - "https://api.modrinth.com/api/v1/mod?" + "https://api.modrinth.com/v2/search?" "offset=%1&" "limit=25&" "query=%2&" "index=%3&" - "filters=categories=\"%4\" AND versions=\"%5\"" + "facets=[[\"categories:%4\"],[\"versions:%5\"],[\"project_type:mod\"]]" ) .arg(nextSearchOffset) .arg(currentSearchTerm) diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp index 61912cd7..ee3c9e76 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp @@ -27,6 +27,7 @@ ModrinthPage::ModrinthPage(ModDownloadDialog *dialog, BaseInstance *instance) // index is used to set the sorting with the modrinth api ui->sortByBox->addItem(tr("Sort by Relevence")); 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")); @@ -99,8 +100,7 @@ void ModrinthPage::onSelectionChanged(QModelIndex first, QModelIndex second) auto netJob = new NetJob(QString("Modrinth::ModVersions(%1)").arg(current.name), APPLICATION->network()); std::shared_ptr response = std::make_shared(); QString addonId = current.addonId; - addonId.remove(0,6); - netJob->addNetAction(Net::Download::makeByteArray(QString("https://api.modrinth.com/api/v1/mod/%1/version").arg(addonId), response.get())); + netJob->addNetAction(Net::Download::makeByteArray(QString("https://api.modrinth.com/v2/project/%1/version").arg(addonId), response.get())); QObject::connect(netJob, &NetJob::succeeded, this, [this, response, netJob] { From 71b1ac9f349c0831f9d5242e88e7eabd38984e28 Mon Sep 17 00:00:00 2001 From: timoreo Date: Tue, 1 Feb 2022 21:56:52 +0100 Subject: [PATCH 16/17] Fix braindead moments --- launcher/modplatform/flame/FlameModIndex.cpp | 2 +- launcher/modplatform/modrinth/ModrinthPackIndex.cpp | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/launcher/modplatform/flame/FlameModIndex.cpp b/launcher/modplatform/flame/FlameModIndex.cpp index ca8f7fd7..4b81fd7c 100644 --- a/launcher/modplatform/flame/FlameModIndex.cpp +++ b/launcher/modplatform/flame/FlameModIndex.cpp @@ -83,7 +83,7 @@ void FlameMod::loadIndexedPackVersions(FlameMod::IndexedPack & pack, QJsonArray } } } - if(!valid || !hasFabric){ + if(!valid && !hasFabric){ continue; } diff --git a/launcher/modplatform/modrinth/ModrinthPackIndex.cpp b/launcher/modplatform/modrinth/ModrinthPackIndex.cpp index 1a31e940..9017eb67 100644 --- a/launcher/modplatform/modrinth/ModrinthPackIndex.cpp +++ b/launcher/modplatform/modrinth/ModrinthPackIndex.cpp @@ -77,10 +77,12 @@ void Modrinth::loadIndexedPackVersions(Modrinth::IndexedPack & pack, QJsonArray break; } auto parent = files[i].toObject(); - file.downloadUrl = Json::requireString(parent, "url"); - file.fileName = Json::requireString(parent, "filename"); + if(parent.contains("url")) { + file.downloadUrl = Json::requireString(parent, "url"); + file.fileName = Json::requireString(parent, "filename"); - unsortedVersions.append(file); + unsortedVersions.append(file); + } } auto orderSortPredicate = [](const IndexedVersion & a, const IndexedVersion & b) -> bool { From 11841c47e62ecdafc09eecd1998cff722ebcd1e7 Mon Sep 17 00:00:00 2001 From: timoreo22 Date: Tue, 1 Feb 2022 22:23:34 +0100 Subject: [PATCH 17/17] Double braindead combo --- launcher/modplatform/flame/FlameModIndex.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/launcher/modplatform/flame/FlameModIndex.cpp b/launcher/modplatform/flame/FlameModIndex.cpp index 4b81fd7c..a8b2495a 100644 --- a/launcher/modplatform/flame/FlameModIndex.cpp +++ b/launcher/modplatform/flame/FlameModIndex.cpp @@ -83,7 +83,7 @@ void FlameMod::loadIndexedPackVersions(FlameMod::IndexedPack & pack, QJsonArray } } } - if(!valid && !hasFabric){ + if(!valid && hasFabric){ continue; } @@ -97,4 +97,4 @@ void FlameMod::loadIndexedPackVersions(FlameMod::IndexedPack & pack, QJsonArray std::sort(unsortedVersions.begin(), unsortedVersions.end(), orderSortPredicate); pack.versions = unsortedVersions; pack.versionsLoaded = true; -} \ No newline at end of file +}