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 b5c52afa2..12274b701 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 000000000..22955470b --- /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 000000000..067bd91cc --- /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 000000000..fa421ab21 --- /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 000000000..afc31ff27 --- /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 000000000..a40980efd --- /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 000000000..6ce6ff616 --- /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 e63b1434e..d2f5deadc 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 8ef7559b1..fbda3cd85 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 0fb51e84f..b5b4c9b29 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 000000000..3bc70e34b --- /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 000000000..7bd06f6a6 --- /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 000000000..ea1800d29 --- /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 000000000..924bc6ce6 --- /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 000000000..6d183de50 --- /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 zcmeAS@N?(olHy`uVBq!ia0y~yU{C;I4mJh`hT^KKFANL}EX7WqAsieW95oy%9SjWY z2A(dCArY-_!!vV2u1nh9-~0aThWEZ61x*{DFJw5|q_AYEruR}6uCuq&4y488G>cDT z*}5Vt^wz5235&u)J-ZxMmdiY5RcmZpv2=&G!`fSIWz6;|F(X=zUTM3^7EDVzu&cf&h-Daa~_M#9qzUEJaQ_xew^1<&VBE{!#_ELrO|$7x3@d*$tyyC!kW} zfjgGzJ@ql4CUA%~EitUPsd8s|^yz9pe&)7?0&I*1)m2aZKg*a#UGF-f#IWGFa_z@| zsq4e`nSZ#*#G#p06epBWN9y;^MQqP^C!H2^-d3HrY13L0G3JH~>kjaxuH;q8wAs9E z>5|n})^Vx!BG=CBh|J7kWRTjDdV>4)r+4AgzE-u(X|pw{I9Kc&xn$FC7KQ*HIR=f1 z?7F%CD^uT|VP&`=-CZ}8tzJMdFv?bX&DohHzKlt$m%FCeoaFb7F=IN=^=i+|Tg&4` z54udM&1+h%-}j=VZ2hYnSAvAro<8#J*sfE)lU{qw6ITtM(r>SyapJ74Oxx?DzO0_% zE^5DZ{_k2nEg|lzP4%@CpVeIwSAG1@{OnO*KBL2mla`BI#3ueZ;9J4JW$nv<-ph`e z--mfe7TDD&(ir; zD>7|Xxn z$883MC|!1jsy8KmC8mrHfz6*AHhsCLSX6Yuw_+iWCBqlF_iI`odNKrTTi0^v5|1Ur z4ZFyKRDO-G{!Xhr{zy!^cfG6o^3+@6e1}<9e|p!_`d7(l){@ti&u;$}_sNTn{GPw* zb(ICrbpd;CZXb#M-#s6-dJ=V}mYqA5u=Y&&D=t<>w(v7HbohL`eMv5>WY$()c9)aB zE8lh>emOOLd4Wx*xBFLJh6OI?QYzZL7#0{`)tJNYq@d6v)9A#|m94XMp5VklhgGi? zcX+zI6YQ;KXjqaxDduXF;NRnu40Kd0luY}TR|-AtQ!sF1=y>o}+lWEDo{8aNQFh|n z9_wJ2W!itY2D_w)oU}7PnI92nBtB*Or6ZhD{|o z#4Y*b#pcJKUd%dvxADnBp0`{17O8Q$XSV7yDrG83U9VoXbW6271IzZOJzso3*dMFd zWP7_JMc$D2iu-hL+ogy#3-t2$ZTng$@LQPq)8P}l`tE6aGaOJk%Qva^ zrT({`)A@Z;GUeN+e~?rCxUZupeA}we$LF3~B%YqfZTe)o_!aB#j0}FPYMHIFl0}mz z=x>{PEQvp#|A%gLxVEb0qJRuzyEDQ(!WW|M72n$NBTmbFQOk$$@O=|=VlV%#j`<=c z!M~odL1U&OSA6icb=!1qvN25Ty=bg(?*mW$)2frz(Wk8XnkIA#2k3e|V^WCS{Ih6& zMX0k-)kVt%TWpvn9D5q3F7~%>S>5T^pQiuH5mnj1;lS6CqV$ecY3kkDgL8KVD@!LW zXL#`NE#v-spfInwfmnstFoMS zaha^b6aT84e@e_*{sl66_oy>@_0;g n-#M$BpSMfzob|W32x32/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 924bc6ce6..1f7b12aff 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 22955470b..263aa5d0e 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 067bd91cc..8b39917c7 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 fa421ab21..fbfaeac87 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 afc31ff27..e39b69ab7 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 a40980efd..3b4e11e52 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 6ce6ff616..ac40257d3 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 ea1800d29..b68597acd 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 8b39917c7..950a4048b 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 fbfaeac87..89e827b45 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 3b4e11e52..ac5639e05 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 ac40257d3..7b2d18a08 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 d2f5deadc..7ebf66f61 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 3bc70e34b..0242465b9 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 b68597acd..e72a57f65 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 1f7b12aff..59671d0e2 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 263aa5d0e..6e5463fce 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 950a4048b..7e4f1b7d9 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 ac5639e05..86ca050be 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 7b2d18a08..12be72954 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 59671d0e2..8ff5cbe48 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 89e827b45..ce408ca00 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 e39b69ab7..b3cffc405 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 0242465b9..e7f667684 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 7bd06f6a6..53f1f134e 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 e72a57f65..967970626 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 8ff5cbe48..3748d836c 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 967970626..f58a884c6 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 6e5463fce..08a02d299 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 ce408ca00..8d47699b1 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 b3cffc405..01ae4b442 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 f58a884c6..fe5766dc9 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 12274b701..a1aae5243 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 000000000..d298ae83c --- /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 000000000..0293bb230 --- /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 86ca050be..6b807b8c6 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 12be72954..ece8e328f 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 000000000..7deaa36cb --- /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 000000000..0c1cb95e4 --- /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 000000000..ffcb7fdbd --- /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 000000000..85c686202 --- /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 000000000..7da0bb4aa --- /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 d298ae83c..ca8f7fd71 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 7ebf66f61..1d2194f32 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 7deaa36cb..199f9c548 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 ffcb7fdbd..9978ad7b4 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 891676cfd..fe163caee 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 e7f667684..68a20ecfd 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 fe5766dc9..d03199842 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 199f9c548..2cf83261c 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 68a20ecfd..4f6a491e0 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 1d2194f32..09b199fb8 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 9978ad7b4..80f3de19b 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 8d47699b1..a546eb7c1 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 01ae4b442..3a4cd2707 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 d03199842..61912cd75 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 09b199fb8..494d32f03 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 b5b4c9b29..0fb51e84f 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 cbd6c6176..8d5bd12da 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 d1b8cbe70..2b676a8cb 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 a546eb7c1..1a31e940f 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 4f6a491e0..715741563 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 61912cd75..ee3c9e765 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 ca8f7fd71..4b81fd7c0 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 1a31e940f..9017eb67d 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 4b81fd7c0..a8b2495a4 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 +}