From fdbd8d9d2b2e04cd68fd800882309b40c05aba2c Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Tue, 1 Nov 2022 22:45:15 +0100 Subject: refactor: remove old updater Signed-off-by: Sefa Eyeoglu --- launcher/ui/dialogs/BlockedModsDialog.cpp | 12 +- launcher/ui/dialogs/ExportInstanceDialog.cpp | 1 + launcher/ui/dialogs/UpdateDialog.cpp | 217 --------------------------- launcher/ui/dialogs/UpdateDialog.h | 67 --------- launcher/ui/dialogs/UpdateDialog.ui | 91 ----------- 5 files changed, 9 insertions(+), 379 deletions(-) delete mode 100644 launcher/ui/dialogs/UpdateDialog.cpp delete mode 100644 launcher/ui/dialogs/UpdateDialog.h delete mode 100644 launcher/ui/dialogs/UpdateDialog.ui (limited to 'launcher/ui/dialogs') diff --git a/launcher/ui/dialogs/BlockedModsDialog.cpp b/launcher/ui/dialogs/BlockedModsDialog.cpp index edb4ff7d..eeeeb709 100644 --- a/launcher/ui/dialogs/BlockedModsDialog.cpp +++ b/launcher/ui/dialogs/BlockedModsDialog.cpp @@ -1,14 +1,18 @@ #include "BlockedModsDialog.h" -#include -#include -#include -#include "Application.h" #include "ui_BlockedModsDialog.h" +#include "Application.h" + #include +#include +#include +#include +#include #include #include #include +#include +#include #include BlockedModsDialog::BlockedModsDialog(QWidget* parent, const QString& title, const QString& text, QList& mods) diff --git a/launcher/ui/dialogs/ExportInstanceDialog.cpp b/launcher/ui/dialogs/ExportInstanceDialog.cpp index 88552b23..f13e36e8 100644 --- a/launcher/ui/dialogs/ExportInstanceDialog.cpp +++ b/launcher/ui/dialogs/ExportInstanceDialog.cpp @@ -44,6 +44,7 @@ #include #include #include +#include #include "StringUtils.h" #include "SeparatorPrefixTree.h" #include "Application.h" diff --git a/launcher/ui/dialogs/UpdateDialog.cpp b/launcher/ui/dialogs/UpdateDialog.cpp deleted file mode 100644 index 9e82531a..00000000 --- a/launcher/ui/dialogs/UpdateDialog.cpp +++ /dev/null @@ -1,217 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-only -/* - * PolyMC - Minecraft Launcher - * Copyright (C) 2022 Sefa Eyeoglu - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, version 3. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * This file incorporates work covered by the following copyright and - * permission notice: - * - * Copyright 2013-2021 MultiMC Contributors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "UpdateDialog.h" -#include "ui_UpdateDialog.h" -#include -#include "Application.h" -#include -#include - -#include "BuildConfig.h" -#include "HoeDown.h" - -UpdateDialog::UpdateDialog(bool hasUpdate, QWidget *parent) : QDialog(parent), ui(new Ui::UpdateDialog) -{ - ui->setupUi(this); - auto channel = APPLICATION->settings()->get("UpdateChannel").toString(); - if(hasUpdate) - { - ui->label->setText(tr("A new %1 update is available!").arg(channel)); - } - else - { - ui->label->setText(tr("No %1 updates found. You are running the latest version.").arg(channel)); - ui->btnUpdateNow->setHidden(true); - ui->btnUpdateLater->setText(tr("Close")); - } - ui->changelogBrowser->setHtml(tr("

Loading changelog...

")); - loadChangelog(); - restoreGeometry(QByteArray::fromBase64(APPLICATION->settings()->get("UpdateDialogGeometry").toByteArray())); -} - -UpdateDialog::~UpdateDialog() -{ -} - -void UpdateDialog::loadChangelog() -{ - auto channel = APPLICATION->settings()->get("UpdateChannel").toString(); - dljob = new NetJob("Changelog", APPLICATION->network()); - QString url; - if(channel == "stable") - { - url = QString("https://raw.githubusercontent.com/PrismLauncher/PrismLauncher/%1/changelog.md").arg(channel); - m_changelogType = CHANGELOG_MARKDOWN; - } - else - { - url = QString("https://api.github.com/repos/PrismLauncher/PrismLauncher/compare/%1...%2").arg(BuildConfig.GIT_COMMIT, channel); - m_changelogType = CHANGELOG_COMMITS; - } - dljob->addNetAction(Net::Download::makeByteArray(QUrl(url), &changelogData)); - connect(dljob.get(), &NetJob::succeeded, this, &UpdateDialog::changelogLoaded); - connect(dljob.get(), &NetJob::failed, this, &UpdateDialog::changelogFailed); - dljob->start(); -} - -QString reprocessMarkdown(QByteArray markdown) -{ - HoeDown hoedown; - QString output = hoedown.process(markdown); - - // HACK: easier than customizing hoedown - output.replace(QRegularExpression("GH-([0-9]+)"), "GH-\\1"); - qDebug() << output; - return output; -} - -QString reprocessCommits(QByteArray json) -{ - auto channel = APPLICATION->settings()->get("UpdateChannel").toString(); - try - { - QString result; - auto document = Json::requireDocument(json); - auto rootobject = Json::requireObject(document); - auto status = Json::requireString(rootobject, "status"); - auto diff_url = Json::requireString(rootobject, "html_url"); - - auto print_commits = [&]() - { - result += ""; - auto commitarray = Json::requireArray(rootobject, "commits"); - for(int i = commitarray.size() - 1; i >= 0; i--) - { - const auto & commitval = commitarray[i]; - auto commitobj = Json::requireObject(commitval); - auto parents_info = Json::ensureArray(commitobj, "parents"); - // NOTE: this ignores merge commits, because they have more than one parent - if(parents_info.size() > 1) - { - continue; - } - auto commit_url = Json::requireString(commitobj, "html_url"); - auto commit_info = Json::requireObject(commitobj, "commit"); - auto commit_message = Json::requireString(commit_info, "message"); - auto lines = commit_message.split('\n'); - QRegularExpression regexp("(?(GH-(?[0-9]+))|(NOISSUE)|(SCRATCH))? *(?.*) *"); - auto match = regexp.match(lines.takeFirst(), 0, QRegularExpression::NormalMatch); - auto issuenr = match.captured("issuenr"); - auto prefix = match.captured("prefix"); - auto rest = match.captured("rest"); - result += ""; - lines.prepend(rest); - result += ""; - } - result += "
"; - if(issuenr.length()) - { - result += QString("GH-%2").arg(issuenr, issuenr); - } - else if(prefix.length()) - { - result += QString("%2").arg(commit_url, prefix); - } - else - { - result += QString("NOISSUE").arg(commit_url); - } - result += "

" + lines.join("
") + "

"; - }; - - if(status == "identical") - { - return QObject::tr("

There are no code changes between your current version and latest %1.

").arg(channel); - } - else if(status == "ahead") - { - result += QObject::tr("

Following commits were added since last update:

"); - print_commits(); - } - else if(status == "diverged") - { - auto commit_ahead = Json::requireInteger(rootobject, "ahead_by"); - auto commit_behind = Json::requireInteger(rootobject, "behind_by"); - result += QObject::tr("

The update removes %1 commits and adds the following %2:

").arg(commit_behind).arg(commit_ahead); - print_commits(); - } - result += QObject::tr("

You can look at the changes on github.

").arg(diff_url); - return result; - } - catch (const JSONValidationError &e) - { - qWarning() << "Got an unparseable commit log from github:" << e.what(); - qDebug() << json; - } - return QString(); -} - -void UpdateDialog::changelogLoaded() -{ - QString result; - switch(m_changelogType) - { - case CHANGELOG_COMMITS: - result = reprocessCommits(changelogData); - break; - case CHANGELOG_MARKDOWN: - result = reprocessMarkdown(changelogData); - break; - } - changelogData.clear(); - ui->changelogBrowser->setHtml(result); -} - -void UpdateDialog::changelogFailed(QString reason) -{ - ui->changelogBrowser->setHtml(tr("

Failed to fetch changelog... Error: %1

").arg(reason)); -} - -void UpdateDialog::on_btnUpdateLater_clicked() -{ - reject(); -} - -void UpdateDialog::on_btnUpdateNow_clicked() -{ - done(UPDATE_NOW); -} - -void UpdateDialog::closeEvent(QCloseEvent* evt) -{ - APPLICATION->settings()->set("UpdateDialogGeometry", saveGeometry().toBase64()); - QDialog::closeEvent(evt); -} diff --git a/launcher/ui/dialogs/UpdateDialog.h b/launcher/ui/dialogs/UpdateDialog.h deleted file mode 100644 index 07cbe09f..00000000 --- a/launcher/ui/dialogs/UpdateDialog.h +++ /dev/null @@ -1,67 +0,0 @@ -/* 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 "net/NetJob.h" - -namespace Ui -{ -class UpdateDialog; -} - -enum UpdateAction -{ - UPDATE_LATER = QDialog::Rejected, - UPDATE_NOW = QDialog::Accepted, -}; - -enum ChangelogType -{ - CHANGELOG_MARKDOWN, - CHANGELOG_COMMITS -}; - -class UpdateDialog : public QDialog -{ - Q_OBJECT - -public: - explicit UpdateDialog(bool hasUpdate = true, QWidget *parent = 0); - ~UpdateDialog(); - -public slots: - void on_btnUpdateNow_clicked(); - void on_btnUpdateLater_clicked(); - - /// Starts loading the changelog - void loadChangelog(); - - /// Slot for when the chengelog loads successfully. - void changelogLoaded(); - - /// Slot for when the chengelog fails to load... - void changelogFailed(QString reason); - -protected: - void closeEvent(QCloseEvent * ) override; - -private: - Ui::UpdateDialog *ui; - QByteArray changelogData; - NetJob::Ptr dljob; - ChangelogType m_changelogType = CHANGELOG_MARKDOWN; -}; diff --git a/launcher/ui/dialogs/UpdateDialog.ui b/launcher/ui/dialogs/UpdateDialog.ui deleted file mode 100644 index 5eb9d88a..00000000 --- a/launcher/ui/dialogs/UpdateDialog.ui +++ /dev/null @@ -1,91 +0,0 @@ - - - UpdateDialog - - - - 0 - 0 - 657 - 673 - - - - Launcher Update - - - - :/icons/toolbar/checkupdate:/icons/toolbar/checkupdate - - - - - - - - - 14 - - - - - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - changelogBrowser - - - - - - - - - true - - - - - - - - - - 0 - 0 - - - - Update now - - - - - - - - 0 - 0 - - - - Don't update yet - - - - - - - - - changelogBrowser - btnUpdateNow - btnUpdateLater - - - - - - -- cgit From 03b75bf2a98edd4114be4799f974bb10fe9b82c4 Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Fri, 30 Dec 2022 18:06:17 -0700 Subject: feat: Import all the things! Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- launcher/Application.cpp | 35 ++++++---- launcher/Application.h | 2 +- launcher/CMakeLists.txt | 6 +- .../minecraft/mod/tasks/LocalDataPackParseTask.cpp | 4 +- .../minecraft/mod/tasks/LocalResourceParse.cpp | 21 ++++++ launcher/minecraft/mod/tasks/LocalResourceParse.h | 6 ++ launcher/ui/MainWindow.cpp | 56 ++++++++++----- launcher/ui/MainWindow.h | 2 +- launcher/ui/dialogs/ImportResourceDialog.cpp | 70 +++++++++++++++++++ launcher/ui/dialogs/ImportResourceDialog.h | 30 ++++++++ launcher/ui/dialogs/ImportResourceDialog.ui | 81 ++++++++++++++++++++++ launcher/ui/dialogs/ImportResourcePackDialog.cpp | 65 ----------------- launcher/ui/dialogs/ImportResourcePackDialog.h | 27 -------- launcher/ui/dialogs/ImportResourcePackDialog.ui | 74 -------------------- 14 files changed, 277 insertions(+), 202 deletions(-) create mode 100644 launcher/ui/dialogs/ImportResourceDialog.cpp create mode 100644 launcher/ui/dialogs/ImportResourceDialog.h create mode 100644 launcher/ui/dialogs/ImportResourceDialog.ui delete mode 100644 launcher/ui/dialogs/ImportResourcePackDialog.cpp delete mode 100644 launcher/ui/dialogs/ImportResourcePackDialog.h delete mode 100644 launcher/ui/dialogs/ImportResourcePackDialog.ui (limited to 'launcher/ui/dialogs') diff --git a/launcher/Application.cpp b/launcher/Application.cpp index ff34a168..581e51ae 100644 --- a/launcher/Application.cpp +++ b/launcher/Application.cpp @@ -259,9 +259,18 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv) m_serverToJoin = parser.value("server"); m_profileToUse = parser.value("profile"); m_liveCheck = parser.isSet("alive"); - m_zipToImport = parser.value("import"); + m_instanceIdToShowWindowOf = parser.value("show"); + for (auto zip_path : parser.values("import")){ + m_zipsToImport.append(QUrl(zip_path)); + } + + for (auto zip_path : parser.positionalArguments()){ // treat unspesified positional arguments as import urls + m_zipsToImport.append(QUrl(zip_path)); + } + + // error if --launch is missing with --server or --profile if((!m_serverToJoin.isEmpty() || !m_profileToUse.isEmpty()) && m_instanceIdToLaunch.isEmpty()) { @@ -345,7 +354,7 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv) } /* - * Establish the mechanism for communication with an already running PolyMC that uses the same data path. + * Establish the mechanism for communication with an already running PrismLauncher that uses the same data path. * If there is one, tell it what the user actually wanted to do and exit. * We want to initialize this before logging to avoid messing with the log of a potential already running copy. */ @@ -363,12 +372,14 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv) activate.command = "activate"; m_peerInstance->sendMessage(activate.serialize(), timeout); - if(!m_zipToImport.isEmpty()) + if(!m_zipsToImport.isEmpty()) { - ApplicationMessage import; - import.command = "import"; - import.args.insert("path", m_zipToImport.toString()); - m_peerInstance->sendMessage(import.serialize(), timeout); + for (auto zip_url : m_zipsToImport) { + ApplicationMessage import; + import.command = "import"; + import.args.insert("path", zip_url.toString()); + m_peerInstance->sendMessage(import.serialize(), timeout); + } } } else @@ -938,7 +949,7 @@ bool Application::event(QEvent* event) if (event->type() == QEvent::FileOpen) { auto ev = static_cast(event); - m_mainWindow->droppedURLs({ ev->url() }); + m_mainWindow->processURLs({ ev->url() }); } return QApplication::event(event); @@ -998,10 +1009,10 @@ void Application::performMainStartupAction() showMainWindow(false); qDebug() << "<> Main window shown."; } - if(!m_zipToImport.isEmpty()) + if(!m_zipsToImport.isEmpty()) { - qDebug() << "<> Importing instance from zip:" << m_zipToImport; - m_mainWindow->droppedURLs({ m_zipToImport }); + qDebug() << "<> Importing from zip:" << m_zipsToImport; + m_mainWindow->processURLs( m_zipsToImport ); } } @@ -1054,7 +1065,7 @@ void Application::messageReceived(const QByteArray& message) qWarning() << "Received" << command << "message without a zip path/URL."; return; } - m_mainWindow->droppedURLs({ QUrl(path) }); + m_mainWindow->processURLs({ QUrl(path) }); } else if(command == "launch") { diff --git a/launcher/Application.h b/launcher/Application.h index 7884227a..cd90088e 100644 --- a/launcher/Application.h +++ b/launcher/Application.h @@ -303,7 +303,7 @@ public: QString m_serverToJoin; QString m_profileToUse; bool m_liveCheck = false; - QUrl m_zipToImport; + QList m_zipsToImport; QString m_instanceIdToShowWindowOf; std::unique_ptr logFile; }; diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index 8b5c63ff..a3520e72 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -841,8 +841,8 @@ SET(LAUNCHER_SOURCES ui/dialogs/ExportInstanceDialog.h ui/dialogs/IconPickerDialog.cpp ui/dialogs/IconPickerDialog.h - ui/dialogs/ImportResourcePackDialog.cpp - ui/dialogs/ImportResourcePackDialog.h + ui/dialogs/ImportResourceDialog.cpp + ui/dialogs/ImportResourceDialog.h ui/dialogs/LoginDialog.cpp ui/dialogs/LoginDialog.h ui/dialogs/MSALoginDialog.cpp @@ -992,7 +992,7 @@ qt_wrap_ui(LAUNCHER_UI ui/dialogs/SkinUploadDialog.ui ui/dialogs/ExportInstanceDialog.ui ui/dialogs/IconPickerDialog.ui - ui/dialogs/ImportResourcePackDialog.ui + ui/dialogs/ImportResourceDialog.ui ui/dialogs/MSALoginDialog.ui ui/dialogs/OfflineLoginDialog.ui ui/dialogs/AboutDialog.ui diff --git a/launcher/minecraft/mod/tasks/LocalDataPackParseTask.cpp b/launcher/minecraft/mod/tasks/LocalDataPackParseTask.cpp index 3fcb2110..5bb44877 100644 --- a/launcher/minecraft/mod/tasks/LocalDataPackParseTask.cpp +++ b/launcher/minecraft/mod/tasks/LocalDataPackParseTask.cpp @@ -50,7 +50,7 @@ bool processFolder(DataPack& pack, ProcessingLevel level) Q_ASSERT(pack.type() == ResourceType::FOLDER); auto mcmeta_invalid = [&pack]() { - qWarning() << "Resource pack at" << pack.fileinfo().filePath() << "does not have a valid pack.mcmeta"; + qWarning() << "Data pack at" << pack.fileinfo().filePath() << "does not have a valid pack.mcmeta"; return false; // the mcmeta is not optional }; @@ -95,7 +95,7 @@ bool processZIP(DataPack& pack, ProcessingLevel level) QuaZipFile file(&zip); auto mcmeta_invalid = [&pack]() { - qWarning() << "Resource pack at" << pack.fileinfo().filePath() << "does not have a valid pack.mcmeta"; + qWarning() << "Data pack at" << pack.fileinfo().filePath() << "does not have a valid pack.mcmeta"; return false; // the mcmeta is not optional }; diff --git a/launcher/minecraft/mod/tasks/LocalResourceParse.cpp b/launcher/minecraft/mod/tasks/LocalResourceParse.cpp index 19ddc899..63833832 100644 --- a/launcher/minecraft/mod/tasks/LocalResourceParse.cpp +++ b/launcher/minecraft/mod/tasks/LocalResourceParse.cpp @@ -19,6 +19,8 @@ * along with this program. If not, see . */ +#include + #include "LocalResourceParse.h" #include "LocalDataPackParseTask.h" @@ -28,6 +30,17 @@ #include "LocalTexturePackParseTask.h" #include "LocalWorldSaveParseTask.h" + +static const QMap s_packed_type_names = { + {PackedResourceType::ResourcePack, QObject::tr("resource pack")}, + {PackedResourceType::TexturePack, QObject::tr("texture pack")}, + {PackedResourceType::DataPack, QObject::tr("data pack")}, + {PackedResourceType::ShaderPack, QObject::tr("shader pack")}, + {PackedResourceType::WorldSave, QObject::tr("world save")}, + {PackedResourceType::Mod , QObject::tr("mod")}, + {PackedResourceType::UNKNOWN, QObject::tr("unknown")} +}; + namespace ResourceUtils { PackedResourceType identify(QFileInfo file){ if (file.exists() && file.isFile()) { @@ -57,4 +70,12 @@ PackedResourceType identify(QFileInfo file){ } return PackedResourceType::UNKNOWN; } + +QString getPackedTypeName(PackedResourceType type) { + return s_packed_type_names.constFind(type).value(); } + +} + + + diff --git a/launcher/minecraft/mod/tasks/LocalResourceParse.h b/launcher/minecraft/mod/tasks/LocalResourceParse.h index b07a874c..7385d24b 100644 --- a/launcher/minecraft/mod/tasks/LocalResourceParse.h +++ b/launcher/minecraft/mod/tasks/LocalResourceParse.h @@ -21,11 +21,17 @@ #pragma once +#include + #include #include #include enum class PackedResourceType { DataPack, ResourcePack, TexturePack, ShaderPack, WorldSave, Mod, UNKNOWN }; namespace ResourceUtils { +static const std::set ValidResourceTypes = { PackedResourceType::DataPack, PackedResourceType::ResourcePack, + PackedResourceType::TexturePack, PackedResourceType::ShaderPack, + PackedResourceType::WorldSave, PackedResourceType::Mod }; PackedResourceType identify(QFileInfo file); +QString getPackedTypeName(PackedResourceType type); } // namespace ResourceUtils diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp index e913849d..1d2e44e5 100644 --- a/launcher/ui/MainWindow.cpp +++ b/launcher/ui/MainWindow.cpp @@ -109,13 +109,12 @@ #include "ui/dialogs/UpdateDialog.h" #include "ui/dialogs/EditAccountDialog.h" #include "ui/dialogs/ExportInstanceDialog.h" -#include "ui/dialogs/ImportResourcePackDialog.h" +#include "ui/dialogs/ImportResourceDialog.h" #include "ui/themes/ITheme.h" -#include -#include -#include -#include +#include "minecraft/mod/tasks/LocalResourceParse.h" +#include "minecraft/mod/ModFolderModel.h" +#include "minecraft/WorldList.h" #include "UpdateController.h" #include "KonamiCode.h" @@ -954,7 +953,7 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new MainWindow view->installEventFilter(this); view->setContextMenuPolicy(Qt::CustomContextMenu); connect(view, &QWidget::customContextMenuRequested, this, &MainWindow::showInstanceContextMenu); - connect(view, &InstanceView::droppedURLs, this, &MainWindow::droppedURLs, Qt::QueuedConnection); + connect(view, &InstanceView::droppedURLs, this, &MainWindow::processURLs, Qt::QueuedConnection); proxymodel = new InstanceProxyModel(this); proxymodel->setSourceModel(APPLICATION->instances().get()); @@ -1813,10 +1812,12 @@ void MainWindow::on_actionAddInstance_triggered() addInstance(); } -void MainWindow::droppedURLs(QList urls) +void MainWindow::processURLs(QList urls) { // NOTE: This loop only processes one dropped file! for (auto& url : urls) { + qDebug() << "Processing :" << url; + // The isLocalFile() check below doesn't work as intended without an explicit scheme. if (url.scheme().isEmpty()) url.setScheme("file"); @@ -1829,28 +1830,49 @@ void MainWindow::droppedURLs(QList urls) auto localFileName = url.toLocalFile(); QFileInfo localFileInfo(localFileName); - bool isResourcePack = ResourcePackUtils::validate(localFileInfo); - bool isTexturePack = TexturePackUtils::validate(localFileInfo); + auto type = ResourceUtils::identify(localFileInfo); + + // bool is_resource = type; - if (!isResourcePack && !isTexturePack) { // probably instance/modpack + if (!(ResourceUtils::ValidResourceTypes.count(type) > 0)) { // probably instance/modpack addInstance(localFileName); - break; + continue; } - ImportResourcePackDialog dlg(this); + ImportResourceDialog dlg(localFileName, type, this); if (dlg.exec() != QDialog::Accepted) - break; + continue; - qDebug() << "Adding resource/texture pack" << localFileName << "to" << dlg.selectedInstanceKey; + qDebug() << "Adding resource" << localFileName << "to" << dlg.selectedInstanceKey; auto inst = APPLICATION->instances()->getInstanceById(dlg.selectedInstanceKey); auto minecraftInst = std::dynamic_pointer_cast(inst); - if (isResourcePack) + + switch (type) { + case PackedResourceType::ResourcePack: minecraftInst->resourcePackList()->installResource(localFileName); - else if (isTexturePack) + break; + case PackedResourceType::TexturePack: minecraftInst->texturePackList()->installResource(localFileName); - break; + break; + case PackedResourceType::DataPack: + qWarning() << "Importing of Data Packs not supported at this time. Ignoring" << localFileName; + break; + case PackedResourceType::Mod: + minecraftInst->loaderModList()->installMod(localFileName); + break; + case PackedResourceType::ShaderPack: + minecraftInst->shaderPackList()->installResource(localFileName); + break; + case PackedResourceType::WorldSave: + minecraftInst->worldList()->installWorld(localFileName); + break; + case PackedResourceType::UNKNOWN: + default: + qDebug() << "Can't Identify" << localFileName << "Ignoring it."; + break; + } } } diff --git a/launcher/ui/MainWindow.h b/launcher/ui/MainWindow.h index f96f641d..6bf5f428 100644 --- a/launcher/ui/MainWindow.h +++ b/launcher/ui/MainWindow.h @@ -80,7 +80,7 @@ public: void updatesAllowedChanged(bool allowed); - void droppedURLs(QList urls); + void processURLs(QList urls); signals: void isClosing(); diff --git a/launcher/ui/dialogs/ImportResourceDialog.cpp b/launcher/ui/dialogs/ImportResourceDialog.cpp new file mode 100644 index 00000000..84b69273 --- /dev/null +++ b/launcher/ui/dialogs/ImportResourceDialog.cpp @@ -0,0 +1,70 @@ +#include "ImportResourceDialog.h" +#include "ui_ImportResourceDialog.h" + +#include +#include + +#include "Application.h" +#include "InstanceList.h" + +#include +#include "ui/instanceview/InstanceDelegate.h" +#include "ui/instanceview/InstanceProxyModel.h" + +ImportResourceDialog::ImportResourceDialog(QString file_path, PackedResourceType type, QWidget* parent) + : QDialog(parent), ui(new Ui::ImportResourceDialog), m_resource_type(type), m_file_path(file_path) +{ + ui->setupUi(this); + setWindowModality(Qt::WindowModal); + + auto contentsWidget = ui->instanceView; + contentsWidget->setViewMode(QListView::ListMode); + contentsWidget->setFlow(QListView::LeftToRight); + contentsWidget->setIconSize(QSize(48, 48)); + contentsWidget->setMovement(QListView::Static); + contentsWidget->setResizeMode(QListView::Adjust); + contentsWidget->setSelectionMode(QAbstractItemView::SingleSelection); + contentsWidget->setSpacing(5); + contentsWidget->setWordWrap(true); + contentsWidget->setWrapping(true); + // NOTE: We can't have uniform sizes because the text may wrap if it's too long. If we set this, it will cut off the wrapped text. + contentsWidget->setUniformItemSizes(false); + contentsWidget->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel); + contentsWidget->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + contentsWidget->setItemDelegate(new ListViewDelegate()); + + proxyModel = new InstanceProxyModel(this); + proxyModel->setSourceModel(APPLICATION->instances().get()); + proxyModel->sort(0); + contentsWidget->setModel(proxyModel); + + connect(contentsWidget, SIGNAL(doubleClicked(QModelIndex)), SLOT(activated(QModelIndex))); + connect(contentsWidget->selectionModel(), SIGNAL(selectionChanged(QItemSelection, QItemSelection)), + SLOT(selectionChanged(QItemSelection, QItemSelection))); + + ui->label->setText( + tr("Choose the instance you would like to import this %1 to.").arg(ResourceUtils::getPackedTypeName(m_resource_type))); + ui->label_file_path->setText(tr("File: %1").arg(m_file_path)); +} + +void ImportResourceDialog::activated(QModelIndex index) +{ + selectedInstanceKey = index.data(InstanceList::InstanceIDRole).toString(); + accept(); +} + +void ImportResourceDialog::selectionChanged(QItemSelection selected, QItemSelection deselected) +{ + if (selected.empty()) + return; + + QString key = selected.first().indexes().first().data(InstanceList::InstanceIDRole).toString(); + if (!key.isEmpty()) { + selectedInstanceKey = key; + } +} + +ImportResourceDialog::~ImportResourceDialog() +{ + delete ui; +} diff --git a/launcher/ui/dialogs/ImportResourceDialog.h b/launcher/ui/dialogs/ImportResourceDialog.h new file mode 100644 index 00000000..c9e3f956 --- /dev/null +++ b/launcher/ui/dialogs/ImportResourceDialog.h @@ -0,0 +1,30 @@ +#pragma once + +#include +#include + +#include "ui/instanceview/InstanceProxyModel.h" +#include "minecraft/mod/tasks/LocalResourceParse.h" + +namespace Ui { +class ImportResourceDialog; +} + +class ImportResourceDialog : public QDialog { + Q_OBJECT + + public: + explicit ImportResourceDialog(QString file_path, PackedResourceType type, QWidget* parent = 0); + ~ImportResourceDialog(); + InstanceProxyModel* proxyModel; + QString selectedInstanceKey; + + private: + Ui::ImportResourceDialog* ui; + PackedResourceType m_resource_type; + QString m_file_path; + + private slots: + void selectionChanged(QItemSelection, QItemSelection); + void activated(QModelIndex); +}; diff --git a/launcher/ui/dialogs/ImportResourceDialog.ui b/launcher/ui/dialogs/ImportResourceDialog.ui new file mode 100644 index 00000000..cc3f4ec1 --- /dev/null +++ b/launcher/ui/dialogs/ImportResourceDialog.ui @@ -0,0 +1,81 @@ + + + ImportResourceDialog + + + + 0 + 0 + 676 + 555 + + + + Choose instance to import to + + + + + + Choose the instance you would like to import this resource pack to. + + + + + + + + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + buttonBox + accepted() + ImportResourceDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + ImportResourceDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/launcher/ui/dialogs/ImportResourcePackDialog.cpp b/launcher/ui/dialogs/ImportResourcePackDialog.cpp deleted file mode 100644 index e8902656..00000000 --- a/launcher/ui/dialogs/ImportResourcePackDialog.cpp +++ /dev/null @@ -1,65 +0,0 @@ -#include "ImportResourcePackDialog.h" -#include "ui_ImportResourcePackDialog.h" - -#include -#include - -#include "Application.h" -#include "InstanceList.h" - -#include -#include "ui/instanceview/InstanceProxyModel.h" -#include "ui/instanceview/InstanceDelegate.h" - -ImportResourcePackDialog::ImportResourcePackDialog(QWidget* parent) : QDialog(parent), ui(new Ui::ImportResourcePackDialog) -{ - ui->setupUi(this); - setWindowModality(Qt::WindowModal); - - auto contentsWidget = ui->instanceView; - contentsWidget->setViewMode(QListView::ListMode); - contentsWidget->setFlow(QListView::LeftToRight); - contentsWidget->setIconSize(QSize(48, 48)); - contentsWidget->setMovement(QListView::Static); - contentsWidget->setResizeMode(QListView::Adjust); - contentsWidget->setSelectionMode(QAbstractItemView::SingleSelection); - contentsWidget->setSpacing(5); - contentsWidget->setWordWrap(true); - contentsWidget->setWrapping(true); - // NOTE: We can't have uniform sizes because the text may wrap if it's too long. If we set this, it will cut off the wrapped text. - contentsWidget->setUniformItemSizes(false); - contentsWidget->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel); - contentsWidget->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - contentsWidget->setItemDelegate(new ListViewDelegate()); - - proxyModel = new InstanceProxyModel(this); - proxyModel->setSourceModel(APPLICATION->instances().get()); - proxyModel->sort(0); - contentsWidget->setModel(proxyModel); - - connect(contentsWidget, SIGNAL(doubleClicked(QModelIndex)), SLOT(activated(QModelIndex))); - connect(contentsWidget->selectionModel(), SIGNAL(selectionChanged(QItemSelection, QItemSelection)), - SLOT(selectionChanged(QItemSelection, QItemSelection))); -} - -void ImportResourcePackDialog::activated(QModelIndex index) -{ - selectedInstanceKey = index.data(InstanceList::InstanceIDRole).toString(); - accept(); -} - -void ImportResourcePackDialog::selectionChanged(QItemSelection selected, QItemSelection deselected) -{ - if (selected.empty()) - return; - - QString key = selected.first().indexes().first().data(InstanceList::InstanceIDRole).toString(); - if (!key.isEmpty()) { - selectedInstanceKey = key; - } -} - -ImportResourcePackDialog::~ImportResourcePackDialog() -{ - delete ui; -} diff --git a/launcher/ui/dialogs/ImportResourcePackDialog.h b/launcher/ui/dialogs/ImportResourcePackDialog.h deleted file mode 100644 index 8356f204..00000000 --- a/launcher/ui/dialogs/ImportResourcePackDialog.h +++ /dev/null @@ -1,27 +0,0 @@ -#pragma once - -#include -#include - -#include "ui/instanceview/InstanceProxyModel.h" - -namespace Ui { -class ImportResourcePackDialog; -} - -class ImportResourcePackDialog : public QDialog { - Q_OBJECT - - public: - explicit ImportResourcePackDialog(QWidget* parent = 0); - ~ImportResourcePackDialog(); - InstanceProxyModel* proxyModel; - QString selectedInstanceKey; - - private: - Ui::ImportResourcePackDialog* ui; - - private slots: - void selectionChanged(QItemSelection, QItemSelection); - void activated(QModelIndex); -}; diff --git a/launcher/ui/dialogs/ImportResourcePackDialog.ui b/launcher/ui/dialogs/ImportResourcePackDialog.ui deleted file mode 100644 index 20cb9177..00000000 --- a/launcher/ui/dialogs/ImportResourcePackDialog.ui +++ /dev/null @@ -1,74 +0,0 @@ - - - ImportResourcePackDialog - - - - 0 - 0 - 676 - 555 - - - - Choose instance to import - - - - - - Choose the instance you would like to import this resource pack to. - - - - - - - - - - Qt::Horizontal - - - QDialogButtonBox::Cancel|QDialogButtonBox::Ok - - - - - - - - - buttonBox - accepted() - ImportResourcePackDialog - accept() - - - 248 - 254 - - - 157 - 274 - - - - - buttonBox - rejected() - ImportResourcePackDialog - reject() - - - 316 - 260 - - - 286 - 274 - - - - - -- cgit From 574af2c795a19246c18e5f07a49d6d41f5670a6e Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Mon, 9 Jan 2023 17:12:28 -0700 Subject: chore: cleanup review suggestions Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- launcher/minecraft/mod/tasks/LocalResourceParse.cpp | 3 --- launcher/ui/dialogs/ImportResourceDialog.h | 10 +++++----- 2 files changed, 5 insertions(+), 8 deletions(-) (limited to 'launcher/ui/dialogs') diff --git a/launcher/minecraft/mod/tasks/LocalResourceParse.cpp b/launcher/minecraft/mod/tasks/LocalResourceParse.cpp index 63833832..4d760df2 100644 --- a/launcher/minecraft/mod/tasks/LocalResourceParse.cpp +++ b/launcher/minecraft/mod/tasks/LocalResourceParse.cpp @@ -76,6 +76,3 @@ QString getPackedTypeName(PackedResourceType type) { } } - - - diff --git a/launcher/ui/dialogs/ImportResourceDialog.h b/launcher/ui/dialogs/ImportResourceDialog.h index c9e3f956..5f2f7a92 100644 --- a/launcher/ui/dialogs/ImportResourceDialog.h +++ b/launcher/ui/dialogs/ImportResourceDialog.h @@ -3,8 +3,8 @@ #include #include -#include "ui/instanceview/InstanceProxyModel.h" #include "minecraft/mod/tasks/LocalResourceParse.h" +#include "ui/instanceview/InstanceProxyModel.h" namespace Ui { class ImportResourceDialog; @@ -14,15 +14,15 @@ class ImportResourceDialog : public QDialog { Q_OBJECT public: - explicit ImportResourceDialog(QString file_path, PackedResourceType type, QWidget* parent = 0); - ~ImportResourceDialog(); - InstanceProxyModel* proxyModel; + explicit ImportResourceDialog(QString file_path, PackedResourceType type, QWidget* parent = nullptr); + ~ImportResourceDialog() override; QString selectedInstanceKey; - + private: Ui::ImportResourceDialog* ui; PackedResourceType m_resource_type; QString m_file_path; + InstanceProxyModel* proxyModel; private slots: void selectionChanged(QItemSelection, QItemSelection); -- cgit From 24a4bd3a1c33702946b88a3d8017268fb8134210 Mon Sep 17 00:00:00 2001 From: Joshua Goins Date: Fri, 6 Jan 2023 15:26:26 -0500 Subject: refactor: replace hoedown markdown parser with cmark Signed-off-by: Joshua Goins --- launcher/CMakeLists.txt | 4 +- launcher/HoeDown.h | 76 ---------------------- launcher/Markdown.h | 34 ++++++++++ launcher/ui/dialogs/AboutDialog.cpp | 6 +- launcher/ui/dialogs/ModUpdateDialog.cpp | 11 +--- launcher/ui/dialogs/UpdateDialog.cpp | 5 +- launcher/ui/pages/instance/ManagedPackPage.cpp | 6 +- launcher/ui/pages/modplatform/ModPage.cpp | 11 +--- launcher/ui/pages/modplatform/ftb/FtbPage.cpp | 5 +- .../ui/pages/modplatform/modrinth/ModrinthPage.cpp | 6 +- 10 files changed, 50 insertions(+), 114 deletions(-) delete mode 100644 launcher/HoeDown.h create mode 100644 launcher/Markdown.h (limited to 'launcher/ui/dialogs') diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index 6ca88ec6..7dc744aa 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -617,7 +617,7 @@ SET(LAUNCHER_SOURCES DesktopServices.cpp VersionProxyModel.h VersionProxyModel.cpp - HoeDown.h + Markdown.h # Super secret! KonamiCode.h @@ -1043,7 +1043,7 @@ target_link_libraries(Launcher_logic ) target_link_libraries(Launcher_logic QuaZip::QuaZip - hoedown + cmark LocalPeer Launcher_rainbow ) diff --git a/launcher/HoeDown.h b/launcher/HoeDown.h deleted file mode 100644 index cb62de6c..00000000 --- a/launcher/HoeDown.h +++ /dev/null @@ -1,76 +0,0 @@ -/* 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 -#include - -/** - * hoedown wrapper, because dealing with resource lifetime in C is stupid - */ -class HoeDown -{ -public: - class buffer - { - public: - buffer(size_t unit = 4096) - { - buf = hoedown_buffer_new(unit); - } - ~buffer() - { - hoedown_buffer_free(buf); - } - const char * cstr() - { - return hoedown_buffer_cstr(buf); - } - void put(QByteArray input) - { - hoedown_buffer_put(buf, reinterpret_cast(input.data()), input.size()); - } - const uint8_t * data() const - { - return buf->data; - } - size_t size() const - { - return buf->size; - } - hoedown_buffer * buf; - } ib, ob; - HoeDown() - { - renderer = hoedown_html_renderer_new((hoedown_html_flags) 0,0); - document = hoedown_document_new(renderer, (hoedown_extensions) 0, 8); - } - ~HoeDown() - { - hoedown_document_free(document); - hoedown_html_renderer_free(renderer); - } - QString process(QByteArray input) - { - ib.put(input); - hoedown_document_render(document, ob.buf, ib.data(), ib.size()); - return ob.cstr(); - } -private: - hoedown_document * document; - hoedown_renderer * renderer; -}; diff --git a/launcher/Markdown.h b/launcher/Markdown.h new file mode 100644 index 00000000..f115dd57 --- /dev/null +++ b/launcher/Markdown.h @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (C) 2023 Joshua Goins + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once + +#include +#include + +static QString markdownToHTML(const QString& markdown) +{ + const QByteArray markdownData = markdown.toUtf8(); + char* buffer = cmark_markdown_to_html(markdownData.constData(), markdownData.length(), CMARK_OPT_NOBREAKS | CMARK_OPT_UNSAFE); + + QString htmlStr(buffer); + + free(buffer); + + return htmlStr; +} \ No newline at end of file diff --git a/launcher/ui/dialogs/AboutDialog.cpp b/launcher/ui/dialogs/AboutDialog.cpp index a36e4a3d..76e3d8ed 100644 --- a/launcher/ui/dialogs/AboutDialog.cpp +++ b/launcher/ui/dialogs/AboutDialog.cpp @@ -39,12 +39,11 @@ #include #include "Application.h" #include "BuildConfig.h" +#include "Markdown.h" #include #include -#include "HoeDown.h" - namespace { QString getLink(QString link, QString name) { return QString("<%2>").arg(link).arg(name); @@ -114,10 +113,9 @@ QString getCreditsHtml() QString getLicenseHtml() { - HoeDown hoedown; QFile dataFile(":/documents/COPYING.md"); dataFile.open(QIODevice::ReadOnly); - QString output = hoedown.process(dataFile.readAll()); + QString output = markdownToHTML(dataFile.readAll()); return output; } diff --git a/launcher/ui/dialogs/ModUpdateDialog.cpp b/launcher/ui/dialogs/ModUpdateDialog.cpp index cedd4a96..2704243e 100644 --- a/launcher/ui/dialogs/ModUpdateDialog.cpp +++ b/launcher/ui/dialogs/ModUpdateDialog.cpp @@ -7,6 +7,7 @@ #include "FileSystem.h" #include "Json.h" +#include "Markdown.h" #include "tasks/ConcurrentTask.h" @@ -17,7 +18,6 @@ #include "modplatform/flame/FlameCheckUpdate.h" #include "modplatform/modrinth/ModrinthCheckUpdate.h" -#include #include #include @@ -369,14 +369,7 @@ void ModUpdateDialog::appendMod(CheckUpdateTask::UpdatableMod const& info) QString text = info.changelog; switch (info.provider) { case ModPlatform::Provider::MODRINTH: { - HoeDown h; - // HoeDown bug?: \n aren't converted to
- text = h.process(info.changelog.toUtf8()); - - // Don't convert if there's an HTML tag right after (Qt rendering weirdness) - text.remove(QRegularExpression("(\n+)(?=<)")); - text.replace('\n', "
"); - + text = markdownToHTML(info.changelog.toUtf8()); break; } default: diff --git a/launcher/ui/dialogs/UpdateDialog.cpp b/launcher/ui/dialogs/UpdateDialog.cpp index 9e82531a..349d768f 100644 --- a/launcher/ui/dialogs/UpdateDialog.cpp +++ b/launcher/ui/dialogs/UpdateDialog.cpp @@ -41,7 +41,7 @@ #include #include "BuildConfig.h" -#include "HoeDown.h" +#include "Markdown.h" UpdateDialog::UpdateDialog(bool hasUpdate, QWidget *parent) : QDialog(parent), ui(new Ui::UpdateDialog) { @@ -89,8 +89,7 @@ void UpdateDialog::loadChangelog() QString reprocessMarkdown(QByteArray markdown) { - HoeDown hoedown; - QString output = hoedown.process(markdown); + QString output = markdownToHTML(markdown); // HACK: easier than customizing hoedown output.replace(QRegularExpression("GH-([0-9]+)"), "GH-\\1"); diff --git a/launcher/ui/pages/instance/ManagedPackPage.cpp b/launcher/ui/pages/instance/ManagedPackPage.cpp index 4de80468..8d56d894 100644 --- a/launcher/ui/pages/instance/ManagedPackPage.cpp +++ b/launcher/ui/pages/instance/ManagedPackPage.cpp @@ -9,14 +9,13 @@ #include #include -#include - #include "Application.h" #include "BuildConfig.h" #include "InstanceImportTask.h" #include "InstanceList.h" #include "InstanceTask.h" #include "Json.h" +#include "Markdown.h" #include "modplatform/modrinth/ModrinthPackManifest.h" @@ -263,8 +262,7 @@ void ModrinthManagedPackPage::suggestVersion() auto index = ui->versionsComboBox->currentIndex(); auto version = m_pack.versions.at(index); - HoeDown md_parser; - ui->changelogTextBrowser->setHtml(md_parser.process(version.changelog.toUtf8())); + ui->changelogTextBrowser->setHtml(markdownToHTML(version.changelog.toUtf8())); ManagedPackPage::suggestVersion(); } diff --git a/launcher/ui/pages/modplatform/ModPage.cpp b/launcher/ui/pages/modplatform/ModPage.cpp index 75be25b2..0f30689e 100644 --- a/launcher/ui/pages/modplatform/ModPage.cpp +++ b/launcher/ui/pages/modplatform/ModPage.cpp @@ -43,13 +43,11 @@ #include #include -#include - #include "minecraft/MinecraftInstance.h" #include "minecraft/PackProfile.h" #include "ui/dialogs/ModDownloadDialog.h" #include "ui/widgets/ProjectItem.h" - +#include "Markdown.h" ModPage::ModPage(ModDownloadDialog* dialog, BaseInstance* instance, ModAPI* api) : QWidget(dialog) @@ -427,11 +425,6 @@ void ModPage::updateUi() text += "
"; - HoeDown h; - - // hoedown bug: it doesn't handle markdown surrounded by block tags (like center, div) so strip them - current.extraData.body.remove(QRegularExpression("<[^>]*(?:center|div)\\W*>")); - - ui->packDescription->setHtml(text + (current.extraData.body.isEmpty() ? current.description : h.process(current.extraData.body.toUtf8()))); + ui->packDescription->setHtml(text + (current.extraData.body.isEmpty() ? current.description : markdownToHTML(current.extraData.body))); ui->packDescription->flush(); } diff --git a/launcher/ui/pages/modplatform/ftb/FtbPage.cpp b/launcher/ui/pages/modplatform/ftb/FtbPage.cpp index b08f3bc4..7d59a6ae 100644 --- a/launcher/ui/pages/modplatform/ftb/FtbPage.cpp +++ b/launcher/ui/pages/modplatform/ftb/FtbPage.cpp @@ -43,7 +43,7 @@ #include "ui/dialogs/NewInstanceDialog.h" #include "modplatform/modpacksch/FTBPackInstallTask.h" -#include "HoeDown.h" +#include "Markdown.h" FtbPage::FtbPage(NewInstanceDialog* dialog, QWidget *parent) : QWidget(parent), ui(new Ui::FtbPage), dialog(dialog) @@ -175,8 +175,7 @@ void FtbPage::onSelectionChanged(QModelIndex first, QModelIndex second) selected = filterModel->data(first, Qt::UserRole).value(); - HoeDown hoedown; - QString output = hoedown.process(selected.description.toUtf8()); + QString output = markdownToHTML(selected.description.toUtf8()); ui->packDescription->setHtml(output); // reverse foreach, so that the newest versions are first diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp index 8ab2ad1d..0bb11d83 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp @@ -42,11 +42,10 @@ #include "BuildConfig.h" #include "InstanceImportTask.h" #include "Json.h" +#include "Markdown.h" #include "ui/widgets/ProjectItem.h" -#include - #include #include #include @@ -280,8 +279,7 @@ void ModrinthPage::updateUI() text += "
"; - HoeDown h; - text += h.process(current.extra.body.toUtf8()); + text += markdownToHTML(current.extra.body.toUtf8()); ui->packDescription->setHtml(text + current.description); ui->packDescription->flush(); -- cgit From 6a1807995390b2a2cbe074ee1f47d3791e0e3f10 Mon Sep 17 00:00:00 2001 From: flow Date: Fri, 25 Nov 2022 09:23:46 -0300 Subject: refactor: generalize mod models and APIs to resources Firstly, this abstract away behavior in the mod download models that can also be applied to other types of resources into a superclass, allowing other resource types to be implemented without so much code duplication. For that, this also generalizes the APIs used (currently, ModrinthAPI and FlameAPI) to be able to make requests to other types of resources. It also does a general cleanup of both of those. In particular, this makes use of std::optional instead of invalid values for errors and, well, optional values :p This is a squash of some commits that were becoming too interlaced together to be cleanly separated. Signed-off-by: flow --- launcher/CMakeLists.txt | 37 ++- launcher/ModDownloadTask.cpp | 72 ----- launcher/ModDownloadTask.h | 57 ---- launcher/ResourceDownloadTask.cpp | 80 +++++ launcher/ResourceDownloadTask.h | 57 ++++ launcher/minecraft/PackProfile.cpp | 28 +- launcher/minecraft/PackProfile.h | 4 +- launcher/modplatform/CheckUpdateTask.h | 14 +- launcher/modplatform/EnsureMetadataTask.cpp | 14 +- launcher/modplatform/EnsureMetadataTask.h | 6 +- launcher/modplatform/ModAPI.h | 118 ------- launcher/modplatform/ModIndex.cpp | 24 +- launcher/modplatform/ModIndex.h | 19 +- launcher/modplatform/ResourceAPI.h | 149 +++++++++ launcher/modplatform/flame/FlameAPI.cpp | 16 +- launcher/modplatform/flame/FlameAPI.h | 99 +++--- launcher/modplatform/flame/FlameCheckUpdate.cpp | 11 +- launcher/modplatform/flame/FlameCheckUpdate.h | 2 +- .../flame/FlameInstanceCreationTask.cpp | 4 +- .../modplatform/flame/FlameInstanceCreationTask.h | 2 +- launcher/modplatform/flame/FlameModIndex.cpp | 4 +- launcher/modplatform/helpers/HashUtils.cpp | 16 +- launcher/modplatform/helpers/HashUtils.h | 10 +- launcher/modplatform/helpers/NetworkModAPI.cpp | 97 ------ launcher/modplatform/helpers/NetworkModAPI.h | 17 - .../modplatform/helpers/NetworkResourceAPI.cpp | 124 +++++++ launcher/modplatform/helpers/NetworkResourceAPI.h | 18 ++ launcher/modplatform/modrinth/ModrinthAPI.cpp | 36 ++- launcher/modplatform/modrinth/ModrinthAPI.h | 106 +++--- .../modplatform/modrinth/ModrinthCheckUpdate.cpp | 25 +- .../modplatform/modrinth/ModrinthCheckUpdate.h | 2 +- .../modplatform/modrinth/ModrinthPackIndex.cpp | 4 +- launcher/modplatform/packwiz/Packwiz.cpp | 8 +- launcher/modplatform/packwiz/Packwiz.h | 2 +- launcher/net/NetAction.h | 4 - launcher/net/NetJob.cpp | 5 +- launcher/ui/dialogs/BlockedModsDialog.cpp | 2 +- launcher/ui/dialogs/ChooseProviderDialog.cpp | 6 +- launcher/ui/dialogs/ChooseProviderDialog.h | 6 +- launcher/ui/dialogs/ModDownloadDialog.cpp | 165 +--------- launcher/ui/dialogs/ModDownloadDialog.h | 43 +-- launcher/ui/dialogs/ModUpdateDialog.cpp | 44 +-- launcher/ui/dialogs/ModUpdateDialog.h | 8 +- launcher/ui/dialogs/ResourceDownloadDialog.cpp | 152 +++++++++ launcher/ui/dialogs/ResourceDownloadDialog.h | 55 ++++ launcher/ui/dialogs/ReviewMessageBox.cpp | 4 +- launcher/ui/dialogs/ReviewMessageBox.h | 8 +- launcher/ui/pages/instance/ModFolderPage.cpp | 6 +- launcher/ui/pages/instance/ResourcePackPage.h | 1 + launcher/ui/pages/modplatform/ModModel.cpp | 274 +++------------- launcher/ui/pages/modplatform/ModModel.h | 64 +--- launcher/ui/pages/modplatform/ModPage.cpp | 357 +++------------------ launcher/ui/pages/modplatform/ModPage.h | 78 +---- launcher/ui/pages/modplatform/ModPage.ui | 118 ------- launcher/ui/pages/modplatform/ResourceModel.cpp | 258 +++++++++++++++ launcher/ui/pages/modplatform/ResourceModel.h | 101 ++++++ launcher/ui/pages/modplatform/ResourcePage.cpp | 347 ++++++++++++++++++++ launcher/ui/pages/modplatform/ResourcePage.h | 95 ++++++ launcher/ui/pages/modplatform/ResourcePage.ui | 118 +++++++ .../ui/pages/modplatform/flame/FlameModModel.cpp | 31 -- .../ui/pages/modplatform/flame/FlameModModel.h | 26 -- .../ui/pages/modplatform/flame/FlameModPage.cpp | 97 ------ launcher/ui/pages/modplatform/flame/FlameModPage.h | 70 ---- .../modplatform/flame/FlameResourceModels.cpp | 31 ++ .../pages/modplatform/flame/FlameResourceModels.h | 26 ++ .../pages/modplatform/flame/FlameResourcePages.cpp | 97 ++++++ .../pages/modplatform/flame/FlameResourcePages.h | 71 ++++ .../modplatform/modrinth/ModrinthModModel.cpp | 48 --- .../pages/modplatform/modrinth/ModrinthModModel.h | 44 --- .../pages/modplatform/modrinth/ModrinthModPage.cpp | 84 ----- .../pages/modplatform/modrinth/ModrinthModPage.h | 66 ---- .../modrinth/ModrinthResourceModels.cpp | 53 +++ .../modplatform/modrinth/ModrinthResourceModels.h | 49 +++ .../modplatform/modrinth/ModrinthResourcePages.cpp | 89 +++++ .../modplatform/modrinth/ModrinthResourcePages.h | 72 +++++ launcher/ui/widgets/ProgressWidget.cpp | 6 +- launcher/ui/widgets/ProgressWidget.h | 6 +- tests/Packwiz_test.cpp | 4 +- 78 files changed, 2508 insertions(+), 2063 deletions(-) delete mode 100644 launcher/ModDownloadTask.cpp delete mode 100644 launcher/ModDownloadTask.h create mode 100644 launcher/ResourceDownloadTask.cpp create mode 100644 launcher/ResourceDownloadTask.h delete mode 100644 launcher/modplatform/ModAPI.h create mode 100644 launcher/modplatform/ResourceAPI.h delete mode 100644 launcher/modplatform/helpers/NetworkModAPI.cpp delete mode 100644 launcher/modplatform/helpers/NetworkModAPI.h create mode 100644 launcher/modplatform/helpers/NetworkResourceAPI.cpp create mode 100644 launcher/modplatform/helpers/NetworkResourceAPI.h create mode 100644 launcher/ui/dialogs/ResourceDownloadDialog.cpp create mode 100644 launcher/ui/dialogs/ResourceDownloadDialog.h delete mode 100644 launcher/ui/pages/modplatform/ModPage.ui create mode 100644 launcher/ui/pages/modplatform/ResourceModel.cpp create mode 100644 launcher/ui/pages/modplatform/ResourceModel.h create mode 100644 launcher/ui/pages/modplatform/ResourcePage.cpp create mode 100644 launcher/ui/pages/modplatform/ResourcePage.h create mode 100644 launcher/ui/pages/modplatform/ResourcePage.ui delete mode 100644 launcher/ui/pages/modplatform/flame/FlameModModel.cpp delete mode 100644 launcher/ui/pages/modplatform/flame/FlameModModel.h delete mode 100644 launcher/ui/pages/modplatform/flame/FlameModPage.cpp delete mode 100644 launcher/ui/pages/modplatform/flame/FlameModPage.h create mode 100644 launcher/ui/pages/modplatform/flame/FlameResourceModels.cpp create mode 100644 launcher/ui/pages/modplatform/flame/FlameResourceModels.h create mode 100644 launcher/ui/pages/modplatform/flame/FlameResourcePages.cpp create mode 100644 launcher/ui/pages/modplatform/flame/FlameResourcePages.h delete mode 100644 launcher/ui/pages/modplatform/modrinth/ModrinthModModel.cpp delete mode 100644 launcher/ui/pages/modplatform/modrinth/ModrinthModModel.h delete mode 100644 launcher/ui/pages/modplatform/modrinth/ModrinthModPage.cpp delete mode 100644 launcher/ui/pages/modplatform/modrinth/ModrinthModPage.h create mode 100644 launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.cpp create mode 100644 launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.h create mode 100644 launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.cpp create mode 100644 launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.h (limited to 'launcher/ui/dialogs') diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index eec6c787..a1a68f5b 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -38,9 +38,9 @@ set(CORE_SOURCES InstanceImportTask.h InstanceImportTask.cpp - # Mod downloading task - ModDownloadTask.h - ModDownloadTask.cpp + # Resource downloading task + ResourceDownloadTask.h + ResourceDownloadTask.cpp # Use tracking separate from memory management Usable.h @@ -473,7 +473,7 @@ set(API_SOURCES modplatform/ModIndex.h modplatform/ModIndex.cpp - modplatform/ModAPI.h + modplatform/ResourceAPI.h modplatform/EnsureMetadataTask.h modplatform/EnsureMetadataTask.cpp @@ -484,8 +484,8 @@ set(API_SOURCES modplatform/flame/FlameAPI.cpp modplatform/modrinth/ModrinthAPI.h modplatform/modrinth/ModrinthAPI.cpp - modplatform/helpers/NetworkModAPI.h - modplatform/helpers/NetworkModAPI.cpp + modplatform/helpers/NetworkResourceAPI.h + modplatform/helpers/NetworkResourceAPI.cpp modplatform/helpers/HashUtils.h modplatform/helpers/HashUtils.cpp modplatform/helpers/OverrideUtils.h @@ -771,6 +771,11 @@ SET(LAUNCHER_SOURCES ui/pages/modplatform/VanillaPage.cpp ui/pages/modplatform/VanillaPage.h + ui/pages/modplatform/ResourcePage.cpp + ui/pages/modplatform/ResourcePage.h + ui/pages/modplatform/ResourceModel.cpp + ui/pages/modplatform/ResourceModel.h + ui/pages/modplatform/ModPage.cpp ui/pages/modplatform/ModPage.h ui/pages/modplatform/ModModel.cpp @@ -803,10 +808,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/flame/FlameResourceModels.cpp + ui/pages/modplatform/flame/FlameResourceModels.h + ui/pages/modplatform/flame/FlameResourcePages.cpp + ui/pages/modplatform/flame/FlameResourcePages.h ui/pages/modplatform/modrinth/ModrinthPage.cpp ui/pages/modplatform/modrinth/ModrinthPage.h @@ -821,10 +826,10 @@ SET(LAUNCHER_SOURCES ui/pages/modplatform/ImportPage.cpp ui/pages/modplatform/ImportPage.h - ui/pages/modplatform/modrinth/ModrinthModModel.cpp - ui/pages/modplatform/modrinth/ModrinthModModel.h - ui/pages/modplatform/modrinth/ModrinthModPage.cpp - ui/pages/modplatform/modrinth/ModrinthModPage.h + ui/pages/modplatform/modrinth/ModrinthResourceModels.cpp + ui/pages/modplatform/modrinth/ModrinthResourceModels.h + ui/pages/modplatform/modrinth/ModrinthResourcePages.cpp + ui/pages/modplatform/modrinth/ModrinthResourcePages.h # GUI - dialogs ui/dialogs/AboutDialog.cpp @@ -869,6 +874,8 @@ SET(LAUNCHER_SOURCES ui/dialogs/VersionSelectDialog.h ui/dialogs/SkinUploadDialog.cpp ui/dialogs/SkinUploadDialog.h + ui/dialogs/ResourceDownloadDialog.cpp + ui/dialogs/ResourceDownloadDialog.h ui/dialogs/ModDownloadDialog.cpp ui/dialogs/ModDownloadDialog.h ui/dialogs/ScrollMessageBox.cpp @@ -965,7 +972,7 @@ qt_wrap_ui(LAUNCHER_UI ui/pages/modplatform/atlauncher/AtlOptionalModDialog.ui ui/pages/modplatform/atlauncher/AtlPage.ui ui/pages/modplatform/VanillaPage.ui - ui/pages/modplatform/ModPage.ui + ui/pages/modplatform/ResourcePage.ui ui/pages/modplatform/flame/FlamePage.ui ui/pages/modplatform/legacy_ftb/Page.ui ui/pages/modplatform/ImportPage.ui diff --git a/launcher/ModDownloadTask.cpp b/launcher/ModDownloadTask.cpp deleted file mode 100644 index 2b0343f4..00000000 --- a/launcher/ModDownloadTask.cpp +++ /dev/null @@ -1,72 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-only -/* -* PolyMC - Minecraft Launcher -* Copyright (c) 2022 flowln -* Copyright (C) 2022 Sefa Eyeoglu -* -* This program is free software: you can redistribute it and/or modify -* it under the terms of the GNU General Public License as published by -* the Free Software Foundation, version 3. -* -* This program is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU General Public License for more details. -* -* You should have received a copy of the GNU General Public License -* along with this program. If not, see . -*/ - -#include "ModDownloadTask.h" - -#include "Application.h" -#include "minecraft/mod/ModFolderModel.h" - -ModDownloadTask::ModDownloadTask(ModPlatform::IndexedPack mod, ModPlatform::IndexedVersion version, const std::shared_ptr mods, bool is_indexed) - : m_mod(mod), m_mod_version(version), mods(mods) -{ - if (is_indexed) { - m_update_task.reset(new LocalModUpdateTask(mods->indexDir(), m_mod, m_mod_version)); - connect(m_update_task.get(), &LocalModUpdateTask::hasOldMod, this, &ModDownloadTask::hasOldMod); - - addTask(m_update_task); - } - - m_filesNetJob.reset(new NetJob(tr("Mod download"), APPLICATION->network())); - m_filesNetJob->setStatus(tr("Downloading mod:\n%1").arg(m_mod_version.downloadUrl)); - - m_filesNetJob->addNetAction(Net::Download::makeFile(m_mod_version.downloadUrl, mods->dir().absoluteFilePath(getFilename()))); - 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); - - addTask(m_filesNetJob); -} - -void ModDownloadTask::downloadSucceeded() -{ - m_filesNetJob.reset(); - auto name = std::get<0>(to_delete); - auto filename = std::get<1>(to_delete); - if (!name.isEmpty() && filename != m_mod_version.fileName) { - mods->uninstallMod(filename, true); - } -} - -void ModDownloadTask::downloadFailed(QString reason) -{ - emitFailed(reason); - m_filesNetJob.reset(); -} - -void ModDownloadTask::downloadProgressChanged(qint64 current, qint64 total) -{ - emit progress(current, total); -} - -// This indirection is done so that we don't delete a mod before being sure it was -// downloaded successfully! -void ModDownloadTask::hasOldMod(QString name, QString filename) -{ - to_delete = {name, filename}; -} diff --git a/launcher/ModDownloadTask.h b/launcher/ModDownloadTask.h deleted file mode 100644 index 95020470..00000000 --- a/launcher/ModDownloadTask.h +++ /dev/null @@ -1,57 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-only -/* -* PolyMC - Minecraft Launcher -* Copyright (c) 2022 flowln -* Copyright (C) 2022 Sefa Eyeoglu -* -* This program is free software: you can redistribute it and/or modify -* it under the terms of the GNU General Public License as published by -* the Free Software Foundation, version 3. -* -* This program is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU General Public License for more details. -* -* You should have received a copy of the GNU General Public License -* along with this program. If not, see . -*/ - -#pragma once - -#include "net/NetJob.h" -#include "tasks/SequentialTask.h" - -#include "modplatform/ModIndex.h" -#include "minecraft/mod/tasks/LocalModUpdateTask.h" - -class ModFolderModel; - -class ModDownloadTask : public SequentialTask { - Q_OBJECT -public: - explicit ModDownloadTask(ModPlatform::IndexedPack mod, ModPlatform::IndexedVersion version, const std::shared_ptr mods, bool is_indexed = true); - const QString& getFilename() const { return m_mod_version.fileName; } - -private: - ModPlatform::IndexedPack m_mod; - ModPlatform::IndexedVersion m_mod_version; - const std::shared_ptr mods; - - NetJob::Ptr m_filesNetJob; - LocalModUpdateTask::Ptr m_update_task; - - void downloadProgressChanged(qint64 current, qint64 total); - - void downloadFailed(QString reason); - - void downloadSucceeded(); - - std::tuple to_delete {"", ""}; - -private slots: - void hasOldMod(QString name, QString filename); -}; - - - diff --git a/launcher/ResourceDownloadTask.cpp b/launcher/ResourceDownloadTask.cpp new file mode 100644 index 00000000..687eaf51 --- /dev/null +++ b/launcher/ResourceDownloadTask.cpp @@ -0,0 +1,80 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* +* PolyMC - Minecraft Launcher +* Copyright (c) 2022 flowln +* Copyright (C) 2022 Sefa Eyeoglu +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, version 3. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see . +*/ + +#include "ResourceDownloadTask.h" + +#include "Application.h" + +#include "minecraft/mod/ModFolderModel.h" +#include "minecraft/mod/ResourceFolderModel.h" + +ResourceDownloadTask::ResourceDownloadTask(ModPlatform::IndexedPack pack, + ModPlatform::IndexedVersion version, + const std::shared_ptr packs, + bool is_indexed) + : m_pack(std::move(pack)), m_pack_version(std::move(version)), m_pack_model(packs) +{ + if (auto model = dynamic_cast(m_pack_model.get()); model && is_indexed) { + m_update_task.reset(new LocalModUpdateTask(model->indexDir(), m_pack, m_pack_version)); + connect(m_update_task.get(), &LocalModUpdateTask::hasOldMod, this, &ResourceDownloadTask::hasOldResource); + + addTask(m_update_task); + } + + m_filesNetJob.reset(new NetJob(tr("Resource download"), APPLICATION->network())); + m_filesNetJob->setStatus(tr("Downloading resource:\n%1").arg(m_pack_version.downloadUrl)); + + m_filesNetJob->addNetAction(Net::Download::makeFile(m_pack_version.downloadUrl, m_pack_model->dir().absoluteFilePath(getFilename()))); + connect(m_filesNetJob.get(), &NetJob::succeeded, this, &ResourceDownloadTask::downloadSucceeded); + connect(m_filesNetJob.get(), &NetJob::progress, this, &ResourceDownloadTask::downloadProgressChanged); + connect(m_filesNetJob.get(), &NetJob::failed, this, &ResourceDownloadTask::downloadFailed); + + addTask(m_filesNetJob); +} + +void ResourceDownloadTask::downloadSucceeded() +{ + m_filesNetJob.reset(); + auto name = std::get<0>(to_delete); + auto filename = std::get<1>(to_delete); + if (!name.isEmpty() && filename != m_pack_version.fileName) { + if (auto model = dynamic_cast(m_pack_model.get()); model) + model->uninstallMod(filename, true); + else + m_pack_model->uninstallResource(filename); + } +} + +void ResourceDownloadTask::downloadFailed(QString reason) +{ + emitFailed(reason); + m_filesNetJob.reset(); +} + +void ResourceDownloadTask::downloadProgressChanged(qint64 current, qint64 total) +{ + emit progress(current, total); +} + +// This indirection is done so that we don't delete a mod before being sure it was +// downloaded successfully! +void ResourceDownloadTask::hasOldResource(QString name, QString filename) +{ + to_delete = { name, filename }; +} diff --git a/launcher/ResourceDownloadTask.h b/launcher/ResourceDownloadTask.h new file mode 100644 index 00000000..350c2edd --- /dev/null +++ b/launcher/ResourceDownloadTask.h @@ -0,0 +1,57 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* +* PolyMC - Minecraft Launcher +* Copyright (c) 2022 flowln +* Copyright (C) 2022 Sefa Eyeoglu +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, version 3. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see . +*/ + +#pragma once + +#include "net/NetJob.h" +#include "tasks/SequentialTask.h" + +#include "modplatform/ModIndex.h" +#include "minecraft/mod/tasks/LocalModUpdateTask.h" + +class ResourceFolderModel; + +class ResourceDownloadTask : public SequentialTask { + Q_OBJECT +public: + explicit ResourceDownloadTask(ModPlatform::IndexedPack pack, ModPlatform::IndexedVersion version, const std::shared_ptr packs, bool is_indexed = true); + const QString& getFilename() const { return m_pack_version.fileName; } + +private: + ModPlatform::IndexedPack m_pack; + ModPlatform::IndexedVersion m_pack_version; + const std::shared_ptr m_pack_model; + + NetJob::Ptr m_filesNetJob; + LocalModUpdateTask::Ptr m_update_task; + + void downloadProgressChanged(qint64 current, qint64 total); + + void downloadFailed(QString reason); + + void downloadSucceeded(); + + std::tuple to_delete {"", ""}; + +private slots: + void hasOldResource(QString name, QString filename); +}; + + + diff --git a/launcher/minecraft/PackProfile.cpp b/launcher/minecraft/PackProfile.cpp index 43fa3f8d..42021b3c 100644 --- a/launcher/minecraft/PackProfile.cpp +++ b/launcher/minecraft/PackProfile.cpp @@ -55,12 +55,13 @@ #include "PackProfile_p.h" #include "ComponentUpdateTask.h" -#include "modplatform/ModAPI.h" +#include "Application.h" +#include "modplatform/ResourceAPI.h" -static const QMap modloaderMapping{ - {"net.minecraftforge", ModAPI::Forge}, - {"net.fabricmc.fabric-loader", ModAPI::Fabric}, - {"org.quiltmc.quilt-loader", ModAPI::Quilt} +static const QMap modloaderMapping{ + {"net.minecraftforge", ResourceAPI::Forge}, + {"net.fabricmc.fabric-loader", ResourceAPI::Fabric}, + {"org.quiltmc.quilt-loader", ResourceAPI::Quilt} }; PackProfile::PackProfile(MinecraftInstance * instance) @@ -1066,19 +1067,22 @@ void PackProfile::disableInteraction(bool disable) } } -ModAPI::ModLoaderTypes PackProfile::getModLoaders() +std::optional PackProfile::getModLoaders() { - ModAPI::ModLoaderTypes result = ModAPI::Unspecified; + ResourceAPI::ModLoaderTypes result; + bool has_any_loader = false; - QMapIterator i(modloaderMapping); + QMapIterator i(modloaderMapping); - while (i.hasNext()) - { + while (i.hasNext()) { i.next(); - Component* c = getComponent(i.key()); - if (c != nullptr && c->isEnabled()) { + if (auto c = getComponent(i.key()); c != nullptr && c->isEnabled()) { result |= i.value(); + has_any_loader = true; } } + + if (!has_any_loader) + return {}; return result; } diff --git a/launcher/minecraft/PackProfile.h b/launcher/minecraft/PackProfile.h index 2330cca1..67b418f4 100644 --- a/launcher/minecraft/PackProfile.h +++ b/launcher/minecraft/PackProfile.h @@ -49,7 +49,7 @@ #include "BaseVersion.h" #include "MojangDownloadInfo.h" #include "net/Mode.h" -#include "modplatform/ModAPI.h" +#include "modplatform/ResourceAPI.h" class MinecraftInstance; struct PackProfileData; @@ -145,7 +145,7 @@ public: // todo(merged): is this the best approach void appendComponent(ComponentPtr component); - ModAPI::ModLoaderTypes getModLoaders(); + std::optional getModLoaders(); private: void scheduleSave(); diff --git a/launcher/modplatform/CheckUpdateTask.h b/launcher/modplatform/CheckUpdateTask.h index 91922034..932a62d9 100644 --- a/launcher/modplatform/CheckUpdateTask.h +++ b/launcher/modplatform/CheckUpdateTask.h @@ -1,18 +1,18 @@ #pragma once #include "minecraft/mod/Mod.h" -#include "modplatform/ModAPI.h" +#include "modplatform/ResourceAPI.h" #include "modplatform/ModIndex.h" #include "tasks/Task.h" -class ModDownloadTask; +class ResourceDownloadTask; class ModFolderModel; class CheckUpdateTask : public Task { Q_OBJECT public: - CheckUpdateTask(QList& mods, std::list& mcVersions, ModAPI::ModLoaderTypes loaders, std::shared_ptr mods_folder) + CheckUpdateTask(QList& mods, std::list& mcVersions, std::optional loaders, std::shared_ptr mods_folder) : Task(nullptr), m_mods(mods), m_game_versions(mcVersions), m_loaders(loaders), m_mods_folder(mods_folder) {}; struct UpdatableMod { @@ -21,11 +21,11 @@ class CheckUpdateTask : public Task { QString old_version; QString new_version; QString changelog; - ModPlatform::Provider provider; - ModDownloadTask* download; + ModPlatform::ResourceProvider provider; + ResourceDownloadTask* download; public: - UpdatableMod(QString name, QString old_h, QString old_v, QString new_v, QString changelog, ModPlatform::Provider p, ModDownloadTask* t) + UpdatableMod(QString name, QString old_h, QString old_v, QString new_v, QString changelog, ModPlatform::ResourceProvider p, ResourceDownloadTask* t) : name(name), old_hash(old_h), old_version(old_v), new_version(new_v), changelog(changelog), provider(p), download(t) {} }; @@ -44,7 +44,7 @@ class CheckUpdateTask : public Task { protected: QList& m_mods; std::list& m_game_versions; - ModAPI::ModLoaderTypes m_loaders; + std::optional m_loaders; std::shared_ptr m_mods_folder; std::vector m_updatable; diff --git a/launcher/modplatform/EnsureMetadataTask.cpp b/launcher/modplatform/EnsureMetadataTask.cpp index 234330a7..9bf81338 100644 --- a/launcher/modplatform/EnsureMetadataTask.cpp +++ b/launcher/modplatform/EnsureMetadataTask.cpp @@ -20,7 +20,7 @@ static ModPlatform::ProviderCapabilities ProviderCaps; static ModrinthAPI modrinth_api; static FlameAPI flame_api; -EnsureMetadataTask::EnsureMetadataTask(Mod* mod, QDir dir, ModPlatform::Provider prov) +EnsureMetadataTask::EnsureMetadataTask(Mod* mod, QDir dir, ModPlatform::ResourceProvider prov) : Task(nullptr), m_index_dir(dir), m_provider(prov), m_hashing_task(nullptr), m_current_task(nullptr) { auto hash_task = createNewHash(mod); @@ -31,7 +31,7 @@ EnsureMetadataTask::EnsureMetadataTask(Mod* mod, QDir dir, ModPlatform::Provider hash_task->start(); } -EnsureMetadataTask::EnsureMetadataTask(QList& mods, QDir dir, ModPlatform::Provider prov) +EnsureMetadataTask::EnsureMetadataTask(QList& mods, QDir dir, ModPlatform::ResourceProvider prov) : Task(nullptr), m_index_dir(dir), m_provider(prov), m_current_task(nullptr) { m_hashing_task = new ConcurrentTask(this, "MakeHashesTask", 10); @@ -110,10 +110,10 @@ void EnsureMetadataTask::executeTask() NetJob::Ptr version_task; switch (m_provider) { - case (ModPlatform::Provider::MODRINTH): + case (ModPlatform::ResourceProvider::MODRINTH): version_task = modrinthVersionsTask(); break; - case (ModPlatform::Provider::FLAME): + case (ModPlatform::ResourceProvider::FLAME): version_task = flameVersionsTask(); break; } @@ -130,10 +130,10 @@ void EnsureMetadataTask::executeTask() NetJob::Ptr project_task; switch (m_provider) { - case (ModPlatform::Provider::MODRINTH): + case (ModPlatform::ResourceProvider::MODRINTH): project_task = modrinthProjectsTask(); break; - case (ModPlatform::Provider::FLAME): + case (ModPlatform::ResourceProvider::FLAME): project_task = flameProjectsTask(); break; } @@ -212,7 +212,7 @@ void EnsureMetadataTask::emitFail(Mod* m, QString key, RemoveFromList remove) NetJob::Ptr EnsureMetadataTask::modrinthVersionsTask() { - auto hash_type = ProviderCaps.hashType(ModPlatform::Provider::MODRINTH).first(); + auto hash_type = ProviderCaps.hashType(ModPlatform::ResourceProvider::MODRINTH).first(); auto* response = new QByteArray(); auto ver_task = modrinth_api.currentVersions(m_mods.keys(), hash_type, response); diff --git a/launcher/modplatform/EnsureMetadataTask.h b/launcher/modplatform/EnsureMetadataTask.h index a8b0851e..a79e5861 100644 --- a/launcher/modplatform/EnsureMetadataTask.h +++ b/launcher/modplatform/EnsureMetadataTask.h @@ -14,8 +14,8 @@ class EnsureMetadataTask : public Task { Q_OBJECT public: - EnsureMetadataTask(Mod*, QDir, ModPlatform::Provider = ModPlatform::Provider::MODRINTH); - EnsureMetadataTask(QList&, QDir, ModPlatform::Provider = ModPlatform::Provider::MODRINTH); + EnsureMetadataTask(Mod*, QDir, ModPlatform::ResourceProvider = ModPlatform::ResourceProvider::MODRINTH); + EnsureMetadataTask(QList&, QDir, ModPlatform::ResourceProvider = ModPlatform::ResourceProvider::MODRINTH); ~EnsureMetadataTask() = default; @@ -57,7 +57,7 @@ class EnsureMetadataTask : public Task { private: QHash m_mods; QDir m_index_dir; - ModPlatform::Provider m_provider; + ModPlatform::ResourceProvider m_provider; QHash m_temp_versions; ConcurrentTask* m_hashing_task; diff --git a/launcher/modplatform/ModAPI.h b/launcher/modplatform/ModAPI.h deleted file mode 100644 index 703de143..00000000 --- a/launcher/modplatform/ModAPI.h +++ /dev/null @@ -1,118 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-only -/* - * PolyMC - Minecraft Launcher - * Copyright (C) 2022 Sefa Eyeoglu - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, version 3. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * This file incorporates work covered by the following copyright and - * permission notice: - * - * Copyright 2013-2021 MultiMC Contributors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include -#include -#include - -#include "../Version.h" -#include "net/NetJob.h" - -namespace ModPlatform { -class ListModel; -struct IndexedPack; -} - -class ModAPI { - protected: - using CallerType = ModPlatform::ListModel; - - public: - virtual ~ModAPI() = default; - - enum ModLoaderType { - Unspecified = 0, - Forge = 1 << 0, - Cauldron = 1 << 1, - LiteLoader = 1 << 2, - Fabric = 1 << 3, - Quilt = 1 << 4 - }; - Q_DECLARE_FLAGS(ModLoaderTypes, ModLoaderType) - - struct SearchArgs { - int offset; - QString search; - QString sorting; - ModLoaderTypes loaders; - std::list versions; - }; - - virtual void searchMods(CallerType* caller, SearchArgs&& args) const = 0; - virtual void getModInfo(ModPlatform::IndexedPack& pack, std::function callback) = 0; - - virtual auto getProject(QString addonId, QByteArray* response) const -> NetJob* = 0; - virtual auto getProjects(QStringList addonIds, QByteArray* response) const -> NetJob* = 0; - - - struct VersionSearchArgs { - QString addonId; - std::list mcVersions; - ModLoaderTypes loaders; - }; - - virtual void getVersions(VersionSearchArgs&& args, std::function callback) const = 0; - - static auto getModLoaderString(ModLoaderType type) -> const QString { - switch (type) { - case Unspecified: - break; - case Forge: - return "forge"; - case Cauldron: - return "cauldron"; - case LiteLoader: - return "liteloader"; - case Fabric: - return "fabric"; - case Quilt: - return "quilt"; - } - return ""; - } - - protected: - inline auto getGameVersionsString(std::list mcVersions) const -> QString - { - QString s; - for(auto& ver : mcVersions){ - s += QString("\"%1\",").arg(ver.toString()); - } - s.remove(s.length() - 1, 1); //remove last comma - return s; - } -}; diff --git a/launcher/modplatform/ModIndex.cpp b/launcher/modplatform/ModIndex.cpp index 34fd9f30..6a507caf 100644 --- a/launcher/modplatform/ModIndex.cpp +++ b/launcher/modplatform/ModIndex.cpp @@ -24,47 +24,47 @@ namespace ModPlatform { -auto ProviderCapabilities::name(Provider p) -> const char* +auto ProviderCapabilities::name(ResourceProvider p) -> const char* { switch (p) { - case Provider::MODRINTH: + case ResourceProvider::MODRINTH: return "modrinth"; - case Provider::FLAME: + case ResourceProvider::FLAME: return "curseforge"; } return {}; } -auto ProviderCapabilities::readableName(Provider p) -> QString +auto ProviderCapabilities::readableName(ResourceProvider p) -> QString { switch (p) { - case Provider::MODRINTH: + case ResourceProvider::MODRINTH: return "Modrinth"; - case Provider::FLAME: + case ResourceProvider::FLAME: return "CurseForge"; } return {}; } -auto ProviderCapabilities::hashType(Provider p) -> QStringList +auto ProviderCapabilities::hashType(ResourceProvider p) -> QStringList { switch (p) { - case Provider::MODRINTH: + case ResourceProvider::MODRINTH: return { "sha512", "sha1" }; - case Provider::FLAME: + case ResourceProvider::FLAME: // Try newer formats first, fall back to old format return { "sha1", "md5", "murmur2" }; } return {}; } -auto ProviderCapabilities::hash(Provider p, QIODevice* device, QString type) -> QString +auto ProviderCapabilities::hash(ResourceProvider p, QIODevice* device, QString type) -> QString { QCryptographicHash::Algorithm algo = QCryptographicHash::Sha1; switch (p) { - case Provider::MODRINTH: { + case ResourceProvider::MODRINTH: { algo = (type == "sha1") ? QCryptographicHash::Sha1 : QCryptographicHash::Sha512; break; } - case Provider::FLAME: + case ResourceProvider::FLAME: algo = (type == "sha1") ? QCryptographicHash::Sha1 : QCryptographicHash::Md5; break; } diff --git a/launcher/modplatform/ModIndex.h b/launcher/modplatform/ModIndex.h index 518fed7c..f65a6a4b 100644 --- a/launcher/modplatform/ModIndex.h +++ b/launcher/modplatform/ModIndex.h @@ -28,17 +28,16 @@ class QIODevice; namespace ModPlatform { -enum class Provider { - MODRINTH, - FLAME -}; +enum class ResourceProvider { MODRINTH, FLAME }; + +enum class ResourceType { MOD, RESOURCE_PACK }; class ProviderCapabilities { public: - auto name(Provider) -> const char*; - auto readableName(Provider) -> QString; - auto hashType(Provider) -> QStringList; - auto hash(Provider, QIODevice*, QString type = "") -> QString; + auto name(ResourceProvider) -> const char*; + auto readableName(ResourceProvider) -> QString; + auto hashType(ResourceProvider) -> QStringList; + auto hash(ResourceProvider, QIODevice*, QString type = "") -> QString; }; struct ModpackAuthor { @@ -81,7 +80,7 @@ struct ExtraPackData { struct IndexedPack { QVariant addonId; - Provider provider; + ResourceProvider provider; QString name; QString slug; QString description; @@ -101,4 +100,4 @@ struct IndexedPack { } // namespace ModPlatform Q_DECLARE_METATYPE(ModPlatform::IndexedPack) -Q_DECLARE_METATYPE(ModPlatform::Provider) +Q_DECLARE_METATYPE(ModPlatform::ResourceProvider) diff --git a/launcher/modplatform/ResourceAPI.h b/launcher/modplatform/ResourceAPI.h new file mode 100644 index 00000000..d18a2caa --- /dev/null +++ b/launcher/modplatform/ResourceAPI.h @@ -0,0 +1,149 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (C) 2022 Sefa Eyeoglu + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * This file incorporates work covered by the following copyright and + * permission notice: + * + * Copyright 2013-2021 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include + +#include + +#include "../Version.h" + +#include "modplatform/ModIndex.h" +#include "net/NetJob.h" + +/* Simple class with a common interface for interacting with APIs */ +class ResourceAPI { + public: + virtual ~ResourceAPI() = default; + + enum ModLoaderType { Forge = 1 << 0, Cauldron = 1 << 1, LiteLoader = 1 << 2, Fabric = 1 << 3, Quilt = 1 << 4 }; + Q_DECLARE_FLAGS(ModLoaderTypes, ModLoaderType) + + struct SearchArgs { + ModPlatform::ResourceType type{}; + int offset = 0; + + std::optional search; + std::optional sorting; + std::optional loaders; + std::optional > versions; + }; + struct SearchCallbacks { + std::function on_succeed; + std::function on_fail; + std::function on_abort; + }; + + struct VersionSearchArgs { + QString addonId; + + std::optional > mcVersions; + std::optional loaders; + }; + struct VersionSearchCallbacks { + std::function on_succeed; + }; + + struct ProjectInfoArgs { + ModPlatform::IndexedPack& pack; + + void operator=(ProjectInfoArgs other) { pack = other.pack; } + }; + struct ProjectInfoCallbacks { + std::function on_succeed; + }; + + public slots: + [[nodiscard]] virtual NetJob::Ptr searchProjects(SearchArgs&&, SearchCallbacks&&) const + { + qWarning() << "TODO"; + return nullptr; + } + [[nodiscard]] virtual NetJob::Ptr getProject(QString addonId, QByteArray* response) const + { + qWarning() << "TODO"; + return nullptr; + } + [[nodiscard]] virtual NetJob::Ptr getProjects(QStringList addonIds, QByteArray* response) const + { + qWarning() << "TODO"; + return nullptr; + } + + [[nodiscard]] virtual NetJob::Ptr getProjectInfo(ProjectInfoArgs&&, ProjectInfoCallbacks&&) const + { + qWarning() << "TODO"; + return nullptr; + } + [[nodiscard]] virtual NetJob::Ptr getProjectVersions(VersionSearchArgs&&, VersionSearchCallbacks&&) const + { + qWarning() << "TODO"; + return nullptr; + } + + static auto getModLoaderString(ModLoaderType type) -> const QString + { + switch (type) { + case Forge: + return "forge"; + case Cauldron: + return "cauldron"; + case LiteLoader: + return "liteloader"; + case Fabric: + return "fabric"; + case Quilt: + return "quilt"; + default: + break; + } + return ""; + } + + protected: + [[nodiscard]] inline QString debugName() const { return "External resource API"; } + + [[nodiscard]] inline auto getGameVersionsString(std::list mcVersions) const -> QString + { + QString s; + for (auto& ver : mcVersions) { + s += QString("\"%1\",").arg(ver.toString()); + } + s.remove(s.length() - 1, 1); // remove last comma + return s; + } +}; diff --git a/launcher/modplatform/flame/FlameAPI.cpp b/launcher/modplatform/flame/FlameAPI.cpp index 4d71da21..ae401399 100644 --- a/launcher/modplatform/flame/FlameAPI.cpp +++ b/launcher/modplatform/flame/FlameAPI.cpp @@ -106,13 +106,19 @@ auto FlameAPI::getModDescription(int modId) -> QString auto FlameAPI::getLatestVersion(VersionSearchArgs&& args) -> ModPlatform::IndexedVersion { + auto versions_url_optional = getVersionsURL(args); + if (!versions_url_optional.has_value()) + return {}; + + auto versions_url = versions_url_optional.value(); + QEventLoop loop; auto netJob = new NetJob(QString("Flame::GetLatestVersion(%1)").arg(args.addonId), APPLICATION->network()); auto response = new QByteArray(); ModPlatform::IndexedVersion ver; - netJob->addNetAction(Net::Download::makeByteArray(getVersionsURL(args), response)); + netJob->addNetAction(Net::Download::makeByteArray(versions_url, response)); QObject::connect(netJob, &NetJob::succeeded, [response, args, &ver] { QJsonParseError parse_error{}; @@ -161,7 +167,7 @@ auto FlameAPI::getLatestVersion(VersionSearchArgs&& args) -> ModPlatform::Indexe return ver; } -auto FlameAPI::getProjects(QStringList addonIds, QByteArray* response) const -> NetJob* +NetJob::Ptr FlameAPI::getProjects(QStringList addonIds, QByteArray* response) const { auto* netJob = new NetJob(QString("Flame::GetProjects"), APPLICATION->network()); @@ -178,13 +184,13 @@ auto FlameAPI::getProjects(QStringList addonIds, QByteArray* response) const -> netJob->addNetAction(Net::Upload::makeByteArray(QString("https://api.curseforge.com/v1/mods"), response, body_raw)); - QObject::connect(netJob, &NetJob::finished, [response, netJob] { delete response; netJob->deleteLater(); }); + QObject::connect(netJob, &NetJob::finished, [response] { delete response; }); QObject::connect(netJob, &NetJob::failed, [body_raw] { qDebug() << body_raw; }); return netJob; } -auto FlameAPI::getFiles(const QStringList& fileIds, QByteArray* response) const -> NetJob* +NetJob::Ptr FlameAPI::getFiles(const QStringList& fileIds, QByteArray* response) const { auto* netJob = new NetJob(QString("Flame::GetFiles"), APPLICATION->network()); @@ -201,7 +207,7 @@ auto FlameAPI::getFiles(const QStringList& fileIds, QByteArray* response) const netJob->addNetAction(Net::Upload::makeByteArray(QString("https://api.curseforge.com/v1/mods/files"), response, body_raw)); - QObject::connect(netJob, &NetJob::finished, [response, netJob] { delete response; netJob->deleteLater(); }); + QObject::connect(netJob, &NetJob::finished, [response] { delete response; }); QObject::connect(netJob, &NetJob::failed, [body_raw] { qDebug() << body_raw; }); return netJob; diff --git a/launcher/modplatform/flame/FlameAPI.h b/launcher/modplatform/flame/FlameAPI.h index 4c6ca64c..114a2716 100644 --- a/launcher/modplatform/flame/FlameAPI.h +++ b/launcher/modplatform/flame/FlameAPI.h @@ -1,21 +1,21 @@ #pragma once #include "modplatform/ModIndex.h" -#include "modplatform/helpers/NetworkModAPI.h" +#include "modplatform/helpers/NetworkResourceAPI.h" -class FlameAPI : public NetworkModAPI { +class FlameAPI : public NetworkResourceAPI { public: - auto matchFingerprints(const QList& fingerprints, QByteArray* response) -> NetJob::Ptr; auto getModFileChangelog(int modId, int fileId) -> QString; auto getModDescription(int modId) -> QString; auto getLatestVersion(VersionSearchArgs&& args) -> ModPlatform::IndexedVersion; - auto getProjects(QStringList addonIds, QByteArray* response) const -> NetJob* override; - auto getFiles(const QStringList& fileIds, QByteArray* response) const -> NetJob*; + NetJob::Ptr getProjects(QStringList addonIds, QByteArray* response) const override; + NetJob::Ptr matchFingerprints(const QList& fingerprints, QByteArray* response); + NetJob::Ptr getFiles(const QStringList& fileIds, QByteArray* response) const; private: - inline auto getSortFieldInt(QString sortString) const -> int + static int getSortFieldInt(QString const& sortString) { return sortString == "Featured" ? 1 : sortString == "Popularity" ? 2 @@ -28,48 +28,16 @@ class FlameAPI : public NetworkModAPI { : 1; } - private: - inline auto getModSearchURL(SearchArgs& args) const -> QString override - { - auto gameVersionStr = args.versions.size() != 0 ? QString("gameVersion=%1").arg(args.versions.front().toString()) : QString(); - - return QString( - "https://api.curseforge.com/v1/mods/search?" - "gameId=432&" - "classId=6&" - - "index=%1&" - "pageSize=25&" - "searchFilter=%2&" - "sortField=%3&" - "sortOrder=desc&" - "modLoaderType=%4&" - "%5") - .arg(args.offset) - .arg(args.search) - .arg(getSortFieldInt(args.sorting)) - .arg(getMappedModLoader(args.loaders)) - .arg(gameVersionStr); - }; - - inline auto getModInfoURL(QString& id) const -> QString override + static int getClassId(ModPlatform::ResourceType type) { - return QString("https://api.curseforge.com/v1/mods/%1").arg(id); - }; - - inline auto getVersionsURL(VersionSearchArgs& args) const -> QString override - { - QString gameVersionQuery = args.mcVersions.size() == 1 ? QString("gameVersion=%1&").arg(args.mcVersions.front().toString()) : ""; - QString modLoaderQuery = QString("modLoaderType=%1&").arg(getMappedModLoader(args.loaders)); - - return QString("https://api.curseforge.com/v1/mods/%1/files?pageSize=10000&%2%3") - .arg(args.addonId) - .arg(gameVersionQuery) - .arg(modLoaderQuery); - }; + switch (type) { + default: + case ModPlatform::ResourceType::MOD: + return 6; + } + } - public: - static auto getMappedModLoader(const ModLoaderTypes loaders) -> int + static int getMappedModLoader(ModLoaderTypes loaders) { // https://docs.curseforge.com/?http#tocS_ModLoaderType if (loaders & Forge) @@ -81,4 +49,43 @@ class FlameAPI : public NetworkModAPI { return 4; // Quilt would probably be 5 return 0; } + + private: + [[nodiscard]] std::optional getSearchURL(SearchArgs const& args) const override + { + auto gameVersionStr = args.versions.has_value() ? QString("gameVersion=%1").arg(args.versions.value().front().toString()) : QString(); + + QStringList get_arguments; + get_arguments.append(QString("classId=%1").arg(getClassId(args.type))); + get_arguments.append(QString("index=%1").arg(args.offset)); + get_arguments.append("pageSize=25"); + if (args.search.has_value()) + get_arguments.append(QString("searchFilter=%1").arg(args.search.value())); + if (args.sorting.has_value()) + get_arguments.append(QString("sortField=%1").arg(getSortFieldInt(args.sorting.value()))); + get_arguments.append("sortOrder=desc"); + if (args.loaders.has_value()) + get_arguments.append(QString("modLoaderType=%1").arg(getMappedModLoader(args.loaders.value()))); + get_arguments.append(gameVersionStr); + + return "https://api.curseforge.com/v1/mods/search?gameId=432&" + get_arguments.join('&'); + }; + + [[nodiscard]] std::optional getInfoURL(QString const& id) const override + { + return QString("https://api.curseforge.com/v1/mods/%1").arg(id); + }; + + [[nodiscard]] std::optional getVersionsURL(VersionSearchArgs const& args) const override + { + QString url{QString("https://api.curseforge.com/v1/mods/%1/files?pageSize=10000&").arg(args.addonId)}; + + QStringList get_parameters; + if (args.mcVersions.has_value()) + get_parameters.append(QString("gameVersion=%1").arg(args.mcVersions.value().front().toString())); + if (args.loaders.has_value()) + get_parameters.append(QString("modLoaderType=%1").arg(getMappedModLoader(args.loaders.value()))); + + return url + get_parameters.join('&'); + }; }; diff --git a/launcher/modplatform/flame/FlameCheckUpdate.cpp b/launcher/modplatform/flame/FlameCheckUpdate.cpp index 8dd3a846..285fa49f 100644 --- a/launcher/modplatform/flame/FlameCheckUpdate.cpp +++ b/launcher/modplatform/flame/FlameCheckUpdate.cpp @@ -7,7 +7,10 @@ #include "FileSystem.h" #include "Json.h" -#include "ModDownloadTask.h" +#include "ResourceDownloadTask.h" + +#include "minecraft/mod/ModFolderModel.h" +#include "minecraft/mod/ResourceFolderModel.h" static FlameAPI api; @@ -160,7 +163,7 @@ void FlameCheckUpdate::executeTask() for (auto& author : mod->authors()) pack.authors.append({ author }); pack.description = mod->description(); - pack.provider = ModPlatform::Provider::FLAME; + pack.provider = ModPlatform::ResourceProvider::FLAME; auto old_version = mod->version(); if (old_version.isEmpty() && mod->status() != ModStatus::NotInstalled) { @@ -168,10 +171,10 @@ void FlameCheckUpdate::executeTask() old_version = current_ver.version; } - auto download_task = new ModDownloadTask(pack, latest_ver, m_mods_folder); + auto download_task = new ResourceDownloadTask(pack, latest_ver, m_mods_folder); m_updatable.emplace_back(pack.name, mod->metadata()->hash, old_version, latest_ver.version, api.getModFileChangelog(latest_ver.addonId.toInt(), latest_ver.fileId.toInt()), - ModPlatform::Provider::FLAME, download_task); + ModPlatform::ResourceProvider::FLAME, download_task); } } diff --git a/launcher/modplatform/flame/FlameCheckUpdate.h b/launcher/modplatform/flame/FlameCheckUpdate.h index 163c706c..4a98d684 100644 --- a/launcher/modplatform/flame/FlameCheckUpdate.h +++ b/launcher/modplatform/flame/FlameCheckUpdate.h @@ -8,7 +8,7 @@ class FlameCheckUpdate : public CheckUpdateTask { Q_OBJECT public: - FlameCheckUpdate(QList& mods, std::list& mcVersions, ModAPI::ModLoaderTypes loaders, std::shared_ptr mods_folder) + FlameCheckUpdate(QList& mods, std::list& mcVersions, std::optional loaders, std::shared_ptr mods_folder) : CheckUpdateTask(mods, mcVersions, loaders, mods_folder) {} diff --git a/launcher/modplatform/flame/FlameInstanceCreationTask.cpp b/launcher/modplatform/flame/FlameInstanceCreationTask.cpp index dc69769a..fb6f78e8 100644 --- a/launcher/modplatform/flame/FlameInstanceCreationTask.cpp +++ b/launcher/modplatform/flame/FlameInstanceCreationTask.cpp @@ -183,7 +183,7 @@ bool FlameCreationTask::updateInstance() QEventLoop loop; - connect(job, &NetJob::succeeded, this, [this, raw_response, fileIds, old_inst_dir, &old_files, old_minecraft_dir] { + connect(job.get(), &NetJob::succeeded, this, [this, raw_response, fileIds, old_inst_dir, &old_files, old_minecraft_dir] { // Parse the API response QJsonParseError parse_error{}; auto doc = QJsonDocument::fromJson(*raw_response, &parse_error); @@ -225,7 +225,7 @@ bool FlameCreationTask::updateInstance() m_files_to_remove.append(old_minecraft_dir.absoluteFilePath(relative_path)); } }); - connect(job, &NetJob::finished, &loop, &QEventLoop::quit); + connect(job.get(), &NetJob::finished, &loop, &QEventLoop::quit); m_process_update_file_info_job = job; job->start(); diff --git a/launcher/modplatform/flame/FlameInstanceCreationTask.h b/launcher/modplatform/flame/FlameInstanceCreationTask.h index 498e1d6e..36b62e3e 100644 --- a/launcher/modplatform/flame/FlameInstanceCreationTask.h +++ b/launcher/modplatform/flame/FlameInstanceCreationTask.h @@ -86,7 +86,7 @@ class FlameCreationTask final : public InstanceCreationTask { Flame::Manifest m_pack; // Handle to allow aborting - NetJob* m_process_update_file_info_job = nullptr; + NetJob::Ptr m_process_update_file_info_job = nullptr; NetJob::Ptr m_files_job = nullptr; QString m_managed_id, m_managed_version_id; diff --git a/launcher/modplatform/flame/FlameModIndex.cpp b/launcher/modplatform/flame/FlameModIndex.cpp index 32aa4bdb..617b98ce 100644 --- a/launcher/modplatform/flame/FlameModIndex.cpp +++ b/launcher/modplatform/flame/FlameModIndex.cpp @@ -11,7 +11,7 @@ static ModPlatform::ProviderCapabilities ProviderCaps; void FlameMod::loadIndexedPack(ModPlatform::IndexedPack& pack, QJsonObject& obj) { pack.addonId = Json::requireInteger(obj, "id"); - pack.provider = ModPlatform::Provider::FLAME; + pack.provider = ModPlatform::ResourceProvider::FLAME; pack.name = Json::requireString(obj, "name"); pack.slug = Json::requireString(obj, "slug"); pack.websiteUrl = Json::ensureString(Json::ensureObject(obj, "links"), "websiteUrl", ""); @@ -127,7 +127,7 @@ auto FlameMod::loadIndexedPackVersion(QJsonObject& obj, bool load_changelog) -> auto hash_list = Json::ensureArray(obj, "hashes"); for (auto h : hash_list) { auto hash_entry = Json::ensureObject(h); - auto hash_types = ProviderCaps.hashType(ModPlatform::Provider::FLAME); + auto hash_types = ProviderCaps.hashType(ModPlatform::ResourceProvider::FLAME); auto hash_algo = enumToString(Json::ensureInteger(hash_entry, "algo", 1, "algorithm")); if (hash_types.contains(hash_algo)) { file.hash = Json::requireString(hash_entry, "value"); diff --git a/launcher/modplatform/helpers/HashUtils.cpp b/launcher/modplatform/helpers/HashUtils.cpp index f1e4759e..af484be0 100644 --- a/launcher/modplatform/helpers/HashUtils.cpp +++ b/launcher/modplatform/helpers/HashUtils.cpp @@ -12,12 +12,12 @@ namespace Hashing { static ModPlatform::ProviderCapabilities ProviderCaps; -Hasher::Ptr createHasher(QString file_path, ModPlatform::Provider provider) +Hasher::Ptr createHasher(QString file_path, ModPlatform::ResourceProvider provider) { switch (provider) { - case ModPlatform::Provider::MODRINTH: + case ModPlatform::ResourceProvider::MODRINTH: return createModrinthHasher(file_path); - case ModPlatform::Provider::FLAME: + case ModPlatform::ResourceProvider::FLAME: return createFlameHasher(file_path); default: qCritical() << "[Hashing]" @@ -36,12 +36,12 @@ Hasher::Ptr createFlameHasher(QString file_path) return new FlameHasher(file_path); } -Hasher::Ptr createBlockedModHasher(QString file_path, ModPlatform::Provider provider) +Hasher::Ptr createBlockedModHasher(QString file_path, ModPlatform::ResourceProvider provider) { return new BlockedModHasher(file_path, provider); } -Hasher::Ptr createBlockedModHasher(QString file_path, ModPlatform::Provider provider, QString type) +Hasher::Ptr createBlockedModHasher(QString file_path, ModPlatform::ResourceProvider provider, QString type) { auto hasher = new BlockedModHasher(file_path, provider); hasher->useHashType(type); @@ -62,8 +62,8 @@ void ModrinthHasher::executeTask() return; } - auto hash_type = ProviderCaps.hashType(ModPlatform::Provider::MODRINTH).first(); - m_hash = ProviderCaps.hash(ModPlatform::Provider::MODRINTH, &file, hash_type); + auto hash_type = ProviderCaps.hashType(ModPlatform::ResourceProvider::MODRINTH).first(); + m_hash = ProviderCaps.hash(ModPlatform::ResourceProvider::MODRINTH, &file, hash_type); file.close(); @@ -92,7 +92,7 @@ void FlameHasher::executeTask() } -BlockedModHasher::BlockedModHasher(QString file_path, ModPlatform::Provider provider) +BlockedModHasher::BlockedModHasher(QString file_path, ModPlatform::ResourceProvider provider) : Hasher(file_path), provider(provider) { setObjectName(QString("BlockedModHasher: %1").arg(file_path)); hash_type = ProviderCaps.hashType(provider).first(); diff --git a/launcher/modplatform/helpers/HashUtils.h b/launcher/modplatform/helpers/HashUtils.h index fa3244f6..91146a52 100644 --- a/launcher/modplatform/helpers/HashUtils.h +++ b/launcher/modplatform/helpers/HashUtils.h @@ -42,21 +42,21 @@ class ModrinthHasher : public Hasher { class BlockedModHasher : public Hasher { public: - BlockedModHasher(QString file_path, ModPlatform::Provider provider); + BlockedModHasher(QString file_path, ModPlatform::ResourceProvider provider); void executeTask() override; QStringList getHashTypes(); bool useHashType(QString type); private: - ModPlatform::Provider provider; + ModPlatform::ResourceProvider provider; QString hash_type; }; -Hasher::Ptr createHasher(QString file_path, ModPlatform::Provider provider); +Hasher::Ptr createHasher(QString file_path, ModPlatform::ResourceProvider provider); Hasher::Ptr createFlameHasher(QString file_path); Hasher::Ptr createModrinthHasher(QString file_path); -Hasher::Ptr createBlockedModHasher(QString file_path, ModPlatform::Provider provider); -Hasher::Ptr createBlockedModHasher(QString file_path, ModPlatform::Provider provider, QString type); +Hasher::Ptr createBlockedModHasher(QString file_path, ModPlatform::ResourceProvider provider); +Hasher::Ptr createBlockedModHasher(QString file_path, ModPlatform::ResourceProvider provider, QString type); } // namespace Hashing diff --git a/launcher/modplatform/helpers/NetworkModAPI.cpp b/launcher/modplatform/helpers/NetworkModAPI.cpp deleted file mode 100644 index 7633030e..00000000 --- a/launcher/modplatform/helpers/NetworkModAPI.cpp +++ /dev/null @@ -1,97 +0,0 @@ -#include "NetworkModAPI.h" - -#include "ui/pages/modplatform/ModModel.h" - -#include "Application.h" -#include "net/NetJob.h" - -void NetworkModAPI::searchMods(CallerType* caller, SearchArgs&& args) const -{ - auto netJob = new NetJob(QString("%1::Search").arg(caller->debugName()), APPLICATION->network()); - auto searchUrl = getModSearchURL(args); - - auto response = new QByteArray(); - netJob->addNetAction(Net::Download::makeByteArray(QUrl(searchUrl), response)); - - QObject::connect(netJob, &NetJob::started, caller, [caller, netJob] { caller->setActiveJob(netJob); }); - QObject::connect(netJob, &NetJob::failed, caller, &CallerType::searchRequestFailed); - QObject::connect(netJob, &NetJob::aborted, caller, &CallerType::searchRequestAborted); - QObject::connect(netJob, &NetJob::succeeded, caller, [caller, response] { - QJsonParseError parse_error{}; - QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error); - if (parse_error.error != QJsonParseError::NoError) { - qWarning() << "Error while parsing JSON response from " << caller->debugName() << " at " << parse_error.offset - << " reason: " << parse_error.errorString(); - qWarning() << *response; - return; - } - - caller->searchRequestFinished(doc); - }); - - netJob->start(); -} - -void NetworkModAPI::getModInfo(ModPlatform::IndexedPack& pack, std::function callback) -{ - auto response = new QByteArray(); - auto job = getProject(pack.addonId.toString(), response); - - QObject::connect(job, &NetJob::succeeded, [callback, &pack, response] { - QJsonParseError parse_error{}; - QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error); - if (parse_error.error != QJsonParseError::NoError) { - qWarning() << "Error while parsing JSON response for mod info at " << parse_error.offset - << " reason: " << parse_error.errorString(); - qWarning() << *response; - return; - } - - callback(doc, pack); - }); - - job->start(); -} - -void NetworkModAPI::getVersions(VersionSearchArgs&& args, std::function callback) const -{ - auto netJob = new NetJob(QString("ModVersions(%2)").arg(args.addonId), APPLICATION->network()); - auto response = new QByteArray(); - - netJob->addNetAction(Net::Download::makeByteArray(getVersionsURL(args), response)); - - QObject::connect(netJob, &NetJob::succeeded, [response, callback, args] { - QJsonParseError parse_error{}; - QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error); - if (parse_error.error != QJsonParseError::NoError) { - qWarning() << "Error while parsing JSON response for getting versions at " << parse_error.offset - << " reason: " << parse_error.errorString(); - qWarning() << *response; - return; - } - - callback(doc, args.addonId); - }); - - QObject::connect(netJob, &NetJob::finished, [response, netJob] { - netJob->deleteLater(); - delete response; - }); - - netJob->start(); -} - -auto NetworkModAPI::getProject(QString addonId, QByteArray* response) const -> NetJob* -{ - auto netJob = new NetJob(QString("%1::GetProject").arg(addonId), APPLICATION->network()); - auto searchUrl = getModInfoURL(addonId); - - netJob->addNetAction(Net::Download::makeByteArray(QUrl(searchUrl), response)); - - QObject::connect(netJob, &NetJob::finished, [response, netJob] { - netJob->deleteLater(); - delete response; - }); - - return netJob; -} diff --git a/launcher/modplatform/helpers/NetworkModAPI.h b/launcher/modplatform/helpers/NetworkModAPI.h deleted file mode 100644 index b8af22c7..00000000 --- a/launcher/modplatform/helpers/NetworkModAPI.h +++ /dev/null @@ -1,17 +0,0 @@ -#pragma once - -#include "modplatform/ModAPI.h" - -class NetworkModAPI : public ModAPI { - public: - void searchMods(CallerType* caller, SearchArgs&& args) const override; - void getModInfo(ModPlatform::IndexedPack& pack, std::function callback) override; - void getVersions(VersionSearchArgs&& args, std::function callback) const override; - - auto getProject(QString addonId, QByteArray* response) const -> NetJob* override; - - protected: - virtual auto getModSearchURL(SearchArgs& args) const -> QString = 0; - virtual auto getModInfoURL(QString& id) const -> QString = 0; - virtual auto getVersionsURL(VersionSearchArgs& args) const -> QString = 0; -}; diff --git a/launcher/modplatform/helpers/NetworkResourceAPI.cpp b/launcher/modplatform/helpers/NetworkResourceAPI.cpp new file mode 100644 index 00000000..eb17008c --- /dev/null +++ b/launcher/modplatform/helpers/NetworkResourceAPI.cpp @@ -0,0 +1,124 @@ +#include "NetworkResourceAPI.h" + +#include "Application.h" +#include "net/NetJob.h" + +#include "modplatform/ModIndex.h" + +NetJob::Ptr NetworkResourceAPI::searchProjects(SearchArgs&& args, SearchCallbacks&& callbacks) const +{ + auto search_url_optional = getSearchURL(args); + if (!search_url_optional.has_value()) { + callbacks.on_fail("Failed to create search URL", -1); + return nullptr; + } + + auto search_url = search_url_optional.value(); + + auto response = new QByteArray(); + auto netJob = new NetJob(QString("%1::Search").arg(debugName()), APPLICATION->network()); + + netJob->addNetAction(Net::Download::makeByteArray(QUrl(search_url), response)); + + QObject::connect(netJob, &NetJob::succeeded, [=]{ + QJsonParseError parse_error{}; + QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error); + if (parse_error.error != QJsonParseError::NoError) { + qWarning() << "Error while parsing JSON response from " << debugName() << " at " << parse_error.offset + << " reason: " << parse_error.errorString(); + qWarning() << *response; + + callbacks.on_fail(parse_error.errorString(), -1); + + return; + } + + callbacks.on_succeed(doc); + }); + + QObject::connect(netJob, &NetJob::failed, [=](QString reason){ + int network_error_code = -1; + if (auto* failed_action = netJob->getFailedActions().at(0); failed_action && failed_action->m_reply) + network_error_code = failed_action->m_reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + + callbacks.on_fail(reason, network_error_code); + }); + QObject::connect(netJob, &NetJob::aborted, [=]{ + callbacks.on_abort(); + }); + + return netJob; +} + +NetJob::Ptr NetworkResourceAPI::getProjectInfo(ProjectInfoArgs&& args, ProjectInfoCallbacks&& callbacks) const +{ + auto response = new QByteArray(); + auto job = getProject(args.pack.addonId.toString(), response); + + QObject::connect(job.get(), &NetJob::succeeded, [response, callbacks, args] { + QJsonParseError parse_error{}; + QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error); + if (parse_error.error != QJsonParseError::NoError) { + qWarning() << "Error while parsing JSON response for mod info at " << parse_error.offset + << " reason: " << parse_error.errorString(); + qWarning() << *response; + return; + } + + callbacks.on_succeed(doc, args.pack); + }); + + return job; +} + +NetJob::Ptr NetworkResourceAPI::getProjectVersions(VersionSearchArgs&& args, VersionSearchCallbacks&& callbacks) const +{ + auto versions_url_optional = getVersionsURL(args); + if (!versions_url_optional.has_value()) + return nullptr; + + auto versions_url = versions_url_optional.value(); + + auto netJob = new NetJob(QString("%1::Versions").arg(args.addonId), APPLICATION->network()); + auto response = new QByteArray(); + + netJob->addNetAction(Net::Download::makeByteArray(versions_url, response)); + + QObject::connect(netJob, &NetJob::succeeded, [=] { + QJsonParseError parse_error{}; + QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error); + if (parse_error.error != QJsonParseError::NoError) { + qWarning() << "Error while parsing JSON response for getting versions at " << parse_error.offset + << " reason: " << parse_error.errorString(); + qWarning() << *response; + return; + } + + callbacks.on_succeed(doc, args.addonId); + }); + + QObject::connect(netJob, &NetJob::finished, [response] { + delete response; + }); + + return netJob; +} + +NetJob::Ptr NetworkResourceAPI::getProject(QString addonId, QByteArray* response) const +{ + auto project_url_optional = getInfoURL(addonId); + if (!project_url_optional.has_value()) + return nullptr; + + auto project_url = project_url_optional.value(); + + auto netJob = new NetJob(QString("%1::GetProject").arg(addonId), APPLICATION->network()); + + netJob->addNetAction(Net::Download::makeByteArray(QUrl(project_url), response)); + + QObject::connect(netJob, &NetJob::finished, [response] { + delete response; + }); + + return netJob; +} diff --git a/launcher/modplatform/helpers/NetworkResourceAPI.h b/launcher/modplatform/helpers/NetworkResourceAPI.h new file mode 100644 index 00000000..834f274a --- /dev/null +++ b/launcher/modplatform/helpers/NetworkResourceAPI.h @@ -0,0 +1,18 @@ +#pragma once + +#include "modplatform/ResourceAPI.h" + +class NetworkResourceAPI : public ResourceAPI { + public: + NetJob::Ptr searchProjects(SearchArgs&&, SearchCallbacks&&) const override; + + NetJob::Ptr getProject(QString addonId, QByteArray* response) const override; + + NetJob::Ptr getProjectInfo(ProjectInfoArgs&&, ProjectInfoCallbacks&&) const override; + NetJob::Ptr getProjectVersions(VersionSearchArgs&&, VersionSearchCallbacks&&) const override; + + protected: + [[nodiscard]] virtual auto getSearchURL(SearchArgs const& args) const -> std::optional = 0; + [[nodiscard]] virtual auto getInfoURL(QString const& id) const -> std::optional = 0; + [[nodiscard]] virtual auto getVersionsURL(VersionSearchArgs const& args) const -> std::optional = 0; +}; diff --git a/launcher/modplatform/modrinth/ModrinthAPI.cpp b/launcher/modplatform/modrinth/ModrinthAPI.cpp index 747cf4c3..8e64be09 100644 --- a/launcher/modplatform/modrinth/ModrinthAPI.cpp +++ b/launcher/modplatform/modrinth/ModrinthAPI.cpp @@ -37,21 +37,24 @@ auto ModrinthAPI::currentVersions(const QStringList& hashes, QString hash_format auto ModrinthAPI::latestVersion(QString hash, QString hash_format, - std::list mcVersions, - ModLoaderTypes loaders, + std::optional> mcVersions, + std::optional loaders, QByteArray* response) -> NetJob::Ptr { auto* netJob = new NetJob(QString("Modrinth::GetLatestVersion"), APPLICATION->network()); QJsonObject body_obj; - Json::writeStringList(body_obj, "loaders", getModLoaderStrings(loaders)); + if (loaders.has_value()) + Json::writeStringList(body_obj, "loaders", getModLoaderStrings(loaders.value())); - QStringList game_versions; - for (auto& ver : mcVersions) { - game_versions.append(ver.toString()); + if (mcVersions.has_value()) { + QStringList game_versions; + for (auto& ver : mcVersions.value()) { + game_versions.append(ver.toString()); + } + Json::writeStringList(body_obj, "game_versions", game_versions); } - Json::writeStringList(body_obj, "game_versions", game_versions); QJsonDocument body(body_obj); auto body_raw = body.toJson(); @@ -66,8 +69,8 @@ auto ModrinthAPI::latestVersion(QString hash, auto ModrinthAPI::latestVersions(const QStringList& hashes, QString hash_format, - std::list mcVersions, - ModLoaderTypes loaders, + std::optional> mcVersions, + std::optional loaders, QByteArray* response) -> NetJob::Ptr { auto* netJob = new NetJob(QString("Modrinth::GetLatestVersions"), APPLICATION->network()); @@ -77,13 +80,16 @@ auto ModrinthAPI::latestVersions(const QStringList& hashes, Json::writeStringList(body_obj, "hashes", hashes); Json::writeString(body_obj, "algorithm", hash_format); - Json::writeStringList(body_obj, "loaders", getModLoaderStrings(loaders)); + if (loaders.has_value()) + Json::writeStringList(body_obj, "loaders", getModLoaderStrings(loaders.value())); - QStringList game_versions; - for (auto& ver : mcVersions) { - game_versions.append(ver.toString()); + if (mcVersions.has_value()) { + QStringList game_versions; + for (auto& ver : mcVersions.value()) { + game_versions.append(ver.toString()); + } + Json::writeStringList(body_obj, "game_versions", game_versions); } - Json::writeStringList(body_obj, "game_versions", game_versions); QJsonDocument body(body_obj); auto body_raw = body.toJson(); @@ -95,7 +101,7 @@ auto ModrinthAPI::latestVersions(const QStringList& hashes, return netJob; } -auto ModrinthAPI::getProjects(QStringList addonIds, QByteArray* response) const -> NetJob* +NetJob::Ptr ModrinthAPI::getProjects(QStringList addonIds, QByteArray* response) const { auto netJob = new NetJob(QString("Modrinth::GetProjects"), APPLICATION->network()); auto searchUrl = getMultipleModInfoURL(addonIds); diff --git a/launcher/modplatform/modrinth/ModrinthAPI.h b/launcher/modplatform/modrinth/ModrinthAPI.h index e1a18681..bd84fb54 100644 --- a/launcher/modplatform/modrinth/ModrinthAPI.h +++ b/launcher/modplatform/modrinth/ModrinthAPI.h @@ -19,13 +19,12 @@ #pragma once #include "BuildConfig.h" -#include "modplatform/ModAPI.h" #include "modplatform/ModIndex.h" -#include "modplatform/helpers/NetworkModAPI.h" +#include "modplatform/helpers/NetworkResourceAPI.h" #include -class ModrinthAPI : public NetworkModAPI { +class ModrinthAPI : public NetworkResourceAPI { public: auto currentVersion(QString hash, QString hash_format, @@ -37,17 +36,17 @@ class ModrinthAPI : public NetworkModAPI { auto latestVersion(QString hash, QString hash_format, - std::list mcVersions, - ModLoaderTypes loaders, + std::optional> mcVersions, + std::optional loaders, QByteArray* response) -> NetJob::Ptr; auto latestVersions(const QStringList& hashes, QString hash_format, - std::list mcVersions, - ModLoaderTypes loaders, + std::optional> mcVersions, + std::optional loaders, QByteArray* response) -> NetJob::Ptr; - auto getProjects(QStringList addonIds, QByteArray* response) const -> NetJob* override; + NetJob::Ptr getProjects(QStringList addonIds, QByteArray* response) const override; public: inline auto getAuthorURL(const QString& name) const -> QString { return "https://modrinth.com/user/" + name; }; @@ -55,15 +54,13 @@ class ModrinthAPI : public NetworkModAPI { static auto getModLoaderStrings(const ModLoaderTypes types) -> const QStringList { QStringList l; - for (auto loader : {Forge, Fabric, Quilt}) - { - if ((types & loader) || types == Unspecified) - { - l << ModAPI::getModLoaderString(loader); + for (auto loader : {Forge, Fabric, Quilt}) { + if (types & loader) { + l << getModLoaderString(loader); } } if ((types & Quilt) && (~types & Fabric)) // Add Fabric if Quilt is in use, if Fabric isn't already there - l << ModAPI::getModLoaderString(Fabric); + l << getModLoaderString(Fabric); return l; } @@ -78,28 +75,54 @@ class ModrinthAPI : public NetworkModAPI { } private: - inline auto getModSearchURL(SearchArgs& args) const -> QString override + [[nodiscard]] static QString resourceTypeParameter(ModPlatform::ResourceType type) { - if (!validateModLoaders(args.loaders)) { - qWarning() << "Modrinth only have Forge and Fabric-compatible mods!"; - return ""; + switch (type) { + case ModPlatform::ResourceType::MOD: + return "mod"; + default: + qWarning() << "Invalid resource type for Modrinth API!"; + break; } - return QString(BuildConfig.MODRINTH_PROD_URL + - "/search?" - "offset=%1&" - "limit=25&" - "query=%2&" - "index=%3&" - "facets=[[%4],%5[\"project_type:mod\"]]") - .arg(args.offset) - .arg(args.search) - .arg(args.sorting) - .arg(getModLoaderFilters(args.loaders)) - .arg(getGameVersionsArray(args.versions)); + return ""; + } + [[nodiscard]] QString createFacets(SearchArgs const& args) const + { + QStringList facets_list; + + if (args.loaders.has_value()) + facets_list.append(QString("[%1]").arg(getModLoaderFilters(args.loaders.value()))); + if (args.versions.has_value()) + facets_list.append(QString("[%1]").arg(getGameVersionsArray(args.versions.value()))); + facets_list.append(QString("[\"project_type:%1\"]").arg(resourceTypeParameter(args.type))); + + return QString("[%1]").arg(facets_list.join(',')); + } + + public: + [[nodiscard]] inline auto getSearchURL(SearchArgs const& args) const -> std::optional override + { + if (args.loaders.has_value()) { + if (!validateModLoaders(args.loaders.value())) { + qWarning() << "Modrinth only have Forge and Fabric-compatible mods!"; + return {}; + } + } + + QStringList get_arguments; + get_arguments.append(QString("offset=%1").arg(args.offset)); + get_arguments.append(QString("limit=25")); + if (args.search.has_value()) + get_arguments.append(QString("query=%1").arg(args.search.value())); + if (args.sorting.has_value()) + get_arguments.append(QString("index=%1").arg(args.sorting.value())); + get_arguments.append(QString("facets=%1").arg(createFacets(args))); + + return BuildConfig.MODRINTH_PROD_URL + "/search?" + get_arguments.join('&'); }; - inline auto getModInfoURL(QString& id) const -> QString override + inline auto getInfoURL(QString const& id) const -> std::optional override { return BuildConfig.MODRINTH_PROD_URL + "/project/" + id; }; @@ -109,15 +132,16 @@ class ModrinthAPI : public NetworkModAPI { return BuildConfig.MODRINTH_PROD_URL + QString("/projects?ids=[\"%1\"]").arg(ids.join("\",\"")); }; - inline auto getVersionsURL(VersionSearchArgs& args) const -> QString override + inline auto getVersionsURL(VersionSearchArgs const& args) const -> std::optional override { - return QString(BuildConfig.MODRINTH_PROD_URL + - "/project/%1/version?" - "game_versions=[%2]&" - "loaders=[\"%3\"]") - .arg(args.addonId, - getGameVersionsString(args.mcVersions), - getModLoaderStrings(args.loaders).join("\",\"")); + QStringList get_arguments; + if (args.mcVersions.has_value()) + get_arguments.append(QString("game_versions=[%1]").arg(getGameVersionsString(args.mcVersions.value()))); + if (args.loaders.has_value()) + get_arguments.append(QString("loaders=[\"%1\"]").arg(getModLoaderStrings(args.loaders.value()).join("\",\""))); + + return QString("%1/project/%2/version%3%4") + .arg(BuildConfig.MODRINTH_PROD_URL, args.addonId, get_arguments.isEmpty() ? "" : "?", get_arguments.join('&')); }; auto getGameVersionsArray(std::list mcVersions) const -> QString @@ -127,12 +151,12 @@ class ModrinthAPI : public NetworkModAPI { s += QString("\"versions:%1\",").arg(ver.toString()); } s.remove(s.length() - 1, 1); //remove last comma - return s.isEmpty() ? QString() : QString("[%1],").arg(s); + return s.isEmpty() ? QString() : s; } inline auto validateModLoaders(ModLoaderTypes loaders) const -> bool { - return (loaders == Unspecified) || (loaders & (Forge | Fabric | Quilt)); + return loaders & (Forge | Fabric | Quilt); } }; diff --git a/launcher/modplatform/modrinth/ModrinthCheckUpdate.cpp b/launcher/modplatform/modrinth/ModrinthCheckUpdate.cpp index e2d27547..7826b33d 100644 --- a/launcher/modplatform/modrinth/ModrinthCheckUpdate.cpp +++ b/launcher/modplatform/modrinth/ModrinthCheckUpdate.cpp @@ -4,12 +4,15 @@ #include "Json.h" -#include "ModDownloadTask.h" +#include "ResourceDownloadTask.h" #include "modplatform/helpers/HashUtils.h" #include "tasks/ConcurrentTask.h" +#include "minecraft/mod/ModFolderModel.h" +#include "minecraft/mod/ResourceFolderModel.h" + static ModrinthAPI api; static ModPlatform::ProviderCapabilities ProviderCaps; @@ -34,7 +37,7 @@ void ModrinthCheckUpdate::executeTask() // Create all hashes QStringList hashes; - auto best_hash_type = ProviderCaps.hashType(ModPlatform::Provider::MODRINTH).first(); + auto best_hash_type = ProviderCaps.hashType(ModPlatform::ResourceProvider::MODRINTH).first(); ConcurrentTask hashing_task(this, "MakeModrinthHashesTask", 10); for (auto* mod : m_mods) { @@ -108,11 +111,13 @@ void ModrinthCheckUpdate::executeTask() // Sometimes a version may have multiple files, one with "forge" and one with "fabric", // so we may want to filter it QString loader_filter; - static auto flags = { ModAPI::ModLoaderType::Forge, ModAPI::ModLoaderType::Fabric, ModAPI::ModLoaderType::Quilt }; - for (auto flag : flags) { - if (m_loaders.testFlag(flag)) { - loader_filter = api.getModLoaderString(flag); - break; + if (m_loaders.has_value()) { + static auto flags = { ResourceAPI::ModLoaderType::Forge, ResourceAPI::ModLoaderType::Fabric, ResourceAPI::ModLoaderType::Quilt }; + for (auto flag : flags) { + if (m_loaders.value().testFlag(flag)) { + loader_filter = api.getModLoaderString(flag); + break; + } } } @@ -152,12 +157,12 @@ void ModrinthCheckUpdate::executeTask() for (auto& author : mod->authors()) pack.authors.append({ author }); pack.description = mod->description(); - pack.provider = ModPlatform::Provider::MODRINTH; + pack.provider = ModPlatform::ResourceProvider::MODRINTH; - auto download_task = new ModDownloadTask(pack, project_ver, m_mods_folder); + auto download_task = new ResourceDownloadTask(pack, project_ver, m_mods_folder); m_updatable.emplace_back(pack.name, hash, mod->version(), project_ver.version_number, project_ver.changelog, - ModPlatform::Provider::MODRINTH, download_task); + ModPlatform::ResourceProvider::MODRINTH, download_task); } } } catch (Json::JsonException& e) { diff --git a/launcher/modplatform/modrinth/ModrinthCheckUpdate.h b/launcher/modplatform/modrinth/ModrinthCheckUpdate.h index abf8ada1..177ce516 100644 --- a/launcher/modplatform/modrinth/ModrinthCheckUpdate.h +++ b/launcher/modplatform/modrinth/ModrinthCheckUpdate.h @@ -8,7 +8,7 @@ class ModrinthCheckUpdate : public CheckUpdateTask { Q_OBJECT public: - ModrinthCheckUpdate(QList& mods, std::list& mcVersions, ModAPI::ModLoaderTypes loaders, std::shared_ptr mods_folder) + ModrinthCheckUpdate(QList& mods, std::list& mcVersions, std::optional loaders, std::shared_ptr mods_folder) : CheckUpdateTask(mods, mcVersions, loaders, mods_folder) {} diff --git a/launcher/modplatform/modrinth/ModrinthPackIndex.cpp b/launcher/modplatform/modrinth/ModrinthPackIndex.cpp index aec45a73..a0161089 100644 --- a/launcher/modplatform/modrinth/ModrinthPackIndex.cpp +++ b/launcher/modplatform/modrinth/ModrinthPackIndex.cpp @@ -33,7 +33,7 @@ void Modrinth::loadIndexedPack(ModPlatform::IndexedPack& pack, QJsonObject& obj) if (pack.addonId.toString().isEmpty()) pack.addonId = Json::requireString(obj, "id"); - pack.provider = ModPlatform::Provider::MODRINTH; + pack.provider = ModPlatform::ResourceProvider::MODRINTH; pack.name = Json::requireString(obj, "title"); pack.slug = Json::ensureString(obj, "slug", ""); @@ -179,7 +179,7 @@ auto Modrinth::loadIndexedPackVersion(QJsonObject& obj, QString preferred_hash_t file.hash = Json::requireString(hash_list, preferred_hash_type); file.hash_type = preferred_hash_type; } else { - auto hash_types = ProviderCaps.hashType(ModPlatform::Provider::MODRINTH); + auto hash_types = ProviderCaps.hashType(ModPlatform::ResourceProvider::MODRINTH); for (auto& hash_type : hash_types) { if (hash_list.contains(hash_type)) { file.hash = Json::requireString(hash_list, hash_type); diff --git a/launcher/modplatform/packwiz/Packwiz.cpp b/launcher/modplatform/packwiz/Packwiz.cpp index 0ed29311..510c7309 100644 --- a/launcher/modplatform/packwiz/Packwiz.cpp +++ b/launcher/modplatform/packwiz/Packwiz.cpp @@ -97,7 +97,7 @@ auto V1::createModFormat(QDir& index_dir, ModPlatform::IndexedPack& mod_pack, Mo mod.name = mod_pack.name; mod.filename = mod_version.fileName; - if (mod_pack.provider == ModPlatform::Provider::FLAME) { + if (mod_pack.provider == ModPlatform::ResourceProvider::FLAME) { mod.mode = "metadata:curseforge"; } else { mod.mode = "url"; @@ -176,11 +176,11 @@ void V1::updateModIndex(QDir& index_dir, Mod& mod) in_stream << QString("\n[update]\n"); in_stream << QString("[update.%1]\n").arg(ProviderCaps.name(mod.provider)); switch (mod.provider) { - case (ModPlatform::Provider::FLAME): + case (ModPlatform::ResourceProvider::FLAME): in_stream << QString("file-id = %1\n").arg(mod.file_id.toString()); in_stream << QString("project-id = %1\n").arg(mod.project_id.toString()); break; - case (ModPlatform::Provider::MODRINTH): + case (ModPlatform::ResourceProvider::MODRINTH): addToStream("mod-id", mod.mod_id().toString()); addToStream("version", mod.version().toString()); break; @@ -273,7 +273,7 @@ auto V1::getIndexForMod(QDir& index_dir, QString slug) -> Mod } { // [update] info - using Provider = ModPlatform::Provider; + using Provider = ModPlatform::ResourceProvider; auto update_table = table["update"]; if (!update_table || !update_table.is_table()) { diff --git a/launcher/modplatform/packwiz/Packwiz.h b/launcher/modplatform/packwiz/Packwiz.h index 9754e5c4..4b096eec 100644 --- a/launcher/modplatform/packwiz/Packwiz.h +++ b/launcher/modplatform/packwiz/Packwiz.h @@ -49,7 +49,7 @@ class V1 { QString hash {}; // [update] - ModPlatform::Provider provider {}; + ModPlatform::ResourceProvider provider {}; QVariant file_id {}; QVariant project_id {}; diff --git a/launcher/net/NetAction.h b/launcher/net/NetAction.h index d9c4fadc..38fe058b 100644 --- a/launcher/net/NetAction.h +++ b/launcher/net/NetAction.h @@ -52,7 +52,6 @@ class NetAction : public Task { virtual ~NetAction() = default; QUrl url() { return m_url; } - auto index() -> int { return m_index_within_job; } void setNetwork(shared_qobject_ptr network) { m_network = network; } @@ -75,9 +74,6 @@ class NetAction : public Task { public: shared_qobject_ptr m_network; - /// index within the parent job, FIXME: nuke - int m_index_within_job = 0; - /// the network reply unique_qobject_ptr m_reply; diff --git a/launcher/net/NetJob.cpp b/launcher/net/NetJob.cpp index 9b5d4f1b..4bcd40b5 100644 --- a/launcher/net/NetJob.cpp +++ b/launcher/net/NetJob.cpp @@ -38,11 +38,10 @@ auto NetJob::addNetAction(NetAction::Ptr action) -> bool { - action->m_index_within_job = m_queue.size(); - m_queue.append(action); - action->setNetwork(m_network); + addTask(action); + return true; } diff --git a/launcher/ui/dialogs/BlockedModsDialog.cpp b/launcher/ui/dialogs/BlockedModsDialog.cpp index 8b49bd1a..5977fd10 100644 --- a/launcher/ui/dialogs/BlockedModsDialog.cpp +++ b/launcher/ui/dialogs/BlockedModsDialog.cpp @@ -230,7 +230,7 @@ void BlockedModsDialog::addHashTask(QString path) /// @param path the path to the local file being hashed void BlockedModsDialog::buildHashTask(QString path) { - auto hash_task = Hashing::createBlockedModHasher(path, ModPlatform::Provider::FLAME, "sha1"); + auto hash_task = Hashing::createBlockedModHasher(path, ModPlatform::ResourceProvider::FLAME, "sha1"); qDebug() << "[Blocked Mods Dialog] Creating Hash task for path: " << path; diff --git a/launcher/ui/dialogs/ChooseProviderDialog.cpp b/launcher/ui/dialogs/ChooseProviderDialog.cpp index 89935d9a..83748e1e 100644 --- a/launcher/ui/dialogs/ChooseProviderDialog.cpp +++ b/launcher/ui/dialogs/ChooseProviderDialog.cpp @@ -67,9 +67,9 @@ void ChooseProviderDialog::confirmAll() accept(); } -auto ChooseProviderDialog::getSelectedProvider() const -> ModPlatform::Provider +auto ChooseProviderDialog::getSelectedProvider() const -> ModPlatform::ResourceProvider { - return ModPlatform::Provider(m_providers.checkedId()); + return ModPlatform::ResourceProvider(m_providers.checkedId()); } void ChooseProviderDialog::addProviders() @@ -77,7 +77,7 @@ void ChooseProviderDialog::addProviders() int btn_index = 0; QRadioButton* btn; - for (auto& provider : { ModPlatform::Provider::MODRINTH, ModPlatform::Provider::FLAME }) { + for (auto& provider : { ModPlatform::ResourceProvider::MODRINTH, ModPlatform::ResourceProvider::FLAME }) { btn = new QRadioButton(ProviderCaps.readableName(provider), this); m_providers.addButton(btn, btn_index++); ui->providersLayout->addWidget(btn); diff --git a/launcher/ui/dialogs/ChooseProviderDialog.h b/launcher/ui/dialogs/ChooseProviderDialog.h index 4a3b9f29..be9735b5 100644 --- a/launcher/ui/dialogs/ChooseProviderDialog.h +++ b/launcher/ui/dialogs/ChooseProviderDialog.h @@ -8,7 +8,7 @@ class ChooseProviderDialog; } namespace ModPlatform { -enum class Provider; +enum class ResourceProvider; } class Mod; @@ -24,7 +24,7 @@ class ChooseProviderDialog : public QDialog { bool try_others = false; - ModPlatform::Provider chosen; + ModPlatform::ResourceProvider chosen; }; public: @@ -45,7 +45,7 @@ class ChooseProviderDialog : public QDialog { void addProviders(); void disableInput(); - auto getSelectedProvider() const -> ModPlatform::Provider; + auto getSelectedProvider() const -> ModPlatform::ResourceProvider; private: Ui::ChooseProviderDialog* ui; diff --git a/launcher/ui/dialogs/ModDownloadDialog.cpp b/launcher/ui/dialogs/ModDownloadDialog.cpp index 24d23ba9..8a77ef7f 100644 --- a/launcher/ui/dialogs/ModDownloadDialog.cpp +++ b/launcher/ui/dialogs/ModDownloadDialog.cpp @@ -19,76 +19,24 @@ #include "ModDownloadDialog.h" -#include -#include -#include - #include "Application.h" -#include "ReviewMessageBox.h" - -#include -#include -#include -#include -#include "ModDownloadTask.h" -#include "ui/pages/modplatform/flame/FlameModPage.h" -#include "ui/pages/modplatform/modrinth/ModrinthModPage.h" -#include "ui/widgets/PageContainer.h" +#include "ui/pages/modplatform/flame/FlameResourcePages.h" +#include "ui/pages/modplatform/modrinth/ModrinthResourcePages.h" -ModDownloadDialog::ModDownloadDialog(const std::shared_ptr& mods, QWidget* parent, BaseInstance* instance) - : QDialog(parent), mods(mods), m_verticalLayout(new QVBoxLayout(this)), m_instance(instance) +ModDownloadDialog::ModDownloadDialog(QWidget* parent, const std::shared_ptr& mods, BaseInstance* instance) + : ResourceDownloadDialog(parent, mods), m_instance(instance) { - setObjectName(QStringLiteral("ModDownloadDialog")); - m_verticalLayout->setObjectName(QStringLiteral("verticalLayout")); - - resize(std::max(0.5 * parent->width(), 400.0), std::max(0.75 * parent->height(), 400.0)); - - 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); - - connect(m_container, &PageContainer::selectedPageChanged, this, &ModDownloadDialog::selectedPageChanged); - - // 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->setEnabled(false); - OkButton->setDefault(true); - OkButton->setAutoDefault(true); - OkButton->setText(tr("Review and confirm")); - OkButton->setShortcut(tr("Ctrl+Return")); - OkButton->setToolTip(tr("Opens a new popup to review your selected mods and confirm your selection. Shortcut: Ctrl+Return")); - connect(OkButton, &QPushButton::clicked, this, &ModDownloadDialog::confirm); - - 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(dialogTitle()); + initializeContainer(); + connectButtons(); restoreGeometry(QByteArray::fromBase64(APPLICATION->settings()->get("ModDownloadGeometry").toByteArray())); } -QString ModDownloadDialog::dialogTitle() +void ModDownloadDialog::accept() { - return tr("Download mods"); + APPLICATION->settings()->set("ModDownloadGeometry", saveGeometry().toBase64()); + QDialog::accept(); } void ModDownloadDialog::reject() @@ -97,106 +45,15 @@ void ModDownloadDialog::reject() QDialog::reject(); } -void ModDownloadDialog::confirm() -{ - auto keys = modTask.keys(); - keys.sort(Qt::CaseInsensitive); - - auto confirm_dialog = ReviewMessageBox::create(this, tr("Confirm mods to download")); - - for (auto& task : keys) { - confirm_dialog->appendMod({ task, modTask.find(task).value()->getFilename() }); - } - - if (confirm_dialog->exec()) { - auto deselected = confirm_dialog->deselectedMods(); - for (auto name : deselected) { - modTask.remove(name); - } - - this->accept(); - } -} - -void ModDownloadDialog::accept() -{ - APPLICATION->settings()->set("ModDownloadGeometry", saveGeometry().toBase64()); - QDialog::accept(); -} - QList ModDownloadDialog::getPages() { QList pages; - pages.append(ModrinthModPage::create(this, m_instance)); + pages.append(ModrinthModPage::create(this, *m_instance)); if (APPLICATION->capabilities() & Application::SupportsFlame) - pages.append(FlameModPage::create(this, m_instance)); + pages.append(FlameModPage::create(this, *m_instance)); m_selectedPage = dynamic_cast(pages[0]); return pages; } - -void ModDownloadDialog::addSelectedMod(QString name, ModDownloadTask* task) -{ - removeSelectedMod(name); - modTask.insert(name, task); - - m_buttons->button(QDialogButtonBox::Ok)->setEnabled(!modTask.isEmpty()); -} - -void ModDownloadDialog::removeSelectedMod(QString name) -{ - if (modTask.contains(name)) - delete modTask.find(name).value(); - modTask.remove(name); - - m_buttons->button(QDialogButtonBox::Ok)->setEnabled(!modTask.isEmpty()); -} - -bool ModDownloadDialog::isModSelected(QString name, QString filename) const -{ - // FIXME: Is there a way to check for versions without checking the filename - // as a heuristic, other than adding such info to ModDownloadTask itself? - auto iter = modTask.find(name); - return iter != modTask.end() && (iter.value()->getFilename() == filename); -} - -bool ModDownloadDialog::isModSelected(QString name) const -{ - auto iter = modTask.find(name); - return iter != modTask.end(); -} - -const QList ModDownloadDialog::getTasks() -{ - return modTask.values(); -} - -void ModDownloadDialog::selectedPageChanged(BasePage* previous, BasePage* selected) -{ - auto* prev_page = dynamic_cast(previous); - if (!prev_page) { - qCritical() << "Page '" << previous->displayName() << "' in ModDownloadDialog is not a ModPage!"; - return; - } - - m_selectedPage = dynamic_cast(selected); - if (!m_selectedPage) { - qCritical() << "Page '" << selected->displayName() << "' in ModDownloadDialog is not a ModPage!"; - return; - } - - // Same effect as having a global search bar - m_selectedPage->setSearchTerm(prev_page->getSearchTerm()); -} - -bool ModDownloadDialog::selectPage(QString pageId) -{ - return m_container->selectPage(pageId); -} - -ModPage* ModDownloadDialog::getSelectedPage() -{ - return m_selectedPage; -} diff --git a/launcher/ui/dialogs/ModDownloadDialog.h b/launcher/ui/dialogs/ModDownloadDialog.h index fcf6f4fc..19036042 100644 --- a/launcher/ui/dialogs/ModDownloadDialog.h +++ b/launcher/ui/dialogs/ModDownloadDialog.h @@ -19,60 +19,29 @@ #pragma once -#include -#include - -#include "ModDownloadTask.h" #include "minecraft/mod/ModFolderModel.h" -#include "ui/pages/BasePageProvider.h" -namespace Ui -{ -class ModDownloadDialog; -} +#include "ui/dialogs/ResourceDownloadDialog.h" -class PageContainer; class QDialogButtonBox; -class ModPage; -class ModrinthModPage; -class ModDownloadDialog final : public QDialog, public BasePageProvider +class ModDownloadDialog final : public ResourceDownloadDialog { Q_OBJECT public: - explicit ModDownloadDialog(const std::shared_ptr& mods, QWidget* parent, BaseInstance* instance); + explicit ModDownloadDialog(QWidget* parent, const std::shared_ptr& mods, BaseInstance* instance); ~ModDownloadDialog() override = default; - QString dialogTitle() override; - QList getPages() override; - - void addSelectedMod(QString name = QString(), ModDownloadTask* task = nullptr); - void removeSelectedMod(QString name = QString()); - bool isModSelected(QString name, QString filename) const; - bool isModSelected(QString name) const; + //: String that gets appended to the mod download dialog title ("Download " + resourcesString()) + [[nodiscard]] QString resourceString() const override { return tr("mods"); } - const QList getTasks(); - const std::shared_ptr& mods; - - bool selectPage(QString pageId); - ModPage* getSelectedPage(); + QList getPages() override; public slots: - void confirm(); void accept() override; void reject() override; - private slots: - void selectedPageChanged(BasePage* previous, BasePage* selected); - private: - Ui::ModDownloadDialog* ui = nullptr; - PageContainer* m_container = nullptr; - QDialogButtonBox* m_buttons = nullptr; - QVBoxLayout* m_verticalLayout = nullptr; - ModPage* m_selectedPage = nullptr; - - QHash modTask; BaseInstance* m_instance; }; diff --git a/launcher/ui/dialogs/ModUpdateDialog.cpp b/launcher/ui/dialogs/ModUpdateDialog.cpp index 2704243e..4ef42d6c 100644 --- a/launcher/ui/dialogs/ModUpdateDialog.cpp +++ b/launcher/ui/dialogs/ModUpdateDialog.cpp @@ -21,6 +21,8 @@ #include #include +#include + static ModPlatform::ProviderCapabilities ProviderCaps; static std::list mcVersions(BaseInstance* inst) @@ -28,7 +30,7 @@ static std::list mcVersions(BaseInstance* inst) return { static_cast(inst)->getPackProfile()->getComponent("net.minecraft")->getVersion() }; } -static ModAPI::ModLoaderTypes mcLoaders(BaseInstance* inst) +static std::optional mcLoaders(BaseInstance* inst) { return { static_cast(inst)->getPackProfile()->getModLoaders() }; } @@ -212,14 +214,14 @@ auto ModUpdateDialog::ensureMetadata() -> bool bool confirm_rest = false; bool try_others_rest = false; bool skip_rest = false; - ModPlatform::Provider provider_rest = ModPlatform::Provider::MODRINTH; + ModPlatform::ResourceProvider provider_rest = ModPlatform::ResourceProvider::MODRINTH; - auto addToTmp = [&](Mod* m, ModPlatform::Provider p) { + auto addToTmp = [&](Mod* m, ModPlatform::ResourceProvider p) { switch (p) { - case ModPlatform::Provider::MODRINTH: + case ModPlatform::ResourceProvider::MODRINTH: modrinth_tmp.push_back(m); break; - case ModPlatform::Provider::FLAME: + case ModPlatform::ResourceProvider::FLAME: flame_tmp.push_back(m); break; } @@ -264,10 +266,10 @@ auto ModUpdateDialog::ensureMetadata() -> bool } if (!modrinth_tmp.empty()) { - auto* modrinth_task = new EnsureMetadataTask(modrinth_tmp, index_dir, ModPlatform::Provider::MODRINTH); + auto* modrinth_task = new EnsureMetadataTask(modrinth_tmp, index_dir, ModPlatform::ResourceProvider::MODRINTH); connect(modrinth_task, &EnsureMetadataTask::metadataReady, [this](Mod* candidate) { onMetadataEnsured(candidate); }); connect(modrinth_task, &EnsureMetadataTask::metadataFailed, [this, &should_try_others](Mod* candidate) { - onMetadataFailed(candidate, should_try_others.find(candidate->internal_id()).value(), ModPlatform::Provider::MODRINTH); + onMetadataFailed(candidate, should_try_others.find(candidate->internal_id()).value(), ModPlatform::ResourceProvider::MODRINTH); }); if (modrinth_task->getHashingTask()) @@ -277,10 +279,10 @@ auto ModUpdateDialog::ensureMetadata() -> bool } if (!flame_tmp.empty()) { - auto* flame_task = new EnsureMetadataTask(flame_tmp, index_dir, ModPlatform::Provider::FLAME); + auto* flame_task = new EnsureMetadataTask(flame_tmp, index_dir, ModPlatform::ResourceProvider::FLAME); connect(flame_task, &EnsureMetadataTask::metadataReady, [this](Mod* candidate) { onMetadataEnsured(candidate); }); connect(flame_task, &EnsureMetadataTask::metadataFailed, [this, &should_try_others](Mod* candidate) { - onMetadataFailed(candidate, should_try_others.find(candidate->internal_id()).value(), ModPlatform::Provider::FLAME); + onMetadataFailed(candidate, should_try_others.find(candidate->internal_id()).value(), ModPlatform::ResourceProvider::FLAME); }); if (flame_task->getHashingTask()) @@ -306,28 +308,28 @@ void ModUpdateDialog::onMetadataEnsured(Mod* mod) return; switch (mod->metadata()->provider) { - case ModPlatform::Provider::MODRINTH: + case ModPlatform::ResourceProvider::MODRINTH: m_modrinth_to_update.push_back(mod); break; - case ModPlatform::Provider::FLAME: + case ModPlatform::ResourceProvider::FLAME: m_flame_to_update.push_back(mod); break; } } -ModPlatform::Provider next(ModPlatform::Provider p) +ModPlatform::ResourceProvider next(ModPlatform::ResourceProvider p) { switch (p) { - case ModPlatform::Provider::MODRINTH: - return ModPlatform::Provider::FLAME; - case ModPlatform::Provider::FLAME: - return ModPlatform::Provider::MODRINTH; + case ModPlatform::ResourceProvider::MODRINTH: + return ModPlatform::ResourceProvider::FLAME; + case ModPlatform::ResourceProvider::FLAME: + return ModPlatform::ResourceProvider::MODRINTH; } - return ModPlatform::Provider::FLAME; + return ModPlatform::ResourceProvider::FLAME; } -void ModUpdateDialog::onMetadataFailed(Mod* mod, bool try_others, ModPlatform::Provider first_choice) +void ModUpdateDialog::onMetadataFailed(Mod* mod, bool try_others, ModPlatform::ResourceProvider first_choice) { if (try_others) { auto index_dir = indexDir(); @@ -368,7 +370,7 @@ void ModUpdateDialog::appendMod(CheckUpdateTask::UpdatableMod const& info) QString text = info.changelog; switch (info.provider) { - case ModPlatform::Provider::MODRINTH: { + case ModPlatform::ResourceProvider::MODRINTH: { text = markdownToHTML(info.changelog.toUtf8()); break; } @@ -386,9 +388,9 @@ void ModUpdateDialog::appendMod(CheckUpdateTask::UpdatableMod const& info) ui->modTreeWidget->addTopLevelItem(item_top); } -auto ModUpdateDialog::getTasks() -> const QList +auto ModUpdateDialog::getTasks() -> const QList { - QList list; + QList list; auto* item = ui->modTreeWidget->topLevelItem(0); diff --git a/launcher/ui/dialogs/ModUpdateDialog.h b/launcher/ui/dialogs/ModUpdateDialog.h index bd486f0d..3e3dd90d 100644 --- a/launcher/ui/dialogs/ModUpdateDialog.h +++ b/launcher/ui/dialogs/ModUpdateDialog.h @@ -1,7 +1,7 @@ #pragma once #include "BaseInstance.h" -#include "ModDownloadTask.h" +#include "ResourceDownloadTask.h" #include "ReviewMessageBox.h" #include "minecraft/mod/ModFolderModel.h" @@ -25,7 +25,7 @@ class ModUpdateDialog final : public ReviewMessageBox { void appendMod(const CheckUpdateTask::UpdatableMod& info); - const QList getTasks(); + const QList getTasks(); auto indexDir() const -> QDir { return m_mod_model->indexDir(); } auto noUpdates() const -> bool { return m_no_updates; }; @@ -36,7 +36,7 @@ class ModUpdateDialog final : public ReviewMessageBox { private slots: void onMetadataEnsured(Mod*); - void onMetadataFailed(Mod*, bool try_others = false, ModPlatform::Provider first_choice = ModPlatform::Provider::MODRINTH); + void onMetadataFailed(Mod*, bool try_others = false, ModPlatform::ResourceProvider first_choice = ModPlatform::ResourceProvider::MODRINTH); private: QWidget* m_parent; @@ -54,7 +54,7 @@ class ModUpdateDialog final : public ReviewMessageBox { QList> m_failed_metadata; QList> m_failed_check_update; - QHash m_tasks; + QHash m_tasks; BaseInstance* m_instance; bool m_no_updates = false; diff --git a/launcher/ui/dialogs/ResourceDownloadDialog.cpp b/launcher/ui/dialogs/ResourceDownloadDialog.cpp new file mode 100644 index 00000000..7367548f --- /dev/null +++ b/launcher/ui/dialogs/ResourceDownloadDialog.cpp @@ -0,0 +1,152 @@ +#include "ResourceDownloadDialog.h" + +#include + +#include "Application.h" +#include "ResourceDownloadTask.h" + +#include "ui/dialogs/ReviewMessageBox.h" +#include "ui/pages/modplatform/ResourcePage.h" +#include "ui/widgets/PageContainer.h" + +ResourceDownloadDialog::ResourceDownloadDialog(QWidget* parent, const std::shared_ptr base_model) + : QDialog(parent), m_base_model(base_model), m_buttons(QDialogButtonBox::Help | QDialogButtonBox::Ok | QDialogButtonBox::Cancel), m_vertical_layout(this) +{ + setObjectName(QStringLiteral("ResourceDownloadDialog")); + + resize(std::max(0.5 * parent->width(), 400.0), std::max(0.75 * parent->height(), 400.0)); + + setWindowIcon(APPLICATION->getThemedIcon("new")); + + // 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->setEnabled(false); + OkButton->setDefault(true); + OkButton->setAutoDefault(true); + OkButton->setText(tr("Review and confirm")); + OkButton->setShortcut(tr("Ctrl+Return")); + + auto CancelButton = m_buttons.button(QDialogButtonBox::Cancel); + CancelButton->setDefault(false); + CancelButton->setAutoDefault(false); + + auto HelpButton = m_buttons.button(QDialogButtonBox::Help); + HelpButton->setDefault(false); + HelpButton->setAutoDefault(false); + + setWindowModality(Qt::WindowModal); + setWindowTitle(dialogTitle()); +} + +// NOTE: We can't have this in the ctor because PageContainer calls a virtual function, and so +// won't work with subclasses if we put it in this ctor. +void ResourceDownloadDialog::initializeContainer() +{ + m_container = new PageContainer(this); + m_container->setSizePolicy(QSizePolicy::Policy::Preferred, QSizePolicy::Policy::Expanding); + m_container->layout()->setContentsMargins(0, 0, 0, 0); + m_vertical_layout.addWidget(m_container); + + m_container->addButtons(&m_buttons); + + connect(m_container, &PageContainer::selectedPageChanged, this, &ResourceDownloadDialog::selectedPageChanged); +} + +void ResourceDownloadDialog::connectButtons() +{ + auto OkButton = m_buttons.button(QDialogButtonBox::Ok); + OkButton->setToolTip(tr("Opens a new popup to review your selected %1 and confirm your selection. Shortcut: Ctrl+Return").arg(resourceString())); + connect(OkButton, &QPushButton::clicked, this, &ResourceDownloadDialog::confirm); + + auto CancelButton = m_buttons.button(QDialogButtonBox::Cancel); + connect(CancelButton, &QPushButton::clicked, this, &ResourceDownloadDialog::reject); + + auto HelpButton = m_buttons.button(QDialogButtonBox::Help); + connect(HelpButton, &QPushButton::clicked, m_container, &PageContainer::help); +} + +void ResourceDownloadDialog::confirm() +{ + auto keys = m_selected.keys(); + keys.sort(Qt::CaseInsensitive); + + auto confirm_dialog = ReviewMessageBox::create(this, tr("Confirm %1 to download").arg(resourceString())); + + for (auto& task : keys) { + confirm_dialog->appendResource({ task, m_selected.find(task).value()->getFilename() }); + } + + if (confirm_dialog->exec()) { + auto deselected = confirm_dialog->deselectedResources(); + for (auto name : deselected) { + m_selected.remove(name); + } + + this->accept(); + } +} + +bool ResourceDownloadDialog::selectPage(QString pageId) +{ + return m_container->selectPage(pageId); +} + +ResourcePage* ResourceDownloadDialog::getSelectedPage() +{ + return m_selectedPage; +} + +void ResourceDownloadDialog::addResource(QString name, ResourceDownloadTask* task) +{ + removeResource(name); + m_selected.insert(name, task); + + m_buttons.button(QDialogButtonBox::Ok)->setEnabled(!m_selected.isEmpty()); +} + +void ResourceDownloadDialog::removeResource(QString name) +{ + if (m_selected.contains(name)) + m_selected.find(name).value()->deleteLater(); + m_selected.remove(name); + + m_buttons.button(QDialogButtonBox::Ok)->setEnabled(!m_selected.isEmpty()); +} + +bool ResourceDownloadDialog::isSelected(QString name, QString filename) const +{ + auto iter = m_selected.constFind(name); + if (iter == m_selected.constEnd()) + return false; + + // FIXME: Is there a way to check for versions without checking the filename + // as a heuristic, other than adding such info to ResourceDownloadTask itself? + if (!filename.isEmpty()) + return iter.value()->getFilename() == filename; + + return true; +} + +const QList ResourceDownloadDialog::getTasks() +{ + return m_selected.values(); +} + +void ResourceDownloadDialog::selectedPageChanged(BasePage* previous, BasePage* selected) +{ + auto* prev_page = dynamic_cast(previous); + if (!prev_page) { + qCritical() << "Page '" << previous->displayName() << "' in ResourceDownloadDialog is not a ResourcePage!"; + return; + } + + m_selectedPage = dynamic_cast(selected); + if (!m_selectedPage) { + qCritical() << "Page '" << selected->displayName() << "' in ResourceDownloadDialog is not a ResourcePage!"; + return; + } + + // Same effect as having a global search bar + m_selectedPage->setSearchTerm(prev_page->getSearchTerm()); +} diff --git a/launcher/ui/dialogs/ResourceDownloadDialog.h b/launcher/ui/dialogs/ResourceDownloadDialog.h new file mode 100644 index 00000000..d6b3938b --- /dev/null +++ b/launcher/ui/dialogs/ResourceDownloadDialog.h @@ -0,0 +1,55 @@ +#pragma once + +#include +#include +#include + +#include "ui/pages/BasePageProvider.h" + +class ResourceDownloadTask; +class ResourcePage; +class ResourceFolderModel; +class PageContainer; +class QVBoxLayout; +class QDialogButtonBox; + +class ResourceDownloadDialog : public QDialog, public BasePageProvider { + Q_OBJECT + + public: + ResourceDownloadDialog(QWidget* parent, const std::shared_ptr base_model); + + void initializeContainer(); + void connectButtons(); + + //: String that gets appended to the download dialog title ("Download " + resourcesString()) + [[nodiscard]] virtual QString resourceString() const { return tr("resources"); } + + QString dialogTitle() override { return tr("Download %1").arg(resourceString()); }; + + bool selectPage(QString pageId); + ResourcePage* getSelectedPage(); + + void addResource(QString name, ResourceDownloadTask* task); + void removeResource(QString name); + [[nodiscard]] bool isSelected(QString name, QString filename = "") const; + + const QList getTasks(); + [[nodiscard]] const std::shared_ptr getBaseModel() const { return m_base_model; } + + protected slots: + void selectedPageChanged(BasePage* previous, BasePage* selected); + + virtual void confirm(); + + protected: + const std::shared_ptr m_base_model; + + PageContainer* m_container = nullptr; + ResourcePage* m_selectedPage = nullptr; + + QDialogButtonBox m_buttons; + QVBoxLayout m_vertical_layout; + + QHash m_selected; +}; diff --git a/launcher/ui/dialogs/ReviewMessageBox.cpp b/launcher/ui/dialogs/ReviewMessageBox.cpp index 7c25c91c..f45a9c4a 100644 --- a/launcher/ui/dialogs/ReviewMessageBox.cpp +++ b/launcher/ui/dialogs/ReviewMessageBox.cpp @@ -25,7 +25,7 @@ auto ReviewMessageBox::create(QWidget* parent, QString&& title, QString&& icon) return new ReviewMessageBox(parent, title, icon); } -void ReviewMessageBox::appendMod(ModInformation&& info) +void ReviewMessageBox::appendResource(ResourceInformation&& info) { auto itemTop = new QTreeWidgetItem(ui->modTreeWidget); itemTop->setCheckState(0, Qt::CheckState::Checked); @@ -39,7 +39,7 @@ void ReviewMessageBox::appendMod(ModInformation&& info) ui->modTreeWidget->addTopLevelItem(itemTop); } -auto ReviewMessageBox::deselectedMods() -> QStringList +auto ReviewMessageBox::deselectedResources() -> QStringList { QStringList list; diff --git a/launcher/ui/dialogs/ReviewMessageBox.h b/launcher/ui/dialogs/ReviewMessageBox.h index 9cfa679a..e2d0ce37 100644 --- a/launcher/ui/dialogs/ReviewMessageBox.h +++ b/launcher/ui/dialogs/ReviewMessageBox.h @@ -12,15 +12,15 @@ class ReviewMessageBox : public QDialog { public: static auto create(QWidget* parent, QString&& title, QString&& icon = "") -> ReviewMessageBox*; - using ModInformation = struct { + using ResourceInformation = struct { QString name; QString filename; }; - void appendMod(ModInformation&& info); - auto deselectedMods() -> QStringList; + void appendResource(ResourceInformation&& info); + auto deselectedResources() -> QStringList; - ~ReviewMessageBox(); + ~ReviewMessageBox() override; protected: ReviewMessageBox(QWidget* parent, const QString& title, const QString& icon); diff --git a/launcher/ui/pages/instance/ModFolderPage.cpp b/launcher/ui/pages/instance/ModFolderPage.cpp index 627e71e5..1bce3c0d 100644 --- a/launcher/ui/pages/instance/ModFolderPage.cpp +++ b/launcher/ui/pages/instance/ModFolderPage.cpp @@ -59,7 +59,7 @@ #include "minecraft/mod/Mod.h" #include "minecraft/mod/ModFolderModel.h" -#include "modplatform/ModAPI.h" +#include "modplatform/ResourceAPI.h" #include "Version.h" #include "tasks/ConcurrentTask.h" @@ -153,12 +153,12 @@ void ModFolderPage::installMods() return; // this is a null instance or a legacy instance auto profile = static_cast(m_instance)->getPackProfile(); - if (profile->getModLoaders() == ModAPI::Unspecified) { + if (!profile->getModLoaders().has_value()) { QMessageBox::critical(this, tr("Error"), tr("Please install a mod loader first!")); return; } - ModDownloadDialog mdownload(m_model, this, m_instance); + ModDownloadDialog mdownload(this, m_model, m_instance); if (mdownload.exec()) { ConcurrentTask* tasks = new ConcurrentTask(this); connect(tasks, &Task::failed, [this, tasks](QString reason) { diff --git a/launcher/ui/pages/instance/ResourcePackPage.h b/launcher/ui/pages/instance/ResourcePackPage.h index 9633e3b4..db8af0c5 100644 --- a/launcher/ui/pages/instance/ResourcePackPage.h +++ b/launcher/ui/pages/instance/ResourcePackPage.h @@ -73,3 +73,4 @@ public: return true; } }; + diff --git a/launcher/ui/pages/modplatform/ModModel.cpp b/launcher/ui/pages/modplatform/ModModel.cpp index ed58eb32..31aae746 100644 --- a/launcher/ui/pages/modplatform/ModModel.cpp +++ b/launcher/ui/pages/modplatform/ModModel.cpp @@ -1,226 +1,81 @@ #include "ModModel.h" -#include "BuildConfig.h" #include "Json.h" #include "ModPage.h" #include "minecraft/MinecraftInstance.h" #include "minecraft/PackProfile.h" -#include "ui/dialogs/ModDownloadDialog.h" - -#include "ui/widgets/ProjectItem.h" #include namespace ModPlatform { -// HACK: We need this to prevent callbacks from calling the ListModel after it has already been deleted. -// This leaks a tiny bit of memory per time the user has opened the mod dialog. How to make this better? -static QHash s_running; - -ListModel::ListModel(ModPage* parent) : QAbstractListModel(parent), m_parent(parent) { s_running.insert(this, true); } - -ListModel::~ListModel() -{ - s_running.find(this).value() = false; -} - -auto ListModel::debugName() const -> QString -{ - return m_parent->debugName(); -} +ListModel::ListModel(ModPage* parent, ResourceAPI* api) : ResourceModel(parent, api) {} /******** Make data requests ********/ -void ListModel::fetchMore(const QModelIndex& parent) +ResourceAPI::SearchArgs ListModel::createSearchArguments() { - if (parent.isValid()) - return; - if (nextSearchOffset == 0) { - qWarning() << "fetchMore with 0 offset is wrong..."; - return; - } - performPaginatedSearch(); + auto profile = static_cast(m_associated_page->m_base_instance).getPackProfile(); + return { ModPlatform::ResourceType::MOD, m_next_search_offset, m_search_term, + getSorts()[currentSort], profile->getModLoaders(), getMineVersions() }; } - -auto ListModel::data(const QModelIndex& index, int role) const -> QVariant +ResourceAPI::SearchCallbacks ListModel::createSearchCallbacks() { - int pos = index.row(); - if (pos >= modpacks.size() || pos < 0 || !index.isValid()) { - return QString("INVALID INDEX %1").arg(pos); - } - - ModPlatform::IndexedPack pack = modpacks.at(pos); - switch (role) { - case 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; - } - case Qt::DecorationRole: { - if (m_logoMap.contains(pack.logoName)) { - return m_logoMap.value(pack.logoName); - } - QIcon icon = APPLICATION->getThemedIcon("screenshot-placeholder"); - // un-const-ify this - ((ListModel*)this)->requestLogo(pack.logoName, pack.logoUrl); - return icon; - } - case Qt::SizeHintRole: - return QSize(0, 58); - case Qt::UserRole: { - QVariant v; - v.setValue(pack); - return v; - } - // Custom data - case UserDataTypes::TITLE: - return pack.name; - case UserDataTypes::DESCRIPTION: - return pack.description; - case UserDataTypes::SELECTED: - return m_parent->getDialog()->isModSelected(pack.name); - default: - break; - } - - return {}; + return { [this](auto& doc) { + if (!s_running_models.constFind(this).value()) + return; + searchRequestFinished(doc); + } }; } -bool ListModel::setData(const QModelIndex &index, const QVariant &value, int role) +ResourceAPI::VersionSearchArgs ListModel::createVersionsArguments(QModelIndex& entry) { - int pos = index.row(); - if (pos >= modpacks.size() || pos < 0 || !index.isValid()) - return false; + auto const& pack = m_packs[entry.row()]; + auto profile = static_cast(m_associated_page->m_base_instance).getPackProfile(); - modpacks[pos] = value.value(); - - return true; + return { pack.addonId.toString(), getMineVersions(), profile->getModLoaders() }; } - -void ListModel::requestModVersions(ModPlatform::IndexedPack const& current, QModelIndex index) +ResourceAPI::VersionSearchCallbacks ListModel::createVersionsCallbacks(QModelIndex& entry) { - auto profile = (dynamic_cast((dynamic_cast(parent()))->m_instance))->getPackProfile(); - - m_parent->apiProvider()->getVersions({ current.addonId.toString(), getMineVersions(), profile->getModLoaders() }, - [this, current, index](QJsonDocument& doc, QString addonId) { - if (!s_running.constFind(this).value()) - return; - versionRequestSucceeded(doc, addonId, index); - }); -} - -void ListModel::performPaginatedSearch() -{ - auto profile = (dynamic_cast((dynamic_cast(parent()))->m_instance))->getPackProfile(); + auto const& pack = m_packs[entry.row()]; - m_parent->apiProvider()->searchMods( - this, { nextSearchOffset, currentSearchTerm, getSorts()[currentSort], profile->getModLoaders(), getMineVersions() }); + return { [this, pack, entry](auto& doc, auto addonId) { + if (!s_running_models.constFind(this).value()) + return; + versionRequestSucceeded(doc, addonId, entry); + } }; } -void ListModel::requestModInfo(ModPlatform::IndexedPack& current, QModelIndex index) +ResourceAPI::ProjectInfoArgs ListModel::createInfoArguments(QModelIndex& entry) { - m_parent->apiProvider()->getModInfo(current, [this, index](QJsonDocument& doc, ModPlatform::IndexedPack& pack) { - if (!s_running.constFind(this).value()) - return; - infoRequestFinished(doc, pack, index); - }); + auto& pack = m_packs[entry.row()]; + return { pack }; } - -void ListModel::refresh() +ResourceAPI::ProjectInfoCallbacks ListModel::createInfoCallbacks(QModelIndex& entry) { - if (jobPtr) { - jobPtr->abort(); - searchState = ResetRequested; - return; - } else { - beginResetModel(); - modpacks.clear(); - endResetModel(); - searchState = None; - } - nextSearchOffset = 0; - performPaginatedSearch(); + return { [this, entry](auto& doc, auto& pack) { + if (!s_running_models.constFind(this).value()) + return; + infoRequestFinished(doc, pack, entry); + } }; } void ListModel::searchWithTerm(const QString& term, const int sort, const bool filter_changed) { - if (currentSearchTerm == term && currentSearchTerm.isNull() == term.isNull() && currentSort == sort && !filter_changed) { + if (m_search_term == term && m_search_term.isNull() == term.isNull() && currentSort == sort && !filter_changed) { return; } - currentSearchTerm = term; + setSearchTerm(term); currentSort = sort; refresh(); } -void ListModel::getLogo(const QString& logo, const QString& logoUrl, LogoCallback callback) -{ - if (m_logoMap.contains(logo)) { - callback(APPLICATION->metacache() - ->resolveEntry(m_parent->metaEntryBase(), QString("logos/%1").arg(logo.section(".", 0, 0))) - ->getFullPath()); - } else { - requestLogo(logo, logoUrl); - } -} - -void ListModel::requestLogo(QString logo, QString url) -{ - if (m_loadingLogos.contains(logo) || m_failedLogos.contains(logo) || url.isEmpty()) { - return; - } - - MetaEntryPtr entry = - APPLICATION->metacache()->resolveEntry(m_parent->metaEntryBase(), QString("logos/%1").arg(logo.section(".", 0, 0))); - auto job = new NetJob(QString("%1 Icon Download %2").arg(m_parent->debugName()).arg(logo), APPLICATION->network()); - job->addNetAction(Net::Download::makeCached(QUrl(url), entry)); - - auto fullPath = entry->getFullPath(); - QObject::connect(job, &NetJob::succeeded, this, [this, logo, fullPath, job] { - job->deleteLater(); - emit logoLoaded(logo, QIcon(fullPath)); - if (waitingCallbacks.contains(logo)) { - waitingCallbacks.value(logo)(fullPath); - } - }); - - QObject::connect(job, &NetJob::failed, this, [this, logo, job] { - job->deleteLater(); - emit logoFailed(logo); - }); - - job->start(); - m_loadingLogos.append(logo); -} - /******** Request callbacks ********/ -void 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::searchRequestFinished(QJsonDocument& doc) { - jobPtr.reset(); - QList newList; auto packs = documentToArray(doc); @@ -232,62 +87,27 @@ void ListModel::searchRequestFinished(QJsonDocument& doc) loadIndexedPack(pack, packObj); newList.append(pack); } catch (const JSONValidationError& e) { - qWarning() << "Error while loading mod from " << m_parent->debugName() << ": " << e.cause(); + qWarning() << "Error while loading mod from " << m_associated_page->debugName() << ": " << e.cause(); continue; } } if (packs.size() < 25) { - searchState = Finished; + m_search_state = SearchState::Finished; } else { - nextSearchOffset += 25; - searchState = CanPossiblyFetchMore; + m_next_search_offset += 25; + m_search_state = SearchState::CanFetchMore; } // When you have a Qt build with assertions turned on, proceeding here will abort the application if (newList.size() == 0) return; - beginInsertRows(QModelIndex(), modpacks.size(), modpacks.size() + newList.size() - 1); - modpacks.append(newList); + beginInsertRows(QModelIndex(), m_packs.size(), m_packs.size() + newList.size() - 1); + m_packs.append(newList); endInsertRows(); } -void ListModel::searchRequestFailed(QString reason) -{ - auto failed_action = jobPtr->getFailedActions().at(0); - if (!failed_action->m_reply) { - // Network error - QMessageBox::critical(nullptr, tr("Error"), tr("A network error occurred. Could not load mods.")); - } else if (failed_action->m_reply && failed_action->m_reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() == 409) { - // 409 Gone, notify user to update - QMessageBox::critical(nullptr, tr("Error"), - //: %1 refers to the launcher itself - QString("%1 %2") - .arg(m_parent->displayName()) - .arg(tr("API version too old!\nPlease update %1!").arg(BuildConfig.LAUNCHER_DISPLAYNAME))); - } - - jobPtr.reset(); - searchState = Finished; -} - -void ListModel::searchRequestAborted() -{ - if (searchState != ResetRequested) - qCritical() << "Search task in ModModel aborted by an unknown reason!"; - - // Retry fetching - jobPtr.reset(); - - beginResetModel(); - modpacks.clear(); - endResetModel(); - - nextSearchOffset = 0; - performPaginatedSearch(); -} - void ListModel::infoRequestFinished(QJsonDocument& doc, ModPlatform::IndexedPack& pack, const QModelIndex& index) { qDebug() << "Loading mod info"; @@ -310,12 +130,12 @@ void ListModel::infoRequestFinished(QJsonDocument& doc, ModPlatform::IndexedPack } } - m_parent->updateUi(); + m_associated_page->updateUi(); } void ListModel::versionRequestSucceeded(QJsonDocument doc, QString addonId, const QModelIndex& index) { - auto& current = m_parent->getCurrent(); + auto current = m_associated_page->getCurrentPack(); if (addonId != current.addonId) { return; } @@ -336,15 +156,19 @@ void ListModel::versionRequestSucceeded(QJsonDocument doc, QString addonId, cons qWarning() << "Failed to cache mod versions!"; } - - m_parent->updateModVersions(); + m_associated_page->updateVersionList(); } } // namespace ModPlatform /******** Helpers ********/ -auto ModPlatform::ListModel::getMineVersions() const -> std::list +#define MOD_PAGE(x) static_cast(x) + +auto ModPlatform::ListModel::getMineVersions() const -> std::optional> { - return m_parent->getFilter()->versions; + auto versions = MOD_PAGE(m_associated_page)->getFilter()->versions; + if (!versions.empty()) + return versions; + return {}; } diff --git a/launcher/ui/pages/modplatform/ModModel.h b/launcher/ui/pages/modplatform/ModModel.h index 36840649..7c735d90 100644 --- a/launcher/ui/pages/modplatform/ModModel.h +++ b/launcher/ui/pages/modplatform/ModModel.h @@ -3,90 +3,52 @@ #include #include "modplatform/ModIndex.h" -#include "net/NetJob.h" +#include "modplatform/ResourceAPI.h" + +#include "ui/pages/modplatform/ResourceModel.h" class ModPage; class Version; namespace ModPlatform { -using LogoMap = QMap; -using LogoCallback = std::function; - -class ListModel : public QAbstractListModel { +class ListModel : public ResourceModel { Q_OBJECT public: - ListModel(ModPage* parent); - ~ListModel() override; - - inline auto rowCount(const QModelIndex& parent) const -> int override { return parent.isValid() ? 0 : modpacks.size(); }; - inline auto columnCount(const QModelIndex& parent) const -> int override { return parent.isValid() ? 0 : 1; }; - inline auto flags(const QModelIndex& index) const -> Qt::ItemFlags override { return QAbstractListModel::flags(index); }; - - auto debugName() const -> QString; - - /* Retrieve information from the model at a given index with the given role */ - auto data(const QModelIndex& index, int role) const -> QVariant override; - bool setData(const QModelIndex &index, const QVariant &value, int role) override; - - inline void setActiveJob(NetJob::Ptr ptr) { jobPtr = ptr; } - inline NetJob* activeJob() { return jobPtr.get(); } + ListModel(ModPage* parent, ResourceAPI* api); /* Ask the API for more information */ - void fetchMore(const QModelIndex& parent) override; - void refresh(); void searchWithTerm(const QString& term, const int sort, const bool filter_changed); - void requestModInfo(ModPlatform::IndexedPack& current, QModelIndex index); - void requestModVersions(const ModPlatform::IndexedPack& current, QModelIndex index); virtual void loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj) = 0; virtual void loadExtraPackInfo(ModPlatform::IndexedPack& m, QJsonObject& obj) = 0; virtual void loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr) = 0; - void getLogo(const QString& logo, const QString& logoUrl, LogoCallback callback); - - inline auto canFetchMore(const QModelIndex& parent) const -> bool override { return parent.isValid() ? false : searchState == CanPossiblyFetchMore; }; - public slots: void searchRequestFinished(QJsonDocument& doc); - void searchRequestFailed(QString reason); - void searchRequestAborted(); void infoRequestFinished(QJsonDocument& doc, ModPlatform::IndexedPack& pack, const QModelIndex& index); void versionRequestSucceeded(QJsonDocument doc, QString addonId, const QModelIndex& index); - protected slots: + public slots: + ResourceAPI::SearchArgs createSearchArguments() override; + ResourceAPI::SearchCallbacks createSearchCallbacks() override; - void logoFailed(QString logo); - void logoLoaded(QString logo, QIcon out); + ResourceAPI::VersionSearchArgs createVersionsArguments(QModelIndex&) override; + ResourceAPI::VersionSearchCallbacks createVersionsCallbacks(QModelIndex&) override; - void performPaginatedSearch(); + ResourceAPI::ProjectInfoArgs createInfoArguments(QModelIndex&) override; + ResourceAPI::ProjectInfoCallbacks createInfoCallbacks(QModelIndex&) override; protected: virtual auto documentToArray(QJsonDocument& obj) const -> QJsonArray = 0; virtual auto getSorts() const -> const char** = 0; - void requestLogo(QString file, QString url); - - inline auto getMineVersions() const -> std::list; + inline auto getMineVersions() const -> std::optional>; protected: - ModPage* m_parent; - - QList modpacks; - - LogoMap m_logoMap; - QMap waitingCallbacks; - QStringList m_failedLogos; - QStringList m_loadingLogos; - - QString currentSearchTerm; int currentSort = 0; - int nextSearchOffset = 0; - enum SearchState { None, CanPossiblyFetchMore, ResetRequested, Finished } searchState = None; - - NetJob::Ptr jobPtr; }; } // namespace ModPlatform diff --git a/launcher/ui/pages/modplatform/ModPage.cpp b/launcher/ui/pages/modplatform/ModPage.cpp index 0f30689e..853f2c54 100644 --- a/launcher/ui/pages/modplatform/ModPage.cpp +++ b/launcher/ui/pages/modplatform/ModPage.cpp @@ -35,59 +35,30 @@ */ #include "ModPage.h" -#include "Application.h" -#include "ui_ModPage.h" +#include "ui_ResourcePage.h" #include #include #include + #include +#include "Application.h" +#include "ResourceDownloadTask.h" + #include "minecraft/MinecraftInstance.h" #include "minecraft/PackProfile.h" -#include "ui/dialogs/ModDownloadDialog.h" -#include "ui/widgets/ProjectItem.h" -#include "Markdown.h" - -ModPage::ModPage(ModDownloadDialog* dialog, BaseInstance* instance, ModAPI* api) - : QWidget(dialog) - , m_instance(instance) - , ui(new Ui::ModPage) - , dialog(dialog) - , m_fetch_progress(this, false) - , api(api) -{ - ui->setupUi(this); - - connect(ui->searchButton, &QPushButton::clicked, this, &ModPage::triggerSearch); - connect(ui->modFilterButton, &QPushButton::clicked, this, &ModPage::filterMods); - connect(ui->packView, &QListView::doubleClicked, this, &ModPage::onModSelected); - - m_search_timer.setTimerType(Qt::TimerType::CoarseTimer); - m_search_timer.setSingleShot(true); - - connect(&m_search_timer, &QTimer::timeout, this, &ModPage::triggerSearch); - - ui->searchEdit->installEventFilter(this); - - ui->versionSelectionBox->view()->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); - ui->versionSelectionBox->view()->parentWidget()->setMaximumHeight(300); - - m_fetch_progress.hideIfInactive(true); - m_fetch_progress.setFixedHeight(24); - m_fetch_progress.progressFormat(""); - - ui->gridLayout_3->addWidget(&m_fetch_progress, 0, 0, 1, ui->gridLayout_3->columnCount()); - ui->packView->setItemDelegate(new ProjectItemDelegate(this)); - ui->packView->installEventFilter(this); +#include "ui/dialogs/ModDownloadDialog.h" - connect(ui->packDescription, &QTextBrowser::anchorClicked, this, &ModPage::openUrl); -} +#include "ui/pages/modplatform/ModModel.h" -ModPage::~ModPage() +ModPage::ModPage(ModDownloadDialog* dialog, BaseInstance& instance) + : ResourcePage(dialog, instance) { - delete ui; + connect(m_ui->searchButton, &QPushButton::clicked, this, &ModPage::triggerSearch); + connect(m_ui->resourceFilterButton, &QPushButton::clicked, this, &ModPage::filterMods); + connect(m_ui->packView, &QListView::doubleClicked, this, &ModPage::onResourceSelected); } void ModPage::setFilterWidget(unique_qobject_ptr& widget) @@ -97,59 +68,19 @@ void ModPage::setFilterWidget(unique_qobject_ptr& widget) m_filter_widget.swap(widget); - ui->gridLayout_3->addWidget(m_filter_widget.get(), 0, 0, 1, ui->gridLayout_3->columnCount()); + m_ui->gridLayout_3->addWidget(m_filter_widget.get(), 0, 0, 1, m_ui->gridLayout_3->columnCount()); - m_filter_widget->setInstance(static_cast(m_instance)); + m_filter_widget->setInstance(&static_cast(m_base_instance)); m_filter = m_filter_widget->getFilter(); connect(m_filter_widget.get(), &ModFilterWidget::filterChanged, this, [&]{ - ui->searchButton->setStyleSheet("text-decoration: underline"); + m_ui->searchButton->setStyleSheet("text-decoration: underline"); }); connect(m_filter_widget.get(), &ModFilterWidget::filterUnchanged, this, [&]{ - ui->searchButton->setStyleSheet("text-decoration: none"); + m_ui->searchButton->setStyleSheet("text-decoration: none"); }); } - -/******** Qt things ********/ - -void ModPage::openedImpl() -{ - updateSelectionButton(); - triggerSearch(); -} - -auto ModPage::eventFilter(QObject* watched, QEvent* event) -> bool -{ - if (watched == ui->searchEdit && event->type() == QEvent::KeyPress) { - auto* keyEvent = dynamic_cast(event); - if (keyEvent->key() == Qt::Key_Return) { - triggerSearch(); - keyEvent->accept(); - return true; - } else { - if (m_search_timer.isActive()) - m_search_timer.stop(); - - m_search_timer.start(350); - } - } else if (watched == ui->packView && event->type() == QEvent::KeyPress) { - auto* keyEvent = dynamic_cast(event); - if (keyEvent->key() == Qt::Key_Return) { - onModSelected(); - - // To have the 'select mod' button outlined instead of the 'review and confirm' one - ui->modSelectionButton->setFocus(Qt::FocusReason::ShortcutFocusReason); - ui->packView->setFocus(Qt::FocusReason::NoFocusReason); - - keyEvent->accept(); - return true; - } - } - return QWidget::eventFilter(watched, event); -} - - /******** Callbacks to events in the UI (set up in the derived classes) ********/ void ModPage::filterMods() @@ -163,176 +94,37 @@ void ModPage::triggerSearch() m_filter = m_filter_widget->getFilter(); if (changed) { - ui->packView->clearSelection(); - ui->packDescription->clear(); - ui->versionSelectionBox->clear(); + m_ui->packView->clearSelection(); + m_ui->packDescription->clear(); + m_ui->versionSelectionBox->clear(); updateSelectionButton(); } - listModel->searchWithTerm(getSearchTerm(), ui->sortByBox->currentIndex(), changed); - m_fetch_progress.watch(listModel->activeJob()); -} - -QString ModPage::getSearchTerm() const -{ - return ui->searchEdit->text(); -} -void ModPage::setSearchTerm(QString term) -{ - ui->searchEdit->setText(term); + static_cast(m_model)->searchWithTerm(getSearchTerm(), m_ui->sortByBox->currentIndex(), changed); + m_fetch_progress.watch(&m_model->activeJob()); } -void ModPage::onSelectionChanged(QModelIndex curr, QModelIndex prev) +QMap ModPage::urlHandlers() const { - ui->versionSelectionBox->clear(); - - if (!curr.isValid()) { return; } - - current = listModel->data(curr, Qt::UserRole).value(); - - if (!current.versionsLoaded) { - qDebug() << QString("Loading %1 mod versions").arg(debugName()); - - ui->modSelectionButton->setText(tr("Loading versions...")); - ui->modSelectionButton->setEnabled(false); - - listModel->requestModVersions(current, curr); - } else { - for (int i = 0; i < current.versions.size(); i++) { - ui->versionSelectionBox->addItem(current.versions[i].version, QVariant(i)); - } - if (ui->versionSelectionBox->count() == 0) { ui->versionSelectionBox->addItem(tr("No valid version found."), QVariant(-1)); } - - updateSelectionButton(); - } - - if(!current.extraDataLoaded){ - qDebug() << QString("Loading %1 mod info").arg(debugName()); - - listModel->requestModInfo(current, curr); - } - - updateUi(); -} - -void ModPage::onVersionSelectionChanged(QString data) -{ - if (data.isNull() || data.isEmpty()) { - selectedVersion = -1; - return; - } - selectedVersion = ui->versionSelectionBox->currentData().toInt(); - updateSelectionButton(); -} - -void ModPage::onModSelected() -{ - if (selectedVersion < 0) - return; - - auto& version = current.versions[selectedVersion]; - if (dialog->isModSelected(current.name, version.fileName)) { - dialog->removeSelectedMod(current.name); - } else { - bool is_indexed = !APPLICATION->settings()->get("ModMetadataDisabled").toBool(); - dialog->addSelectedMod(current.name, new ModDownloadTask(current, version, dialog->mods, is_indexed)); - } - - updateSelectionButton(); - - /* Force redraw on the mods list when the selection changes */ - ui->packView->adjustSize(); -} - -static const QRegularExpression modrinth(QRegularExpression::anchoredPattern("(?:www\\.)?modrinth\\.com\\/mod\\/([^\\/]+)\\/?")); -static const QRegularExpression curseForge(QRegularExpression::anchoredPattern("(?:www\\.)?curseforge\\.com\\/minecraft\\/mc-mods\\/([^\\/]+)\\/?")); -static const QRegularExpression curseForgeOld(QRegularExpression::anchoredPattern("minecraft\\.curseforge\\.com\\/projects\\/([^\\/]+)\\/?")); - -void ModPage::openUrl(const QUrl& url) -{ - // do not allow other url schemes for security reasons - if (!(url.scheme() == "http" || url.scheme() == "https")) { - qWarning() << "Unsupported scheme" << url.scheme(); - return; - } - - // detect mod URLs and search instead - - const QString address = url.host() + url.path(); - QRegularExpressionMatch match; - QString page; - - match = modrinth.match(address); - if (match.hasMatch()) - page = "modrinth"; - else if (APPLICATION->capabilities() & Application::SupportsFlame) { - match = curseForge.match(address); - if (!match.hasMatch()) - match = curseForgeOld.match(address); - - if (match.hasMatch()) - page = "curseforge"; - } - - if (!page.isNull()) { - const QString slug = match.captured(1); - - // ensure the user isn't opening the same mod - if (slug != current.slug) { - dialog->selectPage(page); - - ModPage* newPage = dialog->getSelectedPage(); - - QLineEdit* searchEdit = newPage->ui->searchEdit; - ModPlatform::ListModel* model = newPage->listModel; - QListView* view = newPage->ui->packView; - - auto jump = [url, slug, model, view] { - for (int row = 0; row < model->rowCount({}); row++) { - const QModelIndex index = model->index(row); - const auto pack = model->data(index, Qt::UserRole).value(); - - if (pack.slug == slug) { - view->setCurrentIndex(index); - return; - } - } - - // The final fallback. - QDesktopServices::openUrl(url); - }; - - searchEdit->setText(slug); - newPage->triggerSearch(); - - if (model->activeJob()) - connect(model->activeJob(), &Task::finished, jump); - else - jump(); - - return; - } - } - - // open in the user's web browser - QDesktopServices::openUrl(url); + QMap map; + map.insert(QRegularExpression::anchoredPattern("(?:www\\.)?modrinth\\.com\\/mod\\/([^\\/]+)\\/?"), "modrinth"); + map.insert(QRegularExpression::anchoredPattern("(?:www\\.)?curseforge\\.com\\/minecraft\\/mc-mods\\/([^\\/]+)\\/?"), "curseforge"); + map.insert(QRegularExpression::anchoredPattern("minecraft\\.curseforge\\.com\\/projects\\/([^\\/]+)\\/?"), "curseforge"); + return map; } /******** Make changes to the UI ********/ -void ModPage::retranslate() -{ - ui->retranslateUi(this); -} - -void ModPage::updateModVersions(int prev_count) +void ModPage::updateVersionList() { - auto packProfile = (dynamic_cast(m_instance))->getPackProfile(); + m_ui->versionSelectionBox->clear(); + auto packProfile = (dynamic_cast(m_base_instance)).getPackProfile(); QString mcVersion = packProfile->getComponentVersion("net.minecraft"); - for (int i = 0; i < current.versions.size(); i++) { - auto version = current.versions[i]; + auto current_pack = getCurrentPack(); + for (int i = 0; i < current_pack.versions.size(); i++) { + auto version = current_pack.versions[i]; bool valid = false; for(auto& mcVer : m_filter->versions){ //NOTE: Flame doesn't care about loader, so passing it changes nothing. @@ -344,87 +136,18 @@ void ModPage::updateModVersions(int prev_count) // Only add the version if it's valid or using the 'Any' filter, but never if the version is opted out if ((valid || m_filter->versions.empty()) && !optedOut(version)) - ui->versionSelectionBox->addItem(version.version, QVariant(i)); + m_ui->versionSelectionBox->addItem(version.version, QVariant(i)); } - if (ui->versionSelectionBox->count() == 0 && prev_count != 0) { - ui->versionSelectionBox->addItem(tr("No valid version found!"), QVariant(-1)); - ui->modSelectionButton->setText(tr("Cannot select invalid version :(")); + if (m_ui->versionSelectionBox->count() == 0) { + m_ui->versionSelectionBox->addItem(tr("No valid version found!"), QVariant(-1)); + m_ui->resourceSelectionButton->setText(tr("Cannot select invalid version :(")); } updateSelectionButton(); } - -void ModPage::updateSelectionButton() -{ - if (!isOpened || selectedVersion < 0) { - ui->modSelectionButton->setEnabled(false); - return; - } - - ui->modSelectionButton->setEnabled(true); - auto& version = current.versions[selectedVersion]; - if (!dialog->isModSelected(current.name, version.fileName)) { - ui->modSelectionButton->setText(tr("Select mod for download")); - } else { - ui->modSelectionButton->setText(tr("Deselect mod for download")); - } -} - -void ModPage::updateUi() +void ModPage::addResourceToDialog(ModPlatform::IndexedPack& pack, ModPlatform::IndexedVersion& version) { - QString text = ""; - QString name = current.name; - - if (current.websiteUrl.isEmpty()) - text = name; - else - text = "" + name + ""; - - if (!current.authors.empty()) { - auto authorToStr = [](ModPlatform::ModpackAuthor& author) -> QString { - 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(", "); - } - - if (current.extraDataLoaded) { - if (!current.extraData.donate.isEmpty()) { - text += "

" + tr("Donate information: "); - auto donateToStr = [](ModPlatform::DonationData& donate) -> QString { - return QString("%2").arg(donate.url, donate.platform); - }; - QStringList donates; - for (auto& donate : current.extraData.donate) { - donates.append(donateToStr(donate)); - } - text += donates.join(", "); - } - - if (!current.extraData.issuesUrl.isEmpty() - || !current.extraData.sourceUrl.isEmpty() - || !current.extraData.wikiUrl.isEmpty() - || !current.extraData.discordUrl.isEmpty()) { - text += "

" + tr("External links:") + "
"; - } - - if (!current.extraData.issuesUrl.isEmpty()) - text += "- " + tr("Issues: %1").arg(current.extraData.issuesUrl) + "
"; - if (!current.extraData.wikiUrl.isEmpty()) - text += "- " + tr("Wiki: %1").arg(current.extraData.wikiUrl) + "
"; - if (!current.extraData.sourceUrl.isEmpty()) - text += "- " + tr("Source code: %1").arg(current.extraData.sourceUrl) + "
"; - if (!current.extraData.discordUrl.isEmpty()) - text += "- " + tr("Discord: %1").arg(current.extraData.discordUrl) + "
"; - } - - text += "
"; - - ui->packDescription->setHtml(text + (current.extraData.body.isEmpty() ? current.description : markdownToHTML(current.extraData.body))); - ui->packDescription->flush(); + bool is_indexed = !APPLICATION->settings()->get("ModMetadataDisabled").toBool(); + m_parent_dialog->addResource(pack.name, new ResourceDownloadTask(pack, version, m_parent_dialog->getBaseModel(), is_indexed)); } diff --git a/launcher/ui/pages/modplatform/ModPage.h b/launcher/ui/pages/modplatform/ModPage.h index c9ccbaf2..8c1fec84 100644 --- a/launcher/ui/pages/modplatform/ModPage.h +++ b/launcher/ui/pages/modplatform/ModPage.h @@ -2,104 +2,58 @@ #include -#include "Application.h" -#include "modplatform/ModAPI.h" #include "modplatform/ModIndex.h" -#include "ui/pages/BasePage.h" -#include "ui/pages/modplatform/ModModel.h" + +#include "ui/pages/modplatform/ResourcePage.h" #include "ui/widgets/ModFilterWidget.h" -#include "ui/widgets/ProgressWidget.h" class ModDownloadDialog; namespace Ui { -class ModPage; +class ResourcePage; } /* This page handles most logic related to browsing and selecting mods to download. */ -class ModPage : public QWidget, public BasePage { +class ModPage : public ResourcePage { Q_OBJECT public: template - static T* create(ModDownloadDialog* dialog, BaseInstance* instance) + static T* create(ModDownloadDialog* dialog, BaseInstance& instance) { auto page = new T(dialog, instance); - auto filter_widget = ModFilterWidget::create(static_cast(instance)->getPackProfile()->getComponentVersion("net.minecraft"), page); + auto filter_widget = ModFilterWidget::create(static_cast(instance).getPackProfile()->getComponentVersion("net.minecraft"), page); page->setFilterWidget(filter_widget); return page; } - ~ModPage() override; - - /* Affects what the user sees */ - auto displayName() const -> QString override = 0; - auto icon() const -> QIcon override = 0; - auto id() const -> QString override = 0; - auto helpPage() const -> QString override = 0; + ~ModPage() override = default; - /* Used internally */ - virtual auto metaEntryBase() const -> QString = 0; - virtual auto debugName() const -> QString = 0; + [[nodiscard]] inline QString resourceString() const override { return tr("mod"); } + [[nodiscard]] QMap urlHandlers() const override; - void retranslate() override; + void addResourceToDialog(ModPlatform::IndexedPack&, ModPlatform::IndexedVersion&) override; - void updateUi(); + virtual auto validateVersion(ModPlatform::IndexedVersion& ver, QString mineVer, std::optional loaders = {}) const -> bool = 0; - auto shouldDisplay() const -> bool override = 0; - virtual auto validateVersion(ModPlatform::IndexedVersion& ver, QString mineVer, ModAPI::ModLoaderTypes loaders = ModAPI::Unspecified) const -> bool = 0; - virtual bool optedOut(ModPlatform::IndexedVersion& ver) const { return false; }; - - auto apiProvider() -> ModAPI* { return api.get(); }; + [[nodiscard]] bool supportsFiltering() const override { return true; }; auto getFilter() const -> const std::shared_ptr { return m_filter; } - auto getDialog() const -> const ModDownloadDialog* { return dialog; } - - /** Get the current term in the search bar. */ - auto getSearchTerm() const -> QString; - /** Programatically set the term in the search bar. */ - void setSearchTerm(QString); - void setFilterWidget(unique_qobject_ptr&); - auto getCurrent() -> ModPlatform::IndexedPack& { return current; } - void updateModVersions(int prev_count = -1); - - void openedImpl() override; - auto eventFilter(QObject* watched, QEvent* event) -> bool override; - - BaseInstance* m_instance; + public slots: + void updateVersionList() override; protected: - ModPage(ModDownloadDialog* dialog, BaseInstance* instance, ModAPI* api); - void updateSelectionButton(); + ModPage(ModDownloadDialog* dialog, BaseInstance& instance); protected slots: virtual void filterMods(); - void triggerSearch(); - void onSelectionChanged(QModelIndex first, QModelIndex second); - void onVersionSelectionChanged(QString data); - void onModSelected(); - virtual void openUrl(const QUrl& url); + void triggerSearch() override; protected: - Ui::ModPage* ui = nullptr; - ModDownloadDialog* dialog = nullptr; - unique_qobject_ptr m_filter_widget; std::shared_ptr m_filter; - - ProgressWidget m_fetch_progress; - - ModPlatform::ListModel* listModel = nullptr; - ModPlatform::IndexedPack current; - - std::unique_ptr api; - - int selectedVersion = -1; - - // Used to do instant searching with a delay to cache quick changes - QTimer m_search_timer; }; diff --git a/launcher/ui/pages/modplatform/ModPage.ui b/launcher/ui/pages/modplatform/ModPage.ui deleted file mode 100644 index 94365aa5..00000000 --- a/launcher/ui/pages/modplatform/ModPage.ui +++ /dev/null @@ -1,118 +0,0 @@ - - - ModPage - - - - 0 - 0 - 837 - 685 - - - - - - - - - false - - - false - - - - - - - Qt::ScrollBarAlwaysOff - - - true - - - - 48 - 48 - - - - - - - - - - Search - - - - - - - Search for mods... - - - - - - - - - - - - Version selected: - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - - - - Select mod for download - - - - - - - - - Filter options - - - - - - - Qt::Vertical - - - - - - - - ProjectDescriptionPage - QTextBrowser -
ui/widgets/ProjectDescriptionPage.h
-
-
- - searchEdit - searchButton - packView - packDescription - sortByBox - versionSelectionBox - - - -
diff --git a/launcher/ui/pages/modplatform/ResourceModel.cpp b/launcher/ui/pages/modplatform/ResourceModel.cpp new file mode 100644 index 00000000..d672a2ac --- /dev/null +++ b/launcher/ui/pages/modplatform/ResourceModel.cpp @@ -0,0 +1,258 @@ +#include "ResourceModel.h" + +#include +#include +#include +#include +#include + +#include "Application.h" +#include "BuildConfig.h" + +#include "net/Download.h" +#include "net/NetJob.h" + +#include "minecraft/MinecraftInstance.h" +#include "minecraft/PackProfile.h" + +#include "modplatform/ModIndex.h" + +#include "ui/pages/modplatform/ResourcePage.h" +#include "ui/widgets/ProjectItem.h" + +QHash ResourceModel::s_running_models; + +ResourceModel::ResourceModel(ResourcePage* parent, ResourceAPI* api) : QAbstractListModel(), m_api(api), m_associated_page(parent) +{ + s_running_models.insert(this, true); +} + +ResourceModel::~ResourceModel() +{ + s_running_models.find(this).value() = false; +} + +auto ResourceModel::data(const QModelIndex& index, int role) const -> QVariant +{ + int pos = index.row(); + if (pos >= m_packs.size() || pos < 0 || !index.isValid()) { + return QString("INVALID INDEX %1").arg(pos); + } + + auto pack = m_packs.at(pos); + switch (role) { + case 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; + } + case Qt::DecorationRole: { + if (auto icon_or_none = const_cast(this)->getIcon(const_cast(index), pack.logoUrl); + icon_or_none.has_value()) + return icon_or_none.value(); + + return APPLICATION->getThemedIcon("screenshot-placeholder"); + } + case Qt::SizeHintRole: + return QSize(0, 58); + case Qt::UserRole: { + QVariant v; + v.setValue(pack); + return v; + } + // Custom data + case UserDataTypes::TITLE: + return pack.name; + case UserDataTypes::DESCRIPTION: + return pack.description; + case UserDataTypes::SELECTED: + return isPackSelected(pack); + default: + break; + } + + return {}; +} + +bool ResourceModel::setData(const QModelIndex& index, const QVariant& value, int role) +{ + int pos = index.row(); + if (pos >= m_packs.size() || pos < 0 || !index.isValid()) + return false; + + m_packs[pos] = value.value(); + + return true; +} + +QString ResourceModel::debugName() const +{ + return m_associated_page->debugName() + " (Model)"; +} + +void ResourceModel::fetchMore(const QModelIndex& parent) +{ + if (parent.isValid()) + return; + + Q_ASSERT(m_next_search_offset != 0); + + search(); +} + +void ResourceModel::search() +{ + if (!m_current_job.isRunning()) + m_current_job.clear(); + + auto args{ createSearchArguments() }; + + auto callbacks{ createSearchCallbacks() }; + Q_ASSERT(callbacks.on_succeed); + + // Use defaults if no callbacks are set + if (!callbacks.on_fail) + callbacks.on_fail = [this](QString reason, int network_error_code) { + if (!s_running_models.constFind(this).value()) + return; + searchRequestFailed(reason, network_error_code); + }; + if (!callbacks.on_abort) + callbacks.on_abort = [this] { + if (!s_running_models.constFind(this).value()) + return; + searchRequestAborted(); + }; + + if (auto job = m_api->searchProjects(std::move(args), std::move(callbacks)); job) + addActiveJob(job); +} + +void ResourceModel::loadEntry(QModelIndex& entry) +{ + auto const& pack = m_packs[entry.row()]; + + if (!m_current_job.isRunning()) + m_current_job.clear(); + + if (!pack.versionsLoaded) { + auto args{ createVersionsArguments(entry) }; + auto callbacks{ createVersionsCallbacks(entry) }; + + if (auto job = m_api->getProjectVersions(std::move(args), std::move(callbacks)); job) + addActiveJob(job); + } + + if (!pack.extraDataLoaded) { + auto args{ createInfoArguments(entry) }; + auto callbacks{ createInfoCallbacks(entry) }; + + if (auto job = m_api->getProjectInfo(std::move(args), std::move(callbacks)); job) + addActiveJob(job); + } +} + +void ResourceModel::refresh() +{ + if (m_current_job.isRunning()) { + m_current_job.abort(); + m_search_state = SearchState::ResetRequested; + return; + } + + clearData(); + m_search_state = SearchState::None; + + m_next_search_offset = 0; + search(); +} + +void ResourceModel::clearData() +{ + beginResetModel(); + m_packs.clear(); + endResetModel(); +} + +std::optional ResourceModel::getIcon(QModelIndex& index, const QUrl& url) +{ + QPixmap pixmap; + if (QPixmapCache::find(url.toString(), &pixmap)) + return { pixmap }; + + if (!m_current_icon_job) + m_current_icon_job = new NetJob("IconJob", APPLICATION->network()); + + if (m_currently_running_icon_actions.contains(url)) + return {}; + if (m_failed_icon_actions.contains(url)) + return {}; + + auto cache_entry = APPLICATION->metacache()->resolveEntry( + m_associated_page->metaEntryBase(), + QString("logos/%1").arg(QString(QCryptographicHash::hash(url.toEncoded(), QCryptographicHash::Algorithm::Sha1).toHex()))); + auto icon_fetch_action = Net::Download::makeCached(url, cache_entry); + + auto full_file_path = cache_entry->getFullPath(); + connect(icon_fetch_action.get(), &NetAction::succeeded, this, [=] { + auto icon = QIcon(full_file_path); + QPixmapCache::insert(url.toString(), icon.pixmap(icon.actualSize({ 64, 64 }))); + + m_currently_running_icon_actions.remove(url); + + emit dataChanged(index, index, { Qt::DecorationRole }); + }); + connect(icon_fetch_action.get(), &NetAction::failed, this, [=] { + m_currently_running_icon_actions.remove(url); + m_failed_icon_actions.insert(url); + }); + + m_currently_running_icon_actions.insert(url); + + m_current_icon_job->addNetAction(icon_fetch_action); + if (!m_current_icon_job->isRunning()) + QMetaObject::invokeMethod(m_current_icon_job.get(), &NetJob::start); + + return {}; +} + +bool ResourceModel::isPackSelected(const ModPlatform::IndexedPack& pack) const +{ + return m_associated_page->isPackSelected(pack); +} + +void ResourceModel::searchRequestFailed(QString reason, int network_error_code) +{ + switch (network_error_code) { + default: + // Network error + QMessageBox::critical(nullptr, tr("Error"), tr("A network error occurred. Could not load mods.")); + break; + case 409: + // 409 Gone, notify user to update + QMessageBox::critical(nullptr, tr("Error"), + //: %1 refers to the launcher itself + QString("%1 %2") + .arg(m_associated_page->displayName()) + .arg(tr("API version too old!\nPlease update %1!").arg(BuildConfig.LAUNCHER_DISPLAYNAME))); + break; + } + + m_search_state = SearchState::Finished; +} + +void ResourceModel::searchRequestAborted() +{ + if (m_search_state != SearchState::ResetRequested) + qCritical() << "Search task in" << debugName() << "aborted by an unknown reason!"; + + // Retry fetching + clearData(); + + m_next_search_offset = 0; + search(); +} diff --git a/launcher/ui/pages/modplatform/ResourceModel.h b/launcher/ui/pages/modplatform/ResourceModel.h new file mode 100644 index 00000000..af0e9f55 --- /dev/null +++ b/launcher/ui/pages/modplatform/ResourceModel.h @@ -0,0 +1,101 @@ +#pragma once + +#include + +#include + +#include "QObjectPtr.h" +#include "modplatform/ResourceAPI.h" +#include "tasks/ConcurrentTask.h" + +class NetJob; +class ResourcePage; +class ResourceAPI; + +namespace ModPlatform { +struct IndexedPack; +} + + +class ResourceModel : public QAbstractListModel { + Q_OBJECT + + public: + ResourceModel(ResourcePage* parent, ResourceAPI* api); + ~ResourceModel() override; + + [[nodiscard]] auto data(const QModelIndex&, int role) const -> QVariant override; + bool setData(const QModelIndex& index, const QVariant& value, int role) override; + + [[nodiscard]] auto debugName() const -> QString; + + [[nodiscard]] inline int rowCount(const QModelIndex& parent) const override { return parent.isValid() ? 0 : m_packs.size(); } + [[nodiscard]] inline int columnCount(const QModelIndex& parent) const override { return parent.isValid() ? 0 : 1; }; + [[nodiscard]] inline auto flags(const QModelIndex& index) const -> Qt::ItemFlags override { return QAbstractListModel::flags(index); }; + + inline void addActiveJob(Task::Ptr ptr) { m_current_job.addTask(ptr); if (!m_current_job.isRunning()) m_current_job.start(); } + inline Task const& activeJob() { return m_current_job; } + + public slots: + void fetchMore(const QModelIndex& parent) override; + [[nodiscard]] inline bool canFetchMore(const QModelIndex& parent) const override + { + return parent.isValid() ? false : m_search_state == SearchState::CanFetchMore; + } + + void setSearchTerm(QString term) { m_search_term = term; } + + virtual ResourceAPI::SearchArgs createSearchArguments() = 0; + virtual ResourceAPI::SearchCallbacks createSearchCallbacks() = 0; + + virtual ResourceAPI::VersionSearchArgs createVersionsArguments(QModelIndex&) = 0; + virtual ResourceAPI::VersionSearchCallbacks createVersionsCallbacks(QModelIndex&) = 0; + + virtual ResourceAPI::ProjectInfoArgs createInfoArguments(QModelIndex&) = 0; + virtual ResourceAPI::ProjectInfoCallbacks createInfoCallbacks(QModelIndex&) = 0; + + /** Requests the API for more entries. */ + virtual void search(); + + /** Applies any processing / extra requests needed to fully load the specified entry's information. */ + virtual void loadEntry(QModelIndex&); + + /** Schedule a refresh, clearing the current state. */ + void refresh(); + + /** Gets the icon at the URL for the given index. If it's not fetched yet, fetch it and update when fisinhed. */ + std::optional getIcon(QModelIndex&, const QUrl&); + + protected: + /** Resets the model's data. */ + void clearData(); + + [[nodiscard]] bool isPackSelected(const ModPlatform::IndexedPack&) const; + + protected: + /* Basic search parameters */ + enum class SearchState { None, CanFetchMore, ResetRequested, Finished } m_search_state = SearchState::None; + int m_next_search_offset = 0; + QString m_search_term; + + std::unique_ptr m_api; + + ConcurrentTask m_current_job; + + shared_qobject_ptr m_current_icon_job; + QSet m_currently_running_icon_actions; + QSet m_failed_icon_actions; + + ResourcePage* m_associated_page = nullptr; + + QList m_packs; + + // HACK: We need this to prevent callbacks from calling the model after it has already been deleted. + // This leaks a tiny bit of memory per time the user has opened a resource dialog. How to make this better? + static QHash s_running_models; + + private: + /* Default search request callbacks */ + void searchRequestFailed(QString reason, int network_error_code); + void searchRequestAborted(); +}; diff --git a/launcher/ui/pages/modplatform/ResourcePage.cpp b/launcher/ui/pages/modplatform/ResourcePage.cpp new file mode 100644 index 00000000..3b382d20 --- /dev/null +++ b/launcher/ui/pages/modplatform/ResourcePage.cpp @@ -0,0 +1,347 @@ +#include "ResourcePage.h" +#include "ui_ResourcePage.h" + +#include +#include + +#include "Markdown.h" +#include "ResourceDownloadTask.h" + +#include "minecraft/MinecraftInstance.h" + +#include "ui/dialogs/ResourceDownloadDialog.h" +#include "ui/pages/modplatform/ResourceModel.h" +#include "ui/widgets/ProjectItem.h" + +ResourcePage::ResourcePage(ResourceDownloadDialog* parent, BaseInstance& base_instance) + : QWidget(parent), m_base_instance(base_instance), m_ui(new Ui::ResourcePage), m_parent_dialog(parent), m_fetch_progress(this, false) +{ + m_ui->setupUi(this); + + m_ui->searchEdit->installEventFilter(this); + + m_ui->versionSelectionBox->view()->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); + m_ui->versionSelectionBox->view()->parentWidget()->setMaximumHeight(300); + + m_search_timer.setTimerType(Qt::TimerType::CoarseTimer); + m_search_timer.setSingleShot(true); + + connect(&m_search_timer, &QTimer::timeout, this, &ResourcePage::triggerSearch); + + m_fetch_progress.hideIfInactive(true); + m_fetch_progress.setFixedHeight(24); + m_fetch_progress.progressFormat(""); + + m_ui->gridLayout_3->addWidget(&m_fetch_progress, 0, 0, 1, m_ui->gridLayout_3->columnCount()); + + m_ui->packView->setItemDelegate(new ProjectItemDelegate(this)); + m_ui->packView->installEventFilter(this); + + connect(m_ui->packDescription, &QTextBrowser::anchorClicked, this, &ResourcePage::openUrl); +} + +ResourcePage::~ResourcePage() +{ + delete m_ui; +} + +void ResourcePage::retranslate() +{ + m_ui->retranslateUi(this); +} + +void ResourcePage::openedImpl() +{ + if (!supportsFiltering()) + m_ui->resourceFilterButton->setVisible(false); + + updateSelectionButton(); + triggerSearch(); +} + +auto ResourcePage::eventFilter(QObject* watched, QEvent* event) -> bool +{ + if (event->type() == QEvent::KeyPress) { + auto* keyEvent = static_cast(event); + if (watched == m_ui->searchEdit) { + if (keyEvent->key() == Qt::Key_Return) { + triggerSearch(); + keyEvent->accept(); + return true; + } else { + if (m_search_timer.isActive()) + m_search_timer.stop(); + + m_search_timer.start(350); + } + } else if (watched == m_ui->packView) { + if (keyEvent->key() == Qt::Key_Return) { + onResourceSelected(); + + // To have the 'select mod' button outlined instead of the 'review and confirm' one + m_ui->resourceSelectionButton->setFocus(Qt::FocusReason::ShortcutFocusReason); + m_ui->packView->setFocus(Qt::FocusReason::NoFocusReason); + + keyEvent->accept(); + return true; + } + } + } + + return QWidget::eventFilter(watched, event); +} + +QString ResourcePage::getSearchTerm() const +{ + return m_ui->searchEdit->text(); +} + +void ResourcePage::setSearchTerm(QString term) +{ + m_ui->searchEdit->setText(term); +} + +ModPlatform::IndexedPack ResourcePage::getCurrentPack() const +{ + return m_model->data(m_ui->packView->currentIndex(), Qt::UserRole).value(); +} + +bool ResourcePage::isPackSelected(const ModPlatform::IndexedPack& pack, int version) const +{ + if (version < 0 || !pack.versionsLoaded) + return m_parent_dialog->isSelected(pack.name); + + return m_parent_dialog->isSelected(pack.name, pack.versions[version].fileName); +} + +void ResourcePage::updateUi() +{ + auto current_pack = getCurrentPack(); + + QString text = ""; + QString name = current_pack.name; + + if (current_pack.websiteUrl.isEmpty()) + text = name; + else + text = "" + name + ""; + + if (!current_pack.authors.empty()) { + auto authorToStr = [](ModPlatform::ModpackAuthor& author) -> QString { + if (author.url.isEmpty()) { + return author.name; + } + return QString("%2").arg(author.url, author.name); + }; + QStringList authorStrs; + for (auto& author : current_pack.authors) { + authorStrs.push_back(authorToStr(author)); + } + text += "
" + tr(" by ") + authorStrs.join(", "); + } + + if (current_pack.extraDataLoaded) { + if (!current_pack.extraData.donate.isEmpty()) { + text += "

" + tr("Donate information: "); + auto donateToStr = [](ModPlatform::DonationData& donate) -> QString { + return QString("%2").arg(donate.url, donate.platform); + }; + QStringList donates; + for (auto& donate : current_pack.extraData.donate) { + donates.append(donateToStr(donate)); + } + text += donates.join(", "); + } + + if (!current_pack.extraData.issuesUrl.isEmpty() || !current_pack.extraData.sourceUrl.isEmpty() || + !current_pack.extraData.wikiUrl.isEmpty() || !current_pack.extraData.discordUrl.isEmpty()) { + text += "

" + tr("External links:") + "
"; + } + + if (!current_pack.extraData.issuesUrl.isEmpty()) + text += "- " + tr("Issues: %1").arg(current_pack.extraData.issuesUrl) + "
"; + if (!current_pack.extraData.wikiUrl.isEmpty()) + text += "- " + tr("Wiki: %1").arg(current_pack.extraData.wikiUrl) + "
"; + if (!current_pack.extraData.sourceUrl.isEmpty()) + text += "- " + tr("Source code: %1").arg(current_pack.extraData.sourceUrl) + "
"; + if (!current_pack.extraData.discordUrl.isEmpty()) + text += "- " + tr("Discord: %1").arg(current_pack.extraData.discordUrl) + "
"; + } + + text += "
"; + + m_ui->packDescription->setHtml( + text + (current_pack.extraData.body.isEmpty() ? current_pack.description : markdownToHTML(current_pack.extraData.body))); + m_ui->packDescription->flush(); +} + +void ResourcePage::updateSelectionButton() +{ + if (!isOpened || m_selected_version_index < 0) { + m_ui->resourceSelectionButton->setEnabled(false); + return; + } + + m_ui->resourceSelectionButton->setEnabled(true); + if (!isPackSelected(getCurrentPack(), m_selected_version_index)) { + m_ui->resourceSelectionButton->setText(tr("Select %1 for download").arg(resourceString())); + } else { + m_ui->resourceSelectionButton->setText(tr("Deselect %1 for download").arg(resourceString())); + } +} + +void ResourcePage::updateVersionList() +{ + auto current_pack = getCurrentPack(); + + m_ui->versionSelectionBox->blockSignals(true); + m_ui->versionSelectionBox->clear(); + m_ui->versionSelectionBox->blockSignals(false); + + for (int i = 0; i < current_pack.versions.size(); i++) { + auto& version = current_pack.versions[i]; + if (optedOut(version)) + continue; + + m_ui->versionSelectionBox->addItem(current_pack.versions[i].version, QVariant(i)); + } + + if (m_ui->versionSelectionBox->count() == 0) { + m_ui->versionSelectionBox->addItem(tr("No valid version found."), QVariant(-1)); + m_ui->resourceSelectionButton->setText(tr("Cannot select invalid version :(")); + } + + updateSelectionButton(); +} + +void ResourcePage::onSelectionChanged(QModelIndex curr, QModelIndex prev) +{ + if (!curr.isValid()) { + return; + } + + auto current_pack = getCurrentPack(); + + bool request_load = false; + if (!current_pack.versionsLoaded) { + m_ui->resourceSelectionButton->setText(tr("Loading versions...")); + m_ui->resourceSelectionButton->setEnabled(false); + + request_load = true; + } else { + updateVersionList(); + } + + if (!current_pack.extraDataLoaded) + request_load = true; + + if (request_load) + m_model->loadEntry(curr); + + updateUi(); +} + +void ResourcePage::onVersionSelectionChanged(QString data) +{ + if (data.isNull() || data.isEmpty()) { + m_selected_version_index = -1; + return; + } + + m_selected_version_index = m_ui->versionSelectionBox->currentData().toInt(); + updateSelectionButton(); +} + +void ResourcePage::addResourceToDialog(ModPlatform::IndexedPack& pack, ModPlatform::IndexedVersion& version) +{ + m_parent_dialog->addResource(pack.name, new ResourceDownloadTask(pack, version, m_parent_dialog->getBaseModel())); +} + +void ResourcePage::removeResourceFromDialog(ModPlatform::IndexedPack& pack, ModPlatform::IndexedVersion&) +{ + m_parent_dialog->removeResource(pack.name); +} + +void ResourcePage::onResourceSelected() +{ + if (m_selected_version_index < 0) + return; + + auto current_pack = getCurrentPack(); + + auto& version = current_pack.versions[m_selected_version_index]; + if (m_parent_dialog->isSelected(current_pack.name, version.fileName)) + removeResourceFromDialog(current_pack, version); + else + addResourceToDialog(current_pack, version); + + updateSelectionButton(); + + /* Force redraw on the resource list when the selection changes */ + m_ui->packView->adjustSize(); +} + +void ResourcePage::openUrl(const QUrl& url) +{ + // do not allow other url schemes for security reasons + if (!(url.scheme() == "http" || url.scheme() == "https")) { + qWarning() << "Unsupported scheme" << url.scheme(); + return; + } + + // detect URLs and search instead + + const QString address = url.host() + url.path(); + QRegularExpressionMatch match; + QString page; + + for (auto&& [regex, candidate] : urlHandlers().asKeyValueRange()) { + if (match = QRegularExpression(regex).match(address); match.hasMatch()) { + page = candidate; + break; + } + } + + if (!page.isNull()) { + const QString slug = match.captured(1); + + // ensure the user isn't opening the same mod + if (slug != getCurrentPack().slug) { + m_parent_dialog->selectPage(page); + + auto newPage = m_parent_dialog->getSelectedPage(); + + QLineEdit* searchEdit = newPage->m_ui->searchEdit; + auto model = newPage->m_model; + QListView* view = newPage->m_ui->packView; + + auto jump = [url, slug, model, view] { + for (int row = 0; row < model->rowCount({}); row++) { + const QModelIndex index = model->index(row); + const auto pack = model->data(index, Qt::UserRole).value(); + + if (pack.slug == slug) { + view->setCurrentIndex(index); + return; + } + } + + // The final fallback. + QDesktopServices::openUrl(url); + }; + + searchEdit->setText(slug); + newPage->triggerSearch(); + + if (model->activeJob().isRunning()) + connect(&model->activeJob(), &Task::finished, jump); + else + jump(); + + return; + } + } + + // open in the user's web browser + QDesktopServices::openUrl(url); +} diff --git a/launcher/ui/pages/modplatform/ResourcePage.h b/launcher/ui/pages/modplatform/ResourcePage.h new file mode 100644 index 00000000..32aad3d9 --- /dev/null +++ b/launcher/ui/pages/modplatform/ResourcePage.h @@ -0,0 +1,95 @@ +#pragma once + +#include +#include + +#include "modplatform/ModIndex.h" +#include "modplatform/ResourceAPI.h" + +#include "ui/pages/BasePage.h" +#include "ui/widgets/ProgressWidget.h" + +namespace Ui { +class ResourcePage; +} + +class BaseInstance; +class ResourceModel; +class ResourceDownloadDialog; + +class ResourcePage : public QWidget, public BasePage { + Q_OBJECT + public: + ~ResourcePage() override; + + /* Affects what the user sees */ + [[nodiscard]] auto displayName() const -> QString override = 0; + [[nodiscard]] auto icon() const -> QIcon override = 0; + [[nodiscard]] auto id() const -> QString override = 0; + [[nodiscard]] auto helpPage() const -> QString override = 0; + [[nodiscard]] bool shouldDisplay() const override = 0; + + /* Used internally */ + [[nodiscard]] virtual auto metaEntryBase() const -> QString = 0; + [[nodiscard]] virtual auto debugName() const -> QString = 0; + + [[nodiscard]] virtual inline QString resourceString() const { return tr("resource"); } + + /* Features this resource's page supports */ + [[nodiscard]] virtual bool supportsFiltering() const = 0; + + void retranslate() override; + void openedImpl() override; + auto eventFilter(QObject* watched, QEvent* event) -> bool override; + + /** Get the current term in the search bar. */ + [[nodiscard]] auto getSearchTerm() const -> QString; + /** Programatically set the term in the search bar. */ + void setSearchTerm(QString); + + [[nodiscard]] bool isPackSelected(const ModPlatform::IndexedPack&, int version = -1) const; + [[nodiscard]] auto getCurrentPack() const -> ModPlatform::IndexedPack; + + [[nodiscard]] auto getDialog() const -> const ResourceDownloadDialog* { return m_parent_dialog; } + + protected: + ResourcePage(ResourceDownloadDialog* parent, BaseInstance&); + + public slots: + virtual void updateUi(); + virtual void updateSelectionButton(); + virtual void updateVersionList(); + + virtual void addResourceToDialog(ModPlatform::IndexedPack&, ModPlatform::IndexedVersion&); + virtual void removeResourceFromDialog(ModPlatform::IndexedPack&, ModPlatform::IndexedVersion&); + + protected slots: + virtual void triggerSearch() {} + + void onSelectionChanged(QModelIndex first, QModelIndex second); + void onVersionSelectionChanged(QString data); + void onResourceSelected(); + + /** Associates regex expressions to pages in the order they're given in the map. */ + [[nodiscard]] virtual QMap urlHandlers() const = 0; + virtual void openUrl(const QUrl&); + + /** Whether the version is opted out or not. Currently only makes sense in CF. */ + virtual bool optedOut(ModPlatform::IndexedVersion& ver) const { return false; }; + + public: + BaseInstance& m_base_instance; + + protected: + Ui::ResourcePage* m_ui; + + ResourceDownloadDialog* m_parent_dialog = nullptr; + ResourceModel* m_model = nullptr; + + int m_selected_version_index = -1; + + ProgressWidget m_fetch_progress; + + // Used to do instant searching with a delay to cache quick changes + QTimer m_search_timer; +}; diff --git a/launcher/ui/pages/modplatform/ResourcePage.ui b/launcher/ui/pages/modplatform/ResourcePage.ui new file mode 100644 index 00000000..8fe1d613 --- /dev/null +++ b/launcher/ui/pages/modplatform/ResourcePage.ui @@ -0,0 +1,118 @@ + + + ResourcePage + + + + 0 + 0 + 837 + 685 + + + + + + + + + false + + + false + + + + + + + Qt::ScrollBarAlwaysOff + + + true + + + + 48 + 48 + + + + + + + + + + Search + + + + + + + Search for resources... + + + + + + + + + + + + Version selected: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + + Select resource for download + + + + + + + + + Filter options + + + + + + + Qt::Vertical + + + + + + + + ProjectDescriptionPage + QTextBrowser +
ui/widgets/ProjectDescriptionPage.h
+
+
+ + searchEdit + searchButton + packView + packDescription + sortByBox + versionSelectionBox + + + +
diff --git a/launcher/ui/pages/modplatform/flame/FlameModModel.cpp b/launcher/ui/pages/modplatform/flame/FlameModModel.cpp deleted file mode 100644 index bc2c686c..00000000 --- a/launcher/ui/pages/modplatform/flame/FlameModModel.cpp +++ /dev/null @@ -1,31 +0,0 @@ -#include "FlameModModel.h" -#include "Json.h" -#include "modplatform/flame/FlameModIndex.h" - -namespace FlameMod { - -// NOLINTNEXTLINE(modernize-avoid-c-arrays) -const char* ListModel::sorts[6]{ "Featured", "Popularity", "LastUpdated", "Name", "Author", "TotalDownloads" }; - -void ListModel::loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj) -{ - FlameMod::loadIndexedPack(m, obj); -} - -// We already deal with the URLs when initializing the pack, due to the API response's structure -void ListModel::loadExtraPackInfo(ModPlatform::IndexedPack& m, QJsonObject& obj) -{ - FlameMod::loadBody(m, obj); -} - -void ListModel::loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr) -{ - FlameMod::loadIndexedPackVersions(m, arr, APPLICATION->network(), m_parent->m_instance); -} - -auto ListModel::documentToArray(QJsonDocument& obj) const -> QJsonArray -{ - return Json::ensureArray(obj.object(), "data"); -} - -} // namespace FlameMod diff --git a/launcher/ui/pages/modplatform/flame/FlameModModel.h b/launcher/ui/pages/modplatform/flame/FlameModModel.h deleted file mode 100644 index 6a6aef2e..00000000 --- a/launcher/ui/pages/modplatform/flame/FlameModModel.h +++ /dev/null @@ -1,26 +0,0 @@ -#pragma once - -#include "FlameModPage.h" - -namespace FlameMod { - -class ListModel : public ModPlatform::ListModel { - Q_OBJECT - - public: - ListModel(FlameModPage* parent) : ModPlatform::ListModel(parent) {} - ~ListModel() override = default; - - private: - void loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj) override; - void loadExtraPackInfo(ModPlatform::IndexedPack& m, QJsonObject& obj) override; - void loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr) override; - - auto documentToArray(QJsonDocument& obj) const -> QJsonArray override; - - // NOLINTNEXTLINE(modernize-avoid-c-arrays) - static const char* sorts[6]; - inline auto getSorts() const -> const char** override { return sorts; }; -}; - -} // namespace FlameMod diff --git a/launcher/ui/pages/modplatform/flame/FlameModPage.cpp b/launcher/ui/pages/modplatform/flame/FlameModPage.cpp deleted file mode 100644 index bad78c97..00000000 --- a/launcher/ui/pages/modplatform/flame/FlameModPage.cpp +++ /dev/null @@ -1,97 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-only -/* - * Prism Launcher - Minecraft Launcher - * Copyright (C) 2022 Sefa Eyeoglu - * Copyright (C) 2022 TheKodeToad - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, version 3. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * This file incorporates work covered by the following copyright and - * permission notice: - * - * Copyright 2013-2021 MultiMC Contributors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "FlameModPage.h" -#include "ui_ModPage.h" - -#include "FlameModModel.h" -#include "ui/dialogs/ModDownloadDialog.h" - -FlameModPage::FlameModPage(ModDownloadDialog* dialog, BaseInstance* instance) - : ModPage(dialog, instance, new FlameAPI()) -{ - listModel = new FlameMod::ListModel(this); - ui->packView->setModel(listModel); - - // 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")); - - // sometimes Qt just ignores virtual slots and doesn't work as intended it seems, - // so it's best not to connect them in the parent's contructor... - 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); - connect(ui->modSelectionButton, &QPushButton::clicked, this, &FlameModPage::onModSelected); - - ui->packDescription->setMetaEntry(metaEntryBase()); -} - -auto FlameModPage::validateVersion(ModPlatform::IndexedVersion& ver, QString mineVer, ModAPI::ModLoaderTypes loaders) const -> bool -{ - Q_UNUSED(loaders); - return ver.mcVersion.contains(mineVer) && !ver.downloadUrl.isEmpty(); -} - -bool FlameModPage::optedOut(ModPlatform::IndexedVersion& ver) const -{ - return ver.downloadUrl.isEmpty(); -} - -// I don't know why, but doing this on the parent class makes it so that -// other mod providers start loading before being selected, at least with -// my Qt, so we need to implement this in every derived class... -auto FlameModPage::shouldDisplay() const -> bool { return true; } - -void FlameModPage::openUrl(const QUrl& url) -{ - if (url.scheme().isEmpty()) { - QString query = url.query(QUrl::FullyDecoded); - - if (query.startsWith("remoteUrl=")) { - // attempt to resolve url from warning page - query.remove(0, 10); - ModPage::openUrl({QUrl::fromPercentEncoding(query.toUtf8())}); // double decoding is necessary - return; - } - } - - ModPage::openUrl(url); -} diff --git a/launcher/ui/pages/modplatform/flame/FlameModPage.h b/launcher/ui/pages/modplatform/flame/FlameModPage.h deleted file mode 100644 index 58479ab9..00000000 --- a/launcher/ui/pages/modplatform/flame/FlameModPage.h +++ /dev/null @@ -1,70 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-only -/* - * Prism Launcher - Minecraft Launcher - * Copyright (C) 2022 Sefa Eyeoglu - * Copyright (C) 2022 TheKodeToad - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, version 3. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * This file incorporates work covered by the following copyright and - * permission notice: - * - * Copyright 2013-2021 MultiMC Contributors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include "modplatform/ModAPI.h" -#include "ui/pages/modplatform/ModPage.h" - -#include "modplatform/flame/FlameAPI.h" - -class FlameModPage : public ModPage { - Q_OBJECT - - public: - static FlameModPage* create(ModDownloadDialog* dialog, BaseInstance* instance) - { - return ModPage::create(dialog, instance); - } - - FlameModPage(ModDownloadDialog* dialog, BaseInstance* instance); - ~FlameModPage() override = default; - - inline auto displayName() const -> QString override { return "CurseForge"; } - inline auto icon() const -> QIcon override { return APPLICATION->getThemedIcon("flame"); } - inline auto id() const -> QString override { return "curseforge"; } - inline auto helpPage() const -> QString override { return "Mod-platform"; } - - inline auto debugName() const -> QString override { return "Flame"; } - inline auto metaEntryBase() const -> QString override { return "FlameMods"; }; - - auto validateVersion(ModPlatform::IndexedVersion& ver, QString mineVer, ModAPI::ModLoaderTypes loaders = ModAPI::Unspecified) const -> bool override; - bool optedOut(ModPlatform::IndexedVersion& ver) const override; - - auto shouldDisplay() const -> bool override; - - void openUrl(const QUrl& url) override; -}; diff --git a/launcher/ui/pages/modplatform/flame/FlameResourceModels.cpp b/launcher/ui/pages/modplatform/flame/FlameResourceModels.cpp new file mode 100644 index 00000000..b602dfac --- /dev/null +++ b/launcher/ui/pages/modplatform/flame/FlameResourceModels.cpp @@ -0,0 +1,31 @@ +#include "FlameResourceModels.h" +#include "Json.h" +#include "modplatform/flame/FlameModIndex.h" + +namespace FlameMod { + +// NOLINTNEXTLINE(modernize-avoid-c-arrays) +const char* ListModel::sorts[6]{ "Featured", "Popularity", "LastUpdated", "Name", "Author", "TotalDownloads" }; + +void ListModel::loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj) +{ + FlameMod::loadIndexedPack(m, obj); +} + +// We already deal with the URLs when initializing the pack, due to the API response's structure +void ListModel::loadExtraPackInfo(ModPlatform::IndexedPack& m, QJsonObject& obj) +{ + FlameMod::loadBody(m, obj); +} + +void ListModel::loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr) +{ + FlameMod::loadIndexedPackVersions(m, arr, APPLICATION->network(), &m_associated_page->m_base_instance); +} + +auto ListModel::documentToArray(QJsonDocument& obj) const -> QJsonArray +{ + return Json::ensureArray(obj.object(), "data"); +} + +} // namespace FlameMod diff --git a/launcher/ui/pages/modplatform/flame/FlameResourceModels.h b/launcher/ui/pages/modplatform/flame/FlameResourceModels.h new file mode 100644 index 00000000..b94377d3 --- /dev/null +++ b/launcher/ui/pages/modplatform/flame/FlameResourceModels.h @@ -0,0 +1,26 @@ +#pragma once + +#include "modplatform/flame/FlameAPI.h" + +namespace FlameMod { + +class ListModel : public ModPlatform::ListModel { + Q_OBJECT + + public: + ListModel(FlameModPage* parent) : ModPlatform::ListModel(parent, new FlameAPI) {} + ~ListModel() override = default; + + private: + void loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj) override; + void loadExtraPackInfo(ModPlatform::IndexedPack& m, QJsonObject& obj) override; + void loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr) override; + + auto documentToArray(QJsonDocument& obj) const -> QJsonArray override; + + // NOLINTNEXTLINE(modernize-avoid-c-arrays) + static const char* sorts[6]; + inline auto getSorts() const -> const char** override { return sorts; }; +}; + +} // namespace FlameMod diff --git a/launcher/ui/pages/modplatform/flame/FlameResourcePages.cpp b/launcher/ui/pages/modplatform/flame/FlameResourcePages.cpp new file mode 100644 index 00000000..490578ad --- /dev/null +++ b/launcher/ui/pages/modplatform/flame/FlameResourcePages.cpp @@ -0,0 +1,97 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (C) 2022 Sefa Eyeoglu + * Copyright (C) 2022 TheKodeToad + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * This file incorporates work covered by the following copyright and + * permission notice: + * + * Copyright 2013-2021 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "FlameResourcePages.h" +#include "ui_ResourcePage.h" + +#include "FlameResourceModels.h" +#include "ui/dialogs/ModDownloadDialog.h" + +FlameModPage::FlameModPage(ModDownloadDialog* dialog, BaseInstance& instance) + : ModPage(dialog, instance) +{ + m_model = new FlameMod::ListModel(this); + m_ui->packView->setModel(m_model); + + // index is used to set the sorting with the flame api + m_ui->sortByBox->addItem(tr("Sort by Featured")); + m_ui->sortByBox->addItem(tr("Sort by Popularity")); + m_ui->sortByBox->addItem(tr("Sort by Last Updated")); + m_ui->sortByBox->addItem(tr("Sort by Name")); + m_ui->sortByBox->addItem(tr("Sort by Author")); + m_ui->sortByBox->addItem(tr("Sort by Downloads")); + + // sometimes Qt just ignores virtual slots and doesn't work as intended it seems, + // so it's best not to connect them in the parent's contructor... + connect(m_ui->sortByBox, SIGNAL(currentIndexChanged(int)), this, SLOT(triggerSearch())); + connect(m_ui->packView->selectionModel(), &QItemSelectionModel::currentChanged, this, &FlameModPage::onSelectionChanged); + connect(m_ui->versionSelectionBox, &QComboBox::currentTextChanged, this, &FlameModPage::onVersionSelectionChanged); + connect(m_ui->resourceSelectionButton, &QPushButton::clicked, this, &FlameModPage::onResourceSelected); + + m_ui->packDescription->setMetaEntry(metaEntryBase()); +} + +auto FlameModPage::validateVersion(ModPlatform::IndexedVersion& ver, QString mineVer, std::optional loaders) const -> bool +{ + Q_UNUSED(loaders); + return ver.mcVersion.contains(mineVer) && !ver.downloadUrl.isEmpty(); +} + +bool FlameModPage::optedOut(ModPlatform::IndexedVersion& ver) const +{ + return ver.downloadUrl.isEmpty(); +} + +// I don't know why, but doing this on the parent class makes it so that +// other mod providers start loading before being selected, at least with +// my Qt, so we need to implement this in every derived class... +auto FlameModPage::shouldDisplay() const -> bool { return true; } + +void FlameModPage::openUrl(const QUrl& url) +{ + if (url.scheme().isEmpty()) { + QString query = url.query(QUrl::FullyDecoded); + + if (query.startsWith("remoteUrl=")) { + // attempt to resolve url from warning page + query.remove(0, 10); + ModPage::openUrl({QUrl::fromPercentEncoding(query.toUtf8())}); // double decoding is necessary + return; + } + } + + ModPage::openUrl(url); +} diff --git a/launcher/ui/pages/modplatform/flame/FlameResourcePages.h b/launcher/ui/pages/modplatform/flame/FlameResourcePages.h new file mode 100644 index 00000000..597a0c25 --- /dev/null +++ b/launcher/ui/pages/modplatform/flame/FlameResourcePages.h @@ -0,0 +1,71 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (C) 2022 Sefa Eyeoglu + * Copyright (C) 2022 TheKodeToad + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * This file incorporates work covered by the following copyright and + * permission notice: + * + * Copyright 2013-2021 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "Application.h" + +#include "modplatform/ResourceAPI.h" + +#include "ui/pages/modplatform/ModPage.h" + +class FlameModPage : public ModPage { + Q_OBJECT + + public: + static FlameModPage* create(ModDownloadDialog* dialog, BaseInstance& instance) + { + return ModPage::create(dialog, instance); + } + + FlameModPage(ModDownloadDialog* dialog, BaseInstance& instance); + ~FlameModPage() override = default; + + inline auto displayName() const -> QString override { return "CurseForge"; } + inline auto icon() const -> QIcon override { return APPLICATION->getThemedIcon("flame"); } + inline auto id() const -> QString override { return "curseforge"; } + inline auto helpPage() const -> QString override { return "Mod-platform"; } + + inline auto debugName() const -> QString override { return "Flame"; } + inline auto metaEntryBase() const -> QString override { return "FlameMods"; }; + + auto validateVersion(ModPlatform::IndexedVersion& ver, QString mineVer, std::optional loaders = {}) const -> bool override; + bool optedOut(ModPlatform::IndexedVersion& ver) const override; + + auto shouldDisplay() const -> bool override; + + void openUrl(const QUrl& url) override; +}; diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthModModel.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthModModel.cpp deleted file mode 100644 index af92e63e..00000000 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthModModel.cpp +++ /dev/null @@ -1,48 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-only -/* - * PolyMC - Minecraft Launcher - * Copyright (C) 2022 Sefa Eyeoglu - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, version 3. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#include "ModrinthModModel.h" - -#include "modplatform/modrinth/ModrinthPackIndex.h" - -namespace Modrinth { - -// NOLINTNEXTLINE(modernize-avoid-c-arrays) -const char* ListModel::sorts[5]{ "relevance", "downloads", "follows", "updated", "newest" }; - -void ListModel::loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj) -{ - Modrinth::loadIndexedPack(m, obj); -} - -void ListModel::loadExtraPackInfo(ModPlatform::IndexedPack& m, QJsonObject& obj) -{ - Modrinth::loadExtraPackData(m, obj); -} - -void ListModel::loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr) -{ - Modrinth::loadIndexedPackVersions(m, arr, APPLICATION->network(), m_parent->m_instance); -} - -auto ListModel::documentToArray(QJsonDocument& obj) const -> QJsonArray -{ - return obj.object().value("hits").toArray(); -} - -} // namespace Modrinth diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthModModel.h b/launcher/ui/pages/modplatform/modrinth/ModrinthModModel.h deleted file mode 100644 index 386897fd..00000000 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthModModel.h +++ /dev/null @@ -1,44 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-only -/* - * PolyMC - Minecraft Launcher - * Copyright (C) 2022 Sefa Eyeoglu - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, version 3. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#pragma once - -#include "ModrinthModPage.h" - -namespace Modrinth { - -class ListModel : public ModPlatform::ListModel { - Q_OBJECT - - public: - ListModel(ModrinthModPage* parent) : ModPlatform::ListModel(parent){}; - ~ListModel() override = default; - - private: - void loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj) override; - void loadExtraPackInfo(ModPlatform::IndexedPack& m, QJsonObject& obj) override; - void loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr) override; - - auto documentToArray(QJsonDocument& obj) const -> QJsonArray override; - - // NOLINTNEXTLINE(modernize-avoid-c-arrays) - static const char* sorts[5]; - inline auto getSorts() const -> const char** override { return sorts; }; -}; - -} // namespace Modrinth diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthModPage.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthModPage.cpp deleted file mode 100644 index c531ea90..00000000 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthModPage.cpp +++ /dev/null @@ -1,84 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-only -/* - * PolyMC - Minecraft Launcher - * Copyright (C) 2022 Sefa Eyeoglu - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, version 3. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * This file incorporates work covered by the following copyright and - * permission notice: - * - * Copyright 2013-2021 MultiMC Contributors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "ModrinthModPage.h" -#include "modplatform/modrinth/ModrinthAPI.h" -#include "ui_ModPage.h" - -#include "ModrinthModModel.h" -#include "ui/dialogs/ModDownloadDialog.h" - -ModrinthModPage::ModrinthModPage(ModDownloadDialog* dialog, BaseInstance* instance) - : ModPage(dialog, instance, new ModrinthAPI()) -{ - listModel = new Modrinth::ListModel(this); - ui->packView->setModel(listModel); - - // index is used to set the sorting with the modrinth api - ui->sortByBox->addItem(tr("Sort by Relevance")); - ui->sortByBox->addItem(tr("Sort by Downloads")); - ui->sortByBox->addItem(tr("Sort by Follows")); - ui->sortByBox->addItem(tr("Sort by Last Updated")); - ui->sortByBox->addItem(tr("Sort by Newest")); - - // sometimes Qt just ignores virtual slots and doesn't work as intended it seems, - // so it's best not to connect them in the parent's constructor... - connect(ui->sortByBox, SIGNAL(currentIndexChanged(int)), this, SLOT(triggerSearch())); - connect(ui->packView->selectionModel(), &QItemSelectionModel::currentChanged, this, &ModrinthModPage::onSelectionChanged); - connect(ui->versionSelectionBox, &QComboBox::currentTextChanged, this, &ModrinthModPage::onVersionSelectionChanged); - connect(ui->modSelectionButton, &QPushButton::clicked, this, &ModrinthModPage::onModSelected); - - ui->packDescription->setMetaEntry(metaEntryBase()); -} - -auto ModrinthModPage::validateVersion(ModPlatform::IndexedVersion& ver, QString mineVer, ModAPI::ModLoaderTypes loaders) const -> bool -{ - auto loaderStrings = ModrinthAPI::getModLoaderStrings(loaders); - - auto loaderCompatible = false; - for (auto remoteLoader : ver.loaders) - { - if (loaderStrings.contains(remoteLoader)) { - loaderCompatible = true; - break; - } - } - return ver.mcVersion.contains(mineVer) && loaderCompatible; -} - -// I don't know why, but doing this on the parent class makes it so that -// other mod providers start loading before being selected, at least with -// my Qt, so we need to implement this in every derived class... -auto ModrinthModPage::shouldDisplay() const -> bool { return true; } diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthModPage.h b/launcher/ui/pages/modplatform/modrinth/ModrinthModPage.h deleted file mode 100644 index 40d82e6f..00000000 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthModPage.h +++ /dev/null @@ -1,66 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-only -/* - * PolyMC - Minecraft Launcher - * Copyright (C) 2022 Sefa Eyeoglu - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, version 3. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * This file incorporates work covered by the following copyright and - * permission notice: - * - * Copyright 2013-2021 MultiMC Contributors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include "modplatform/ModAPI.h" -#include "ui/pages/modplatform/ModPage.h" - -#include "modplatform/modrinth/ModrinthAPI.h" - -class ModrinthModPage : public ModPage { - Q_OBJECT - - public: - static ModrinthModPage* create(ModDownloadDialog* dialog, BaseInstance* instance) - { - return ModPage::create(dialog, instance); - } - - ModrinthModPage(ModDownloadDialog* dialog, BaseInstance* instance); - ~ModrinthModPage() override = default; - - inline auto displayName() const -> QString override { return "Modrinth"; } - inline auto icon() const -> QIcon override { return APPLICATION->getThemedIcon("modrinth"); } - inline auto id() const -> QString override { return "modrinth"; } - inline auto helpPage() const -> QString override { return "Mod-platform"; } - - inline auto debugName() const -> QString override { return "Modrinth"; } - inline auto metaEntryBase() const -> QString override { return "ModrinthPacks"; }; - - auto validateVersion(ModPlatform::IndexedVersion& ver, QString mineVer, ModAPI::ModLoaderTypes loaders = ModAPI::Unspecified) const -> bool override; - - auto shouldDisplay() const -> bool override; -}; diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.cpp new file mode 100644 index 00000000..51278546 --- /dev/null +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.cpp @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (C) 2022 Sefa Eyeoglu + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "ModrinthResourceModels.h" + +#include "ui/pages/modplatform/modrinth/ModrinthResourcePages.h" + +#include "modplatform/modrinth/ModrinthAPI.h" +#include "modplatform/modrinth/ModrinthPackIndex.h" + +namespace Modrinth { + +// NOLINTNEXTLINE(modernize-avoid-c-arrays) +const char* ListModel::sorts[5]{ "relevance", "downloads", "follows", "updated", "newest" }; + +void ListModel::loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj) +{ + Modrinth::loadIndexedPack(m, obj); +} + +void ListModel::loadExtraPackInfo(ModPlatform::IndexedPack& m, QJsonObject& obj) +{ + Modrinth::loadExtraPackData(m, obj); +} + +void ListModel::loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr) +{ + Modrinth::loadIndexedPackVersions(m, arr, APPLICATION->network(), &m_associated_page->m_base_instance); +} + +auto ListModel::documentToArray(QJsonDocument& obj) const -> QJsonArray +{ + return obj.object().value("hits").toArray(); +} + +} // namespace Modrinth + + diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.h b/launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.h new file mode 100644 index 00000000..bf62d22f --- /dev/null +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.h @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (C) 2022 Sefa Eyeoglu + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once + +#include "ui/pages/modplatform/ModModel.h" + +#include "ui/pages/modplatform/modrinth/ModrinthResourcePages.h" + +#include "modplatform/modrinth/ModrinthAPI.h" + +namespace Modrinth { + +class ListModel : public ModPlatform::ListModel { + Q_OBJECT + + public: + ListModel(ModrinthModPage* parent) : ModPlatform::ListModel(parent, new ModrinthAPI){}; + ~ListModel() override = default; + + private: + void loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj) override; + void loadExtraPackInfo(ModPlatform::IndexedPack& m, QJsonObject& obj) override; + void loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr) override; + + auto documentToArray(QJsonDocument& obj) const -> QJsonArray override; + + // NOLINTNEXTLINE(modernize-avoid-c-arrays) + static const char* sorts[5]; + inline auto getSorts() const -> const char** override { return sorts; }; +}; + +} // namespace Modrinth + diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.cpp new file mode 100644 index 00000000..17f0bc93 --- /dev/null +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.cpp @@ -0,0 +1,89 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (C) 2022 Sefa Eyeoglu + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * This file incorporates work covered by the following copyright and + * permission notice: + * + * Copyright 2013-2021 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "ModrinthResourcePages.h" +#include "ui_ResourcePage.h" + +#include "modplatform/modrinth/ModrinthAPI.h" + +#include "ModrinthResourceModels.h" +#include "ui/dialogs/ModDownloadDialog.h" + +ModrinthModPage::ModrinthModPage(ModDownloadDialog* dialog, BaseInstance& instance) + : ModPage(dialog, instance) +{ + m_model = new Modrinth::ListModel(this); + m_ui->packView->setModel(m_model); + + // index is used to set the sorting with the modrinth api + m_ui->sortByBox->addItem(tr("Sort by Relevance")); + m_ui->sortByBox->addItem(tr("Sort by Downloads")); + m_ui->sortByBox->addItem(tr("Sort by Follows")); + m_ui->sortByBox->addItem(tr("Sort by Last Updated")); + m_ui->sortByBox->addItem(tr("Sort by Newest")); + + // sometimes Qt just ignores virtual slots and doesn't work as intended it seems, + // so it's best not to connect them in the parent's constructor... + connect(m_ui->sortByBox, SIGNAL(currentIndexChanged(int)), this, SLOT(triggerSearch())); + connect(m_ui->packView->selectionModel(), &QItemSelectionModel::currentChanged, this, &ModrinthModPage::onSelectionChanged); + connect(m_ui->versionSelectionBox, &QComboBox::currentTextChanged, this, &ModrinthModPage::onVersionSelectionChanged); + connect(m_ui->resourceSelectionButton, &QPushButton::clicked, this, &ModrinthModPage::onResourceSelected); + + m_ui->packDescription->setMetaEntry(metaEntryBase()); +} + +auto ModrinthModPage::validateVersion(ModPlatform::IndexedVersion& ver, QString mineVer, std::optional loaders) const -> bool +{ + auto loaderCompatible = !loaders.has_value(); + + if (!loaderCompatible) { + auto loaderStrings = ModrinthAPI::getModLoaderStrings(loaders.value()); + for (auto remoteLoader : ver.loaders) + { + if (loaderStrings.contains(remoteLoader)) { + loaderCompatible = true; + break; + } + } + } + + return ver.mcVersion.contains(mineVer) && loaderCompatible; +} + +// I don't know why, but doing this on the parent class makes it so that +// other mod providers start loading before being selected, at least with +// my Qt, so we need to implement this in every derived class... +auto ModrinthModPage::shouldDisplay() const -> bool { return true; } + diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.h b/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.h new file mode 100644 index 00000000..6f816cfd --- /dev/null +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.h @@ -0,0 +1,72 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (C) 2022 Sefa Eyeoglu + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * This file incorporates work covered by the following copyright and + * permission notice: + * + * Copyright 2013-2021 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "Application.h" + +#include "modplatform/ResourceAPI.h" + +#include "ui/pages/modplatform/ModPage.h" + +static inline QString displayName() { return "Modrinth"; } +static inline QIcon icon() { return APPLICATION->getThemedIcon("modrinth"); } +static inline QString id() { return "modrinth"; } +static inline QString debugName() { return "Modrinth"; } +static inline QString metaEntryBase() { return "ModrinthPacks"; }; + +class ModrinthModPage : public ModPage { + Q_OBJECT + + public: + static ModrinthModPage* create(ModDownloadDialog* dialog, BaseInstance& instance) + { + return ModPage::create(dialog, instance); + } + + ModrinthModPage(ModDownloadDialog* dialog, BaseInstance& instance); + ~ModrinthModPage() override = default; + + [[nodiscard]] bool shouldDisplay() const override; + + [[nodiscard]] inline auto displayName() const -> QString override { return ::displayName(); } \ + [[nodiscard]] inline auto icon() const -> QIcon override { return ::icon(); } \ + [[nodiscard]] inline auto id() const -> QString override { return ::id(); } \ + [[nodiscard]] inline auto debugName() const -> QString override { return ::debugName(); } \ + [[nodiscard]] inline auto metaEntryBase() const -> QString override { return ::metaEntryBase(); } + inline auto helpPage() const -> QString override { return "Mod-platform"; } + + auto validateVersion(ModPlatform::IndexedVersion& ver, QString mineVer, std::optional loaders = {}) const -> bool override; +}; diff --git a/launcher/ui/widgets/ProgressWidget.cpp b/launcher/ui/widgets/ProgressWidget.cpp index b60d9a7a..18b51fc3 100644 --- a/launcher/ui/widgets/ProgressWidget.cpp +++ b/launcher/ui/widgets/ProgressWidget.cpp @@ -39,7 +39,7 @@ void ProgressWidget::progressFormat(QString format) m_bar->setFormat(format); } -void ProgressWidget::watch(Task* task) +void ProgressWidget::watch(const Task* task) { if (!task) return; @@ -57,11 +57,11 @@ void ProgressWidget::watch(Task* task) show(); } -void ProgressWidget::start(Task* task) +void ProgressWidget::start(const Task* task) { watch(task); if (!m_task->isRunning()) - QMetaObject::invokeMethod(m_task, "start", Qt::QueuedConnection); + QMetaObject::invokeMethod(const_cast(m_task), "start", Qt::QueuedConnection); } bool ProgressWidget::exec(std::shared_ptr task) diff --git a/launcher/ui/widgets/ProgressWidget.h b/launcher/ui/widgets/ProgressWidget.h index 4d9097b8..b0458f33 100644 --- a/launcher/ui/widgets/ProgressWidget.h +++ b/launcher/ui/widgets/ProgressWidget.h @@ -27,10 +27,10 @@ class ProgressWidget : public QWidget { public slots: /** Watch the progress of a task. */ - void watch(Task* task); + void watch(const Task* task); /** Watch the progress of a task, and start it if needed */ - void start(Task* task); + void start(const Task* task); /** Blocking way of waiting for a task to finish. */ bool exec(std::shared_ptr task); @@ -50,7 +50,7 @@ class ProgressWidget : public QWidget { private: QLabel* m_label = nullptr; QProgressBar* m_bar = nullptr; - Task* m_task = nullptr; + const Task* m_task = nullptr; bool m_hide_if_inactive = false; }; diff --git a/tests/Packwiz_test.cpp b/tests/Packwiz_test.cpp index 098e8f89..29289469 100644 --- a/tests/Packwiz_test.cpp +++ b/tests/Packwiz_test.cpp @@ -48,7 +48,7 @@ class PackwizTest : public QObject { QCOMPARE(metadata.hash_format, "sha512"); QCOMPARE(metadata.hash, "c8fe6e15ddea32668822dddb26e1851e5f03834be4bcb2eff9c0da7fdc086a9b6cead78e31a44d3bc66335cba11144ee0337c6d5346f1ba63623064499b3188d"); - QCOMPARE(metadata.provider, ModPlatform::Provider::MODRINTH); + QCOMPARE(metadata.provider, ModPlatform::ResourceProvider::MODRINTH); QCOMPARE(metadata.version(), "ug2qKTPR"); QCOMPARE(metadata.mod_id(), "kYq5qkSL"); } @@ -76,7 +76,7 @@ class PackwizTest : public QObject { QCOMPARE(metadata.hash_format, "murmur2"); QCOMPARE(metadata.hash, "1781245820"); - QCOMPARE(metadata.provider, ModPlatform::Provider::FLAME); + QCOMPARE(metadata.provider, ModPlatform::ResourceProvider::FLAME); QCOMPARE(metadata.file_id, 3509043); QCOMPARE(metadata.project_id, 327154); } -- cgit From 433a802c6ed3070b1b2f4435937a456eb4192f78 Mon Sep 17 00:00:00 2001 From: flow Date: Fri, 16 Dec 2022 19:03:52 -0300 Subject: refactor: put resource downloading classes in common namespace Puts them all inside the 'ResourceDownload' namespace, so that it's a bit clearer from the outside that those belong to the same 'module'. Signed-off-by: flow --- launcher/ui/dialogs/ModDownloadDialog.cpp | 4 +++ launcher/ui/dialogs/ModDownloadDialog.h | 7 +++-- launcher/ui/dialogs/ResourceDownloadDialog.cpp | 4 +++ launcher/ui/dialogs/ResourceDownloadDialog.h | 7 ++++- launcher/ui/pages/instance/ModFolderPage.cpp | 2 +- launcher/ui/pages/modplatform/ModModel.cpp | 30 +++++++++++----------- launcher/ui/pages/modplatform/ModModel.h | 12 +++++---- launcher/ui/pages/modplatform/ModPage.cpp | 6 ++++- launcher/ui/pages/modplatform/ModPage.h | 8 ++++-- launcher/ui/pages/modplatform/ResourceModel.cpp | 4 +++ launcher/ui/pages/modplatform/ResourceModel.h | 6 ++++- launcher/ui/pages/modplatform/ResourcePage.cpp | 4 +++ launcher/ui/pages/modplatform/ResourcePage.h | 7 ++++- .../modplatform/flame/FlameResourceModels.cpp | 18 ++++++++----- .../pages/modplatform/flame/FlameResourceModels.h | 14 ++++++---- .../pages/modplatform/flame/FlameResourcePages.cpp | 6 ++++- .../pages/modplatform/flame/FlameResourcePages.h | 30 +++++++++++++++------- .../modrinth/ModrinthResourceModels.cpp | 24 ++++++++--------- .../modplatform/modrinth/ModrinthResourceModels.h | 17 +++++------- .../modplatform/modrinth/ModrinthResourcePages.cpp | 8 ++++-- .../modplatform/modrinth/ModrinthResourcePages.h | 19 +++++++++----- 21 files changed, 156 insertions(+), 81 deletions(-) (limited to 'launcher/ui/dialogs') diff --git a/launcher/ui/dialogs/ModDownloadDialog.cpp b/launcher/ui/dialogs/ModDownloadDialog.cpp index 8a77ef7f..89b87300 100644 --- a/launcher/ui/dialogs/ModDownloadDialog.cpp +++ b/launcher/ui/dialogs/ModDownloadDialog.cpp @@ -24,6 +24,8 @@ #include "ui/pages/modplatform/flame/FlameResourcePages.h" #include "ui/pages/modplatform/modrinth/ModrinthResourcePages.h" +namespace ResourceDownload { + ModDownloadDialog::ModDownloadDialog(QWidget* parent, const std::shared_ptr& mods, BaseInstance* instance) : ResourceDownloadDialog(parent, mods), m_instance(instance) { @@ -57,3 +59,5 @@ QList ModDownloadDialog::getPages() return pages; } + +} // namespace ResourceDownload diff --git a/launcher/ui/dialogs/ModDownloadDialog.h b/launcher/ui/dialogs/ModDownloadDialog.h index 19036042..b378b5a9 100644 --- a/launcher/ui/dialogs/ModDownloadDialog.h +++ b/launcher/ui/dialogs/ModDownloadDialog.h @@ -25,8 +25,9 @@ class QDialogButtonBox; -class ModDownloadDialog final : public ResourceDownloadDialog -{ +namespace ResourceDownload { + +class ModDownloadDialog final : public ResourceDownloadDialog { Q_OBJECT public: @@ -45,3 +46,5 @@ class ModDownloadDialog final : public ResourceDownloadDialog private: BaseInstance* m_instance; }; + +} // namespace ResourceDownload diff --git a/launcher/ui/dialogs/ResourceDownloadDialog.cpp b/launcher/ui/dialogs/ResourceDownloadDialog.cpp index 7367548f..b143750b 100644 --- a/launcher/ui/dialogs/ResourceDownloadDialog.cpp +++ b/launcher/ui/dialogs/ResourceDownloadDialog.cpp @@ -9,6 +9,8 @@ #include "ui/pages/modplatform/ResourcePage.h" #include "ui/widgets/PageContainer.h" +namespace ResourceDownload { + ResourceDownloadDialog::ResourceDownloadDialog(QWidget* parent, const std::shared_ptr base_model) : QDialog(parent), m_base_model(base_model), m_buttons(QDialogButtonBox::Help | QDialogButtonBox::Ok | QDialogButtonBox::Cancel), m_vertical_layout(this) { @@ -150,3 +152,5 @@ void ResourceDownloadDialog::selectedPageChanged(BasePage* previous, BasePage* s // Same effect as having a global search bar m_selectedPage->setSearchTerm(prev_page->getSearchTerm()); } + +} // namespace ResourceDownload diff --git a/launcher/ui/dialogs/ResourceDownloadDialog.h b/launcher/ui/dialogs/ResourceDownloadDialog.h index d6b3938b..3b234cd1 100644 --- a/launcher/ui/dialogs/ResourceDownloadDialog.h +++ b/launcher/ui/dialogs/ResourceDownloadDialog.h @@ -7,12 +7,15 @@ #include "ui/pages/BasePageProvider.h" class ResourceDownloadTask; -class ResourcePage; class ResourceFolderModel; class PageContainer; class QVBoxLayout; class QDialogButtonBox; +namespace ResourceDownload { + +class ResourcePage; + class ResourceDownloadDialog : public QDialog, public BasePageProvider { Q_OBJECT @@ -53,3 +56,5 @@ class ResourceDownloadDialog : public QDialog, public BasePageProvider { QHash m_selected; }; + +} // namespace ResourceDownload diff --git a/launcher/ui/pages/instance/ModFolderPage.cpp b/launcher/ui/pages/instance/ModFolderPage.cpp index 1bce3c0d..7c4b8952 100644 --- a/launcher/ui/pages/instance/ModFolderPage.cpp +++ b/launcher/ui/pages/instance/ModFolderPage.cpp @@ -158,7 +158,7 @@ void ModFolderPage::installMods() return; } - ModDownloadDialog mdownload(this, m_model, m_instance); + ResourceDownload::ModDownloadDialog mdownload(this, m_model, m_instance); if (mdownload.exec()) { ConcurrentTask* tasks = new ConcurrentTask(this); connect(tasks, &Task::failed, [this, tasks](QString reason) { diff --git a/launcher/ui/pages/modplatform/ModModel.cpp b/launcher/ui/pages/modplatform/ModModel.cpp index 31aae746..59399c59 100644 --- a/launcher/ui/pages/modplatform/ModModel.cpp +++ b/launcher/ui/pages/modplatform/ModModel.cpp @@ -7,19 +7,19 @@ #include -namespace ModPlatform { +namespace ResourceDownload { -ListModel::ListModel(ModPage* parent, ResourceAPI* api) : ResourceModel(parent, api) {} +ModModel::ModModel(ModPage* parent, ResourceAPI* api) : ResourceModel(parent, api) {} /******** Make data requests ********/ -ResourceAPI::SearchArgs ListModel::createSearchArguments() +ResourceAPI::SearchArgs ModModel::createSearchArguments() { auto profile = static_cast(m_associated_page->m_base_instance).getPackProfile(); return { ModPlatform::ResourceType::MOD, m_next_search_offset, m_search_term, getSorts()[currentSort], profile->getModLoaders(), getMineVersions() }; } -ResourceAPI::SearchCallbacks ListModel::createSearchCallbacks() +ResourceAPI::SearchCallbacks ModModel::createSearchCallbacks() { return { [this](auto& doc) { if (!s_running_models.constFind(this).value()) @@ -28,14 +28,14 @@ ResourceAPI::SearchCallbacks ListModel::createSearchCallbacks() } }; } -ResourceAPI::VersionSearchArgs ListModel::createVersionsArguments(QModelIndex& entry) +ResourceAPI::VersionSearchArgs ModModel::createVersionsArguments(QModelIndex& entry) { auto const& pack = m_packs[entry.row()]; auto profile = static_cast(m_associated_page->m_base_instance).getPackProfile(); return { pack.addonId.toString(), getMineVersions(), profile->getModLoaders() }; } -ResourceAPI::VersionSearchCallbacks ListModel::createVersionsCallbacks(QModelIndex& entry) +ResourceAPI::VersionSearchCallbacks ModModel::createVersionsCallbacks(QModelIndex& entry) { auto const& pack = m_packs[entry.row()]; @@ -46,12 +46,12 @@ ResourceAPI::VersionSearchCallbacks ListModel::createVersionsCallbacks(QModelInd } }; } -ResourceAPI::ProjectInfoArgs ListModel::createInfoArguments(QModelIndex& entry) +ResourceAPI::ProjectInfoArgs ModModel::createInfoArguments(QModelIndex& entry) { auto& pack = m_packs[entry.row()]; return { pack }; } -ResourceAPI::ProjectInfoCallbacks ListModel::createInfoCallbacks(QModelIndex& entry) +ResourceAPI::ProjectInfoCallbacks ModModel::createInfoCallbacks(QModelIndex& entry) { return { [this, entry](auto& doc, auto& pack) { if (!s_running_models.constFind(this).value()) @@ -60,7 +60,7 @@ ResourceAPI::ProjectInfoCallbacks ListModel::createInfoCallbacks(QModelIndex& en } }; } -void ListModel::searchWithTerm(const QString& term, const int sort, const bool filter_changed) +void ModModel::searchWithTerm(const QString& term, const int sort, const bool filter_changed) { if (m_search_term == term && m_search_term.isNull() == term.isNull() && currentSort == sort && !filter_changed) { return; @@ -74,7 +74,7 @@ void ListModel::searchWithTerm(const QString& term, const int sort, const bool f /******** Request callbacks ********/ -void ListModel::searchRequestFinished(QJsonDocument& doc) +void ModModel::searchRequestFinished(QJsonDocument& doc) { QList newList; auto packs = documentToArray(doc); @@ -108,7 +108,7 @@ void ListModel::searchRequestFinished(QJsonDocument& doc) endInsertRows(); } -void ListModel::infoRequestFinished(QJsonDocument& doc, ModPlatform::IndexedPack& pack, const QModelIndex& index) +void ModModel::infoRequestFinished(QJsonDocument& doc, ModPlatform::IndexedPack& pack, const QModelIndex& index) { qDebug() << "Loading mod info"; @@ -133,7 +133,7 @@ void ListModel::infoRequestFinished(QJsonDocument& doc, ModPlatform::IndexedPack m_associated_page->updateUi(); } -void ListModel::versionRequestSucceeded(QJsonDocument doc, QString addonId, const QModelIndex& index) +void ModModel::versionRequestSucceeded(QJsonDocument doc, QString addonId, const QModelIndex& index) { auto current = m_associated_page->getCurrentPack(); if (addonId != current.addonId) { @@ -159,16 +159,16 @@ void ListModel::versionRequestSucceeded(QJsonDocument doc, QString addonId, cons m_associated_page->updateVersionList(); } -} // namespace ModPlatform - /******** Helpers ********/ #define MOD_PAGE(x) static_cast(x) -auto ModPlatform::ListModel::getMineVersions() const -> std::optional> +auto ModModel::getMineVersions() const -> std::optional> { auto versions = MOD_PAGE(m_associated_page)->getFilter()->versions; if (!versions.empty()) return versions; return {}; } + +} // namespace ResourceDownload diff --git a/launcher/ui/pages/modplatform/ModModel.h b/launcher/ui/pages/modplatform/ModModel.h index 7c735d90..e3d760a2 100644 --- a/launcher/ui/pages/modplatform/ModModel.h +++ b/launcher/ui/pages/modplatform/ModModel.h @@ -7,16 +7,17 @@ #include "ui/pages/modplatform/ResourceModel.h" -class ModPage; class Version; -namespace ModPlatform { +namespace ResourceDownload { + +class ModPage; -class ListModel : public ResourceModel { +class ModModel : public ResourceModel { Q_OBJECT public: - ListModel(ModPage* parent, ResourceAPI* api); + ModModel(ModPage* parent, ResourceAPI* api); /* Ask the API for more information */ void searchWithTerm(const QString& term, const int sort, const bool filter_changed); @@ -51,4 +52,5 @@ class ListModel : public ResourceModel { protected: int currentSort = 0; }; -} // namespace ModPlatform + +} // namespace ResourceDownload diff --git a/launcher/ui/pages/modplatform/ModPage.cpp b/launcher/ui/pages/modplatform/ModPage.cpp index 853f2c54..8941d9b7 100644 --- a/launcher/ui/pages/modplatform/ModPage.cpp +++ b/launcher/ui/pages/modplatform/ModPage.cpp @@ -53,6 +53,8 @@ #include "ui/pages/modplatform/ModModel.h" +namespace ResourceDownload { + ModPage::ModPage(ModDownloadDialog* dialog, BaseInstance& instance) : ResourcePage(dialog, instance) { @@ -100,7 +102,7 @@ void ModPage::triggerSearch() updateSelectionButton(); } - static_cast(m_model)->searchWithTerm(getSearchTerm(), m_ui->sortByBox->currentIndex(), changed); + static_cast(m_model)->searchWithTerm(getSearchTerm(), m_ui->sortByBox->currentIndex(), changed); m_fetch_progress.watch(&m_model->activeJob()); } @@ -151,3 +153,5 @@ void ModPage::addResourceToDialog(ModPlatform::IndexedPack& pack, ModPlatform::I bool is_indexed = !APPLICATION->settings()->get("ModMetadataDisabled").toBool(); m_parent_dialog->addResource(pack.name, new ResourceDownloadTask(pack, version, m_parent_dialog->getBaseModel(), is_indexed)); } + +} // namespace ResourceDownload diff --git a/launcher/ui/pages/modplatform/ModPage.h b/launcher/ui/pages/modplatform/ModPage.h index 8c1fec84..137a6046 100644 --- a/launcher/ui/pages/modplatform/ModPage.h +++ b/launcher/ui/pages/modplatform/ModPage.h @@ -7,12 +7,14 @@ #include "ui/pages/modplatform/ResourcePage.h" #include "ui/widgets/ModFilterWidget.h" -class ModDownloadDialog; - namespace Ui { class ResourcePage; } +namespace ResourceDownload { + +class ModDownloadDialog; + /* This page handles most logic related to browsing and selecting mods to download. */ class ModPage : public ResourcePage { Q_OBJECT @@ -57,3 +59,5 @@ class ModPage : public ResourcePage { unique_qobject_ptr m_filter_widget; std::shared_ptr m_filter; }; + +} // namespace ResourceDownload diff --git a/launcher/ui/pages/modplatform/ResourceModel.cpp b/launcher/ui/pages/modplatform/ResourceModel.cpp index d672a2ac..e8af0e7a 100644 --- a/launcher/ui/pages/modplatform/ResourceModel.cpp +++ b/launcher/ui/pages/modplatform/ResourceModel.cpp @@ -20,6 +20,8 @@ #include "ui/pages/modplatform/ResourcePage.h" #include "ui/widgets/ProjectItem.h" +namespace ResourceDownload { + QHash ResourceModel::s_running_models; ResourceModel::ResourceModel(ResourcePage* parent, ResourceAPI* api) : QAbstractListModel(), m_api(api), m_associated_page(parent) @@ -256,3 +258,5 @@ void ResourceModel::searchRequestAborted() m_next_search_offset = 0; search(); } + +} // namespace ResourceDownload diff --git a/launcher/ui/pages/modplatform/ResourceModel.h b/launcher/ui/pages/modplatform/ResourceModel.h index af0e9f55..6a94c399 100644 --- a/launcher/ui/pages/modplatform/ResourceModel.h +++ b/launcher/ui/pages/modplatform/ResourceModel.h @@ -9,13 +9,15 @@ #include "tasks/ConcurrentTask.h" class NetJob; -class ResourcePage; class ResourceAPI; namespace ModPlatform { struct IndexedPack; } +namespace ResourceDownload { + +class ResourcePage; class ResourceModel : public QAbstractListModel { Q_OBJECT @@ -99,3 +101,5 @@ class ResourceModel : public QAbstractListModel { void searchRequestFailed(QString reason, int network_error_code); void searchRequestAborted(); }; + +} // namespace ResourceDownload diff --git a/launcher/ui/pages/modplatform/ResourcePage.cpp b/launcher/ui/pages/modplatform/ResourcePage.cpp index 3b382d20..161b5c22 100644 --- a/launcher/ui/pages/modplatform/ResourcePage.cpp +++ b/launcher/ui/pages/modplatform/ResourcePage.cpp @@ -13,6 +13,8 @@ #include "ui/pages/modplatform/ResourceModel.h" #include "ui/widgets/ProjectItem.h" +namespace ResourceDownload { + ResourcePage::ResourcePage(ResourceDownloadDialog* parent, BaseInstance& base_instance) : QWidget(parent), m_base_instance(base_instance), m_ui(new Ui::ResourcePage), m_parent_dialog(parent), m_fetch_progress(this, false) { @@ -345,3 +347,5 @@ void ResourcePage::openUrl(const QUrl& url) // open in the user's web browser QDesktopServices::openUrl(url); } + +} // namespace ResourceDownload diff --git a/launcher/ui/pages/modplatform/ResourcePage.h b/launcher/ui/pages/modplatform/ResourcePage.h index 32aad3d9..f731cf56 100644 --- a/launcher/ui/pages/modplatform/ResourcePage.h +++ b/launcher/ui/pages/modplatform/ResourcePage.h @@ -14,8 +14,11 @@ class ResourcePage; } class BaseInstance; -class ResourceModel; + +namespace ResourceDownload { + class ResourceDownloadDialog; +class ResourceModel; class ResourcePage : public QWidget, public BasePage { Q_OBJECT @@ -93,3 +96,5 @@ class ResourcePage : public QWidget, public BasePage { // Used to do instant searching with a delay to cache quick changes QTimer m_search_timer; }; + +} // namespace ResourceDownload diff --git a/launcher/ui/pages/modplatform/flame/FlameResourceModels.cpp b/launcher/ui/pages/modplatform/flame/FlameResourceModels.cpp index b602dfac..cfe4080a 100644 --- a/launcher/ui/pages/modplatform/flame/FlameResourceModels.cpp +++ b/launcher/ui/pages/modplatform/flame/FlameResourceModels.cpp @@ -1,31 +1,35 @@ #include "FlameResourceModels.h" + #include "Json.h" + #include "modplatform/flame/FlameModIndex.h" -namespace FlameMod { +namespace ResourceDownload { // NOLINTNEXTLINE(modernize-avoid-c-arrays) -const char* ListModel::sorts[6]{ "Featured", "Popularity", "LastUpdated", "Name", "Author", "TotalDownloads" }; +const char* FlameModModel::sorts[6]{ "Featured", "Popularity", "LastUpdated", "Name", "Author", "TotalDownloads" }; + +FlameModModel::FlameModModel(FlameModPage* parent) : ModModel(parent, new FlameAPI) {} -void ListModel::loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj) +void FlameModModel::loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj) { FlameMod::loadIndexedPack(m, obj); } // We already deal with the URLs when initializing the pack, due to the API response's structure -void ListModel::loadExtraPackInfo(ModPlatform::IndexedPack& m, QJsonObject& obj) +void FlameModModel::loadExtraPackInfo(ModPlatform::IndexedPack& m, QJsonObject& obj) { FlameMod::loadBody(m, obj); } -void ListModel::loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr) +void FlameModModel::loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr) { FlameMod::loadIndexedPackVersions(m, arr, APPLICATION->network(), &m_associated_page->m_base_instance); } -auto ListModel::documentToArray(QJsonDocument& obj) const -> QJsonArray +auto FlameModModel::documentToArray(QJsonDocument& obj) const -> QJsonArray { return Json::ensureArray(obj.object(), "data"); } -} // namespace FlameMod +} // namespace ResourceDownload diff --git a/launcher/ui/pages/modplatform/flame/FlameResourceModels.h b/launcher/ui/pages/modplatform/flame/FlameResourceModels.h index b94377d3..501937e2 100644 --- a/launcher/ui/pages/modplatform/flame/FlameResourceModels.h +++ b/launcher/ui/pages/modplatform/flame/FlameResourceModels.h @@ -2,14 +2,18 @@ #include "modplatform/flame/FlameAPI.h" -namespace FlameMod { +#include "ui/pages/modplatform/ModModel.h" -class ListModel : public ModPlatform::ListModel { +#include "ui/pages/modplatform/flame/FlameResourcePages.h" + +namespace ResourceDownload { + +class FlameModModel : public ModModel { Q_OBJECT public: - ListModel(FlameModPage* parent) : ModPlatform::ListModel(parent, new FlameAPI) {} - ~ListModel() override = default; + FlameModModel(FlameModPage* parent); + ~FlameModModel() override = default; private: void loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj) override; @@ -23,4 +27,4 @@ class ListModel : public ModPlatform::ListModel { inline auto getSorts() const -> const char** override { return sorts; }; }; -} // namespace FlameMod +} // namespace ResourceDownload diff --git a/launcher/ui/pages/modplatform/flame/FlameResourcePages.cpp b/launcher/ui/pages/modplatform/flame/FlameResourcePages.cpp index 490578ad..723819fb 100644 --- a/launcher/ui/pages/modplatform/flame/FlameResourcePages.cpp +++ b/launcher/ui/pages/modplatform/flame/FlameResourcePages.cpp @@ -40,10 +40,12 @@ #include "FlameResourceModels.h" #include "ui/dialogs/ModDownloadDialog.h" +namespace ResourceDownload { + FlameModPage::FlameModPage(ModDownloadDialog* dialog, BaseInstance& instance) : ModPage(dialog, instance) { - m_model = new FlameMod::ListModel(this); + m_model = new FlameModModel(this); m_ui->packView->setModel(m_model); // index is used to set the sorting with the flame api @@ -95,3 +97,5 @@ void FlameModPage::openUrl(const QUrl& url) ModPage::openUrl(url); } + +} // namespace ResourceDownload diff --git a/launcher/ui/pages/modplatform/flame/FlameResourcePages.h b/launcher/ui/pages/modplatform/flame/FlameResourcePages.h index 597a0c25..6c7d0247 100644 --- a/launcher/ui/pages/modplatform/flame/FlameResourcePages.h +++ b/launcher/ui/pages/modplatform/flame/FlameResourcePages.h @@ -42,6 +42,16 @@ #include "ui/pages/modplatform/ModPage.h" +namespace ResourceDownload { + +namespace Flame { +static inline QString displayName() { return "CurseForge"; } +static inline QIcon icon() { return APPLICATION->getThemedIcon("flame"); } +static inline QString id() { return "curseforge"; } +static inline QString debugName() { return "Flame"; } +static inline QString metaEntryBase() { return "FlameMods"; }; +} + class FlameModPage : public ModPage { Q_OBJECT @@ -54,18 +64,20 @@ class FlameModPage : public ModPage { FlameModPage(ModDownloadDialog* dialog, BaseInstance& instance); ~FlameModPage() override = default; - inline auto displayName() const -> QString override { return "CurseForge"; } - inline auto icon() const -> QIcon override { return APPLICATION->getThemedIcon("flame"); } - inline auto id() const -> QString override { return "curseforge"; } - inline auto helpPage() const -> QString override { return "Mod-platform"; } + [[nodiscard]] bool shouldDisplay() const override; - inline auto debugName() const -> QString override { return "Flame"; } - inline auto metaEntryBase() const -> QString override { return "FlameMods"; }; + [[nodiscard]] inline auto displayName() const -> QString override { return Flame::displayName(); } + [[nodiscard]] inline auto icon() const -> QIcon override { return Flame::icon(); } + [[nodiscard]] inline auto id() const -> QString override { return Flame::id(); } + [[nodiscard]] inline auto debugName() const -> QString override { return Flame::debugName(); } + [[nodiscard]] inline auto metaEntryBase() const -> QString override { return Flame::metaEntryBase(); } - auto validateVersion(ModPlatform::IndexedVersion& ver, QString mineVer, std::optional loaders = {}) const -> bool override; - bool optedOut(ModPlatform::IndexedVersion& ver) const override; + [[nodiscard]] inline auto helpPage() const -> QString override { return "Mod-platform"; } - auto shouldDisplay() const -> bool override; + bool validateVersion(ModPlatform::IndexedVersion& ver, QString mineVer, std::optional loaders = {}) const override; + bool optedOut(ModPlatform::IndexedVersion& ver) const override; void openUrl(const QUrl& url) override; }; + +} // namespace ResourceDownload diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.cpp index 51278546..ee96f0de 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.cpp +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.cpp @@ -23,31 +23,31 @@ #include "modplatform/modrinth/ModrinthAPI.h" #include "modplatform/modrinth/ModrinthPackIndex.h" -namespace Modrinth { +namespace ResourceDownload { // NOLINTNEXTLINE(modernize-avoid-c-arrays) -const char* ListModel::sorts[5]{ "relevance", "downloads", "follows", "updated", "newest" }; +const char* ModrinthModModel::sorts[5]{ "relevance", "downloads", "follows", "updated", "newest" }; -void ListModel::loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj) +ModrinthModModel::ModrinthModModel(ModrinthModPage* parent) : ModModel(parent, new ModrinthAPI){}; + +void ModrinthModModel::loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj) { - Modrinth::loadIndexedPack(m, obj); + ::Modrinth::loadIndexedPack(m, obj); } -void ListModel::loadExtraPackInfo(ModPlatform::IndexedPack& m, QJsonObject& obj) +void ModrinthModModel::loadExtraPackInfo(ModPlatform::IndexedPack& m, QJsonObject& obj) { - Modrinth::loadExtraPackData(m, obj); + ::Modrinth::loadExtraPackData(m, obj); } -void ListModel::loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr) +void ModrinthModModel::loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr) { - Modrinth::loadIndexedPackVersions(m, arr, APPLICATION->network(), &m_associated_page->m_base_instance); + ::Modrinth::loadIndexedPackVersions(m, arr, APPLICATION->network(), &m_associated_page->m_base_instance); } -auto ListModel::documentToArray(QJsonDocument& obj) const -> QJsonArray +auto ModrinthModModel::documentToArray(QJsonDocument& obj) const -> QJsonArray { return obj.object().value("hits").toArray(); } -} // namespace Modrinth - - +} // namespace ResourceDownload diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.h b/launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.h index bf62d22f..b0088a73 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.h +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.h @@ -20,24 +20,22 @@ #include "ui/pages/modplatform/ModModel.h" -#include "ui/pages/modplatform/modrinth/ModrinthResourcePages.h" +namespace ResourceDownload { -#include "modplatform/modrinth/ModrinthAPI.h" +class ModrinthModPage; -namespace Modrinth { - -class ListModel : public ModPlatform::ListModel { +class ModrinthModModel : public ModModel { Q_OBJECT public: - ListModel(ModrinthModPage* parent) : ModPlatform::ListModel(parent, new ModrinthAPI){}; - ~ListModel() override = default; + ModrinthModModel(ModrinthModPage* parent); + ~ModrinthModModel() override = default; private: void loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj) override; void loadExtraPackInfo(ModPlatform::IndexedPack& m, QJsonObject& obj) override; void loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr) override; - + auto documentToArray(QJsonDocument& obj) const -> QJsonArray override; // NOLINTNEXTLINE(modernize-avoid-c-arrays) @@ -45,5 +43,4 @@ class ListModel : public ModPlatform::ListModel { inline auto getSorts() const -> const char** override { return sorts; }; }; -} // namespace Modrinth - +} // namespace ResourceDownload diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.cpp index 17f0bc93..5d2680b0 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.cpp +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.cpp @@ -38,13 +38,16 @@ #include "modplatform/modrinth/ModrinthAPI.h" -#include "ModrinthResourceModels.h" #include "ui/dialogs/ModDownloadDialog.h" +#include "ui/pages/modplatform/modrinth/ModrinthResourceModels.h" + +namespace ResourceDownload { + ModrinthModPage::ModrinthModPage(ModDownloadDialog* dialog, BaseInstance& instance) : ModPage(dialog, instance) { - m_model = new Modrinth::ListModel(this); + m_model = new ModrinthModModel(this); m_ui->packView->setModel(m_model); // index is used to set the sorting with the modrinth api @@ -87,3 +90,4 @@ auto ModrinthModPage::validateVersion(ModPlatform::IndexedVersion& ver, QString // my Qt, so we need to implement this in every derived class... auto ModrinthModPage::shouldDisplay() const -> bool { return true; } +} // namespace ResourceDownload diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.h b/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.h index 6f816cfd..07b32c0c 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.h +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.h @@ -41,11 +41,15 @@ #include "ui/pages/modplatform/ModPage.h" +namespace ResourceDownload { + +namespace Modrinth { static inline QString displayName() { return "Modrinth"; } static inline QIcon icon() { return APPLICATION->getThemedIcon("modrinth"); } static inline QString id() { return "modrinth"; } static inline QString debugName() { return "Modrinth"; } static inline QString metaEntryBase() { return "ModrinthPacks"; }; +} class ModrinthModPage : public ModPage { Q_OBJECT @@ -61,12 +65,15 @@ class ModrinthModPage : public ModPage { [[nodiscard]] bool shouldDisplay() const override; - [[nodiscard]] inline auto displayName() const -> QString override { return ::displayName(); } \ - [[nodiscard]] inline auto icon() const -> QIcon override { return ::icon(); } \ - [[nodiscard]] inline auto id() const -> QString override { return ::id(); } \ - [[nodiscard]] inline auto debugName() const -> QString override { return ::debugName(); } \ - [[nodiscard]] inline auto metaEntryBase() const -> QString override { return ::metaEntryBase(); } - inline auto helpPage() const -> QString override { return "Mod-platform"; } + [[nodiscard]] inline auto displayName() const -> QString override { return Modrinth::displayName(); } + [[nodiscard]] inline auto icon() const -> QIcon override { return Modrinth::icon(); } + [[nodiscard]] inline auto id() const -> QString override { return Modrinth::id(); } + [[nodiscard]] inline auto debugName() const -> QString override { return Modrinth::debugName(); } + [[nodiscard]] inline auto metaEntryBase() const -> QString override { return Modrinth::metaEntryBase(); } + + [[nodiscard]] inline auto helpPage() const -> QString override { return "Mod-platform"; } auto validateVersion(ModPlatform::IndexedVersion& ver, QString mineVer, std::optional loaders = {}) const -> bool override; }; + +} // namespace ResourceDownload -- cgit From 39b7ac90d40eb53d7b88ef99b0fa46fb3e1840b9 Mon Sep 17 00:00:00 2001 From: flow Date: Fri, 16 Dec 2022 21:44:21 -0300 Subject: refactor(RD): unify download dialogs into a single file No need for multiple files since the subclasses are so small now Signed-off-by: flow --- launcher/CMakeLists.txt | 2 - launcher/ui/dialogs/ModDownloadDialog.cpp | 63 -------------------- launcher/ui/dialogs/ModDownloadDialog.h | 50 ---------------- launcher/ui/dialogs/ResourceDownloadDialog.cpp | 67 ++++++++++++++++++++++ launcher/ui/dialogs/ResourceDownloadDialog.h | 51 +++++++++++++++- launcher/ui/pages/instance/ModFolderPage.cpp | 2 +- launcher/ui/pages/modplatform/ModPage.cpp | 2 +- .../pages/modplatform/flame/FlameResourcePages.cpp | 2 +- .../pages/modplatform/modrinth/ModrinthModel.cpp | 1 - .../modplatform/modrinth/ModrinthResourcePages.cpp | 2 +- 10 files changed, 120 insertions(+), 122 deletions(-) delete mode 100644 launcher/ui/dialogs/ModDownloadDialog.cpp delete mode 100644 launcher/ui/dialogs/ModDownloadDialog.h (limited to 'launcher/ui/dialogs') diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index a1a68f5b..77c69106 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -876,8 +876,6 @@ SET(LAUNCHER_SOURCES ui/dialogs/SkinUploadDialog.h ui/dialogs/ResourceDownloadDialog.cpp ui/dialogs/ResourceDownloadDialog.h - ui/dialogs/ModDownloadDialog.cpp - ui/dialogs/ModDownloadDialog.h ui/dialogs/ScrollMessageBox.cpp ui/dialogs/ScrollMessageBox.h ui/dialogs/BlockedModsDialog.cpp diff --git a/launcher/ui/dialogs/ModDownloadDialog.cpp b/launcher/ui/dialogs/ModDownloadDialog.cpp deleted file mode 100644 index 89b87300..00000000 --- a/launcher/ui/dialogs/ModDownloadDialog.cpp +++ /dev/null @@ -1,63 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-only -/* - * Prism Launcher - Minecraft Launcher - * Copyright (C) 2022 Sefa Eyeoglu - * Copyright (C) 2022 TheKodeToad - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, version 3. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#include "ModDownloadDialog.h" - -#include "Application.h" - -#include "ui/pages/modplatform/flame/FlameResourcePages.h" -#include "ui/pages/modplatform/modrinth/ModrinthResourcePages.h" - -namespace ResourceDownload { - -ModDownloadDialog::ModDownloadDialog(QWidget* parent, const std::shared_ptr& mods, BaseInstance* instance) - : ResourceDownloadDialog(parent, mods), m_instance(instance) -{ - initializeContainer(); - connectButtons(); - - restoreGeometry(QByteArray::fromBase64(APPLICATION->settings()->get("ModDownloadGeometry").toByteArray())); -} - -void ModDownloadDialog::accept() -{ - APPLICATION->settings()->set("ModDownloadGeometry", saveGeometry().toBase64()); - QDialog::accept(); -} - -void ModDownloadDialog::reject() -{ - APPLICATION->settings()->set("ModDownloadGeometry", saveGeometry().toBase64()); - QDialog::reject(); -} - -QList ModDownloadDialog::getPages() -{ - QList pages; - - pages.append(ModrinthModPage::create(this, *m_instance)); - if (APPLICATION->capabilities() & Application::SupportsFlame) - pages.append(FlameModPage::create(this, *m_instance)); - - m_selectedPage = dynamic_cast(pages[0]); - - return pages; -} - -} // namespace ResourceDownload diff --git a/launcher/ui/dialogs/ModDownloadDialog.h b/launcher/ui/dialogs/ModDownloadDialog.h deleted file mode 100644 index b378b5a9..00000000 --- a/launcher/ui/dialogs/ModDownloadDialog.h +++ /dev/null @@ -1,50 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-only -/* - * Prism Launcher - Minecraft Launcher - * Copyright (C) 2022 Sefa Eyeoglu - * Copyright (C) 2022 TheKodeToad - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, version 3. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#pragma once - -#include "minecraft/mod/ModFolderModel.h" - -#include "ui/dialogs/ResourceDownloadDialog.h" - -class QDialogButtonBox; - -namespace ResourceDownload { - -class ModDownloadDialog final : public ResourceDownloadDialog { - Q_OBJECT - - public: - explicit ModDownloadDialog(QWidget* parent, const std::shared_ptr& mods, BaseInstance* instance); - ~ModDownloadDialog() override = default; - - //: String that gets appended to the mod download dialog title ("Download " + resourcesString()) - [[nodiscard]] QString resourceString() const override { return tr("mods"); } - - QList getPages() override; - - public slots: - void accept() override; - void reject() override; - - private: - BaseInstance* m_instance; -}; - -} // namespace ResourceDownload diff --git a/launcher/ui/dialogs/ResourceDownloadDialog.cpp b/launcher/ui/dialogs/ResourceDownloadDialog.cpp index b143750b..523a1636 100644 --- a/launcher/ui/dialogs/ResourceDownloadDialog.cpp +++ b/launcher/ui/dialogs/ResourceDownloadDialog.cpp @@ -1,3 +1,22 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (C) 2022 Sefa Eyeoglu + * Copyright (C) 2022 TheKodeToad + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + #include "ResourceDownloadDialog.h" #include @@ -5,8 +24,15 @@ #include "Application.h" #include "ResourceDownloadTask.h" +#include "minecraft/mod/ModFolderModel.h" + #include "ui/dialogs/ReviewMessageBox.h" + #include "ui/pages/modplatform/ResourcePage.h" + +#include "ui/pages/modplatform/flame/FlameResourcePages.h" +#include "ui/pages/modplatform/modrinth/ModrinthResourcePages.h" + #include "ui/widgets/PageContainer.h" namespace ResourceDownload { @@ -41,6 +67,22 @@ ResourceDownloadDialog::ResourceDownloadDialog(QWidget* parent, const std::share setWindowTitle(dialogTitle()); } +void ResourceDownloadDialog::accept() +{ + if (!geometrySaveKey().isEmpty()) + APPLICATION->settings()->set(geometrySaveKey(), saveGeometry().toBase64()); + + QDialog::accept(); +} + +void ResourceDownloadDialog::reject() +{ + if (!geometrySaveKey().isEmpty()) + APPLICATION->settings()->set(geometrySaveKey(), saveGeometry().toBase64()); + + QDialog::reject(); +} + // NOTE: We can't have this in the ctor because PageContainer calls a virtual function, and so // won't work with subclasses if we put it in this ctor. void ResourceDownloadDialog::initializeContainer() @@ -153,4 +195,29 @@ void ResourceDownloadDialog::selectedPageChanged(BasePage* previous, BasePage* s m_selectedPage->setSearchTerm(prev_page->getSearchTerm()); } + + +ModDownloadDialog::ModDownloadDialog(QWidget* parent, const std::shared_ptr& mods, BaseInstance* instance) + : ResourceDownloadDialog(parent, mods), m_instance(instance) +{ + initializeContainer(); + connectButtons(); + + if (!geometrySaveKey().isEmpty()) + restoreGeometry(QByteArray::fromBase64(APPLICATION->settings()->get(geometrySaveKey()).toByteArray())); +} + +QList ModDownloadDialog::getPages() +{ + QList pages; + + pages.append(ModrinthModPage::create(this, *m_instance)); + if (APPLICATION->capabilities() & Application::SupportsFlame) + pages.append(FlameModPage::create(this, *m_instance)); + + m_selectedPage = dynamic_cast(pages[0]); + + return pages; +} + } // namespace ResourceDownload diff --git a/launcher/ui/dialogs/ResourceDownloadDialog.h b/launcher/ui/dialogs/ResourceDownloadDialog.h index 3b234cd1..29813493 100644 --- a/launcher/ui/dialogs/ResourceDownloadDialog.h +++ b/launcher/ui/dialogs/ResourceDownloadDialog.h @@ -1,3 +1,22 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (C) 2022 Sefa Eyeoglu + * Copyright (C) 2022 TheKodeToad + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + #pragma once #include @@ -6,11 +25,13 @@ #include "ui/pages/BasePageProvider.h" -class ResourceDownloadTask; -class ResourceFolderModel; +class BaseInstance; +class ModFolderModel; class PageContainer; class QVBoxLayout; class QDialogButtonBox; +class ResourceDownloadTask; +class ResourceFolderModel; namespace ResourceDownload { @@ -40,11 +61,18 @@ class ResourceDownloadDialog : public QDialog, public BasePageProvider { const QList getTasks(); [[nodiscard]] const std::shared_ptr getBaseModel() const { return m_base_model; } + public slots: + void accept() override; + void reject() override; + protected slots: void selectedPageChanged(BasePage* previous, BasePage* selected); virtual void confirm(); + protected: + [[nodiscard]] virtual QString geometrySaveKey() const { return ""; } + protected: const std::shared_ptr m_base_model; @@ -57,4 +85,23 @@ class ResourceDownloadDialog : public QDialog, public BasePageProvider { QHash m_selected; }; + + +class ModDownloadDialog final : public ResourceDownloadDialog { + Q_OBJECT + + public: + explicit ModDownloadDialog(QWidget* parent, const std::shared_ptr& mods, BaseInstance* instance); + ~ModDownloadDialog() override = default; + + //: String that gets appended to the mod download dialog title ("Download " + resourcesString()) + [[nodiscard]] QString resourceString() const override { return tr("mods"); } + [[nodiscard]] QString geometrySaveKey() const override { return "ModDownloadGeometry"; } + + QList getPages() override; + + private: + BaseInstance* m_instance; +}; + } // namespace ResourceDownload diff --git a/launcher/ui/pages/instance/ModFolderPage.cpp b/launcher/ui/pages/instance/ModFolderPage.cpp index 7c4b8952..d9069915 100644 --- a/launcher/ui/pages/instance/ModFolderPage.cpp +++ b/launcher/ui/pages/instance/ModFolderPage.cpp @@ -49,8 +49,8 @@ #include "ui/GuiUtil.h" #include "ui/dialogs/CustomMessageBox.h" -#include "ui/dialogs/ModDownloadDialog.h" #include "ui/dialogs/ModUpdateDialog.h" +#include "ui/dialogs/ResourceDownloadDialog.h" #include "DesktopServices.h" diff --git a/launcher/ui/pages/modplatform/ModPage.cpp b/launcher/ui/pages/modplatform/ModPage.cpp index 8941d9b7..8d441546 100644 --- a/launcher/ui/pages/modplatform/ModPage.cpp +++ b/launcher/ui/pages/modplatform/ModPage.cpp @@ -49,7 +49,7 @@ #include "minecraft/MinecraftInstance.h" #include "minecraft/PackProfile.h" -#include "ui/dialogs/ModDownloadDialog.h" +#include "ui/dialogs/ResourceDownloadDialog.h" #include "ui/pages/modplatform/ModModel.h" diff --git a/launcher/ui/pages/modplatform/flame/FlameResourcePages.cpp b/launcher/ui/pages/modplatform/flame/FlameResourcePages.cpp index 723819fb..2a8ab526 100644 --- a/launcher/ui/pages/modplatform/flame/FlameResourcePages.cpp +++ b/launcher/ui/pages/modplatform/flame/FlameResourcePages.cpp @@ -38,7 +38,7 @@ #include "ui_ResourcePage.h" #include "FlameResourceModels.h" -#include "ui/dialogs/ModDownloadDialog.h" +#include "ui/dialogs/ResourceDownloadDialog.h" namespace ResourceDownload { diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp index e6704eef..80850b4c 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp @@ -40,7 +40,6 @@ #include "Json.h" #include "minecraft/MinecraftInstance.h" #include "minecraft/PackProfile.h" -#include "ui/dialogs/ModDownloadDialog.h" #include "ui/widgets/ProjectItem.h" #include diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.cpp index 5d2680b0..1352e2f6 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.cpp +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.cpp @@ -38,7 +38,7 @@ #include "modplatform/modrinth/ModrinthAPI.h" -#include "ui/dialogs/ModDownloadDialog.h" +#include "ui/dialogs/ResourceDownloadDialog.h" #include "ui/pages/modplatform/modrinth/ModrinthResourceModels.h" -- cgit From 45d1319891ce87cc1546a316ad550f892d411633 Mon Sep 17 00:00:00 2001 From: flow Date: Sun, 18 Dec 2022 15:41:46 -0300 Subject: refactor(RD): decouple ResourceModels from ResourcePages This makes it so that we don't need a reference to the parent page in the model. It will be useful once we change the page from a widget-based one to a QML page. It also makes tasks be created in the dialog instead of the page, so that the dialog can also have the necessary information to mark versions as selected / deselected easily. It also makes the task pointers into smart pointers. Signed-off-by: flow --- launcher/ResourceDownloadTask.h | 1 + launcher/modplatform/ModIndex.h | 20 ++++++ launcher/modplatform/ResourceAPI.h | 11 ++- launcher/modplatform/flame/FlameAPI.cpp | 2 +- launcher/modplatform/flame/FlameAPI.h | 2 +- launcher/modplatform/flame/FlameCheckUpdate.cpp | 3 +- launcher/modplatform/flame/FlameModIndex.cpp | 4 +- launcher/modplatform/flame/FlameModIndex.h | 2 +- .../modplatform/helpers/NetworkResourceAPI.cpp | 4 +- launcher/modplatform/modrinth/ModrinthAPI.h | 2 +- .../modplatform/modrinth/ModrinthPackIndex.cpp | 4 +- launcher/modplatform/modrinth/ModrinthPackIndex.h | 2 +- launcher/ui/dialogs/ResourceDownloadDialog.cpp | 44 +++++++----- launcher/ui/dialogs/ResourceDownloadDialog.h | 13 ++-- launcher/ui/pages/modplatform/ModModel.cpp | 81 +++++++++++----------- launcher/ui/pages/modplatform/ModModel.h | 13 ++-- launcher/ui/pages/modplatform/ModPage.cpp | 4 +- launcher/ui/pages/modplatform/ModPage.h | 6 ++ launcher/ui/pages/modplatform/ResourceModel.cpp | 19 ++--- launcher/ui/pages/modplatform/ResourceModel.h | 18 ++--- launcher/ui/pages/modplatform/ResourcePage.cpp | 29 ++++---- launcher/ui/pages/modplatform/ResourcePage.h | 4 +- .../modplatform/flame/FlameResourceModels.cpp | 5 +- .../pages/modplatform/flame/FlameResourceModels.h | 8 +-- .../pages/modplatform/flame/FlameResourcePages.cpp | 2 +- .../modrinth/ModrinthResourceModels.cpp | 6 +- .../modplatform/modrinth/ModrinthResourceModels.h | 6 +- .../modplatform/modrinth/ModrinthResourcePages.cpp | 2 +- 28 files changed, 183 insertions(+), 134 deletions(-) (limited to 'launcher/ui/dialogs') diff --git a/launcher/ResourceDownloadTask.h b/launcher/ResourceDownloadTask.h index 350c2edd..275ddbe1 100644 --- a/launcher/ResourceDownloadTask.h +++ b/launcher/ResourceDownloadTask.h @@ -32,6 +32,7 @@ class ResourceDownloadTask : public SequentialTask { public: explicit ResourceDownloadTask(ModPlatform::IndexedPack pack, ModPlatform::IndexedVersion version, const std::shared_ptr packs, bool is_indexed = true); const QString& getFilename() const { return m_pack_version.fileName; } + const QVariant& getVersionID() const { return m_pack_version.fileId; } private: ModPlatform::IndexedPack m_pack; diff --git a/launcher/modplatform/ModIndex.h b/launcher/modplatform/ModIndex.h index f65a6a4b..cd40a6ba 100644 --- a/launcher/modplatform/ModIndex.h +++ b/launcher/modplatform/ModIndex.h @@ -65,6 +65,9 @@ struct IndexedVersion { QString hash; bool is_preferred = true; QString changelog; + + // For internal use, not provided by APIs + bool is_currently_selected = false; }; struct ExtraPackData { @@ -95,6 +98,23 @@ struct IndexedPack { // Don't load by default, since some modplatform don't have that info bool extraDataLoaded = true; ExtraPackData extraData; + + // For internal use, not provided by APIs + [[nodiscard]] bool isVersionSelected(size_t index) const + { + if (!versionsLoaded) + return false; + + return versions.at(index).is_currently_selected; + } + [[nodiscard]] bool isAnyVersionSelected() const + { + if (!versionsLoaded) + return false; + + return std::any_of(versions.constBegin(), versions.constEnd(), + [](auto const& v) { return v.is_currently_selected; }); + } }; } // namespace ModPlatform diff --git a/launcher/modplatform/ResourceAPI.h b/launcher/modplatform/ResourceAPI.h index d18a2caa..49aac712 100644 --- a/launcher/modplatform/ResourceAPI.h +++ b/launcher/modplatform/ResourceAPI.h @@ -69,13 +69,20 @@ class ResourceAPI { }; struct VersionSearchArgs { - QString addonId; + ModPlatform::IndexedPack& pack; std::optional > mcVersions; std::optional loaders; + + void operator=(VersionSearchArgs other) + { + pack = other.pack; + mcVersions = other.mcVersions; + loaders = other.loaders; + } }; struct VersionSearchCallbacks { - std::function on_succeed; + std::function on_succeed; }; struct ProjectInfoArgs { diff --git a/launcher/modplatform/flame/FlameAPI.cpp b/launcher/modplatform/flame/FlameAPI.cpp index ae401399..89249c41 100644 --- a/launcher/modplatform/flame/FlameAPI.cpp +++ b/launcher/modplatform/flame/FlameAPI.cpp @@ -114,7 +114,7 @@ auto FlameAPI::getLatestVersion(VersionSearchArgs&& args) -> ModPlatform::Indexe QEventLoop loop; - auto netJob = new NetJob(QString("Flame::GetLatestVersion(%1)").arg(args.addonId), APPLICATION->network()); + auto netJob = new NetJob(QString("Flame::GetLatestVersion(%1)").arg(args.pack.name), APPLICATION->network()); auto response = new QByteArray(); ModPlatform::IndexedVersion ver; diff --git a/launcher/modplatform/flame/FlameAPI.h b/launcher/modplatform/flame/FlameAPI.h index 114a2716..f3cc0bbf 100644 --- a/launcher/modplatform/flame/FlameAPI.h +++ b/launcher/modplatform/flame/FlameAPI.h @@ -78,7 +78,7 @@ class FlameAPI : public NetworkResourceAPI { [[nodiscard]] std::optional getVersionsURL(VersionSearchArgs const& args) const override { - QString url{QString("https://api.curseforge.com/v1/mods/%1/files?pageSize=10000&").arg(args.addonId)}; + QString url{QString("https://api.curseforge.com/v1/mods/%1/files?pageSize=10000&").arg(args.pack.addonId.toString())}; QStringList get_parameters; if (args.mcVersions.has_value()) diff --git a/launcher/modplatform/flame/FlameCheckUpdate.cpp b/launcher/modplatform/flame/FlameCheckUpdate.cpp index 285fa49f..7aee4f4c 100644 --- a/launcher/modplatform/flame/FlameCheckUpdate.cpp +++ b/launcher/modplatform/flame/FlameCheckUpdate.cpp @@ -129,7 +129,8 @@ void FlameCheckUpdate::executeTask() setStatus(tr("Getting API response from CurseForge for '%1'...").arg(mod->name())); setProgress(i++, m_mods.size()); - auto latest_ver = api.getLatestVersion({ mod->metadata()->project_id.toString(), m_game_versions, m_loaders }); + ModPlatform::IndexedPack pack{ mod->metadata()->project_id.toString() }; + auto latest_ver = api.getLatestVersion({ pack, m_game_versions, m_loaders }); // Check if we were aborted while getting the latest version if (m_was_aborted) { diff --git a/launcher/modplatform/flame/FlameModIndex.cpp b/launcher/modplatform/flame/FlameModIndex.cpp index 617b98ce..7498e830 100644 --- a/launcher/modplatform/flame/FlameModIndex.cpp +++ b/launcher/modplatform/flame/FlameModIndex.cpp @@ -76,10 +76,10 @@ static QString enumToString(int hash_algorithm) void FlameMod::loadIndexedPackVersions(ModPlatform::IndexedPack& pack, QJsonArray& arr, const shared_qobject_ptr& network, - BaseInstance* inst) + const BaseInstance* inst) { QVector unsortedVersions; - auto profile = (dynamic_cast(inst))->getPackProfile(); + auto profile = (dynamic_cast(inst))->getPackProfile(); QString mcVersion = profile->getComponentVersion("net.minecraft"); for (auto versionIter : arr) { diff --git a/launcher/modplatform/flame/FlameModIndex.h b/launcher/modplatform/flame/FlameModIndex.h index db63cdbb..33c4a529 100644 --- a/launcher/modplatform/flame/FlameModIndex.h +++ b/launcher/modplatform/flame/FlameModIndex.h @@ -17,7 +17,7 @@ void loadBody(ModPlatform::IndexedPack& m, QJsonObject& obj); void loadIndexedPackVersions(ModPlatform::IndexedPack& pack, QJsonArray& arr, const shared_qobject_ptr& network, - BaseInstance* inst); + const BaseInstance* inst); auto loadIndexedPackVersion(QJsonObject& obj, bool load_changelog = false) -> ModPlatform::IndexedVersion; } // namespace FlameMod diff --git a/launcher/modplatform/helpers/NetworkResourceAPI.cpp b/launcher/modplatform/helpers/NetworkResourceAPI.cpp index eb17008c..77b085c0 100644 --- a/launcher/modplatform/helpers/NetworkResourceAPI.cpp +++ b/launcher/modplatform/helpers/NetworkResourceAPI.cpp @@ -79,7 +79,7 @@ NetJob::Ptr NetworkResourceAPI::getProjectVersions(VersionSearchArgs&& args, Ver auto versions_url = versions_url_optional.value(); - auto netJob = new NetJob(QString("%1::Versions").arg(args.addonId), APPLICATION->network()); + auto netJob = new NetJob(QString("%1::Versions").arg(args.pack.name), APPLICATION->network()); auto response = new QByteArray(); netJob->addNetAction(Net::Download::makeByteArray(versions_url, response)); @@ -94,7 +94,7 @@ NetJob::Ptr NetworkResourceAPI::getProjectVersions(VersionSearchArgs&& args, Ver return; } - callbacks.on_succeed(doc, args.addonId); + callbacks.on_succeed(doc, args.pack); }); QObject::connect(netJob, &NetJob::finished, [response] { diff --git a/launcher/modplatform/modrinth/ModrinthAPI.h b/launcher/modplatform/modrinth/ModrinthAPI.h index bd84fb54..ec38d9ee 100644 --- a/launcher/modplatform/modrinth/ModrinthAPI.h +++ b/launcher/modplatform/modrinth/ModrinthAPI.h @@ -141,7 +141,7 @@ class ModrinthAPI : public NetworkResourceAPI { get_arguments.append(QString("loaders=[\"%1\"]").arg(getModLoaderStrings(args.loaders.value()).join("\",\""))); return QString("%1/project/%2/version%3%4") - .arg(BuildConfig.MODRINTH_PROD_URL, args.addonId, get_arguments.isEmpty() ? "" : "?", get_arguments.join('&')); + .arg(BuildConfig.MODRINTH_PROD_URL, args.pack.addonId.toString(), get_arguments.isEmpty() ? "" : "?", get_arguments.join('&')); }; auto getGameVersionsArray(std::list mcVersions) const -> QString diff --git a/launcher/modplatform/modrinth/ModrinthPackIndex.cpp b/launcher/modplatform/modrinth/ModrinthPackIndex.cpp index a0161089..f270f470 100644 --- a/launcher/modplatform/modrinth/ModrinthPackIndex.cpp +++ b/launcher/modplatform/modrinth/ModrinthPackIndex.cpp @@ -95,10 +95,10 @@ void Modrinth::loadExtraPackData(ModPlatform::IndexedPack& pack, QJsonObject& ob void Modrinth::loadIndexedPackVersions(ModPlatform::IndexedPack& pack, QJsonArray& arr, const shared_qobject_ptr& network, - BaseInstance* inst) + const BaseInstance* inst) { QVector unsortedVersions; - QString mcVersion = (static_cast(inst))->getPackProfile()->getComponentVersion("net.minecraft"); + QString mcVersion = (static_cast(inst))->getPackProfile()->getComponentVersion("net.minecraft"); for (auto versionIter : arr) { auto obj = versionIter.toObject(); diff --git a/launcher/modplatform/modrinth/ModrinthPackIndex.h b/launcher/modplatform/modrinth/ModrinthPackIndex.h index 31881414..e73e4b18 100644 --- a/launcher/modplatform/modrinth/ModrinthPackIndex.h +++ b/launcher/modplatform/modrinth/ModrinthPackIndex.h @@ -29,7 +29,7 @@ void loadExtraPackData(ModPlatform::IndexedPack& m, QJsonObject& obj); void loadIndexedPackVersions(ModPlatform::IndexedPack& pack, QJsonArray& arr, const shared_qobject_ptr& network, - BaseInstance* inst); + const BaseInstance* inst); auto loadIndexedPackVersion(QJsonObject& obj, QString hash_type = "sha512", QString filename_prefer = "") -> ModPlatform::IndexedVersion; } // namespace Modrinth diff --git a/launcher/ui/dialogs/ResourceDownloadDialog.cpp b/launcher/ui/dialogs/ResourceDownloadDialog.cpp index 523a1636..2eb85928 100644 --- a/launcher/ui/dialogs/ResourceDownloadDialog.cpp +++ b/launcher/ui/dialogs/ResourceDownloadDialog.cpp @@ -141,38 +141,44 @@ ResourcePage* ResourceDownloadDialog::getSelectedPage() return m_selectedPage; } -void ResourceDownloadDialog::addResource(QString name, ResourceDownloadTask* task) +void ResourceDownloadDialog::addResource(ModPlatform::IndexedPack& pack, ModPlatform::IndexedVersion& ver, bool is_indexed) { - removeResource(name); - m_selected.insert(name, task); + removeResource(pack, ver); + + ver.is_currently_selected = true; + m_selected.insert(pack.name, new ResourceDownloadTask(pack, ver, getBaseModel(), is_indexed)); m_buttons.button(QDialogButtonBox::Ok)->setEnabled(!m_selected.isEmpty()); } -void ResourceDownloadDialog::removeResource(QString name) +static ModPlatform::IndexedVersion& getVersionWithID(ModPlatform::IndexedPack& pack, QVariant id) { - if (m_selected.contains(name)) - m_selected.find(name).value()->deleteLater(); - m_selected.remove(name); - - m_buttons.button(QDialogButtonBox::Ok)->setEnabled(!m_selected.isEmpty()); + Q_ASSERT(pack.versionsLoaded); + auto it = std::find_if(pack.versions.begin(), pack.versions.end(), [id](auto const& v) { return v.fileId == id; }); + Q_ASSERT(it != pack.versions.end()); + return *it; } -bool ResourceDownloadDialog::isSelected(QString name, QString filename) const +void ResourceDownloadDialog::removeResource(ModPlatform::IndexedPack& pack, ModPlatform::IndexedVersion& ver) { - auto iter = m_selected.constFind(name); - if (iter == m_selected.constEnd()) - return false; + if (auto selected_task_it = m_selected.find(pack.name); selected_task_it != m_selected.end()) { + auto selected_task = *selected_task_it; + auto old_version_id = selected_task->getVersionID(); - // FIXME: Is there a way to check for versions without checking the filename - // as a heuristic, other than adding such info to ResourceDownloadTask itself? - if (!filename.isEmpty()) - return iter.value()->getFilename() == filename; + // If the new and old version IDs don't match, search for the old one and deselect it. + if (ver.fileId != old_version_id) + getVersionWithID(pack, old_version_id).is_currently_selected = false; + } - return true; + // Deselect the new version too, since all versions of that pack got removed. + ver.is_currently_selected = false; + + m_selected.remove(pack.name); + + m_buttons.button(QDialogButtonBox::Ok)->setEnabled(!m_selected.isEmpty()); } -const QList ResourceDownloadDialog::getTasks() +const QList ResourceDownloadDialog::getTasks() { return m_selected.values(); } diff --git a/launcher/ui/dialogs/ResourceDownloadDialog.h b/launcher/ui/dialogs/ResourceDownloadDialog.h index 29813493..95a5e628 100644 --- a/launcher/ui/dialogs/ResourceDownloadDialog.h +++ b/launcher/ui/dialogs/ResourceDownloadDialog.h @@ -23,6 +23,8 @@ #include #include +#include "QObjectPtr.h" +#include "modplatform/ModIndex.h" #include "ui/pages/BasePageProvider.h" class BaseInstance; @@ -41,6 +43,8 @@ class ResourceDownloadDialog : public QDialog, public BasePageProvider { Q_OBJECT public: + using DownloadTaskPtr = shared_qobject_ptr; + ResourceDownloadDialog(QWidget* parent, const std::shared_ptr base_model); void initializeContainer(); @@ -54,11 +58,10 @@ class ResourceDownloadDialog : public QDialog, public BasePageProvider { bool selectPage(QString pageId); ResourcePage* getSelectedPage(); - void addResource(QString name, ResourceDownloadTask* task); - void removeResource(QString name); - [[nodiscard]] bool isSelected(QString name, QString filename = "") const; + void addResource(ModPlatform::IndexedPack&, ModPlatform::IndexedVersion&, bool is_indexed = false); + void removeResource(ModPlatform::IndexedPack&, ModPlatform::IndexedVersion&); - const QList getTasks(); + const QList getTasks(); [[nodiscard]] const std::shared_ptr getBaseModel() const { return m_base_model; } public slots: @@ -82,7 +85,7 @@ class ResourceDownloadDialog : public QDialog, public BasePageProvider { QDialogButtonBox m_buttons; QVBoxLayout m_vertical_layout; - QHash m_selected; + QHash m_selected; }; diff --git a/launcher/ui/pages/modplatform/ModModel.cpp b/launcher/ui/pages/modplatform/ModModel.cpp index 59399c59..c9dee449 100644 --- a/launcher/ui/pages/modplatform/ModModel.cpp +++ b/launcher/ui/pages/modplatform/ModModel.cpp @@ -1,7 +1,7 @@ #include "ModModel.h" #include "Json.h" -#include "ModPage.h" + #include "minecraft/MinecraftInstance.h" #include "minecraft/PackProfile.h" @@ -9,15 +9,23 @@ namespace ResourceDownload { -ModModel::ModModel(ModPage* parent, ResourceAPI* api) : ResourceModel(parent, api) {} +ModModel::ModModel(BaseInstance const& base_inst, ResourceAPI* api) : ResourceModel(base_inst, api) {} /******** Make data requests ********/ ResourceAPI::SearchArgs ModModel::createSearchArguments() { - auto profile = static_cast(m_associated_page->m_base_instance).getPackProfile(); + auto profile = static_cast(m_base_instance).getPackProfile(); + + Q_ASSERT(profile); + Q_ASSERT(m_filter); + + std::optional> versions {}; + if (!m_filter->versions.empty()) + versions = m_filter->versions; + return { ModPlatform::ResourceType::MOD, m_next_search_offset, m_search_term, - getSorts()[currentSort], profile->getModLoaders(), getMineVersions() }; + getSorts()[currentSort], profile->getModLoaders(), versions }; } ResourceAPI::SearchCallbacks ModModel::createSearchCallbacks() { @@ -30,19 +38,24 @@ ResourceAPI::SearchCallbacks ModModel::createSearchCallbacks() ResourceAPI::VersionSearchArgs ModModel::createVersionsArguments(QModelIndex& entry) { - auto const& pack = m_packs[entry.row()]; - auto profile = static_cast(m_associated_page->m_base_instance).getPackProfile(); + auto& pack = m_packs[entry.row()]; + auto profile = static_cast(m_base_instance).getPackProfile(); + + Q_ASSERT(profile); + Q_ASSERT(m_filter); - return { pack.addonId.toString(), getMineVersions(), profile->getModLoaders() }; + std::optional> versions {}; + if (!m_filter->versions.empty()) + versions = m_filter->versions; + + return { pack, versions, profile->getModLoaders() }; } ResourceAPI::VersionSearchCallbacks ModModel::createVersionsCallbacks(QModelIndex& entry) { - auto const& pack = m_packs[entry.row()]; - - return { [this, pack, entry](auto& doc, auto addonId) { + return { [this, entry](auto& doc, auto& pack) { if (!s_running_models.constFind(this).value()) return; - versionRequestSucceeded(doc, addonId, entry); + versionRequestSucceeded(doc, pack, entry); } }; } @@ -87,7 +100,7 @@ void ModModel::searchRequestFinished(QJsonDocument& doc) loadIndexedPack(pack, packObj); newList.append(pack); } catch (const JSONValidationError& e) { - qWarning() << "Error while loading mod from " << m_associated_page->debugName() << ": " << e.cause(); + qWarning() << "Error while loading mod from " << debugName() << ": " << e.cause(); continue; } } @@ -127,48 +140,36 @@ void ModModel::infoRequestFinished(QJsonDocument& doc, ModPlatform::IndexedPack& new_pack.setValue(pack); if (!setData(index, new_pack, Qt::UserRole)) { qWarning() << "Failed to cache mod info!"; + return; } + + emit projectInfoUpdated(); } - - m_associated_page->updateUi(); } -void ModModel::versionRequestSucceeded(QJsonDocument doc, QString addonId, const QModelIndex& index) +void ModModel::versionRequestSucceeded(QJsonDocument doc, ModPlatform::IndexedPack& pack, const QModelIndex& index) { - auto current = m_associated_page->getCurrentPack(); - if (addonId != current.addonId) { - return; - } - auto arr = doc.isObject() ? Json::ensureArray(doc.object(), "data") : doc.array(); try { - loadIndexedPackVersions(current, arr); + loadIndexedPackVersions(pack, arr); } catch (const JSONValidationError& e) { qDebug() << doc; qWarning() << "Error while reading " << debugName() << " mod version: " << e.cause(); } - // Cache info :^) - QVariant new_pack; - new_pack.setValue(current); - if (!setData(index, new_pack, Qt::UserRole)) { - qWarning() << "Failed to cache mod versions!"; - } - - m_associated_page->updateVersionList(); -} - -/******** Helpers ********/ - -#define MOD_PAGE(x) static_cast(x) + // Check if the index is still valid for this mod or not + if (pack.addonId == data(index, Qt::UserRole).value().addonId) { + // Cache info :^) + QVariant new_pack; + new_pack.setValue(pack); + if (!setData(index, new_pack, Qt::UserRole)) { + qWarning() << "Failed to cache mod versions!"; + return; + } -auto ModModel::getMineVersions() const -> std::optional> -{ - auto versions = MOD_PAGE(m_associated_page)->getFilter()->versions; - if (!versions.empty()) - return versions; - return {}; + emit versionListUpdated(); + } } } // namespace ResourceDownload diff --git a/launcher/ui/pages/modplatform/ModModel.h b/launcher/ui/pages/modplatform/ModModel.h index e3d760a2..39d062f9 100644 --- a/launcher/ui/pages/modplatform/ModModel.h +++ b/launcher/ui/pages/modplatform/ModModel.h @@ -6,6 +6,7 @@ #include "modplatform/ResourceAPI.h" #include "ui/pages/modplatform/ResourceModel.h" +#include "ui/widgets/ModFilterWidget.h" class Version; @@ -17,7 +18,7 @@ class ModModel : public ResourceModel { Q_OBJECT public: - ModModel(ModPage* parent, ResourceAPI* api); + ModModel(const BaseInstance&, ResourceAPI* api); /* Ask the API for more information */ void searchWithTerm(const QString& term, const int sort, const bool filter_changed); @@ -26,12 +27,12 @@ class ModModel : public ResourceModel { virtual void loadExtraPackInfo(ModPlatform::IndexedPack& m, QJsonObject& obj) = 0; virtual void loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr) = 0; + void setFilter(std::shared_ptr filter) { m_filter = filter; } + public slots: void searchRequestFinished(QJsonDocument& doc); - void infoRequestFinished(QJsonDocument& doc, ModPlatform::IndexedPack& pack, const QModelIndex& index); - - void versionRequestSucceeded(QJsonDocument doc, QString addonId, const QModelIndex& index); + void versionRequestSucceeded(QJsonDocument doc, ModPlatform::IndexedPack& pack, const QModelIndex& index); public slots: ResourceAPI::SearchArgs createSearchArguments() override; @@ -47,10 +48,10 @@ class ModModel : public ResourceModel { virtual auto documentToArray(QJsonDocument& obj) const -> QJsonArray = 0; virtual auto getSorts() const -> const char** = 0; - inline auto getMineVersions() const -> std::optional>; - protected: int currentSort = 0; + + std::shared_ptr m_filter = nullptr; }; } // namespace ResourceDownload diff --git a/launcher/ui/pages/modplatform/ModPage.cpp b/launcher/ui/pages/modplatform/ModPage.cpp index 8d441546..556bd642 100644 --- a/launcher/ui/pages/modplatform/ModPage.cpp +++ b/launcher/ui/pages/modplatform/ModPage.cpp @@ -51,8 +51,6 @@ #include "ui/dialogs/ResourceDownloadDialog.h" -#include "ui/pages/modplatform/ModModel.h" - namespace ResourceDownload { ModPage::ModPage(ModDownloadDialog* dialog, BaseInstance& instance) @@ -151,7 +149,7 @@ void ModPage::updateVersionList() void ModPage::addResourceToDialog(ModPlatform::IndexedPack& pack, ModPlatform::IndexedVersion& version) { bool is_indexed = !APPLICATION->settings()->get("ModMetadataDisabled").toBool(); - m_parent_dialog->addResource(pack.name, new ResourceDownloadTask(pack, version, m_parent_dialog->getBaseModel(), is_indexed)); + m_parent_dialog->addResource(pack, version, is_indexed); } } // namespace ResourceDownload diff --git a/launcher/ui/pages/modplatform/ModPage.h b/launcher/ui/pages/modplatform/ModPage.h index 137a6046..2fda3b68 100644 --- a/launcher/ui/pages/modplatform/ModPage.h +++ b/launcher/ui/pages/modplatform/ModPage.h @@ -5,6 +5,7 @@ #include "modplatform/ModIndex.h" #include "ui/pages/modplatform/ResourcePage.h" +#include "ui/pages/modplatform/ModModel.h" #include "ui/widgets/ModFilterWidget.h" namespace Ui { @@ -24,9 +25,14 @@ class ModPage : public ResourcePage { static T* create(ModDownloadDialog* dialog, BaseInstance& instance) { auto page = new T(dialog, instance); + auto model = static_cast(page->getModel()); auto filter_widget = ModFilterWidget::create(static_cast(instance).getPackProfile()->getComponentVersion("net.minecraft"), page); page->setFilterWidget(filter_widget); + model->setFilter(page->getFilter()); + + connect(model, &ResourceModel::versionListUpdated, page, &ResourcePage::updateVersionList); + connect(model, &ResourceModel::projectInfoUpdated, page, &ResourcePage::updateUi); return page; } diff --git a/launcher/ui/pages/modplatform/ResourceModel.cpp b/launcher/ui/pages/modplatform/ResourceModel.cpp index e8af0e7a..cf40fef2 100644 --- a/launcher/ui/pages/modplatform/ResourceModel.cpp +++ b/launcher/ui/pages/modplatform/ResourceModel.cpp @@ -17,14 +17,13 @@ #include "modplatform/ModIndex.h" -#include "ui/pages/modplatform/ResourcePage.h" #include "ui/widgets/ProjectItem.h" namespace ResourceDownload { QHash ResourceModel::s_running_models; -ResourceModel::ResourceModel(ResourcePage* parent, ResourceAPI* api) : QAbstractListModel(), m_api(api), m_associated_page(parent) +ResourceModel::ResourceModel(BaseInstance const& base_inst, ResourceAPI* api) : QAbstractListModel(), m_base_instance(base_inst), m_api(api) { s_running_models.insert(this, true); } @@ -72,7 +71,7 @@ auto ResourceModel::data(const QModelIndex& index, int role) const -> QVariant case UserDataTypes::DESCRIPTION: return pack.description; case UserDataTypes::SELECTED: - return isPackSelected(pack); + return pack.isAnyVersionSelected(); default: break; } @@ -87,13 +86,14 @@ bool ResourceModel::setData(const QModelIndex& index, const QVariant& value, int return false; m_packs[pos] = value.value(); + emit dataChanged(index, index); return true; } QString ResourceModel::debugName() const { - return m_associated_page->debugName() + " (Model)"; + return "ResourceDownload (Model)"; } void ResourceModel::fetchMore(const QModelIndex& parent) @@ -195,7 +195,7 @@ std::optional ResourceModel::getIcon(QModelIndex& index, const QUrl& url) return {}; auto cache_entry = APPLICATION->metacache()->resolveEntry( - m_associated_page->metaEntryBase(), + metaEntryBase(), QString("logos/%1").arg(QString(QCryptographicHash::hash(url.toEncoded(), QCryptographicHash::Algorithm::Sha1).toHex()))); auto icon_fetch_action = Net::Download::makeCached(url, cache_entry); @@ -222,11 +222,6 @@ std::optional ResourceModel::getIcon(QModelIndex& index, const QUrl& url) return {}; } -bool ResourceModel::isPackSelected(const ModPlatform::IndexedPack& pack) const -{ - return m_associated_page->isPackSelected(pack); -} - void ResourceModel::searchRequestFailed(QString reason, int network_error_code) { switch (network_error_code) { @@ -237,9 +232,7 @@ void ResourceModel::searchRequestFailed(QString reason, int network_error_code) case 409: // 409 Gone, notify user to update QMessageBox::critical(nullptr, tr("Error"), - //: %1 refers to the launcher itself - QString("%1 %2") - .arg(m_associated_page->displayName()) + QString("%1") .arg(tr("API version too old!\nPlease update %1!").arg(BuildConfig.LAUNCHER_DISPLAYNAME))); break; } diff --git a/launcher/ui/pages/modplatform/ResourceModel.h b/launcher/ui/pages/modplatform/ResourceModel.h index 6a94c399..af33bf55 100644 --- a/launcher/ui/pages/modplatform/ResourceModel.h +++ b/launcher/ui/pages/modplatform/ResourceModel.h @@ -5,6 +5,7 @@ #include #include "QObjectPtr.h" +#include "BaseInstance.h" #include "modplatform/ResourceAPI.h" #include "tasks/ConcurrentTask.h" @@ -17,19 +18,18 @@ struct IndexedPack; namespace ResourceDownload { -class ResourcePage; - class ResourceModel : public QAbstractListModel { Q_OBJECT public: - ResourceModel(ResourcePage* parent, ResourceAPI* api); + ResourceModel(BaseInstance const&, ResourceAPI* api); ~ResourceModel() override; [[nodiscard]] auto data(const QModelIndex&, int role) const -> QVariant override; bool setData(const QModelIndex& index, const QVariant& value, int role) override; - [[nodiscard]] auto debugName() const -> QString; + [[nodiscard]] virtual auto debugName() const -> QString; + [[nodiscard]] virtual auto metaEntryBase() const -> QString = 0; [[nodiscard]] inline int rowCount(const QModelIndex& parent) const override { return parent.isValid() ? 0 : m_packs.size(); } [[nodiscard]] inline int columnCount(const QModelIndex& parent) const override { return parent.isValid() ? 0 : 1; }; @@ -38,6 +38,10 @@ class ResourceModel : public QAbstractListModel { inline void addActiveJob(Task::Ptr ptr) { m_current_job.addTask(ptr); if (!m_current_job.isRunning()) m_current_job.start(); } inline Task const& activeJob() { return m_current_job; } + signals: + void versionListUpdated(); + void projectInfoUpdated(); + public slots: void fetchMore(const QModelIndex& parent) override; [[nodiscard]] inline bool canFetchMore(const QModelIndex& parent) const override @@ -72,9 +76,9 @@ class ResourceModel : public QAbstractListModel { /** Resets the model's data. */ void clearData(); - [[nodiscard]] bool isPackSelected(const ModPlatform::IndexedPack&) const; - protected: + const BaseInstance& m_base_instance; + /* Basic search parameters */ enum class SearchState { None, CanFetchMore, ResetRequested, Finished } m_search_state = SearchState::None; int m_next_search_offset = 0; @@ -88,8 +92,6 @@ class ResourceModel : public QAbstractListModel { QSet m_currently_running_icon_actions; QSet m_failed_icon_actions; - ResourcePage* m_associated_page = nullptr; - QList m_packs; // HACK: We need this to prevent callbacks from calling the model after it has already been deleted. diff --git a/launcher/ui/pages/modplatform/ResourcePage.cpp b/launcher/ui/pages/modplatform/ResourcePage.cpp index 161b5c22..e04278af 100644 --- a/launcher/ui/pages/modplatform/ResourcePage.cpp +++ b/launcher/ui/pages/modplatform/ResourcePage.cpp @@ -103,17 +103,16 @@ void ResourcePage::setSearchTerm(QString term) m_ui->searchEdit->setText(term); } -ModPlatform::IndexedPack ResourcePage::getCurrentPack() const +bool ResourcePage::setCurrentPack(ModPlatform::IndexedPack pack) { - return m_model->data(m_ui->packView->currentIndex(), Qt::UserRole).value(); + QVariant v; + v.setValue(pack); + return m_model->setData(m_ui->packView->currentIndex(), v, Qt::UserRole); } -bool ResourcePage::isPackSelected(const ModPlatform::IndexedPack& pack, int version) const +ModPlatform::IndexedPack ResourcePage::getCurrentPack() const { - if (version < 0 || !pack.versionsLoaded) - return m_parent_dialog->isSelected(pack.name); - - return m_parent_dialog->isSelected(pack.name, pack.versions[version].fileName); + return m_model->data(m_ui->packView->currentIndex(), Qt::UserRole).value(); } void ResourcePage::updateUi() @@ -185,7 +184,7 @@ void ResourcePage::updateSelectionButton() } m_ui->resourceSelectionButton->setEnabled(true); - if (!isPackSelected(getCurrentPack(), m_selected_version_index)) { + if (!getCurrentPack().isVersionSelected(m_selected_version_index)) { m_ui->resourceSelectionButton->setText(tr("Select %1 for download").arg(resourceString())); } else { m_ui->resourceSelectionButton->setText(tr("Deselect %1 for download").arg(resourceString())); @@ -256,12 +255,12 @@ void ResourcePage::onVersionSelectionChanged(QString data) void ResourcePage::addResourceToDialog(ModPlatform::IndexedPack& pack, ModPlatform::IndexedVersion& version) { - m_parent_dialog->addResource(pack.name, new ResourceDownloadTask(pack, version, m_parent_dialog->getBaseModel())); + m_parent_dialog->addResource(pack, version); } -void ResourcePage::removeResourceFromDialog(ModPlatform::IndexedPack& pack, ModPlatform::IndexedVersion&) +void ResourcePage::removeResourceFromDialog(ModPlatform::IndexedPack& pack, ModPlatform::IndexedVersion& version) { - m_parent_dialog->removeResource(pack.name); + m_parent_dialog->removeResource(pack, version); } void ResourcePage::onResourceSelected() @@ -270,13 +269,19 @@ void ResourcePage::onResourceSelected() return; auto current_pack = getCurrentPack(); + if (!current_pack.versionsLoaded) + return; auto& version = current_pack.versions[m_selected_version_index]; - if (m_parent_dialog->isSelected(current_pack.name, version.fileName)) + if (version.is_currently_selected) removeResourceFromDialog(current_pack, version); else addResourceToDialog(current_pack, version); + // Save the modified pack (and prevent warning in release build) + [[maybe_unused]] bool set = setCurrentPack(current_pack); + Q_ASSERT(set); + updateSelectionButton(); /* Force redraw on the resource list when the selection changes */ diff --git a/launcher/ui/pages/modplatform/ResourcePage.h b/launcher/ui/pages/modplatform/ResourcePage.h index f731cf56..b51c7ccb 100644 --- a/launcher/ui/pages/modplatform/ResourcePage.h +++ b/launcher/ui/pages/modplatform/ResourcePage.h @@ -50,11 +50,13 @@ class ResourcePage : public QWidget, public BasePage { /** Programatically set the term in the search bar. */ void setSearchTerm(QString); - [[nodiscard]] bool isPackSelected(const ModPlatform::IndexedPack&, int version = -1) const; + [[nodiscard]] bool setCurrentPack(ModPlatform::IndexedPack); [[nodiscard]] auto getCurrentPack() const -> ModPlatform::IndexedPack; [[nodiscard]] auto getDialog() const -> const ResourceDownloadDialog* { return m_parent_dialog; } + [[nodiscard]] auto getModel() const -> ResourceModel* { return m_model; } + protected: ResourcePage(ResourceDownloadDialog* parent, BaseInstance&); diff --git a/launcher/ui/pages/modplatform/flame/FlameResourceModels.cpp b/launcher/ui/pages/modplatform/flame/FlameResourceModels.cpp index cfe4080a..d0f109de 100644 --- a/launcher/ui/pages/modplatform/flame/FlameResourceModels.cpp +++ b/launcher/ui/pages/modplatform/flame/FlameResourceModels.cpp @@ -2,6 +2,7 @@ #include "Json.h" +#include "modplatform/flame/FlameAPI.h" #include "modplatform/flame/FlameModIndex.h" namespace ResourceDownload { @@ -9,7 +10,7 @@ namespace ResourceDownload { // NOLINTNEXTLINE(modernize-avoid-c-arrays) const char* FlameModModel::sorts[6]{ "Featured", "Popularity", "LastUpdated", "Name", "Author", "TotalDownloads" }; -FlameModModel::FlameModModel(FlameModPage* parent) : ModModel(parent, new FlameAPI) {} +FlameModModel::FlameModModel(BaseInstance const& base) : ModModel(base, new FlameAPI) {} void FlameModModel::loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj) { @@ -24,7 +25,7 @@ void FlameModModel::loadExtraPackInfo(ModPlatform::IndexedPack& m, QJsonObject& void FlameModModel::loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr) { - FlameMod::loadIndexedPackVersions(m, arr, APPLICATION->network(), &m_associated_page->m_base_instance); + FlameMod::loadIndexedPackVersions(m, arr, APPLICATION->network(), &m_base_instance); } auto FlameModModel::documentToArray(QJsonDocument& obj) const -> QJsonArray diff --git a/launcher/ui/pages/modplatform/flame/FlameResourceModels.h b/launcher/ui/pages/modplatform/flame/FlameResourceModels.h index 501937e2..7b253dce 100644 --- a/launcher/ui/pages/modplatform/flame/FlameResourceModels.h +++ b/launcher/ui/pages/modplatform/flame/FlameResourceModels.h @@ -1,9 +1,6 @@ #pragma once -#include "modplatform/flame/FlameAPI.h" - #include "ui/pages/modplatform/ModModel.h" - #include "ui/pages/modplatform/flame/FlameResourcePages.h" namespace ResourceDownload { @@ -12,10 +9,13 @@ class FlameModModel : public ModModel { Q_OBJECT public: - FlameModModel(FlameModPage* parent); + FlameModModel(const BaseInstance&); ~FlameModModel() override = default; private: + [[nodiscard]] QString debugName() const override { return Flame::debugName() + " (Model)"; } + [[nodiscard]] QString metaEntryBase() const override { return Flame::metaEntryBase(); } + void loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj) override; void loadExtraPackInfo(ModPlatform::IndexedPack& m, QJsonObject& obj) override; void loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr) override; diff --git a/launcher/ui/pages/modplatform/flame/FlameResourcePages.cpp b/launcher/ui/pages/modplatform/flame/FlameResourcePages.cpp index 2a8ab526..67737a76 100644 --- a/launcher/ui/pages/modplatform/flame/FlameResourcePages.cpp +++ b/launcher/ui/pages/modplatform/flame/FlameResourcePages.cpp @@ -45,7 +45,7 @@ namespace ResourceDownload { FlameModPage::FlameModPage(ModDownloadDialog* dialog, BaseInstance& instance) : ModPage(dialog, instance) { - m_model = new FlameModModel(this); + m_model = new FlameModModel(instance); m_ui->packView->setModel(m_model); // index is used to set the sorting with the flame api diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.cpp index ee96f0de..9d26ae05 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.cpp +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.cpp @@ -18,8 +18,6 @@ #include "ModrinthResourceModels.h" -#include "ui/pages/modplatform/modrinth/ModrinthResourcePages.h" - #include "modplatform/modrinth/ModrinthAPI.h" #include "modplatform/modrinth/ModrinthPackIndex.h" @@ -28,7 +26,7 @@ namespace ResourceDownload { // NOLINTNEXTLINE(modernize-avoid-c-arrays) const char* ModrinthModModel::sorts[5]{ "relevance", "downloads", "follows", "updated", "newest" }; -ModrinthModModel::ModrinthModModel(ModrinthModPage* parent) : ModModel(parent, new ModrinthAPI){}; +ModrinthModModel::ModrinthModModel(BaseInstance const& base) : ModModel(base, new ModrinthAPI){}; void ModrinthModModel::loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj) { @@ -42,7 +40,7 @@ void ModrinthModModel::loadExtraPackInfo(ModPlatform::IndexedPack& m, QJsonObjec void ModrinthModModel::loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr) { - ::Modrinth::loadIndexedPackVersions(m, arr, APPLICATION->network(), &m_associated_page->m_base_instance); + ::Modrinth::loadIndexedPackVersions(m, arr, APPLICATION->network(), &m_base_instance); } auto ModrinthModModel::documentToArray(QJsonDocument& obj) const -> QJsonArray diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.h b/launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.h index b0088a73..798a70e6 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.h +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.h @@ -19,6 +19,7 @@ #pragma once #include "ui/pages/modplatform/ModModel.h" +#include "ui/pages/modplatform/modrinth/ModrinthResourcePages.h" namespace ResourceDownload { @@ -28,10 +29,13 @@ class ModrinthModModel : public ModModel { Q_OBJECT public: - ModrinthModModel(ModrinthModPage* parent); + ModrinthModModel(const BaseInstance&); ~ModrinthModModel() override = default; private: + [[nodiscard]] QString debugName() const override { return Modrinth::debugName() + " (Model)"; } + [[nodiscard]] QString metaEntryBase() const override { return Modrinth::metaEntryBase(); } + void loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj) override; void loadExtraPackInfo(ModPlatform::IndexedPack& m, QJsonObject& obj) override; void loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr) override; diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.cpp index 1352e2f6..88621e05 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.cpp +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.cpp @@ -47,7 +47,7 @@ namespace ResourceDownload { ModrinthModPage::ModrinthModPage(ModDownloadDialog* dialog, BaseInstance& instance) : ModPage(dialog, instance) { - m_model = new ModrinthModModel(this); + m_model = new ModrinthModModel(instance); m_ui->packView->setModel(m_model); // index is used to set the sorting with the modrinth api -- cgit From c8eca4fb8508a22b9d4819d57627dd684f8d98c5 Mon Sep 17 00:00:00 2001 From: flow Date: Sun, 18 Dec 2022 17:03:39 -0300 Subject: fix: build with qt5.12 on Linux and pedantic flag Signed-off-by: flow --- launcher/modplatform/ResourceAPI.h | 1 + launcher/ui/dialogs/ResourceDownloadDialog.h | 1 + launcher/ui/pages/modplatform/ResourceModel.h | 15 ++++++++------- launcher/ui/pages/modplatform/ResourcePage.cpp | 4 +++- launcher/ui/pages/modplatform/ResourcePage.h | 4 +++- launcher/ui/pages/modplatform/flame/FlameResourcePages.h | 2 +- .../pages/modplatform/modrinth/ModrinthResourceModels.cpp | 2 +- .../ui/pages/modplatform/modrinth/ModrinthResourcePages.h | 2 +- 8 files changed, 19 insertions(+), 12 deletions(-) (limited to 'launcher/ui/dialogs') diff --git a/launcher/modplatform/ResourceAPI.h b/launcher/modplatform/ResourceAPI.h index 49aac712..78441c34 100644 --- a/launcher/modplatform/ResourceAPI.h +++ b/launcher/modplatform/ResourceAPI.h @@ -39,6 +39,7 @@ #include #include +#include #include "../Version.h" diff --git a/launcher/ui/dialogs/ResourceDownloadDialog.h b/launcher/ui/dialogs/ResourceDownloadDialog.h index 95a5e628..34120350 100644 --- a/launcher/ui/dialogs/ResourceDownloadDialog.h +++ b/launcher/ui/dialogs/ResourceDownloadDialog.h @@ -21,6 +21,7 @@ #include #include +#include #include #include "QObjectPtr.h" diff --git a/launcher/ui/pages/modplatform/ResourceModel.h b/launcher/ui/pages/modplatform/ResourceModel.h index 45af33a2..d0b9234b 100644 --- a/launcher/ui/pages/modplatform/ResourceModel.h +++ b/launcher/ui/pages/modplatform/ResourceModel.h @@ -35,19 +35,16 @@ class ResourceModel : public QAbstractListModel { [[nodiscard]] virtual auto metaEntryBase() const -> QString = 0; [[nodiscard]] inline int rowCount(const QModelIndex& parent) const override { return parent.isValid() ? 0 : m_packs.size(); } - [[nodiscard]] inline int columnCount(const QModelIndex& parent) const override { return parent.isValid() ? 0 : 1; }; - [[nodiscard]] inline auto flags(const QModelIndex& index) const -> Qt::ItemFlags override { return QAbstractListModel::flags(index); }; + [[nodiscard]] inline int columnCount(const QModelIndex& parent) const override { return parent.isValid() ? 0 : 1; } + [[nodiscard]] inline auto flags(const QModelIndex& index) const -> Qt::ItemFlags override { return QAbstractListModel::flags(index); } inline void addActiveJob(Task::Ptr ptr) { m_current_job.addTask(ptr); if (!m_current_job.isRunning()) m_current_job.start(); } inline Task const& activeJob() { return m_current_job; } - signals: - void versionListUpdated(); - void projectInfoUpdated(); - public slots: void fetchMore(const QModelIndex& parent) override; - [[nodiscard]] inline bool canFetchMore(const QModelIndex& parent) const override + // NOTE: Can't use [[nodiscard]] here because of https://bugreports.qt.io/browse/QTBUG-58628 on Qt 5.12 + inline bool canFetchMore(const QModelIndex& parent) const override { return parent.isValid() ? false : m_search_state == SearchState::CanFetchMore; } @@ -105,6 +102,10 @@ class ResourceModel : public QAbstractListModel { /* Default search request callbacks */ void searchRequestFailed(QString reason, int network_error_code); void searchRequestAborted(); + + signals: + void versionListUpdated(); + void projectInfoUpdated(); }; } // namespace ResourceDownload diff --git a/launcher/ui/pages/modplatform/ResourcePage.cpp b/launcher/ui/pages/modplatform/ResourcePage.cpp index e04278af..6e6868c5 100644 --- a/launcher/ui/pages/modplatform/ResourcePage.cpp +++ b/launcher/ui/pages/modplatform/ResourcePage.cpp @@ -302,7 +302,9 @@ void ResourcePage::openUrl(const QUrl& url) QRegularExpressionMatch match; QString page; - for (auto&& [regex, candidate] : urlHandlers().asKeyValueRange()) { + auto handlers = urlHandlers(); + for (auto it = handlers.constKeyValueBegin(); it != handlers.constKeyValueEnd(); it++) { + auto&& [regex, candidate] = *it; if (match = QRegularExpression(regex).match(address); match.hasMatch()) { page = candidate; break; diff --git a/launcher/ui/pages/modplatform/ResourcePage.h b/launcher/ui/pages/modplatform/ResourcePage.h index b51c7ccb..b95c5a40 100644 --- a/launcher/ui/pages/modplatform/ResourcePage.h +++ b/launcher/ui/pages/modplatform/ResourcePage.h @@ -75,8 +75,10 @@ class ResourcePage : public QWidget, public BasePage { void onVersionSelectionChanged(QString data); void onResourceSelected(); + // NOTE: Can't use [[nodiscard]] here because of https://bugreports.qt.io/browse/QTBUG-58628 on Qt 5.12 + /** Associates regex expressions to pages in the order they're given in the map. */ - [[nodiscard]] virtual QMap urlHandlers() const = 0; + virtual QMap urlHandlers() const = 0; virtual void openUrl(const QUrl&); /** Whether the version is opted out or not. Currently only makes sense in CF. */ diff --git a/launcher/ui/pages/modplatform/flame/FlameResourcePages.h b/launcher/ui/pages/modplatform/flame/FlameResourcePages.h index 6c7d0247..12b51aa9 100644 --- a/launcher/ui/pages/modplatform/flame/FlameResourcePages.h +++ b/launcher/ui/pages/modplatform/flame/FlameResourcePages.h @@ -49,7 +49,7 @@ static inline QString displayName() { return "CurseForge"; } static inline QIcon icon() { return APPLICATION->getThemedIcon("flame"); } static inline QString id() { return "curseforge"; } static inline QString debugName() { return "Flame"; } -static inline QString metaEntryBase() { return "FlameMods"; }; +static inline QString metaEntryBase() { return "FlameMods"; } } class FlameModPage : public ModPage { diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.cpp index 9d26ae05..895e23fd 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.cpp +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.cpp @@ -26,7 +26,7 @@ namespace ResourceDownload { // NOLINTNEXTLINE(modernize-avoid-c-arrays) const char* ModrinthModModel::sorts[5]{ "relevance", "downloads", "follows", "updated", "newest" }; -ModrinthModModel::ModrinthModModel(BaseInstance const& base) : ModModel(base, new ModrinthAPI){}; +ModrinthModModel::ModrinthModModel(BaseInstance const& base) : ModModel(base, new ModrinthAPI) {} void ModrinthModModel::loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj) { diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.h b/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.h index 07b32c0c..a263bd44 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.h +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.h @@ -48,7 +48,7 @@ static inline QString displayName() { return "Modrinth"; } static inline QIcon icon() { return APPLICATION->getThemedIcon("modrinth"); } static inline QString id() { return "modrinth"; } static inline QString debugName() { return "Modrinth"; } -static inline QString metaEntryBase() { return "ModrinthPacks"; }; +static inline QString metaEntryBase() { return "ModrinthPacks"; } } class ModrinthModPage : public ModPage { -- cgit From b3330cb0da39db6e8add3bbe35cd6d417374146a Mon Sep 17 00:00:00 2001 From: flow Date: Fri, 30 Dec 2022 16:59:35 -0300 Subject: fix(RD): correctly set the strings for the specific resource names Signed-off-by: flow --- launcher/ui/dialogs/ResourceDownloadDialog.cpp | 3 ++- launcher/ui/pages/modplatform/ModPage.h | 3 +++ launcher/ui/pages/modplatform/ResourcePage.cpp | 4 ++++ launcher/ui/pages/modplatform/ResourcePage.h | 3 +++ launcher/ui/pages/modplatform/ResourcePage.ui | 12 ++---------- 5 files changed, 14 insertions(+), 11 deletions(-) (limited to 'launcher/ui/dialogs') diff --git a/launcher/ui/dialogs/ResourceDownloadDialog.cpp b/launcher/ui/dialogs/ResourceDownloadDialog.cpp index 2eb85928..fa3352b3 100644 --- a/launcher/ui/dialogs/ResourceDownloadDialog.cpp +++ b/launcher/ui/dialogs/ResourceDownloadDialog.cpp @@ -64,7 +64,6 @@ ResourceDownloadDialog::ResourceDownloadDialog(QWidget* parent, const std::share HelpButton->setAutoDefault(false); setWindowModality(Qt::WindowModal); - setWindowTitle(dialogTitle()); } void ResourceDownloadDialog::accept() @@ -206,6 +205,8 @@ void ResourceDownloadDialog::selectedPageChanged(BasePage* previous, BasePage* s ModDownloadDialog::ModDownloadDialog(QWidget* parent, const std::shared_ptr& mods, BaseInstance* instance) : ResourceDownloadDialog(parent, mods), m_instance(instance) { + setWindowTitle(dialogTitle()); + initializeContainer(); connectButtons(); diff --git a/launcher/ui/pages/modplatform/ModPage.h b/launcher/ui/pages/modplatform/ModPage.h index 2fda3b68..a3aab1de 100644 --- a/launcher/ui/pages/modplatform/ModPage.h +++ b/launcher/ui/pages/modplatform/ModPage.h @@ -39,6 +39,9 @@ class ModPage : public ResourcePage { ~ModPage() override = default; + //: The plural version of 'mod' + [[nodiscard]] inline QString resourcesString() const override { return tr("mods"); } + //: The singular version of 'mods' [[nodiscard]] inline QString resourceString() const override { return tr("mod"); } [[nodiscard]] QMap urlHandlers() const override; diff --git a/launcher/ui/pages/modplatform/ResourcePage.cpp b/launcher/ui/pages/modplatform/ResourcePage.cpp index 200943da..bfa7e33d 100644 --- a/launcher/ui/pages/modplatform/ResourcePage.cpp +++ b/launcher/ui/pages/modplatform/ResourcePage.cpp @@ -57,6 +57,10 @@ void ResourcePage::openedImpl() if (!supportsFiltering()) m_ui->resourceFilterButton->setVisible(false); + //: String in the search bar of the mod downloading dialog + m_ui->searchEdit->setPlaceholderText(tr("Search for %1...").arg(resourcesString())); + m_ui->resourceSelectionButton->setText(tr("Select %1 for download").arg(resourceString())); + updateSelectionButton(); triggerSearch(); } diff --git a/launcher/ui/pages/modplatform/ResourcePage.h b/launcher/ui/pages/modplatform/ResourcePage.h index 547c4056..71fc6593 100644 --- a/launcher/ui/pages/modplatform/ResourcePage.h +++ b/launcher/ui/pages/modplatform/ResourcePage.h @@ -36,6 +36,9 @@ class ResourcePage : public QWidget, public BasePage { [[nodiscard]] virtual auto metaEntryBase() const -> QString = 0; [[nodiscard]] virtual auto debugName() const -> QString = 0; + //: The plural version of 'resource' + [[nodiscard]] virtual inline QString resourcesString() const { return tr("resources"); } + //: The singular version of 'resources' [[nodiscard]] virtual inline QString resourceString() const { return tr("resource"); } /* Features this resource's page supports */ diff --git a/launcher/ui/pages/modplatform/ResourcePage.ui b/launcher/ui/pages/modplatform/ResourcePage.ui index 8fe1d613..73a9d3b1 100644 --- a/launcher/ui/pages/modplatform/ResourcePage.ui +++ b/launcher/ui/pages/modplatform/ResourcePage.ui @@ -49,11 +49,7 @@ - - - Search for resources... - - + @@ -74,11 +70,7 @@ - - - Select resource for download - - + -- cgit From bd36f8e220fb3019b0a9588b21ed1cbce5afbf93 Mon Sep 17 00:00:00 2001 From: flow Date: Sun, 8 Jan 2023 12:28:55 -0300 Subject: fix(RD): set resource strings for ReviewMessageBox too Signed-off-by: flow --- launcher/ui/dialogs/ResourceDownloadDialog.cpp | 5 +++-- launcher/ui/dialogs/ResourceDownloadDialog.h | 6 +++--- launcher/ui/dialogs/ReviewMessageBox.cpp | 8 ++++++++ launcher/ui/dialogs/ReviewMessageBox.h | 2 ++ launcher/ui/dialogs/ReviewMessageBox.ui | 14 +++++--------- 5 files changed, 21 insertions(+), 14 deletions(-) (limited to 'launcher/ui/dialogs') diff --git a/launcher/ui/dialogs/ResourceDownloadDialog.cpp b/launcher/ui/dialogs/ResourceDownloadDialog.cpp index fa3352b3..147373c9 100644 --- a/launcher/ui/dialogs/ResourceDownloadDialog.cpp +++ b/launcher/ui/dialogs/ResourceDownloadDialog.cpp @@ -99,7 +99,7 @@ void ResourceDownloadDialog::initializeContainer() void ResourceDownloadDialog::connectButtons() { auto OkButton = m_buttons.button(QDialogButtonBox::Ok); - OkButton->setToolTip(tr("Opens a new popup to review your selected %1 and confirm your selection. Shortcut: Ctrl+Return").arg(resourceString())); + OkButton->setToolTip(tr("Opens a new popup to review your selected %1 and confirm your selection. Shortcut: Ctrl+Return").arg(resourcesString())); connect(OkButton, &QPushButton::clicked, this, &ResourceDownloadDialog::confirm); auto CancelButton = m_buttons.button(QDialogButtonBox::Cancel); @@ -114,7 +114,8 @@ void ResourceDownloadDialog::confirm() auto keys = m_selected.keys(); keys.sort(Qt::CaseInsensitive); - auto confirm_dialog = ReviewMessageBox::create(this, tr("Confirm %1 to download").arg(resourceString())); + auto confirm_dialog = ReviewMessageBox::create(this, tr("Confirm %1 to download").arg(resourcesString())); + confirm_dialog->retranslateUi(resourcesString()); for (auto& task : keys) { confirm_dialog->appendResource({ task, m_selected.find(task).value()->getFilename() }); diff --git a/launcher/ui/dialogs/ResourceDownloadDialog.h b/launcher/ui/dialogs/ResourceDownloadDialog.h index 34120350..19843532 100644 --- a/launcher/ui/dialogs/ResourceDownloadDialog.h +++ b/launcher/ui/dialogs/ResourceDownloadDialog.h @@ -52,9 +52,9 @@ class ResourceDownloadDialog : public QDialog, public BasePageProvider { void connectButtons(); //: String that gets appended to the download dialog title ("Download " + resourcesString()) - [[nodiscard]] virtual QString resourceString() const { return tr("resources"); } + [[nodiscard]] virtual QString resourcesString() const { return tr("resources"); } - QString dialogTitle() override { return tr("Download %1").arg(resourceString()); }; + QString dialogTitle() override { return tr("Download %1").arg(resourcesString()); }; bool selectPage(QString pageId); ResourcePage* getSelectedPage(); @@ -99,7 +99,7 @@ class ModDownloadDialog final : public ResourceDownloadDialog { ~ModDownloadDialog() override = default; //: String that gets appended to the mod download dialog title ("Download " + resourcesString()) - [[nodiscard]] QString resourceString() const override { return tr("mods"); } + [[nodiscard]] QString resourcesString() const override { return tr("mods"); } [[nodiscard]] QString geometrySaveKey() const override { return "ModDownloadGeometry"; } QList getPages() override; diff --git a/launcher/ui/dialogs/ReviewMessageBox.cpp b/launcher/ui/dialogs/ReviewMessageBox.cpp index f45a9c4a..9c638d1f 100644 --- a/launcher/ui/dialogs/ReviewMessageBox.cpp +++ b/launcher/ui/dialogs/ReviewMessageBox.cpp @@ -55,3 +55,11 @@ auto ReviewMessageBox::deselectedResources() -> QStringList return list; } + +void ReviewMessageBox::retranslateUi(QString resources_name) +{ + setWindowTitle(tr("Confirm %1 selection").arg(resources_name)); + + ui->explainLabel->setText(tr("You're about to download the following %1:").arg(resources_name)); + ui->onlyCheckedLabel->setText(tr("Only %1 with a check will be downloaded!").arg(resources_name)); +} diff --git a/launcher/ui/dialogs/ReviewMessageBox.h b/launcher/ui/dialogs/ReviewMessageBox.h index e2d0ce37..7ee0d65d 100644 --- a/launcher/ui/dialogs/ReviewMessageBox.h +++ b/launcher/ui/dialogs/ReviewMessageBox.h @@ -20,6 +20,8 @@ class ReviewMessageBox : public QDialog { void appendResource(ResourceInformation&& info); auto deselectedResources() -> QStringList; + void retranslateUi(QString resources_name); + ~ReviewMessageBox() override; protected: diff --git a/launcher/ui/dialogs/ReviewMessageBox.ui b/launcher/ui/dialogs/ReviewMessageBox.ui index ab3bcc2f..bf53ae80 100644 --- a/launcher/ui/dialogs/ReviewMessageBox.ui +++ b/launcher/ui/dialogs/ReviewMessageBox.ui @@ -10,9 +10,6 @@ 350 - - Confirm mod selection - true @@ -39,22 +36,21 @@ + + + + + - - You're about to download the following mods: - - - Only mods with a check will be downloaded! - -- cgit From c294c2d1df57c3d599fdea65bab9bb97b1fd699f Mon Sep 17 00:00:00 2001 From: flow Date: Sun, 8 Jan 2023 12:33:10 -0300 Subject: refactor(RD): allow setting custom folder target for downloaded resources Signed-off-by: flow --- launcher/ResourceDownloadTask.cpp | 12 +++++++++++- launcher/ResourceDownloadTask.h | 3 +-- launcher/modplatform/ModIndex.h | 1 + launcher/ui/dialogs/ResourceDownloadDialog.cpp | 3 ++- launcher/ui/dialogs/ReviewMessageBox.cpp | 16 ++++++++++++++++ launcher/ui/dialogs/ReviewMessageBox.h | 3 ++- 6 files changed, 33 insertions(+), 5 deletions(-) (limited to 'launcher/ui/dialogs') diff --git a/launcher/ResourceDownloadTask.cpp b/launcher/ResourceDownloadTask.cpp index 687eaf51..8c9dae6f 100644 --- a/launcher/ResourceDownloadTask.cpp +++ b/launcher/ResourceDownloadTask.cpp @@ -40,7 +40,17 @@ ResourceDownloadTask::ResourceDownloadTask(ModPlatform::IndexedPack pack, m_filesNetJob.reset(new NetJob(tr("Resource download"), APPLICATION->network())); m_filesNetJob->setStatus(tr("Downloading resource:\n%1").arg(m_pack_version.downloadUrl)); - m_filesNetJob->addNetAction(Net::Download::makeFile(m_pack_version.downloadUrl, m_pack_model->dir().absoluteFilePath(getFilename()))); + QDir dir { m_pack_model->dir() }; + { + // FIXME: Make this more generic. May require adding additional info to IndexedVersion, + // or adquiring a reference to the base instance. + if (!m_pack_version.custom_target_folder.isEmpty()) { + dir.cdUp(); + dir.cd(m_pack_version.custom_target_folder); + } + } + + m_filesNetJob->addNetAction(Net::Download::makeFile(m_pack_version.downloadUrl, dir.absoluteFilePath(getFilename()))); connect(m_filesNetJob.get(), &NetJob::succeeded, this, &ResourceDownloadTask::downloadSucceeded); connect(m_filesNetJob.get(), &NetJob::progress, this, &ResourceDownloadTask::downloadProgressChanged); connect(m_filesNetJob.get(), &NetJob::failed, this, &ResourceDownloadTask::downloadFailed); diff --git a/launcher/ResourceDownloadTask.h b/launcher/ResourceDownloadTask.h index 275ddbe1..5ce39d69 100644 --- a/launcher/ResourceDownloadTask.h +++ b/launcher/ResourceDownloadTask.h @@ -32,6 +32,7 @@ class ResourceDownloadTask : public SequentialTask { public: explicit ResourceDownloadTask(ModPlatform::IndexedPack pack, ModPlatform::IndexedVersion version, const std::shared_ptr packs, bool is_indexed = true); const QString& getFilename() const { return m_pack_version.fileName; } + const QString& getCustomPath() const { return m_pack_version.custom_target_folder; } const QVariant& getVersionID() const { return m_pack_version.fileId; } private: @@ -43,9 +44,7 @@ private: LocalModUpdateTask::Ptr m_update_task; void downloadProgressChanged(qint64 current, qint64 total); - void downloadFailed(QString reason); - void downloadSucceeded(); std::tuple to_delete {"", ""}; diff --git a/launcher/modplatform/ModIndex.h b/launcher/modplatform/ModIndex.h index cd40a6ba..b1f8050d 100644 --- a/launcher/modplatform/ModIndex.h +++ b/launcher/modplatform/ModIndex.h @@ -68,6 +68,7 @@ struct IndexedVersion { // For internal use, not provided by APIs bool is_currently_selected = false; + QString custom_target_folder; }; struct ExtraPackData { diff --git a/launcher/ui/dialogs/ResourceDownloadDialog.cpp b/launcher/ui/dialogs/ResourceDownloadDialog.cpp index 147373c9..b9367c16 100644 --- a/launcher/ui/dialogs/ResourceDownloadDialog.cpp +++ b/launcher/ui/dialogs/ResourceDownloadDialog.cpp @@ -118,7 +118,8 @@ void ResourceDownloadDialog::confirm() confirm_dialog->retranslateUi(resourcesString()); for (auto& task : keys) { - confirm_dialog->appendResource({ task, m_selected.find(task).value()->getFilename() }); + auto selected = m_selected.constFind(task).value(); + confirm_dialog->appendResource({ task, selected->getFilename(), selected->getCustomPath() }); } if (confirm_dialog->exec()) { diff --git a/launcher/ui/dialogs/ReviewMessageBox.cpp b/launcher/ui/dialogs/ReviewMessageBox.cpp index 9c638d1f..7b2df278 100644 --- a/launcher/ui/dialogs/ReviewMessageBox.cpp +++ b/launcher/ui/dialogs/ReviewMessageBox.cpp @@ -1,6 +1,8 @@ #include "ReviewMessageBox.h" #include "ui_ReviewMessageBox.h" +#include "Application.h" + #include ReviewMessageBox::ReviewMessageBox(QWidget* parent, QString const& title, QString const& icon) @@ -11,6 +13,10 @@ ReviewMessageBox::ReviewMessageBox(QWidget* parent, QString const& title, QStrin auto back_button = ui->buttonBox->button(QDialogButtonBox::Cancel); back_button->setText(tr("Back")); + ui->modTreeWidget->header()->setSectionResizeMode(0, QHeaderView::Stretch); + ui->modTreeWidget->header()->setStretchLastSection(false); + ui->modTreeWidget->header()->setSectionResizeMode(1, QHeaderView::ResizeToContents); + connect(ui->buttonBox, &QDialogButtonBox::accepted, this, &ReviewMessageBox::accept); connect(ui->buttonBox, &QDialogButtonBox::rejected, this, &ReviewMessageBox::reject); } @@ -36,6 +42,16 @@ void ReviewMessageBox::appendResource(ResourceInformation&& info) itemTop->insertChildren(0, { filenameItem }); + if (!info.custom_file_path.isEmpty()) { + auto customPathItem = new QTreeWidgetItem(itemTop); + customPathItem->setText(0, tr("This download will be placed in: %1").arg(info.custom_file_path)); + + itemTop->insertChildren(1, { customPathItem }); + + itemTop->setIcon(1, QIcon(APPLICATION->getThemedIcon("status-yellow"))); + itemTop->setToolTip(1, tr("This file will be downloaded to a folder location different from the default, possibly due to its loader requiring it.")); + } + ui->modTreeWidget->addTopLevelItem(itemTop); } diff --git a/launcher/ui/dialogs/ReviewMessageBox.h b/launcher/ui/dialogs/ReviewMessageBox.h index 7ee0d65d..5ec2bc23 100644 --- a/launcher/ui/dialogs/ReviewMessageBox.h +++ b/launcher/ui/dialogs/ReviewMessageBox.h @@ -12,9 +12,10 @@ class ReviewMessageBox : public QDialog { public: static auto create(QWidget* parent, QString&& title, QString&& icon = "") -> ReviewMessageBox*; - using ResourceInformation = struct { + using ResourceInformation = struct res_info { QString name; QString filename; + QString custom_file_path {}; }; void appendResource(ResourceInformation&& info); -- cgit From 29f7ea752fd34bdea64a7c7f2c505982ac39ce0d Mon Sep 17 00:00:00 2001 From: flow Date: Tue, 24 Jan 2023 16:52:09 -0300 Subject: refactor: make shared_qobject_ptr ctor explicit This turns issues like creating two shared ptrs from a single raw ptr from popping up at runtime, instead making them a compile error. Signed-off-by: flow --- launcher/Application.cpp | 2 +- launcher/InstanceImportTask.cpp | 4 +- launcher/LaunchController.cpp | 6 +- launcher/QObjectPtr.h | 16 +++- launcher/java/JavaInstallList.cpp | 4 +- launcher/launch/steps/CheckJava.cpp | 2 +- launcher/meta/BaseEntity.cpp | 2 +- launcher/minecraft/AssetsUtils.cpp | 2 +- launcher/minecraft/ComponentUpdateTask.cpp | 2 +- launcher/minecraft/MinecraftInstance.cpp | 39 ++++---- launcher/minecraft/MinecraftUpdate.cpp | 8 +- launcher/minecraft/PackProfile.cpp | 20 ++-- launcher/minecraft/PackProfile.h | 4 +- launcher/minecraft/auth/MinecraftAccount.cpp | 4 +- launcher/minecraft/auth/flows/MSA.cpp | 36 +++---- launcher/minecraft/auth/flows/Mojang.cpp | 16 ++-- launcher/minecraft/auth/flows/Offline.cpp | 4 +- launcher/minecraft/mod/ResourcePackFolderModel.cpp | 2 +- launcher/minecraft/mod/TexturePackFolderModel.cpp | 2 +- launcher/minecraft/mod/tasks/BasicFolderLoadTask.h | 8 +- launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp | 8 +- launcher/minecraft/update/AssetUpdateTask.cpp | 2 +- launcher/minecraft/update/FMLLibrariesTask.cpp | 10 +- launcher/minecraft/update/LibrariesTask.cpp | 2 +- launcher/modplatform/CheckUpdateTask.h | 4 +- launcher/modplatform/EnsureMetadataTask.cpp | 8 +- launcher/modplatform/EnsureMetadataTask.h | 2 +- .../modplatform/atlauncher/ATLPackInstallTask.cpp | 19 ++-- launcher/modplatform/flame/FileResolvingTask.cpp | 4 +- launcher/modplatform/flame/FlameAPI.cpp | 16 ++-- launcher/modplatform/flame/FlameCheckUpdate.cpp | 2 +- .../flame/FlameInstanceCreationTask.cpp | 4 +- launcher/modplatform/helpers/HashUtils.cpp | 8 +- .../modplatform/helpers/NetworkResourceAPI.cpp | 18 ++-- launcher/modplatform/legacy_ftb/PackFetchTask.cpp | 2 +- .../modplatform/legacy_ftb/PackInstallTask.cpp | 2 +- .../modplatform/modpacksch/FTBPackInstallTask.cpp | 22 ++--- launcher/modplatform/modrinth/ModrinthAPI.cpp | 21 ++--- .../modplatform/modrinth/ModrinthCheckUpdate.cpp | 2 +- .../modrinth/ModrinthInstanceCreationTask.cpp | 2 +- .../technic/SingleZipPackInstallTask.cpp | 4 +- .../modplatform/technic/SolderPackInstallTask.cpp | 6 +- launcher/net/Download.cpp | 11 +-- launcher/net/Download.h | 3 - launcher/net/Upload.cpp | 2 +- launcher/net/Upload.h | 2 + launcher/news/NewsChecker.cpp | 6 +- launcher/tasks/ConcurrentTask.h | 2 + launcher/translations/TranslationsModel.cpp | 4 +- launcher/ui/dialogs/ModUpdateDialog.cpp | 30 +++--- launcher/ui/dialogs/ModUpdateDialog.h | 10 +- launcher/ui/dialogs/ResourceDownloadDialog.cpp | 2 +- launcher/ui/pages/instance/VersionPage.cpp | 2 +- launcher/ui/pages/instance/VersionPage.h | 2 +- launcher/ui/pages/modplatform/ResourceModel.cpp | 2 +- .../pages/modplatform/atlauncher/AtlListModel.cpp | 6 +- launcher/ui/pages/modplatform/flame/FlameModel.cpp | 6 +- launcher/ui/pages/modplatform/ftb/FtbListModel.cpp | 18 ++-- .../pages/modplatform/modrinth/ModrinthModel.cpp | 6 +- .../ui/pages/modplatform/technic/TechnicModel.cpp | 6 +- .../ui/pages/modplatform/technic/TechnicPage.cpp | 8 +- tests/DummyResourceAPI.h | 5 +- tests/Task_test.cpp | 104 +++++++++++---------- 63 files changed, 301 insertions(+), 287 deletions(-) (limited to 'launcher/ui/dialogs') diff --git a/launcher/Application.cpp b/launcher/Application.cpp index d4a1284f..387f735c 100644 --- a/launcher/Application.cpp +++ b/launcher/Application.cpp @@ -679,7 +679,7 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv) // initialize network access and proxy setup { - m_network = new QNetworkAccessManager(); + m_network.reset(new QNetworkAccessManager()); QString proxyTypeStr = settings()->get("ProxyType").toString(); QString addr = settings()->get("ProxyAddr").toString(); int port = settings()->get("ProxyPort").value(); diff --git a/launcher/InstanceImportTask.cpp b/launcher/InstanceImportTask.cpp index 6b3fd296..70bf5784 100644 --- a/launcher/InstanceImportTask.cpp +++ b/launcher/InstanceImportTask.cpp @@ -88,7 +88,7 @@ void InstanceImportTask::executeTask() entry->setStale(true); m_archivePath = entry->getFullPath(); - m_filesNetJob = new NetJob(tr("Modpack download"), APPLICATION->network()); + m_filesNetJob.reset(new NetJob(tr("Modpack download"), APPLICATION->network())); m_filesNetJob->addNetAction(Net::Download::makeCached(m_sourceUrl, entry)); connect(m_filesNetJob.get(), &NetJob::succeeded, this, &InstanceImportTask::downloadSucceeded); @@ -301,7 +301,7 @@ void InstanceImportTask::processFlame() void InstanceImportTask::processTechnic() { - shared_qobject_ptr packProcessor = new Technic::TechnicPackProcessor(); + shared_qobject_ptr packProcessor{ new Technic::TechnicPackProcessor }; connect(packProcessor.get(), &Technic::TechnicPackProcessor::succeeded, this, &InstanceImportTask::emitSucceeded); connect(packProcessor.get(), &Technic::TechnicPackProcessor::failed, this, &InstanceImportTask::emitFailed); packProcessor->run(m_globalSettings, name(), m_instIcon, m_stagingPath); diff --git a/launcher/LaunchController.cpp b/launcher/LaunchController.cpp index 9741fd95..070ee283 100644 --- a/launcher/LaunchController.cpp +++ b/launcher/LaunchController.cpp @@ -382,15 +382,15 @@ void LaunchController::launchInstance() } resolved_servers = resolved_servers + "]\n\n"; } - m_launcher->prependStep(new TextPrint(m_launcher.get(), resolved_servers, MessageLevel::Launcher)); + m_launcher->prependStep(makeShared(m_launcher.get(), resolved_servers, MessageLevel::Launcher)); } else { online_mode = m_demo ? "demo" : "offline"; } - m_launcher->prependStep(new TextPrint(m_launcher.get(), "Launched instance in " + online_mode + " mode\n", MessageLevel::Launcher)); + m_launcher->prependStep(makeShared(m_launcher.get(), "Launched instance in " + online_mode + " mode\n", MessageLevel::Launcher)); // Prepend Version - m_launcher->prependStep(new TextPrint(m_launcher.get(), BuildConfig.LAUNCHER_DISPLAYNAME + " version: " + BuildConfig.printableVersionString() + "\n\n", MessageLevel::Launcher)); + m_launcher->prependStep(makeShared(m_launcher.get(), BuildConfig.LAUNCHER_DISPLAYNAME + " version: " + BuildConfig.printableVersionString() + "\n\n", MessageLevel::Launcher)); m_launcher->start(); } diff --git a/launcher/QObjectPtr.h b/launcher/QObjectPtr.h index ec466096..a1c64b43 100644 --- a/launcher/QObjectPtr.h +++ b/launcher/QObjectPtr.h @@ -20,8 +20,8 @@ using unique_qobject_ptr = QScopedPointer; template class shared_qobject_ptr : public QSharedPointer { public: - constexpr shared_qobject_ptr() : QSharedPointer() {} - constexpr shared_qobject_ptr(T* ptr) : QSharedPointer(ptr, &QObject::deleteLater) {} + constexpr explicit shared_qobject_ptr() : QSharedPointer() {} + constexpr explicit shared_qobject_ptr(T* ptr) : QSharedPointer(ptr, &QObject::deleteLater) {} constexpr shared_qobject_ptr(std::nullptr_t null_ptr) : QSharedPointer(null_ptr, &QObject::deleteLater) {} template @@ -33,9 +33,21 @@ class shared_qobject_ptr : public QSharedPointer { {} void reset() { QSharedPointer::reset(); } + void reset(T*&& other) + { + shared_qobject_ptr t(other); + this->swap(t); + } void reset(const shared_qobject_ptr& other) { shared_qobject_ptr t(other); this->swap(t); } }; + +template +shared_qobject_ptr makeShared(Args... args) +{ + auto obj = new T(args...); + return shared_qobject_ptr(obj); +} diff --git a/launcher/java/JavaInstallList.cpp b/launcher/java/JavaInstallList.cpp index e2f0aa00..b29af857 100644 --- a/launcher/java/JavaInstallList.cpp +++ b/launcher/java/JavaInstallList.cpp @@ -67,7 +67,7 @@ void JavaInstallList::load() if(m_status != Status::InProgress) { m_status = Status::InProgress; - m_loadTask = new JavaListLoadTask(this); + m_loadTask.reset(new JavaListLoadTask(this)); m_loadTask->start(); } } @@ -167,7 +167,7 @@ void JavaListLoadTask::executeTask() JavaUtils ju; QList candidate_paths = ju.FindJavaPaths(); - m_job = new JavaCheckerJob("Java detection"); + m_job.reset(new JavaCheckerJob("Java detection")); connect(m_job.get(), &Task::finished, this, &JavaListLoadTask::javaCheckerFinished); connect(m_job.get(), &Task::progress, this, &Task::setProgress); diff --git a/launcher/launch/steps/CheckJava.cpp b/launcher/launch/steps/CheckJava.cpp index 7aeb61bf..f0187586 100644 --- a/launcher/launch/steps/CheckJava.cpp +++ b/launcher/launch/steps/CheckJava.cpp @@ -93,7 +93,7 @@ void CheckJava::executeTask() || storedArchitecture.size() == 0 || storedRealArchitecture.size() == 0 || storedVendor.size() == 0) { - m_JavaChecker = new JavaChecker(); + m_JavaChecker.reset(new JavaChecker); emit logLine(QString("Checking Java version..."), MessageLevel::Launcher); connect(m_JavaChecker.get(), &JavaChecker::checkFinished, this, &CheckJava::checkJavaFinished); m_JavaChecker->m_path = realJavaPath; diff --git a/launcher/meta/BaseEntity.cpp b/launcher/meta/BaseEntity.cpp index de4e1012..97815eba 100644 --- a/launcher/meta/BaseEntity.cpp +++ b/launcher/meta/BaseEntity.cpp @@ -126,7 +126,7 @@ void Meta::BaseEntity::load(Net::Mode loadType) { return; } - m_updateTask = new NetJob(QObject::tr("Download of meta file %1").arg(localFilename()), APPLICATION->network()); + m_updateTask.reset(new NetJob(QObject::tr("Download of meta file %1").arg(localFilename()), APPLICATION->network())); auto url = this->url(); auto entry = APPLICATION->metacache()->resolveEntry("meta", localFilename()); entry->setStale(true); diff --git a/launcher/minecraft/AssetsUtils.cpp b/launcher/minecraft/AssetsUtils.cpp index 15062c2b..16fdfdb1 100644 --- a/launcher/minecraft/AssetsUtils.cpp +++ b/launcher/minecraft/AssetsUtils.cpp @@ -340,7 +340,7 @@ QString AssetObject::getRelPath() NetJob::Ptr AssetsIndex::getDownloadJob() { - auto job = new NetJob(QObject::tr("Assets for %1").arg(id), APPLICATION->network()); + auto job = makeShared(QObject::tr("Assets for %1").arg(id), APPLICATION->network()); for (auto &object : objects.values()) { auto dl = object.getDownloadAction(); diff --git a/launcher/minecraft/ComponentUpdateTask.cpp b/launcher/minecraft/ComponentUpdateTask.cpp index 6db21622..d55bc17f 100644 --- a/launcher/minecraft/ComponentUpdateTask.cpp +++ b/launcher/minecraft/ComponentUpdateTask.cpp @@ -572,7 +572,7 @@ void ComponentUpdateTask::resolveDependencies(bool checkOnly) // add stuff... for(auto &add: toAdd) { - ComponentPtr component = new Component(d->m_list, add.uid); + auto component = makeShared(d->m_list, add.uid); if(!add.equalsVersion.isEmpty()) { // exact version diff --git a/launcher/minecraft/MinecraftInstance.cpp b/launcher/minecraft/MinecraftInstance.cpp index d0a5ed31..8a814cbf 100644 --- a/launcher/minecraft/MinecraftInstance.cpp +++ b/launcher/minecraft/MinecraftInstance.cpp @@ -962,12 +962,12 @@ shared_qobject_ptr MinecraftInstance::createLaunchTask(AuthSessionPt // print a header { - process->appendStep(new TextPrint(pptr, "Minecraft folder is:\n" + gameRoot() + "\n\n", MessageLevel::Launcher)); + process->appendStep(makeShared(pptr, "Minecraft folder is:\n" + gameRoot() + "\n\n", MessageLevel::Launcher)); } // check java { - process->appendStep(new CheckJava(pptr)); + process->appendStep(makeShared(pptr)); } // check launch method @@ -975,13 +975,13 @@ shared_qobject_ptr MinecraftInstance::createLaunchTask(AuthSessionPt QString method = launchMethod(); if(!validMethods.contains(method)) { - process->appendStep(new TextPrint(pptr, "Selected launch method \"" + method + "\" is not valid.\n", MessageLevel::Fatal)); + process->appendStep(makeShared(pptr, "Selected launch method \"" + method + "\" is not valid.\n", MessageLevel::Fatal)); return process; } // create the .minecraft folder and server-resource-packs (workaround for Minecraft bug MCL-3732) { - process->appendStep(new CreateGameFolders(pptr)); + process->appendStep(makeShared(pptr)); } if (!serverToJoin && settings()->get("JoinServerOnLaunch").toBool()) @@ -993,7 +993,7 @@ shared_qobject_ptr MinecraftInstance::createLaunchTask(AuthSessionPt if(serverToJoin && serverToJoin->port == 25565) { // Resolve server address to join on launch - auto *step = new LookupServerAddress(pptr); + auto step = makeShared(pptr); step->setLookupAddress(serverToJoin->address); step->setOutputAddressPtr(serverToJoin); process->appendStep(step); @@ -1002,7 +1002,7 @@ shared_qobject_ptr MinecraftInstance::createLaunchTask(AuthSessionPt // run pre-launch command if that's needed if(getPreLaunchCommand().size()) { - auto step = new PreLaunchCommand(pptr); + auto step = makeShared(pptr); step->setWorkingDirectory(gameRoot()); process->appendStep(step); } @@ -1011,43 +1011,43 @@ shared_qobject_ptr MinecraftInstance::createLaunchTask(AuthSessionPt if(session->status != AuthSession::PlayableOffline) { if(!session->demo) { - process->appendStep(new ClaimAccount(pptr, session)); + process->appendStep(makeShared(pptr, session)); } - process->appendStep(new Update(pptr, Net::Mode::Online)); + process->appendStep(makeShared(pptr, Net::Mode::Online)); } else { - process->appendStep(new Update(pptr, Net::Mode::Offline)); + process->appendStep(makeShared(pptr, Net::Mode::Offline)); } // if there are any jar mods { - process->appendStep(new ModMinecraftJar(pptr)); + process->appendStep(makeShared(pptr)); } // Scan mods folders for mods { - process->appendStep(new ScanModFolders(pptr)); + process->appendStep(makeShared(pptr)); } // print some instance info here... { - process->appendStep(new PrintInstanceInfo(pptr, session, serverToJoin)); + process->appendStep(makeShared(pptr, session, serverToJoin)); } // extract native jars if needed { - process->appendStep(new ExtractNatives(pptr)); + process->appendStep(makeShared(pptr)); } // reconstruct assets if needed { - process->appendStep(new ReconstructAssets(pptr)); + process->appendStep(makeShared(pptr)); } // verify that minimum Java requirements are met { - process->appendStep(new VerifyJavaInstall(pptr)); + process->appendStep(makeShared(pptr)); } { @@ -1055,7 +1055,7 @@ shared_qobject_ptr MinecraftInstance::createLaunchTask(AuthSessionPt auto method = launchMethod(); if(method == "LauncherPart") { - auto step = new LauncherPartLaunch(pptr); + auto step = makeShared(pptr); step->setWorkingDirectory(gameRoot()); step->setAuthSession(session); step->setServerToJoin(serverToJoin); @@ -1063,7 +1063,7 @@ shared_qobject_ptr MinecraftInstance::createLaunchTask(AuthSessionPt } else if (method == "DirectJava") { - auto step = new DirectJavaLaunch(pptr); + auto step = makeShared(pptr); step->setWorkingDirectory(gameRoot()); step->setAuthSession(session); step->setServerToJoin(serverToJoin); @@ -1074,7 +1074,7 @@ shared_qobject_ptr MinecraftInstance::createLaunchTask(AuthSessionPt // run post-exit command if that's needed if(getPostExitCommand().size()) { - auto step = new PostLaunchCommand(pptr); + auto step = makeShared(pptr); step->setWorkingDirectory(gameRoot()); process->appendStep(step); } @@ -1084,8 +1084,7 @@ shared_qobject_ptr MinecraftInstance::createLaunchTask(AuthSessionPt } if(m_settings->get("QuitAfterGameStop").toBool()) { - auto step = new QuitAfterGameStop(pptr); - process->appendStep(step); + process->appendStep(makeShared(pptr)); } m_launchProcess = process; emit launchTaskChanged(m_launchProcess); diff --git a/launcher/minecraft/MinecraftUpdate.cpp b/launcher/minecraft/MinecraftUpdate.cpp index 3a3aa864..07ad4882 100644 --- a/launcher/minecraft/MinecraftUpdate.cpp +++ b/launcher/minecraft/MinecraftUpdate.cpp @@ -43,7 +43,7 @@ void MinecraftUpdate::executeTask() m_tasks.clear(); // create folders { - m_tasks.append(new FoldersTask(m_inst)); + m_tasks.append(makeShared(m_inst)); } // add metadata update task if necessary @@ -59,17 +59,17 @@ void MinecraftUpdate::executeTask() // libraries download { - m_tasks.append(new LibrariesTask(m_inst)); + m_tasks.append(makeShared(m_inst)); } // FML libraries download and copy into the instance { - m_tasks.append(new FMLLibrariesTask(m_inst)); + m_tasks.append(makeShared(m_inst)); } // assets update { - m_tasks.append(new AssetUpdateTask(m_inst)); + m_tasks.append(makeShared(m_inst)); } if(!m_preFailure.isEmpty()) diff --git a/launcher/minecraft/PackProfile.cpp b/launcher/minecraft/PackProfile.cpp index 42021b3c..da7c1d84 100644 --- a/launcher/minecraft/PackProfile.cpp +++ b/launcher/minecraft/PackProfile.cpp @@ -130,7 +130,7 @@ static ComponentPtr componentFromJsonV1(PackProfile * parent, const QString & co // critical auto uid = Json::requireString(obj.value("uid")); auto filePath = componentJsonPattern.arg(uid); - auto component = new Component(parent, uid); + auto component = makeShared(parent, uid); component->m_version = Json::ensureString(obj.value("version")); component->m_dependencyOnly = Json::ensureBoolean(obj.value("dependencyOnly"), false); component->m_important = Json::ensureBoolean(obj.value("important"), false); @@ -518,23 +518,23 @@ bool PackProfile::revertToBase(int index) return true; } -Component * PackProfile::getComponent(const QString &id) +ComponentPtr PackProfile::getComponent(const QString &id) { auto iter = d->componentIndex.find(id); if (iter == d->componentIndex.end()) { return nullptr; } - return (*iter).get(); + return (*iter); } -Component * PackProfile::getComponent(int index) +ComponentPtr PackProfile::getComponent(int index) { if(index < 0 || index >= d->components.size()) { return nullptr; } - return d->components[index].get(); + return d->components[index]; } QVariant PackProfile::data(const QModelIndex &index, int role) const @@ -765,7 +765,7 @@ bool PackProfile::installEmpty(const QString& uid, const QString& name) file.write(OneSixVersionFormat::versionFileToJson(f).toJson()); file.close(); - appendComponent(new Component(this, f->uid, f)); + appendComponent(makeShared(this, f->uid, f)); scheduleSave(); invalidateLaunchProfile(); return true; @@ -872,7 +872,7 @@ bool PackProfile::installJarMods_internal(QStringList filepaths) file.write(OneSixVersionFormat::versionFileToJson(f).toJson()); file.close(); - appendComponent(new Component(this, f->uid, f)); + appendComponent(makeShared(this, f->uid, f)); } scheduleSave(); invalidateLaunchProfile(); @@ -933,7 +933,7 @@ bool PackProfile::installCustomJar_internal(QString filepath) file.write(OneSixVersionFormat::versionFileToJson(f).toJson()); file.close(); - appendComponent(new Component(this, f->uid, f)); + appendComponent(makeShared(this, f->uid, f)); scheduleSave(); invalidateLaunchProfile(); @@ -989,7 +989,7 @@ bool PackProfile::installAgents_internal(QStringList filepaths) patchFile.write(OneSixVersionFormat::versionFileToJson(versionFile).toJson()); patchFile.close(); - appendComponent(new Component(this, versionFile->uid, versionFile)); + appendComponent(makeShared(this, versionFile->uid, versionFile)); } scheduleSave(); @@ -1038,7 +1038,7 @@ bool PackProfile::setComponentVersion(const QString& uid, const QString& version else { // add new - auto component = new Component(this, uid); + auto component = makeShared(this, uid); component->m_version = version; component->m_important = important; appendComponent(component); diff --git a/launcher/minecraft/PackProfile.h b/launcher/minecraft/PackProfile.h index 67b418f4..731cd0ba 100644 --- a/launcher/minecraft/PackProfile.h +++ b/launcher/minecraft/PackProfile.h @@ -136,10 +136,10 @@ signals: public: /// get the profile component by id - Component * getComponent(const QString &id); + ComponentPtr getComponent(const QString &id); /// get the profile component by index - Component * getComponent(int index); + ComponentPtr getComponent(int index); /// Add the component to the internal list of patches // todo(merged): is this the best approach diff --git a/launcher/minecraft/auth/MinecraftAccount.cpp b/launcher/minecraft/auth/MinecraftAccount.cpp index 73d570f1..48cf5d42 100644 --- a/launcher/minecraft/auth/MinecraftAccount.cpp +++ b/launcher/minecraft/auth/MinecraftAccount.cpp @@ -75,7 +75,7 @@ MinecraftAccountPtr MinecraftAccount::loadFromJsonV3(const QJsonObject& json) { MinecraftAccountPtr MinecraftAccount::createFromUsername(const QString &username) { - MinecraftAccountPtr account = new MinecraftAccount(); + auto account = makeShared(); account->data.type = AccountType::Mojang; account->data.yggdrasilToken.extra["userName"] = username; account->data.yggdrasilToken.extra["clientToken"] = QUuid::createUuid().toString().remove(QRegularExpression("[{}-]")); @@ -91,7 +91,7 @@ MinecraftAccountPtr MinecraftAccount::createBlankMSA() MinecraftAccountPtr MinecraftAccount::createOffline(const QString &username) { - MinecraftAccountPtr account = new MinecraftAccount(); + auto account = makeShared(); account->data.type = AccountType::Offline; account->data.yggdrasilToken.token = "offline"; account->data.yggdrasilToken.validity = Katabasis::Validity::Certain; diff --git a/launcher/minecraft/auth/flows/MSA.cpp b/launcher/minecraft/auth/flows/MSA.cpp index 416b8f2c..f1987e0c 100644 --- a/launcher/minecraft/auth/flows/MSA.cpp +++ b/launcher/minecraft/auth/flows/MSA.cpp @@ -10,28 +10,28 @@ #include "minecraft/auth/steps/GetSkinStep.h" MSASilent::MSASilent(AccountData* data, QObject* parent) : AuthFlow(data, parent) { - m_steps.append(new MSAStep(m_data, MSAStep::Action::Refresh)); - m_steps.append(new XboxUserStep(m_data)); - m_steps.append(new XboxAuthorizationStep(m_data, &m_data->xboxApiToken, "http://xboxlive.com", "Xbox")); - m_steps.append(new XboxAuthorizationStep(m_data, &m_data->mojangservicesToken, "rp://api.minecraftservices.com/", "Mojang")); - m_steps.append(new LauncherLoginStep(m_data)); - m_steps.append(new XboxProfileStep(m_data)); - m_steps.append(new EntitlementsStep(m_data)); - m_steps.append(new MinecraftProfileStep(m_data)); - m_steps.append(new GetSkinStep(m_data)); + m_steps.append(makeShared(m_data, MSAStep::Action::Refresh)); + m_steps.append(makeShared(m_data)); + m_steps.append(makeShared(m_data, &m_data->xboxApiToken, "http://xboxlive.com", "Xbox")); + m_steps.append(makeShared(m_data, &m_data->mojangservicesToken, "rp://api.minecraftservices.com/", "Mojang")); + m_steps.append(makeShared(m_data)); + m_steps.append(makeShared(m_data)); + m_steps.append(makeShared(m_data)); + m_steps.append(makeShared(m_data)); + m_steps.append(makeShared(m_data)); } MSAInteractive::MSAInteractive( AccountData* data, QObject* parent ) : AuthFlow(data, parent) { - m_steps.append(new MSAStep(m_data, MSAStep::Action::Login)); - m_steps.append(new XboxUserStep(m_data)); - m_steps.append(new XboxAuthorizationStep(m_data, &m_data->xboxApiToken, "http://xboxlive.com", "Xbox")); - m_steps.append(new XboxAuthorizationStep(m_data, &m_data->mojangservicesToken, "rp://api.minecraftservices.com/", "Mojang")); - m_steps.append(new LauncherLoginStep(m_data)); - m_steps.append(new XboxProfileStep(m_data)); - m_steps.append(new EntitlementsStep(m_data)); - m_steps.append(new MinecraftProfileStep(m_data)); - m_steps.append(new GetSkinStep(m_data)); + m_steps.append(makeShared(m_data, MSAStep::Action::Login)); + m_steps.append(makeShared(m_data)); + m_steps.append(makeShared(m_data, &m_data->xboxApiToken, "http://xboxlive.com", "Xbox")); + m_steps.append(makeShared(m_data, &m_data->mojangservicesToken, "rp://api.minecraftservices.com/", "Mojang")); + m_steps.append(makeShared(m_data)); + m_steps.append(makeShared(m_data)); + m_steps.append(makeShared(m_data)); + m_steps.append(makeShared(m_data)); + m_steps.append(makeShared(m_data)); } diff --git a/launcher/minecraft/auth/flows/Mojang.cpp b/launcher/minecraft/auth/flows/Mojang.cpp index b86b0936..5900ea98 100644 --- a/launcher/minecraft/auth/flows/Mojang.cpp +++ b/launcher/minecraft/auth/flows/Mojang.cpp @@ -9,10 +9,10 @@ MojangRefresh::MojangRefresh( AccountData *data, QObject *parent ) : AuthFlow(data, parent) { - m_steps.append(new YggdrasilStep(m_data, QString())); - m_steps.append(new MinecraftProfileStepMojang(m_data)); - m_steps.append(new MigrationEligibilityStep(m_data)); - m_steps.append(new GetSkinStep(m_data)); + m_steps.append(makeShared(m_data, QString())); + m_steps.append(makeShared(m_data)); + m_steps.append(makeShared(m_data)); + m_steps.append(makeShared(m_data)); } MojangLogin::MojangLogin( @@ -20,8 +20,8 @@ MojangLogin::MojangLogin( QString password, QObject *parent ): AuthFlow(data, parent), m_password(password) { - m_steps.append(new YggdrasilStep(m_data, m_password)); - m_steps.append(new MinecraftProfileStepMojang(m_data)); - m_steps.append(new MigrationEligibilityStep(m_data)); - m_steps.append(new GetSkinStep(m_data)); + m_steps.append(makeShared(m_data, m_password)); + m_steps.append(makeShared(m_data)); + m_steps.append(makeShared(m_data)); + m_steps.append(makeShared(m_data)); } diff --git a/launcher/minecraft/auth/flows/Offline.cpp b/launcher/minecraft/auth/flows/Offline.cpp index fc614a8c..d5c63271 100644 --- a/launcher/minecraft/auth/flows/Offline.cpp +++ b/launcher/minecraft/auth/flows/Offline.cpp @@ -6,12 +6,12 @@ OfflineRefresh::OfflineRefresh( AccountData *data, QObject *parent ) : AuthFlow(data, parent) { - m_steps.append(new OfflineStep(m_data)); + m_steps.append(makeShared(m_data)); } OfflineLogin::OfflineLogin( AccountData *data, QObject *parent ) : AuthFlow(data, parent) { - m_steps.append(new OfflineStep(m_data)); + m_steps.append(makeShared(m_data)); } diff --git a/launcher/minecraft/mod/ResourcePackFolderModel.cpp b/launcher/minecraft/mod/ResourcePackFolderModel.cpp index ebac707d..da4bd091 100644 --- a/launcher/minecraft/mod/ResourcePackFolderModel.cpp +++ b/launcher/minecraft/mod/ResourcePackFolderModel.cpp @@ -142,7 +142,7 @@ int ResourcePackFolderModel::columnCount(const QModelIndex& parent) const Task* ResourcePackFolderModel::createUpdateTask() { - return new BasicFolderLoadTask(m_dir, [](QFileInfo const& entry) { return new ResourcePack(entry); }); + return new BasicFolderLoadTask(m_dir, [](QFileInfo const& entry) { return makeShared(entry); }); } Task* ResourcePackFolderModel::createParseTask(Resource& resource) diff --git a/launcher/minecraft/mod/TexturePackFolderModel.cpp b/launcher/minecraft/mod/TexturePackFolderModel.cpp index 561f6202..5a32cfaf 100644 --- a/launcher/minecraft/mod/TexturePackFolderModel.cpp +++ b/launcher/minecraft/mod/TexturePackFolderModel.cpp @@ -43,7 +43,7 @@ TexturePackFolderModel::TexturePackFolderModel(const QString &dir) : ResourceFol Task* TexturePackFolderModel::createUpdateTask() { - return new BasicFolderLoadTask(m_dir, [](QFileInfo const& entry) { return new TexturePack(entry); }); + return new BasicFolderLoadTask(m_dir, [](QFileInfo const& entry) { return makeShared(entry); }); } Task* TexturePackFolderModel::createParseTask(Resource& resource) diff --git a/launcher/minecraft/mod/tasks/BasicFolderLoadTask.h b/launcher/minecraft/mod/tasks/BasicFolderLoadTask.h index 2fce2942..3ee7e2e0 100644 --- a/launcher/minecraft/mod/tasks/BasicFolderLoadTask.h +++ b/launcher/minecraft/mod/tasks/BasicFolderLoadTask.h @@ -26,11 +26,11 @@ class BasicFolderLoadTask : public Task { public: BasicFolderLoadTask(QDir dir) : Task(nullptr, false), m_dir(dir), m_result(new Result), m_thread_to_spawn_into(thread()) { - m_create_func = [](QFileInfo const& entry) -> Resource* { - return new Resource(entry); + m_create_func = [](QFileInfo const& entry) -> Resource::Ptr { + return makeShared(entry); }; } - BasicFolderLoadTask(QDir dir, std::function create_function) + BasicFolderLoadTask(QDir dir, std::function create_function) : Task(nullptr, false), m_dir(dir), m_result(new Result), m_create_func(std::move(create_function)), m_thread_to_spawn_into(thread()) {} @@ -65,7 +65,7 @@ private: std::atomic m_aborted = false; - std::function m_create_func; + std::function m_create_func; /** This is the thread in which we should put new mod objects */ QThread* m_thread_to_spawn_into; diff --git a/launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp b/launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp index 78ef4386..3677a1dc 100644 --- a/launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp +++ b/launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp @@ -72,14 +72,14 @@ void ModFolderLoadTask::executeTask() delete mod; } else { - m_result->mods[mod->internal_id()] = mod; + m_result->mods[mod->internal_id()].reset(std::move(mod)); m_result->mods[mod->internal_id()]->setStatus(ModStatus::NoMetadata); } } else { QString chopped_id = mod->internal_id().chopped(9); if (m_result->mods.contains(chopped_id)) { - m_result->mods[mod->internal_id()] = mod; + m_result->mods[mod->internal_id()].reset(std::move(mod)); auto metadata = m_result->mods[chopped_id]->metadata(); if (metadata) { @@ -90,7 +90,7 @@ void ModFolderLoadTask::executeTask() } } else { - m_result->mods[mod->internal_id()] = mod; + m_result->mods[mod->internal_id()].reset(std::move(mod)); m_result->mods[mod->internal_id()]->setStatus(ModStatus::NoMetadata); } } @@ -130,6 +130,6 @@ void ModFolderLoadTask::getFromMetadata() auto* mod = new Mod(m_mods_dir, metadata); mod->setStatus(ModStatus::NotInstalled); - m_result->mods[mod->internal_id()] = mod; + m_result->mods[mod->internal_id()].reset(std::move(mod)); } } diff --git a/launcher/minecraft/update/AssetUpdateTask.cpp b/launcher/minecraft/update/AssetUpdateTask.cpp index dd246665..8ccb0e1d 100644 --- a/launcher/minecraft/update/AssetUpdateTask.cpp +++ b/launcher/minecraft/update/AssetUpdateTask.cpp @@ -24,7 +24,7 @@ void AssetUpdateTask::executeTask() auto assets = profile->getMinecraftAssets(); QUrl indexUrl = assets->url; QString localPath = assets->id + ".json"; - auto job = new NetJob( + auto job = makeShared( tr("Asset index for %1").arg(m_inst->name()), APPLICATION->network() ); diff --git a/launcher/minecraft/update/FMLLibrariesTask.cpp b/launcher/minecraft/update/FMLLibrariesTask.cpp index 7a0bd2f3..96fd3ba3 100644 --- a/launcher/minecraft/update/FMLLibrariesTask.cpp +++ b/launcher/minecraft/update/FMLLibrariesTask.cpp @@ -61,7 +61,7 @@ void FMLLibrariesTask::executeTask() // download missing libs to our place setStatus(tr("Downloading FML libraries...")); - auto dljob = new NetJob("FML libraries", APPLICATION->network()); + NetJob::Ptr dljob{ new NetJob("FML libraries", APPLICATION->network()) }; auto metacache = APPLICATION->metacache(); Net::Download::Options options = Net::Download::Option::MakeEternal; for (auto &lib : fmlLibsToProcess) @@ -71,10 +71,10 @@ void FMLLibrariesTask::executeTask() dljob->addNetAction(Net::Download::makeCached(QUrl(urlString), entry, options)); } - connect(dljob, &NetJob::succeeded, this, &FMLLibrariesTask::fmllibsFinished); - connect(dljob, &NetJob::failed, this, &FMLLibrariesTask::fmllibsFailed); - connect(dljob, &NetJob::aborted, this, [this]{ emitFailed(tr("Aborted")); }); - connect(dljob, &NetJob::progress, this, &FMLLibrariesTask::progress); + connect(dljob.get(), &NetJob::succeeded, this, &FMLLibrariesTask::fmllibsFinished); + connect(dljob.get(), &NetJob::failed, this, &FMLLibrariesTask::fmllibsFailed); + connect(dljob.get(), &NetJob::aborted, this, [this]{ emitFailed(tr("Aborted")); }); + connect(dljob.get(), &NetJob::progress, this, &FMLLibrariesTask::progress); downloadJob.reset(dljob); downloadJob->start(); } diff --git a/launcher/minecraft/update/LibrariesTask.cpp b/launcher/minecraft/update/LibrariesTask.cpp index 33a575c2..b9410111 100644 --- a/launcher/minecraft/update/LibrariesTask.cpp +++ b/launcher/minecraft/update/LibrariesTask.cpp @@ -20,7 +20,7 @@ void LibrariesTask::executeTask() auto components = inst->getPackProfile(); auto profile = components->getProfile(); - auto job = new NetJob(tr("Libraries for instance %1").arg(inst->name()), APPLICATION->network()); + NetJob::Ptr job{ new NetJob(tr("Libraries for instance %1").arg(inst->name()), APPLICATION->network()) }; downloadJob.reset(job); auto metacache = APPLICATION->metacache(); diff --git a/launcher/modplatform/CheckUpdateTask.h b/launcher/modplatform/CheckUpdateTask.h index 932a62d9..f7582b8f 100644 --- a/launcher/modplatform/CheckUpdateTask.h +++ b/launcher/modplatform/CheckUpdateTask.h @@ -22,10 +22,10 @@ class CheckUpdateTask : public Task { QString new_version; QString changelog; ModPlatform::ResourceProvider provider; - ResourceDownloadTask* download; + shared_qobject_ptr download; public: - UpdatableMod(QString name, QString old_h, QString old_v, QString new_v, QString changelog, ModPlatform::ResourceProvider p, ResourceDownloadTask* t) + UpdatableMod(QString name, QString old_h, QString old_v, QString new_v, QString changelog, ModPlatform::ResourceProvider p, shared_qobject_ptr t) : name(name), old_hash(old_h), old_version(old_v), new_version(new_v), changelog(changelog), provider(p), download(t) {} }; diff --git a/launcher/modplatform/EnsureMetadataTask.cpp b/launcher/modplatform/EnsureMetadataTask.cpp index d9523052..34d969f0 100644 --- a/launcher/modplatform/EnsureMetadataTask.cpp +++ b/launcher/modplatform/EnsureMetadataTask.cpp @@ -32,7 +32,7 @@ EnsureMetadataTask::EnsureMetadataTask(Mod* mod, QDir dir, ModPlatform::Resource EnsureMetadataTask::EnsureMetadataTask(QList& mods, QDir dir, ModPlatform::ResourceProvider prov) : Task(nullptr), m_index_dir(dir), m_provider(prov), m_current_task(nullptr) { - m_hashing_task = new ConcurrentTask(this, "MakeHashesTask", 10); + m_hashing_task.reset(new ConcurrentTask(this, "MakeHashesTask", 10)); for (auto* mod : mods) { auto hash_task = createNewHash(mod); if (!hash_task) @@ -217,7 +217,7 @@ Task::Ptr EnsureMetadataTask::modrinthVersionsTask() // Prevents unfortunate timings when aborting the task if (!ver_task) - return {}; + return Task::Ptr{nullptr}; connect(ver_task.get(), &Task::succeeded, this, [this, response] { QJsonParseError parse_error{}; @@ -277,7 +277,7 @@ Task::Ptr EnsureMetadataTask::modrinthProjectsTask() // Prevents unfortunate timings when aborting the task if (!proj_task) - return {}; + return Task::Ptr{nullptr}; connect(proj_task.get(), &Task::succeeded, this, [this, response, addonIds] { QJsonParseError parse_error{}; @@ -434,7 +434,7 @@ Task::Ptr EnsureMetadataTask::flameProjectsTask() // Prevents unfortunate timings when aborting the task if (!proj_task) - return {}; + return Task::Ptr{nullptr}; connect(proj_task.get(), &Task::succeeded, this, [this, response, addonIds] { QJsonParseError parse_error{}; diff --git a/launcher/modplatform/EnsureMetadataTask.h b/launcher/modplatform/EnsureMetadataTask.h index 635f4a2b..03cae4e4 100644 --- a/launcher/modplatform/EnsureMetadataTask.h +++ b/launcher/modplatform/EnsureMetadataTask.h @@ -60,6 +60,6 @@ class EnsureMetadataTask : public Task { ModPlatform::ResourceProvider m_provider; QHash m_temp_versions; - ConcurrentTask* m_hashing_task; + ConcurrentTask::Ptr m_hashing_task; Task::Ptr m_current_task; }; diff --git a/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp b/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp index 291ad916..4bd8b7f2 100644 --- a/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp +++ b/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp @@ -81,16 +81,17 @@ bool PackInstallTask::abort() void PackInstallTask::executeTask() { qDebug() << "PackInstallTask::executeTask: " << QThread::currentThreadId(); - auto *netJob = new NetJob("ATLauncher::VersionFetch", APPLICATION->network()); + NetJob::Ptr netJob{ new NetJob("ATLauncher::VersionFetch", APPLICATION->network()) }; auto searchUrl = QString(BuildConfig.ATL_DOWNLOAD_SERVER_URL + "packs/%1/versions/%2/Configs.json") .arg(m_pack_safe_name).arg(m_version_name); netJob->addNetAction(Net::Download::makeByteArray(QUrl(searchUrl), &response)); + + QObject::connect(netJob.get(), &NetJob::succeeded, this, &PackInstallTask::onDownloadSucceeded); + QObject::connect(netJob.get(), &NetJob::failed, this, &PackInstallTask::onDownloadFailed); + QObject::connect(netJob.get(), &NetJob::aborted, this, &PackInstallTask::onDownloadAborted); + jobPtr = netJob; jobPtr->start(); - - QObject::connect(netJob, &NetJob::succeeded, this, &PackInstallTask::onDownloadSucceeded); - QObject::connect(netJob, &NetJob::failed, this, &PackInstallTask::onDownloadFailed); - QObject::connect(netJob, &NetJob::aborted, this, &PackInstallTask::onDownloadAborted); } void PackInstallTask::onDownloadSucceeded() @@ -552,7 +553,7 @@ bool PackInstallTask::createLibrariesComponent(QString instanceRoot, std::shared file.write(OneSixVersionFormat::versionFileToJson(f).toJson()); file.close(); - profile->appendComponent(new Component(profile.get(), target_id, f)); + profile->appendComponent(ComponentPtr{ new Component(profile.get(), target_id, f) }); return true; } @@ -641,7 +642,7 @@ bool PackInstallTask::createPackComponent(QString instanceRoot, std::shared_ptr< file.write(OneSixVersionFormat::versionFileToJson(f).toJson()); file.close(); - profile->appendComponent(new Component(profile.get(), target_id, f)); + profile->appendComponent(ComponentPtr{ new Component(profile.get(), target_id, f) }); return true; } @@ -649,7 +650,7 @@ void PackInstallTask::installConfigs() { qDebug() << "PackInstallTask::installConfigs: " << QThread::currentThreadId(); setStatus(tr("Downloading configs...")); - jobPtr = new NetJob(tr("Config download"), APPLICATION->network()); + jobPtr.reset(new NetJob(tr("Config download"), APPLICATION->network())); auto path = QString("Configs/%1/%2.zip").arg(m_pack_safe_name).arg(m_version_name); auto url = QString(BuildConfig.ATL_DOWNLOAD_SERVER_URL + "packs/%1/versions/%2/Configs.zip") @@ -747,7 +748,7 @@ void PackInstallTask::downloadMods() setStatus(tr("Downloading mods...")); jarmods.clear(); - jobPtr = new NetJob(tr("Mod download"), APPLICATION->network()); + jobPtr.reset(new NetJob(tr("Mod download"), APPLICATION->network())); for(const auto& mod : m_version.mods) { // skip non-client mods if(!mod.client) continue; diff --git a/launcher/modplatform/flame/FileResolvingTask.cpp b/launcher/modplatform/flame/FileResolvingTask.cpp index 7f1beb1a..d3a737bb 100644 --- a/launcher/modplatform/flame/FileResolvingTask.cpp +++ b/launcher/modplatform/flame/FileResolvingTask.cpp @@ -23,7 +23,7 @@ void Flame::FileResolvingTask::executeTask() { setStatus(tr("Resolving mod IDs...")); setProgress(0, 3); - m_dljob = new NetJob("Mod id resolver", m_network); + m_dljob.reset(new NetJob("Mod id resolver", m_network)); result.reset(new QByteArray()); //build json data to send QJsonObject object; @@ -43,7 +43,7 @@ void Flame::FileResolvingTask::netJobFinished() { setProgress(1, 3); // job to check modrinth for blocked projects - m_checkJob = new NetJob("Modrinth check", m_network); + m_checkJob.reset(new NetJob("Modrinth check", m_network)); blockedProjects = QMap(); QJsonDocument doc; diff --git a/launcher/modplatform/flame/FlameAPI.cpp b/launcher/modplatform/flame/FlameAPI.cpp index 4b926ec3..5ef9a409 100644 --- a/launcher/modplatform/flame/FlameAPI.cpp +++ b/launcher/modplatform/flame/FlameAPI.cpp @@ -13,7 +13,7 @@ Task::Ptr FlameAPI::matchFingerprints(const QList& fingerprints, QByteArray* response) { - auto* netJob = new NetJob(QString("Flame::MatchFingerprints"), APPLICATION->network()); + auto netJob = makeShared(QString("Flame::MatchFingerprints"), APPLICATION->network()); QJsonObject body_obj; QJsonArray fingerprints_arr; @@ -28,7 +28,7 @@ Task::Ptr FlameAPI::matchFingerprints(const QList& fingerprints, QByteArra netJob->addNetAction(Net::Upload::makeByteArray(QString("https://api.curseforge.com/v1/fingerprints"), response, body_raw)); - QObject::connect(netJob, &NetJob::finished, [response] { delete response; }); + QObject::connect(netJob.get(), &NetJob::finished, [response] { delete response; }); return netJob; } @@ -173,7 +173,7 @@ auto FlameAPI::getLatestVersion(VersionSearchArgs&& args) -> ModPlatform::Indexe Task::Ptr FlameAPI::getProjects(QStringList addonIds, QByteArray* response) const { - auto* netJob = new NetJob(QString("Flame::GetProjects"), APPLICATION->network()); + auto netJob = makeShared(QString("Flame::GetProjects"), APPLICATION->network()); QJsonObject body_obj; QJsonArray addons_arr; @@ -188,15 +188,15 @@ Task::Ptr FlameAPI::getProjects(QStringList addonIds, QByteArray* response) cons netJob->addNetAction(Net::Upload::makeByteArray(QString("https://api.curseforge.com/v1/mods"), response, body_raw)); - QObject::connect(netJob, &NetJob::finished, [response] { delete response; }); - QObject::connect(netJob, &NetJob::failed, [body_raw] { qDebug() << body_raw; }); + QObject::connect(netJob.get(), &NetJob::finished, [response] { delete response; }); + QObject::connect(netJob.get(), &NetJob::failed, [body_raw] { qDebug() << body_raw; }); return netJob; } Task::Ptr FlameAPI::getFiles(const QStringList& fileIds, QByteArray* response) const { - auto* netJob = new NetJob(QString("Flame::GetFiles"), APPLICATION->network()); + auto netJob = makeShared(QString("Flame::GetFiles"), APPLICATION->network()); QJsonObject body_obj; QJsonArray files_arr; @@ -211,8 +211,8 @@ Task::Ptr FlameAPI::getFiles(const QStringList& fileIds, QByteArray* response) c netJob->addNetAction(Net::Upload::makeByteArray(QString("https://api.curseforge.com/v1/mods/files"), response, body_raw)); - QObject::connect(netJob, &NetJob::finished, [response] { delete response; }); - QObject::connect(netJob, &NetJob::failed, [body_raw] { qDebug() << body_raw; }); + QObject::connect(netJob.get(), &NetJob::finished, [response] { delete response; }); + QObject::connect(netJob.get(), &NetJob::failed, [body_raw] { qDebug() << body_raw; }); return netJob; } diff --git a/launcher/modplatform/flame/FlameCheckUpdate.cpp b/launcher/modplatform/flame/FlameCheckUpdate.cpp index 7aee4f4c..06a89502 100644 --- a/launcher/modplatform/flame/FlameCheckUpdate.cpp +++ b/launcher/modplatform/flame/FlameCheckUpdate.cpp @@ -172,7 +172,7 @@ void FlameCheckUpdate::executeTask() old_version = current_ver.version; } - auto download_task = new ResourceDownloadTask(pack, latest_ver, m_mods_folder); + auto download_task = makeShared(pack, latest_ver, m_mods_folder); m_updatable.emplace_back(pack.name, mod->metadata()->hash, old_version, latest_ver.version, api.getModFileChangelog(latest_ver.addonId.toInt(), latest_ver.fileId.toInt()), ModPlatform::ResourceProvider::FLAME, download_task); diff --git a/launcher/modplatform/flame/FlameInstanceCreationTask.cpp b/launcher/modplatform/flame/FlameInstanceCreationTask.cpp index 890bff48..964b559c 100644 --- a/launcher/modplatform/flame/FlameInstanceCreationTask.cpp +++ b/launcher/modplatform/flame/FlameInstanceCreationTask.cpp @@ -373,7 +373,7 @@ bool FlameCreationTask::createInstance() instance.setManagedPack("flame", m_managed_id, m_pack.name, m_managed_version_id, m_pack.version); instance.setName(name()); - m_mod_id_resolver = new Flame::FileResolvingTask(APPLICATION->network(), m_pack); + m_mod_id_resolver.reset(new Flame::FileResolvingTask(APPLICATION->network(), m_pack)); connect(m_mod_id_resolver.get(), &Flame::FileResolvingTask::succeeded, this, [this, &loop] { idResolverSucceeded(loop); }); connect(m_mod_id_resolver.get(), &Flame::FileResolvingTask::failed, [&](QString reason) { m_mod_id_resolver.reset(); @@ -452,7 +452,7 @@ void FlameCreationTask::idResolverSucceeded(QEventLoop& loop) void FlameCreationTask::setupDownloadJob(QEventLoop& loop) { - m_files_job = new NetJob(tr("Mod download"), APPLICATION->network()); + m_files_job.reset(new NetJob(tr("Mod download"), APPLICATION->network())); for (const auto& result : m_mod_id_resolver->getResults().files) { QString filename = result.fileName; if (!result.required) { diff --git a/launcher/modplatform/helpers/HashUtils.cpp b/launcher/modplatform/helpers/HashUtils.cpp index af484be0..2177ddad 100644 --- a/launcher/modplatform/helpers/HashUtils.cpp +++ b/launcher/modplatform/helpers/HashUtils.cpp @@ -28,22 +28,22 @@ Hasher::Ptr createHasher(QString file_path, ModPlatform::ResourceProvider provid Hasher::Ptr createModrinthHasher(QString file_path) { - return new ModrinthHasher(file_path); + return makeShared(file_path); } Hasher::Ptr createFlameHasher(QString file_path) { - return new FlameHasher(file_path); + return makeShared(file_path); } Hasher::Ptr createBlockedModHasher(QString file_path, ModPlatform::ResourceProvider provider) { - return new BlockedModHasher(file_path, provider); + return makeShared(file_path, provider); } Hasher::Ptr createBlockedModHasher(QString file_path, ModPlatform::ResourceProvider provider, QString type) { - auto hasher = new BlockedModHasher(file_path, provider); + auto hasher = makeShared(file_path, provider); hasher->useHashType(type); return hasher; } diff --git a/launcher/modplatform/helpers/NetworkResourceAPI.cpp b/launcher/modplatform/helpers/NetworkResourceAPI.cpp index ac994c31..010ac15e 100644 --- a/launcher/modplatform/helpers/NetworkResourceAPI.cpp +++ b/launcher/modplatform/helpers/NetworkResourceAPI.cpp @@ -20,11 +20,11 @@ Task::Ptr NetworkResourceAPI::searchProjects(SearchArgs&& args, SearchCallbacks& auto search_url = search_url_optional.value(); auto response = new QByteArray(); - auto netJob = new NetJob(QString("%1::Search").arg(debugName()), APPLICATION->network()); + auto netJob = makeShared(QString("%1::Search").arg(debugName()), APPLICATION->network()); netJob->addNetAction(Net::Download::makeByteArray(QUrl(search_url), response)); - QObject::connect(netJob, &NetJob::succeeded, [=]{ + QObject::connect(netJob.get(), &NetJob::succeeded, [=]{ QJsonParseError parse_error{}; QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error); if (parse_error.error != QJsonParseError::NoError) { @@ -40,14 +40,14 @@ Task::Ptr NetworkResourceAPI::searchProjects(SearchArgs&& args, SearchCallbacks& callbacks.on_succeed(doc); }); - QObject::connect(netJob, &NetJob::failed, [=](QString reason){ + QObject::connect(netJob.get(), &NetJob::failed, [=](QString reason){ int network_error_code = -1; if (auto* failed_action = netJob->getFailedActions().at(0); failed_action && failed_action->m_reply) network_error_code = failed_action->m_reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); callbacks.on_fail(reason, network_error_code); }); - QObject::connect(netJob, &NetJob::aborted, [=]{ + QObject::connect(netJob.get(), &NetJob::aborted, [=]{ callbacks.on_abort(); }); @@ -83,12 +83,12 @@ Task::Ptr NetworkResourceAPI::getProjectVersions(VersionSearchArgs&& args, Versi auto versions_url = versions_url_optional.value(); - auto netJob = new NetJob(QString("%1::Versions").arg(args.pack.name), APPLICATION->network()); + auto netJob = makeShared(QString("%1::Versions").arg(args.pack.name), APPLICATION->network()); auto response = new QByteArray(); netJob->addNetAction(Net::Download::makeByteArray(versions_url, response)); - QObject::connect(netJob, &NetJob::succeeded, [=] { + QObject::connect(netJob.get(), &NetJob::succeeded, [=] { QJsonParseError parse_error{}; QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error); if (parse_error.error != QJsonParseError::NoError) { @@ -101,7 +101,7 @@ Task::Ptr NetworkResourceAPI::getProjectVersions(VersionSearchArgs&& args, Versi callbacks.on_succeed(doc, args.pack); }); - QObject::connect(netJob, &NetJob::finished, [response] { + QObject::connect(netJob.get(), &NetJob::finished, [response] { delete response; }); @@ -116,11 +116,11 @@ Task::Ptr NetworkResourceAPI::getProject(QString addonId, QByteArray* response) auto project_url = project_url_optional.value(); - auto netJob = new NetJob(QString("%1::GetProject").arg(addonId), APPLICATION->network()); + auto netJob = makeShared(QString("%1::GetProject").arg(addonId), APPLICATION->network()); netJob->addNetAction(Net::Download::makeByteArray(QUrl(project_url), response)); - QObject::connect(netJob, &NetJob::finished, [response] { + QObject::connect(netJob.get(), &NetJob::finished, [response] { delete response; }); diff --git a/launcher/modplatform/legacy_ftb/PackFetchTask.cpp b/launcher/modplatform/legacy_ftb/PackFetchTask.cpp index 36aa60c7..e8768c5c 100644 --- a/launcher/modplatform/legacy_ftb/PackFetchTask.cpp +++ b/launcher/modplatform/legacy_ftb/PackFetchTask.cpp @@ -47,7 +47,7 @@ void PackFetchTask::fetch() publicPacks.clear(); thirdPartyPacks.clear(); - jobPtr = new NetJob("LegacyFTB::ModpackFetch", m_network); + jobPtr.reset(new NetJob("LegacyFTB::ModpackFetch", m_network)); QUrl publicPacksUrl = QUrl(BuildConfig.LEGACY_FTB_CDN_BASE_URL + "static/modpacks.xml"); qDebug() << "Downloading public version info from" << publicPacksUrl.toString(); diff --git a/launcher/modplatform/legacy_ftb/PackInstallTask.cpp b/launcher/modplatform/legacy_ftb/PackInstallTask.cpp index 06b3788b..8d45fc5c 100644 --- a/launcher/modplatform/legacy_ftb/PackInstallTask.cpp +++ b/launcher/modplatform/legacy_ftb/PackInstallTask.cpp @@ -69,7 +69,7 @@ void PackInstallTask::downloadPack() archivePath = QString("%1/%2/%3").arg(m_pack.dir, m_version.replace(".", "_"), m_pack.file); - netJobContainer = new NetJob("Download FTB Pack", m_network); + netJobContainer.reset(new NetJob("Download FTB Pack", m_network)); QString url; if (m_pack.type == PackType::Private) { url = QString(BuildConfig.LEGACY_FTB_CDN_BASE_URL + "privatepacks/%1").arg(archivePath); diff --git a/launcher/modplatform/modpacksch/FTBPackInstallTask.cpp b/launcher/modplatform/modpacksch/FTBPackInstallTask.cpp index 2979663d..68d4751c 100644 --- a/launcher/modplatform/modpacksch/FTBPackInstallTask.cpp +++ b/launcher/modplatform/modpacksch/FTBPackInstallTask.cpp @@ -87,15 +87,15 @@ void PackInstallTask::executeTask() auto version = *version_it; - auto* netJob = new NetJob("ModpacksCH::VersionFetch", APPLICATION->network()); + auto netJob = makeShared("ModpacksCH::VersionFetch", APPLICATION->network()); auto searchUrl = QString(BuildConfig.MODPACKSCH_API_BASE_URL + "public/modpack/%1/%2").arg(m_pack.id).arg(version.id); netJob->addNetAction(Net::Download::makeByteArray(QUrl(searchUrl), &m_response)); - QObject::connect(netJob, &NetJob::succeeded, this, &PackInstallTask::onManifestDownloadSucceeded); - QObject::connect(netJob, &NetJob::failed, this, &PackInstallTask::onManifestDownloadFailed); - QObject::connect(netJob, &NetJob::aborted, this, &PackInstallTask::abort); - QObject::connect(netJob, &NetJob::progress, this, &PackInstallTask::setProgress); + QObject::connect(netJob.get(), &NetJob::succeeded, this, &PackInstallTask::onManifestDownloadSucceeded); + QObject::connect(netJob.get(), &NetJob::failed, this, &PackInstallTask::onManifestDownloadFailed); + QObject::connect(netJob.get(), &NetJob::aborted, this, &PackInstallTask::abort); + QObject::connect(netJob.get(), &NetJob::progress, this, &PackInstallTask::setProgress); m_net_job = netJob; @@ -162,7 +162,7 @@ void PackInstallTask::resolveMods() index++; } - m_mod_id_resolver_task = new Flame::FileResolvingTask(APPLICATION->network(), manifest); + m_mod_id_resolver_task.reset(new Flame::FileResolvingTask(APPLICATION->network(), manifest)); connect(m_mod_id_resolver_task.get(), &Flame::FileResolvingTask::succeeded, this, &PackInstallTask::onResolveModsSucceeded); connect(m_mod_id_resolver_task.get(), &Flame::FileResolvingTask::failed, this, &PackInstallTask::onResolveModsFailed); @@ -294,7 +294,7 @@ void PackInstallTask::downloadPack() setStatus(tr("Downloading mods...")); setAbortable(false); - auto* jobPtr = new NetJob(tr("Mod download"), APPLICATION->network()); + auto jobPtr = makeShared(tr("Mod download"), APPLICATION->network()); for (auto const& file : m_version.files) { if (file.serverOnly || file.url.isEmpty()) continue; @@ -313,10 +313,10 @@ void PackInstallTask::downloadPack() jobPtr->addNetAction(dl); } - connect(jobPtr, &NetJob::succeeded, this, &PackInstallTask::onModDownloadSucceeded); - connect(jobPtr, &NetJob::failed, this, &PackInstallTask::onModDownloadFailed); - connect(jobPtr, &NetJob::aborted, this, &PackInstallTask::abort); - connect(jobPtr, &NetJob::progress, this, &PackInstallTask::setProgress); + connect(jobPtr.get(), &NetJob::succeeded, this, &PackInstallTask::onModDownloadSucceeded); + connect(jobPtr.get(), &NetJob::failed, this, &PackInstallTask::onModDownloadFailed); + connect(jobPtr.get(), &NetJob::aborted, this, &PackInstallTask::abort); + connect(jobPtr.get(), &NetJob::progress, this, &PackInstallTask::setProgress); m_net_job = jobPtr; diff --git a/launcher/modplatform/modrinth/ModrinthAPI.cpp b/launcher/modplatform/modrinth/ModrinthAPI.cpp index 5a16113d..29e3d129 100644 --- a/launcher/modplatform/modrinth/ModrinthAPI.cpp +++ b/launcher/modplatform/modrinth/ModrinthAPI.cpp @@ -11,19 +11,19 @@ Task::Ptr ModrinthAPI::currentVersion(QString hash, QString hash_format, QByteArray* response) { - auto* netJob = new NetJob(QString("Modrinth::GetCurrentVersion"), APPLICATION->network()); + auto netJob = makeShared(QString("Modrinth::GetCurrentVersion"), APPLICATION->network()); netJob->addNetAction(Net::Download::makeByteArray( QString(BuildConfig.MODRINTH_PROD_URL + "/version_file/%1?algorithm=%2").arg(hash, hash_format), response)); - QObject::connect(netJob, &NetJob::finished, [response] { delete response; }); + QObject::connect(netJob.get(), &NetJob::finished, [response] { delete response; }); return netJob; } Task::Ptr ModrinthAPI::currentVersions(const QStringList& hashes, QString hash_format, QByteArray* response) { - auto* netJob = new NetJob(QString("Modrinth::GetCurrentVersions"), APPLICATION->network()); + auto netJob = makeShared(QString("Modrinth::GetCurrentVersions"), APPLICATION->network()); QJsonObject body_obj; @@ -35,7 +35,7 @@ Task::Ptr ModrinthAPI::currentVersions(const QStringList& hashes, QString hash_f netJob->addNetAction(Net::Upload::makeByteArray(QString(BuildConfig.MODRINTH_PROD_URL + "/version_files"), response, body_raw)); - QObject::connect(netJob, &NetJob::finished, [response] { delete response; }); + QObject::connect(netJob.get(), &NetJob::finished, [response] { delete response; }); return netJob; } @@ -46,7 +46,7 @@ Task::Ptr ModrinthAPI::latestVersion(QString hash, std::optional loaders, QByteArray* response) { - auto* netJob = new NetJob(QString("Modrinth::GetLatestVersion"), APPLICATION->network()); + auto netJob = makeShared(QString("Modrinth::GetLatestVersion"), APPLICATION->network()); QJsonObject body_obj; @@ -67,7 +67,7 @@ Task::Ptr ModrinthAPI::latestVersion(QString hash, netJob->addNetAction(Net::Upload::makeByteArray( QString(BuildConfig.MODRINTH_PROD_URL + "/version_file/%1/update?algorithm=%2").arg(hash, hash_format), response, body_raw)); - QObject::connect(netJob, &NetJob::finished, [response] { delete response; }); + QObject::connect(netJob.get(), &NetJob::finished, [response] { delete response; }); return netJob; } @@ -78,7 +78,7 @@ Task::Ptr ModrinthAPI::latestVersions(const QStringList& hashes, std::optional loaders, QByteArray* response) { - auto* netJob = new NetJob(QString("Modrinth::GetLatestVersions"), APPLICATION->network()); + auto netJob = makeShared(QString("Modrinth::GetLatestVersions"), APPLICATION->network()); QJsonObject body_obj; @@ -101,21 +101,20 @@ Task::Ptr ModrinthAPI::latestVersions(const QStringList& hashes, netJob->addNetAction(Net::Upload::makeByteArray(QString(BuildConfig.MODRINTH_PROD_URL + "/version_files/update"), response, body_raw)); - QObject::connect(netJob, &NetJob::finished, [response] { delete response; }); + QObject::connect(netJob.get(), &NetJob::finished, [response] { delete response; }); return netJob; } Task::Ptr ModrinthAPI::getProjects(QStringList addonIds, QByteArray* response) const { - auto netJob = new NetJob(QString("Modrinth::GetProjects"), APPLICATION->network()); + auto netJob = makeShared(QString("Modrinth::GetProjects"), APPLICATION->network()); auto searchUrl = getMultipleModInfoURL(addonIds); netJob->addNetAction(Net::Download::makeByteArray(QUrl(searchUrl), response)); - QObject::connect(netJob, &NetJob::finished, [response, netJob] { + QObject::connect(netJob.get(), &NetJob::finished, [response, netJob] { delete response; - netJob->deleteLater(); }); return netJob; diff --git a/launcher/modplatform/modrinth/ModrinthCheckUpdate.cpp b/launcher/modplatform/modrinth/ModrinthCheckUpdate.cpp index daca68d7..d1be7209 100644 --- a/launcher/modplatform/modrinth/ModrinthCheckUpdate.cpp +++ b/launcher/modplatform/modrinth/ModrinthCheckUpdate.cpp @@ -159,7 +159,7 @@ void ModrinthCheckUpdate::executeTask() pack.description = mod->description(); pack.provider = ModPlatform::ResourceProvider::MODRINTH; - auto download_task = new ResourceDownloadTask(pack, project_ver, m_mods_folder); + auto download_task = makeShared(pack, project_ver, m_mods_folder); m_updatable.emplace_back(pack.name, hash, mod->version(), project_ver.version_number, project_ver.changelog, ModPlatform::ResourceProvider::MODRINTH, download_task); diff --git a/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp b/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp index c5a27c9d..94c0bf77 100644 --- a/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp +++ b/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp @@ -223,7 +223,7 @@ bool ModrinthCreationTask::createInstance() instance.setName(name()); instance.saveNow(); - m_files_job = new NetJob(tr("Mod download"), APPLICATION->network()); + m_files_job.reset(new NetJob(tr("Mod download"), APPLICATION->network())); for (auto file : m_files) { auto path = FS::PathCombine(m_stagingPath, ".minecraft", file.path); diff --git a/launcher/modplatform/technic/SingleZipPackInstallTask.cpp b/launcher/modplatform/technic/SingleZipPackInstallTask.cpp index 6438d9ef..8fd43d21 100644 --- a/launcher/modplatform/technic/SingleZipPackInstallTask.cpp +++ b/launcher/modplatform/technic/SingleZipPackInstallTask.cpp @@ -44,7 +44,7 @@ void Technic::SingleZipPackInstallTask::executeTask() const QString path = m_sourceUrl.host() + '/' + m_sourceUrl.path(); auto entry = APPLICATION->metacache()->resolveEntry("general", path); entry->setStale(true); - m_filesNetJob = new NetJob(tr("Modpack download"), APPLICATION->network()); + m_filesNetJob.reset(new NetJob(tr("Modpack download"), APPLICATION->network())); m_filesNetJob->addNetAction(Net::Download::makeCached(m_sourceUrl, entry)); m_archivePath = entry->getFullPath(); auto job = m_filesNetJob.get(); @@ -130,7 +130,7 @@ void Technic::SingleZipPackInstallTask::extractFinished() } } - shared_qobject_ptr packProcessor = new Technic::TechnicPackProcessor(); + auto packProcessor = makeShared(); connect(packProcessor.get(), &Technic::TechnicPackProcessor::succeeded, this, &Technic::SingleZipPackInstallTask::emitSucceeded); connect(packProcessor.get(), &Technic::TechnicPackProcessor::failed, this, &Technic::SingleZipPackInstallTask::emitFailed); packProcessor->run(m_globalSettings, name(), m_instIcon, m_stagingPath, m_minecraftVersion); diff --git a/launcher/modplatform/technic/SolderPackInstallTask.cpp b/launcher/modplatform/technic/SolderPackInstallTask.cpp index 19731b38..77c503f0 100644 --- a/launcher/modplatform/technic/SolderPackInstallTask.cpp +++ b/launcher/modplatform/technic/SolderPackInstallTask.cpp @@ -70,7 +70,7 @@ void Technic::SolderPackInstallTask::executeTask() { setStatus(tr("Resolving modpack files")); - m_filesNetJob = new NetJob(tr("Resolving modpack files"), m_network); + m_filesNetJob.reset(new NetJob(tr("Resolving modpack files"), m_network)); auto sourceUrl = QString("%1/modpack/%2/%3").arg(m_solderUrl.toString(), m_pack, m_version); m_filesNetJob->addNetAction(Net::Download::makeByteArray(sourceUrl, &m_response)); @@ -107,7 +107,7 @@ void Technic::SolderPackInstallTask::fileListSucceeded() if (!build.minecraft.isEmpty()) m_minecraftVersion = build.minecraft; - m_filesNetJob = new NetJob(tr("Downloading modpack"), m_network); + m_filesNetJob.reset(new NetJob(tr("Downloading modpack"), m_network)); int i = 0; for (const auto &mod : build.mods) { @@ -219,7 +219,7 @@ void Technic::SolderPackInstallTask::extractFinished() } } - shared_qobject_ptr packProcessor = new Technic::TechnicPackProcessor(); + auto packProcessor = makeShared(); connect(packProcessor.get(), &Technic::TechnicPackProcessor::succeeded, this, &Technic::SolderPackInstallTask::emitSucceeded); connect(packProcessor.get(), &Technic::TechnicPackProcessor::failed, this, &Technic::SolderPackInstallTask::emitFailed); packProcessor->run(m_globalSettings, name(), m_instIcon, m_stagingPath, m_minecraftVersion, true); diff --git a/launcher/net/Download.cpp b/launcher/net/Download.cpp index fd3dbedc..5982c8c9 100644 --- a/launcher/net/Download.cpp +++ b/launcher/net/Download.cpp @@ -49,14 +49,9 @@ namespace Net { -Download::Download() : NetAction() -{ - m_state = State::Inactive; -} - auto Download::makeCached(QUrl url, MetaEntryPtr entry, Options options) -> Download::Ptr { - auto* dl = new Download(); + auto dl = makeShared(); dl->m_url = url; dl->m_options = options; auto md5Node = new ChecksumValidator(QCryptographicHash::Md5); @@ -67,7 +62,7 @@ auto Download::makeCached(QUrl url, MetaEntryPtr entry, Options options) -> Down auto Download::makeByteArray(QUrl url, QByteArray* output, Options options) -> Download::Ptr { - auto* dl = new Download(); + auto dl = makeShared(); dl->m_url = url; dl->m_options = options; dl->m_sink.reset(new ByteArraySink(output)); @@ -76,7 +71,7 @@ auto Download::makeByteArray(QUrl url, QByteArray* output, Options options) -> D auto Download::makeFile(QUrl url, QString path, Options options) -> Download::Ptr { - auto* dl = new Download(); + auto dl = makeShared(); dl->m_url = url; dl->m_options = options; dl->m_sink.reset(new FileSink(path)); diff --git a/launcher/net/Download.h b/launcher/net/Download.h index 3faa5db5..7e1df322 100644 --- a/launcher/net/Download.h +++ b/launcher/net/Download.h @@ -52,9 +52,6 @@ class Download : public NetAction { enum class Option { NoOptions = 0, AcceptLocalFiles = 1, MakeEternal = 2 }; Q_DECLARE_FLAGS(Options, Option) - protected: - explicit Download(); - public: ~Download() override = default; diff --git a/launcher/net/Upload.cpp b/launcher/net/Upload.cpp index f3b19022..79b6af8d 100644 --- a/launcher/net/Upload.cpp +++ b/launcher/net/Upload.cpp @@ -233,7 +233,7 @@ namespace Net { } Upload::Ptr Upload::makeByteArray(QUrl url, QByteArray *output, QByteArray m_post_data) { - auto* up = new Upload(); + auto up = makeShared(); up->m_url = std::move(url); up->m_sink.reset(new ByteArraySink(output)); up->m_post_data = std::move(m_post_data); diff --git a/launcher/net/Upload.h b/launcher/net/Upload.h index 7c194bbc..5a0b2e74 100644 --- a/launcher/net/Upload.h +++ b/launcher/net/Upload.h @@ -45,6 +45,8 @@ namespace Net { Q_OBJECT public: + using Ptr = shared_qobject_ptr; + static Upload::Ptr makeByteArray(QUrl url, QByteArray *output, QByteArray m_post_data); auto abort() -> bool override; auto canAbort() const -> bool override { return true; }; diff --git a/launcher/news/NewsChecker.cpp b/launcher/news/NewsChecker.cpp index 3b969732..1f1520d0 100644 --- a/launcher/news/NewsChecker.cpp +++ b/launcher/news/NewsChecker.cpp @@ -57,10 +57,10 @@ void NewsChecker::reloadNews() qDebug() << "Reloading news."; - NetJob* job = new NetJob("News RSS Feed", m_network); + NetJob::Ptr job{ new NetJob("News RSS Feed", m_network) }; job->addNetAction(Net::Download::makeByteArray(m_feedUrl, &newsData)); - QObject::connect(job, &NetJob::succeeded, this, &NewsChecker::rssDownloadFinished); - QObject::connect(job, &NetJob::failed, this, &NewsChecker::rssDownloadFailed); + QObject::connect(job.get(), &NetJob::succeeded, this, &NewsChecker::rssDownloadFinished); + QObject::connect(job.get(), &NetJob::failed, this, &NewsChecker::rssDownloadFailed); m_newsNetJob.reset(job); job->start(); } diff --git a/launcher/tasks/ConcurrentTask.h b/launcher/tasks/ConcurrentTask.h index b46919fb..d074d2e2 100644 --- a/launcher/tasks/ConcurrentTask.h +++ b/launcher/tasks/ConcurrentTask.h @@ -8,6 +8,8 @@ class ConcurrentTask : public Task { Q_OBJECT public: + using Ptr = shared_qobject_ptr; + explicit ConcurrentTask(QObject* parent = nullptr, QString task_name = "", int max_concurrent = 6); ~ConcurrentTask() override; diff --git a/launcher/translations/TranslationsModel.cpp b/launcher/translations/TranslationsModel.cpp index 38f48296..46db4804 100644 --- a/launcher/translations/TranslationsModel.cpp +++ b/launcher/translations/TranslationsModel.cpp @@ -670,7 +670,7 @@ void TranslationsModel::downloadIndex() return; } qDebug() << "Downloading Translations Index..."; - d->m_index_job = new NetJob("Translations Index", APPLICATION->network()); + d->m_index_job.reset(new NetJob("Translations Index", APPLICATION->network())); MetaEntryPtr entry = APPLICATION->metacache()->resolveEntry("translations", "index_v2.json"); entry->setStale(true); d->m_index_task = Net::Download::makeCached(QUrl(BuildConfig.TRANSLATIONS_BASE_URL + "index_v2.json"), entry); @@ -722,7 +722,7 @@ void TranslationsModel::downloadTranslation(QString key) dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Sha1, rawHash)); dl->setProgress(dl->getProgress(), lang->file_size); - d->m_dl_job = new NetJob("Translation for " + key, APPLICATION->network()); + d->m_dl_job.reset(new NetJob("Translation for " + key, APPLICATION->network())); d->m_dl_job->addNetAction(dl); connect(d->m_dl_job.get(), &NetJob::succeeded, this, &TranslationsModel::dlGood); diff --git a/launcher/ui/dialogs/ModUpdateDialog.cpp b/launcher/ui/dialogs/ModUpdateDialog.cpp index 4ef42d6c..8618b924 100644 --- a/launcher/ui/dialogs/ModUpdateDialog.cpp +++ b/launcher/ui/dialogs/ModUpdateDialog.cpp @@ -88,15 +88,15 @@ void ModUpdateDialog::checkCandidates() SequentialTask check_task(m_parent, tr("Checking for updates")); if (!m_modrinth_to_update.empty()) { - m_modrinth_check_task = new ModrinthCheckUpdate(m_modrinth_to_update, versions, loaders, m_mod_model); - connect(m_modrinth_check_task, &CheckUpdateTask::checkFailed, this, + m_modrinth_check_task.reset(new ModrinthCheckUpdate(m_modrinth_to_update, versions, loaders, m_mod_model)); + connect(m_modrinth_check_task.get(), &CheckUpdateTask::checkFailed, this, [this](Mod* mod, QString reason, QUrl recover_url) { m_failed_check_update.append({mod, reason, recover_url}); }); check_task.addTask(m_modrinth_check_task); } if (!m_flame_to_update.empty()) { - m_flame_check_task = new FlameCheckUpdate(m_flame_to_update, versions, loaders, m_mod_model); - connect(m_flame_check_task, &CheckUpdateTask::checkFailed, this, + m_flame_check_task.reset(new FlameCheckUpdate(m_flame_to_update, versions, loaders, m_mod_model)); + connect(m_flame_check_task.get(), &CheckUpdateTask::checkFailed, this, [this](Mod* mod, QString reason, QUrl recover_url) { m_failed_check_update.append({mod, reason, recover_url}); }); check_task.addTask(m_flame_check_task); } @@ -266,9 +266,9 @@ auto ModUpdateDialog::ensureMetadata() -> bool } if (!modrinth_tmp.empty()) { - auto* modrinth_task = new EnsureMetadataTask(modrinth_tmp, index_dir, ModPlatform::ResourceProvider::MODRINTH); - connect(modrinth_task, &EnsureMetadataTask::metadataReady, [this](Mod* candidate) { onMetadataEnsured(candidate); }); - connect(modrinth_task, &EnsureMetadataTask::metadataFailed, [this, &should_try_others](Mod* candidate) { + auto modrinth_task = makeShared(modrinth_tmp, index_dir, ModPlatform::ResourceProvider::MODRINTH); + connect(modrinth_task.get(), &EnsureMetadataTask::metadataReady, [this](Mod* candidate) { onMetadataEnsured(candidate); }); + connect(modrinth_task.get(), &EnsureMetadataTask::metadataFailed, [this, &should_try_others](Mod* candidate) { onMetadataFailed(candidate, should_try_others.find(candidate->internal_id()).value(), ModPlatform::ResourceProvider::MODRINTH); }); @@ -279,9 +279,9 @@ auto ModUpdateDialog::ensureMetadata() -> bool } if (!flame_tmp.empty()) { - auto* flame_task = new EnsureMetadataTask(flame_tmp, index_dir, ModPlatform::ResourceProvider::FLAME); - connect(flame_task, &EnsureMetadataTask::metadataReady, [this](Mod* candidate) { onMetadataEnsured(candidate); }); - connect(flame_task, &EnsureMetadataTask::metadataFailed, [this, &should_try_others](Mod* candidate) { + auto flame_task = makeShared(flame_tmp, index_dir, ModPlatform::ResourceProvider::FLAME); + connect(flame_task.get(), &EnsureMetadataTask::metadataReady, [this](Mod* candidate) { onMetadataEnsured(candidate); }); + connect(flame_task.get(), &EnsureMetadataTask::metadataFailed, [this, &should_try_others](Mod* candidate) { onMetadataFailed(candidate, should_try_others.find(candidate->internal_id()).value(), ModPlatform::ResourceProvider::FLAME); }); @@ -334,9 +334,9 @@ void ModUpdateDialog::onMetadataFailed(Mod* mod, bool try_others, ModPlatform::R if (try_others) { auto index_dir = indexDir(); - auto* task = new EnsureMetadataTask(mod, index_dir, next(first_choice)); - connect(task, &EnsureMetadataTask::metadataReady, [this](Mod* candidate) { onMetadataEnsured(candidate); }); - connect(task, &EnsureMetadataTask::metadataFailed, [this](Mod* candidate) { onMetadataFailed(candidate, false); }); + auto task = makeShared(mod, index_dir, next(first_choice)); + connect(task.get(), &EnsureMetadataTask::metadataReady, [this](Mod* candidate) { onMetadataEnsured(candidate); }); + connect(task.get(), &EnsureMetadataTask::metadataFailed, [this](Mod* candidate) { onMetadataFailed(candidate, false); }); m_second_try_metadata->addTask(task); } else { @@ -388,9 +388,9 @@ void ModUpdateDialog::appendMod(CheckUpdateTask::UpdatableMod const& info) ui->modTreeWidget->addTopLevelItem(item_top); } -auto ModUpdateDialog::getTasks() -> const QList +auto ModUpdateDialog::getTasks() -> const QList { - QList list; + QList list; auto* item = ui->modTreeWidget->topLevelItem(0); diff --git a/launcher/ui/dialogs/ModUpdateDialog.h b/launcher/ui/dialogs/ModUpdateDialog.h index 3e3dd90d..1a92f613 100644 --- a/launcher/ui/dialogs/ModUpdateDialog.h +++ b/launcher/ui/dialogs/ModUpdateDialog.h @@ -25,7 +25,7 @@ class ModUpdateDialog final : public ReviewMessageBox { void appendMod(const CheckUpdateTask::UpdatableMod& info); - const QList getTasks(); + const QList getTasks(); auto indexDir() const -> QDir { return m_mod_model->indexDir(); } auto noUpdates() const -> bool { return m_no_updates; }; @@ -41,8 +41,8 @@ class ModUpdateDialog final : public ReviewMessageBox { private: QWidget* m_parent; - ModrinthCheckUpdate* m_modrinth_check_task = nullptr; - FlameCheckUpdate* m_flame_check_task = nullptr; + shared_qobject_ptr m_modrinth_check_task; + shared_qobject_ptr m_flame_check_task; const std::shared_ptr m_mod_model; @@ -50,11 +50,11 @@ class ModUpdateDialog final : public ReviewMessageBox { QList m_modrinth_to_update; QList m_flame_to_update; - ConcurrentTask* m_second_try_metadata; + ConcurrentTask::Ptr m_second_try_metadata; QList> m_failed_metadata; QList> m_failed_check_update; - QHash m_tasks; + QHash m_tasks; BaseInstance* m_instance; bool m_no_updates = false; diff --git a/launcher/ui/dialogs/ResourceDownloadDialog.cpp b/launcher/ui/dialogs/ResourceDownloadDialog.cpp index b9367c16..fa829bfb 100644 --- a/launcher/ui/dialogs/ResourceDownloadDialog.cpp +++ b/launcher/ui/dialogs/ResourceDownloadDialog.cpp @@ -147,7 +147,7 @@ void ResourceDownloadDialog::addResource(ModPlatform::IndexedPack& pack, ModPlat removeResource(pack, ver); ver.is_currently_selected = true; - m_selected.insert(pack.name, new ResourceDownloadTask(pack, ver, getBaseModel(), is_indexed)); + m_selected.insert(pack.name, makeShared(pack, ver, getBaseModel(), is_indexed)); m_buttons.button(QDialogButtonBox::Ok)->setEnabled(!m_selected.isEmpty()); } diff --git a/launcher/ui/pages/instance/VersionPage.cpp b/launcher/ui/pages/instance/VersionPage.cpp index d200652a..94315395 100644 --- a/launcher/ui/pages/instance/VersionPage.cpp +++ b/launcher/ui/pages/instance/VersionPage.cpp @@ -660,7 +660,7 @@ void VersionPage::onGameUpdateError(QString error) CustomMessageBox::selectable(this, tr("Error updating instance"), error, QMessageBox::Warning)->show(); } -Component * VersionPage::current() +ComponentPtr VersionPage::current() { auto row = currentRow(); if(row < 0) diff --git a/launcher/ui/pages/instance/VersionPage.h b/launcher/ui/pages/instance/VersionPage.h index 166f36bb..183bad9a 100644 --- a/launcher/ui/pages/instance/VersionPage.h +++ b/launcher/ui/pages/instance/VersionPage.h @@ -99,7 +99,7 @@ private slots: void updateVersionControls(); private: - Component * current(); + ComponentPtr current(); int currentRow(); void updateButtons(int row = -1); void preselect(int row = 0); diff --git a/launcher/ui/pages/modplatform/ResourceModel.cpp b/launcher/ui/pages/modplatform/ResourceModel.cpp index 8af70104..db7d26f8 100644 --- a/launcher/ui/pages/modplatform/ResourceModel.cpp +++ b/launcher/ui/pages/modplatform/ResourceModel.cpp @@ -265,7 +265,7 @@ std::optional ResourceModel::getIcon(QModelIndex& index, const QUrl& url) return { pixmap }; if (!m_current_icon_job) - m_current_icon_job = new NetJob("IconJob", APPLICATION->network()); + m_current_icon_job.reset(new NetJob("IconJob", APPLICATION->network())); if (m_currently_running_icon_actions.contains(url)) return {}; diff --git a/launcher/ui/pages/modplatform/atlauncher/AtlListModel.cpp b/launcher/ui/pages/modplatform/atlauncher/AtlListModel.cpp index 2ce04068..9ad26f47 100644 --- a/launcher/ui/pages/modplatform/atlauncher/AtlListModel.cpp +++ b/launcher/ui/pages/modplatform/atlauncher/AtlListModel.cpp @@ -86,14 +86,14 @@ void ListModel::request() modpacks.clear(); endResetModel(); - auto *netJob = new NetJob("Atl::Request", APPLICATION->network()); + auto netJob = makeShared("Atl::Request", APPLICATION->network()); auto url = QString(BuildConfig.ATL_DOWNLOAD_SERVER_URL + "launcher/json/packsnew.json"); netJob->addNetAction(Net::Download::makeByteArray(QUrl(url), &response)); jobPtr = netJob; jobPtr->start(); - QObject::connect(netJob, &NetJob::succeeded, this, &ListModel::requestFinished); - QObject::connect(netJob, &NetJob::failed, this, &ListModel::requestFailed); + QObject::connect(netJob.get(), &NetJob::succeeded, this, &ListModel::requestFinished); + QObject::connect(netJob.get(), &NetJob::failed, this, &ListModel::requestFailed); } void ListModel::requestFinished() diff --git a/launcher/ui/pages/modplatform/flame/FlameModel.cpp b/launcher/ui/pages/modplatform/flame/FlameModel.cpp index 127c3de5..5961ea02 100644 --- a/launcher/ui/pages/modplatform/flame/FlameModel.cpp +++ b/launcher/ui/pages/modplatform/flame/FlameModel.cpp @@ -155,7 +155,7 @@ void ListModel::fetchMore(const QModelIndex& parent) void ListModel::performPaginatedSearch() { - NetJob* netJob = new NetJob("Flame::Search", APPLICATION->network()); + auto netJob = makeShared("Flame::Search", APPLICATION->network()); auto searchUrl = QString( "https://api.curseforge.com/v1/mods/search?" "gameId=432&" @@ -172,8 +172,8 @@ void ListModel::performPaginatedSearch() 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); + QObject::connect(netJob.get(), &NetJob::succeeded, this, &ListModel::searchRequestFinished); + QObject::connect(netJob.get(), &NetJob::failed, this, &ListModel::searchRequestFailed); } void ListModel::searchWithTerm(const QString& term, int sort) diff --git a/launcher/ui/pages/modplatform/ftb/FtbListModel.cpp b/launcher/ui/pages/modplatform/ftb/FtbListModel.cpp index ce2b2b18..e8065415 100644 --- a/launcher/ui/pages/modplatform/ftb/FtbListModel.cpp +++ b/launcher/ui/pages/modplatform/ftb/FtbListModel.cpp @@ -109,14 +109,14 @@ void ListModel::request() modpacks.clear(); endResetModel(); - auto *netJob = new NetJob("Ftb::Request", APPLICATION->network()); + auto netJob = makeShared("Ftb::Request", APPLICATION->network()); auto url = QString(BuildConfig.MODPACKSCH_API_BASE_URL + "public/modpack/all"); netJob->addNetAction(Net::Download::makeByteArray(QUrl(url), &response)); jobPtr = netJob; jobPtr->start(); - QObject::connect(netJob, &NetJob::succeeded, this, &ListModel::requestFinished); - QObject::connect(netJob, &NetJob::failed, this, &ListModel::requestFailed); + QObject::connect(netJob.get(), &NetJob::succeeded, this, &ListModel::requestFinished); + QObject::connect(netJob.get(), &NetJob::failed, this, &ListModel::requestFailed); } void ListModel::abortRequest() @@ -158,14 +158,14 @@ void ListModel::requestFailed(QString reason) void ListModel::requestPack() { - auto *netJob = new NetJob("Ftb::Search", APPLICATION->network()); + auto netJob = makeShared("Ftb::Search", APPLICATION->network()); auto searchUrl = QString(BuildConfig.MODPACKSCH_API_BASE_URL + "public/modpack/%1").arg(currentPack); netJob->addNetAction(Net::Download::makeByteArray(QUrl(searchUrl), &response)); jobPtr = netJob; jobPtr->start(); - QObject::connect(netJob, &NetJob::succeeded, this, &ListModel::packRequestFinished); - QObject::connect(netJob, &NetJob::failed, this, &ListModel::packRequestFailed); + QObject::connect(netJob.get(), &NetJob::succeeded, this, &ListModel::packRequestFinished); + QObject::connect(netJob.get(), &NetJob::failed, this, &ListModel::packRequestFailed); } void ListModel::packRequestFinished() @@ -281,16 +281,16 @@ void ListModel::requestLogo(QString logo, QString url) bool stale = entry->isStale(); - NetJob *job = new NetJob(QString("ModpacksCH Icon Download %1").arg(logo), APPLICATION->network()); + auto job = makeShared(QString("ModpacksCH Icon Download %1").arg(logo), APPLICATION->network()); job->addNetAction(Net::Download::makeCached(QUrl(url), entry)); auto fullPath = entry->getFullPath(); - QObject::connect(job, &NetJob::finished, this, [this, logo, fullPath, stale] + QObject::connect(job.get(), &NetJob::finished, this, [this, logo, fullPath, stale] { logoLoaded(logo, stale); }); - QObject::connect(job, &NetJob::failed, this, [this, logo] + QObject::connect(job.get(), &NetJob::failed, this, [this, logo] { logoFailed(logo); }); diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp index 80850b4c..346a00b0 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp @@ -127,7 +127,7 @@ bool ModpackListModel::setData(const QModelIndex &index, const QVariant &value, void ModpackListModel::performPaginatedSearch() { // TODO: Move to standalone API - NetJob* netJob = new NetJob("Modrinth::SearchModpack", APPLICATION->network()); + auto netJob = makeShared("Modrinth::SearchModpack", APPLICATION->network()); auto searchAllUrl = QString(BuildConfig.MODRINTH_PROD_URL + "/search?" "offset=%1&" @@ -142,7 +142,7 @@ void ModpackListModel::performPaginatedSearch() netJob->addNetAction(Net::Download::makeByteArray(QUrl(searchAllUrl), &m_all_response)); - QObject::connect(netJob, &NetJob::succeeded, this, [this] { + QObject::connect(netJob.get(), &NetJob::succeeded, this, [this] { QJsonParseError parse_error_all{}; QJsonDocument doc_all = QJsonDocument::fromJson(m_all_response, &parse_error_all); @@ -155,7 +155,7 @@ void ModpackListModel::performPaginatedSearch() searchRequestFinished(doc_all); }); - QObject::connect(netJob, &NetJob::failed, this, &ModpackListModel::searchRequestFailed); + QObject::connect(netJob.get(), &NetJob::failed, this, &ModpackListModel::searchRequestFailed); jobPtr = netJob; jobPtr->start(); diff --git a/launcher/ui/pages/modplatform/technic/TechnicModel.cpp b/launcher/ui/pages/modplatform/technic/TechnicModel.cpp index b2af1ac0..50f0c72d 100644 --- a/launcher/ui/pages/modplatform/technic/TechnicModel.cpp +++ b/launcher/ui/pages/modplatform/technic/TechnicModel.cpp @@ -112,7 +112,7 @@ void Technic::ListModel::searchWithTerm(const QString& term) void Technic::ListModel::performSearch() { - NetJob *netJob = new NetJob("Technic::Search", APPLICATION->network()); + auto netJob = makeShared("Technic::Search", APPLICATION->network()); QString searchUrl = ""; if (currentSearchTerm.isEmpty()) { searchUrl = QString("%1trending?build=%2") @@ -137,8 +137,8 @@ void Technic::ListModel::performSearch() 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); + QObject::connect(netJob.get(), &NetJob::succeeded, this, &ListModel::searchRequestFinished); + QObject::connect(netJob.get(), &NetJob::failed, this, &ListModel::searchRequestFailed); } void Technic::ListModel::searchRequestFinished() diff --git a/launcher/ui/pages/modplatform/technic/TechnicPage.cpp b/launcher/ui/pages/modplatform/technic/TechnicPage.cpp index b15af244..859da97e 100644 --- a/launcher/ui/pages/modplatform/technic/TechnicPage.cpp +++ b/launcher/ui/pages/modplatform/technic/TechnicPage.cpp @@ -141,10 +141,10 @@ void TechnicPage::suggestCurrent() return; } - NetJob *netJob = new NetJob(QString("Technic::PackMeta(%1)").arg(current.name), APPLICATION->network()); + auto netJob = makeShared(QString("Technic::PackMeta(%1)").arg(current.name), APPLICATION->network()); QString slug = current.slug; netJob->addNetAction(Net::Download::makeByteArray(QString("%1modpack/%2?build=%3").arg(BuildConfig.TECHNIC_API_BASE_URL, slug, BuildConfig.TECHNIC_API_BUILD), &response)); - QObject::connect(netJob, &NetJob::succeeded, this, [this, slug] + QObject::connect(netJob.get(), &NetJob::succeeded, this, [this, slug] { jobPtr.reset(); @@ -247,11 +247,11 @@ void TechnicPage::metadataLoaded() // version so we can display something quicker ui->versionSelectionBox->addItem(current.currentVersion); - auto* netJob = new NetJob(QString("Technic::SolderMeta(%1)").arg(current.name), APPLICATION->network()); + auto netJob = makeShared(QString("Technic::SolderMeta(%1)").arg(current.name), APPLICATION->network()); auto url = QString("%1/modpack/%2").arg(current.url, current.slug); netJob->addNetAction(Net::Download::makeByteArray(QUrl(url), &response)); - QObject::connect(netJob, &NetJob::succeeded, this, &TechnicPage::onSolderLoaded); + QObject::connect(netJob.get(), &NetJob::succeeded, this, &TechnicPage::onSolderLoaded); jobPtr = netJob; jobPtr->start(); diff --git a/tests/DummyResourceAPI.h b/tests/DummyResourceAPI.h index e91be96c..0cc90958 100644 --- a/tests/DummyResourceAPI.h +++ b/tests/DummyResourceAPI.h @@ -36,12 +36,11 @@ class DummyResourceAPI : public ResourceAPI { [[nodiscard]] Task::Ptr searchProjects(SearchArgs&&, SearchCallbacks&& callbacks) const override { - auto task = new SearchTask; - QObject::connect(task, &Task::succeeded, [=] { + auto task = makeShared(); + QObject::connect(task.get(), &Task::succeeded, [=] { auto json = searchRequestResult(); callbacks.on_succeed(json); }); - QObject::connect(task, &Task::finished, task, &Task::deleteLater); return task; } }; diff --git a/tests/Task_test.cpp b/tests/Task_test.cpp index 6649b724..558cd2c0 100644 --- a/tests/Task_test.cpp +++ b/tests/Task_test.cpp @@ -49,10 +49,10 @@ class BigConcurrentTask : public QThread { // NOTE: Arbitrary value that manages to trigger a problem when there is one. static const unsigned s_num_tasks = 1 << 14; - auto sub_tasks = new BasicTask*[s_num_tasks]; + auto sub_tasks = new BasicTask::Ptr[s_num_tasks]; for (unsigned i = 0; i < s_num_tasks; i++) { - sub_tasks[i] = new BasicTask(false); + sub_tasks[i] = makeShared(false); big_task.addTask(sub_tasks[i]); } @@ -119,21 +119,21 @@ class TaskTest : public QObject { } void test_basicConcurrentRun(){ - BasicTask t1; - BasicTask t2; - BasicTask t3; + auto t1 = makeShared(); + auto t2 = makeShared(); + auto t3 = makeShared(); ConcurrentTask t; - t.addTask(&t1); - t.addTask(&t2); - t.addTask(&t3); + t.addTask(t1); + t.addTask(t2); + t.addTask(t3); QObject::connect(&t, &Task::finished, [&]{ QVERIFY2(t.wasSuccessful(), "Task finished but was not successful when it should have been."); - QVERIFY(t1.wasSuccessful()); - QVERIFY(t2.wasSuccessful()); - QVERIFY(t3.wasSuccessful()); + QVERIFY(t1->wasSuccessful()); + QVERIFY(t2->wasSuccessful()); + QVERIFY(t3->wasSuccessful()); }); t.start(); @@ -144,31 +144,39 @@ class TaskTest : public QObject { // Tests if starting new tasks after the 6 initial ones is working void test_moreConcurrentRun(){ - BasicTask t1, t2, t3, t4, t5, t6, t7, t8, t9; + auto t1 = makeShared(); + auto t2 = makeShared(); + auto t3 = makeShared(); + auto t4 = makeShared(); + auto t5 = makeShared(); + auto t6 = makeShared(); + auto t7 = makeShared(); + auto t8 = makeShared(); + auto t9 = makeShared(); ConcurrentTask t; - t.addTask(&t1); - t.addTask(&t2); - t.addTask(&t3); - t.addTask(&t4); - t.addTask(&t5); - t.addTask(&t6); - t.addTask(&t7); - t.addTask(&t8); - t.addTask(&t9); + t.addTask(t1); + t.addTask(t2); + t.addTask(t3); + t.addTask(t4); + t.addTask(t5); + t.addTask(t6); + t.addTask(t7); + t.addTask(t8); + t.addTask(t9); QObject::connect(&t, &Task::finished, [&]{ QVERIFY2(t.wasSuccessful(), "Task finished but was not successful when it should have been."); - QVERIFY(t1.wasSuccessful()); - QVERIFY(t2.wasSuccessful()); - QVERIFY(t3.wasSuccessful()); - QVERIFY(t4.wasSuccessful()); - QVERIFY(t5.wasSuccessful()); - QVERIFY(t6.wasSuccessful()); - QVERIFY(t7.wasSuccessful()); - QVERIFY(t8.wasSuccessful()); - QVERIFY(t9.wasSuccessful()); + QVERIFY(t1->wasSuccessful()); + QVERIFY(t2->wasSuccessful()); + QVERIFY(t3->wasSuccessful()); + QVERIFY(t4->wasSuccessful()); + QVERIFY(t5->wasSuccessful()); + QVERIFY(t6->wasSuccessful()); + QVERIFY(t7->wasSuccessful()); + QVERIFY(t8->wasSuccessful()); + QVERIFY(t9->wasSuccessful()); }); t.start(); @@ -178,21 +186,21 @@ class TaskTest : public QObject { } void test_basicSequentialRun(){ - BasicTask t1; - BasicTask t2; - BasicTask t3; + auto t1 = makeShared(); + auto t2 = makeShared(); + auto t3 = makeShared(); SequentialTask t; - t.addTask(&t1); - t.addTask(&t2); - t.addTask(&t3); + t.addTask(t1); + t.addTask(t2); + t.addTask(t3); QObject::connect(&t, &Task::finished, [&]{ QVERIFY2(t.wasSuccessful(), "Task finished but was not successful when it should have been."); - QVERIFY(t1.wasSuccessful()); - QVERIFY(t2.wasSuccessful()); - QVERIFY(t3.wasSuccessful()); + QVERIFY(t1->wasSuccessful()); + QVERIFY(t2->wasSuccessful()); + QVERIFY(t3->wasSuccessful()); }); t.start(); @@ -202,21 +210,21 @@ class TaskTest : public QObject { } void test_basicMultipleOptionsRun(){ - BasicTask t1; - BasicTask t2; - BasicTask t3; + auto t1 = makeShared(); + auto t2 = makeShared(); + auto t3 = makeShared(); MultipleOptionsTask t; - t.addTask(&t1); - t.addTask(&t2); - t.addTask(&t3); + t.addTask(t1); + t.addTask(t2); + t.addTask(t3); QObject::connect(&t, &Task::finished, [&]{ QVERIFY2(t.wasSuccessful(), "Task finished but was not successful when it should have been."); - QVERIFY(t1.wasSuccessful()); - QVERIFY(!t2.wasSuccessful()); - QVERIFY(!t3.wasSuccessful()); + QVERIFY(t1->wasSuccessful()); + QVERIFY(!t2->wasSuccessful()); + QVERIFY(!t3->wasSuccessful()); }); t.start(); -- cgit From c3ea303a3742c886aae9e05d2e5f5fbb497260a1 Mon Sep 17 00:00:00 2001 From: flow Date: Fri, 16 Dec 2022 20:26:10 -0300 Subject: feat(RD): add resource pack downloader Signed-off-by: flow --- launcher/Application.cpp | 1 + launcher/CMakeLists.txt | 4 + launcher/modplatform/flame/FlameAPI.h | 2 + launcher/modplatform/modrinth/ModrinthAPI.h | 2 + launcher/ui/dialogs/ResourceDownloadDialog.cpp | 27 ++++++ launcher/ui/dialogs/ResourceDownloadDialog.h | 20 ++++ launcher/ui/pages/instance/ResourcePackPage.cpp | 102 +++++++++++++++++++++ launcher/ui/pages/instance/ResourcePackPage.h | 18 +--- .../ui/pages/modplatform/ResourcePackModel.cpp | 42 +++++++++ launcher/ui/pages/modplatform/ResourcePackModel.h | 39 ++++++++ launcher/ui/pages/modplatform/ResourcePackPage.cpp | 42 +++++++++ launcher/ui/pages/modplatform/ResourcePackPage.h | 48 ++++++++++ .../modplatform/flame/FlameResourceModels.cpp | 24 +++++ .../pages/modplatform/flame/FlameResourceModels.h | 19 ++++ .../pages/modplatform/flame/FlameResourcePages.cpp | 57 ++++++++++-- .../pages/modplatform/flame/FlameResourcePages.h | 28 ++++++ .../modrinth/ModrinthResourceModels.cpp | 22 +++++ .../modplatform/modrinth/ModrinthResourceModels.h | 21 ++++- .../modplatform/modrinth/ModrinthResourcePages.cpp | 19 ++++ .../modplatform/modrinth/ModrinthResourcePages.h | 24 +++++ 20 files changed, 538 insertions(+), 23 deletions(-) create mode 100644 launcher/ui/pages/instance/ResourcePackPage.cpp create mode 100644 launcher/ui/pages/modplatform/ResourcePackModel.cpp create mode 100644 launcher/ui/pages/modplatform/ResourcePackModel.h create mode 100644 launcher/ui/pages/modplatform/ResourcePackPage.cpp create mode 100644 launcher/ui/pages/modplatform/ResourcePackPage.h (limited to 'launcher/ui/dialogs') diff --git a/launcher/Application.cpp b/launcher/Application.cpp index 0d3b086f..c1ce4659 100644 --- a/launcher/Application.cpp +++ b/launcher/Application.cpp @@ -610,6 +610,7 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv) m_settings->registerSetting("UpdateDialogGeometry", ""); m_settings->registerSetting("ModDownloadGeometry", ""); + m_settings->registerSetting("RPDownloadGeometry", ""); // HACK: This code feels so stupid is there a less stupid way of doing this? { diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index 1bfe9cbc..7ae148e0 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -721,6 +721,7 @@ SET(LAUNCHER_SOURCES ui/pages/instance/ManagedPackPage.h ui/pages/instance/TexturePackPage.h ui/pages/instance/ResourcePackPage.h + ui/pages/instance/ResourcePackPage.cpp ui/pages/instance/ShaderPackPage.h ui/pages/instance/ModFolderPage.cpp ui/pages/instance/ModFolderPage.h @@ -773,6 +774,9 @@ SET(LAUNCHER_SOURCES ui/pages/modplatform/ModModel.cpp ui/pages/modplatform/ModModel.h + ui/pages/modplatform/ResourcePackPage.cpp + ui/pages/modplatform/ResourcePackModel.cpp + ui/pages/modplatform/atlauncher/AtlFilterModel.cpp ui/pages/modplatform/atlauncher/AtlFilterModel.h ui/pages/modplatform/atlauncher/AtlListModel.cpp diff --git a/launcher/modplatform/flame/FlameAPI.h b/launcher/modplatform/flame/FlameAPI.h index 06d749e6..5811d717 100644 --- a/launcher/modplatform/flame/FlameAPI.h +++ b/launcher/modplatform/flame/FlameAPI.h @@ -27,6 +27,8 @@ class FlameAPI : public NetworkResourceAPI { default: case ModPlatform::ResourceType::MOD: return 6; + case ModPlatform::ResourceType::RESOURCE_PACK: + return 12; } } diff --git a/launcher/modplatform/modrinth/ModrinthAPI.h b/launcher/modplatform/modrinth/ModrinthAPI.h index dda27303..0b2d149e 100644 --- a/launcher/modplatform/modrinth/ModrinthAPI.h +++ b/launcher/modplatform/modrinth/ModrinthAPI.h @@ -68,6 +68,8 @@ class ModrinthAPI : public NetworkResourceAPI { switch (type) { case ModPlatform::ResourceType::MOD: return "mod"; + case ModPlatform::ResourceType::RESOURCE_PACK: + return "resourcepack"; default: qWarning() << "Invalid resource type for Modrinth API!"; break; diff --git a/launcher/ui/dialogs/ResourceDownloadDialog.cpp b/launcher/ui/dialogs/ResourceDownloadDialog.cpp index fa829bfb..edcd642e 100644 --- a/launcher/ui/dialogs/ResourceDownloadDialog.cpp +++ b/launcher/ui/dialogs/ResourceDownloadDialog.cpp @@ -25,6 +25,7 @@ #include "ResourceDownloadTask.h" #include "minecraft/mod/ModFolderModel.h" +#include "minecraft/mod/ResourcePackFolderModel.h" #include "ui/dialogs/ReviewMessageBox.h" @@ -229,4 +230,30 @@ QList ModDownloadDialog::getPages() return pages; } + +ResourcePackDownloadDialog::ResourcePackDownloadDialog(QWidget* parent, + const std::shared_ptr& resource_packs, + BaseInstance* instance) + : ResourceDownloadDialog(parent, resource_packs), m_instance(instance) +{ + setWindowTitle(dialogTitle()); + + initializeContainer(); + connectButtons(); + + if (!geometrySaveKey().isEmpty()) + restoreGeometry(QByteArray::fromBase64(APPLICATION->settings()->get(geometrySaveKey()).toByteArray())); +} + +QList ResourcePackDownloadDialog::getPages() +{ + QList pages; + + pages.append(ModrinthResourcePackPage::create(this, *m_instance)); + if (APPLICATION->capabilities() & Application::SupportsFlame) + pages.append(FlameResourcePackPage::create(this, *m_instance)); + + return pages; +} + } // namespace ResourceDownload diff --git a/launcher/ui/dialogs/ResourceDownloadDialog.h b/launcher/ui/dialogs/ResourceDownloadDialog.h index 19843532..f534cccc 100644 --- a/launcher/ui/dialogs/ResourceDownloadDialog.h +++ b/launcher/ui/dialogs/ResourceDownloadDialog.h @@ -35,6 +35,7 @@ class QVBoxLayout; class QDialogButtonBox; class ResourceDownloadTask; class ResourceFolderModel; +class ResourcePackFolderModel; namespace ResourceDownload { @@ -108,4 +109,23 @@ class ModDownloadDialog final : public ResourceDownloadDialog { BaseInstance* m_instance; }; +class ResourcePackDownloadDialog final : public ResourceDownloadDialog { + Q_OBJECT + + public: + explicit ResourcePackDownloadDialog(QWidget* parent, + const std::shared_ptr& resource_packs, + BaseInstance* instance); + ~ResourcePackDownloadDialog() override = default; + + //: String that gets appended to the resource pack download dialog title ("Download " + resourcesString()) + [[nodiscard]] QString resourcesString() const override { return tr("resource packs"); } + [[nodiscard]] QString geometrySaveKey() const override { return "RPDownloadGeometry"; } + + QList getPages() override; + + private: + BaseInstance* m_instance; +}; + } // namespace ResourceDownload diff --git a/launcher/ui/pages/instance/ResourcePackPage.cpp b/launcher/ui/pages/instance/ResourcePackPage.cpp new file mode 100644 index 00000000..e705f29e --- /dev/null +++ b/launcher/ui/pages/instance/ResourcePackPage.cpp @@ -0,0 +1,102 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (c) 2022 Jamie Mansfield + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * This file incorporates work covered by the following copyright and + * permission notice: + * + * Copyright 2013-2021 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "ResourcePackPage.h" + +#include "ResourceDownloadTask.h" + +#include "ui/dialogs/CustomMessageBox.h" +#include "ui/dialogs/ProgressDialog.h" +#include "ui/dialogs/ResourceDownloadDialog.h" + +ResourcePackPage::ResourcePackPage(MinecraftInstance* instance, std::shared_ptr model, QWidget* parent) + : ExternalResourcesPage(instance, model, parent) +{ + ui->actionDownloadItem->setText(tr("Download RPs")); + ui->actionDownloadItem->setToolTip(tr("Download RPs from online platforms")); + ui->actionDownloadItem->setEnabled(true); + connect(ui->actionDownloadItem, &QAction::triggered, this, &ResourcePackPage::downloadRPs); + ui->actionsToolbar->insertActionBefore(ui->actionAddItem, ui->actionDownloadItem); + + ui->actionViewConfigs->setVisible(false); +} + +bool ResourcePackPage::onSelectionChanged(const QModelIndex& current, const QModelIndex& previous) +{ + auto sourceCurrent = m_filterModel->mapToSource(current); + int row = sourceCurrent.row(); + auto& rp = static_cast(m_model->at(row)); + ui->frame->updateWithResourcePack(rp); + + return true; +} + +void ResourcePackPage::downloadRPs() +{ + if (!m_controlsEnabled) + return; + if (m_instance->typeName() != "Minecraft") + return; // this is a null instance or a legacy instance + + ResourceDownload::ResourcePackDownloadDialog mdownload(this, std::static_pointer_cast(m_model), m_instance); + if (mdownload.exec()) { + auto tasks = new ConcurrentTask(this); + connect(tasks, &Task::failed, [this, tasks](QString reason) { + CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); + tasks->deleteLater(); + }); + connect(tasks, &Task::aborted, [this, tasks]() { + CustomMessageBox::selectable(this, tr("Aborted"), tr("Download stopped by user."), QMessageBox::Information)->show(); + tasks->deleteLater(); + }); + connect(tasks, &Task::succeeded, [this, tasks]() { + QStringList warnings = tasks->warnings(); + if (warnings.count()) + CustomMessageBox::selectable(this, tr("Warnings"), warnings.join('\n'), QMessageBox::Warning)->show(); + + tasks->deleteLater(); + }); + + for (auto& task : mdownload.getTasks()) { + tasks->addTask(task); + } + + ProgressDialog loadDialog(this); + loadDialog.setSkipButton(true, tr("Abort")); + loadDialog.execWithTask(tasks); + + m_model->update(); + } +} diff --git a/launcher/ui/pages/instance/ResourcePackPage.h b/launcher/ui/pages/instance/ResourcePackPage.h index db8af0c5..fc05158c 100644 --- a/launcher/ui/pages/instance/ResourcePackPage.h +++ b/launcher/ui/pages/instance/ResourcePackPage.h @@ -44,12 +44,7 @@ class ResourcePackPage : public ExternalResourcesPage { Q_OBJECT public: - explicit ResourcePackPage(MinecraftInstance *instance, std::shared_ptr model, QWidget *parent = 0) - : ExternalResourcesPage(instance, model, parent) - { - ui->actionViewConfigs->setVisible(false); - } - virtual ~ResourcePackPage() {} + explicit ResourcePackPage(MinecraftInstance *instance, std::shared_ptr model, QWidget *parent = 0); QString displayName() const override { return tr("Resource packs"); } QIcon icon() const override { return APPLICATION->getThemedIcon("resourcepacks"); } @@ -63,14 +58,7 @@ public: } public slots: - bool onSelectionChanged(const QModelIndex& current, const QModelIndex& previous) override - { - auto sourceCurrent = m_filterModel->mapToSource(current); - int row = sourceCurrent.row(); - auto& rp = static_cast(m_model->at(row)); - ui->frame->updateWithResourcePack(rp); - - return true; - } + bool onSelectionChanged(const QModelIndex& current, const QModelIndex& previous) override; + void downloadRPs(); }; diff --git a/launcher/ui/pages/modplatform/ResourcePackModel.cpp b/launcher/ui/pages/modplatform/ResourcePackModel.cpp new file mode 100644 index 00000000..fd1afa0d --- /dev/null +++ b/launcher/ui/pages/modplatform/ResourcePackModel.cpp @@ -0,0 +1,42 @@ +#include "ResourcePackModel.h" + +#include + +namespace ResourceDownload { + +ResourcePackResourceModel::ResourcePackResourceModel(BaseInstance const& base_inst, ResourceAPI* api) + : ResourceModel(api), m_base_instance(base_inst){}; + +/******** Make data requests ********/ + +ResourceAPI::SearchArgs ResourcePackResourceModel::createSearchArguments() +{ + auto sort = getCurrentSortingMethodByIndex(); + return { ModPlatform::ResourceType::RESOURCE_PACK, m_next_search_offset, m_search_term, sort }; +} + +ResourceAPI::VersionSearchArgs ResourcePackResourceModel::createVersionsArguments(QModelIndex& entry) +{ + auto& pack = m_packs[entry.row()]; + return { pack }; +} + +ResourceAPI::ProjectInfoArgs ResourcePackResourceModel::createInfoArguments(QModelIndex& entry) +{ + auto& pack = m_packs[entry.row()]; + return { pack }; +} + +void ResourcePackResourceModel::searchWithTerm(const QString& term, unsigned int sort) +{ + if (m_search_term == term && m_search_term.isNull() == term.isNull() && m_current_sort_index == sort) { + return; + } + + setSearchTerm(term); + m_current_sort_index = sort; + + refresh(); +} + +} // namespace ResourceDownload diff --git a/launcher/ui/pages/modplatform/ResourcePackModel.h b/launcher/ui/pages/modplatform/ResourcePackModel.h new file mode 100644 index 00000000..63aa533c --- /dev/null +++ b/launcher/ui/pages/modplatform/ResourcePackModel.h @@ -0,0 +1,39 @@ +#pragma once + +#include + +#include "BaseInstance.h" + +#include "modplatform/ModIndex.h" + +#include "ui/pages/modplatform/ResourceModel.h" + +class Version; + +namespace ResourceDownload { + +class ResourcePackResourceModel : public ResourceModel { + Q_OBJECT + + public: + ResourcePackResourceModel(BaseInstance const&, ResourceAPI*); + + /* Ask the API for more information */ + void searchWithTerm(const QString& term, unsigned int sort); + + void loadIndexedPack(ModPlatform::IndexedPack&, QJsonObject&) override = 0; + void loadExtraPackInfo(ModPlatform::IndexedPack&, QJsonObject&) override = 0; + void loadIndexedPackVersions(ModPlatform::IndexedPack&, QJsonArray&) override = 0; + + public slots: + ResourceAPI::SearchArgs createSearchArguments() override; + ResourceAPI::VersionSearchArgs createVersionsArguments(QModelIndex&) override; + ResourceAPI::ProjectInfoArgs createInfoArguments(QModelIndex&) override; + + protected: + const BaseInstance& m_base_instance; + + auto documentToArray(QJsonDocument& obj) const -> QJsonArray override = 0; +}; + +} // namespace ResourceDownload diff --git a/launcher/ui/pages/modplatform/ResourcePackPage.cpp b/launcher/ui/pages/modplatform/ResourcePackPage.cpp new file mode 100644 index 00000000..8d663aa8 --- /dev/null +++ b/launcher/ui/pages/modplatform/ResourcePackPage.cpp @@ -0,0 +1,42 @@ +#include "ResourcePackPage.h" +#include "ui_ResourcePage.h" + +#include "ResourcePackModel.h" + +#include "ui/dialogs/ResourceDownloadDialog.h" + +#include + +namespace ResourceDownload { + +ResourcePackResourcePage::ResourcePackResourcePage(ResourcePackDownloadDialog* dialog, BaseInstance& instance) + : ResourcePage(dialog, instance) +{ + connect(m_ui->searchButton, &QPushButton::clicked, this, &ResourcePackResourcePage::triggerSearch); + connect(m_ui->packView, &QListView::doubleClicked, this, &ResourcePackResourcePage::onResourceSelected); +} + +/******** Callbacks to events in the UI (set up in the derived classes) ********/ + +void ResourcePackResourcePage::triggerSearch() +{ + m_ui->packView->clearSelection(); + m_ui->packDescription->clear(); + m_ui->versionSelectionBox->clear(); + + updateSelectionButton(); + + static_cast(m_model)->searchWithTerm(getSearchTerm(), m_ui->sortByBox->currentData().toUInt()); + m_fetch_progress.watch(m_model->activeSearchJob().get()); +} + +QMap ResourcePackResourcePage::urlHandlers() const +{ + QMap map; + map.insert(QRegularExpression::anchoredPattern("(?:www\\.)?modrinth\\.com\\/resourcepack\\/([^\\/]+)\\/?"), "modrinth"); + map.insert(QRegularExpression::anchoredPattern("(?:www\\.)?curseforge\\.com\\/minecraft\\/texture-packs\\/([^\\/]+)\\/?"), "curseforge"); + map.insert(QRegularExpression::anchoredPattern("minecraft\\.curseforge\\.com\\/projects\\/([^\\/]+)\\/?"), "curseforge"); + return map; +} + +} // namespace ResourceDownload diff --git a/launcher/ui/pages/modplatform/ResourcePackPage.h b/launcher/ui/pages/modplatform/ResourcePackPage.h new file mode 100644 index 00000000..2ecff390 --- /dev/null +++ b/launcher/ui/pages/modplatform/ResourcePackPage.h @@ -0,0 +1,48 @@ +#pragma once + +#include "ui/pages/modplatform/ResourcePage.h" +#include "ui/pages/modplatform/ResourcePackModel.h" + +namespace Ui { +class ResourcePage; +} + +namespace ResourceDownload { + +class ResourcePackDownloadDialog; + +class ResourcePackResourcePage : public ResourcePage { + Q_OBJECT + + public: + template + static T* create(ResourcePackDownloadDialog* dialog, BaseInstance& instance) + { + auto page = new T(dialog, instance); + auto model = static_cast(page->getModel()); + + connect(model, &ResourceModel::versionListUpdated, page, &ResourcePage::updateVersionList); + connect(model, &ResourceModel::projectInfoUpdated, page, &ResourcePage::updateUi); + + return page; + } + + ~ResourcePackResourcePage() override = default; + + //: The plural version of 'resource pack' + [[nodiscard]] inline QString resourcesString() const override { return tr("resource packs"); } + //: The singular version of 'resource packs' + [[nodiscard]] inline QString resourceString() const override { return tr("resource pack"); } + + [[nodiscard]] bool supportsFiltering() const override { return false; }; + + [[nodiscard]] QMap urlHandlers() const override; + + protected: + ResourcePackResourcePage(ResourcePackDownloadDialog* dialog, BaseInstance& instance); + + protected slots: + void triggerSearch() override; +}; + +} // namespace ResourceDownload diff --git a/launcher/ui/pages/modplatform/flame/FlameResourceModels.cpp b/launcher/ui/pages/modplatform/flame/FlameResourceModels.cpp index de1f2122..95d915fc 100644 --- a/launcher/ui/pages/modplatform/flame/FlameResourceModels.cpp +++ b/launcher/ui/pages/modplatform/flame/FlameResourceModels.cpp @@ -34,4 +34,28 @@ auto FlameModModel::documentToArray(QJsonDocument& obj) const -> QJsonArray return Json::ensureArray(obj.object(), "data"); } +FlameResourcePackModel::FlameResourcePackModel(const BaseInstance& base) : ResourcePackResourceModel(base, new FlameAPI) {} + +void FlameResourcePackModel::loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj) +{ + FlameMod::loadIndexedPack(m, obj); +} + +// We already deal with the URLs when initializing the pack, due to the API response's structure +void FlameResourcePackModel::loadExtraPackInfo(ModPlatform::IndexedPack& m, QJsonObject& obj) +{ + FlameMod::loadBody(m, obj); +} + +void FlameResourcePackModel::loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr) +{ + FlameMod::loadIndexedPackVersions(m, arr, APPLICATION->network(), &m_base_instance); +} + +auto FlameResourcePackModel::documentToArray(QJsonDocument& obj) const -> QJsonArray +{ + return Json::ensureArray(obj.object(), "data"); +} + + } // namespace ResourceDownload diff --git a/launcher/ui/pages/modplatform/flame/FlameResourceModels.h b/launcher/ui/pages/modplatform/flame/FlameResourceModels.h index 625a2a7d..be214716 100644 --- a/launcher/ui/pages/modplatform/flame/FlameResourceModels.h +++ b/launcher/ui/pages/modplatform/flame/FlameResourceModels.h @@ -5,6 +5,7 @@ #pragma once #include "ui/pages/modplatform/ModModel.h" +#include "ui/pages/modplatform/ResourcePackModel.h" #include "ui/pages/modplatform/flame/FlameResourcePages.h" namespace ResourceDownload { @@ -27,4 +28,22 @@ class FlameModModel : public ModModel { auto documentToArray(QJsonDocument& obj) const -> QJsonArray override; }; +class FlameResourcePackModel : public ResourcePackResourceModel { + Q_OBJECT + + public: + FlameResourcePackModel(const BaseInstance&); + ~FlameResourcePackModel() override = default; + + private: + [[nodiscard]] QString debugName() const override { return Flame::debugName() + " (Model)"; } + [[nodiscard]] QString metaEntryBase() const override { return Flame::metaEntryBase(); } + + void loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj) override; + void loadExtraPackInfo(ModPlatform::IndexedPack& m, QJsonObject& obj) override; + void loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr) override; + + auto documentToArray(QJsonDocument& obj) const -> QJsonArray override; +}; + } // namespace ResourceDownload diff --git a/launcher/ui/pages/modplatform/flame/FlameResourcePages.cpp b/launcher/ui/pages/modplatform/flame/FlameResourcePages.cpp index 485431a7..15b04ab4 100644 --- a/launcher/ui/pages/modplatform/flame/FlameResourcePages.cpp +++ b/launcher/ui/pages/modplatform/flame/FlameResourcePages.cpp @@ -44,6 +44,11 @@ namespace ResourceDownload { +static bool isOptedOut(ModPlatform::IndexedVersion const& ver) +{ + return ver.downloadUrl.isEmpty(); +} + FlameModPage::FlameModPage(ModDownloadDialog* dialog, BaseInstance& instance) : ModPage(dialog, instance) { @@ -70,14 +75,9 @@ auto FlameModPage::validateVersion(ModPlatform::IndexedVersion& ver, QString min bool FlameModPage::optedOut(ModPlatform::IndexedVersion& ver) const { - return ver.downloadUrl.isEmpty(); + return isOptedOut(ver); } -// I don't know why, but doing this on the parent class makes it so that -// other mod providers start loading before being selected, at least with -// my Qt, so we need to implement this in every derived class... -auto FlameModPage::shouldDisplay() const -> bool { return true; } - void FlameModPage::openUrl(const QUrl& url) { if (url.scheme().isEmpty()) { @@ -94,4 +94,49 @@ void FlameModPage::openUrl(const QUrl& url) ModPage::openUrl(url); } +FlameResourcePackPage::FlameResourcePackPage(ResourcePackDownloadDialog* dialog, BaseInstance& instance) + : ResourcePackResourcePage(dialog, instance) +{ + m_model = new FlameResourcePackModel(instance); + m_ui->packView->setModel(m_model); + + addSortings(); + + // sometimes Qt just ignores virtual slots and doesn't work as intended it seems, + // so it's best not to connect them in the parent's contructor... + connect(m_ui->sortByBox, SIGNAL(currentIndexChanged(int)), this, SLOT(triggerSearch())); + connect(m_ui->packView->selectionModel(), &QItemSelectionModel::currentChanged, this, &FlameResourcePackPage::onSelectionChanged); + connect(m_ui->versionSelectionBox, &QComboBox::currentTextChanged, this, &FlameResourcePackPage::onVersionSelectionChanged); + connect(m_ui->resourceSelectionButton, &QPushButton::clicked, this, &FlameResourcePackPage::onResourceSelected); + + m_ui->packDescription->setMetaEntry(metaEntryBase()); +} + +bool FlameResourcePackPage::optedOut(ModPlatform::IndexedVersion& ver) const +{ + return isOptedOut(ver); +} + +void FlameResourcePackPage::openUrl(const QUrl& url) +{ + if (url.scheme().isEmpty()) { + QString query = url.query(QUrl::FullyDecoded); + + if (query.startsWith("remoteUrl=")) { + // attempt to resolve url from warning page + query.remove(0, 10); + ResourcePackResourcePage::openUrl({QUrl::fromPercentEncoding(query.toUtf8())}); // double decoding is necessary + return; + } + } + + ResourcePackResourcePage::openUrl(url); +} + +// I don't know why, but doing this on the parent class makes it so that +// other mod providers start loading before being selected, at least with +// my Qt, so we need to implement this in every derived class... +auto FlameModPage::shouldDisplay() const -> bool { return true; } +auto FlameResourcePackPage::shouldDisplay() const -> bool { return true; } + } // namespace ResourceDownload diff --git a/launcher/ui/pages/modplatform/flame/FlameResourcePages.h b/launcher/ui/pages/modplatform/flame/FlameResourcePages.h index b21a53ad..03cf7795 100644 --- a/launcher/ui/pages/modplatform/flame/FlameResourcePages.h +++ b/launcher/ui/pages/modplatform/flame/FlameResourcePages.h @@ -43,6 +43,7 @@ #include "modplatform/ResourceAPI.h" #include "ui/pages/modplatform/ModPage.h" +#include "ui/pages/modplatform/ResourcePackPage.h" namespace ResourceDownload { @@ -82,4 +83,31 @@ class FlameModPage : public ModPage { void openUrl(const QUrl& url) override; }; +class FlameResourcePackPage : public ResourcePackResourcePage { + Q_OBJECT + + public: + static FlameResourcePackPage* create(ResourcePackDownloadDialog* dialog, BaseInstance& instance) + { + return ResourcePackResourcePage::create(dialog, instance); + } + + FlameResourcePackPage(ResourcePackDownloadDialog* dialog, BaseInstance& instance); + ~FlameResourcePackPage() override = default; + + [[nodiscard]] bool shouldDisplay() const override; + + [[nodiscard]] inline auto displayName() const -> QString override { return Flame::displayName(); } + [[nodiscard]] inline auto icon() const -> QIcon override { return Flame::icon(); } + [[nodiscard]] inline auto id() const -> QString override { return Flame::id(); } + [[nodiscard]] inline auto debugName() const -> QString override { return Flame::debugName(); } + [[nodiscard]] inline auto metaEntryBase() const -> QString override { return Flame::metaEntryBase(); } + + [[nodiscard]] inline auto helpPage() const -> QString override { return ""; } + + bool optedOut(ModPlatform::IndexedVersion& ver) const override; + + void openUrl(const QUrl& url) override; +}; + } // namespace ResourceDownload diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.cpp index 73d55133..b36339d7 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.cpp +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.cpp @@ -47,4 +47,26 @@ auto ModrinthModModel::documentToArray(QJsonDocument& obj) const -> QJsonArray return obj.object().value("hits").toArray(); } +ModrinthResourcePackModel::ModrinthResourcePackModel(const BaseInstance& base) : ResourcePackResourceModel(base, new ModrinthAPI){} + +void ModrinthResourcePackModel::loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj) +{ + ::Modrinth::loadIndexedPack(m, obj); +} + +void ModrinthResourcePackModel::loadExtraPackInfo(ModPlatform::IndexedPack& m, QJsonObject& obj) +{ + ::Modrinth::loadExtraPackData(m, obj); +} + +void ModrinthResourcePackModel::loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr) +{ + ::Modrinth::loadIndexedPackVersions(m, arr, APPLICATION->network(), &m_base_instance); +} + +auto ModrinthResourcePackModel::documentToArray(QJsonDocument& obj) const -> QJsonArray +{ + return obj.object().value("hits").toArray(); +} + } // namespace ResourceDownload diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.h b/launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.h index 56cab146..0a91ef23 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.h +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.h @@ -21,12 +21,11 @@ #pragma once #include "ui/pages/modplatform/ModModel.h" +#include "ui/pages/modplatform/ResourcePackModel.h" #include "ui/pages/modplatform/modrinth/ModrinthResourcePages.h" namespace ResourceDownload { -class ModrinthModPage; - class ModrinthModModel : public ModModel { Q_OBJECT @@ -45,4 +44,22 @@ class ModrinthModModel : public ModModel { auto documentToArray(QJsonDocument& obj) const -> QJsonArray override; }; +class ModrinthResourcePackModel : public ResourcePackResourceModel { + Q_OBJECT + + public: + ModrinthResourcePackModel(const BaseInstance&); + ~ModrinthResourcePackModel() override = default; + + private: + [[nodiscard]] QString debugName() const override { return Modrinth::debugName() + " (Model)"; } + [[nodiscard]] QString metaEntryBase() const override { return Modrinth::metaEntryBase(); } + + void loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj) override; + void loadExtraPackInfo(ModPlatform::IndexedPack& m, QJsonObject& obj) override; + void loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr) override; + + auto documentToArray(QJsonDocument& obj) const -> QJsonArray override; +}; + } // namespace ResourceDownload diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.cpp index b82f800e..d32b9091 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.cpp +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.cpp @@ -82,9 +82,28 @@ auto ModrinthModPage::validateVersion(ModPlatform::IndexedVersion& ver, QString return ver.mcVersion.contains(mineVer) && loaderCompatible; } +ModrinthResourcePackPage::ModrinthResourcePackPage(ResourcePackDownloadDialog* dialog, BaseInstance& instance) + : ResourcePackResourcePage(dialog, instance) +{ + m_model = new ModrinthResourcePackModel(instance); + m_ui->packView->setModel(m_model); + + addSortings(); + + // sometimes Qt just ignores virtual slots and doesn't work as intended it seems, + // so it's best not to connect them in the parent's constructor... + connect(m_ui->sortByBox, SIGNAL(currentIndexChanged(int)), this, SLOT(triggerSearch())); + connect(m_ui->packView->selectionModel(), &QItemSelectionModel::currentChanged, this, &ModrinthResourcePackPage::onSelectionChanged); + connect(m_ui->versionSelectionBox, &QComboBox::currentTextChanged, this, &ModrinthResourcePackPage::onVersionSelectionChanged); + connect(m_ui->resourceSelectionButton, &QPushButton::clicked, this, &ModrinthResourcePackPage::onResourceSelected); + + m_ui->packDescription->setMetaEntry(metaEntryBase()); +} + // I don't know why, but doing this on the parent class makes it so that // other mod providers start loading before being selected, at least with // my Qt, so we need to implement this in every derived class... auto ModrinthModPage::shouldDisplay() const -> bool { return true; } +auto ModrinthResourcePackPage::shouldDisplay() const -> bool { return true; } } // namespace ResourceDownload diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.h b/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.h index be38eff1..be3740a1 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.h +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.h @@ -42,6 +42,7 @@ #include "modplatform/ResourceAPI.h" #include "ui/pages/modplatform/ModPage.h" +#include "ui/pages/modplatform/ResourcePackPage.h" namespace ResourceDownload { @@ -78,4 +79,27 @@ class ModrinthModPage : public ModPage { auto validateVersion(ModPlatform::IndexedVersion& ver, QString mineVer, std::optional loaders = {}) const -> bool override; }; +class ModrinthResourcePackPage : public ResourcePackResourcePage { + Q_OBJECT + + public: + static ModrinthResourcePackPage* create(ResourcePackDownloadDialog* dialog, BaseInstance& instance) + { + return ResourcePackResourcePage::create(dialog, instance); + } + + ModrinthResourcePackPage(ResourcePackDownloadDialog* dialog, BaseInstance& instance); + ~ModrinthResourcePackPage() override = default; + + [[nodiscard]] bool shouldDisplay() const override; + + [[nodiscard]] inline auto displayName() const -> QString override { return Modrinth::displayName(); } + [[nodiscard]] inline auto icon() const -> QIcon override { return Modrinth::icon(); } + [[nodiscard]] inline auto id() const -> QString override { return Modrinth::id(); } + [[nodiscard]] inline auto debugName() const -> QString override { return Modrinth::debugName(); } + [[nodiscard]] inline auto metaEntryBase() const -> QString override { return Modrinth::metaEntryBase(); } + + [[nodiscard]] inline auto helpPage() const -> QString override { return ""; } +}; + } // namespace ResourceDownload -- cgit From b724607e31d102c50cb42225b4a31f2932b2eb61 Mon Sep 17 00:00:00 2001 From: flow Date: Fri, 30 Dec 2022 14:06:07 -0300 Subject: feat(RD): add shader pack downloader Signed-off-by: flow --- launcher/Application.cpp | 1 + launcher/CMakeLists.txt | 4 + launcher/modplatform/ModIndex.h | 2 +- launcher/modplatform/modrinth/ModrinthAPI.h | 2 + launcher/ui/MainWindow.cpp | 1 + launcher/ui/dialogs/ResourceDownloadDialog.cpp | 25 ++++++ launcher/ui/dialogs/ResourceDownloadDialog.h | 20 +++++ launcher/ui/pages/instance/ShaderPackPage.cpp | 96 ++++++++++++++++++++++ launcher/ui/pages/instance/ShaderPackPage.h | 19 ++--- launcher/ui/pages/modplatform/ShaderPackModel.cpp | 42 ++++++++++ launcher/ui/pages/modplatform/ShaderPackModel.h | 39 +++++++++ launcher/ui/pages/modplatform/ShaderPackPage.cpp | 50 +++++++++++ launcher/ui/pages/modplatform/ShaderPackPage.h | 50 +++++++++++ .../pages/modplatform/flame/FlameResourcePages.h | 1 + .../modrinth/ModrinthResourceModels.cpp | 22 +++++ .../modplatform/modrinth/ModrinthResourceModels.h | 18 ++++ .../modplatform/modrinth/ModrinthResourcePages.cpp | 19 +++++ .../modplatform/modrinth/ModrinthResourcePages.h | 24 ++++++ 18 files changed, 421 insertions(+), 14 deletions(-) create mode 100644 launcher/ui/pages/instance/ShaderPackPage.cpp create mode 100644 launcher/ui/pages/modplatform/ShaderPackModel.cpp create mode 100644 launcher/ui/pages/modplatform/ShaderPackModel.h create mode 100644 launcher/ui/pages/modplatform/ShaderPackPage.cpp create mode 100644 launcher/ui/pages/modplatform/ShaderPackPage.h (limited to 'launcher/ui/dialogs') diff --git a/launcher/Application.cpp b/launcher/Application.cpp index c1ce4659..0f7cce66 100644 --- a/launcher/Application.cpp +++ b/launcher/Application.cpp @@ -611,6 +611,7 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv) m_settings->registerSetting("ModDownloadGeometry", ""); m_settings->registerSetting("RPDownloadGeometry", ""); + m_settings->registerSetting("ShaderDownloadGeometry", ""); // HACK: This code feels so stupid is there a less stupid way of doing this? { diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index 7ae148e0..795aeb9f 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -723,6 +723,7 @@ SET(LAUNCHER_SOURCES ui/pages/instance/ResourcePackPage.h ui/pages/instance/ResourcePackPage.cpp ui/pages/instance/ShaderPackPage.h + ui/pages/instance/ShaderPackPage.cpp ui/pages/instance/ModFolderPage.cpp ui/pages/instance/ModFolderPage.h ui/pages/instance/NotesPage.cpp @@ -777,6 +778,9 @@ SET(LAUNCHER_SOURCES ui/pages/modplatform/ResourcePackPage.cpp ui/pages/modplatform/ResourcePackModel.cpp + ui/pages/modplatform/ShaderPackPage.cpp + ui/pages/modplatform/ShaderPackModel.cpp + ui/pages/modplatform/atlauncher/AtlFilterModel.cpp ui/pages/modplatform/atlauncher/AtlFilterModel.h ui/pages/modplatform/atlauncher/AtlListModel.cpp diff --git a/launcher/modplatform/ModIndex.h b/launcher/modplatform/ModIndex.h index b1f8050d..40f1efc4 100644 --- a/launcher/modplatform/ModIndex.h +++ b/launcher/modplatform/ModIndex.h @@ -30,7 +30,7 @@ namespace ModPlatform { enum class ResourceProvider { MODRINTH, FLAME }; -enum class ResourceType { MOD, RESOURCE_PACK }; +enum class ResourceType { MOD, RESOURCE_PACK, SHADER_PACK }; class ProviderCapabilities { public: diff --git a/launcher/modplatform/modrinth/ModrinthAPI.h b/launcher/modplatform/modrinth/ModrinthAPI.h index 0b2d149e..b91ac5c1 100644 --- a/launcher/modplatform/modrinth/ModrinthAPI.h +++ b/launcher/modplatform/modrinth/ModrinthAPI.h @@ -70,6 +70,8 @@ class ModrinthAPI : public NetworkResourceAPI { return "mod"; case ModPlatform::ResourceType::RESOURCE_PACK: return "resourcepack"; + case ModPlatform::ResourceType::SHADER_PACK: + return "shader"; default: qWarning() << "Invalid resource type for Modrinth API!"; break; diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp index 6d21f5ed..8490b292 100644 --- a/launcher/ui/MainWindow.cpp +++ b/launcher/ui/MainWindow.cpp @@ -113,6 +113,7 @@ #include "minecraft/mod/tasks/LocalResourceParse.h" #include "minecraft/mod/ModFolderModel.h" +#include "minecraft/mod/ShaderPackFolderModel.h" #include "minecraft/WorldList.h" #include "KonamiCode.h" diff --git a/launcher/ui/dialogs/ResourceDownloadDialog.cpp b/launcher/ui/dialogs/ResourceDownloadDialog.cpp index edcd642e..98a2eb88 100644 --- a/launcher/ui/dialogs/ResourceDownloadDialog.cpp +++ b/launcher/ui/dialogs/ResourceDownloadDialog.cpp @@ -26,6 +26,7 @@ #include "minecraft/mod/ModFolderModel.h" #include "minecraft/mod/ResourcePackFolderModel.h" +#include "minecraft/mod/ShaderPackFolderModel.h" #include "ui/dialogs/ReviewMessageBox.h" @@ -256,4 +257,28 @@ QList ResourcePackDownloadDialog::getPages() return pages; } + +ShaderPackDownloadDialog::ShaderPackDownloadDialog(QWidget* parent, + const std::shared_ptr& shaders, + BaseInstance* instance) + : ResourceDownloadDialog(parent, shaders), m_instance(instance) +{ + setWindowTitle(dialogTitle()); + + initializeContainer(); + connectButtons(); + + if (!geometrySaveKey().isEmpty()) + restoreGeometry(QByteArray::fromBase64(APPLICATION->settings()->get(geometrySaveKey()).toByteArray())); +} + +QList ShaderPackDownloadDialog::getPages() +{ + QList pages; + + pages.append(ModrinthShaderPackPage::create(this, *m_instance)); + + return pages; +} + } // namespace ResourceDownload diff --git a/launcher/ui/dialogs/ResourceDownloadDialog.h b/launcher/ui/dialogs/ResourceDownloadDialog.h index f534cccc..203bac66 100644 --- a/launcher/ui/dialogs/ResourceDownloadDialog.h +++ b/launcher/ui/dialogs/ResourceDownloadDialog.h @@ -36,6 +36,7 @@ class QDialogButtonBox; class ResourceDownloadTask; class ResourceFolderModel; class ResourcePackFolderModel; +class ShaderPackFolderModel; namespace ResourceDownload { @@ -128,4 +129,23 @@ class ResourcePackDownloadDialog final : public ResourceDownloadDialog { BaseInstance* m_instance; }; +class ShaderPackDownloadDialog final : public ResourceDownloadDialog { + Q_OBJECT + + public: + explicit ShaderPackDownloadDialog(QWidget* parent, + const std::shared_ptr& shader_packs, + BaseInstance* instance); + ~ShaderPackDownloadDialog() override = default; + + //: String that gets appended to the shader pack download dialog title ("Download " + resourcesString()) + [[nodiscard]] QString resourcesString() const override { return tr("shader packs"); } + [[nodiscard]] QString geometrySaveKey() const override { return "ShaderDownloadGeometry"; } + + QList getPages() override; + + private: + BaseInstance* m_instance; +}; + } // namespace ResourceDownload diff --git a/launcher/ui/pages/instance/ShaderPackPage.cpp b/launcher/ui/pages/instance/ShaderPackPage.cpp new file mode 100644 index 00000000..2dde4dc7 --- /dev/null +++ b/launcher/ui/pages/instance/ShaderPackPage.cpp @@ -0,0 +1,96 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (c) 2022 Jamie Mansfield + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * This file incorporates work covered by the following copyright and + * permission notice: + * + * Copyright 2013-2021 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "ShaderPackPage.h" +#include "ui_ExternalResourcesPage.h" + +#include "ResourceDownloadTask.h" + +#include "minecraft/mod/ShaderPackFolderModel.h" + +#include "ui/dialogs/CustomMessageBox.h" +#include "ui/dialogs/ProgressDialog.h" +#include "ui/dialogs/ResourceDownloadDialog.h" + + +ShaderPackPage::ShaderPackPage(MinecraftInstance* instance, std::shared_ptr model, QWidget* parent) + : ExternalResourcesPage(instance, model, parent) +{ + ui->actionDownloadItem->setText(tr("Download shaders")); + ui->actionDownloadItem->setToolTip(tr("Download shaders from online platforms")); + ui->actionDownloadItem->setEnabled(true); + connect(ui->actionDownloadItem, &QAction::triggered, this, &ShaderPackPage::downloadShaders); + ui->actionsToolbar->insertActionBefore(ui->actionAddItem, ui->actionDownloadItem); + + ui->actionViewConfigs->setVisible(false); +} + +void ShaderPackPage::downloadShaders() +{ + if (!m_controlsEnabled) + return; + if (m_instance->typeName() != "Minecraft") + return; // this is a null instance or a legacy instance + + ResourceDownload::ShaderPackDownloadDialog mdownload(this, std::static_pointer_cast(m_model), m_instance); + if (mdownload.exec()) { + auto tasks = new ConcurrentTask(this); + connect(tasks, &Task::failed, [this, tasks](QString reason) { + CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); + tasks->deleteLater(); + }); + connect(tasks, &Task::aborted, [this, tasks]() { + CustomMessageBox::selectable(this, tr("Aborted"), tr("Download stopped by user."), QMessageBox::Information)->show(); + tasks->deleteLater(); + }); + connect(tasks, &Task::succeeded, [this, tasks]() { + QStringList warnings = tasks->warnings(); + if (warnings.count()) + CustomMessageBox::selectable(this, tr("Warnings"), warnings.join('\n'), QMessageBox::Warning)->show(); + + tasks->deleteLater(); + }); + + for (auto& task : mdownload.getTasks()) { + tasks->addTask(task); + } + + ProgressDialog loadDialog(this); + loadDialog.setSkipButton(true, tr("Abort")); + loadDialog.execWithTask(tasks); + + m_model->update(); + } +} diff --git a/launcher/ui/pages/instance/ShaderPackPage.h b/launcher/ui/pages/instance/ShaderPackPage.h index 7f7ff8c1..09895949 100644 --- a/launcher/ui/pages/instance/ShaderPackPage.h +++ b/launcher/ui/pages/instance/ShaderPackPage.h @@ -36,28 +36,21 @@ #pragma once #include "ExternalResourcesPage.h" -#include "ui_ExternalResourcesPage.h" - -#include "minecraft/mod/ShaderPackFolderModel.h" class ShaderPackPage : public ExternalResourcesPage { Q_OBJECT public: - explicit ShaderPackPage(MinecraftInstance *instance, std::shared_ptr model, QWidget *parent = 0) - : ExternalResourcesPage(instance, model, parent) - { - ui->actionViewConfigs->setVisible(false); - } - virtual ~ShaderPackPage() {} + explicit ShaderPackPage(MinecraftInstance *instance, std::shared_ptr model, QWidget *parent = nullptr); + ~ShaderPackPage() override = default; QString displayName() const override { return tr("Shader packs"); } QIcon icon() const override { return APPLICATION->getThemedIcon("shaderpacks"); } QString id() const override { return "shaderpacks"; } QString helpPage() const override { return "Resource-packs"; } - virtual bool shouldDisplay() const override - { - return true; - } + bool shouldDisplay() const override { return true; } + + public slots: + void downloadShaders(); }; diff --git a/launcher/ui/pages/modplatform/ShaderPackModel.cpp b/launcher/ui/pages/modplatform/ShaderPackModel.cpp new file mode 100644 index 00000000..36993288 --- /dev/null +++ b/launcher/ui/pages/modplatform/ShaderPackModel.cpp @@ -0,0 +1,42 @@ +#include "ShaderPackModel.h" + +#include + +namespace ResourceDownload { + +ShaderPackResourceModel::ShaderPackResourceModel(BaseInstance const& base_inst, ResourceAPI* api) + : ResourceModel(api), m_base_instance(base_inst){}; + +/******** Make data requests ********/ + +ResourceAPI::SearchArgs ShaderPackResourceModel::createSearchArguments() +{ + auto sort = getCurrentSortingMethodByIndex(); + return { ModPlatform::ResourceType::SHADER_PACK, m_next_search_offset, m_search_term, sort }; +} + +ResourceAPI::VersionSearchArgs ShaderPackResourceModel::createVersionsArguments(QModelIndex& entry) +{ + auto& pack = m_packs[entry.row()]; + return { pack }; +} + +ResourceAPI::ProjectInfoArgs ShaderPackResourceModel::createInfoArguments(QModelIndex& entry) +{ + auto& pack = m_packs[entry.row()]; + return { pack }; +} + +void ShaderPackResourceModel::searchWithTerm(const QString& term, unsigned int sort) +{ + if (m_search_term == term && m_search_term.isNull() == term.isNull() && m_current_sort_index == sort) { + return; + } + + setSearchTerm(term); + m_current_sort_index = sort; + + refresh(); +} + +} // namespace ResourceDownload diff --git a/launcher/ui/pages/modplatform/ShaderPackModel.h b/launcher/ui/pages/modplatform/ShaderPackModel.h new file mode 100644 index 00000000..c84df15a --- /dev/null +++ b/launcher/ui/pages/modplatform/ShaderPackModel.h @@ -0,0 +1,39 @@ +#pragma once + +#include + +#include "BaseInstance.h" + +#include "modplatform/ModIndex.h" + +#include "ui/pages/modplatform/ResourceModel.h" + +class Version; + +namespace ResourceDownload { + +class ShaderPackResourceModel : public ResourceModel { + Q_OBJECT + + public: + ShaderPackResourceModel(BaseInstance const&, ResourceAPI*); + + /* Ask the API for more information */ + void searchWithTerm(const QString& term, unsigned int sort); + + void loadIndexedPack(ModPlatform::IndexedPack&, QJsonObject&) override = 0; + void loadExtraPackInfo(ModPlatform::IndexedPack&, QJsonObject&) override = 0; + void loadIndexedPackVersions(ModPlatform::IndexedPack&, QJsonArray&) override = 0; + + public slots: + ResourceAPI::SearchArgs createSearchArguments() override; + ResourceAPI::VersionSearchArgs createVersionsArguments(QModelIndex&) override; + ResourceAPI::ProjectInfoArgs createInfoArguments(QModelIndex&) override; + + protected: + const BaseInstance& m_base_instance; + + auto documentToArray(QJsonDocument& obj) const -> QJsonArray override = 0; +}; + +} // namespace ResourceDownload diff --git a/launcher/ui/pages/modplatform/ShaderPackPage.cpp b/launcher/ui/pages/modplatform/ShaderPackPage.cpp new file mode 100644 index 00000000..364d0a54 --- /dev/null +++ b/launcher/ui/pages/modplatform/ShaderPackPage.cpp @@ -0,0 +1,50 @@ +#include "ShaderPackPage.h" +#include "ui_ResourcePage.h" + +#include "ShaderPackModel.h" + +#include "ui/dialogs/ResourceDownloadDialog.h" + +#include + +namespace ResourceDownload { + +ShaderPackResourcePage::ShaderPackResourcePage(ShaderPackDownloadDialog* dialog, BaseInstance& instance) + : ResourcePage(dialog, instance) +{ + connect(m_ui->searchButton, &QPushButton::clicked, this, &ShaderPackResourcePage::triggerSearch); + connect(m_ui->packView, &QListView::doubleClicked, this, &ShaderPackResourcePage::onResourceSelected); +} + +/******** Callbacks to events in the UI (set up in the derived classes) ********/ + +void ShaderPackResourcePage::triggerSearch() +{ + m_ui->packView->clearSelection(); + m_ui->packDescription->clear(); + m_ui->versionSelectionBox->clear(); + + updateSelectionButton(); + + static_cast(m_model)->searchWithTerm(getSearchTerm(), m_ui->sortByBox->currentData().toUInt()); + m_fetch_progress.watch(m_model->activeSearchJob().get()); +} + +QMap ShaderPackResourcePage::urlHandlers() const +{ + QMap map; + map.insert(QRegularExpression::anchoredPattern("(?:www\\.)?modrinth\\.com\\/shaders\\/([^\\/]+)\\/?"), "modrinth"); + map.insert(QRegularExpression::anchoredPattern("(?:www\\.)?curseforge\\.com\\/minecraft\\/customization\\/([^\\/]+)\\/?"), "curseforge"); + map.insert(QRegularExpression::anchoredPattern("minecraft\\.curseforge\\.com\\/projects\\/([^\\/]+)\\/?"), "curseforge"); + return map; +} + +void ShaderPackResourcePage::addResourceToDialog(ModPlatform::IndexedPack& pack, ModPlatform::IndexedVersion& version) +{ + if (version.loaders.contains(QStringLiteral("canvas"))) + version.custom_target_folder = QStringLiteral("resourcepacks"); + + m_parent_dialog->addResource(pack, version); +} + +} // namespace ResourceDownload diff --git a/launcher/ui/pages/modplatform/ShaderPackPage.h b/launcher/ui/pages/modplatform/ShaderPackPage.h new file mode 100644 index 00000000..92a9c3ee --- /dev/null +++ b/launcher/ui/pages/modplatform/ShaderPackPage.h @@ -0,0 +1,50 @@ +#pragma once + +#include "ui/pages/modplatform/ResourcePage.h" +#include "ui/pages/modplatform/ShaderPackModel.h" + +namespace Ui { +class ResourcePage; +} + +namespace ResourceDownload { + +class ShaderPackDownloadDialog; + +class ShaderPackResourcePage : public ResourcePage { + Q_OBJECT + + public: + template + static T* create(ShaderPackDownloadDialog* dialog, BaseInstance& instance) + { + auto page = new T(dialog, instance); + auto model = static_cast(page->getModel()); + + connect(model, &ResourceModel::versionListUpdated, page, &ResourcePage::updateVersionList); + connect(model, &ResourceModel::projectInfoUpdated, page, &ResourcePage::updateUi); + + return page; + } + + ~ShaderPackResourcePage() override = default; + + //: The plural version of 'shader pack' + [[nodiscard]] inline QString resourcesString() const override { return tr("shader packs"); } + //: The singular version of 'shader packs' + [[nodiscard]] inline QString resourceString() const override { return tr("shader pack"); } + + [[nodiscard]] bool supportsFiltering() const override { return false; }; + + void addResourceToDialog(ModPlatform::IndexedPack&, ModPlatform::IndexedVersion&) override; + + [[nodiscard]] QMap urlHandlers() const override; + + protected: + ShaderPackResourcePage(ShaderPackDownloadDialog* dialog, BaseInstance& instance); + + protected slots: + void triggerSearch() override; +}; + +} // namespace ResourceDownload diff --git a/launcher/ui/pages/modplatform/flame/FlameResourcePages.h b/launcher/ui/pages/modplatform/flame/FlameResourcePages.h index 03cf7795..4507a32a 100644 --- a/launcher/ui/pages/modplatform/flame/FlameResourcePages.h +++ b/launcher/ui/pages/modplatform/flame/FlameResourcePages.h @@ -44,6 +44,7 @@ #include "ui/pages/modplatform/ModPage.h" #include "ui/pages/modplatform/ResourcePackPage.h" +#include "ui/pages/modplatform/ShaderPackPage.h" namespace ResourceDownload { diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.cpp index b36339d7..bd433121 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.cpp +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.cpp @@ -69,4 +69,26 @@ auto ModrinthResourcePackModel::documentToArray(QJsonDocument& obj) const -> QJs return obj.object().value("hits").toArray(); } +ModrinthShaderPackModel::ModrinthShaderPackModel(const BaseInstance& base) : ShaderPackResourceModel(base, new ModrinthAPI){} + +void ModrinthShaderPackModel::loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj) +{ + ::Modrinth::loadIndexedPack(m, obj); +} + +void ModrinthShaderPackModel::loadExtraPackInfo(ModPlatform::IndexedPack& m, QJsonObject& obj) +{ + ::Modrinth::loadExtraPackData(m, obj); +} + +void ModrinthShaderPackModel::loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr) +{ + ::Modrinth::loadIndexedPackVersions(m, arr, APPLICATION->network(), &m_base_instance); +} + +auto ModrinthShaderPackModel::documentToArray(QJsonDocument& obj) const -> QJsonArray +{ + return obj.object().value("hits").toArray(); +} + } // namespace ResourceDownload diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.h b/launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.h index 0a91ef23..80a48089 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.h +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.h @@ -62,4 +62,22 @@ class ModrinthResourcePackModel : public ResourcePackResourceModel { auto documentToArray(QJsonDocument& obj) const -> QJsonArray override; }; +class ModrinthShaderPackModel : public ShaderPackResourceModel { + Q_OBJECT + + public: + ModrinthShaderPackModel(const BaseInstance&); + ~ModrinthShaderPackModel() override = default; + + private: + [[nodiscard]] QString debugName() const override { return Modrinth::debugName() + " (Model)"; } + [[nodiscard]] QString metaEntryBase() const override { return Modrinth::metaEntryBase(); } + + void loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj) override; + void loadExtraPackInfo(ModPlatform::IndexedPack& m, QJsonObject& obj) override; + void loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr) override; + + auto documentToArray(QJsonDocument& obj) const -> QJsonArray override; +}; + } // namespace ResourceDownload diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.cpp index d32b9091..2826b5d3 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.cpp +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.cpp @@ -100,10 +100,29 @@ ModrinthResourcePackPage::ModrinthResourcePackPage(ResourcePackDownloadDialog* d m_ui->packDescription->setMetaEntry(metaEntryBase()); } +ModrinthShaderPackPage::ModrinthShaderPackPage(ShaderPackDownloadDialog* dialog, BaseInstance& instance) + : ShaderPackResourcePage(dialog, instance) +{ + m_model = new ModrinthShaderPackModel(instance); + m_ui->packView->setModel(m_model); + + addSortings(); + + // sometimes Qt just ignores virtual slots and doesn't work as intended it seems, + // so it's best not to connect them in the parent's constructor... + connect(m_ui->sortByBox, SIGNAL(currentIndexChanged(int)), this, SLOT(triggerSearch())); + connect(m_ui->packView->selectionModel(), &QItemSelectionModel::currentChanged, this, &ModrinthShaderPackPage::onSelectionChanged); + connect(m_ui->versionSelectionBox, &QComboBox::currentTextChanged, this, &ModrinthShaderPackPage::onVersionSelectionChanged); + connect(m_ui->resourceSelectionButton, &QPushButton::clicked, this, &ModrinthShaderPackPage::onResourceSelected); + + m_ui->packDescription->setMetaEntry(metaEntryBase()); +} + // I don't know why, but doing this on the parent class makes it so that // other mod providers start loading before being selected, at least with // my Qt, so we need to implement this in every derived class... auto ModrinthModPage::shouldDisplay() const -> bool { return true; } auto ModrinthResourcePackPage::shouldDisplay() const -> bool { return true; } +auto ModrinthShaderPackPage::shouldDisplay() const -> bool { return true; } } // namespace ResourceDownload diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.h b/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.h index be3740a1..8733a1b2 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.h +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.h @@ -43,6 +43,7 @@ #include "ui/pages/modplatform/ModPage.h" #include "ui/pages/modplatform/ResourcePackPage.h" +#include "ui/pages/modplatform/ShaderPackPage.h" namespace ResourceDownload { @@ -102,4 +103,27 @@ class ModrinthResourcePackPage : public ResourcePackResourcePage { [[nodiscard]] inline auto helpPage() const -> QString override { return ""; } }; +class ModrinthShaderPackPage : public ShaderPackResourcePage { + Q_OBJECT + + public: + static ModrinthShaderPackPage* create(ShaderPackDownloadDialog* dialog, BaseInstance& instance) + { + return ShaderPackResourcePage::create(dialog, instance); + } + + ModrinthShaderPackPage(ShaderPackDownloadDialog* dialog, BaseInstance& instance); + ~ModrinthShaderPackPage() override = default; + + [[nodiscard]] bool shouldDisplay() const override; + + [[nodiscard]] inline auto displayName() const -> QString override { return Modrinth::displayName(); } + [[nodiscard]] inline auto icon() const -> QIcon override { return Modrinth::icon(); } + [[nodiscard]] inline auto id() const -> QString override { return Modrinth::id(); } + [[nodiscard]] inline auto debugName() const -> QString override { return Modrinth::debugName(); } + [[nodiscard]] inline auto metaEntryBase() const -> QString override { return Modrinth::metaEntryBase(); } + + [[nodiscard]] inline auto helpPage() const -> QString override { return ""; } +}; + } // namespace ResourceDownload -- cgit From ada5e88eb933a41691121316c78cd2e564965fa0 Mon Sep 17 00:00:00 2001 From: flow Date: Sun, 29 Jan 2023 18:07:49 -0300 Subject: feat(RD): add texture pack downloader This extends the resource pack downloader, with the custom behavior of filtering the versions that shows up, to those <= 1.6. As always, Flame is funky and requires a bit more workarounds than average. This will also get a nice improvement when the Version parsing and comparison PR gets merged! :D Signed-off-by: flow --- launcher/Application.cpp | 1 + launcher/CMakeLists.txt | 5 + launcher/meta/Version.cpp | 5 + launcher/meta/Version.h | 3 + launcher/ui/dialogs/ResourceDownloadDialog.cpp | 27 ++++++ launcher/ui/dialogs/ResourceDownloadDialog.h | 20 ++++ launcher/ui/pages/instance/TexturePackPage.cpp | 104 +++++++++++++++++++++ launcher/ui/pages/instance/TexturePackPage.h | 19 +--- launcher/ui/pages/modplatform/ResourceModel.h | 2 +- launcher/ui/pages/modplatform/ResourcePackPage.cpp | 2 +- launcher/ui/pages/modplatform/ResourcePackPage.h | 2 +- launcher/ui/pages/modplatform/TexturePackModel.cpp | 80 ++++++++++++++++ launcher/ui/pages/modplatform/TexturePackModel.h | 23 +++++ launcher/ui/pages/modplatform/TexturePackPage.h | 46 +++++++++ .../modplatform/flame/FlameResourceModels.cpp | 58 ++++++++++++ .../pages/modplatform/flame/FlameResourceModels.h | 21 +++++ .../pages/modplatform/flame/FlameResourcePages.cpp | 40 ++++++++ .../pages/modplatform/flame/FlameResourcePages.h | 29 +++++- .../modrinth/ModrinthResourceModels.cpp | 22 +++++ .../modplatform/modrinth/ModrinthResourceModels.h | 18 ++++ .../modplatform/modrinth/ModrinthResourcePages.cpp | 19 ++++ .../modplatform/modrinth/ModrinthResourcePages.h | 24 +++++ 22 files changed, 550 insertions(+), 20 deletions(-) create mode 100644 launcher/ui/pages/instance/TexturePackPage.cpp create mode 100644 launcher/ui/pages/modplatform/TexturePackModel.cpp create mode 100644 launcher/ui/pages/modplatform/TexturePackModel.h create mode 100644 launcher/ui/pages/modplatform/TexturePackPage.h (limited to 'launcher/ui/dialogs') diff --git a/launcher/Application.cpp b/launcher/Application.cpp index 0f7cce66..2956d798 100644 --- a/launcher/Application.cpp +++ b/launcher/Application.cpp @@ -611,6 +611,7 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv) m_settings->registerSetting("ModDownloadGeometry", ""); m_settings->registerSetting("RPDownloadGeometry", ""); + m_settings->registerSetting("TPDownloadGeometry", ""); m_settings->registerSetting("ShaderDownloadGeometry", ""); // HACK: This code feels so stupid is there a less stupid way of doing this? diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index 795aeb9f..202e633c 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -720,6 +720,7 @@ SET(LAUNCHER_SOURCES ui/pages/instance/ManagedPackPage.cpp ui/pages/instance/ManagedPackPage.h ui/pages/instance/TexturePackPage.h + ui/pages/instance/TexturePackPage.cpp ui/pages/instance/ResourcePackPage.h ui/pages/instance/ResourcePackPage.cpp ui/pages/instance/ShaderPackPage.h @@ -778,6 +779,10 @@ SET(LAUNCHER_SOURCES ui/pages/modplatform/ResourcePackPage.cpp ui/pages/modplatform/ResourcePackModel.cpp + # Needed for MOC to find them without a corresponding .cpp + ui/pages/modplatform/TexturePackPage.h + ui/pages/modplatform/TexturePackModel.cpp + ui/pages/modplatform/ShaderPackPage.cpp ui/pages/modplatform/ShaderPackModel.cpp diff --git a/launcher/meta/Version.cpp b/launcher/meta/Version.cpp index 68cfa55c..e617abf8 100644 --- a/launcher/meta/Version.cpp +++ b/launcher/meta/Version.cpp @@ -99,6 +99,11 @@ QString Meta::Version::localFilename() const return m_uid + '/' + m_version + ".json"; } +::Version Meta::Version::toComparableVersion() const +{ + return { const_cast(this)->descriptor() }; +} + void Meta::Version::setType(const QString &type) { m_type = type; diff --git a/launcher/meta/Version.h b/launcher/meta/Version.h index 7228fa36..78156193 100644 --- a/launcher/meta/Version.h +++ b/launcher/meta/Version.h @@ -16,6 +16,7 @@ #pragma once #include "BaseVersion.h" +#include "../Version.h" #include #include @@ -85,6 +86,8 @@ public: QString localFilename() const override; + [[nodiscard]] ::Version toComparableVersion() const; + public: // for usage by format parsers only void setType(const QString &type); void setTime(const qint64 time); diff --git a/launcher/ui/dialogs/ResourceDownloadDialog.cpp b/launcher/ui/dialogs/ResourceDownloadDialog.cpp index 98a2eb88..edb7d063 100644 --- a/launcher/ui/dialogs/ResourceDownloadDialog.cpp +++ b/launcher/ui/dialogs/ResourceDownloadDialog.cpp @@ -26,6 +26,7 @@ #include "minecraft/mod/ModFolderModel.h" #include "minecraft/mod/ResourcePackFolderModel.h" +#include "minecraft/mod/TexturePackFolderModel.h" #include "minecraft/mod/ShaderPackFolderModel.h" #include "ui/dialogs/ReviewMessageBox.h" @@ -258,6 +259,32 @@ QList ResourcePackDownloadDialog::getPages() } +TexturePackDownloadDialog::TexturePackDownloadDialog(QWidget* parent, + const std::shared_ptr& resource_packs, + BaseInstance* instance) + : ResourceDownloadDialog(parent, resource_packs), m_instance(instance) +{ + setWindowTitle(dialogTitle()); + + initializeContainer(); + connectButtons(); + + if (!geometrySaveKey().isEmpty()) + restoreGeometry(QByteArray::fromBase64(APPLICATION->settings()->get(geometrySaveKey()).toByteArray())); +} + +QList TexturePackDownloadDialog::getPages() +{ + QList pages; + + pages.append(ModrinthTexturePackPage::create(this, *m_instance)); + if (APPLICATION->capabilities() & Application::SupportsFlame) + pages.append(FlameTexturePackPage::create(this, *m_instance)); + + return pages; +} + + ShaderPackDownloadDialog::ShaderPackDownloadDialog(QWidget* parent, const std::shared_ptr& shaders, BaseInstance* instance) diff --git a/launcher/ui/dialogs/ResourceDownloadDialog.h b/launcher/ui/dialogs/ResourceDownloadDialog.h index 203bac66..5678dc8b 100644 --- a/launcher/ui/dialogs/ResourceDownloadDialog.h +++ b/launcher/ui/dialogs/ResourceDownloadDialog.h @@ -36,6 +36,7 @@ class QDialogButtonBox; class ResourceDownloadTask; class ResourceFolderModel; class ResourcePackFolderModel; +class TexturePackFolderModel; class ShaderPackFolderModel; namespace ResourceDownload { @@ -129,6 +130,25 @@ class ResourcePackDownloadDialog final : public ResourceDownloadDialog { BaseInstance* m_instance; }; +class TexturePackDownloadDialog final : public ResourceDownloadDialog { + Q_OBJECT + + public: + explicit TexturePackDownloadDialog(QWidget* parent, + const std::shared_ptr& resource_packs, + BaseInstance* instance); + ~TexturePackDownloadDialog() override = default; + + //: String that gets appended to the texture pack download dialog title ("Download " + resourcesString()) + [[nodiscard]] QString resourcesString() const override { return tr("texture packs"); } + [[nodiscard]] QString geometrySaveKey() const override { return "TPDownloadGeometry"; } + + QList getPages() override; + + private: + BaseInstance* m_instance; +}; + class ShaderPackDownloadDialog final : public ResourceDownloadDialog { Q_OBJECT diff --git a/launcher/ui/pages/instance/TexturePackPage.cpp b/launcher/ui/pages/instance/TexturePackPage.cpp new file mode 100644 index 00000000..5b68c102 --- /dev/null +++ b/launcher/ui/pages/instance/TexturePackPage.cpp @@ -0,0 +1,104 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (c) 2022 Jamie Mansfield + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * This file incorporates work covered by the following copyright and + * permission notice: + * + * Copyright 2013-2021 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "TexturePackPage.h" + +#include "ResourceDownloadTask.h" + +#include "minecraft/mod/TexturePack.h" + +#include "ui/dialogs/CustomMessageBox.h" +#include "ui/dialogs/ProgressDialog.h" +#include "ui/dialogs/ResourceDownloadDialog.h" + +TexturePackPage::TexturePackPage(MinecraftInstance* instance, std::shared_ptr model, QWidget* parent) + : ExternalResourcesPage(instance, model, parent) +{ + ui->actionDownloadItem->setText(tr("Download TPs")); + ui->actionDownloadItem->setToolTip(tr("Download TPs from online platforms")); + ui->actionDownloadItem->setEnabled(true); + connect(ui->actionDownloadItem, &QAction::triggered, this, &TexturePackPage::downloadTPs); + ui->actionsToolbar->insertActionBefore(ui->actionAddItem, ui->actionDownloadItem); + + ui->actionViewConfigs->setVisible(false); +} + +bool TexturePackPage::onSelectionChanged(const QModelIndex& current, const QModelIndex& previous) +{ + auto sourceCurrent = m_filterModel->mapToSource(current); + int row = sourceCurrent.row(); + auto& rp = static_cast(m_model->at(row)); + ui->frame->updateWithTexturePack(rp); + + return true; +} + +void TexturePackPage::downloadTPs() +{ + if (!m_controlsEnabled) + return; + if (m_instance->typeName() != "Minecraft") + return; // this is a null instance or a legacy instance + + ResourceDownload::TexturePackDownloadDialog mdownload(this, std::static_pointer_cast(m_model), m_instance); + if (mdownload.exec()) { + auto tasks = new ConcurrentTask(this); + connect(tasks, &Task::failed, [this, tasks](QString reason) { + CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); + tasks->deleteLater(); + }); + connect(tasks, &Task::aborted, [this, tasks]() { + CustomMessageBox::selectable(this, tr("Aborted"), tr("Download stopped by user."), QMessageBox::Information)->show(); + tasks->deleteLater(); + }); + connect(tasks, &Task::succeeded, [this, tasks]() { + QStringList warnings = tasks->warnings(); + if (warnings.count()) + CustomMessageBox::selectable(this, tr("Warnings"), warnings.join('\n'), QMessageBox::Warning)->show(); + + tasks->deleteLater(); + }); + + for (auto& task : mdownload.getTasks()) { + tasks->addTask(task); + } + + ProgressDialog loadDialog(this); + loadDialog.setSkipButton(true, tr("Abort")); + loadDialog.execWithTask(tasks); + + m_model->update(); + } +} diff --git a/launcher/ui/pages/instance/TexturePackPage.h b/launcher/ui/pages/instance/TexturePackPage.h index 69b836ca..5712f7d6 100644 --- a/launcher/ui/pages/instance/TexturePackPage.h +++ b/launcher/ui/pages/instance/TexturePackPage.h @@ -39,18 +39,12 @@ #include "ui_ExternalResourcesPage.h" #include "minecraft/mod/TexturePackFolderModel.h" -#include "minecraft/mod/TexturePack.h" class TexturePackPage : public ExternalResourcesPage { Q_OBJECT public: - explicit TexturePackPage(MinecraftInstance *instance, std::shared_ptr model, QWidget *parent = 0) - : ExternalResourcesPage(instance, model, parent) - { - ui->actionViewConfigs->setVisible(false); - } - virtual ~TexturePackPage() {} + explicit TexturePackPage(MinecraftInstance *instance, std::shared_ptr model, QWidget* parent = nullptr); QString displayName() const override { return tr("Texture packs"); } QIcon icon() const override { return APPLICATION->getThemedIcon("resourcepacks"); } @@ -63,13 +57,6 @@ public: } public slots: - bool onSelectionChanged(const QModelIndex& current, const QModelIndex& previous) override - { - auto sourceCurrent = m_filterModel->mapToSource(current); - int row = sourceCurrent.row(); - auto& rp = static_cast(m_model->at(row)); - ui->frame->updateWithTexturePack(rp); - - return true; - } + bool onSelectionChanged(const QModelIndex& current, const QModelIndex& previous) override; + void downloadTPs(); }; diff --git a/launcher/ui/pages/modplatform/ResourceModel.h b/launcher/ui/pages/modplatform/ResourceModel.h index 610b631c..46a02d6e 100644 --- a/launcher/ui/pages/modplatform/ResourceModel.h +++ b/launcher/ui/pages/modplatform/ResourceModel.h @@ -98,7 +98,7 @@ class ResourceModel : public QAbstractListModel { /** Functions to load data into a pack. * - * Those are needed for the same reason as ddocumentToArray, and NEED to be re-implemented in the same way. + * Those are needed for the same reason as documentToArray, and NEED to be re-implemented in the same way. */ virtual void loadIndexedPack(ModPlatform::IndexedPack&, QJsonObject&); diff --git a/launcher/ui/pages/modplatform/ResourcePackPage.cpp b/launcher/ui/pages/modplatform/ResourcePackPage.cpp index 8d663aa8..c2de9e3b 100644 --- a/launcher/ui/pages/modplatform/ResourcePackPage.cpp +++ b/launcher/ui/pages/modplatform/ResourcePackPage.cpp @@ -9,7 +9,7 @@ namespace ResourceDownload { -ResourcePackResourcePage::ResourcePackResourcePage(ResourcePackDownloadDialog* dialog, BaseInstance& instance) +ResourcePackResourcePage::ResourcePackResourcePage(ResourceDownloadDialog* dialog, BaseInstance& instance) : ResourcePage(dialog, instance) { connect(m_ui->searchButton, &QPushButton::clicked, this, &ResourcePackResourcePage::triggerSearch); diff --git a/launcher/ui/pages/modplatform/ResourcePackPage.h b/launcher/ui/pages/modplatform/ResourcePackPage.h index 2ecff390..97780047 100644 --- a/launcher/ui/pages/modplatform/ResourcePackPage.h +++ b/launcher/ui/pages/modplatform/ResourcePackPage.h @@ -39,7 +39,7 @@ class ResourcePackResourcePage : public ResourcePage { [[nodiscard]] QMap urlHandlers() const override; protected: - ResourcePackResourcePage(ResourcePackDownloadDialog* dialog, BaseInstance& instance); + ResourcePackResourcePage(ResourceDownloadDialog* dialog, BaseInstance& instance); protected slots: void triggerSearch() override; diff --git a/launcher/ui/pages/modplatform/TexturePackModel.cpp b/launcher/ui/pages/modplatform/TexturePackModel.cpp new file mode 100644 index 00000000..076a200d --- /dev/null +++ b/launcher/ui/pages/modplatform/TexturePackModel.cpp @@ -0,0 +1,80 @@ +#include "TexturePackModel.h" + +#include "Application.h" + +#include "meta/Index.h" +#include "meta/Version.h" + +static std::list s_availableVersions = {}; + +namespace ResourceDownload { +TexturePackResourceModel::TexturePackResourceModel(BaseInstance const& inst, ResourceAPI* api) + : ResourcePackResourceModel(inst, api), m_version_list(APPLICATION->metadataIndex()->get("net.minecraft")) +{ + if (!m_version_list->isLoaded()) { + qDebug() << "Loading version list..."; + auto task = m_version_list->getLoadTask(); + if (!task->isRunning()) + task->start(); + } +} + +void waitOnVersionListLoad(Meta::VersionList::Ptr version_list) +{ + QEventLoop load_version_list_loop; + + QTimer time_limit_for_list_load; + time_limit_for_list_load.setTimerType(Qt::TimerType::CoarseTimer); + time_limit_for_list_load.setSingleShot(true); + time_limit_for_list_load.callOnTimeout(&load_version_list_loop, &QEventLoop::quit); + time_limit_for_list_load.start(4000); + + auto task = version_list->getLoadTask(); + QObject::connect(task.get(), &Task::finished, &load_version_list_loop, &QEventLoop::quit); + + load_version_list_loop.exec(); + if (time_limit_for_list_load.isActive()) + time_limit_for_list_load.stop(); +} + +ResourceAPI::SearchArgs TexturePackResourceModel::createSearchArguments() +{ + if (s_availableVersions.empty()) + waitOnVersionListLoad(m_version_list); + + auto args = ResourcePackResourceModel::createSearchArguments(); + + if (!m_version_list->isLoaded()) { + qCritical() << "The version list could not be loaded. Falling back to showing all entries."; + return args; + } + + if (s_availableVersions.empty()) { + for (auto&& version : m_version_list->versions()) { + // FIXME: This duplicates the logic in meta for the 'texturepacks' trait. However, we don't have access to that + // information from the index file alone. Also, downloading every version's file isn't a very good idea. + if (auto ver = version->toComparableVersion(); ver <= maximumTexturePackVersion()) + s_availableVersions.push_back(ver); + } + } + + Q_ASSERT(!s_availableVersions.empty()); + + args.versions = s_availableVersions; + + return args; +} + +ResourceAPI::VersionSearchArgs TexturePackResourceModel::createVersionsArguments(QModelIndex& entry) +{ + auto args = ResourcePackResourceModel::createVersionsArguments(entry); + if (!m_version_list->isLoaded()) { + qCritical() << "The version list could not be loaded. Falling back to showing all entries."; + return args; + } + + args.mcVersions = s_availableVersions; + return args; +} + +} // namespace ResourceDownload diff --git a/launcher/ui/pages/modplatform/TexturePackModel.h b/launcher/ui/pages/modplatform/TexturePackModel.h new file mode 100644 index 00000000..0ae5bdd4 --- /dev/null +++ b/launcher/ui/pages/modplatform/TexturePackModel.h @@ -0,0 +1,23 @@ +#pragma once + +#include "meta/VersionList.h" +#include "ui/pages/modplatform/ResourcePackModel.h" + +namespace ResourceDownload { + +class TexturePackResourceModel : public ResourcePackResourceModel { + Q_OBJECT + + public: + TexturePackResourceModel(BaseInstance const& inst, ResourceAPI* api); + + [[nodiscard]] inline ::Version maximumTexturePackVersion() const { return { "1.6" }; } + + ResourceAPI::SearchArgs createSearchArguments() override; + ResourceAPI::VersionSearchArgs createVersionsArguments(QModelIndex&) override; + + protected: + Meta::VersionList::Ptr m_version_list; +}; + +} // namespace ResourceDownload diff --git a/launcher/ui/pages/modplatform/TexturePackPage.h b/launcher/ui/pages/modplatform/TexturePackPage.h new file mode 100644 index 00000000..3466d3ea --- /dev/null +++ b/launcher/ui/pages/modplatform/TexturePackPage.h @@ -0,0 +1,46 @@ +#pragma once + +#include "ui_ResourcePage.h" +#include "ui/dialogs/ResourceDownloadDialog.h" +#include "ui/pages/modplatform/ResourcePackPage.h" +#include "ui/pages/modplatform/TexturePackModel.h" + +namespace Ui { +class ResourcePage; +} + +namespace ResourceDownload { + +class TexturePackDownloadDialog; + +class TexturePackResourcePage : public ResourcePackResourcePage { + Q_OBJECT + + public: + template + static T* create(TexturePackDownloadDialog* dialog, BaseInstance& instance) + { + auto page = new T(dialog, instance); + auto model = static_cast(page->getModel()); + + connect(model, &ResourceModel::versionListUpdated, page, &ResourcePage::updateVersionList); + connect(model, &ResourceModel::projectInfoUpdated, page, &ResourcePage::updateUi); + + return page; + } + + //: The plural version of 'texture pack' + [[nodiscard]] inline QString resourcesString() const override { return tr("texture packs"); } + //: The singular version of 'texture packs' + [[nodiscard]] inline QString resourceString() const override { return tr("texture pack"); } + + protected: + TexturePackResourcePage(TexturePackDownloadDialog* dialog, BaseInstance& instance) + : ResourcePackResourcePage(dialog, instance) + { + connect(m_ui->searchButton, &QPushButton::clicked, this, &TexturePackResourcePage::triggerSearch); + connect(m_ui->packView, &QListView::doubleClicked, this, &TexturePackResourcePage::onResourceSelected); + } +}; + +} // namespace ResourceDownload diff --git a/launcher/ui/pages/modplatform/flame/FlameResourceModels.cpp b/launcher/ui/pages/modplatform/flame/FlameResourceModels.cpp index 95d915fc..e3d0bc14 100644 --- a/launcher/ui/pages/modplatform/flame/FlameResourceModels.cpp +++ b/launcher/ui/pages/modplatform/flame/FlameResourceModels.cpp @@ -57,5 +57,63 @@ auto FlameResourcePackModel::documentToArray(QJsonDocument& obj) const -> QJsonA return Json::ensureArray(obj.object(), "data"); } +FlameTexturePackModel::FlameTexturePackModel(const BaseInstance& base) : TexturePackResourceModel(base, new FlameAPI) {} + +void FlameTexturePackModel::loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj) +{ + FlameMod::loadIndexedPack(m, obj); +} + +// We already deal with the URLs when initializing the pack, due to the API response's structure +void FlameTexturePackModel::loadExtraPackInfo(ModPlatform::IndexedPack& m, QJsonObject& obj) +{ + FlameMod::loadBody(m, obj); +} + +void FlameTexturePackModel::loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr) +{ + FlameMod::loadIndexedPackVersions(m, arr, APPLICATION->network(), &m_base_instance); + + QVector filtered_versions(m.versions.size()); + + // FIXME: Client-side version filtering. This won't take into account any user-selected filtering. + for (auto const& version : m.versions) { + auto const& mc_versions = version.mcVersion; + + if (std::any_of(mc_versions.constBegin(), mc_versions.constEnd(), + [this](auto const& mc_version){ return Version(mc_version) <= maximumTexturePackVersion(); })) + filtered_versions.push_back(version); + } + + m.versions = filtered_versions; +} + +ResourceAPI::SearchArgs FlameTexturePackModel::createSearchArguments() +{ + auto args = TexturePackResourceModel::createSearchArguments(); + + auto profile = static_cast(m_base_instance).getPackProfile(); + QString instance_minecraft_version = profile->getComponentVersion("net.minecraft"); + + // Bypass the texture pack logic, because we can't do multiple versions in the API query + args.versions = { instance_minecraft_version }; + + return args; +} + +ResourceAPI::VersionSearchArgs FlameTexturePackModel::createVersionsArguments(QModelIndex& entry) +{ + auto args = TexturePackResourceModel::createVersionsArguments(entry); + + // Bypass the texture pack logic, because we can't do multiple versions in the API query + args.mcVersions = {}; + + return args; +} + +auto FlameTexturePackModel::documentToArray(QJsonDocument& obj) const -> QJsonArray +{ + return Json::ensureArray(obj.object(), "data"); +} } // namespace ResourceDownload diff --git a/launcher/ui/pages/modplatform/flame/FlameResourceModels.h b/launcher/ui/pages/modplatform/flame/FlameResourceModels.h index be214716..0252ac40 100644 --- a/launcher/ui/pages/modplatform/flame/FlameResourceModels.h +++ b/launcher/ui/pages/modplatform/flame/FlameResourceModels.h @@ -46,4 +46,25 @@ class FlameResourcePackModel : public ResourcePackResourceModel { auto documentToArray(QJsonDocument& obj) const -> QJsonArray override; }; +class FlameTexturePackModel : public TexturePackResourceModel { + Q_OBJECT + + public: + FlameTexturePackModel(const BaseInstance&); + ~FlameTexturePackModel() override = default; + + private: + [[nodiscard]] QString debugName() const override { return Flame::debugName() + " (Model)"; } + [[nodiscard]] QString metaEntryBase() const override { return Flame::metaEntryBase(); } + + void loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj) override; + void loadExtraPackInfo(ModPlatform::IndexedPack& m, QJsonObject& obj) override; + void loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr) override; + + ResourceAPI::SearchArgs createSearchArguments() override; + ResourceAPI::VersionSearchArgs createVersionsArguments(QModelIndex&) override; + + auto documentToArray(QJsonDocument& obj) const -> QJsonArray override; +}; + } // namespace ResourceDownload diff --git a/launcher/ui/pages/modplatform/flame/FlameResourcePages.cpp b/launcher/ui/pages/modplatform/flame/FlameResourcePages.cpp index 15b04ab4..f93e27e6 100644 --- a/launcher/ui/pages/modplatform/flame/FlameResourcePages.cpp +++ b/launcher/ui/pages/modplatform/flame/FlameResourcePages.cpp @@ -133,10 +133,50 @@ void FlameResourcePackPage::openUrl(const QUrl& url) ResourcePackResourcePage::openUrl(url); } +FlameTexturePackPage::FlameTexturePackPage(TexturePackDownloadDialog* dialog, BaseInstance& instance) + : TexturePackResourcePage(dialog, instance) +{ + m_model = new FlameTexturePackModel(instance); + m_ui->packView->setModel(m_model); + + addSortings(); + + // sometimes Qt just ignores virtual slots and doesn't work as intended it seems, + // so it's best not to connect them in the parent's contructor... + connect(m_ui->sortByBox, SIGNAL(currentIndexChanged(int)), this, SLOT(triggerSearch())); + connect(m_ui->packView->selectionModel(), &QItemSelectionModel::currentChanged, this, &FlameTexturePackPage::onSelectionChanged); + connect(m_ui->versionSelectionBox, &QComboBox::currentTextChanged, this, &FlameTexturePackPage::onVersionSelectionChanged); + connect(m_ui->resourceSelectionButton, &QPushButton::clicked, this, &FlameTexturePackPage::onResourceSelected); + + m_ui->packDescription->setMetaEntry(metaEntryBase()); +} + +bool FlameTexturePackPage::optedOut(ModPlatform::IndexedVersion& ver) const +{ + return isOptedOut(ver); +} + +void FlameTexturePackPage::openUrl(const QUrl& url) +{ + if (url.scheme().isEmpty()) { + QString query = url.query(QUrl::FullyDecoded); + + if (query.startsWith("remoteUrl=")) { + // attempt to resolve url from warning page + query.remove(0, 10); + ResourcePackResourcePage::openUrl({QUrl::fromPercentEncoding(query.toUtf8())}); // double decoding is necessary + return; + } + } + + TexturePackResourcePage::openUrl(url); +} + // I don't know why, but doing this on the parent class makes it so that // other mod providers start loading before being selected, at least with // my Qt, so we need to implement this in every derived class... auto FlameModPage::shouldDisplay() const -> bool { return true; } auto FlameResourcePackPage::shouldDisplay() const -> bool { return true; } +auto FlameTexturePackPage::shouldDisplay() const -> bool { return true; } } // namespace ResourceDownload diff --git a/launcher/ui/pages/modplatform/flame/FlameResourcePages.h b/launcher/ui/pages/modplatform/flame/FlameResourcePages.h index 4507a32a..103a6bb9 100644 --- a/launcher/ui/pages/modplatform/flame/FlameResourcePages.h +++ b/launcher/ui/pages/modplatform/flame/FlameResourcePages.h @@ -44,7 +44,7 @@ #include "ui/pages/modplatform/ModPage.h" #include "ui/pages/modplatform/ResourcePackPage.h" -#include "ui/pages/modplatform/ShaderPackPage.h" +#include "ui/pages/modplatform/TexturePackPage.h" namespace ResourceDownload { @@ -111,4 +111,31 @@ class FlameResourcePackPage : public ResourcePackResourcePage { void openUrl(const QUrl& url) override; }; +class FlameTexturePackPage : public TexturePackResourcePage { + Q_OBJECT + + public: + static FlameTexturePackPage* create(TexturePackDownloadDialog* dialog, BaseInstance& instance) + { + return TexturePackResourcePage::create(dialog, instance); + } + + FlameTexturePackPage(TexturePackDownloadDialog* dialog, BaseInstance& instance); + ~FlameTexturePackPage() override = default; + + [[nodiscard]] bool shouldDisplay() const override; + + [[nodiscard]] inline auto displayName() const -> QString override { return Flame::displayName(); } + [[nodiscard]] inline auto icon() const -> QIcon override { return Flame::icon(); } + [[nodiscard]] inline auto id() const -> QString override { return Flame::id(); } + [[nodiscard]] inline auto debugName() const -> QString override { return Flame::debugName(); } + [[nodiscard]] inline auto metaEntryBase() const -> QString override { return Flame::metaEntryBase(); } + + [[nodiscard]] inline auto helpPage() const -> QString override { return ""; } + + bool optedOut(ModPlatform::IndexedVersion& ver) const override; + + void openUrl(const QUrl& url) override; +}; + } // namespace ResourceDownload diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.cpp index bd433121..f5d1cc28 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.cpp +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.cpp @@ -69,6 +69,28 @@ auto ModrinthResourcePackModel::documentToArray(QJsonDocument& obj) const -> QJs return obj.object().value("hits").toArray(); } +ModrinthTexturePackModel::ModrinthTexturePackModel(const BaseInstance& base) : TexturePackResourceModel(base, new ModrinthAPI){} + +void ModrinthTexturePackModel::loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj) +{ + ::Modrinth::loadIndexedPack(m, obj); +} + +void ModrinthTexturePackModel::loadExtraPackInfo(ModPlatform::IndexedPack& m, QJsonObject& obj) +{ + ::Modrinth::loadExtraPackData(m, obj); +} + +void ModrinthTexturePackModel::loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr) +{ + ::Modrinth::loadIndexedPackVersions(m, arr, APPLICATION->network(), &m_base_instance); +} + +auto ModrinthTexturePackModel::documentToArray(QJsonDocument& obj) const -> QJsonArray +{ + return obj.object().value("hits").toArray(); +} + ModrinthShaderPackModel::ModrinthShaderPackModel(const BaseInstance& base) : ShaderPackResourceModel(base, new ModrinthAPI){} void ModrinthShaderPackModel::loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj) diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.h b/launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.h index 80a48089..b351b19b 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.h +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.h @@ -62,6 +62,24 @@ class ModrinthResourcePackModel : public ResourcePackResourceModel { auto documentToArray(QJsonDocument& obj) const -> QJsonArray override; }; +class ModrinthTexturePackModel : public TexturePackResourceModel { + Q_OBJECT + + public: + ModrinthTexturePackModel(const BaseInstance&); + ~ModrinthTexturePackModel() override = default; + + private: + [[nodiscard]] QString debugName() const override { return Modrinth::debugName() + " (Model)"; } + [[nodiscard]] QString metaEntryBase() const override { return Modrinth::metaEntryBase(); } + + void loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj) override; + void loadExtraPackInfo(ModPlatform::IndexedPack& m, QJsonObject& obj) override; + void loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr) override; + + auto documentToArray(QJsonDocument& obj) const -> QJsonArray override; +}; + class ModrinthShaderPackModel : public ShaderPackResourceModel { Q_OBJECT diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.cpp index 2826b5d3..dd143700 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.cpp +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.cpp @@ -100,6 +100,24 @@ ModrinthResourcePackPage::ModrinthResourcePackPage(ResourcePackDownloadDialog* d m_ui->packDescription->setMetaEntry(metaEntryBase()); } +ModrinthTexturePackPage::ModrinthTexturePackPage(TexturePackDownloadDialog* dialog, BaseInstance& instance) + : TexturePackResourcePage(dialog, instance) +{ + m_model = new ModrinthTexturePackModel(instance); + m_ui->packView->setModel(m_model); + + addSortings(); + + // sometimes Qt just ignores virtual slots and doesn't work as intended it seems, + // so it's best not to connect them in the parent's constructor... + connect(m_ui->sortByBox, SIGNAL(currentIndexChanged(int)), this, SLOT(triggerSearch())); + connect(m_ui->packView->selectionModel(), &QItemSelectionModel::currentChanged, this, &ModrinthTexturePackPage::onSelectionChanged); + connect(m_ui->versionSelectionBox, &QComboBox::currentTextChanged, this, &ModrinthTexturePackPage::onVersionSelectionChanged); + connect(m_ui->resourceSelectionButton, &QPushButton::clicked, this, &ModrinthTexturePackPage::onResourceSelected); + + m_ui->packDescription->setMetaEntry(metaEntryBase()); +} + ModrinthShaderPackPage::ModrinthShaderPackPage(ShaderPackDownloadDialog* dialog, BaseInstance& instance) : ShaderPackResourcePage(dialog, instance) { @@ -123,6 +141,7 @@ ModrinthShaderPackPage::ModrinthShaderPackPage(ShaderPackDownloadDialog* dialog, // my Qt, so we need to implement this in every derived class... auto ModrinthModPage::shouldDisplay() const -> bool { return true; } auto ModrinthResourcePackPage::shouldDisplay() const -> bool { return true; } +auto ModrinthTexturePackPage::shouldDisplay() const -> bool { return true; } auto ModrinthShaderPackPage::shouldDisplay() const -> bool { return true; } } // namespace ResourceDownload diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.h b/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.h index 8733a1b2..f4eb5ce0 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.h +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.h @@ -43,6 +43,7 @@ #include "ui/pages/modplatform/ModPage.h" #include "ui/pages/modplatform/ResourcePackPage.h" +#include "ui/pages/modplatform/TexturePackPage.h" #include "ui/pages/modplatform/ShaderPackPage.h" namespace ResourceDownload { @@ -103,6 +104,29 @@ class ModrinthResourcePackPage : public ResourcePackResourcePage { [[nodiscard]] inline auto helpPage() const -> QString override { return ""; } }; +class ModrinthTexturePackPage : public TexturePackResourcePage { + Q_OBJECT + + public: + static ModrinthTexturePackPage* create(TexturePackDownloadDialog* dialog, BaseInstance& instance) + { + return TexturePackResourcePage::create(dialog, instance); + } + + ModrinthTexturePackPage(TexturePackDownloadDialog* dialog, BaseInstance& instance); + ~ModrinthTexturePackPage() override = default; + + [[nodiscard]] bool shouldDisplay() const override; + + [[nodiscard]] inline auto displayName() const -> QString override { return Modrinth::displayName(); } + [[nodiscard]] inline auto icon() const -> QIcon override { return Modrinth::icon(); } + [[nodiscard]] inline auto id() const -> QString override { return Modrinth::id(); } + [[nodiscard]] inline auto debugName() const -> QString override { return Modrinth::debugName(); } + [[nodiscard]] inline auto metaEntryBase() const -> QString override { return Modrinth::metaEntryBase(); } + + [[nodiscard]] inline auto helpPage() const -> QString override { return ""; } +}; + class ModrinthShaderPackPage : public ShaderPackResourcePage { Q_OBJECT -- cgit From c17f6e37e5e32138f778cedf66c80e20f1eacd2e Mon Sep 17 00:00:00 2001 From: Janrupf Date: Mon, 13 Mar 2023 16:37:45 +0100 Subject: feat: Add setting for downloads directory Closes #641 Signed-off-by: Janrupf --- launcher/Application.cpp | 1 + launcher/ui/dialogs/BlockedModsDialog.cpp | 2 +- launcher/ui/pages/global/LauncherPage.cpp | 18 ++++++++- launcher/ui/pages/global/LauncherPage.h | 1 + launcher/ui/pages/global/LauncherPage.ui | 67 +++++++++++++++++++------------ 5 files changed, 61 insertions(+), 28 deletions(-) (limited to 'launcher/ui/dialogs') diff --git a/launcher/Application.cpp b/launcher/Application.cpp index 321f944b..879af535 100644 --- a/launcher/Application.cpp +++ b/launcher/Application.cpp @@ -516,6 +516,7 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv) m_settings->registerSetting("InstanceDir", "instances"); m_settings->registerSetting({"CentralModsDir", "ModsDir"}, "mods"); m_settings->registerSetting("IconsDir", "icons"); + m_settings->registerSetting("DownloadsDir", QStandardPaths::writableLocation(QStandardPaths::DownloadLocation)); // Editors m_settings->registerSetting("JsonEditor", QString()); diff --git a/launcher/ui/dialogs/BlockedModsDialog.cpp b/launcher/ui/dialogs/BlockedModsDialog.cpp index eb427953..ff885f10 100644 --- a/launcher/ui/dialogs/BlockedModsDialog.cpp +++ b/launcher/ui/dialogs/BlockedModsDialog.cpp @@ -184,7 +184,7 @@ void BlockedModsDialog::directoryChanged(QString path) /// @brief add the user downloads folder and the global mods folder to the filesystem watcher void BlockedModsDialog::setupWatch() { - const QString downloadsFolder = QStandardPaths::writableLocation(QStandardPaths::DownloadLocation); + const QString downloadsFolder = APPLICATION->settings()->get("DownloadsDir").toString(); const QString modsFolder = APPLICATION->settings()->get("CentralModsDir").toString(); m_watcher.addPath(downloadsFolder); m_watcher.addPath(modsFolder); diff --git a/launcher/ui/pages/global/LauncherPage.cpp b/launcher/ui/pages/global/LauncherPage.cpp index a9f44c0b..324eb461 100644 --- a/launcher/ui/pages/global/LauncherPage.cpp +++ b/launcher/ui/pages/global/LauncherPage.cpp @@ -140,8 +140,8 @@ void LauncherPage::on_instDirBrowseBtn_clicked() if (result == QMessageBox::Ok) { ui->instDirTextBox->setText(cooked_dir); - } - } + } + } else { ui->instDirTextBox->setText(cooked_dir); @@ -160,6 +160,7 @@ void LauncherPage::on_iconsDirBrowseBtn_clicked() ui->iconsDirTextBox->setText(cooked_dir); } } + void LauncherPage::on_modsDirBrowseBtn_clicked() { QString raw_dir = QFileDialog::getExistingDirectory(this, tr("Mods Folder"), ui->modsDirTextBox->text()); @@ -172,6 +173,17 @@ void LauncherPage::on_modsDirBrowseBtn_clicked() } } +void LauncherPage::on_downloadsDirBrowseBtn_clicked() +{ + QString raw_dir = QFileDialog::getExistingDirectory(this, tr("Downloads Folder"), ui->downloadsDirTextBox->text()); + + if (!raw_dir.isEmpty() && QDir(raw_dir).exists()) + { + QString cooked_dir = FS::NormalizePath(raw_dir); + ui->downloadsDirTextBox->setText(cooked_dir); + } +} + void LauncherPage::on_metadataDisableBtn_clicked() { ui->metadataWarningLabel->setHidden(!ui->metadataDisableBtn->isChecked()); @@ -204,6 +216,7 @@ void LauncherPage::applySettings() s->set("InstanceDir", ui->instDirTextBox->text()); s->set("CentralModsDir", ui->modsDirTextBox->text()); s->set("IconsDir", ui->iconsDirTextBox->text()); + s->set("DownloadsDir", ui->downloadsDirTextBox->text()); auto sortMode = (InstSortMode)ui->sortingModeGroup->checkedId(); switch (sortMode) @@ -260,6 +273,7 @@ void LauncherPage::loadSettings() ui->instDirTextBox->setText(s->get("InstanceDir").toString()); ui->modsDirTextBox->setText(s->get("CentralModsDir").toString()); ui->iconsDirTextBox->setText(s->get("IconsDir").toString()); + ui->downloadsDirTextBox->setText(s->get("DownloadsDir").toString()); QString sortMode = s->get("InstSortMode").toString(); diff --git a/launcher/ui/pages/global/LauncherPage.h b/launcher/ui/pages/global/LauncherPage.h index c60156c2..33f66f1b 100644 --- a/launcher/ui/pages/global/LauncherPage.h +++ b/launcher/ui/pages/global/LauncherPage.h @@ -88,6 +88,7 @@ slots: void on_instDirBrowseBtn_clicked(); void on_modsDirBrowseBtn_clicked(); void on_iconsDirBrowseBtn_clicked(); + void on_downloadsDirBrowseBtn_clicked(); void on_metadataDisableBtn_clicked(); /*! diff --git a/launcher/ui/pages/global/LauncherPage.ui b/launcher/ui/pages/global/LauncherPage.ui index f084d970..1fe55678 100644 --- a/launcher/ui/pages/global/LauncherPage.ui +++ b/launcher/ui/pages/global/LauncherPage.ui @@ -38,7 +38,7 @@ QTabWidget::Rounded - 1 + 0 @@ -67,20 +67,32 @@ Folders - - + + ... - - + + + + + + + + - ... + I&nstances: + + + instDirTextBox + + + @@ -88,8 +100,22 @@ - - + + + + ... + + + + + + + &Mods: + + + modsDirTextBox + + @@ -101,29 +127,20 @@ - - - - - + + - I&nstances: - - - instDirTextBox + Downloads: - - + + - - + + - &Mods: - - - modsDirTextBox + ... -- cgit From f794e49bb6eadd70c52683e60a700a1d7e9cd17b Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Mon, 6 Feb 2023 23:05:06 -0800 Subject: we want to make links! Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- .gitignore | 2 + launcher/CMakeLists.txt | 52 ++++++ launcher/FileSystem.cpp | 87 ++++++++- launcher/FileSystem.h | 68 ++++++- launcher/InstanceCopyPrefs.cpp | 30 +++ launcher/InstanceCopyPrefs.h | 9 + launcher/filelink/FileLink.cpp | 119 ++++++++++++ launcher/filelink/FileLink.h | 52 ++++++ launcher/filelink/filelink.exe.manifest | 28 +++ launcher/filelink/main.cpp | 31 ++++ launcher/ui/dialogs/CopyInstanceDialog.cpp | 19 ++ launcher/ui/dialogs/CopyInstanceDialog.h | 3 + launcher/ui/dialogs/CopyInstanceDialog.ui | 200 +++++++++++++------- tests/FileSystem_test.cpp | 289 +++++++++++++++++++++++++++++ 14 files changed, 918 insertions(+), 71 deletions(-) create mode 100644 launcher/filelink/FileLink.cpp create mode 100644 launcher/filelink/FileLink.h create mode 100644 launcher/filelink/filelink.exe.manifest create mode 100644 launcher/filelink/main.cpp (limited to 'launcher/ui/dialogs') diff --git a/.gitignore b/.gitignore index 3340670b..b3e2ee7d 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,8 @@ html/ CMakeLists.txt.user CMakeLists.txt.user.* CMakeSettings.json +/CMakeFiles +CMakeCache.txt /.project /.settings /.idea diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index 074570e3..2216aa1b 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -559,6 +559,11 @@ set(ATLAUNCHER_SOURCES modplatform/atlauncher/ATLShareCode.h ) +set(LINKDAEMON_SOURCES + filelink/FileLink.h + filelink/FileLink.cpp +) + ######## Logging categories ######## ecm_qt_declare_logging_category(CORE_SOURCES @@ -1107,6 +1112,53 @@ install(TARGETS ${Launcher_Name} FRAMEWORK DESTINATION ${FRAMEWORK_DEST_DIR} COMPONENT Runtime ) +if(WIN32) + add_library(filelink_logic STATIC ${LINKDAEMON_SOURCES}) + target_include_directories(filelink_logic PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) + target_link_libraries(filelink_logic + systeminfo + BuildConfig + Qt${QT_VERSION_MAJOR}::Widgets + ghcFilesystem::ghc_filesystem + ) + target_link_libraries(filelink_logic + Qt${QT_VERSION_MAJOR}::Core + Qt${QT_VERSION_MAJOR}::Xml + Qt${QT_VERSION_MAJOR}::Network + Qt${QT_VERSION_MAJOR}::Concurrent + Qt${QT_VERSION_MAJOR}::Gui + Qt${QT_VERSION_MAJOR}::Widgets + ${Launcher_QT_LIBS} + ) + + add_executable("${Launcher_Name}_filelink" WIN32 filelink/main.cpp) + + target_sources("${Launcher_Name}_filelink" PRIVATE filelink/filelink.exe.manifest) + + target_link_libraries("${Launcher_Name}_filelink" filelink_logic) + + if(DEFINED Launcher_APP_BINARY_NAME) + set_target_properties("${Launcher_Name}_filelink" PROPERTIES OUTPUT_NAME "${Launcher_APP_BINARY_NAME}_filelink") + endif() + if(DEFINED Launcher_BINARY_RPATH) + SET_TARGET_PROPERTIES("${Launcher_Name}_filelink" PROPERTIES INSTALL_RPATH "${Launcher_BINARY_RPATH}") + endif() + + if(CMAKE_GENERATOR MATCHES "Visual Studio") + SET_TARGET_PROPERTIES("${Launcher_Name}_filelink" PROPERTIES LINK_FLAGS "/level='requireAdministrator' /uiAccess='false' /SUBSYSTEM:CONSOLE") + else() + SET_TARGET_PROPERTIES("${Launcher_Name}_filelink" PROPERTIES LINK_FLAGS "/MANIFESTUAC:\"level='requireAdministrator' uiAccess='false'\" /SUBSYSTEM:CONSOLE") + endif() + + + install(TARGETS "${Launcher_Name}_filelink" + BUNDLE DESTINATION "." COMPONENT Runtime + LIBRARY DESTINATION ${LIBRARY_DEST_DIR} COMPONENT Runtime + RUNTIME DESTINATION ${BINARY_DEST_DIR} COMPONENT Runtime + FRAMEWORK DESTINATION ${FRAMEWORK_DEST_DIR} COMPONENT Runtime + ) +endif() + if (UNIX AND APPLE) # Add Sparkle updater # It has to be copied here instead of just allowing fixup_bundle to install it, otherwise essential parts of diff --git a/launcher/FileSystem.cpp b/launcher/FileSystem.cpp index aee5245d..ec4af98c 100644 --- a/launcher/FileSystem.cpp +++ b/launcher/FileSystem.cpp @@ -152,9 +152,11 @@ bool ensureFolderPathExists(QString foldernamepath) return success; } -/// @brief Copies a directory and it's contents from src to dest -/// @param offset subdirectory form src to copy to dest -/// @return if there was an error during the filecopy +/** + * @brief Copies a directory and it's contents from src to dest + * @param offset subdirectory form src to copy to dest + * @return if there was an error during the filecopy + */ bool copy::operator()(const QString& offset, bool dryRun) { using copy_opts = fs::copy_options; @@ -215,6 +217,85 @@ bool copy::operator()(const QString& offset, bool dryRun) return err.value() == 0; } + +/** + * @brief links a directory and it's contents from src to dest + * @param offset subdirectory form src to link to dest + * @return if there was an error during the attempt to link + */ +bool create_link::operator()(const QString& offset, bool dryRun) +{ + m_linked = 0; // reset counter + + auto src = PathCombine(m_src.absolutePath(), offset); + auto dst = PathCombine(m_dst.absolutePath(), offset); + + std::error_code err; + + // you can't hard link a directory so make sure if we deal with a directory we do so recursively + if (m_useHardLinks) + m_recursive = true; + + // Function that'll do the actual linking + auto link_file = [&](QString src_path, QString relative_dst_path) { + if (m_matcher && (m_matcher->matches(relative_dst_path) != m_whitelist)) + return; + + auto dst_path = PathCombine(dst, relative_dst_path); + if (!dryRun) { + + ensureFilePathExists(dst_path); + if (m_useHardLinks) { + if (m_debug) + qDebug() << "making hard link:" << src_path << "to" << dst_path; + fs::create_hard_link(StringUtils::toStdString(src_path), StringUtils::toStdString(dst_path), err); + } else if (fs::is_directory(StringUtils::toStdString(src_path))) { + if (m_debug) + qDebug() << "making directory_symlink:" << src_path << "to" << dst_path; + fs::create_directory_symlink(StringUtils::toStdString(src_path), StringUtils::toStdString(dst_path), err); + } else { + if (m_debug) + qDebug() << "making symlink:" << src_path << "to" << dst_path; + fs::create_symlink(StringUtils::toStdString(src_path), StringUtils::toStdString(dst_path), err); + } + + } + if (err) { + qWarning() << "Failed to link files:" << QString::fromStdString(err.message()); + qDebug() << "Source file:" << src_path; + qDebug() << "Destination file:" << dst_path; + qDebug() << "Error catagory:" << err.category().name(); + qDebug() << "Error code:" << err.value(); + m_last_os_err = err.value(); + emit linkFailed(src_path, dst_path, err); + } else { + m_linked++; + emit fileLinked(relative_dst_path); + } + + }; + + if ((!m_recursive) || !fs::is_directory(StringUtils::toStdString(src))) { + if (m_debug) + qDebug() << "linking single file or dir:" << src << "to" << dst; + link_file(src, ""); + } else { + if (m_debug) + qDebug() << "linking recursivly:" << src << "to" << dst; + QDir src_dir(src); + QDirIterator source_it(src, QDir::Filter::Files | QDir::Filter::Hidden, QDirIterator::Subdirectories); + + while (source_it.hasNext()) { + auto src_path = source_it.next(); + auto relative_path = src_dir.relativeFilePath(src_path); + + link_file(src_path, relative_path); + } + } + + return err.value() == 0; +} + bool move(const QString& source, const QString& dest) { std::error_code err; diff --git a/launcher/FileSystem.h b/launcher/FileSystem.h index f083f3c7..98f55f96 100644 --- a/launcher/FileSystem.h +++ b/launcher/FileSystem.h @@ -77,7 +77,9 @@ bool ensureFilePathExists(QString filenamepath); */ bool ensureFolderPathExists(QString filenamepath); -/// @brief Copies a directory and it's contents from src to dest +/** + * @brief Copies a directory and it's contents from src to dest + */ class copy : public QObject { Q_OBJECT public: @@ -122,6 +124,70 @@ class copy : public QObject { int m_copied; }; +/** + * @brief Copies a directory and it's contents from src to dest + */ +class create_link : public QObject { + Q_OBJECT + public: + create_link(const QString& src, const QString& dst, QObject* parent = nullptr) : QObject(parent) + { + m_src.setPath(src); + m_dst.setPath(dst); + } + create_link& useHardLinks(const bool useHard) + { + m_useHardLinks = useHard; + return *this; + } + create_link& matcher(const IPathMatcher* filter) + { + m_matcher = filter; + return *this; + } + create_link& whitelist(bool whitelist) + { + m_whitelist = whitelist; + return *this; + } + create_link& linkRecursively(bool recursive) + { + m_recursive = recursive; + return *this; + } + create_link& debug(bool d) + { + m_debug = d; + return *this; + } + + int getLastOSError() { + return m_last_os_err; + } + + bool operator()(bool dryRun = false) { return operator()(QString(), dryRun); } + + int totalLinked() { return m_linked; } + + signals: + void fileLinked(const QString& relativeName); + void linkFailed(const QString& srcName, const QString& dstName, std::error_code err); + + private: + bool operator()(const QString& offset, bool dryRun = false); + + private: + bool m_useHardLinks = false; + const IPathMatcher* m_matcher = nullptr; + bool m_whitelist = false; + bool m_recursive = true; + QDir m_src; + QDir m_dst; + int m_linked; + bool m_debug = false; + int m_last_os_err = 0; +}; + /** * @brief moves a file by renaming it * @param source source file path diff --git a/launcher/InstanceCopyPrefs.cpp b/launcher/InstanceCopyPrefs.cpp index 7b93a516..18a6d704 100644 --- a/launcher/InstanceCopyPrefs.cpp +++ b/launcher/InstanceCopyPrefs.cpp @@ -93,6 +93,21 @@ bool InstanceCopyPrefs::isCopyScreenshotsEnabled() const return copyScreenshots; } +bool InstanceCopyPrefs::isLinkFilesEnabled() const +{ + return linkFiles; +} + +bool InstanceCopyPrefs::isUseHardLinksEnabled() const +{ + return useHardLinks; +} + +bool InstanceCopyPrefs::isLinkWorldsEnabled() const +{ + return linkWorlds; +} + // ======= Setters ======= void InstanceCopyPrefs::enableCopySaves(bool b) { @@ -133,3 +148,18 @@ void InstanceCopyPrefs::enableCopyScreenshots(bool b) { copyScreenshots = b; } + +void InstanceCopyPrefs::enableLinkFiles(bool b) +{ + linkFiles = b; +} + +void InstanceCopyPrefs::enableUseHardLinks(bool b) +{ + useHardLinks = b; +} + +void InstanceCopyPrefs::enableLinkWorlds(bool b) +{ + linkWorlds = b; +} diff --git a/launcher/InstanceCopyPrefs.h b/launcher/InstanceCopyPrefs.h index 6988b2df..25c0f3fc 100644 --- a/launcher/InstanceCopyPrefs.h +++ b/launcher/InstanceCopyPrefs.h @@ -19,6 +19,9 @@ struct InstanceCopyPrefs { [[nodiscard]] bool isCopyServersEnabled() const; [[nodiscard]] bool isCopyModsEnabled() const; [[nodiscard]] bool isCopyScreenshotsEnabled() const; + [[nodiscard]] bool isLinkFilesEnabled() const; + [[nodiscard]] bool isUseHardLinksEnabled() const; + [[nodiscard]] bool isLinkWorldsEnabled() const; // Setters void enableCopySaves(bool b); void enableKeepPlaytime(bool b); @@ -28,6 +31,9 @@ struct InstanceCopyPrefs { void enableCopyServers(bool b); void enableCopyMods(bool b); void enableCopyScreenshots(bool b); + void enableLinkFiles(bool b); + void enableUseHardLinks(bool b); + void enableLinkWorlds(bool b); protected: // data bool copySaves = true; @@ -38,4 +44,7 @@ struct InstanceCopyPrefs { bool copyServers = true; bool copyMods = true; bool copyScreenshots = true; + bool linkFiles = false; + bool useHardLinks = false; + bool linkWorlds = true; }; diff --git a/launcher/filelink/FileLink.cpp b/launcher/filelink/FileLink.cpp new file mode 100644 index 00000000..9b5589ab --- /dev/null +++ b/launcher/filelink/FileLink.cpp @@ -0,0 +1,119 @@ +// SPDX-FileCopyrightText: 2022 Rachel Powers <508861+Ryex@users.noreply.github.com> +// +// SPDX-License-Identifier: GPL-3.0-only + +/* + * Prism Launcher - Minecraft Launcher + * Copyright (C) 2022 Rachel Powers <508861+Ryex@users.noreply.github.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#include "FileLink.h" +#include "BuildConfig.h" + + +#include + +#include +#include + +#include + + +#include +#include + +#include + +#if defined Q_OS_WIN32 +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#include +#include +#endif + + + + +FileLinkApp::FileLinkApp(int &argc, char **argv) : QCoreApplication(argc, argv) +{ +#if defined Q_OS_WIN32 + // attach the parent console + if(AttachConsole(ATTACH_PARENT_PROCESS)) + { + // if attach succeeds, reopen and sync all the i/o + if(freopen("CON", "w", stdout)) + { + std::cout.sync_with_stdio(); + } + if(freopen("CON", "w", stderr)) + { + std::cerr.sync_with_stdio(); + } + if(freopen("CON", "r", stdin)) + { + std::cin.sync_with_stdio(); + } + auto out = GetStdHandle (STD_OUTPUT_HANDLE); + DWORD written; + const char * endline = "\n"; + WriteConsole(out, endline, strlen(endline), &written, NULL); + consoleAttached = true; + } +#endif + setOrganizationName(BuildConfig.LAUNCHER_NAME); + setOrganizationDomain(BuildConfig.LAUNCHER_DOMAIN); + setApplicationName(BuildConfig.LAUNCHER_NAME + "FileLink"); + setApplicationVersion(BuildConfig.printableVersionString() + "\n" + BuildConfig.GIT_COMMIT); + + // Commandline parsing + QCommandLineParser parser; + parser.setApplicationDescription(QObject::tr("a batch MKLINK program for windows to be useed with prismlauncher")); + + parser.addOptions({ + + }); + parser.addHelpOption(); + parser.addVersionOption(); + + parser.process(arguments()); + + qDebug() << "link program launched"; + +} + + +FileLinkApp::~FileLinkApp() +{ + qDebug() << "link program shutting down"; + // Shut down logger by setting the logger function to nothing + qInstallMessageHandler(nullptr); + +#if defined Q_OS_WIN32 + // Detach from Windows console + if(consoleAttached) + { + fclose(stdout); + fclose(stdin); + fclose(stderr); + FreeConsole(); + } +#endif +} + + + + diff --git a/launcher/filelink/FileLink.h b/launcher/filelink/FileLink.h new file mode 100644 index 00000000..253d1394 --- /dev/null +++ b/launcher/filelink/FileLink.h @@ -0,0 +1,52 @@ +// SPDX-FileCopyrightText: 2022 Rachel Powers <508861+Ryex@users.noreply.github.com> +// +// SPDX-License-Identifier: GPL-3.0-only + +/* + * Prism Launcher - Minecraft Launcher + * Copyright (C) 2022 Rachel Powers <508861+Ryex@users.noreply.github.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#pragma once + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +class FileLinkApp : public QCoreApplication +{ + // friends for the purpose of limiting access to deprecated stuff + Q_OBJECT +public: + + FileLinkApp(int &argc, char **argv); + virtual ~FileLinkApp(); + +private: + QDateTime m_startTime; + +#if defined Q_OS_WIN32 + // used on Windows to attach the standard IO streams + bool consoleAttached = false; +#endif +}; diff --git a/launcher/filelink/filelink.exe.manifest b/launcher/filelink/filelink.exe.manifest new file mode 100644 index 00000000..a4e16264 --- /dev/null +++ b/launcher/filelink/filelink.exe.manifest @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/launcher/filelink/main.cpp b/launcher/filelink/main.cpp new file mode 100644 index 00000000..7f06795e --- /dev/null +++ b/launcher/filelink/main.cpp @@ -0,0 +1,31 @@ +// SPDX-FileCopyrightText: 2022 Rachel Powers <508861+Ryex@users.noreply.github.com> +// +// SPDX-License-Identifier: GPL-3.0-only + +/* + * Prism Launcher - Minecraft Launcher + * Copyright (C) 2022 Rachel Powers <508861+Ryex@users.noreply.github.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#include "FileLink.h" + +int main(int argc, char *argv[]) +{ + + FileLinkApp ldh(argc, argv); + + return ldh.exec(); +} \ No newline at end of file diff --git a/launcher/ui/dialogs/CopyInstanceDialog.cpp b/launcher/ui/dialogs/CopyInstanceDialog.cpp index 3f5122f6..981352ae 100644 --- a/launcher/ui/dialogs/CopyInstanceDialog.cpp +++ b/launcher/ui/dialogs/CopyInstanceDialog.cpp @@ -85,6 +85,10 @@ CopyInstanceDialog::CopyInstanceDialog(InstancePtr original, QWidget *parent) ui->copyServersCheckbox->setChecked(m_selectedOptions.isCopyServersEnabled()); ui->copyModsCheckbox->setChecked(m_selectedOptions.isCopyModsEnabled()); ui->copyScreenshotsCheckbox->setChecked(m_selectedOptions.isCopyScreenshotsEnabled()); + + ui->linkFilesGroup->setChecked(m_selectedOptions.isLinkFilesEnabled()); + ui->hardLinksCheckbox->setChecked(m_selectedOptions.isUseHardLinksEnabled()); + ui->linkWorldsCheckbox->setChecked(m_selectedOptions.isLinkWorldsEnabled()); } CopyInstanceDialog::~CopyInstanceDialog() @@ -220,3 +224,18 @@ void CopyInstanceDialog::on_copyScreenshotsCheckbox_stateChanged(int state) m_selectedOptions.enableCopyScreenshots(state == Qt::Checked); updateSelectAllCheckbox(); } + +void CopyInstanceDialog::on_linkFilesGroup_toggled(bool checked) +{ + m_selectedOptions.enableLinkFiles(checked); +} + +void CopyInstanceDialog::on_hardLinksCheckbox_stateChanged(int state) +{ + m_selectedOptions.enableUseHardLinks(state == Qt::Checked); +} + +void CopyInstanceDialog::on_linkWorldsCheckbox_stateChanged(int state) +{ + m_selectedOptions.enableLinkWorlds(state == Qt::Checked); +} diff --git a/launcher/ui/dialogs/CopyInstanceDialog.h b/launcher/ui/dialogs/CopyInstanceDialog.h index 884501d1..a80faab9 100644 --- a/launcher/ui/dialogs/CopyInstanceDialog.h +++ b/launcher/ui/dialogs/CopyInstanceDialog.h @@ -55,6 +55,9 @@ slots: void on_copyServersCheckbox_stateChanged(int state); void on_copyModsCheckbox_stateChanged(int state); void on_copyScreenshotsCheckbox_stateChanged(int state); + void on_linkFilesGroup_toggled(bool checked); + void on_hardLinksCheckbox_stateChanged(int state); + void on_linkWorldsCheckbox_stateChanged(int state); private: void checkAllCheckboxes(const bool& b); diff --git a/launcher/ui/dialogs/CopyInstanceDialog.ui b/launcher/ui/dialogs/CopyInstanceDialog.ui index b7828fe3..e41ad526 100644 --- a/launcher/ui/dialogs/CopyInstanceDialog.ui +++ b/launcher/ui/dialogs/CopyInstanceDialog.ui @@ -9,8 +9,8 @@ 0 0 - 341 - 399 + 525 + 581 @@ -136,70 +136,126 @@ - - - - - Disabling this will still keep the mod loader (ex: Fabric, Quilt, etc.) but erase the mods folder and their configs. - - - Copy mods - - - - - + + + Instance copy options + + + + + + Disabling this will still keep the mod loader (ex: Fabric, Quilt, etc.) but erase the mods folder and their configs. + + + Copy mods + + + + + + + Copy the in-game options like FOV, max framerate, etc. + + + Copy game options + + + + + + + Copy saves + + + + + + + Copy shader packs + + + + + + + Copy servers + + + + + + + true + + + Copy resource packs + + + + + + + Keep play time + + + + + + + Copy screenshots + + + + + + + + + + - Copy the in-game options like FOV, max framerate, etc. + Use symbolic links instead of copying files. - - Copy game options + + Link files instead of copying them - - - - - - Copy saves - - - - - - - Copy shader packs - - - - - - - Copy servers + + false - - - - - + true - - Copy resource packs - - - - - - - Keep play time - - - - - - - Copy screenshots + + false + + + + + Use hard links instead of symbolic links + + + Use hard links + + + + + + + World save data will be linked and thus shared between instances. + + + Link worlds + + + true + + + false + + + + @@ -210,7 +266,7 @@ Qt::Horizontal - QDialogButtonBox::Cancel|QDialogButtonBox::Ok + QDialogButtonBox::Cancel|QDialogButtonBox::Help|QDialogButtonBox::Ok @@ -220,10 +276,20 @@ iconButton instNameTextBox groupBox + selectAllCheckbox + keepPlaytimeCheckbox + copyScreenshotsCheckbox + copySavesCheckbox + copyShaderPacksCheckbox + copyGameOptionsCheckbox + copyServersCheckbox + copyResPacksCheckbox + copyModsCheckbox + linkFilesGroup + hardLinksCheckbox + linkWorldsCheckbox - - - + buttonBox @@ -232,8 +298,8 @@ accept() - 254 - 316 + 263 + 571 157 @@ -248,8 +314,8 @@ reject() - 322 - 316 + 331 + 571 286 diff --git a/tests/FileSystem_test.cpp b/tests/FileSystem_test.cpp index 3a5c38d0..ce83aa49 100644 --- a/tests/FileSystem_test.cpp +++ b/tests/FileSystem_test.cpp @@ -248,6 +248,295 @@ slots: { QCOMPARE(FS::getDesktopDir(), QStandardPaths::writableLocation(QStandardPaths::DesktopLocation)); } + + + void test_link() + { + QString folder = QFINDTESTDATA("testdata/FileSystem/test_folder"); + auto f = [&folder]() + { + QTemporaryDir tempDir; + tempDir.setAutoRemove(true); + qDebug() << "From:" << folder << "To:" << tempDir.path(); + + QDir target_dir(FS::PathCombine(tempDir.path(), "test_folder")); + qDebug() << tempDir.path(); + qDebug() << target_dir.path(); + FS::create_link lnk(folder, target_dir.path()); + lnk.linkRecursively(false); + lnk.debug(true); + if(!lnk()){ +#if defined Q_OS_WIN32 + qDebug() << "EXPECTED: Link failure, Windows requires permissions for symlinks"; + QVERIFY(lnk.getLastOSError() == 1314); + return; +#endif + qDebug() << "Link Failed!" << lnk.getLastOSError(); + } + + for(auto entry: target_dir.entryList()) + { + qDebug() << entry; + QFileInfo entry_lnk_info(target_dir.filePath(entry)); + QVERIFY(!entry_lnk_info.isSymbolicLink()); + } + + QFileInfo lnk_info(target_dir.path()); + QVERIFY(lnk_info.exists()); + QVERIFY(lnk_info.isSymbolicLink()); + + QVERIFY(target_dir.entryList().contains("pack.mcmeta")); + QVERIFY(target_dir.entryList().contains("assets")); + }; + + // first try variant without trailing / + QVERIFY(!folder.endsWith('/')); + f(); + + // then variant with trailing / + folder.append('/'); + QVERIFY(folder.endsWith('/')); + f(); + } + + void test_hard_link() + { + QString folder = QFINDTESTDATA("testdata/FileSystem/test_folder"); + auto f = [&folder]() + { + QTemporaryDir tempDir; + tempDir.setAutoRemove(true); + qDebug() << "From:" << folder << "To:" << tempDir.path(); + + QDir target_dir(FS::PathCombine(tempDir.path(), "test_folder")); + qDebug() << tempDir.path(); + qDebug() << target_dir.path(); + FS::create_link lnk(folder, target_dir.path()); + lnk.useHardLinks(true); + lnk.debug(true); + if(!lnk()){ + qDebug() << "Link Failed!" << lnk.getLastOSError(); + } + + for(auto entry: target_dir.entryList()) + { + qDebug() << entry; + QFileInfo entry_lnk_info(target_dir.filePath(entry)); + QVERIFY(!entry_lnk_info.isSymbolicLink()); + QFileInfo entry_orig_info(QDir(folder).filePath(entry)); + if (!entry_lnk_info.isDir()) { + qDebug() << "hard link equivalency?" << entry_lnk_info.absoluteFilePath() << "vs" << entry_orig_info.absoluteFilePath(); + QVERIFY(std::filesystem::equivalent(entry_lnk_info.filesystemAbsoluteFilePath(), entry_orig_info.filesystemAbsoluteFilePath())); + } + } + + QFileInfo lnk_info(target_dir.path()); + QVERIFY(lnk_info.exists()); + QVERIFY(!lnk_info.isSymbolicLink()); + + QVERIFY(target_dir.entryList().contains("pack.mcmeta")); + QVERIFY(target_dir.entryList().contains("assets")); + }; + + // first try variant without trailing / + QVERIFY(!folder.endsWith('/')); + f(); + + // then variant with trailing / + folder.append('/'); + QVERIFY(folder.endsWith('/')); + f(); + } + + void test_link_with_blacklist() + { + QString folder = QFINDTESTDATA("testdata/FileSystem/test_folder"); + auto f = [&folder]() + { + QTemporaryDir tempDir; + tempDir.setAutoRemove(true); + qDebug() << "From:" << folder << "To:" << tempDir.path(); + + QDir target_dir(FS::PathCombine(tempDir.path(), "test_folder")); + qDebug() << tempDir.path(); + qDebug() << target_dir.path(); + FS::create_link lnk(folder, target_dir.path()); + lnk.matcher(new RegexpMatcher("[.]?mcmeta")); + lnk.linkRecursively(true); + lnk.debug(true); + if(!lnk()){ +#if defined Q_OS_WIN32 + qDebug() << "EXPECTED: Link failure, Windows requires permissions for symlinks"; + QVERIFY(lnk.getLastOSError() == 1314); + return; +#endif + qDebug() << "Link Failed!" << lnk.getLastOSError(); + } + + for(auto entry: target_dir.entryList()) + { + qDebug() << entry; + QFileInfo entry_lnk_info(target_dir.filePath(entry)); + QVERIFY(entry_lnk_info.isSymbolicLink()); + } + + QFileInfo lnk_info(target_dir.path()); + QVERIFY(lnk_info.exists()); + QVERIFY(lnk_info.isSymbolicLink()); + + QVERIFY(!target_dir.entryList().contains("pack.mcmeta")); + QVERIFY(target_dir.entryList().contains("assets")); + }; + + // first try variant without trailing / + QVERIFY(!folder.endsWith('/')); + f(); + + // then variant with trailing / + folder.append('/'); + QVERIFY(folder.endsWith('/')); + f(); + } + + void test_link_with_whitelist() + { + QString folder = QFINDTESTDATA("testdata/FileSystem/test_folder"); + auto f = [&folder]() + { + QTemporaryDir tempDir; + tempDir.setAutoRemove(true); + qDebug() << "From:" << folder << "To:" << tempDir.path(); + + QDir target_dir(FS::PathCombine(tempDir.path(), "test_folder")); + qDebug() << tempDir.path(); + qDebug() << target_dir.path(); + FS::create_link lnk(folder, target_dir.path()); + lnk.matcher(new RegexpMatcher("[.]?mcmeta")); + lnk.whitelist(true); + lnk.linkRecursively(true); + lnk.debug(true); + if(!lnk()){ +#if defined Q_OS_WIN32 + qDebug() << "EXPECTED: Link failure, Windows requires permissions for symlinks"; + QVERIFY(lnk.getLastOSError() == 1314); + return; +#endif + qDebug() << "Link Failed!" << lnk.getLastOSError(); + } + + for(auto entry: target_dir.entryList()) + { + qDebug() << entry; + QFileInfo entry_lnk_info(target_dir.filePath(entry)); + QVERIFY(entry_lnk_info.isSymbolicLink()); + } + + QFileInfo lnk_info(target_dir.path()); + QVERIFY(lnk_info.exists()); + QVERIFY(lnk_info.isSymbolicLink()); + + QVERIFY(target_dir.entryList().contains("pack.mcmeta")); + QVERIFY(!target_dir.entryList().contains("assets")); + }; + + // first try variant without trailing / + QVERIFY(!folder.endsWith('/')); + f(); + + // then variant with trailing / + folder.append('/'); + QVERIFY(folder.endsWith('/')); + f(); + } + + void test_link_with_dot_hidden() + { + QString folder = QFINDTESTDATA("testdata/FileSystem/test_folder"); + auto f = [&folder]() + { + QTemporaryDir tempDir; + tempDir.setAutoRemove(true); + qDebug() << "From:" << folder << "To:" << tempDir.path(); + + QDir target_dir(FS::PathCombine(tempDir.path(), "test_folder")); + qDebug() << tempDir.path(); + qDebug() << target_dir.path(); + FS::create_link lnk(folder, target_dir.path()); + lnk.linkRecursively(true); + lnk.debug(true); + if(!lnk()){ +#if defined Q_OS_WIN32 + qDebug() << "EXPECTED: Link failure, Windows requires permissions for symlinks"; + QVERIFY(lnk.getLastOSError() == 1314); + return; +#endif + qDebug() << "Link Failed!" << lnk.getLastOSError(); + } + + auto filter = QDir::Filter::Files | QDir::Filter::Dirs | QDir::Filter::Hidden; + + for (auto entry: target_dir.entryList(filter)) { + qDebug() << entry; + QFileInfo entry_lnk_info(target_dir.filePath(entry)); + QVERIFY(entry_lnk_info.isSymbolicLink()); + } + + QFileInfo lnk_info(target_dir.path()); + QVERIFY(lnk_info.exists()); + QVERIFY(lnk_info.isSymbolicLink()); + + QVERIFY(target_dir.entryList(filter).contains(".secret_folder")); + target_dir.cd(".secret_folder"); + QVERIFY(target_dir.entryList(filter).contains(".secret_file.txt")); + }; + + // first try variant without trailing / + QVERIFY(!folder.endsWith('/')); + f(); + + // then variant with trailing / + folder.append('/'); + QVERIFY(folder.endsWith('/')); + f(); + } + + void test_link_single_file() + { + QTemporaryDir tempDir; + tempDir.setAutoRemove(true); + + { + QString file = QFINDTESTDATA("testdata/FileSystem/test_folder/pack.mcmeta"); + + qDebug() << "From:" << file << "To:" << tempDir.path(); + + QDir target_dir(FS::PathCombine(tempDir.path(), "pack.mcmeta")); + qDebug() << tempDir.path(); + qDebug() << target_dir.path(); + FS::create_link lnk(file, target_dir.filePath("pack.mcmeta")); + lnk.debug(true); + if(!lnk()){ +#if defined Q_OS_WIN32 + qDebug() << "EXPECTED: Link failure, Windows requires permissions for symlinks"; + QVERIFY(lnk.getLastOSError() == 1314); + return; +#endif + qDebug() << "Link Failed!" << lnk.getLastOSError(); + } + + auto filter = QDir::Filter::Files; + + for (auto entry: target_dir.entryList(filter)) { + qDebug() << entry; + } + + QFileInfo lnk_info(target_dir.filePath("pack.mcmeta")); + QVERIFY(lnk_info.exists()); + QVERIFY(lnk_info.isSymbolicLink()); + + QVERIFY(target_dir.entryList(filter).contains("pack.mcmeta")); + } + } }; QTEST_GUILESS_MAIN(FileSystemTest) -- cgit From 59788823785c186af78d8100fce3bdedbed85c80 Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Wed, 8 Feb 2023 14:30:45 -0800 Subject: feat(symlinks&hardlinks): linkup copy dialog Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- launcher/FileSystem.cpp | 5 +- launcher/FileSystem.h | 9 +++- launcher/InstanceCopyPrefs.cpp | 8 ++-- launcher/InstanceCopyPrefs.h | 6 +-- launcher/InstanceCopyTask.cpp | 73 ++++++++++++++++++++++++++++-- launcher/InstanceCopyTask.h | 3 ++ launcher/ui/dialogs/CopyInstanceDialog.cpp | 7 +-- launcher/ui/dialogs/CopyInstanceDialog.h | 2 +- launcher/ui/dialogs/CopyInstanceDialog.ui | 9 ++-- 9 files changed, 96 insertions(+), 26 deletions(-) (limited to 'launcher/ui/dialogs') diff --git a/launcher/FileSystem.cpp b/launcher/FileSystem.cpp index c48a3bba..c94770ee 100644 --- a/launcher/FileSystem.cpp +++ b/launcher/FileSystem.cpp @@ -420,7 +420,7 @@ void create_link::runPrivlaged(const QString& offset) qint64 byteswritten = clientConnection->write(block); bool bytesflushed = clientConnection->flush(); qDebug() << "block flushed" << byteswritten << bytesflushed; - //clientConnection->disconnectFromServer(); + }); qDebug() << "Listening on pipe" << serverName; @@ -437,7 +437,6 @@ void create_link::runPrivlaged(const QString& offset) } - void ExternalLinkFileProcess::runLinkFile() { QString fileLinkExe = PathCombine(QCoreApplication::instance()->applicationDirPath(), BuildConfig.LAUNCHER_APP_BINARY_NAME + "_filelink"); QString params = "-s " + m_server; @@ -463,7 +462,7 @@ void ExternalLinkFileProcess::runLinkFile() { ShExecInfo.lpFile = programNameWin; ShExecInfo.lpParameters = paramsWin; ShExecInfo.lpDirectory = NULL; - ShExecInfo.nShow = SW_NORMAL; + ShExecInfo.nShow = SW_HIDE; ShExecInfo.hInstApp = NULL; ShellExecuteEx(&ShExecInfo); diff --git a/launcher/FileSystem.h b/launcher/FileSystem.h index 2e739298..d79096e6 100644 --- a/launcher/FileSystem.h +++ b/launcher/FileSystem.h @@ -211,16 +211,21 @@ class create_link : public QObject { bool operator()(bool dryRun = false) { return operator()(QString(), dryRun); } + int totalLinked() { return m_linked; } + + void runPrivlaged() { runPrivlaged(QString()); } void runPrivlaged(const QString& offset); - int totalLinked() { return m_linked; } + QList getResults() { return m_path_results; } + signals: void fileLinked(const QString& srcName, const QString& dstName); void linkFailed(const QString& srcName, const QString& dstName, const QString& err_msg, int err_value); - void finishedPrivlaged(bool gotResults); void finished(); + void finishedPrivlaged(bool gotResults); + private: bool operator()(const QString& offset, bool dryRun = false); diff --git a/launcher/InstanceCopyPrefs.cpp b/launcher/InstanceCopyPrefs.cpp index 18a6d704..e363d4c6 100644 --- a/launcher/InstanceCopyPrefs.cpp +++ b/launcher/InstanceCopyPrefs.cpp @@ -103,9 +103,9 @@ bool InstanceCopyPrefs::isUseHardLinksEnabled() const return useHardLinks; } -bool InstanceCopyPrefs::isLinkWorldsEnabled() const +bool InstanceCopyPrefs::isDontLinkSavesEnabled() const { - return linkWorlds; + return dontLinkSaves; } // ======= Setters ======= @@ -159,7 +159,7 @@ void InstanceCopyPrefs::enableUseHardLinks(bool b) useHardLinks = b; } -void InstanceCopyPrefs::enableLinkWorlds(bool b) +void InstanceCopyPrefs::enableDontLinkSaves(bool b) { - linkWorlds = b; + dontLinkSaves = b; } diff --git a/launcher/InstanceCopyPrefs.h b/launcher/InstanceCopyPrefs.h index 25c0f3fc..61719a06 100644 --- a/launcher/InstanceCopyPrefs.h +++ b/launcher/InstanceCopyPrefs.h @@ -21,7 +21,7 @@ struct InstanceCopyPrefs { [[nodiscard]] bool isCopyScreenshotsEnabled() const; [[nodiscard]] bool isLinkFilesEnabled() const; [[nodiscard]] bool isUseHardLinksEnabled() const; - [[nodiscard]] bool isLinkWorldsEnabled() const; + [[nodiscard]] bool isDontLinkSavesEnabled() const; // Setters void enableCopySaves(bool b); void enableKeepPlaytime(bool b); @@ -33,7 +33,7 @@ struct InstanceCopyPrefs { void enableCopyScreenshots(bool b); void enableLinkFiles(bool b); void enableUseHardLinks(bool b); - void enableLinkWorlds(bool b); + void enableDontLinkSaves(bool b); protected: // data bool copySaves = true; @@ -46,5 +46,5 @@ struct InstanceCopyPrefs { bool copyScreenshots = true; bool linkFiles = false; bool useHardLinks = false; - bool linkWorlds = true; + bool dontLinkSaves = false; }; diff --git a/launcher/InstanceCopyTask.cpp b/launcher/InstanceCopyTask.cpp index 188d163b..31c6bdca 100644 --- a/launcher/InstanceCopyTask.cpp +++ b/launcher/InstanceCopyTask.cpp @@ -11,6 +11,11 @@ InstanceCopyTask::InstanceCopyTask(InstancePtr origInstance, const InstanceCopyP m_keepPlaytime = prefs.isKeepPlaytimeEnabled(); QString filters = prefs.getSelectedFiltersAsRegex(); + + m_useLinks = prefs.isLinkFilesEnabled(); + m_useHardLinks = prefs.isUseHardLinksEnabled(); + m_copySaves = prefs.isDontLinkSavesEnabled() && prefs.isCopySavesEnabled(); + if (!filters.isEmpty()) { // Set regex filter: @@ -25,11 +30,71 @@ void InstanceCopyTask::executeTask() { setStatus(tr("Copying instance %1").arg(m_origInstance->name())); - m_copyFuture = QtConcurrent::run(QThreadPool::globalInstance(), [this]{ - FS::copy folderCopy(m_origInstance->instanceRoot(), m_stagingPath); - folderCopy.followSymlinks(false).matcher(m_matcher.get()); + auto copySaves = [&](){ + FS::copy savesCopy(FS::PathCombine(m_origInstance->instanceRoot(), "saves") , FS::PathCombine(m_stagingPath, "saves")); + savesCopy.followSymlinks(false); + + return savesCopy(); + }; + + m_copyFuture = QtConcurrent::run(QThreadPool::globalInstance(), [this, copySaves]{ + if (m_useLinks) { + FS::create_link folderLink(m_origInstance->instanceRoot(), m_stagingPath); + folderLink.linkRecursively(true).useHardLinks(m_useHardLinks).matcher(m_matcher.get()); + + bool there_were_errors = false; + + if(!folderLink()){ +#if defined Q_OS_WIN32 + if (!m_useHardLinks) { + qDebug() << "EXPECTED: Link failure, Windows requires permissions for symlinks"; + + qDebug() << "atempting to run with privelage"; + + QEventLoop loop; + bool got_priv_results = false; + + connect(&folderLink, &FS::create_link::finishedPrivlaged, this, [&](bool gotResults){ + if (!gotResults) { + qDebug() << "Privlaged run exited without results!"; + } + got_priv_results = gotResults; + loop.quit(); + }); + folderLink.runPrivlaged(); + + loop.exec(); // wait for the finished signal + + for (auto result : folderLink.getResults()) { + if (result.err_value != 0) { + there_were_errors = true; + } + } + + if (m_copySaves) { + there_were_errors |= !copySaves(); + } + + return got_priv_results && !there_were_errors; + } else { + qDebug() << "Link Failed!" << folderLink.getOSError().value() << folderLink.getOSError().message().c_str(); + } +#else + qDebug() << "Link Failed!" << folderLink.getOSError().value() << folderLink.getOSError().message().c_str(); +#endif return false; + } + + if (m_copySaves) { + there_were_errors |= !copySaves(); + } + + return !there_were_errors; + } else { + FS::copy folderCopy(m_origInstance->instanceRoot(), m_stagingPath); + folderCopy.followSymlinks(false).matcher(m_matcher.get()); - return folderCopy(); + return folderCopy(); + } }); connect(&m_copyFutureWatcher, &QFutureWatcher::finished, this, &InstanceCopyTask::copyFinished); connect(&m_copyFutureWatcher, &QFutureWatcher::canceled, this, &InstanceCopyTask::copyAborted); diff --git a/launcher/InstanceCopyTask.h b/launcher/InstanceCopyTask.h index 1f29b854..d9651b07 100644 --- a/launcher/InstanceCopyTask.h +++ b/launcher/InstanceCopyTask.h @@ -30,4 +30,7 @@ private: QFutureWatcher m_copyFutureWatcher; std::unique_ptr m_matcher; bool m_keepPlaytime; + bool m_useLinks = false; + bool m_useHardLinks = false; + bool m_copySaves = true; }; diff --git a/launcher/ui/dialogs/CopyInstanceDialog.cpp b/launcher/ui/dialogs/CopyInstanceDialog.cpp index 981352ae..e477b4b3 100644 --- a/launcher/ui/dialogs/CopyInstanceDialog.cpp +++ b/launcher/ui/dialogs/CopyInstanceDialog.cpp @@ -88,7 +88,7 @@ CopyInstanceDialog::CopyInstanceDialog(InstancePtr original, QWidget *parent) ui->linkFilesGroup->setChecked(m_selectedOptions.isLinkFilesEnabled()); ui->hardLinksCheckbox->setChecked(m_selectedOptions.isUseHardLinksEnabled()); - ui->linkWorldsCheckbox->setChecked(m_selectedOptions.isLinkWorldsEnabled()); + ui->dontLinkSavesCheckbox->setChecked(m_selectedOptions.isDontLinkSavesEnabled()); } CopyInstanceDialog::~CopyInstanceDialog() @@ -179,6 +179,7 @@ void CopyInstanceDialog::on_selectAllCheckbox_stateChanged(int state) void CopyInstanceDialog::on_copySavesCheckbox_stateChanged(int state) { m_selectedOptions.enableCopySaves(state == Qt::Checked); + ui->dontLinkSavesCheckbox->setChecked((state == Qt::Checked) && ui->dontLinkSavesCheckbox->isChecked()); updateSelectAllCheckbox(); } @@ -235,7 +236,7 @@ void CopyInstanceDialog::on_hardLinksCheckbox_stateChanged(int state) m_selectedOptions.enableUseHardLinks(state == Qt::Checked); } -void CopyInstanceDialog::on_linkWorldsCheckbox_stateChanged(int state) +void CopyInstanceDialog::on_dontLinkSavesCheckbox_stateChanged(int state) { - m_selectedOptions.enableLinkWorlds(state == Qt::Checked); + m_selectedOptions.enableDontLinkSaves(state == Qt::Checked); } diff --git a/launcher/ui/dialogs/CopyInstanceDialog.h b/launcher/ui/dialogs/CopyInstanceDialog.h index a80faab9..57775925 100644 --- a/launcher/ui/dialogs/CopyInstanceDialog.h +++ b/launcher/ui/dialogs/CopyInstanceDialog.h @@ -57,7 +57,7 @@ slots: void on_copyScreenshotsCheckbox_stateChanged(int state); void on_linkFilesGroup_toggled(bool checked); void on_hardLinksCheckbox_stateChanged(int state); - void on_linkWorldsCheckbox_stateChanged(int state); + void on_dontLinkSavesCheckbox_stateChanged(int state); private: void checkAllCheckboxes(const bool& b); diff --git a/launcher/ui/dialogs/CopyInstanceDialog.ui b/launcher/ui/dialogs/CopyInstanceDialog.ui index e41ad526..d8eb96eb 100644 --- a/launcher/ui/dialogs/CopyInstanceDialog.ui +++ b/launcher/ui/dialogs/CopyInstanceDialog.ui @@ -240,17 +240,14 @@ - + - World save data will be linked and thus shared between instances. + If "copy saves" is selected world save data will be copied instead of linked and thus not shared between instances. - Link worlds + Don't link saves - true - - false -- cgit From 1bed7754e0bf3c009a38818963fe8d0832b36852 Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Wed, 8 Feb 2023 18:39:17 -0700 Subject: feat(symlinks): make recursive links explicit Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- launcher/InstanceCopyPrefs.cpp | 10 ++++++++++ launcher/InstanceCopyPrefs.h | 3 +++ launcher/InstanceCopyTask.cpp | 10 ++++++---- launcher/InstanceCopyTask.h | 3 ++- launcher/ui/dialogs/CopyInstanceDialog.cpp | 14 ++++++++++++++ launcher/ui/dialogs/CopyInstanceDialog.h | 1 + launcher/ui/dialogs/CopyInstanceDialog.ui | 25 +++++++++++++++++++++++-- 7 files changed, 59 insertions(+), 7 deletions(-) (limited to 'launcher/ui/dialogs') diff --git a/launcher/InstanceCopyPrefs.cpp b/launcher/InstanceCopyPrefs.cpp index e363d4c6..59825ced 100644 --- a/launcher/InstanceCopyPrefs.cpp +++ b/launcher/InstanceCopyPrefs.cpp @@ -103,6 +103,11 @@ bool InstanceCopyPrefs::isUseHardLinksEnabled() const return useHardLinks; } +bool InstanceCopyPrefs::isLinkRecursivelyEnabled() const +{ + return linkRecursively; +} + bool InstanceCopyPrefs::isDontLinkSavesEnabled() const { return dontLinkSaves; @@ -154,6 +159,11 @@ void InstanceCopyPrefs::enableLinkFiles(bool b) linkFiles = b; } +void InstanceCopyPrefs::enableLinkRecursively(bool b) +{ + linkRecursively = b; +} + void InstanceCopyPrefs::enableUseHardLinks(bool b) { useHardLinks = b; diff --git a/launcher/InstanceCopyPrefs.h b/launcher/InstanceCopyPrefs.h index 61719a06..9fc9dcab 100644 --- a/launcher/InstanceCopyPrefs.h +++ b/launcher/InstanceCopyPrefs.h @@ -20,6 +20,7 @@ struct InstanceCopyPrefs { [[nodiscard]] bool isCopyModsEnabled() const; [[nodiscard]] bool isCopyScreenshotsEnabled() const; [[nodiscard]] bool isLinkFilesEnabled() const; + [[nodiscard]] bool isLinkRecursivelyEnabled() const; [[nodiscard]] bool isUseHardLinksEnabled() const; [[nodiscard]] bool isDontLinkSavesEnabled() const; // Setters @@ -32,6 +33,7 @@ struct InstanceCopyPrefs { void enableCopyMods(bool b); void enableCopyScreenshots(bool b); void enableLinkFiles(bool b); + void enableLinkRecursively(bool b); void enableUseHardLinks(bool b); void enableDontLinkSaves(bool b); @@ -45,6 +47,7 @@ struct InstanceCopyPrefs { bool copyMods = true; bool copyScreenshots = true; bool linkFiles = false; + bool linkRecursively = false; bool useHardLinks = false; bool dontLinkSaves = false; }; diff --git a/launcher/InstanceCopyTask.cpp b/launcher/InstanceCopyTask.cpp index 31c6bdca..81502d89 100644 --- a/launcher/InstanceCopyTask.cpp +++ b/launcher/InstanceCopyTask.cpp @@ -12,9 +12,11 @@ InstanceCopyTask::InstanceCopyTask(InstancePtr origInstance, const InstanceCopyP QString filters = prefs.getSelectedFiltersAsRegex(); + m_useLinks = prefs.isLinkFilesEnabled(); - m_useHardLinks = prefs.isUseHardLinksEnabled(); - m_copySaves = prefs.isDontLinkSavesEnabled() && prefs.isCopySavesEnabled(); + m_linkRecursively = prefs.isLinkRecursivelyEnabled(); + m_useHardLinks = prefs.isLinkRecursivelyEnabled() && prefs.isUseHardLinksEnabled(); + m_copySaves = prefs.isLinkRecursivelyEnabled() && prefs.isDontLinkSavesEnabled() && prefs.isCopySavesEnabled(); if (!filters.isEmpty()) { @@ -32,7 +34,7 @@ void InstanceCopyTask::executeTask() auto copySaves = [&](){ FS::copy savesCopy(FS::PathCombine(m_origInstance->instanceRoot(), "saves") , FS::PathCombine(m_stagingPath, "saves")); - savesCopy.followSymlinks(false); + savesCopy.followSymlinks(true); return savesCopy(); }; @@ -40,7 +42,7 @@ void InstanceCopyTask::executeTask() m_copyFuture = QtConcurrent::run(QThreadPool::globalInstance(), [this, copySaves]{ if (m_useLinks) { FS::create_link folderLink(m_origInstance->instanceRoot(), m_stagingPath); - folderLink.linkRecursively(true).useHardLinks(m_useHardLinks).matcher(m_matcher.get()); + folderLink.linkRecursively(m_linkRecursively).useHardLinks(m_useHardLinks).matcher(m_matcher.get()); bool there_were_errors = false; diff --git a/launcher/InstanceCopyTask.h b/launcher/InstanceCopyTask.h index d9651b07..3dce1662 100644 --- a/launcher/InstanceCopyTask.h +++ b/launcher/InstanceCopyTask.h @@ -32,5 +32,6 @@ private: bool m_keepPlaytime; bool m_useLinks = false; bool m_useHardLinks = false; - bool m_copySaves = true; + bool m_copySaves = false; + bool m_linkRecursively = false; }; diff --git a/launcher/ui/dialogs/CopyInstanceDialog.cpp b/launcher/ui/dialogs/CopyInstanceDialog.cpp index e477b4b3..55962c5a 100644 --- a/launcher/ui/dialogs/CopyInstanceDialog.cpp +++ b/launcher/ui/dialogs/CopyInstanceDialog.cpp @@ -87,6 +87,7 @@ CopyInstanceDialog::CopyInstanceDialog(InstancePtr original, QWidget *parent) ui->copyScreenshotsCheckbox->setChecked(m_selectedOptions.isCopyScreenshotsEnabled()); ui->linkFilesGroup->setChecked(m_selectedOptions.isLinkFilesEnabled()); + ui->recursiveLinkCheckbox->setChecked(m_selectedOptions.isLinkRecursivelyEnabled()); ui->hardLinksCheckbox->setChecked(m_selectedOptions.isUseHardLinksEnabled()); ui->dontLinkSavesCheckbox->setChecked(m_selectedOptions.isDontLinkSavesEnabled()); } @@ -231,9 +232,22 @@ void CopyInstanceDialog::on_linkFilesGroup_toggled(bool checked) m_selectedOptions.enableLinkFiles(checked); } +void CopyInstanceDialog::on_recursiveLinkCheckbox_stateChanged(int state) +{ + m_selectedOptions.enableLinkRecursively(state == Qt::Checked); + if (state != Qt::Checked) { + ui->hardLinksCheckbox->setChecked(false); + ui->dontLinkSavesCheckbox->setChecked(false); + } + +} + void CopyInstanceDialog::on_hardLinksCheckbox_stateChanged(int state) { m_selectedOptions.enableUseHardLinks(state == Qt::Checked); + if (state == Qt::Checked && !ui->recursiveLinkCheckbox->isChecked()) { + ui->recursiveLinkCheckbox->setChecked(true); + } } void CopyInstanceDialog::on_dontLinkSavesCheckbox_stateChanged(int state) diff --git a/launcher/ui/dialogs/CopyInstanceDialog.h b/launcher/ui/dialogs/CopyInstanceDialog.h index 57775925..2fc6f38a 100644 --- a/launcher/ui/dialogs/CopyInstanceDialog.h +++ b/launcher/ui/dialogs/CopyInstanceDialog.h @@ -56,6 +56,7 @@ slots: void on_copyModsCheckbox_stateChanged(int state); void on_copyScreenshotsCheckbox_stateChanged(int state); void on_linkFilesGroup_toggled(bool checked); + void on_recursiveLinkCheckbox_stateChanged(int state); void on_hardLinksCheckbox_stateChanged(int state); void on_dontLinkSavesCheckbox_stateChanged(int state); diff --git a/launcher/ui/dialogs/CopyInstanceDialog.ui b/launcher/ui/dialogs/CopyInstanceDialog.ui index d8eb96eb..8df0d3db 100644 --- a/launcher/ui/dialogs/CopyInstanceDialog.ui +++ b/launcher/ui/dialogs/CopyInstanceDialog.ui @@ -209,6 +209,16 @@ + + + + Advanced Copy Options + + + Qt::AlignCenter + + + @@ -229,8 +239,18 @@ false + + + + Link files recursively + + + + + false + Use hard links instead of symbolic links @@ -242,7 +262,7 @@ - If "copy saves" is selected world save data will be copied instead of linked and thus not shared between instances. + If "copy saves" is selected world save data will be copied instead of linked and thus not shared between instances. Don't link saves @@ -283,8 +303,9 @@ copyResPacksCheckbox copyModsCheckbox linkFilesGroup + recursiveLinkCheckbox hardLinksCheckbox - linkWorldsCheckbox + dontLinkSavesCheckbox -- cgit From c9105e525e175ee8181ab0a6998d0e21526f116d Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Wed, 8 Feb 2023 18:52:50 -0700 Subject: fix: follow symlinks when exporting Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- launcher/FileSystem.h | 2 +- launcher/MMCZip.cpp | 9 ++++++--- launcher/MMCZip.h | 6 ++++-- launcher/ui/dialogs/ExportInstanceDialog.cpp | 5 ++++- 4 files changed, 15 insertions(+), 7 deletions(-) (limited to 'launcher/ui/dialogs') diff --git a/launcher/FileSystem.h b/launcher/FileSystem.h index d79096e6..782a2f40 100644 --- a/launcher/FileSystem.h +++ b/launcher/FileSystem.h @@ -144,7 +144,7 @@ class ExternalLinkFileProcess : public QThread { Q_OBJECT public: ExternalLinkFileProcess(QString server, bool useHardLinks, QObject* parent = nullptr) - : QThread(parent), m_server(server), m_useHardLinks(useHardLinks) + : QThread(parent), m_useHardLinks(useHardLinks), m_server(server) {} void run() override diff --git a/launcher/MMCZip.cpp b/launcher/MMCZip.cpp index 1eda43fe..b4b663c1 100644 --- a/launcher/MMCZip.cpp +++ b/launcher/MMCZip.cpp @@ -94,20 +94,23 @@ bool MMCZip::mergeZipFiles(QuaZip *into, QFileInfo from, QSet &containe return true; } -bool MMCZip::compressDirFiles(QuaZip *zip, QString dir, QFileInfoList files) +bool MMCZip::compressDirFiles(QuaZip *zip, QString dir, QFileInfoList files, bool followSymlinks) { QDir directory(dir); if (!directory.exists()) return false; for (auto e : files) { auto filePath = directory.relativeFilePath(e.absoluteFilePath()); - if( !JlCompress::compressFile(zip, e.absoluteFilePath(), filePath)) return false; + auto srcPath = e.absoluteFilePath(); + if (followSymlinks) + srcPath = e.canonicalFilePath(); + if( !JlCompress::compressFile(zip, srcPath, filePath)) return false; } return true; } -bool MMCZip::compressDirFiles(QString fileCompressed, QString dir, QFileInfoList files) +bool MMCZip::compressDirFiles(QString fileCompressed, QString dir, QFileInfoList files, bool followSymlinks) { QuaZip zip(fileCompressed); QDir().mkpath(QFileInfo(fileCompressed).absolutePath()); diff --git a/launcher/MMCZip.h b/launcher/MMCZip.h index 81f9cb90..2a78f830 100644 --- a/launcher/MMCZip.h +++ b/launcher/MMCZip.h @@ -59,18 +59,20 @@ namespace MMCZip * \param zip target archive * \param dir directory that will be compressed (to compress with relative paths) * \param files list of files to compress + * \param followSymlinks should follow symlinks when compressing file data * \return true for success or false for failure */ - bool compressDirFiles(QuaZip *zip, QString dir, QFileInfoList files); + bool compressDirFiles(QuaZip *zip, QString dir, QFileInfoList files, bool followSymlinks = false); /** * Compress directory, by providing a list of files to compress * \param fileCompressed target archive file * \param dir directory that will be compressed (to compress with relative paths) * \param files list of files to compress + * \param followSymlinks should follow symlinks when compressing file data * \return true for success or false for failure */ - bool compressDirFiles(QString fileCompressed, QString dir, QFileInfoList files); + bool compressDirFiles(QString fileCompressed, QString dir, QFileInfoList files, bool followSymlinks = false); /** * take a source jar, add mods to it, resulting in target jar diff --git a/launcher/ui/dialogs/ExportInstanceDialog.cpp b/launcher/ui/dialogs/ExportInstanceDialog.cpp index f13e36e8..07ec3c70 100644 --- a/launcher/ui/dialogs/ExportInstanceDialog.cpp +++ b/launcher/ui/dialogs/ExportInstanceDialog.cpp @@ -45,6 +45,8 @@ #include #include #include +#include + #include "StringUtils.h" #include "SeparatorPrefixTree.h" #include "Application.h" @@ -429,7 +431,8 @@ bool ExportInstanceDialog::doExport() QMessageBox::warning(this, tr("Error"), tr("Unable to export instance")); return false; } - if (!MMCZip::compressDirFiles(output, m_instance->instanceRoot(), files)) + + if (!MMCZip::compressDirFiles(output, m_instance->instanceRoot(), files, true)) { QMessageBox::warning(this, tr("Error"), tr("Unable to export instance")); return false; -- cgit From 397e7f036339b09569317300423261f2b37d6119 Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Thu, 9 Feb 2023 02:02:40 -0700 Subject: feat(reflink): hook up relink / clone on the copy dialog Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- launcher/FileSystem.cpp | 26 ++++++++++++++---- launcher/FileSystem.h | 3 +++ launcher/InstanceCopyPrefs.cpp | 10 +++++++ launcher/InstanceCopyPrefs.h | 3 +++ launcher/InstanceCopyTask.cpp | 11 ++++++-- launcher/InstanceCopyTask.h | 1 + launcher/ui/dialogs/CopyInstanceDialog.cpp | 28 ++++++++++++++++++- launcher/ui/dialogs/CopyInstanceDialog.h | 5 ++++ launcher/ui/dialogs/CopyInstanceDialog.ui | 43 +++++++++++++++++++++++++++--- 9 files changed, 119 insertions(+), 11 deletions(-) (limited to 'launcher/ui/dialogs') diff --git a/launcher/FileSystem.cpp b/launcher/FileSystem.cpp index 7b7fc80b..fd09842f 100644 --- a/launcher/FileSystem.cpp +++ b/launcher/FileSystem.cpp @@ -103,6 +103,7 @@ namespace fs = ghc::filesystem; #include /* Definition of FICLONE* constants */ #include #include +#include #elif defined(Q_OS_MACOS) || defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD) #include #include @@ -880,6 +881,9 @@ FilesystemInfo statFS(QString path) info.name = storage_info.name(); info.rootPath = storage_info.rootPath(); + qDebug() << "Pulling filesystem info for" << info.rootPath; + qDebug() << "Filesystem: " << fsTypeName << "detected" << getFilesystemTypeName(info.fsType); + return info; } @@ -995,33 +999,45 @@ bool clone_file(const QString& src, const QString& dst, std::error_code& ec) // https://man7.org/linux/man-pages/man2/ioctl_ficlone.2.html int src_fd = open(src_path.c_str(), O_RDONLY); - if(!src_fd) { + if(src_fd == -1) { qWarning() << "Failed to open file:" << src_path.c_str(); qDebug() << "Error:" << strerror(errno); ec = std::make_error_code(static_cast(errno)); return false; } - int dst_fd = open(dst_path.c_str(), O_WRONLY | O_TRUNC); - if(!dst_fd) { + int dst_fd = open(dst_path.c_str(), O_CREAT | O_WRONLY | O_TRUNC, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH); + if(dst_fd == -1) { qWarning() << "Failed to open file:" << dst_path.c_str(); qDebug() << "Error:" << strerror(errno); ec = std::make_error_code(static_cast(errno)); + close(src_fd); return false; } // attempt to clone - if(!ioctl(dst_fd, FICLONE, src_fd)){ + if(ioctl(dst_fd, FICLONE, src_fd) == -1){ qWarning() << "Failed to clone file:" << src_path.c_str() << "to" << dst_path.c_str(); qDebug() << "Error:" << strerror(errno); ec = std::make_error_code(static_cast(errno)); + close(src_fd); + close(dst_fd); return false; } + if(close(src_fd)) { + qWarning() << "Failed to close file:" << src_path.c_str(); + qDebug() << "Error:" << strerror(errno); + } + if(close(dst_fd)) { + qWarning() << "Failed to close file:" << dst_path.c_str(); + qDebug() << "Error:" << strerror(errno); + } #elif defined(Q_OS_MACOS) || defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD) // TODO: use clonefile // clonefile(const char * src, const char * dst, int flags); // https://www.manpagez.com/man/2/clonefile/ - if (!clonefile(src_path.c_str(), dst_path.c_str(), 0)) { + qDebug() << "attempting file clone via clonefile" << src << "to" << dst; + if (clonefile(src_path.c_str(), dst_path.c_str(), 0) == -1) { qWarning() << "Failed to clone file:" << src_path.c_str() << "to" << dst_path.c_str(); qDebug() << "Error:" << strerror(errno); ec = std::make_error_code(static_cast(errno)); diff --git a/launcher/FileSystem.h b/launcher/FileSystem.h index 531036dd..aa28de93 100644 --- a/launcher/FileSystem.h +++ b/launcher/FileSystem.h @@ -329,6 +329,7 @@ enum class FilesystemType { HFS, HFSPLUS, HFSX, + FUSEBLK, UNKNOWN }; @@ -346,6 +347,7 @@ static const QMap s_filesystem_type_names = { {FilesystemType::HFS, QString("HFS")}, {FilesystemType::HFSPLUS, QString("HFSPLUS")}, {FilesystemType::HFSX, QString("HFSX")}, + {FilesystemType::FUSEBLK, QString("FUSEBLK")}, {FilesystemType::UNKNOWN, QString("UNKNOWN")} }; @@ -365,6 +367,7 @@ static const QMap s_filesystem_type_names_inverse = { {QString("HFSPLUS"), FilesystemType::HFSPLUS}, {QString("HFSX"), FilesystemType::HFSX}, {QString("HFS"), FilesystemType::HFS}, + {QString("FUSEBLK"), FilesystemType::FUSEBLK}, {QString("UNKNOWN"), FilesystemType::UNKNOWN} }; diff --git a/launcher/InstanceCopyPrefs.cpp b/launcher/InstanceCopyPrefs.cpp index 59825ced..f2aa5e69 100644 --- a/launcher/InstanceCopyPrefs.cpp +++ b/launcher/InstanceCopyPrefs.cpp @@ -113,6 +113,11 @@ bool InstanceCopyPrefs::isDontLinkSavesEnabled() const return dontLinkSaves; } +bool InstanceCopyPrefs::isUseCloneEnabled() const +{ + return useClone; +} + // ======= Setters ======= void InstanceCopyPrefs::enableCopySaves(bool b) { @@ -173,3 +178,8 @@ void InstanceCopyPrefs::enableDontLinkSaves(bool b) { dontLinkSaves = b; } + +void InstanceCopyPrefs::enableUseClone(bool b) +{ + useClone = b; +} \ No newline at end of file diff --git a/launcher/InstanceCopyPrefs.h b/launcher/InstanceCopyPrefs.h index 9fc9dcab..583fdd4c 100644 --- a/launcher/InstanceCopyPrefs.h +++ b/launcher/InstanceCopyPrefs.h @@ -23,6 +23,7 @@ struct InstanceCopyPrefs { [[nodiscard]] bool isLinkRecursivelyEnabled() const; [[nodiscard]] bool isUseHardLinksEnabled() const; [[nodiscard]] bool isDontLinkSavesEnabled() const; + [[nodiscard]] bool isUseCloneEnabled() const; // Setters void enableCopySaves(bool b); void enableKeepPlaytime(bool b); @@ -36,6 +37,7 @@ struct InstanceCopyPrefs { void enableLinkRecursively(bool b); void enableUseHardLinks(bool b); void enableDontLinkSaves(bool b); + void enableUseClone(bool b); protected: // data bool copySaves = true; @@ -50,4 +52,5 @@ struct InstanceCopyPrefs { bool linkRecursively = false; bool useHardLinks = false; bool dontLinkSaves = false; + bool useClone = false; }; diff --git a/launcher/InstanceCopyTask.cpp b/launcher/InstanceCopyTask.cpp index 81502d89..b3ea54b0 100644 --- a/launcher/InstanceCopyTask.cpp +++ b/launcher/InstanceCopyTask.cpp @@ -17,6 +17,7 @@ InstanceCopyTask::InstanceCopyTask(InstancePtr origInstance, const InstanceCopyP m_linkRecursively = prefs.isLinkRecursivelyEnabled(); m_useHardLinks = prefs.isLinkRecursivelyEnabled() && prefs.isUseHardLinksEnabled(); m_copySaves = prefs.isLinkRecursivelyEnabled() && prefs.isDontLinkSavesEnabled() && prefs.isCopySavesEnabled(); + m_useClone = prefs.isUseCloneEnabled(); if (!filters.isEmpty()) { @@ -40,7 +41,12 @@ void InstanceCopyTask::executeTask() }; m_copyFuture = QtConcurrent::run(QThreadPool::globalInstance(), [this, copySaves]{ - if (m_useLinks) { + if (m_useClone) { + FS::clone folderClone(m_origInstance->instanceRoot(), m_stagingPath); + folderClone.matcher(m_matcher.get()); + + return folderClone(); + } else if (m_useLinks) { FS::create_link folderLink(m_origInstance->instanceRoot(), m_stagingPath); folderLink.linkRecursively(m_linkRecursively).useHardLinks(m_useHardLinks).matcher(m_matcher.get()); @@ -83,7 +89,8 @@ void InstanceCopyTask::executeTask() } #else qDebug() << "Link Failed!" << folderLink.getOSError().value() << folderLink.getOSError().message().c_str(); -#endif return false; +#endif + return false; } if (m_copySaves) { diff --git a/launcher/InstanceCopyTask.h b/launcher/InstanceCopyTask.h index 3dce1662..aea9d99a 100644 --- a/launcher/InstanceCopyTask.h +++ b/launcher/InstanceCopyTask.h @@ -34,4 +34,5 @@ private: bool m_useHardLinks = false; bool m_copySaves = false; bool m_linkRecursively = false; + bool m_useClone = false; }; diff --git a/launcher/ui/dialogs/CopyInstanceDialog.cpp b/launcher/ui/dialogs/CopyInstanceDialog.cpp index 55962c5a..c51bc067 100644 --- a/launcher/ui/dialogs/CopyInstanceDialog.cpp +++ b/launcher/ui/dialogs/CopyInstanceDialog.cpp @@ -46,6 +46,7 @@ #include "icons/IconList.h" #include "BaseInstance.h" #include "InstanceList.h" +#include "FileSystem.h" CopyInstanceDialog::CopyInstanceDialog(InstancePtr original, QWidget *parent) :QDialog(parent), ui(new Ui::CopyInstanceDialog), m_original(original) @@ -85,11 +86,22 @@ CopyInstanceDialog::CopyInstanceDialog(InstancePtr original, QWidget *parent) ui->copyServersCheckbox->setChecked(m_selectedOptions.isCopyServersEnabled()); ui->copyModsCheckbox->setChecked(m_selectedOptions.isCopyModsEnabled()); ui->copyScreenshotsCheckbox->setChecked(m_selectedOptions.isCopyScreenshotsEnabled()); - + ui->linkFilesGroup->setChecked(m_selectedOptions.isLinkFilesEnabled()); ui->recursiveLinkCheckbox->setChecked(m_selectedOptions.isLinkRecursivelyEnabled()); ui->hardLinksCheckbox->setChecked(m_selectedOptions.isUseHardLinksEnabled()); ui->dontLinkSavesCheckbox->setChecked(m_selectedOptions.isDontLinkSavesEnabled()); + + auto detectedOS = FS::statFS(m_original->instanceRoot()).fsType; + m_cloneSupported = FS::canCloneOnFS(detectedOS); + + if (m_cloneSupported) { + ui->cloneSupportedLabel->setText(tr("Clone / Reflink is supported on (%1)").arg(FS::getFilesystemTypeName(detectedOS))); + } else { + ui->cloneSupportedLabel->setText(tr("Clone / Reflink not supported on (%1)").arg(FS::getFilesystemTypeName(detectedOS))); + } + + updateUseCloneCheckbox(); } CopyInstanceDialog::~CopyInstanceDialog() @@ -152,6 +164,12 @@ void CopyInstanceDialog::updateSelectAllCheckbox() ui->selectAllCheckbox->blockSignals(false); } +void CopyInstanceDialog::updateUseCloneCheckbox() +{ + ui->useCloneCheckbox->setEnabled(m_cloneSupported && !ui->linkFilesGroup->isChecked()); + ui->useCloneCheckbox->setChecked(m_cloneSupported && m_selectedOptions.isUseCloneEnabled()); +} + void CopyInstanceDialog::on_iconButton_clicked() { IconPickerDialog dlg(this); @@ -230,6 +248,7 @@ void CopyInstanceDialog::on_copyScreenshotsCheckbox_stateChanged(int state) void CopyInstanceDialog::on_linkFilesGroup_toggled(bool checked) { m_selectedOptions.enableLinkFiles(checked); + updateUseCloneCheckbox(); } void CopyInstanceDialog::on_recursiveLinkCheckbox_stateChanged(int state) @@ -254,3 +273,10 @@ void CopyInstanceDialog::on_dontLinkSavesCheckbox_stateChanged(int state) { m_selectedOptions.enableDontLinkSaves(state == Qt::Checked); } + +void CopyInstanceDialog::on_useCloneCheckbox_stateChanged(int state) +{ + m_selectedOptions.enableUseClone(m_cloneSupported && (state == Qt::Checked)); + ui->linkFilesGroup->setEnabled(!m_selectedOptions.isUseCloneEnabled()); + updateUseCloneCheckbox(); +} \ No newline at end of file diff --git a/launcher/ui/dialogs/CopyInstanceDialog.h b/launcher/ui/dialogs/CopyInstanceDialog.h index 2fc6f38a..859c643c 100644 --- a/launcher/ui/dialogs/CopyInstanceDialog.h +++ b/launcher/ui/dialogs/CopyInstanceDialog.h @@ -16,6 +16,7 @@ #pragma once #include +#include "BaseInstance.h" #include "BaseVersion.h" #include "InstanceCopyPrefs.h" @@ -59,13 +60,17 @@ slots: void on_recursiveLinkCheckbox_stateChanged(int state); void on_hardLinksCheckbox_stateChanged(int state); void on_dontLinkSavesCheckbox_stateChanged(int state); + void on_useCloneCheckbox_stateChanged(int state); private: void checkAllCheckboxes(const bool& b); void updateSelectAllCheckbox(); + void updateUseCloneCheckbox(); + /* data */ Ui::CopyInstanceDialog *ui; QString InstIconKey; InstancePtr m_original; InstanceCopyPrefs m_selectedOptions; + bool m_cloneSupported = false; }; diff --git a/launcher/ui/dialogs/CopyInstanceDialog.ui b/launcher/ui/dialogs/CopyInstanceDialog.ui index 8df0d3db..ce8657f6 100644 --- a/launcher/ui/dialogs/CopyInstanceDialog.ui +++ b/launcher/ui/dialogs/CopyInstanceDialog.ui @@ -9,8 +9,8 @@ 0 0 - 525 - 581 + 531 + 640 @@ -275,6 +275,44 @@ + + + + Clone / Reflink (Copy On Write) Options + + + + + + false + + + Use Clone / Reflink + + + + + + + + 1 + 0 + + + + Clone / Reflink not supported on this filesystem + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + 4 + + + + + + @@ -302,7 +340,6 @@ copyServersCheckbox copyResPacksCheckbox copyModsCheckbox - linkFilesGroup recursiveLinkCheckbox hardLinksCheckbox dontLinkSavesCheckbox -- cgit From bc8336a4b115fd190e068f57159d925683ba3930 Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Thu, 9 Feb 2023 16:19:38 -0700 Subject: fix: cleanup UI, detect FAT and turn off links Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- launcher/DesktopServices.cpp | 1 - launcher/FileSystem.cpp | 28 ++++++++ launcher/FileSystem.h | 24 ++++++- launcher/InstanceCopyPrefs.cpp | 8 +-- launcher/InstanceCopyPrefs.h | 6 +- launcher/InstanceCopyTask.cpp | 2 +- launcher/ui/dialogs/CopyInstanceDialog.cpp | 50 +++++++++---- launcher/ui/dialogs/CopyInstanceDialog.h | 6 +- launcher/ui/dialogs/CopyInstanceDialog.ui | 111 ++++++++++++++++++++--------- 9 files changed, 175 insertions(+), 61 deletions(-) (limited to 'launcher/ui/dialogs') diff --git a/launcher/DesktopServices.cpp b/launcher/DesktopServices.cpp index 69770e99..2984a1b4 100644 --- a/launcher/DesktopServices.cpp +++ b/launcher/DesktopServices.cpp @@ -37,7 +37,6 @@ #include #include #include -//#include "Application.h" /** * This shouldn't exist, but until QTBUG-9328 and other unreported bugs are fixed, it needs to be a thing. diff --git a/launcher/FileSystem.cpp b/launcher/FileSystem.cpp index fd09842f..c363f6ea 100644 --- a/launcher/FileSystem.cpp +++ b/launcher/FileSystem.cpp @@ -1053,4 +1053,32 @@ bool clone_file(const QString& src, const QString& dst, std::error_code& ec) return true; } + +/** + * @brief if the Filesystem is symlink capable + * + */ +bool canLinkOnFS(const QString& path) +{ + FilesystemInfo info = statFS(path); + return canLinkOnFS(info); +} +bool canLinkOnFS(const FilesystemInfo& info) +{ + return canLinkOnFS(info.fsType); +} +bool canLinkOnFS(FilesystemType type) +{ + return !s_non_link_filesystems.contains(type); +} +/** + * @brief if the Filesystem is symlink capable on both ends + * + */ +bool canLink(const QString& src, const QString& dst) +{ + return canLinkOnFS(src) && canLinkOnFS(dst); +} + + } diff --git a/launcher/FileSystem.h b/launcher/FileSystem.h index aa28de93..83ff99a4 100644 --- a/launcher/FileSystem.h +++ b/launcher/FileSystem.h @@ -330,6 +330,7 @@ enum class FilesystemType { HFSPLUS, HFSX, FUSEBLK, + F2FS, UNKNOWN }; @@ -348,6 +349,7 @@ static const QMap s_filesystem_type_names = { {FilesystemType::HFSPLUS, QString("HFSPLUS")}, {FilesystemType::HFSX, QString("HFSX")}, {FilesystemType::FUSEBLK, QString("FUSEBLK")}, + {FilesystemType::F2FS, QString("F2FS")}, {FilesystemType::UNKNOWN, QString("UNKNOWN")} }; @@ -368,6 +370,7 @@ static const QMap s_filesystem_type_names_inverse = { {QString("HFSX"), FilesystemType::HFSX}, {QString("HFS"), FilesystemType::HFS}, {QString("FUSEBLK"), FilesystemType::FUSEBLK}, + {QString("F2FS"), FilesystemType::F2FS}, {QString("UNKNOWN"), FilesystemType::UNKNOWN} }; @@ -405,7 +408,7 @@ bool canCloneOnFS(const FilesystemInfo& info); bool canCloneOnFS(FilesystemType type); /** - * @brief if the Filesystem is reflink/clone capable and both are on the same device + * @brief if the Filesystems are reflink/clone capable and both are on the same device * */ bool canClone(const QString& src, const QString& dst); @@ -457,4 +460,23 @@ class clone : public QObject { */ bool clone_file(const QString& src, const QString& dst, std::error_code& ec); + +static const QList s_non_link_filesystems = { + FilesystemType::FAT, +}; + +/** + * @brief if the Filesystem is symlink capable + * + */ +bool canLinkOnFS(const QString& path); +bool canLinkOnFS(const FilesystemInfo& info); +bool canLinkOnFS(FilesystemType type); + +/** + * @brief if the Filesystem is symlink capable on both ends + * + */ +bool canLink(const QString& src, const QString& dst); + } diff --git a/launcher/InstanceCopyPrefs.cpp b/launcher/InstanceCopyPrefs.cpp index f2aa5e69..c03d0aae 100644 --- a/launcher/InstanceCopyPrefs.cpp +++ b/launcher/InstanceCopyPrefs.cpp @@ -93,9 +93,9 @@ bool InstanceCopyPrefs::isCopyScreenshotsEnabled() const return copyScreenshots; } -bool InstanceCopyPrefs::isLinkFilesEnabled() const +bool InstanceCopyPrefs::isUseSymLinksEnabled() const { - return linkFiles; + return useSymLinks; } bool InstanceCopyPrefs::isUseHardLinksEnabled() const @@ -159,9 +159,9 @@ void InstanceCopyPrefs::enableCopyScreenshots(bool b) copyScreenshots = b; } -void InstanceCopyPrefs::enableLinkFiles(bool b) +void InstanceCopyPrefs::enableUseSymLinks(bool b) { - linkFiles = b; + useSymLinks = b; } void InstanceCopyPrefs::enableLinkRecursively(bool b) diff --git a/launcher/InstanceCopyPrefs.h b/launcher/InstanceCopyPrefs.h index 583fdd4c..14ae9551 100644 --- a/launcher/InstanceCopyPrefs.h +++ b/launcher/InstanceCopyPrefs.h @@ -19,7 +19,7 @@ struct InstanceCopyPrefs { [[nodiscard]] bool isCopyServersEnabled() const; [[nodiscard]] bool isCopyModsEnabled() const; [[nodiscard]] bool isCopyScreenshotsEnabled() const; - [[nodiscard]] bool isLinkFilesEnabled() const; + [[nodiscard]] bool isUseSymLinksEnabled() const; [[nodiscard]] bool isLinkRecursivelyEnabled() const; [[nodiscard]] bool isUseHardLinksEnabled() const; [[nodiscard]] bool isDontLinkSavesEnabled() const; @@ -33,7 +33,7 @@ struct InstanceCopyPrefs { void enableCopyServers(bool b); void enableCopyMods(bool b); void enableCopyScreenshots(bool b); - void enableLinkFiles(bool b); + void enableUseSymLinks(bool b); void enableLinkRecursively(bool b); void enableUseHardLinks(bool b); void enableDontLinkSaves(bool b); @@ -48,7 +48,7 @@ struct InstanceCopyPrefs { bool copyServers = true; bool copyMods = true; bool copyScreenshots = true; - bool linkFiles = false; + bool useSymLinks = false; bool linkRecursively = false; bool useHardLinks = false; bool dontLinkSaves = false; diff --git a/launcher/InstanceCopyTask.cpp b/launcher/InstanceCopyTask.cpp index b3ea54b0..ba0052fa 100644 --- a/launcher/InstanceCopyTask.cpp +++ b/launcher/InstanceCopyTask.cpp @@ -13,7 +13,7 @@ InstanceCopyTask::InstanceCopyTask(InstancePtr origInstance, const InstanceCopyP QString filters = prefs.getSelectedFiltersAsRegex(); - m_useLinks = prefs.isLinkFilesEnabled(); + m_useLinks = prefs.isUseSymLinksEnabled(); m_linkRecursively = prefs.isLinkRecursivelyEnabled(); m_useHardLinks = prefs.isLinkRecursivelyEnabled() && prefs.isUseHardLinksEnabled(); m_copySaves = prefs.isLinkRecursivelyEnabled() && prefs.isDontLinkSavesEnabled() && prefs.isCopySavesEnabled(); diff --git a/launcher/ui/dialogs/CopyInstanceDialog.cpp b/launcher/ui/dialogs/CopyInstanceDialog.cpp index c51bc067..c6cbefcf 100644 --- a/launcher/ui/dialogs/CopyInstanceDialog.cpp +++ b/launcher/ui/dialogs/CopyInstanceDialog.cpp @@ -87,21 +87,26 @@ CopyInstanceDialog::CopyInstanceDialog(InstancePtr original, QWidget *parent) ui->copyModsCheckbox->setChecked(m_selectedOptions.isCopyModsEnabled()); ui->copyScreenshotsCheckbox->setChecked(m_selectedOptions.isCopyScreenshotsEnabled()); - ui->linkFilesGroup->setChecked(m_selectedOptions.isLinkFilesEnabled()); - ui->recursiveLinkCheckbox->setChecked(m_selectedOptions.isLinkRecursivelyEnabled()); + ui->symbolicLinksCheckbox->setChecked(m_selectedOptions.isUseSymLinksEnabled()); ui->hardLinksCheckbox->setChecked(m_selectedOptions.isUseHardLinksEnabled()); + + ui->recursiveLinkCheckbox->setChecked(m_selectedOptions.isLinkRecursivelyEnabled()); ui->dontLinkSavesCheckbox->setChecked(m_selectedOptions.isDontLinkSavesEnabled()); auto detectedOS = FS::statFS(m_original->instanceRoot()).fsType; + m_cloneSupported = FS::canCloneOnFS(detectedOS); + m_linkSupported = FS::canLinkOnFS(detectedOS); if (m_cloneSupported) { - ui->cloneSupportedLabel->setText(tr("Clone / Reflink is supported on (%1)").arg(FS::getFilesystemTypeName(detectedOS))); + ui->cloneSupportedLabel->setText(tr("Reflinks are supported on %1").arg(FS::getFilesystemTypeName(detectedOS))); } else { - ui->cloneSupportedLabel->setText(tr("Clone / Reflink not supported on (%1)").arg(FS::getFilesystemTypeName(detectedOS))); + ui->cloneSupportedLabel->setText(tr("Reflinks aren't supported on %1").arg(FS::getFilesystemTypeName(detectedOS))); } + updateLinkOptions(); updateUseCloneCheckbox(); + } CopyInstanceDialog::~CopyInstanceDialog() @@ -170,6 +175,21 @@ void CopyInstanceDialog::updateUseCloneCheckbox() ui->useCloneCheckbox->setChecked(m_cloneSupported && m_selectedOptions.isUseCloneEnabled()); } +void CopyInstanceDialog::updateLinkOptions() +{ + ui->symbolicLinksCheckbox->setEnabled(m_linkSupported && !ui->hardLinksCheckbox->isChecked()); + ui->hardLinksCheckbox->setEnabled(m_linkSupported && !ui->symbolicLinksCheckbox->isChecked()); + + ui->symbolicLinksCheckbox->setChecked(m_linkSupported && m_selectedOptions.isUseSymLinksEnabled()); + ui->hardLinksCheckbox->setChecked(m_linkSupported && m_selectedOptions.isUseHardLinksEnabled()); + + bool linksInUse = (ui->symbolicLinksCheckbox->isChecked() || ui->hardLinksCheckbox->isChecked()); + ui->recursiveLinkCheckbox->setEnabled(m_linkSupported && linksInUse && !ui->hardLinksCheckbox->isChecked()); + ui->dontLinkSavesCheckbox->setEnabled(m_linkSupported && linksInUse); + ui->recursiveLinkCheckbox->setChecked(m_linkSupported && linksInUse && m_selectedOptions.isLinkRecursivelyEnabled()); + ui->dontLinkSavesCheckbox->setChecked(m_linkSupported && linksInUse && m_selectedOptions.isDontLinkSavesEnabled()); +} + void CopyInstanceDialog::on_iconButton_clicked() { IconPickerDialog dlg(this); @@ -245,10 +265,20 @@ void CopyInstanceDialog::on_copyScreenshotsCheckbox_stateChanged(int state) updateSelectAllCheckbox(); } -void CopyInstanceDialog::on_linkFilesGroup_toggled(bool checked) +void CopyInstanceDialog::on_symbolicLinksCheckbox_stateChanged(int state) { - m_selectedOptions.enableLinkFiles(checked); + m_selectedOptions.enableUseSymLinks(state == Qt::Checked); updateUseCloneCheckbox(); + updateLinkOptions(); +} + +void CopyInstanceDialog::on_hardLinksCheckbox_stateChanged(int state) +{ + m_selectedOptions.enableUseHardLinks(state == Qt::Checked); + if (state == Qt::Checked && !ui->recursiveLinkCheckbox->isChecked()) { + ui->recursiveLinkCheckbox->setChecked(true); + } + updateLinkOptions(); } void CopyInstanceDialog::on_recursiveLinkCheckbox_stateChanged(int state) @@ -261,14 +291,6 @@ void CopyInstanceDialog::on_recursiveLinkCheckbox_stateChanged(int state) } -void CopyInstanceDialog::on_hardLinksCheckbox_stateChanged(int state) -{ - m_selectedOptions.enableUseHardLinks(state == Qt::Checked); - if (state == Qt::Checked && !ui->recursiveLinkCheckbox->isChecked()) { - ui->recursiveLinkCheckbox->setChecked(true); - } -} - void CopyInstanceDialog::on_dontLinkSavesCheckbox_stateChanged(int state) { m_selectedOptions.enableDontLinkSaves(state == Qt::Checked); diff --git a/launcher/ui/dialogs/CopyInstanceDialog.h b/launcher/ui/dialogs/CopyInstanceDialog.h index 859c643c..2dea3795 100644 --- a/launcher/ui/dialogs/CopyInstanceDialog.h +++ b/launcher/ui/dialogs/CopyInstanceDialog.h @@ -56,9 +56,9 @@ slots: void on_copyServersCheckbox_stateChanged(int state); void on_copyModsCheckbox_stateChanged(int state); void on_copyScreenshotsCheckbox_stateChanged(int state); - void on_linkFilesGroup_toggled(bool checked); - void on_recursiveLinkCheckbox_stateChanged(int state); + void on_symbolicLinksCheckbox_stateChanged(int state); void on_hardLinksCheckbox_stateChanged(int state); + void on_recursiveLinkCheckbox_stateChanged(int state); void on_dontLinkSavesCheckbox_stateChanged(int state); void on_useCloneCheckbox_stateChanged(int state); @@ -66,6 +66,7 @@ private: void checkAllCheckboxes(const bool& b); void updateSelectAllCheckbox(); void updateUseCloneCheckbox(); + void updateLinkOptions(); /* data */ Ui::CopyInstanceDialog *ui; @@ -73,4 +74,5 @@ private: InstancePtr m_original; InstanceCopyPrefs m_selectedOptions; bool m_cloneSupported = false; + bool m_linkSupported = false; }; diff --git a/launcher/ui/dialogs/CopyInstanceDialog.ui b/launcher/ui/dialogs/CopyInstanceDialog.ui index ce8657f6..7bf75c2d 100644 --- a/launcher/ui/dialogs/CopyInstanceDialog.ui +++ b/launcher/ui/dialogs/CopyInstanceDialog.ui @@ -9,8 +9,8 @@ 0 0 - 531 - 640 + 527 + 699 @@ -138,7 +138,7 @@ - Instance copy options + Instance Copy Options @@ -224,53 +224,92 @@ - Use symbolic links instead of copying files. + Use symbolic or hard links instead of copying files. - Link files instead of copying them + Symbolic and Hard Link Options false - true + false false - + - Link files recursively - - - - - - - false + Links are supported on most filesystems except FAT - - Use hard links instead of symbolic links - - - Use hard links + + Qt::AlignCenter - - - If "copy saves" is selected world save data will be copied instead of linked and thus not shared between instances. + + + 6 - - Don't link saves + + 6 - - false + + 6 - + + 6 + + + + + true + + + Use hard links instead of symbolic links + + + Use hard links + + + + + + + false + + + Link files recursively + + + + + + + false + + + If "copy saves" is selected world save data will be copied instead of linked and thus not shared between instances. + + + Don't link saves + + + false + + + + + + + Use symbloic links + + + + @@ -278,7 +317,7 @@ - Clone / Reflink (Copy On Write) Options + CoW (Copy-on-Write) Options @@ -287,7 +326,7 @@ false - Use Clone / Reflink + Clone instead of copying @@ -300,7 +339,7 @@ - Clone / Reflink not supported on this filesystem + Your filesystem and/or OS doesn't support reflinks Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter @@ -340,9 +379,11 @@ copyServersCheckbox copyResPacksCheckbox copyModsCheckbox + symbolicLinksCheckbox recursiveLinkCheckbox hardLinksCheckbox dontLinkSavesCheckbox + useCloneCheckbox @@ -353,8 +394,8 @@ accept() - 263 - 571 + 269 + 692 157 @@ -369,8 +410,8 @@ reject() - 331 - 571 + 337 + 692 286 -- cgit From 2837236d81b882f041a1cefadc86ca9d5f09ceeb Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Thu, 9 Feb 2023 19:48:40 -0700 Subject: fix: intelegent recursive links & symlink follow on export Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- launcher/FileSystem.cpp | 66 ++++++++++++- launcher/FileSystem.h | 28 +++++- launcher/InstanceCopyPrefs.cpp | 9 ++ launcher/InstanceCopyPrefs.h | 1 + launcher/InstanceCopyTask.cpp | 16 +++- launcher/MMCZip.cpp | 11 ++- launcher/ui/dialogs/CopyInstanceDialog.cpp | 21 ++-- tests/FileSystem_test.cpp | 148 +++++++++++++++++++++++++++++ 8 files changed, 278 insertions(+), 22 deletions(-) (limited to 'launcher/ui/dialogs') diff --git a/launcher/FileSystem.cpp b/launcher/FileSystem.cpp index c363f6ea..4ee3899b 100644 --- a/launcher/FileSystem.cpp +++ b/launcher/FileSystem.cpp @@ -242,6 +242,14 @@ bool copy::operator()(const QString& offset, bool dryRun) return err.value() == 0; } +/// qDebug print support for the LinkPair struct +QDebug operator<<(QDebug debug, const LinkPair& lp) +{ + QDebugStateSaver saver(debug); + + debug.nospace() << "LinkPair{ src: " << lp.src << " , dst: " << lp.dst << " }"; + return debug; +} bool create_link::operator()(const QString& offset, bool dryRun) { @@ -265,7 +273,7 @@ bool create_link::operator()(const QString& offset, bool dryRun) * @param offset subdirectory form src to link to dest * @return if there was an error during the attempt to link */ -void create_link::make_link_list( const QString& offset) +void create_link::make_link_list(const QString& offset) { for (auto pair : m_path_pairs) { const QString& srcPath = pair.src; @@ -297,14 +305,26 @@ void create_link::make_link_list( const QString& offset) link_file(src, ""); } else { if (m_debug) - qDebug() << "linking recursivly:" << src << "to" << dst; + qDebug() << "linking recursivly:" << src << "to" << dst << "max_depth:" << m_max_depth; QDir src_dir(src); QDirIterator source_it(src, QDir::Filter::Files | QDir::Filter::Hidden, QDirIterator::Subdirectories); + QStringList linkedPaths; + while (source_it.hasNext()) { auto src_path = source_it.next(); auto relative_path = src_dir.relativeFilePath(src_path); + if (m_max_depth >= 0 && PathDepth(relative_path) > m_max_depth){ + relative_path = PathTruncate(relative_path, m_max_depth); + src_path = src_dir.filePath(relative_path); + if (linkedPaths.contains(src_path)) { + continue; + } + } + + linkedPaths.append(src_path); + link_file(src_path, relative_path); } } @@ -312,7 +332,7 @@ void create_link::make_link_list( const QString& offset) } bool create_link::make_links() -{ +{ for (auto link : m_links_to_make) { QString src_path = link.src; @@ -556,11 +576,49 @@ QString PathCombine(const QString& path1, const QString& path2, const QString& p return PathCombine(PathCombine(path1, path2, path3), path4); } -QString AbsolutePath(QString path) +QString AbsolutePath(const QString& path) { return QFileInfo(path).absolutePath(); } +int PathDepth(const QString& path) +{ + if (path.isEmpty()) return 0; + + QFileInfo info(path); + + auto parts = QDir::toNativeSeparators(info.path()).split(QDir::separator(), Qt::SkipEmptyParts); + + int numParts = QDir::toNativeSeparators(info.path()).split(QDir::separator(), Qt::SkipEmptyParts).length(); + numParts -= parts.count("."); + numParts -= parts.count("..") * 2; + + return numParts; +} + +QString PathTruncate(const QString& path, int depth) +{ + if (path.isEmpty() || (depth < 0) ) return ""; + + QString trunc = QFileInfo(path).path(); + + if (PathDepth(trunc) > depth ) { + return PathTruncate(trunc, depth); + } + + auto parts = QDir::toNativeSeparators(trunc).split(QDir::separator(), Qt::SkipEmptyParts); + if (parts.startsWith(".") && !path.startsWith(".")) { + parts.removeFirst(); + } + if (path.startsWith(QDir::separator())) { + parts.prepend(""); + } + + trunc = parts.join(QDir::separator()); + + return trunc; +} + QString ResolveExecutable(QString path) { if (path.isEmpty()) { diff --git a/launcher/FileSystem.h b/launcher/FileSystem.h index 83ff99a4..7485206a 100644 --- a/launcher/FileSystem.h +++ b/launcher/FileSystem.h @@ -200,6 +200,11 @@ class create_link : public QObject { m_recursive = recursive; return *this; } + create_link& setMaxDepth(int depth) + { + m_max_depth = depth; + return *this; + } create_link& debug(bool d) { m_debug = d; @@ -239,6 +244,9 @@ class create_link : public QObject { bool m_whitelist = false; bool m_recursive = true; + /// @brief >= -1 = infinite, 0 = link files at src/* to dest/*, 1 = link files at src/*/* to dest/*/*, etc. + int m_max_depth = -1; + QList m_path_pairs; QList m_path_results; QList m_links_to_make; @@ -272,7 +280,25 @@ QString PathCombine(const QString& path1, const QString& path2); QString PathCombine(const QString& path1, const QString& path2, const QString& path3); QString PathCombine(const QString& path1, const QString& path2, const QString& path3, const QString& path4); -QString AbsolutePath(QString path); +QString AbsolutePath(const QString& path); + +/** + * @brief depth of path. "foo.txt" -> 0 , "bar/foo.txt" -> 1, /baz/bar/foo.txt -> 2, etc. + * + * @param path path to measure + * @return int number of componants before base path + */ +int PathDepth(const QString& path); + + +/** + * @brief cut off segments of path untill it is a max of length depth + * + * @param path path to truncate + * @param depth max depth of new path + * @return QString truncated path + */ +QString PathTruncate(const QString& path, int depth); /** * Resolve an executable diff --git a/launcher/InstanceCopyPrefs.cpp b/launcher/InstanceCopyPrefs.cpp index c03d0aae..0650002b 100644 --- a/launcher/InstanceCopyPrefs.cpp +++ b/launcher/InstanceCopyPrefs.cpp @@ -16,8 +16,13 @@ bool InstanceCopyPrefs::allTrue() const copyScreenshots; } + // Returns a single RegEx string of the selected folders/files to filter out (ex: ".minecraft/saves|.minecraft/server.dat") QString InstanceCopyPrefs::getSelectedFiltersAsRegex() const +{ + return getSelectedFiltersAsRegex({}); +} +QString InstanceCopyPrefs::getSelectedFiltersAsRegex(const QStringList& additionalFilters) const { QStringList filters; @@ -42,6 +47,10 @@ QString InstanceCopyPrefs::getSelectedFiltersAsRegex() const if(!copyScreenshots) filters << "screenshots"; + for (auto filter : additionalFilters) { + filters << filter; + } + // If we have any filters to add, join them as a single regex string to return: if (!filters.isEmpty()) { const QString MC_ROOT = "[.]?minecraft/"; diff --git a/launcher/InstanceCopyPrefs.h b/launcher/InstanceCopyPrefs.h index 14ae9551..c7bde068 100644 --- a/launcher/InstanceCopyPrefs.h +++ b/launcher/InstanceCopyPrefs.h @@ -10,6 +10,7 @@ struct InstanceCopyPrefs { public: [[nodiscard]] bool allTrue() const; [[nodiscard]] QString getSelectedFiltersAsRegex() const; + [[nodiscard]] QString getSelectedFiltersAsRegex(const QStringList& additionalFilters) const; // Getters [[nodiscard]] bool isCopySavesEnabled() const; [[nodiscard]] bool isKeepPlaytimeEnabled() const; diff --git a/launcher/InstanceCopyTask.cpp b/launcher/InstanceCopyTask.cpp index ba0052fa..40babd0f 100644 --- a/launcher/InstanceCopyTask.cpp +++ b/launcher/InstanceCopyTask.cpp @@ -4,13 +4,14 @@ #include "NullInstance.h" #include "pathmatcher/RegexpMatcher.h" #include +#include InstanceCopyTask::InstanceCopyTask(InstancePtr origInstance, const InstanceCopyPrefs& prefs) { m_origInstance = origInstance; m_keepPlaytime = prefs.isKeepPlaytimeEnabled(); - QString filters = prefs.getSelectedFiltersAsRegex(); + m_useLinks = prefs.isUseSymLinksEnabled(); @@ -18,6 +19,14 @@ InstanceCopyTask::InstanceCopyTask(InstancePtr origInstance, const InstanceCopyP m_useHardLinks = prefs.isLinkRecursivelyEnabled() && prefs.isUseHardLinksEnabled(); m_copySaves = prefs.isLinkRecursivelyEnabled() && prefs.isDontLinkSavesEnabled() && prefs.isCopySavesEnabled(); m_useClone = prefs.isUseCloneEnabled(); + + QString filters = prefs.getSelectedFiltersAsRegex(); + if (m_useLinks || m_useHardLinks) { + if (!filters.isEmpty()) filters += "|"; + filters += "instance.cfg"; + } + + qDebug() << "CopyFilters:" << filters; if (!filters.isEmpty()) { @@ -46,9 +55,10 @@ void InstanceCopyTask::executeTask() folderClone.matcher(m_matcher.get()); return folderClone(); - } else if (m_useLinks) { + } else if (m_useLinks || m_useHardLinks) { FS::create_link folderLink(m_origInstance->instanceRoot(), m_stagingPath); - folderLink.linkRecursively(m_linkRecursively).useHardLinks(m_useHardLinks).matcher(m_matcher.get()); + int depth = m_linkRecursively ? -1 : 0; // we need to at least link the top level instead of the instance folder + folderLink.linkRecursively(true).setMaxDepth(depth).useHardLinks(m_useHardLinks).matcher(m_matcher.get()); bool there_were_errors = false; diff --git a/launcher/MMCZip.cpp b/launcher/MMCZip.cpp index b4b663c1..1a336375 100644 --- a/launcher/MMCZip.cpp +++ b/launcher/MMCZip.cpp @@ -102,8 +102,13 @@ bool MMCZip::compressDirFiles(QuaZip *zip, QString dir, QFileInfoList files, boo for (auto e : files) { auto filePath = directory.relativeFilePath(e.absoluteFilePath()); auto srcPath = e.absoluteFilePath(); - if (followSymlinks) - srcPath = e.canonicalFilePath(); + if (followSymlinks) { + if (e.isSymLink()) { + srcPath = e.symLinkTarget(); + } else { + srcPath = e.canonicalFilePath(); + } + } if( !JlCompress::compressFile(zip, srcPath, filePath)) return false; } @@ -119,7 +124,7 @@ bool MMCZip::compressDirFiles(QString fileCompressed, QString dir, QFileInfoList return false; } - auto result = compressDirFiles(&zip, dir, files); + auto result = compressDirFiles(&zip, dir, files, followSymlinks); zip.close(); if(zip.getZipError()!=0) { diff --git a/launcher/ui/dialogs/CopyInstanceDialog.cpp b/launcher/ui/dialogs/CopyInstanceDialog.cpp index c6cbefcf..9fe129f1 100644 --- a/launcher/ui/dialogs/CopyInstanceDialog.cpp +++ b/launcher/ui/dialogs/CopyInstanceDialog.cpp @@ -171,17 +171,18 @@ void CopyInstanceDialog::updateSelectAllCheckbox() void CopyInstanceDialog::updateUseCloneCheckbox() { - ui->useCloneCheckbox->setEnabled(m_cloneSupported && !ui->linkFilesGroup->isChecked()); - ui->useCloneCheckbox->setChecked(m_cloneSupported && m_selectedOptions.isUseCloneEnabled()); + ui->useCloneCheckbox->setEnabled(m_cloneSupported && !ui->symbolicLinksCheckbox->isChecked() && !ui->hardLinksCheckbox->isChecked()); + ui->useCloneCheckbox->setChecked(m_cloneSupported && m_selectedOptions.isUseCloneEnabled() && !ui->symbolicLinksCheckbox->isChecked() && + !ui->hardLinksCheckbox->isChecked()); } void CopyInstanceDialog::updateLinkOptions() { - ui->symbolicLinksCheckbox->setEnabled(m_linkSupported && !ui->hardLinksCheckbox->isChecked()); - ui->hardLinksCheckbox->setEnabled(m_linkSupported && !ui->symbolicLinksCheckbox->isChecked()); + ui->symbolicLinksCheckbox->setEnabled(m_linkSupported && !ui->hardLinksCheckbox->isChecked() && !ui->useCloneCheckbox->isChecked()); + ui->hardLinksCheckbox->setEnabled(m_linkSupported && !ui->symbolicLinksCheckbox->isChecked() && !ui->useCloneCheckbox->isChecked()); - ui->symbolicLinksCheckbox->setChecked(m_linkSupported && m_selectedOptions.isUseSymLinksEnabled()); - ui->hardLinksCheckbox->setChecked(m_linkSupported && m_selectedOptions.isUseHardLinksEnabled()); + ui->symbolicLinksCheckbox->setChecked(m_linkSupported && m_selectedOptions.isUseSymLinksEnabled() && !ui->useCloneCheckbox->isChecked()); + ui->hardLinksCheckbox->setChecked(m_linkSupported && m_selectedOptions.isUseHardLinksEnabled() && !ui->useCloneCheckbox->isChecked()); bool linksInUse = (ui->symbolicLinksCheckbox->isChecked() || ui->hardLinksCheckbox->isChecked()); ui->recursiveLinkCheckbox->setEnabled(m_linkSupported && linksInUse && !ui->hardLinksCheckbox->isChecked()); @@ -278,16 +279,14 @@ void CopyInstanceDialog::on_hardLinksCheckbox_stateChanged(int state) if (state == Qt::Checked && !ui->recursiveLinkCheckbox->isChecked()) { ui->recursiveLinkCheckbox->setChecked(true); } + updateUseCloneCheckbox(); updateLinkOptions(); } void CopyInstanceDialog::on_recursiveLinkCheckbox_stateChanged(int state) { m_selectedOptions.enableLinkRecursively(state == Qt::Checked); - if (state != Qt::Checked) { - ui->hardLinksCheckbox->setChecked(false); - ui->dontLinkSavesCheckbox->setChecked(false); - } + updateLinkOptions(); } @@ -299,6 +298,6 @@ void CopyInstanceDialog::on_dontLinkSavesCheckbox_stateChanged(int state) void CopyInstanceDialog::on_useCloneCheckbox_stateChanged(int state) { m_selectedOptions.enableUseClone(m_cloneSupported && (state == Qt::Checked)); - ui->linkFilesGroup->setEnabled(!m_selectedOptions.isUseCloneEnabled()); updateUseCloneCheckbox(); + updateLinkOptions(); } \ No newline at end of file diff --git a/tests/FileSystem_test.cpp b/tests/FileSystem_test.cpp index 4ccc4003..4418dd62 100644 --- a/tests/FileSystem_test.cpp +++ b/tests/FileSystem_test.cpp @@ -57,6 +57,11 @@ class LinkTask : public Task { m_lnk->whitelist(b); } + void setMaxDepth(int depth) + { + m_lnk->setMaxDepth(depth); + } + private: void executeTask() override { @@ -630,6 +635,149 @@ slots: QVERIFY(target_dir.entryList(filter).contains("pack.mcmeta")); } } + + void test_link_with_max_depth() + { + QString folder = QFINDTESTDATA("testdata/FileSystem/test_folder"); + auto f = [&folder, this]() + { + QTemporaryDir tempDir; + tempDir.setAutoRemove(true); + qDebug() << "From:" << folder << "To:" << tempDir.path(); + + QDir target_dir(FS::PathCombine(tempDir.path(), "test_folder")); + qDebug() << tempDir.path(); + qDebug() << target_dir.path(); + + LinkTask lnk_tsk(folder, target_dir.path()); + lnk_tsk.linkRecursively(true); + lnk_tsk.setMaxDepth(0); + QObject::connect(&lnk_tsk, &Task::finished, [&]{ + QVERIFY2(lnk_tsk.wasSuccessful(), "Task finished but was not successful when it should have been."); + }); + lnk_tsk.start(); + + QVERIFY2(QTest::qWaitFor([&]() { + return lnk_tsk.isFinished(); + }, 100000), "Task didn't finish as it should."); + + QVERIFY(!QFileInfo(target_dir.path()).isSymLink()); + + auto filter = QDir::Filter::Files | QDir::Filter::Dirs | QDir::Filter::Hidden; + for(auto entry: target_dir.entryList(filter)) + { + qDebug() << entry; + if (entry == "." || entry == "..") continue; + QFileInfo entry_lnk_info(target_dir.filePath(entry)); + QVERIFY(entry_lnk_info.isSymLink()); + } + + QFileInfo lnk_info(target_dir.path()); + QVERIFY(lnk_info.exists()); + QVERIFY(!lnk_info.isSymLink()); + + QVERIFY(target_dir.entryList().contains("pack.mcmeta")); + QVERIFY(target_dir.entryList().contains("assets")); + + + }; + + // first try variant without trailing / + QVERIFY(!folder.endsWith('/')); + f(); + + // then variant with trailing / + folder.append('/'); + QVERIFY(folder.endsWith('/')); + f(); + } + + void test_link_with_no_max_depth() + { + QString folder = QFINDTESTDATA("testdata/FileSystem/test_folder"); + auto f = [&folder]() + { + QTemporaryDir tempDir; + tempDir.setAutoRemove(true); + qDebug() << "From:" << folder << "To:" << tempDir.path(); + + QDir target_dir(FS::PathCombine(tempDir.path(), "test_folder")); + qDebug() << tempDir.path(); + qDebug() << target_dir.path(); + + LinkTask lnk_tsk(folder, target_dir.path()); + lnk_tsk.linkRecursively(true); + lnk_tsk.setMaxDepth(-1); + QObject::connect(&lnk_tsk, &Task::finished, [&]{ + QVERIFY2(lnk_tsk.wasSuccessful(), "Task finished but was not successful when it should have been."); + }); + lnk_tsk.start(); + + QVERIFY2(QTest::qWaitFor([&]() { + return lnk_tsk.isFinished(); + }, 100000), "Task didn't finish as it should."); + + + std::function verify_check = [&](QString check_path) { + QDir check_dir(check_path); + auto filter = QDir::Filter::Files | QDir::Filter::Dirs | QDir::Filter::Hidden; + for(auto entry: check_dir.entryList(filter)) + { + QFileInfo entry_lnk_info(check_dir.filePath(entry)); + qDebug() << entry << check_dir.filePath(entry); + if (!entry_lnk_info.isDir()){ + QVERIFY(entry_lnk_info.isSymLink()); + } else if (entry != "." && entry != "..") { + qDebug() << "Decending tree to verify symlinks:" << check_dir.filePath(entry); + verify_check(entry_lnk_info.filePath()); + } + } + }; + + verify_check(target_dir.path()); + + + QFileInfo lnk_info(target_dir.path()); + QVERIFY(lnk_info.exists()); + + QVERIFY(target_dir.entryList().contains("pack.mcmeta")); + QVERIFY(target_dir.entryList().contains("assets")); + }; + + // first try variant without trailing / + QVERIFY(!folder.endsWith('/')); + f(); + + // then variant with trailing / + folder.append('/'); + QVERIFY(folder.endsWith('/')); + f(); + } + + void test_path_depth() { + QCOMPARE_EQ(FS::PathDepth(""), 0); + QCOMPARE_EQ(FS::PathDepth("."), 0); + QCOMPARE_EQ(FS::PathDepth("foo.txt"), 0); + QCOMPARE_EQ(FS::PathDepth("./foo.txt"), 0); + QCOMPARE_EQ(FS::PathDepth("./bar/foo.txt"), 1); + QCOMPARE_EQ(FS::PathDepth("../bar/foo.txt"), 0); + QCOMPARE_EQ(FS::PathDepth("/bar/foo.txt"), 1); + QCOMPARE_EQ(FS::PathDepth("baz/bar/foo.txt"), 2); + QCOMPARE_EQ(FS::PathDepth("/baz/bar/foo.txt"), 2); + QCOMPARE_EQ(FS::PathDepth("./baz/bar/foo.txt"), 2); + QCOMPARE_EQ(FS::PathDepth("/baz/../bar/foo.txt"), 1); + } + + void test_path_trunc() { + QCOMPARE_EQ(FS::PathTruncate("", 0), ""); + QCOMPARE_EQ(FS::PathTruncate("foo.txt", 0), ""); + QCOMPARE_EQ(FS::PathTruncate("foo.txt", 1), ""); + QCOMPARE_EQ(FS::PathTruncate("./bar/foo.txt", 0), "./bar"); + QCOMPARE_EQ(FS::PathTruncate("./bar/foo.txt", 1), "./bar"); + QCOMPARE_EQ(FS::PathTruncate("/bar/foo.txt", 1), "/bar"); + QCOMPARE_EQ(FS::PathTruncate("bar/foo.txt", 1), "bar"); + QCOMPARE_EQ(FS::PathTruncate("baz/bar/foo.txt", 2), "baz/bar"); + } }; QTEST_GUILESS_MAIN(FileSystemTest) -- cgit From 9f441a9678f56c5fb5efbc415b3faff176609b9c Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Sat, 11 Feb 2023 22:51:53 -0800 Subject: feat: Add UAC icon when symlinking on windows. Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- launcher/CMakeLists.txt | 8 -------- launcher/ui/dialogs/CopyInstanceDialog.cpp | 23 ++++++++++++++++++----- launcher/ui/dialogs/CopyInstanceDialog.ui | 5 ++++- 3 files changed, 22 insertions(+), 14 deletions(-) (limited to 'launcher/ui/dialogs') diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index dd62893c..b47f5746 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -1149,14 +1149,6 @@ if(WIN32) SET_TARGET_PROPERTIES("${Launcher_Name}_filelink" PROPERTIES INSTALL_RPATH "${Launcher_BINARY_RPATH}") endif() - # may be unnessacery with manifest - if(CMAKE_GENERATOR MATCHES "Visual Studio") - SET_TARGET_PROPERTIES("${Launcher_Name}_filelink" PROPERTIES LINK_FLAGS "/level='requireAdministrator' /uiAccess='false' /SUBSYSTEM:CONSOLE") - # else() # link arg /MANIFESTUAC only works with MSVC - # SET_TARGET_PROPERTIES("${Launcher_Name}_filelink" PROPERTIES LINK_FLAGS "/MANIFESTUAC:\"level='requireAdministrator' uiAccess='false'\" /SUBSYSTEM:CONSOLE") - endif() - - install(TARGETS "${Launcher_Name}_filelink" BUNDLE DESTINATION "." COMPONENT Runtime LIBRARY DESTINATION ${LIBRARY_DEST_DIR} COMPONENT Runtime diff --git a/launcher/ui/dialogs/CopyInstanceDialog.cpp b/launcher/ui/dialogs/CopyInstanceDialog.cpp index 9fe129f1..495e98e9 100644 --- a/launcher/ui/dialogs/CopyInstanceDialog.cpp +++ b/launcher/ui/dialogs/CopyInstanceDialog.cpp @@ -93,17 +93,25 @@ CopyInstanceDialog::CopyInstanceDialog(InstancePtr original, QWidget *parent) ui->recursiveLinkCheckbox->setChecked(m_selectedOptions.isLinkRecursivelyEnabled()); ui->dontLinkSavesCheckbox->setChecked(m_selectedOptions.isDontLinkSavesEnabled()); - auto detectedOS = FS::statFS(m_original->instanceRoot()).fsType; + auto detectedFS = FS::statFS(m_original->instanceRoot()).fsType; - m_cloneSupported = FS::canCloneOnFS(detectedOS); - m_linkSupported = FS::canLinkOnFS(detectedOS); + m_cloneSupported = FS::canCloneOnFS(detectedFS); + m_linkSupported = FS::canLinkOnFS(detectedFS); if (m_cloneSupported) { - ui->cloneSupportedLabel->setText(tr("Reflinks are supported on %1").arg(FS::getFilesystemTypeName(detectedOS))); + ui->cloneSupportedLabel->setText(tr("Reflinks are supported on %1").arg(FS::getFilesystemTypeName(detectedFS))); } else { - ui->cloneSupportedLabel->setText(tr("Reflinks aren't supported on %1").arg(FS::getFilesystemTypeName(detectedOS))); + ui->cloneSupportedLabel->setText(tr("Reflinks aren't supported on %1").arg(FS::getFilesystemTypeName(detectedFS))); } +#if defined(Q_OS_WIN) + ui->symbolicLinksCheckbox->setIcon(style()->standardIcon(QStyle::SP_VistaShield)); + ui->symbolicLinksCheckbox->setToolTip( + tr("Use symbolic links instead of copying files.") + + tr("\nOn windows symbolic links may require admin permision to create.") + ); +#endif + updateLinkOptions(); updateUseCloneCheckbox(); @@ -189,6 +197,11 @@ void CopyInstanceDialog::updateLinkOptions() ui->dontLinkSavesCheckbox->setEnabled(m_linkSupported && linksInUse); ui->recursiveLinkCheckbox->setChecked(m_linkSupported && linksInUse && m_selectedOptions.isLinkRecursivelyEnabled()); ui->dontLinkSavesCheckbox->setChecked(m_linkSupported && linksInUse && m_selectedOptions.isDontLinkSavesEnabled()); + +#if defined(Q_OS_WIN) + auto OkButton = ui->buttonBox->button(QDialogButtonBox::Ok); + OkButton->setIcon(m_selectedOptions.isUseSymLinksEnabled() ? style()->standardIcon(QStyle::SP_VistaShield) : QIcon()); +#endif } void CopyInstanceDialog::on_iconButton_clicked() diff --git a/launcher/ui/dialogs/CopyInstanceDialog.ui b/launcher/ui/dialogs/CopyInstanceDialog.ui index 7bf75c2d..58442f73 100644 --- a/launcher/ui/dialogs/CopyInstanceDialog.ui +++ b/launcher/ui/dialogs/CopyInstanceDialog.ui @@ -269,7 +269,7 @@ true - Use hard links instead of symbolic links + Use hard links instead of symbolic links. Use hard links @@ -307,6 +307,9 @@ Use symbloic links + + Use symbolic links instead of copying files. + -- cgit From a0e03c41c034ddbc330789c7639f02a4a8ac1a10 Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Sun, 12 Feb 2023 02:59:52 -0700 Subject: fix: typos Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- launcher/minecraft/WorldList.cpp | 6 +++--- launcher/minecraft/mod/ModFolderModel.cpp | 4 ++-- launcher/minecraft/mod/ResourceFolderModel.cpp | 4 ++-- launcher/minecraft/mod/ResourcePackFolderModel.cpp | 4 ++-- launcher/ui/dialogs/CopyInstanceDialog.ui | 2 +- 5 files changed, 10 insertions(+), 10 deletions(-) (limited to 'launcher/ui/dialogs') diff --git a/launcher/minecraft/WorldList.cpp b/launcher/minecraft/WorldList.cpp index 1262fa1d..43733110 100644 --- a/launcher/minecraft/WorldList.cpp +++ b/launcher/minecraft/WorldList.cpp @@ -213,7 +213,7 @@ QVariant WorldList::data(const QModelIndex &index, int role) const case InfoColumn: if (world.isSymLinkUnder(instDirPath())) { - return tr("This world is symbolicly linked from elsewhere."); + return tr("This world is symbolically linked from elsewhere."); } if (world.isMoreThanOneHardLink()) { return tr("\nThis world is hard linked elsewhere."); @@ -237,11 +237,11 @@ QVariant WorldList::data(const QModelIndex &index, int role) const { if (column == InfoColumn) { if (world.isSymLinkUnder(instDirPath())) { - return tr("Warning: This world is symbolicly linked from elsewhere. Editing it will also change the origonal") + + return tr("Warning: This world is symbolically linked from elsewhere. Editing it will also change the original") + tr("\nCanonical Path: %1").arg(world.canonicalFilePath()); } if (world.isMoreThanOneHardLink()) { - return tr("Warning: This world is hard linked elsewhere. Editing it will also change the origonal"); + return tr("Warning: This world is hard linked elsewhere. Editing it will also change the original"); } } return world.folderName(); diff --git a/launcher/minecraft/mod/ModFolderModel.cpp b/launcher/minecraft/mod/ModFolderModel.cpp index e2053f92..943f30ad 100644 --- a/launcher/minecraft/mod/ModFolderModel.cpp +++ b/launcher/minecraft/mod/ModFolderModel.cpp @@ -104,12 +104,12 @@ QVariant ModFolderModel::data(const QModelIndex &index, int role) const if (column == NAME_COLUMN) { if (at(row)->isSymLinkUnder(instDirPath())) { return m_resources[row]->internal_id() + - tr("\nWarning: This resource is symbolicly linked from elsewhere. Editing it will also change the origonal") + + tr("\nWarning: This resource is symbolically linked from elsewhere. Editing it will also change the original") + tr("\nCanonical Path: %1").arg(at(row)->fileinfo().canonicalFilePath()); } if (at(row)->isMoreThanOneHardLink()) { return m_resources[row]->internal_id() + - tr("\nWarning: This resource is hard linked elsewhere. Editing it will also change the origonal"); + tr("\nWarning: This resource is hard linked elsewhere. Editing it will also change the original"); } } return m_resources[row]->internal_id(); diff --git a/launcher/minecraft/mod/ResourceFolderModel.cpp b/launcher/minecraft/mod/ResourceFolderModel.cpp index d1748c1c..95b7651f 100644 --- a/launcher/minecraft/mod/ResourceFolderModel.cpp +++ b/launcher/minecraft/mod/ResourceFolderModel.cpp @@ -424,12 +424,12 @@ QVariant ResourceFolderModel::data(const QModelIndex& index, int role) const if (column == NAME_COLUMN) { if (at(row).isSymLinkUnder(instDirPath())) { return m_resources[row]->internal_id() + - tr("\nWarning: This resource is symbolicly linked from elsewhere. Editing it will also change the origonal") + + tr("\nWarning: This resource is symbolically linked from elsewhere. Editing it will also change the original") + tr("\nCanonical Path: %1").arg(at(row).fileinfo().canonicalFilePath());; } if (at(row).isMoreThanOneHardLink()) { return m_resources[row]->internal_id() + - tr("\nWarning: This resource is hard linked elsewhere. Editing it will also change the origonal"); + tr("\nWarning: This resource is hard linked elsewhere. Editing it will also change the original"); } } diff --git a/launcher/minecraft/mod/ResourcePackFolderModel.cpp b/launcher/minecraft/mod/ResourcePackFolderModel.cpp index 56584a34..1fcfa909 100644 --- a/launcher/minecraft/mod/ResourcePackFolderModel.cpp +++ b/launcher/minecraft/mod/ResourcePackFolderModel.cpp @@ -96,12 +96,12 @@ QVariant ResourcePackFolderModel::data(const QModelIndex& index, int role) const if (column == NAME_COLUMN) { if (at(row)->isSymLinkUnder(instDirPath())) { return m_resources[row]->internal_id() + - tr("\nWarning: This resource is symbolicly linked from elsewhere. Editing it will also change the origonal") + + tr("\nWarning: This resource is symbolically linked from elsewhere. Editing it will also change the original") + tr("\nCanonical Path: %1").arg(at(row)->fileinfo().canonicalFilePath());; } if (at(row)->isMoreThanOneHardLink()) { return m_resources[row]->internal_id() + - tr("\nWarning: This resource is hard linked elsewhere. Editing it will also change the origonal"); + tr("\nWarning: This resource is hard linked elsewhere. Editing it will also change the original"); } } return m_resources[row]->internal_id(); diff --git a/launcher/ui/dialogs/CopyInstanceDialog.ui b/launcher/ui/dialogs/CopyInstanceDialog.ui index 58442f73..009f5b88 100644 --- a/launcher/ui/dialogs/CopyInstanceDialog.ui +++ b/launcher/ui/dialogs/CopyInstanceDialog.ui @@ -305,7 +305,7 @@ - Use symbloic links + Use symbolic links Use symbolic links instead of copying files. -- cgit From e0ef86340f72ce508034815f1c5f8c695d31140d Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Thu, 16 Feb 2023 03:31:04 -0700 Subject: feat: connect new help button help-pages/instance-copy Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- launcher/ui/dialogs/CopyInstanceDialog.cpp | 11 +++++++++++ launcher/ui/dialogs/CopyInstanceDialog.h | 3 +++ 2 files changed, 14 insertions(+) (limited to 'launcher/ui/dialogs') diff --git a/launcher/ui/dialogs/CopyInstanceDialog.cpp b/launcher/ui/dialogs/CopyInstanceDialog.cpp index 495e98e9..62c0bb39 100644 --- a/launcher/ui/dialogs/CopyInstanceDialog.cpp +++ b/launcher/ui/dialogs/CopyInstanceDialog.cpp @@ -37,6 +37,7 @@ #include #include "Application.h" +#include "BuildConfig.h" #include "CopyInstanceDialog.h" #include "ui_CopyInstanceDialog.h" @@ -47,6 +48,7 @@ #include "BaseInstance.h" #include "InstanceList.h" #include "FileSystem.h" +#include "DesktopServices.h" CopyInstanceDialog::CopyInstanceDialog(InstancePtr original, QWidget *parent) :QDialog(parent), ui(new Ui::CopyInstanceDialog), m_original(original) @@ -114,6 +116,9 @@ CopyInstanceDialog::CopyInstanceDialog(InstancePtr original, QWidget *parent) updateLinkOptions(); updateUseCloneCheckbox(); + + auto HelpButton = ui->buttonBox->button(QDialogButtonBox::Help); + connect(HelpButton, &QPushButton::clicked, this, &CopyInstanceDialog::help); } @@ -157,6 +162,12 @@ const InstanceCopyPrefs& CopyInstanceDialog::getChosenOptions() const return m_selectedOptions; } + +void CopyInstanceDialog::help() +{ + DesktopServices::openUrl(QUrl(BuildConfig.HELP_URL.arg("instance-copy"))); +} + void CopyInstanceDialog::checkAllCheckboxes(const bool& b) { ui->keepPlaytimeCheckbox->setChecked(b); diff --git a/launcher/ui/dialogs/CopyInstanceDialog.h b/launcher/ui/dialogs/CopyInstanceDialog.h index 2dea3795..c447bee9 100644 --- a/launcher/ui/dialogs/CopyInstanceDialog.h +++ b/launcher/ui/dialogs/CopyInstanceDialog.h @@ -42,6 +42,9 @@ public: QString iconKey() const; const InstanceCopyPrefs& getChosenOptions() const; +public slots: + void help(); + private slots: void on_iconButton_clicked(); -- cgit From ae289c923c4f896dca7e6696eef7ca35b10be9bf Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Wed, 22 Feb 2023 17:40:07 -0700 Subject: fix: clean up initial review comments (flowin) Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- launcher/FileSystem.cpp | 15 ++-- launcher/FileSystem.h | 6 +- launcher/InstanceCopyTask.cpp | 6 +- launcher/ui/dialogs/CopyInstanceDialog.ui | 129 ++++++++++++++++-------------- tests/FileSystem_test.cpp | 6 +- 5 files changed, 82 insertions(+), 80 deletions(-) (limited to 'launcher/ui/dialogs') diff --git a/launcher/FileSystem.cpp b/launcher/FileSystem.cpp index 45c73d24..2bd0cf52 100644 --- a/launcher/FileSystem.cpp +++ b/launcher/FileSystem.cpp @@ -36,10 +36,6 @@ */ #include "FileSystem.h" -#include -#include -#include -#include #include "BuildConfig.h" @@ -48,6 +44,7 @@ #include #include #include +#include #include #include #include @@ -435,7 +432,7 @@ bool create_link::make_links() return true; } -void create_link::runPrivlaged(const QString& offset) +void create_link::runPrivileged(const QString& offset) { m_linked = 0; // reset counter m_path_results.clear(); @@ -506,10 +503,10 @@ void create_link::runPrivlaged(const QString& offset) in >> err_value; result.err_value = err_value; if (result.err_value) { - qDebug() << "privlaged link fail" << result.src << "to" << result.dst << "code" << result.err_value << result.err_msg; + qDebug() << "privileged link fail" << result.src << "to" << result.dst << "code" << result.err_value << result.err_msg; emit linkFailed(result.src, result.dst, result.err_msg, result.err_value); } else { - qDebug() << "privlaged link success" << result.src << "to" << result.dst; + qDebug() << "privileged link success" << result.src << "to" << result.dst; m_linked++; emit fileLinked(result.src, result.dst); } @@ -533,7 +530,7 @@ void create_link::runPrivlaged(const QString& offset) } ExternalLinkFileProcess* linkFileProcess = new ExternalLinkFileProcess(serverName, m_useHardLinks, this); - connect(linkFileProcess, &ExternalLinkFileProcess::processExited, this, [&]() { emit finishedPrivlaged(gotResults); }); + connect(linkFileProcess, &ExternalLinkFileProcess::processExited, this, [&]() { emit finishedPrivileged(gotResults); }); connect(linkFileProcess, &ExternalLinkFileProcess::finished, linkFileProcess, &QObject::deleteLater); linkFileProcess->start(); @@ -1041,7 +1038,7 @@ FilesystemInfo statFS(const QString& path) QStorageInfo storage_info(NearestExistentAncestor(path)); - info.fsTypeName = QString::fromStdString(storage_info.fileSystemType().toStdString()); + info.fsTypeName = storage_info.fileSystemType(); info.fsType = getFilesystemTypeFuzzy(info.fsTypeName); diff --git a/launcher/FileSystem.h b/launcher/FileSystem.h index 84526c11..71175bb4 100644 --- a/launcher/FileSystem.h +++ b/launcher/FileSystem.h @@ -220,8 +220,8 @@ class create_link : public QObject { int totalLinked() { return m_linked; } - void runPrivlaged() { runPrivlaged(QString()); } - void runPrivlaged(const QString& offset); + void runPrivileged() { runPrivileged(QString()); } + void runPrivileged(const QString& offset); QList getResults() { return m_path_results; } @@ -230,7 +230,7 @@ class create_link : public QObject { void fileLinked(const QString& srcName, const QString& dstName); void linkFailed(const QString& srcName, const QString& dstName, const QString& err_msg, int err_value); void finished(); - void finishedPrivlaged(bool gotResults); + void finishedPrivileged(bool gotResults); private: diff --git a/launcher/InstanceCopyTask.cpp b/launcher/InstanceCopyTask.cpp index e0a4de0b..5ef7a7fd 100644 --- a/launcher/InstanceCopyTask.cpp +++ b/launcher/InstanceCopyTask.cpp @@ -72,14 +72,14 @@ void InstanceCopyTask::executeTask() QEventLoop loop; bool got_priv_results = false; - connect(&folderLink, &FS::create_link::finishedPrivlaged, this, [&](bool gotResults){ + connect(&folderLink, &FS::create_link::finishedPrivileged, this, [&](bool gotResults){ if (!gotResults) { - qDebug() << "Privlaged run exited without results!"; + qDebug() << "Privileged run exited without results!"; } got_priv_results = gotResults; loop.quit(); }); - folderLink.runPrivlaged(); + folderLink.runPrivileged(); loop.exec(); // wait for the finished signal diff --git a/launcher/ui/dialogs/CopyInstanceDialog.ui b/launcher/ui/dialogs/CopyInstanceDialog.ui index 009f5b88..3101acec 100644 --- a/launcher/ui/dialogs/CopyInstanceDialog.ui +++ b/launcher/ui/dialogs/CopyInstanceDialog.ui @@ -9,8 +9,8 @@ 0 0 - 527 - 699 + 531 + 653 @@ -112,35 +112,19 @@ - - - - - - - 0 - 0 - - - - Qt::LeftToRight - - - Select all - - - false - - - - - Instance Copy Options + + + + Keep play time + + + @@ -151,6 +135,16 @@ + + + + true + + + Copy resource packs + + + @@ -161,13 +155,6 @@ - - - - Copy saves - - - @@ -182,33 +169,49 @@ - - - - true - + + - Copy resource packs + Copy saves - - + + - Keep play time + Copy screenshots - - + + + + + 0 + 0 + + + + Qt::LeftToRight + - Copy screenshots + Select all + + + false + + + + Qt::Horizontal + + + @@ -250,7 +253,7 @@ - + 6 @@ -263,24 +266,14 @@ 6 - - - - true - - - Use hard links instead of symbolic links. - - - Use hard links - - - false + + Link each resource individually instead of linking whole folders at once + Link files recursively @@ -302,14 +295,27 @@ - - + + + + true + + + Use hard links instead of copying files. + - Use symbolic links + Use hard links + + + + Use symbolic links instead of copying files. + + Use symbolic links + @@ -373,7 +379,6 @@ iconButton instNameTextBox groupBox - selectAllCheckbox keepPlaytimeCheckbox copyScreenshotsCheckbox copySavesCheckbox diff --git a/tests/FileSystem_test.cpp b/tests/FileSystem_test.cpp index 169f0669..19565a99 100644 --- a/tests/FileSystem_test.cpp +++ b/tests/FileSystem_test.cpp @@ -72,15 +72,15 @@ class LinkTask : public Task { qDebug() << "EXPECTED: Link failure, Windows requires permissions for symlinks"; qDebug() << "atempting to run with privelage"; - connect(m_lnk, &FS::create_link::finishedPrivlaged, this, [&](bool gotResults){ + connect(m_lnk, &FS::create_link::finishedPrivileged, this, [&](bool gotResults){ if (gotResults) { emitSucceeded(); } else { - qDebug() << "Privlaged run exited without results!"; + qDebug() << "Privileged run exited without results!"; emitFailed(); } }); - m_lnk->runPrivlaged(); + m_lnk->runPrivileged(); } else { qDebug() << "Link Failed!" << m_lnk->getOSError().value() << m_lnk->getOSError().message().c_str(); } -- cgit From 458c2f38bc8e560832ca09b846edaa9ddc64f58d Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Fri, 3 Mar 2023 06:33:37 -0700 Subject: cleanup: code review sugestions clean up translation strings Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- launcher/BaseInstance.cpp | 24 +++++++++++----------- launcher/InstanceCopyTask.cpp | 4 ---- launcher/filelink/filelink.exe.manifest | 2 +- launcher/filelink/main.cpp | 2 +- launcher/minecraft/WorldList.cpp | 6 +++--- launcher/minecraft/mod/ModFolderModel.cpp | 7 ++++--- launcher/minecraft/mod/ResourceFolderModel.cpp | 7 ++++--- launcher/minecraft/mod/ResourcePackFolderModel.cpp | 7 ++++--- launcher/ui/dialogs/CopyInstanceDialog.cpp | 2 +- 9 files changed, 30 insertions(+), 31 deletions(-) (limited to 'launcher/ui/dialogs') diff --git a/launcher/BaseInstance.cpp b/launcher/BaseInstance.cpp index 6428be43..ad45aa2d 100644 --- a/launcher/BaseInstance.cpp +++ b/launcher/BaseInstance.cpp @@ -66,7 +66,7 @@ BaseInstance::BaseInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr s m_settings->registerSetting("totalTimePlayed", 0); m_settings->registerSetting("lastTimePlayed", 0); - m_settings->registerSetting("linkedInstancesList", "[]"); + m_settings->registerSetting("linkedInstances", "[]"); // Game time override auto gameTimeOverride = m_settings->registerSetting("OverrideGameTime", false); @@ -188,34 +188,34 @@ bool BaseInstance::shouldStopOnConsoleOverflow() const QStringList BaseInstance::getLinkedInstances() const { - return m_settings->getList("linkedInstancesList"); + return m_settings->getList("linkedInstances"); } void BaseInstance::setLinkedInstances(const QStringList& list) { - auto linkedInstancesList = m_settings->getList("linkedInstancesList"); - m_settings->setList("linkedInstancesList", list); + auto linkedInstances = m_settings->getList("linkedInstances"); + m_settings->setList("linkedInstances", list); } void BaseInstance::addLinkedInstanceId(const QString& id) { - auto linkedInstancesList = m_settings->getList("linkedInstancesList"); - linkedInstancesList.append(id); - setLinkedInstances(linkedInstancesList); + auto linkedInstances = m_settings->getList("linkedInstances"); + linkedInstances.append(id); + setLinkedInstances(linkedInstances); } bool BaseInstance::removeLinkedInstanceId(const QString& id) { - auto linkedInstancesList = m_settings->getList("linkedInstancesList"); - int numRemoved = linkedInstancesList.removeAll(id); - setLinkedInstances(linkedInstancesList); + auto linkedInstances = m_settings->getList("linkedInstances"); + int numRemoved = linkedInstances.removeAll(id); + setLinkedInstances(linkedInstances); return numRemoved > 0; } bool BaseInstance::isLinkedToInstanceId(const QString& id) const { - auto linkedInstancesList = m_settings->getList("linkedInstancesList"); - return linkedInstancesList.contains(id); + auto linkedInstances = m_settings->getList("linkedInstances"); + return linkedInstances.contains(id); } void BaseInstance::iconUpdated(QString key) diff --git a/launcher/InstanceCopyTask.cpp b/launcher/InstanceCopyTask.cpp index 5ef7a7fd..6bd56de3 100644 --- a/launcher/InstanceCopyTask.cpp +++ b/launcher/InstanceCopyTask.cpp @@ -10,10 +10,6 @@ InstanceCopyTask::InstanceCopyTask(InstancePtr origInstance, const InstanceCopyP { m_origInstance = origInstance; m_keepPlaytime = prefs.isKeepPlaytimeEnabled(); - - - - m_useLinks = prefs.isUseSymLinksEnabled(); m_linkRecursively = prefs.isLinkRecursivelyEnabled(); m_useHardLinks = prefs.isLinkRecursivelyEnabled() && prefs.isUseHardLinksEnabled(); diff --git a/launcher/filelink/filelink.exe.manifest b/launcher/filelink/filelink.exe.manifest index a4e16264..239aa978 100644 --- a/launcher/filelink/filelink.exe.manifest +++ b/launcher/filelink/filelink.exe.manifest @@ -25,4 +25,4 @@ - \ No newline at end of file + diff --git a/launcher/filelink/main.cpp b/launcher/filelink/main.cpp index 7f06795e..4a22ff18 100644 --- a/launcher/filelink/main.cpp +++ b/launcher/filelink/main.cpp @@ -28,4 +28,4 @@ int main(int argc, char *argv[]) FileLinkApp ldh(argc, argv); return ldh.exec(); -} \ No newline at end of file +} diff --git a/launcher/minecraft/WorldList.cpp b/launcher/minecraft/WorldList.cpp index 43733110..3681bcda 100644 --- a/launcher/minecraft/WorldList.cpp +++ b/launcher/minecraft/WorldList.cpp @@ -237,11 +237,11 @@ QVariant WorldList::data(const QModelIndex &index, int role) const { if (column == InfoColumn) { if (world.isSymLinkUnder(instDirPath())) { - return tr("Warning: This world is symbolically linked from elsewhere. Editing it will also change the original") + - tr("\nCanonical Path: %1").arg(world.canonicalFilePath()); + return tr("Warning: This world is symbolically linked from elsewhere. Editing it will also change the original." + "\nCanonical Path: %1").arg(world.canonicalFilePath()); } if (world.isMoreThanOneHardLink()) { - return tr("Warning: This world is hard linked elsewhere. Editing it will also change the original"); + return tr("Warning: This world is hard linked elsewhere. Editing it will also change the original."); } } return world.folderName(); diff --git a/launcher/minecraft/mod/ModFolderModel.cpp b/launcher/minecraft/mod/ModFolderModel.cpp index 943f30ad..91d16175 100644 --- a/launcher/minecraft/mod/ModFolderModel.cpp +++ b/launcher/minecraft/mod/ModFolderModel.cpp @@ -104,12 +104,13 @@ QVariant ModFolderModel::data(const QModelIndex &index, int role) const if (column == NAME_COLUMN) { if (at(row)->isSymLinkUnder(instDirPath())) { return m_resources[row]->internal_id() + - tr("\nWarning: This resource is symbolically linked from elsewhere. Editing it will also change the original") + - tr("\nCanonical Path: %1").arg(at(row)->fileinfo().canonicalFilePath()); + tr("\nWarning: This resource is symbolically linked from elsewhere. Editing it will also change the original." + "\nCanonical Path: %1") + .arg(at(row)->fileinfo().canonicalFilePath()); } if (at(row)->isMoreThanOneHardLink()) { return m_resources[row]->internal_id() + - tr("\nWarning: This resource is hard linked elsewhere. Editing it will also change the original"); + tr("\nWarning: This resource is hard linked elsewhere. Editing it will also change the original."); } } return m_resources[row]->internal_id(); diff --git a/launcher/minecraft/mod/ResourceFolderModel.cpp b/launcher/minecraft/mod/ResourceFolderModel.cpp index 95b7651f..29a0c736 100644 --- a/launcher/minecraft/mod/ResourceFolderModel.cpp +++ b/launcher/minecraft/mod/ResourceFolderModel.cpp @@ -424,12 +424,13 @@ QVariant ResourceFolderModel::data(const QModelIndex& index, int role) const if (column == NAME_COLUMN) { if (at(row).isSymLinkUnder(instDirPath())) { return m_resources[row]->internal_id() + - tr("\nWarning: This resource is symbolically linked from elsewhere. Editing it will also change the original") + - tr("\nCanonical Path: %1").arg(at(row).fileinfo().canonicalFilePath());; + tr("\nWarning: This resource is symbolically linked from elsewhere. Editing it will also change the original." + "\nCanonical Path: %1") + .arg(at(row).fileinfo().canonicalFilePath());; } if (at(row).isMoreThanOneHardLink()) { return m_resources[row]->internal_id() + - tr("\nWarning: This resource is hard linked elsewhere. Editing it will also change the original"); + tr("\nWarning: This resource is hard linked elsewhere. Editing it will also change the original."); } } diff --git a/launcher/minecraft/mod/ResourcePackFolderModel.cpp b/launcher/minecraft/mod/ResourcePackFolderModel.cpp index 1fcfa909..0480d8ba 100644 --- a/launcher/minecraft/mod/ResourcePackFolderModel.cpp +++ b/launcher/minecraft/mod/ResourcePackFolderModel.cpp @@ -96,12 +96,13 @@ QVariant ResourcePackFolderModel::data(const QModelIndex& index, int role) const if (column == NAME_COLUMN) { if (at(row)->isSymLinkUnder(instDirPath())) { return m_resources[row]->internal_id() + - tr("\nWarning: This resource is symbolically linked from elsewhere. Editing it will also change the original") + - tr("\nCanonical Path: %1").arg(at(row)->fileinfo().canonicalFilePath());; + tr("\nWarning: This resource is symbolically linked from elsewhere. Editing it will also change the original." + "\nCanonical Path: %1") + .arg(at(row)->fileinfo().canonicalFilePath());; } if (at(row)->isMoreThanOneHardLink()) { return m_resources[row]->internal_id() + - tr("\nWarning: This resource is hard linked elsewhere. Editing it will also change the original"); + tr("\nWarning: This resource is hard linked elsewhere. Editing it will also change the original."); } } return m_resources[row]->internal_id(); diff --git a/launcher/ui/dialogs/CopyInstanceDialog.cpp b/launcher/ui/dialogs/CopyInstanceDialog.cpp index 62c0bb39..ced57ae0 100644 --- a/launcher/ui/dialogs/CopyInstanceDialog.cpp +++ b/launcher/ui/dialogs/CopyInstanceDialog.cpp @@ -324,4 +324,4 @@ void CopyInstanceDialog::on_useCloneCheckbox_stateChanged(int state) m_selectedOptions.enableUseClone(m_cloneSupported && (state == Qt::Checked)); updateUseCloneCheckbox(); updateLinkOptions(); -} \ No newline at end of file +} -- cgit From a28193430c3830af294cbba92d68e9bcccd7dfd2 Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Tue, 14 Mar 2023 16:17:43 -0700 Subject: fix: adjust geometry and add missing tooltip Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- launcher/ui/dialogs/CopyInstanceDialog.ui | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) (limited to 'launcher/ui/dialogs') diff --git a/launcher/ui/dialogs/CopyInstanceDialog.ui b/launcher/ui/dialogs/CopyInstanceDialog.ui index 3101acec..5060debc 100644 --- a/launcher/ui/dialogs/CopyInstanceDialog.ui +++ b/launcher/ui/dialogs/CopyInstanceDialog.ui @@ -9,8 +9,8 @@ 0 0 - 531 - 653 + 575 + 695 @@ -334,11 +334,27 @@ false + + Files cloned with reflinks take up no extra space until they are modified. + Clone instead of copying + + + + Qt::Horizontal + + + + 40 + 20 + + + + -- cgit From 0c986ba4d006740947603afb2e18dd9d2ffdfd2f Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Mon, 20 Mar 2023 16:38:40 -0700 Subject: spelling and formatting Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- launcher/FileSystem.cpp | 14 +-- launcher/FileSystem.h | 185 +++++++++++++---------------- launcher/InstanceCopyTask.cpp | 45 ++++--- launcher/filelink/FileLink.cpp | 110 +++++++---------- launcher/filelink/FileLink.h | 20 ++-- launcher/filelink/main.cpp | 5 +- launcher/ui/dialogs/CopyInstanceDialog.cpp | 40 +++---- launcher/ui/dialogs/CopyInstanceDialog.h | 21 ++-- 8 files changed, 192 insertions(+), 248 deletions(-) (limited to 'launcher/ui/dialogs') diff --git a/launcher/FileSystem.cpp b/launcher/FileSystem.cpp index c913b43e..640cf9be 100644 --- a/launcher/FileSystem.cpp +++ b/launcher/FileSystem.cpp @@ -328,7 +328,7 @@ bool create_link::operator()(const QString& offset, bool dryRun) } /** - * @brief make a list off all the links ot make + * @brief make a list off all the links to * @param offset subdirectory form src to link to dest * @return if there was an error during the attempt to link */ @@ -363,7 +363,7 @@ void create_link::make_link_list(const QString& offset) link_file(src, ""); } else { if (m_debug) - qDebug() << "linking recursivly:" << src << "to" << dst << "max_depth:" << m_max_depth; + qDebug() << "linking recursively:" << src << "to" << dst << "max_depth:" << m_max_depth; QDir src_dir(src); QDirIterator source_it(src, QDir::Filter::Files | QDir::Filter::Hidden, QDirIterator::Subdirectories); @@ -414,7 +414,7 @@ bool create_link::make_links() qWarning() << "Failed to link files:" << QString::fromStdString(m_os_err.message()); qDebug() << "Source file:" << src_path; qDebug() << "Destination file:" << dst_path; - qDebug() << "Error catagory:" << m_os_err.category().name(); + qDebug() << "Error category:" << m_os_err.category().name(); qDebug() << "Error code:" << m_os_err.value(); emit linkFailed(src_path, dst_path, QString::fromStdString(m_os_err.message()), m_os_err.value()); } else { @@ -469,7 +469,7 @@ void create_link::runPrivileged(const QString& offset) in.setDevice(clientConnection); in.setVersion(QDataStream::Qt_5_0); qDebug() << "Reading path results from client"; - qDebug() << "bytes avalible" << clientConnection->bytesAvailable(); + qDebug() << "bytes available" << clientConnection->bytesAvailable(); // Relies on the fact that QDataStream serializes a quint32 into // sizeof(quint32) bytes @@ -479,7 +479,7 @@ void create_link::runPrivileged(const QString& offset) in >> blockSize; qDebug() << "blocksize is" << blockSize; - qDebug() << "bytes avalible" << clientConnection->bytesAvailable(); + qDebug() << "bytes available" << clientConnection->bytesAvailable(); if (clientConnection->bytesAvailable() < blockSize || in.atEnd()) return; @@ -506,7 +506,7 @@ void create_link::runPrivileged(const QString& offset) m_path_results.append(result); } gotResults = true; - qDebug() << "results recieved, closing connection"; + qDebug() << "results received, closing connection"; clientConnection->close(); }); @@ -981,7 +981,7 @@ FilesystemType getFilesystemType(const QString& name) } /** - * @brief path to the near ancestor that exsists + * @brief path to the near ancestor that exists * */ QString NearestExistentAncestor(const QString& path) diff --git a/launcher/FileSystem.h b/launcher/FileSystem.h index 71175bb4..673c3563 100644 --- a/launcher/FileSystem.h +++ b/launcher/FileSystem.h @@ -44,9 +44,9 @@ #include #include +#include #include #include -#include namespace FS { @@ -84,7 +84,7 @@ bool ensureFolderPathExists(QString filenamepath); /** * @brief Copies a directory and it's contents from src to dest - */ + */ class copy : public QObject { Q_OBJECT public: @@ -167,17 +167,14 @@ class ExternalLinkFileProcess : public QThread { /** * @brief links (a file / a directory and it's contents) from src to dest - */ + */ class create_link : public QObject { Q_OBJECT public: - create_link(const QList path_pairs, QObject* parent = nullptr) : QObject(parent) - { - m_path_pairs.append(path_pairs); - } + create_link(const QList path_pairs, QObject* parent = nullptr) : QObject(parent) { m_path_pairs.append(path_pairs); } create_link(const QString& src, const QString& dst, QObject* parent = nullptr) : QObject(parent) { - LinkPair pair = {src, dst}; + LinkPair pair = { src, dst }; m_path_pairs.append(pair); } create_link& useHardLinks(const bool useHard) @@ -211,28 +208,23 @@ class create_link : public QObject { return *this; } - std::error_code getOSError() { - return m_os_err; - } + std::error_code getOSError() { return m_os_err; } bool operator()(bool dryRun = false) { return operator()(QString(), dryRun); } int totalLinked() { return m_linked; } - void runPrivileged() { runPrivileged(QString()); } void runPrivileged(const QString& offset); QList getResults() { return m_path_results; } - signals: void fileLinked(const QString& srcName, const QString& dstName); void linkFailed(const QString& srcName, const QString& dstName, const QString& err_msg, int err_value); void finished(); void finishedPrivileged(bool gotResults); - private: bool operator()(const QString& offset, bool dryRun = false); void make_link_list(const QString& offset); @@ -262,9 +254,9 @@ class create_link : public QObject { * @brief moves a file by renaming it * @param source source file path * @param dest destination filepath - * + * */ -bool move(const QString& source, const QString& dest); +bool move(const QString& source, const QString& dest); /** * Delete a folder recursively @@ -274,7 +266,7 @@ bool deletePath(QString path); /** * Trash a folder / file */ -bool trash(QString path, QString *pathInTrash = nullptr); +bool trash(QString path, QString* pathInTrash = nullptr); QString PathCombine(const QString& path1, const QString& path2); QString PathCombine(const QString& path1, const QString& path2, const QString& path3); @@ -284,16 +276,15 @@ QString AbsolutePath(const QString& path); /** * @brief depth of path. "foo.txt" -> 0 , "bar/foo.txt" -> 1, /baz/bar/foo.txt -> 2, etc. - * + * * @param path path to measure - * @return int number of componants before base path + * @return int number of components before base path */ int PathDepth(const QString& path); - /** - * @brief cut off segments of path untill it is a max of length depth - * + * @brief cut off segments of path until it is a max of length depth + * * @param path path to truncate * @param depth max depth of new path * @return QString truncated path @@ -363,124 +354,118 @@ enum class FilesystemType { /** * @brief Ordered Mapping of enum types to reported filesystem names - * this maping is non exsaustive, it just attempts to capture the filesystems which could be reasonalbly be in use . + * this mapping is non exsaustive, it just attempts to capture the filesystems which could be reasonalbly be in use . * all string values are in uppercase, use `QString.toUpper()` or equivalent during lookup. - * + * * QMap is ordered - * + * */ -static const QMap s_filesystem_type_names = { - {FilesystemType::FAT, QString("FAT")}, - {FilesystemType::NTFS, QString("NTFS")}, - {FilesystemType::REFS, QString("REFS")}, - {FilesystemType::EXT, QString("EXT")}, - {FilesystemType::EXT_2_OLD, QString("EXT_2_OLD")}, - {FilesystemType::EXT_2_3_4, QString("EXT2/3/4")}, - {FilesystemType::XFS, QString("XFS")}, - {FilesystemType::BTRFS, QString("BTRFS")}, - {FilesystemType::NFS, QString("NFS")}, - {FilesystemType::ZFS, QString("ZFS")}, - {FilesystemType::APFS, QString("APFS")}, - {FilesystemType::HFS, QString("HFS")}, - {FilesystemType::HFSPLUS, QString("HFSPLUS")}, - {FilesystemType::HFSX, QString("HFSX")}, - {FilesystemType::FUSEBLK, QString("FUSEBLK")}, - {FilesystemType::F2FS, QString("F2FS")}, - {FilesystemType::UNKNOWN, QString("UNKNOWN")} -}; - +static const QMap s_filesystem_type_names = { { FilesystemType::FAT, QString("FAT") }, + { FilesystemType::NTFS, QString("NTFS") }, + { FilesystemType::REFS, QString("REFS") }, + { FilesystemType::EXT, QString("EXT") }, + { FilesystemType::EXT_2_OLD, QString("EXT_2_OLD") }, + { FilesystemType::EXT_2_3_4, QString("EXT2/3/4") }, + { FilesystemType::XFS, QString("XFS") }, + { FilesystemType::BTRFS, QString("BTRFS") }, + { FilesystemType::NFS, QString("NFS") }, + { FilesystemType::ZFS, QString("ZFS") }, + { FilesystemType::APFS, QString("APFS") }, + { FilesystemType::HFS, QString("HFS") }, + { FilesystemType::HFSPLUS, QString("HFSPLUS") }, + { FilesystemType::HFSX, QString("HFSX") }, + { FilesystemType::FUSEBLK, QString("FUSEBLK") }, + { FilesystemType::F2FS, QString("F2FS") }, + { FilesystemType::UNKNOWN, QString("UNKNOWN") } }; /** * @brief Ordered Mapping of reported filesystem names to enum types - * this maping is non exsaustive, it just attempts to capture the many way these filesystems could be reported. + * this mapping is non exsaustive, it just attempts to capture the many way these filesystems could be reported. * all keys are in uppercase, use `QString.toUpper()` or equivalent during lookup. - * + * * QMap is ordered - * + * */ static const QMap s_filesystem_type_names_inverse = { - {QString("FAT"), FilesystemType::FAT}, - {QString("NTFS"), FilesystemType::NTFS}, - {QString("REFS"), FilesystemType::REFS}, - {QString("EXT2_OLD"), FilesystemType::EXT_2_OLD}, - {QString("EXT_2_OLD"), FilesystemType::EXT_2_OLD}, - {QString("EXT2"), FilesystemType::EXT_2_3_4}, - {QString("EXT3"), FilesystemType::EXT_2_3_4}, - {QString("EXT4"), FilesystemType::EXT_2_3_4}, - {QString("EXT2/3/4"), FilesystemType::EXT_2_3_4}, - {QString("EXT_2_3_4"), FilesystemType::EXT_2_3_4}, - {QString("EXT"), FilesystemType::EXT}, // must come after all other EXT variants to prevent greedy detection - {QString("XFS"), FilesystemType::XFS}, - {QString("BTRFS"), FilesystemType::BTRFS}, - {QString("NFS"), FilesystemType::NFS}, - {QString("ZFS"), FilesystemType::ZFS}, - {QString("APFS"), FilesystemType::APFS}, - {QString("HFSPLUS"), FilesystemType::HFSPLUS}, - {QString("HFSX"), FilesystemType::HFSX}, - {QString("HFS"), FilesystemType::HFS}, - {QString("FUSEBLK"), FilesystemType::FUSEBLK}, - {QString("F2FS"), FilesystemType::F2FS}, - {QString("UNKNOWN"), FilesystemType::UNKNOWN} + { QString("FAT"), FilesystemType::FAT }, + { QString("NTFS"), FilesystemType::NTFS }, + { QString("REFS"), FilesystemType::REFS }, + { QString("EXT2_OLD"), FilesystemType::EXT_2_OLD }, + { QString("EXT_2_OLD"), FilesystemType::EXT_2_OLD }, + { QString("EXT2"), FilesystemType::EXT_2_3_4 }, + { QString("EXT3"), FilesystemType::EXT_2_3_4 }, + { QString("EXT4"), FilesystemType::EXT_2_3_4 }, + { QString("EXT2/3/4"), FilesystemType::EXT_2_3_4 }, + { QString("EXT_2_3_4"), FilesystemType::EXT_2_3_4 }, + { QString("EXT"), FilesystemType::EXT }, // must come after all other EXT variants to prevent greedy detection + { QString("XFS"), FilesystemType::XFS }, + { QString("BTRFS"), FilesystemType::BTRFS }, + { QString("NFS"), FilesystemType::NFS }, + { QString("ZFS"), FilesystemType::ZFS }, + { QString("APFS"), FilesystemType::APFS }, + { QString("HFSPLUS"), FilesystemType::HFSPLUS }, + { QString("HFSX"), FilesystemType::HFSX }, + { QString("HFS"), FilesystemType::HFS }, + { QString("FUSEBLK"), FilesystemType::FUSEBLK }, + { QString("F2FS"), FilesystemType::F2FS }, + { QString("UNKNOWN"), FilesystemType::UNKNOWN } }; /** * @brief Get the string name of Filesystem enum object - * - * @param type - * @return QString + * + * @param type + * @return QString */ QString getFilesystemTypeName(FilesystemType type); /** * @brief Get the Filesystem enum object from a name - * Does a lookup of the type name and returns an exact match + * Does a lookup of the type name and returns an exact match * - * @param name - * @return FilesystemType + * @param name + * @return FilesystemType */ FilesystemType getFilesystemType(const QString& name); /** * @brief Get the Filesystem enum object from a name * Does a fuzzy lookup of the type name and returns an apropreate match - * - * @param name - * @return FilesystemType + * + * @param name + * @return FilesystemType */ FilesystemType getFilesystemTypeFuzzy(const QString& name); - struct FilesystemInfo { FilesystemType fsType = FilesystemType::UNKNOWN; QString fsTypeName; - int blockSize; - qint64 bytesAvailable; - qint64 bytesFree; - qint64 bytesTotal; + int blockSize; + qint64 bytesAvailable; + qint64 bytesFree; + qint64 bytesTotal; QString name; QString rootPath; }; /** - * @brief path to the near ancestor that exsists - * + * @brief path to the near ancestor that exists + * */ QString NearestExistentAncestor(const QString& path); /** * @brief colect information about the filesystem under a file - * + * */ FilesystemInfo statFS(const QString& path); -static const QList s_clone_filesystems = { - FilesystemType::BTRFS, FilesystemType::APFS, FilesystemType::ZFS, - FilesystemType::XFS, FilesystemType::REFS -}; +static const QList s_clone_filesystems = { FilesystemType::BTRFS, FilesystemType::APFS, FilesystemType::ZFS, + FilesystemType::XFS, FilesystemType::REFS }; /** - * @brief if the Filesystem is reflink/clone capable - * + * @brief if the Filesystem is reflink/clone capable + * */ bool canCloneOnFS(const QString& path); bool canCloneOnFS(const FilesystemInfo& info); @@ -488,13 +473,13 @@ bool canCloneOnFS(FilesystemType type); /** * @brief if the Filesystems are reflink/clone capable and both are on the same device - * + * */ bool canClone(const QString& src, const QString& dst); /** * @brief Copies a directory and it's contents from src to dest - */ + */ class clone : public QObject { Q_OBJECT public: @@ -535,7 +520,7 @@ class clone : public QObject { /** * @brief clone/reflink file from src to dst - * + * */ bool clone_file(const QString& src, const QString& dst, std::error_code& ec); @@ -552,8 +537,8 @@ static const QList s_non_link_filesystems = { }; /** - * @brief if the Filesystem is symlink capable - * + * @brief if the Filesystem is symlink capable + * */ bool canLinkOnFS(const QString& path); bool canLinkOnFS(const FilesystemInfo& info); @@ -561,10 +546,10 @@ bool canLinkOnFS(FilesystemType type); /** * @brief if the Filesystem is symlink capable on both ends - * + * */ bool canLink(const QString& src, const QString& dst); uintmax_t hardLinkCount(const QString& path); -} +} // namespace FS diff --git a/launcher/InstanceCopyTask.cpp b/launcher/InstanceCopyTask.cpp index 6bd56de3..4ac3b51a 100644 --- a/launcher/InstanceCopyTask.cpp +++ b/launcher/InstanceCopyTask.cpp @@ -1,10 +1,10 @@ #include "InstanceCopyTask.h" -#include "settings/INISettingsObject.h" +#include +#include #include "FileSystem.h" #include "NullInstance.h" #include "pathmatcher/RegexpMatcher.h" -#include -#include +#include "settings/INISettingsObject.h" InstanceCopyTask::InstanceCopyTask(InstancePtr origInstance, const InstanceCopyPrefs& prefs) { @@ -15,17 +15,17 @@ InstanceCopyTask::InstanceCopyTask(InstancePtr origInstance, const InstanceCopyP m_useHardLinks = prefs.isLinkRecursivelyEnabled() && prefs.isUseHardLinksEnabled(); m_copySaves = prefs.isLinkRecursivelyEnabled() && prefs.isDontLinkSavesEnabled() && prefs.isCopySavesEnabled(); m_useClone = prefs.isUseCloneEnabled(); - + QString filters = prefs.getSelectedFiltersAsRegex(); if (m_useLinks || m_useHardLinks) { - if (!filters.isEmpty()) filters += "|"; + if (!filters.isEmpty()) + filters += "|"; filters += "instance.cfg"; - } + } qDebug() << "CopyFilters:" << filters; - if (!filters.isEmpty()) - { + if (!filters.isEmpty()) { // Set regex filter: // FIXME: get this from the original instance type... auto matcherReal = new RegexpMatcher(filters); @@ -38,14 +38,14 @@ void InstanceCopyTask::executeTask() { setStatus(tr("Copying instance %1").arg(m_origInstance->name())); - auto copySaves = [&](){ - FS::copy savesCopy(FS::PathCombine(m_origInstance->instanceRoot(), "saves") , FS::PathCombine(m_stagingPath, "saves")); + auto copySaves = [&]() { + FS::copy savesCopy(FS::PathCombine(m_origInstance->instanceRoot(), "saves"), FS::PathCombine(m_stagingPath, "saves")); savesCopy.followSymlinks(true); return savesCopy(); }; - m_copyFuture = QtConcurrent::run(QThreadPool::globalInstance(), [this, copySaves]{ + m_copyFuture = QtConcurrent::run(QThreadPool::globalInstance(), [this, copySaves] { if (m_useClone) { FS::clone folderClone(m_origInstance->instanceRoot(), m_stagingPath); folderClone.matcher(m_matcher.get()); @@ -53,22 +53,22 @@ void InstanceCopyTask::executeTask() return folderClone(); } else if (m_useLinks || m_useHardLinks) { FS::create_link folderLink(m_origInstance->instanceRoot(), m_stagingPath); - int depth = m_linkRecursively ? -1 : 0; // we need to at least link the top level instead of the instance folder + int depth = m_linkRecursively ? -1 : 0; // we need to at least link the top level instead of the instance folder folderLink.linkRecursively(true).setMaxDepth(depth).useHardLinks(m_useHardLinks).matcher(m_matcher.get()); bool there_were_errors = false; - if(!folderLink()){ + if (!folderLink()) { #if defined Q_OS_WIN32 if (!m_useHardLinks) { qDebug() << "EXPECTED: Link failure, Windows requires permissions for symlinks"; - qDebug() << "atempting to run with privelage"; + qDebug() << "attempting to run with privelage"; QEventLoop loop; bool got_priv_results = false; - connect(&folderLink, &FS::create_link::finishedPrivileged, this, [&](bool gotResults){ + connect(&folderLink, &FS::create_link::finishedPrivileged, this, [&](bool gotResults) { if (!gotResults) { qDebug() << "Privileged run exited without results!"; } @@ -77,7 +77,7 @@ void InstanceCopyTask::executeTask() }); folderLink.runPrivileged(); - loop.exec(); // wait for the finished signal + loop.exec(); // wait for the finished signal for (auto result : folderLink.getResults()) { if (result.err_value != 0) { @@ -88,17 +88,17 @@ void InstanceCopyTask::executeTask() if (m_copySaves) { there_were_errors |= !copySaves(); } - + return got_priv_results && !there_were_errors; } else { qDebug() << "Link Failed!" << folderLink.getOSError().value() << folderLink.getOSError().message().c_str(); - } + } #else qDebug() << "Link Failed!" << folderLink.getOSError().value() << folderLink.getOSError().message().c_str(); -#endif +#endif return false; } - + if (m_copySaves) { there_were_errors |= !copySaves(); } @@ -119,8 +119,7 @@ void InstanceCopyTask::executeTask() void InstanceCopyTask::copyFinished() { auto successful = m_copyFuture.result(); - if(!successful) - { + if (!successful) { emitFailed(tr("Instance folder copy failed.")); return; } @@ -130,7 +129,7 @@ void InstanceCopyTask::copyFinished() InstancePtr inst(new NullInstance(m_globalSettings, instanceSettings, m_stagingPath)); inst->setName(name()); inst->setIconKey(m_instIcon); - if(!m_keepPlaytime) { + if (!m_keepPlaytime) { inst->resetTimePlayed(); } if (m_useLinks) diff --git a/launcher/filelink/FileLink.cpp b/launcher/filelink/FileLink.cpp index a731ecdb..ded46061 100644 --- a/launcher/filelink/FileLink.cpp +++ b/launcher/filelink/FileLink.cpp @@ -25,7 +25,6 @@ #include "StringUtils.h" - #include #include @@ -41,53 +40,47 @@ #ifndef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN #endif -#include #include +#include #endif // Snippet from https://github.com/gulrak/filesystem#using-it-as-single-file-header #ifdef __APPLE__ -#include // for deployment target to support pre-catalina targets without std::fs -#endif // __APPLE__ +#include // for deployment target to support pre-catalina targets without std::fs +#endif // __APPLE__ #if ((defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) || (defined(__cplusplus) && __cplusplus >= 201703L)) && defined(__has_include) #if __has_include() && (!defined(__MAC_OS_X_VERSION_MIN_REQUIRED) || __MAC_OS_X_VERSION_MIN_REQUIRED >= 101500) #define GHC_USE_STD_FS #include namespace fs = std::filesystem; -#endif // MacOS min version check -#endif // Other OSes version check +#endif // MacOS min version check +#endif // Other OSes version check #ifndef GHC_USE_STD_FS #include namespace fs = ghc::filesystem; #endif - - -FileLinkApp::FileLinkApp(int &argc, char **argv) : QCoreApplication(argc, argv), socket(new QLocalSocket(this)) +FileLinkApp::FileLinkApp(int& argc, char** argv) : QCoreApplication(argc, argv), socket(new QLocalSocket(this)) { #if defined Q_OS_WIN32 // attach the parent console - if(AttachConsole(ATTACH_PARENT_PROCESS)) - { + if (AttachConsole(ATTACH_PARENT_PROCESS)) { // if attach succeeds, reopen and sync all the i/o - if(freopen("CON", "w", stdout)) - { + if (freopen("CON", "w", stdout)) { std::cout.sync_with_stdio(); } - if(freopen("CON", "w", stderr)) - { + if (freopen("CON", "w", stderr)) { std::cerr.sync_with_stdio(); } - if(freopen("CON", "r", stdin)) - { + if (freopen("CON", "r", stdin)) { std::cin.sync_with_stdio(); } - auto out = GetStdHandle (STD_OUTPUT_HANDLE); + auto out = GetStdHandle(STD_OUTPUT_HANDLE); DWORD written; - const char * endline = "\n"; + const char* endline = "\n"; WriteConsole(out, endline, strlen(endline), &written, NULL); consoleAttached = true; } @@ -101,10 +94,8 @@ FileLinkApp::FileLinkApp(int &argc, char **argv) : QCoreApplication(argc, argv), QCommandLineParser parser; parser.setApplicationDescription(QObject::tr("a batch MKLINK program for windows to be used with prismlauncher")); - parser.addOptions({ - {{"s", "server"}, "Join the specified server on launch", "pipe name"}, - {{"H", "hard"}, "use hard links insted of symbolic", "true/false"} - }); + parser.addOptions({ { { "s", "server" }, "Join the specified server on launch", "pipe name" }, + { { "H", "hard" }, "use hard links instead of symbolic", "true/false" } }); parser.addHelpOption(); parser.addVersionOption(); @@ -122,63 +113,57 @@ FileLinkApp::FileLinkApp(int &argc, char **argv) : QCoreApplication(argc, argv), qDebug() << "no server to join"; exit(); } - } void FileLinkApp::joinServer(QString server) { - - blockSize = 0; + blockSize = 0; in.setDevice(&socket); in.setVersion(QDataStream::Qt_5_0); - connect(&socket, &QLocalSocket::connected, this, [&](){ - qDebug() << "connected to server"; - }); + connect(&socket, &QLocalSocket::connected, this, [&]() { qDebug() << "connected to server"; }); connect(&socket, &QLocalSocket::readyRead, this, &FileLinkApp::readPathPairs); - connect(&socket, &QLocalSocket::errorOccurred, this, [&](QLocalSocket::LocalSocketError socketError){ + connect(&socket, &QLocalSocket::errorOccurred, this, [&](QLocalSocket::LocalSocketError socketError) { switch (socketError) { - case QLocalSocket::ServerNotFoundError: - qDebug() << ("The host was not found. Please make sure " + case QLocalSocket::ServerNotFoundError: + qDebug() + << ("The host was not found. Please make sure " "that the server is running and that the " "server name is correct."); - break; - case QLocalSocket::ConnectionRefusedError: - qDebug() << ("The connection was refused by the peer. " + break; + case QLocalSocket::ConnectionRefusedError: + qDebug() + << ("The connection was refused by the peer. " "Make sure the server is running, " "and check that the server name " "is correct."); - break; - case QLocalSocket::PeerClosedError: - qDebug() << ("The connection was closed by the peer. "); - break; - default: - qDebug() << "The following error occurred: " << socket.errorString(); + break; + case QLocalSocket::PeerClosedError: + qDebug() << ("The connection was closed by the peer. "); + break; + default: + qDebug() << "The following error occurred: " << socket.errorString(); } }); - connect(&socket, &QLocalSocket::disconnected, this, [&](){ - qDebug() << "dissconnected from server, should exit"; + connect(&socket, &QLocalSocket::disconnected, this, [&]() { + qDebug() << "disconnected from server, should exit"; exit(); }); socket.connectToServer(server); - - } void FileLinkApp::runLink() -{ - +{ std::error_code os_err; qDebug() << "creating links"; for (auto link : m_links_to_make) { - QString src_path = link.src; QString dst_path = link.dst; @@ -192,26 +177,25 @@ void FileLinkApp::runLink() } else { qDebug() << "making symlink:" << src_path << "to" << dst_path; fs::create_symlink(StringUtils::toStdString(src_path), StringUtils::toStdString(dst_path), os_err); - } + } if (os_err) { qWarning() << "Failed to link files:" << QString::fromStdString(os_err.message()); qDebug() << "Source file:" << src_path; qDebug() << "Destination file:" << dst_path; - qDebug() << "Error catagory:" << os_err.category().name(); + qDebug() << "Error category:" << os_err.category().name(); qDebug() << "Error code:" << os_err.value(); - FS::LinkResult result = {src_path, dst_path, QString::fromStdString(os_err.message()), os_err.value()}; + FS::LinkResult result = { src_path, dst_path, QString::fromStdString(os_err.message()), os_err.value() }; m_path_results.append(result); } else { - FS::LinkResult result = {src_path, dst_path}; + FS::LinkResult result = { src_path, dst_path }; m_path_results.append(result); } } sendResults(); qDebug() << "done, should exit soon"; - } void FileLinkApp::sendResults() @@ -245,10 +229,10 @@ void FileLinkApp::sendResults() } void FileLinkApp::readPathPairs() -{ +{ m_links_to_make.clear(); qDebug() << "Reading path pairs from server"; - qDebug() << "bytes avalible" << socket.bytesAvailable(); + qDebug() << "bytes available" << socket.bytesAvailable(); if (blockSize == 0) { // Relies on the fact that QDataStream serializes a quint32 into // sizeof(quint32) bytes @@ -258,15 +242,15 @@ void FileLinkApp::readPathPairs() in >> blockSize; } qDebug() << "blocksize is" << blockSize; - qDebug() << "bytes avalible" << socket.bytesAvailable(); + qDebug() << "bytes available" << socket.bytesAvailable(); if (socket.bytesAvailable() < blockSize || in.atEnd()) return; - + quint32 numLinks; in >> numLinks; qDebug() << "numLinks" << numLinks; - for(int i = 0; i < numLinks; i++) { + for (int i = 0; i < numLinks; i++) { FS::LinkPair pair; in >> pair.src; in >> pair.dst; @@ -277,17 +261,15 @@ void FileLinkApp::readPathPairs() runLink(); } - FileLinkApp::~FileLinkApp() -{ +{ qDebug() << "link program shutting down"; // Shut down logger by setting the logger function to nothing qInstallMessageHandler(nullptr); #if defined Q_OS_WIN32 // Detach from Windows console - if(consoleAttached) - { + if (consoleAttached) { fclose(stdout); fclose(stdin); fclose(stderr); @@ -295,7 +277,3 @@ FileLinkApp::~FileLinkApp() } #endif } - - - - diff --git a/launcher/filelink/FileLink.h b/launcher/filelink/FileLink.h index d146b8d9..4c47d9bb 100644 --- a/launcher/filelink/FileLink.h +++ b/launcher/filelink/FileLink.h @@ -25,30 +25,26 @@ #include #include -#include +#include +#include #include #include #include -#include -#include -#include -#include #include +#include +#include #define PRISM_EXTERNAL_EXE #include "FileSystem.h" -class FileLinkApp : public QCoreApplication -{ +class FileLinkApp : public QCoreApplication { // friends for the purpose of limiting access to deprecated stuff Q_OBJECT -public: - - FileLinkApp(int &argc, char **argv); + public: + FileLinkApp(int& argc, char** argv); virtual ~FileLinkApp(); -private: - + private: void joinServer(QString server); void readPathPairs(); void runLink(); diff --git a/launcher/filelink/main.cpp b/launcher/filelink/main.cpp index 4a22ff18..83566a3c 100644 --- a/launcher/filelink/main.cpp +++ b/launcher/filelink/main.cpp @@ -22,10 +22,9 @@ #include "FileLink.h" -int main(int argc, char *argv[]) +int main(int argc, char* argv[]) { - FileLinkApp ldh(argc, argv); - + return ldh.exec(); } diff --git a/launcher/ui/dialogs/CopyInstanceDialog.cpp b/launcher/ui/dialogs/CopyInstanceDialog.cpp index ced57ae0..347bd39f 100644 --- a/launcher/ui/dialogs/CopyInstanceDialog.cpp +++ b/launcher/ui/dialogs/CopyInstanceDialog.cpp @@ -43,15 +43,15 @@ #include "ui/dialogs/IconPickerDialog.h" -#include "BaseVersion.h" -#include "icons/IconList.h" #include "BaseInstance.h" -#include "InstanceList.h" -#include "FileSystem.h" +#include "BaseVersion.h" #include "DesktopServices.h" +#include "FileSystem.h" +#include "InstanceList.h" +#include "icons/IconList.h" -CopyInstanceDialog::CopyInstanceDialog(InstancePtr original, QWidget *parent) - :QDialog(parent), ui(new Ui::CopyInstanceDialog), m_original(original) +CopyInstanceDialog::CopyInstanceDialog(InstancePtr original, QWidget* parent) + : QDialog(parent), ui(new Ui::CopyInstanceDialog), m_original(original) { ui->setupUi(this); resize(minimumSizeHint()); @@ -74,8 +74,7 @@ CopyInstanceDialog::CopyInstanceDialog(InstancePtr original, QWidget *parent) groupList.push_front(""); ui->groupBox->addItems(groupList); int index = groupList.indexOf(APPLICATION->instances()->getInstanceGroup(m_original->id())); - if(index == -1) - { + if (index == -1) { index = 0; } ui->groupBox->setCurrentIndex(index); @@ -108,10 +107,8 @@ CopyInstanceDialog::CopyInstanceDialog(InstancePtr original, QWidget *parent) #if defined(Q_OS_WIN) ui->symbolicLinksCheckbox->setIcon(style()->standardIcon(QStyle::SP_VistaShield)); - ui->symbolicLinksCheckbox->setToolTip( - tr("Use symbolic links instead of copying files.") + - tr("\nOn windows symbolic links may require admin permision to create.") - ); + ui->symbolicLinksCheckbox->setToolTip(tr("Use symbolic links instead of copying files.") + + tr("\nOn windows symbolic links may require admin permission to create.")); #endif updateLinkOptions(); @@ -119,7 +116,6 @@ CopyInstanceDialog::CopyInstanceDialog(InstancePtr original, QWidget *parent) auto HelpButton = ui->buttonBox->button(QDialogButtonBox::Help); connect(HelpButton, &QPushButton::clicked, this, &CopyInstanceDialog::help); - } CopyInstanceDialog::~CopyInstanceDialog() @@ -131,8 +127,7 @@ void CopyInstanceDialog::updateDialogState() { auto allowOK = !instName().isEmpty(); auto OkButton = ui->buttonBox->button(QDialogButtonBox::Ok); - if(OkButton->isEnabled() != allowOK) - { + if (OkButton->isEnabled() != allowOK) { OkButton->setEnabled(allowOK); } } @@ -140,8 +135,7 @@ void CopyInstanceDialog::updateDialogState() QString CopyInstanceDialog::instName() const { auto result = ui->instNameTextBox->text().trimmed(); - if(result.size()) - { + if (result.size()) { return result; } return QString(); @@ -162,7 +156,6 @@ const InstanceCopyPrefs& CopyInstanceDialog::getChosenOptions() const return m_selectedOptions; } - void CopyInstanceDialog::help() { DesktopServices::openUrl(QUrl(BuildConfig.HELP_URL.arg("instance-copy"))); @@ -200,7 +193,8 @@ void CopyInstanceDialog::updateLinkOptions() ui->symbolicLinksCheckbox->setEnabled(m_linkSupported && !ui->hardLinksCheckbox->isChecked() && !ui->useCloneCheckbox->isChecked()); ui->hardLinksCheckbox->setEnabled(m_linkSupported && !ui->symbolicLinksCheckbox->isChecked() && !ui->useCloneCheckbox->isChecked()); - ui->symbolicLinksCheckbox->setChecked(m_linkSupported && m_selectedOptions.isUseSymLinksEnabled() && !ui->useCloneCheckbox->isChecked()); + ui->symbolicLinksCheckbox->setChecked(m_linkSupported && m_selectedOptions.isUseSymLinksEnabled() && + !ui->useCloneCheckbox->isChecked()); ui->hardLinksCheckbox->setChecked(m_linkSupported && m_selectedOptions.isUseHardLinksEnabled() && !ui->useCloneCheckbox->isChecked()); bool linksInUse = (ui->symbolicLinksCheckbox->isChecked() || ui->hardLinksCheckbox->isChecked()); @@ -220,15 +214,13 @@ void CopyInstanceDialog::on_iconButton_clicked() IconPickerDialog dlg(this); dlg.execWithSelection(InstIconKey); - if (dlg.result() == QDialog::Accepted) - { + if (dlg.result() == QDialog::Accepted) { InstIconKey = dlg.selectedIconKey; ui->iconButton->setIcon(APPLICATION->icons()->getIcon(InstIconKey)); } } - -void CopyInstanceDialog::on_instNameTextBox_textChanged(const QString &arg1) +void CopyInstanceDialog::on_instNameTextBox_textChanged(const QString& arg1) { updateDialogState(); } @@ -247,7 +239,6 @@ void CopyInstanceDialog::on_copySavesCheckbox_stateChanged(int state) updateSelectAllCheckbox(); } - void CopyInstanceDialog::on_keepPlaytimeCheckbox_stateChanged(int state) { m_selectedOptions.enableKeepPlaytime(state == Qt::Checked); @@ -311,7 +302,6 @@ void CopyInstanceDialog::on_recursiveLinkCheckbox_stateChanged(int state) { m_selectedOptions.enableLinkRecursively(state == Qt::Checked); updateLinkOptions(); - } void CopyInstanceDialog::on_dontLinkSavesCheckbox_stateChanged(int state) diff --git a/launcher/ui/dialogs/CopyInstanceDialog.h b/launcher/ui/dialogs/CopyInstanceDialog.h index c447bee9..698c6e93 100644 --- a/launcher/ui/dialogs/CopyInstanceDialog.h +++ b/launcher/ui/dialogs/CopyInstanceDialog.h @@ -22,17 +22,15 @@ class BaseInstance; -namespace Ui -{ +namespace Ui { class CopyInstanceDialog; } -class CopyInstanceDialog : public QDialog -{ +class CopyInstanceDialog : public QDialog { Q_OBJECT -public: - explicit CopyInstanceDialog(InstancePtr original, QWidget *parent = 0); + public: + explicit CopyInstanceDialog(InstancePtr original, QWidget* parent = 0); ~CopyInstanceDialog(); void updateDialogState(); @@ -42,13 +40,12 @@ public: QString iconKey() const; const InstanceCopyPrefs& getChosenOptions() const; -public slots: + public slots: void help(); -private -slots: + private slots: void on_iconButton_clicked(); - void on_instNameTextBox_textChanged(const QString &arg1); + void on_instNameTextBox_textChanged(const QString& arg1); // Checkboxes void on_selectAllCheckbox_stateChanged(int state); void on_copySavesCheckbox_stateChanged(int state); @@ -65,14 +62,14 @@ slots: void on_dontLinkSavesCheckbox_stateChanged(int state); void on_useCloneCheckbox_stateChanged(int state); -private: + private: void checkAllCheckboxes(const bool& b); void updateSelectAllCheckbox(); void updateUseCloneCheckbox(); void updateLinkOptions(); /* data */ - Ui::CopyInstanceDialog *ui; + Ui::CopyInstanceDialog* ui; QString InstIconKey; InstancePtr m_original; InstanceCopyPrefs m_selectedOptions; -- cgit From 02bf086c09a26fb7dfd9a95d2a6f81a7a7d4c161 Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Tue, 21 Mar 2023 11:07:20 -0700 Subject: feat: watch sub directories for mods Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- launcher/ui/dialogs/BlockedModsDialog.cpp | 29 +++++++++++++++++++++-------- launcher/ui/dialogs/BlockedModsDialog.h | 1 + 2 files changed, 22 insertions(+), 8 deletions(-) (limited to 'launcher/ui/dialogs') diff --git a/launcher/ui/dialogs/BlockedModsDialog.cpp b/launcher/ui/dialogs/BlockedModsDialog.cpp index ff885f10..05f50c06 100644 --- a/launcher/ui/dialogs/BlockedModsDialog.cpp +++ b/launcher/ui/dialogs/BlockedModsDialog.cpp @@ -39,7 +39,6 @@ #include #include #include -#include #include BlockedModsDialog::BlockedModsDialog(QWidget* parent, const QString& title, const QString& text, QList& mods) @@ -89,11 +88,11 @@ void BlockedModsDialog::dragEnterEvent(QDragEnterEvent* e) void BlockedModsDialog::dropEvent(QDropEvent* e) { for (QUrl& url : e->mimeData()->urls()) { - if (url.scheme().isEmpty()) { // ensure isLocalFile() works correctly + if (url.scheme().isEmpty()) { // ensure isLocalFile() works correctly url.setScheme("file"); } - if (!url.isLocalFile()) { // can't drop external files here. + if (!url.isLocalFile()) { // can't drop external files here. continue; } @@ -172,7 +171,7 @@ void BlockedModsDialog::update() } } -/// @brief Signal fired when a watched direcotry has changed +/// @brief Signal fired when a watched directory has changed /// @param path the path to the changed directory void BlockedModsDialog::directoryChanged(QString path) { @@ -186,8 +185,22 @@ void BlockedModsDialog::setupWatch() { const QString downloadsFolder = APPLICATION->settings()->get("DownloadsDir").toString(); const QString modsFolder = APPLICATION->settings()->get("CentralModsDir").toString(); - m_watcher.addPath(downloadsFolder); - m_watcher.addPath(modsFolder); + watchPath(downloadsFolder, true); + watchPath(modsFolder, true); +} + +void BlockedModsDialog::watchPath(QString path, bool watch_subdirectories) +{ + m_watcher.addPath(path); + + if (!watch_subdirectories) + return; + + QDirIterator it(path, QDirIterator::Subdirectories); + while (it.hasNext()) { + QString dir = it.next(); + watchPath(it.next(), watch_subdirectories); + } } /// @brief scan all watched folder @@ -221,7 +234,7 @@ void BlockedModsDialog::scanPath(QString path, bool start_task) } } -/// @brief add a hashing task for the file located at path, add the path to the pending set if the hasing task is already running +/// @brief add a hashing task for the file located at path, add the path to the pending set if the hashing task is already running /// @param path the path to the local file being hashed void BlockedModsDialog::addHashTask(QString path) { @@ -328,7 +341,7 @@ void BlockedModsDialog::validateMatchedMods() } } -/// @brief run hash task or mark a pending run if it is already runing +/// @brief run hash task or mark a pending run if it is already running void BlockedModsDialog::runHashTask() { if (!m_hashing_task->isRunning()) { diff --git a/launcher/ui/dialogs/BlockedModsDialog.h b/launcher/ui/dialogs/BlockedModsDialog.h index 014f488a..01077aa5 100644 --- a/launcher/ui/dialogs/BlockedModsDialog.h +++ b/launcher/ui/dialogs/BlockedModsDialog.h @@ -79,6 +79,7 @@ class BlockedModsDialog : public QDialog { void update(); void directoryChanged(QString path); void setupWatch(); + void watchPath(QString path, bool watch_subdirectories = false); void scanPaths(); void scanPath(QString path, bool start_task); void addHashTask(QString path); -- cgit From ef50e5595e2b4c50a0f25534ca71e6d898c5b090 Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Tue, 21 Mar 2023 12:10:28 -0700 Subject: fix: don't try to watch the entier filesystem by watching parent links Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- launcher/ui/dialogs/BlockedModsDialog.cpp | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) (limited to 'launcher/ui/dialogs') diff --git a/launcher/ui/dialogs/BlockedModsDialog.cpp b/launcher/ui/dialogs/BlockedModsDialog.cpp index 05f50c06..ac06625e 100644 --- a/launcher/ui/dialogs/BlockedModsDialog.cpp +++ b/launcher/ui/dialogs/BlockedModsDialog.cpp @@ -191,15 +191,19 @@ void BlockedModsDialog::setupWatch() void BlockedModsDialog::watchPath(QString path, bool watch_subdirectories) { + qDebug() << "[Blocked Mods Dialog] Adding Watch Path:" << path; m_watcher.addPath(path); if (!watch_subdirectories) return; - QDirIterator it(path, QDirIterator::Subdirectories); + QDirIterator it(path, QDir::Filter::Dirs, QDirIterator::NoIteratorFlags); while (it.hasNext()) { - QString dir = it.next(); - watchPath(it.next(), watch_subdirectories); + QString dir_path = it.next(); + QDir to_watch_dir(dir_path); + if (to_watch_dir.dirName() == "." || to_watch_dir.dirName() == "..") + continue; + watchPath(dir_path, watch_subdirectories); } } -- cgit From a0045ece075d5caf5d0b6982002dd6bd845ddf0f Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Mon, 27 Mar 2023 19:01:53 -0700 Subject: feat: add setting to watch recursively Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- launcher/Application.cpp | 1 + launcher/ui/dialogs/BlockedModsDialog.cpp | 3 +- launcher/ui/pages/global/LauncherPage.cpp | 6 +++ launcher/ui/pages/global/LauncherPage.h | 1 + launcher/ui/pages/global/LauncherPage.ui | 64 ++++++++++++++++++------------- 5 files changed, 47 insertions(+), 28 deletions(-) (limited to 'launcher/ui/dialogs') diff --git a/launcher/Application.cpp b/launcher/Application.cpp index 879af535..1fc31549 100644 --- a/launcher/Application.cpp +++ b/launcher/Application.cpp @@ -517,6 +517,7 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv) m_settings->registerSetting({"CentralModsDir", "ModsDir"}, "mods"); m_settings->registerSetting("IconsDir", "icons"); m_settings->registerSetting("DownloadsDir", QStandardPaths::writableLocation(QStandardPaths::DownloadLocation)); + m_settings->registerSetting("DownloadsDirWatchRecursive", false); // Editors m_settings->registerSetting("JsonEditor", QString()); diff --git a/launcher/ui/dialogs/BlockedModsDialog.cpp b/launcher/ui/dialogs/BlockedModsDialog.cpp index ac06625e..36df5f46 100644 --- a/launcher/ui/dialogs/BlockedModsDialog.cpp +++ b/launcher/ui/dialogs/BlockedModsDialog.cpp @@ -185,7 +185,8 @@ void BlockedModsDialog::setupWatch() { const QString downloadsFolder = APPLICATION->settings()->get("DownloadsDir").toString(); const QString modsFolder = APPLICATION->settings()->get("CentralModsDir").toString(); - watchPath(downloadsFolder, true); + const bool downloadsFolderWatchRecursive = APPLICATION->settings()->get("DownloadsDirWatchRecursive").toBool(); + watchPath(downloadsFolder, downloadsFolderWatchRecursive); watchPath(modsFolder, true); } diff --git a/launcher/ui/pages/global/LauncherPage.cpp b/launcher/ui/pages/global/LauncherPage.cpp index 324eb461..20e02230 100644 --- a/launcher/ui/pages/global/LauncherPage.cpp +++ b/launcher/ui/pages/global/LauncherPage.cpp @@ -184,6 +184,11 @@ void LauncherPage::on_downloadsDirBrowseBtn_clicked() } } +void LauncherPage::on_downloadsDirWatchRecursiveCheckBox_clicked() +{ + // incase anything needs to be done here +} + void LauncherPage::on_metadataDisableBtn_clicked() { ui->metadataWarningLabel->setHidden(!ui->metadataDisableBtn->isChecked()); @@ -217,6 +222,7 @@ void LauncherPage::applySettings() s->set("CentralModsDir", ui->modsDirTextBox->text()); s->set("IconsDir", ui->iconsDirTextBox->text()); s->set("DownloadsDir", ui->downloadsDirTextBox->text()); + s->set("DownloadsDirWatchRecursive", ui->downloadsDirWatchRecursiveCheckBox->isChecked()); auto sortMode = (InstSortMode)ui->sortingModeGroup->checkedId(); switch (sortMode) diff --git a/launcher/ui/pages/global/LauncherPage.h b/launcher/ui/pages/global/LauncherPage.h index 33f66f1b..c5ebbe39 100644 --- a/launcher/ui/pages/global/LauncherPage.h +++ b/launcher/ui/pages/global/LauncherPage.h @@ -90,6 +90,7 @@ slots: void on_iconsDirBrowseBtn_clicked(); void on_downloadsDirBrowseBtn_clicked(); void on_metadataDisableBtn_clicked(); + void on_downloadsDirWatchRecursiveCheckBox_clicked(); /*! * Updates the font preview diff --git a/launcher/ui/pages/global/LauncherPage.ui b/launcher/ui/pages/global/LauncherPage.ui index 923b7f95..6279d879 100644 --- a/launcher/ui/pages/global/LauncherPage.ui +++ b/launcher/ui/pages/global/LauncherPage.ui @@ -67,19 +67,16 @@ Folders - - + + - ... + &Downloads: + + + downloadsDirTextBox - - - - - - @@ -90,16 +87,25 @@ + + + + + + - - + + - ... + ... + + + @@ -117,33 +123,37 @@ - - + + - &Icons: + ... - - iconsDirTextBox + + + + + + ... - - + + - &Downloads: + &Icons: - downloadsDirTextBox + iconsDirTextBox - - - - - + + + + when looking for mods in places like the blocked mods dialog Prismlauncher will check in sub folders of your downloads folder too. + - ... + Check downloads folder recursively -- cgit From df17f5e899c5cb67d9bf7c4193ef6fb651993ee3 Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Mon, 27 Mar 2023 19:11:26 -0700 Subject: fix: use QDir::Filter::NoDotAndDotDot Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- launcher/ui/dialogs/BlockedModsDialog.cpp | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) (limited to 'launcher/ui/dialogs') diff --git a/launcher/ui/dialogs/BlockedModsDialog.cpp b/launcher/ui/dialogs/BlockedModsDialog.cpp index 36df5f46..7b3a395a 100644 --- a/launcher/ui/dialogs/BlockedModsDialog.cpp +++ b/launcher/ui/dialogs/BlockedModsDialog.cpp @@ -198,12 +198,9 @@ void BlockedModsDialog::watchPath(QString path, bool watch_subdirectories) if (!watch_subdirectories) return; - QDirIterator it(path, QDir::Filter::Dirs, QDirIterator::NoIteratorFlags); + QDirIterator it(path, QDir::Filter::Dirs | QDir::Filter::NoDotAndDotDot, QDirIterator::NoIteratorFlags); while (it.hasNext()) { QString dir_path = it.next(); - QDir to_watch_dir(dir_path); - if (to_watch_dir.dirName() == "." || to_watch_dir.dirName() == "..") - continue; watchPath(dir_path, watch_subdirectories); } } -- cgit From 5ce7874280f648e1db240ad922a2e62a7ccedea2 Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Sun, 2 Apr 2023 18:55:21 -0700 Subject: fix: no loops in watch paths! >:( Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- launcher/ui/dialogs/BlockedModsDialog.cpp | 7 +++++-- launcher/ui/pages/global/LauncherPage.cpp | 5 ----- launcher/ui/pages/global/LauncherPage.h | 1 - 3 files changed, 5 insertions(+), 8 deletions(-) (limited to 'launcher/ui/dialogs') diff --git a/launcher/ui/dialogs/BlockedModsDialog.cpp b/launcher/ui/dialogs/BlockedModsDialog.cpp index 7b3a395a..90078a98 100644 --- a/launcher/ui/dialogs/BlockedModsDialog.cpp +++ b/launcher/ui/dialogs/BlockedModsDialog.cpp @@ -192,6 +192,9 @@ void BlockedModsDialog::setupWatch() void BlockedModsDialog::watchPath(QString path, bool watch_subdirectories) { + if (m_watcher.directories().contains(path)) + return; // don't watch the same path twice (no loops!) + qDebug() << "[Blocked Mods Dialog] Adding Watch Path:" << path; m_watcher.addPath(path); @@ -200,8 +203,8 @@ void BlockedModsDialog::watchPath(QString path, bool watch_subdirectories) QDirIterator it(path, QDir::Filter::Dirs | QDir::Filter::NoDotAndDotDot, QDirIterator::NoIteratorFlags); while (it.hasNext()) { - QString dir_path = it.next(); - watchPath(dir_path, watch_subdirectories); + QString watch_dir = QDir(it.next()).canonicalPath(); // resolve symlinks and relative paths + watchPath(watch_dir, watch_subdirectories); } } diff --git a/launcher/ui/pages/global/LauncherPage.cpp b/launcher/ui/pages/global/LauncherPage.cpp index 20e02230..51652320 100644 --- a/launcher/ui/pages/global/LauncherPage.cpp +++ b/launcher/ui/pages/global/LauncherPage.cpp @@ -184,11 +184,6 @@ void LauncherPage::on_downloadsDirBrowseBtn_clicked() } } -void LauncherPage::on_downloadsDirWatchRecursiveCheckBox_clicked() -{ - // incase anything needs to be done here -} - void LauncherPage::on_metadataDisableBtn_clicked() { ui->metadataWarningLabel->setHidden(!ui->metadataDisableBtn->isChecked()); diff --git a/launcher/ui/pages/global/LauncherPage.h b/launcher/ui/pages/global/LauncherPage.h index c5ebbe39..33f66f1b 100644 --- a/launcher/ui/pages/global/LauncherPage.h +++ b/launcher/ui/pages/global/LauncherPage.h @@ -90,7 +90,6 @@ slots: void on_iconsDirBrowseBtn_clicked(); void on_downloadsDirBrowseBtn_clicked(); void on_metadataDisableBtn_clicked(); - void on_downloadsDirWatchRecursiveCheckBox_clicked(); /*! * Updates the font preview -- cgit From 62c59820cf21d35f97d79bd75c946cfd282be5b8 Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Mon, 3 Apr 2023 15:26:26 -0700 Subject: fix: harden watchPath. NO DUPLICATES! >:( Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- launcher/ui/dialogs/BlockedModsDialog.cpp | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) (limited to 'launcher/ui/dialogs') diff --git a/launcher/ui/dialogs/BlockedModsDialog.cpp b/launcher/ui/dialogs/BlockedModsDialog.cpp index 90078a98..e7404f2d 100644 --- a/launcher/ui/dialogs/BlockedModsDialog.cpp +++ b/launcher/ui/dialogs/BlockedModsDialog.cpp @@ -192,16 +192,19 @@ void BlockedModsDialog::setupWatch() void BlockedModsDialog::watchPath(QString path, bool watch_subdirectories) { - if (m_watcher.directories().contains(path)) + auto to_watch = QFileInfo(path); + auto to_watch_path = to_watch.canonicalPath(); + if (m_watcher.directories().contains(to_watch_path)) return; // don't watch the same path twice (no loops!) qDebug() << "[Blocked Mods Dialog] Adding Watch Path:" << path; - m_watcher.addPath(path); + m_watcher.addPath(to_watch_path); - if (!watch_subdirectories) + if (!to_watch.isDir() || !watch_subdirectories) return; - QDirIterator it(path, QDir::Filter::Dirs | QDir::Filter::NoDotAndDotDot, QDirIterator::NoIteratorFlags); + + QDirIterator it(to_watch_path, QDir::Filter::Dirs | QDir::Filter::NoDotAndDotDot, QDirIterator::NoIteratorFlags); while (it.hasNext()) { QString watch_dir = QDir(it.next()).canonicalPath(); // resolve symlinks and relative paths watchPath(watch_dir, watch_subdirectories); -- cgit From 2321d9c065c363dac8304ab6c826289adb9c86a7 Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Fri, 7 Apr 2023 18:36:35 -0700 Subject: fix: canonical*File*Path() Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- launcher/ui/dialogs/BlockedModsDialog.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'launcher/ui/dialogs') diff --git a/launcher/ui/dialogs/BlockedModsDialog.cpp b/launcher/ui/dialogs/BlockedModsDialog.cpp index e7404f2d..f2ef734b 100644 --- a/launcher/ui/dialogs/BlockedModsDialog.cpp +++ b/launcher/ui/dialogs/BlockedModsDialog.cpp @@ -193,7 +193,7 @@ void BlockedModsDialog::setupWatch() void BlockedModsDialog::watchPath(QString path, bool watch_subdirectories) { auto to_watch = QFileInfo(path); - auto to_watch_path = to_watch.canonicalPath(); + auto to_watch_path = to_watch.canonicalFilePath(); if (m_watcher.directories().contains(to_watch_path)) return; // don't watch the same path twice (no loops!) -- cgit From a02f67ed0e9716a9dc2540b1353e7f25fb056d4b Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Wed, 12 Apr 2023 11:30:02 -0700 Subject: refactor: rename watch_subdirectories -> watch_recurisve (prevent confusion of behavior) Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- launcher/ui/dialogs/BlockedModsDialog.cpp | 6 +++--- launcher/ui/dialogs/BlockedModsDialog.h | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) (limited to 'launcher/ui/dialogs') diff --git a/launcher/ui/dialogs/BlockedModsDialog.cpp b/launcher/ui/dialogs/BlockedModsDialog.cpp index f2ef734b..ba453df6 100644 --- a/launcher/ui/dialogs/BlockedModsDialog.cpp +++ b/launcher/ui/dialogs/BlockedModsDialog.cpp @@ -190,7 +190,7 @@ void BlockedModsDialog::setupWatch() watchPath(modsFolder, true); } -void BlockedModsDialog::watchPath(QString path, bool watch_subdirectories) +void BlockedModsDialog::watchPath(QString path, bool watch_recursive) { auto to_watch = QFileInfo(path); auto to_watch_path = to_watch.canonicalFilePath(); @@ -200,14 +200,14 @@ void BlockedModsDialog::watchPath(QString path, bool watch_subdirectories) qDebug() << "[Blocked Mods Dialog] Adding Watch Path:" << path; m_watcher.addPath(to_watch_path); - if (!to_watch.isDir() || !watch_subdirectories) + if (!to_watch.isDir() || !watch_recursive) return; QDirIterator it(to_watch_path, QDir::Filter::Dirs | QDir::Filter::NoDotAndDotDot, QDirIterator::NoIteratorFlags); while (it.hasNext()) { QString watch_dir = QDir(it.next()).canonicalPath(); // resolve symlinks and relative paths - watchPath(watch_dir, watch_subdirectories); + watchPath(watch_dir, watch_recursive); } } diff --git a/launcher/ui/dialogs/BlockedModsDialog.h b/launcher/ui/dialogs/BlockedModsDialog.h index 01077aa5..e3b7c975 100644 --- a/launcher/ui/dialogs/BlockedModsDialog.h +++ b/launcher/ui/dialogs/BlockedModsDialog.h @@ -79,7 +79,7 @@ class BlockedModsDialog : public QDialog { void update(); void directoryChanged(QString path); void setupWatch(); - void watchPath(QString path, bool watch_subdirectories = false); + void watchPath(QString path, bool watch_recursive = false); void scanPaths(); void scanPath(QString path, bool start_task); void addHashTask(QString path); -- cgit From ff07714e8c955003bb3deec84ec6cce6fc3ef5e6 Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Thu, 27 Apr 2023 16:01:40 +0200 Subject: chore: remove FTB modpack support We have been contacted by Feed the Beast to drop support for the FTB modpack browser from Prism Launcher. Signed-off-by: Sefa Eyeoglu --- launcher/CMakeLists.txt | 16 - .../modplatform/modpacksch/FTBPackInstallTask.cpp | 387 --------------------- .../modplatform/modpacksch/FTBPackInstallTask.h | 101 ------ .../modplatform/modpacksch/FTBPackManifest.cpp | 195 ----------- launcher/modplatform/modpacksch/FTBPackManifest.h | 168 --------- launcher/ui/dialogs/NewInstanceDialog.cpp | 2 - .../ui/pages/modplatform/ftb/FtbFilterModel.cpp | 93 ----- launcher/ui/pages/modplatform/ftb/FtbFilterModel.h | 51 --- launcher/ui/pages/modplatform/ftb/FtbListModel.cpp | 304 ---------------- launcher/ui/pages/modplatform/ftb/FtbListModel.h | 83 ----- launcher/ui/pages/modplatform/ftb/FtbPage.cpp | 199 ----------- launcher/ui/pages/modplatform/ftb/FtbPage.h | 105 ------ launcher/ui/pages/modplatform/ftb/FtbPage.ui | 86 ----- 13 files changed, 1790 deletions(-) delete mode 100644 launcher/modplatform/modpacksch/FTBPackInstallTask.cpp delete mode 100644 launcher/modplatform/modpacksch/FTBPackInstallTask.h delete mode 100644 launcher/modplatform/modpacksch/FTBPackManifest.cpp delete mode 100644 launcher/modplatform/modpacksch/FTBPackManifest.h delete mode 100644 launcher/ui/pages/modplatform/ftb/FtbFilterModel.cpp delete mode 100644 launcher/ui/pages/modplatform/ftb/FtbFilterModel.h delete mode 100644 launcher/ui/pages/modplatform/ftb/FtbListModel.cpp delete mode 100644 launcher/ui/pages/modplatform/ftb/FtbListModel.h delete mode 100644 launcher/ui/pages/modplatform/ftb/FtbPage.cpp delete mode 100644 launcher/ui/pages/modplatform/ftb/FtbPage.h delete mode 100644 launcher/ui/pages/modplatform/ftb/FtbPage.ui (limited to 'launcher/ui/dialogs') diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index 074570e3..ee36175f 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -524,13 +524,6 @@ set(MODRINTH_SOURCES modplatform/modrinth/ModrinthInstanceCreationTask.h ) -set(MODPACKSCH_SOURCES - modplatform/modpacksch/FTBPackInstallTask.h - modplatform/modpacksch/FTBPackInstallTask.cpp - modplatform/modpacksch/FTBPackManifest.h - modplatform/modpacksch/FTBPackManifest.cpp -) - set(PACKWIZ_SOURCES modplatform/packwiz/Packwiz.h modplatform/packwiz/Packwiz.cpp @@ -599,7 +592,6 @@ set(LOGIC_SOURCES ${FTB_SOURCES} ${FLAME_SOURCES} ${MODRINTH_SOURCES} - ${MODPACKSCH_SOURCES} ${PACKWIZ_SOURCES} ${TECHNIC_SOURCES} ${ATLAUNCHER_SOURCES} @@ -797,13 +789,6 @@ SET(LAUNCHER_SOURCES ui/pages/modplatform/atlauncher/AtlUserInteractionSupportImpl.cpp ui/pages/modplatform/atlauncher/AtlUserInteractionSupportImpl.h - ui/pages/modplatform/ftb/FtbFilterModel.cpp - ui/pages/modplatform/ftb/FtbFilterModel.h - ui/pages/modplatform/ftb/FtbListModel.cpp - ui/pages/modplatform/ftb/FtbListModel.h - ui/pages/modplatform/ftb/FtbPage.cpp - ui/pages/modplatform/ftb/FtbPage.h - ui/pages/modplatform/legacy_ftb/Page.cpp ui/pages/modplatform/legacy_ftb/Page.h ui/pages/modplatform/legacy_ftb/ListModel.h @@ -978,7 +963,6 @@ qt_wrap_ui(LAUNCHER_UI ui/pages/modplatform/flame/FlamePage.ui ui/pages/modplatform/legacy_ftb/Page.ui ui/pages/modplatform/ImportPage.ui - ui/pages/modplatform/ftb/FtbPage.ui ui/pages/modplatform/modrinth/ModrinthPage.ui ui/pages/modplatform/technic/TechnicPage.ui ui/widgets/InstanceCardWidget.ui diff --git a/launcher/modplatform/modpacksch/FTBPackInstallTask.cpp b/launcher/modplatform/modpacksch/FTBPackInstallTask.cpp deleted file mode 100644 index 68d4751c..00000000 --- a/launcher/modplatform/modpacksch/FTBPackInstallTask.cpp +++ /dev/null @@ -1,387 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-only -/* - * Prism Launcher - Minecraft Launcher - * Copyright (C) 2022 flowln - * Copyright (c) 2022 Jamie Mansfield - * Copyright (C) 2022 Sefa Eyeoglu - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, version 3. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * This file incorporates work covered by the following copyright and - * permission notice: - * - * Copyright 2020-2021 Jamie Mansfield - * Copyright 2020-2021 Petr Mrazek - * - * 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 "FTBPackInstallTask.h" - -#include "FileSystem.h" -#include "Json.h" -#include "minecraft/MinecraftInstance.h" -#include "minecraft/PackProfile.h" -#include "modplatform/flame/PackManifest.h" -#include "net/ChecksumValidator.h" -#include "settings/INISettingsObject.h" - -#include "Application.h" -#include "BuildConfig.h" -#include "ui/dialogs/BlockedModsDialog.h" - -namespace ModpacksCH { - -PackInstallTask::PackInstallTask(Modpack pack, QString version, QWidget* parent) - : m_pack(std::move(pack)), m_version_name(std::move(version)), m_parent(parent) -{} - -bool PackInstallTask::abort() -{ - if (!canAbort()) - return false; - - bool aborted = true; - - if (m_net_job) - aborted &= m_net_job->abort(); - if (m_mod_id_resolver_task) - aborted &= m_mod_id_resolver_task->abort(); - - return aborted ? InstanceTask::abort() : false; -} - -void PackInstallTask::executeTask() -{ - setStatus(tr("Getting the manifest...")); - setAbortable(false); - - // Find pack version - auto version_it = std::find_if(m_pack.versions.constBegin(), m_pack.versions.constEnd(), - [this](ModpacksCH::VersionInfo const& a) { return a.name == m_version_name; }); - - if (version_it == m_pack.versions.constEnd()) { - emitFailed(tr("Failed to find pack version %1").arg(m_version_name)); - return; - } - - auto version = *version_it; - - auto netJob = makeShared("ModpacksCH::VersionFetch", APPLICATION->network()); - - auto searchUrl = QString(BuildConfig.MODPACKSCH_API_BASE_URL + "public/modpack/%1/%2").arg(m_pack.id).arg(version.id); - netJob->addNetAction(Net::Download::makeByteArray(QUrl(searchUrl), &m_response)); - - QObject::connect(netJob.get(), &NetJob::succeeded, this, &PackInstallTask::onManifestDownloadSucceeded); - QObject::connect(netJob.get(), &NetJob::failed, this, &PackInstallTask::onManifestDownloadFailed); - QObject::connect(netJob.get(), &NetJob::aborted, this, &PackInstallTask::abort); - QObject::connect(netJob.get(), &NetJob::progress, this, &PackInstallTask::setProgress); - - m_net_job = netJob; - - setAbortable(true); - netJob->start(); -} - -void PackInstallTask::onManifestDownloadSucceeded() -{ - m_net_job.reset(); - - QJsonParseError parse_error{}; - QJsonDocument doc = QJsonDocument::fromJson(m_response, &parse_error); - if (parse_error.error != QJsonParseError::NoError) { - qWarning() << "Error while parsing JSON response from ModpacksCH at " << parse_error.offset - << " reason: " << parse_error.errorString(); - qWarning() << m_response; - return; - } - - ModpacksCH::Version version; - try { - auto obj = Json::requireObject(doc); - ModpacksCH::loadVersion(version, obj); - } catch (const JSONValidationError& e) { - emitFailed(tr("Could not understand pack manifest:\n") + e.cause()); - return; - } - - m_version = version; - - resolveMods(); -} - -void PackInstallTask::resolveMods() -{ - setStatus(tr("Resolving mods...")); - setAbortable(false); - setProgress(0, 100); - - m_file_id_map.clear(); - - Flame::Manifest manifest; - int index = 0; - - for (auto const& file : m_version.files) { - if (!file.serverOnly && file.url.isEmpty()) { - if (file.curseforge.file_id <= 0) { - emitFailed(tr("Invalid manifest: There's no information available to download the file '%1'!").arg(file.name)); - return; - } - - Flame::File flame_file; - flame_file.projectId = file.curseforge.project_id; - flame_file.fileId = file.curseforge.file_id; - flame_file.hash = file.sha1; - - manifest.files.insert(flame_file.fileId, flame_file); - m_file_id_map.append(flame_file.fileId); - } else { - m_file_id_map.append(-1); - } - - index++; - } - - m_mod_id_resolver_task.reset(new Flame::FileResolvingTask(APPLICATION->network(), manifest)); - - connect(m_mod_id_resolver_task.get(), &Flame::FileResolvingTask::succeeded, this, &PackInstallTask::onResolveModsSucceeded); - connect(m_mod_id_resolver_task.get(), &Flame::FileResolvingTask::failed, this, &PackInstallTask::onResolveModsFailed); - connect(m_mod_id_resolver_task.get(), &Flame::FileResolvingTask::aborted, this, &PackInstallTask::abort); - connect(m_mod_id_resolver_task.get(), &Flame::FileResolvingTask::progress, this, &PackInstallTask::setProgress); - - setAbortable(true); - - m_mod_id_resolver_task->start(); -} - -void PackInstallTask::onResolveModsSucceeded() -{ - auto anyBlocked = false; - - Flame::Manifest results = m_mod_id_resolver_task->getResults(); - for (int index = 0; index < m_file_id_map.size(); index++) { - auto const file_id = m_file_id_map.at(index); - if (file_id < 0) - continue; - - Flame::File results_file = results.files[file_id]; - VersionFile& local_file = m_version.files[index]; - - // First check for blocked mods - if (!results_file.resolved || results_file.url.isEmpty()) { - BlockedMod blocked_mod; - blocked_mod.name = local_file.name; - blocked_mod.websiteUrl = results_file.websiteUrl; - blocked_mod.hash = results_file.hash; - blocked_mod.matched = false; - blocked_mod.localPath = ""; - blocked_mod.targetFolder = results_file.targetFolder; - - m_blocked_mods.append(blocked_mod); - - anyBlocked = true; - } else { - local_file.url = results_file.url.toString(); - } - } - - m_mod_id_resolver_task.reset(); - - if (anyBlocked) { - qDebug() << "Blocked files found, displaying file list"; - - BlockedModsDialog message_dialog(m_parent, tr("Blocked files found"), - tr("The following files are not available for download in third party launchers.
" - "You will need to manually download them and add them to the instance."), - m_blocked_mods); - - message_dialog.setModal(true); - - if (message_dialog.exec() == QDialog::Accepted) { - qDebug() << "Post dialog blocked mods list: " << m_blocked_mods; - createInstance(); - } else { - abort(); - } - - } else { - createInstance(); - } -} - -void PackInstallTask::createInstance() -{ - setAbortable(false); - - setStatus(tr("Creating the instance...")); - QCoreApplication::processEvents(); - - auto instanceConfigPath = FS::PathCombine(m_stagingPath, "instance.cfg"); - auto instanceSettings = std::make_shared(instanceConfigPath); - - MinecraftInstance instance(m_globalSettings, instanceSettings, m_stagingPath); - auto components = instance.getPackProfile(); - components->buildingFromScratch(); - - for (auto target : m_version.targets) { - if (target.type == "game" && target.name == "minecraft") { - components->setComponentVersion("net.minecraft", target.version, true); - break; - } - } - - for (auto target : m_version.targets) { - if (target.type != "modloader") - continue; - - if (target.name == "forge") { - components->setComponentVersion("net.minecraftforge", target.version); - } else if (target.name == "fabric") { - components->setComponentVersion("net.fabricmc.fabric-loader", target.version); - } - } - - // install any jar mods - QDir jarModsDir(FS::PathCombine(m_stagingPath, "minecraft", "jarmods")); - if (jarModsDir.exists()) { - QStringList jarMods; - - for (const auto& info : jarModsDir.entryInfoList(QDir::NoDotAndDotDot | QDir::Files)) { - jarMods.push_back(info.absoluteFilePath()); - } - - components->installJarMods(jarMods); - } - - components->saveNow(); - - instance.setName(name()); - instance.setIconKey(m_instIcon); - instance.setManagedPack("modpacksch", QString::number(m_pack.id), m_pack.name, QString::number(m_version.id), m_version.name); - - instance.saveNow(); - - onCreateInstanceSucceeded(); -} - -void PackInstallTask::onCreateInstanceSucceeded() -{ - downloadPack(); -} - -void PackInstallTask::downloadPack() -{ - setStatus(tr("Downloading mods...")); - setAbortable(false); - - auto jobPtr = makeShared(tr("Mod download"), APPLICATION->network()); - for (auto const& file : m_version.files) { - if (file.serverOnly || file.url.isEmpty()) - continue; - - auto path = FS::PathCombine(m_stagingPath, ".minecraft", file.path, file.name); - qDebug() << "Will try to download" << file.url << "to" << path; - - QFileInfo file_info(file.name); - - auto dl = Net::Download::makeFile(file.url, path); - if (!file.sha1.isEmpty()) { - auto rawSha1 = QByteArray::fromHex(file.sha1.toLatin1()); - dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Sha1, rawSha1)); - } - - jobPtr->addNetAction(dl); - } - - connect(jobPtr.get(), &NetJob::succeeded, this, &PackInstallTask::onModDownloadSucceeded); - connect(jobPtr.get(), &NetJob::failed, this, &PackInstallTask::onModDownloadFailed); - connect(jobPtr.get(), &NetJob::aborted, this, &PackInstallTask::abort); - connect(jobPtr.get(), &NetJob::progress, this, &PackInstallTask::setProgress); - - m_net_job = jobPtr; - - setAbortable(true); - jobPtr->start(); -} - -void PackInstallTask::onModDownloadSucceeded() -{ - m_net_job.reset(); - if (!m_blocked_mods.isEmpty()) { - copyBlockedMods(); - } - emitSucceeded(); -} - -void PackInstallTask::onManifestDownloadFailed(QString reason) -{ - m_net_job.reset(); - emitFailed(reason); -} -void PackInstallTask::onResolveModsFailed(QString reason) -{ - m_net_job.reset(); - emitFailed(reason); -} -void PackInstallTask::onCreateInstanceFailed(QString reason) -{ - emitFailed(reason); -} -void PackInstallTask::onModDownloadFailed(QString reason) -{ - m_net_job.reset(); - emitFailed(reason); -} - -/// @brief copy the matched blocked mods to the instance staging area -void PackInstallTask::copyBlockedMods() -{ - setStatus(tr("Copying Blocked Mods...")); - setAbortable(false); - int i = 0; - int total = m_blocked_mods.length(); - setProgress(i, total); - for (auto const& mod : m_blocked_mods) { - if (!mod.matched) { - qDebug() << mod.name << "was not matched to a local file, skipping copy"; - continue; - } - - auto dest_path = FS::PathCombine(m_stagingPath, ".minecraft", mod.targetFolder, mod.name); - - setStatus(tr("Copying Blocked Mods (%1 out of %2 are done)").arg(QString::number(i), QString::number(total))); - - qDebug() << "Will try to copy" << mod.localPath << "to" << dest_path; - - if (!FS::copy(mod.localPath, dest_path)()) { - qDebug() << "Copy of" << mod.localPath << "to" << dest_path << "Failed"; - } - - i++; - setProgress(i, total); - } - - setAbortable(true); -} - -} // namespace ModpacksCH diff --git a/launcher/modplatform/modpacksch/FTBPackInstallTask.h b/launcher/modplatform/modpacksch/FTBPackInstallTask.h deleted file mode 100644 index 97b1eb0b..00000000 --- a/launcher/modplatform/modpacksch/FTBPackInstallTask.h +++ /dev/null @@ -1,101 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-only -/* - * Prism Launcher - Minecraft Launcher - * Copyright (C) 2022 flowln - * Copyright (C) 2022 Sefa Eyeoglu - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, version 3. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * This file incorporates work covered by the following copyright and - * permission notice: - * - * Copyright 2020-2021 Jamie Mansfield - * Copyright 2020-2021 Petr Mrazek - * - * 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 "FTBPackManifest.h" - -#include "InstanceTask.h" -#include "QObjectPtr.h" -#include "modplatform/flame/FileResolvingTask.h" -#include "net/NetJob.h" -#include "ui/dialogs/BlockedModsDialog.h" - -#include - -namespace ModpacksCH { - -class PackInstallTask final : public InstanceTask -{ - Q_OBJECT - -public: - explicit PackInstallTask(Modpack pack, QString version, QWidget* parent = nullptr); - ~PackInstallTask() override = default; - - bool abort() override; - -protected: - void executeTask() override; - -private slots: - void onManifestDownloadSucceeded(); - void onResolveModsSucceeded(); - void onCreateInstanceSucceeded(); - void onModDownloadSucceeded(); - - void onManifestDownloadFailed(QString reason); - void onResolveModsFailed(QString reason); - void onCreateInstanceFailed(QString reason); - void onModDownloadFailed(QString reason); - -private: - void resolveMods(); - void createInstance(); - void downloadPack(); - void copyBlockedMods(); - -private: - NetJob::Ptr m_net_job = nullptr; - shared_qobject_ptr m_mod_id_resolver_task = nullptr; - - QList m_file_id_map; - - QByteArray m_response; - - Modpack m_pack; - QString m_version_name; - Version m_version; - - QMap m_files_to_copy; - QList m_blocked_mods; - - //FIXME: nuke - QWidget* m_parent; -}; - -} diff --git a/launcher/modplatform/modpacksch/FTBPackManifest.cpp b/launcher/modplatform/modpacksch/FTBPackManifest.cpp deleted file mode 100644 index 421527ae..00000000 --- a/launcher/modplatform/modpacksch/FTBPackManifest.cpp +++ /dev/null @@ -1,195 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-only -/* - * PolyMC - Minecraft Launcher - * Copyright (C) 2022 Sefa Eyeoglu - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, version 3. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * This file incorporates work covered by the following copyright and - * permission notice: - * - * Copyright 2020 Jamie Mansfield - * Copyright 2020-2021 Petr Mrazek - * - * 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 "FTBPackManifest.h" - -#include "Json.h" - -static void loadSpecs(ModpacksCH::Specs & s, QJsonObject & obj) -{ - s.id = Json::requireInteger(obj, "id"); - s.minimum = Json::requireInteger(obj, "minimum"); - s.recommended = Json::requireInteger(obj, "recommended"); -} - -static void loadTag(ModpacksCH::Tag & t, QJsonObject & obj) -{ - t.id = Json::requireInteger(obj, "id"); - t.name = Json::requireString(obj, "name"); -} - -static void loadArt(ModpacksCH::Art & a, QJsonObject & obj) -{ - a.id = Json::requireInteger(obj, "id"); - a.url = Json::requireString(obj, "url"); - a.type = Json::requireString(obj, "type"); - a.width = Json::requireInteger(obj, "width"); - a.height = Json::requireInteger(obj, "height"); - a.compressed = Json::requireBoolean(obj, "compressed"); - a.sha1 = Json::requireString(obj, "sha1"); - a.size = Json::requireInteger(obj, "size"); - a.updated = Json::requireInteger(obj, "updated"); -} - -static void loadAuthor(ModpacksCH::Author & a, QJsonObject & obj) -{ - a.id = Json::requireInteger(obj, "id"); - a.name = Json::requireString(obj, "name"); - a.type = Json::requireString(obj, "type"); - a.website = Json::requireString(obj, "website"); - a.updated = Json::requireInteger(obj, "updated"); -} - -static void loadVersionInfo(ModpacksCH::VersionInfo & v, QJsonObject & obj) -{ - v.id = Json::requireInteger(obj, "id"); - v.name = Json::requireString(obj, "name"); - v.type = Json::requireString(obj, "type"); - v.updated = Json::requireInteger(obj, "updated"); - auto specs = Json::requireObject(obj, "specs"); - loadSpecs(v.specs, specs); -} - -void ModpacksCH::loadModpack(ModpacksCH::Modpack & m, QJsonObject & obj) -{ - m.id = Json::requireInteger(obj, "id"); - m.name = Json::requireString(obj, "name"); - m.synopsis = Json::requireString(obj, "synopsis"); - m.description = Json::requireString(obj, "description"); - m.type = Json::requireString(obj, "type"); - m.featured = Json::requireBoolean(obj, "featured"); - m.installs = Json::requireInteger(obj, "installs"); - m.plays = Json::requireInteger(obj, "plays"); - m.updated = Json::requireInteger(obj, "updated"); - m.refreshed = Json::requireInteger(obj, "refreshed"); - auto artArr = Json::requireArray(obj, "art"); - for (QJsonValueRef artRaw : artArr) - { - auto artObj = Json::requireObject(artRaw); - ModpacksCH::Art art; - loadArt(art, artObj); - m.art.append(art); - } - auto authorArr = Json::requireArray(obj, "authors"); - for (QJsonValueRef authorRaw : authorArr) - { - auto authorObj = Json::requireObject(authorRaw); - ModpacksCH::Author author; - loadAuthor(author, authorObj); - m.authors.append(author); - } - auto versionArr = Json::requireArray(obj, "versions"); - for (QJsonValueRef versionRaw : versionArr) - { - auto versionObj = Json::requireObject(versionRaw); - ModpacksCH::VersionInfo version; - loadVersionInfo(version, versionObj); - m.versions.append(version); - } - auto tagArr = Json::requireArray(obj, "tags"); - for (QJsonValueRef tagRaw : tagArr) - { - auto tagObj = Json::requireObject(tagRaw); - ModpacksCH::Tag tag; - loadTag(tag, tagObj); - m.tags.append(tag); - } - m.updated = Json::requireInteger(obj, "updated"); -} - -static void loadVersionTarget(ModpacksCH::VersionTarget & a, QJsonObject & obj) -{ - a.id = Json::requireInteger(obj, "id"); - a.name = Json::requireString(obj, "name"); - a.type = Json::requireString(obj, "type"); - a.version = Json::requireString(obj, "version"); - a.updated = Json::requireInteger(obj, "updated"); -} - -static void loadVersionFile(ModpacksCH::VersionFile & a, QJsonObject & obj) -{ - a.id = Json::requireInteger(obj, "id"); - a.type = Json::requireString(obj, "type"); - a.path = Json::requireString(obj, "path"); - a.name = Json::requireString(obj, "name"); - a.version = Json::requireString(obj, "version"); - a.url = Json::ensureString(obj, "url"); // optional - a.sha1 = Json::requireString(obj, "sha1"); - a.size = Json::requireInteger(obj, "size"); - a.clientOnly = Json::requireBoolean(obj, "clientonly"); - a.serverOnly = Json::requireBoolean(obj, "serveronly"); - a.optional = Json::requireBoolean(obj, "optional"); - a.updated = Json::requireInteger(obj, "updated"); - auto curseforgeObj = Json::ensureObject(obj, "curseforge"); // optional - a.curseforge.project_id = Json::ensureInteger(curseforgeObj, "project"); - a.curseforge.file_id = Json::ensureInteger(curseforgeObj, "file"); -} - -void ModpacksCH::loadVersion(ModpacksCH::Version & m, QJsonObject & obj) -{ - m.id = Json::requireInteger(obj, "id"); - m.parent = Json::requireInteger(obj, "parent"); - m.name = Json::requireString(obj, "name"); - m.type = Json::requireString(obj, "type"); - m.installs = Json::requireInteger(obj, "installs"); - m.plays = Json::requireInteger(obj, "plays"); - m.updated = Json::requireInteger(obj, "updated"); - m.refreshed = Json::requireInteger(obj, "refreshed"); - auto specs = Json::requireObject(obj, "specs"); - loadSpecs(m.specs, specs); - auto targetArr = Json::requireArray(obj, "targets"); - for (QJsonValueRef targetRaw : targetArr) - { - auto versionObj = Json::requireObject(targetRaw); - ModpacksCH::VersionTarget target; - loadVersionTarget(target, versionObj); - m.targets.append(target); - } - auto fileArr = Json::requireArray(obj, "files"); - for (QJsonValueRef fileRaw : fileArr) - { - auto fileObj = Json::requireObject(fileRaw); - ModpacksCH::VersionFile file; - loadVersionFile(file, fileObj); - m.files.append(file); - } -} - -//static void loadVersionChangelog(ModpacksCH::VersionChangelog & m, QJsonObject & obj) -//{ -// m.content = Json::requireString(obj, "content"); -// m.updated = Json::requireInteger(obj, "updated"); -//} diff --git a/launcher/modplatform/modpacksch/FTBPackManifest.h b/launcher/modplatform/modpacksch/FTBPackManifest.h deleted file mode 100644 index a8b6f35e..00000000 --- a/launcher/modplatform/modpacksch/FTBPackManifest.h +++ /dev/null @@ -1,168 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-only -/* - * PolyMC - Minecraft Launcher - * Copyright (C) 2022 Sefa Eyeoglu - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, version 3. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * This file incorporates work covered by the following copyright and - * permission notice: - * - * Copyright 2020-2021 Jamie Mansfield - * Copyright 2020 Petr Mrazek - * - * 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 -#include -#include - -namespace ModpacksCH -{ - -struct Specs -{ - int id; - int minimum; - int recommended; -}; - -struct Tag -{ - int id; - QString name; -}; - -struct Art -{ - int id; - QString url; - QString type; - int width; - int height; - bool compressed; - QString sha1; - int size; - int64_t updated; -}; - -struct Author -{ - int id; - QString name; - QString type; - QString website; - int64_t updated; -}; - -struct VersionInfo -{ - int id; - QString name; - QString type; - int64_t updated; - Specs specs; -}; - -struct Modpack -{ - int id; - QString name; - QString synopsis; - QString description; - QString type; - bool featured; - int installs; - int plays; - int64_t updated; - int64_t refreshed; - QVector art; - QVector authors; - QVector versions; - QVector tags; -}; - -struct VersionTarget -{ - int id; - QString type; - QString name; - QString version; - int64_t updated; -}; - -struct VersionFileCurseForge -{ - int project_id; - int file_id; -}; - -struct VersionFile -{ - int id; - QString type; - QString path; - QString name; - QString version; - QString url; - QString sha1; - int size; - bool clientOnly; - bool serverOnly; - bool optional; - int64_t updated; - VersionFileCurseForge curseforge; -}; - -struct Version -{ - int id; - int parent; - QString name; - QString type; - int installs; - int plays; - int64_t updated; - int64_t refreshed; - Specs specs; - QVector targets; - QVector files; -}; - -struct VersionChangelog -{ - QString content; - int64_t updated; -}; - -void loadModpack(Modpack & m, QJsonObject & obj); - -void loadVersion(Version & m, QJsonObject & obj); -} - -Q_DECLARE_METATYPE(ModpacksCH::Modpack) diff --git a/launcher/ui/dialogs/NewInstanceDialog.cpp b/launcher/ui/dialogs/NewInstanceDialog.cpp index df182f09..64ed7673 100644 --- a/launcher/ui/dialogs/NewInstanceDialog.cpp +++ b/launcher/ui/dialogs/NewInstanceDialog.cpp @@ -56,7 +56,6 @@ #include "ui/widgets/PageContainer.h" #include "ui/pages/modplatform/VanillaPage.h" #include "ui/pages/modplatform/atlauncher/AtlPage.h" -#include "ui/pages/modplatform/ftb/FtbPage.h" #include "ui/pages/modplatform/legacy_ftb/Page.h" #include "ui/pages/modplatform/flame/FlamePage.h" #include "ui/pages/modplatform/ImportPage.h" @@ -168,7 +167,6 @@ QList NewInstanceDialog::getPages() pages.append(new AtlPage(this)); if (APPLICATION->capabilities() & Application::SupportsFlame) pages.append(new FlamePage(this)); - pages.append(new FtbPage(this)); pages.append(new LegacyFTB::Page(this)); pages.append(new ModrinthPage(this)); pages.append(new TechnicPage(this)); diff --git a/launcher/ui/pages/modplatform/ftb/FtbFilterModel.cpp b/launcher/ui/pages/modplatform/ftb/FtbFilterModel.cpp deleted file mode 100644 index e2b548f2..00000000 --- a/launcher/ui/pages/modplatform/ftb/FtbFilterModel.cpp +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Copyright 2020-2021 Jamie Mansfield - * - * 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 "FtbFilterModel.h" - -#include - -#include "modplatform/modpacksch/FTBPackManifest.h" - -#include "StringUtils.h" - -namespace Ftb { - -FilterModel::FilterModel(QObject *parent) : QSortFilterProxyModel(parent) -{ - currentSorting = Sorting::ByPlays; - sortings.insert(tr("Sort by Plays"), Sorting::ByPlays); - sortings.insert(tr("Sort by Installs"), Sorting::ByInstalls); - sortings.insert(tr("Sort by Name"), Sorting::ByName); -} - -const QMap FilterModel::getAvailableSortings() -{ - return sortings; -} - -QString FilterModel::translateCurrentSorting() -{ - return sortings.key(currentSorting); -} - -void FilterModel::setSorting(Sorting sorting) -{ - currentSorting = sorting; - invalidate(); -} - -FilterModel::Sorting FilterModel::getCurrentSorting() -{ - return currentSorting; -} - -void FilterModel::setSearchTerm(const QString& term) -{ - searchTerm = term.trimmed(); - invalidate(); -} - -bool FilterModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const -{ - if (searchTerm.isEmpty()) { - return true; - } - - auto index = sourceModel()->index(sourceRow, 0, sourceParent); - auto pack = sourceModel()->data(index, Qt::UserRole).value(); - return pack.name.contains(searchTerm, Qt::CaseInsensitive); -} - -bool FilterModel::lessThan(const QModelIndex &left, const QModelIndex &right) const -{ - ModpacksCH::Modpack leftPack = sourceModel()->data(left, Qt::UserRole).value(); - ModpacksCH::Modpack rightPack = sourceModel()->data(right, Qt::UserRole).value(); - - if (currentSorting == ByPlays) { - return leftPack.plays < rightPack.plays; - } - else if (currentSorting == ByInstalls) { - return leftPack.installs < rightPack.installs; - } - else if (currentSorting == ByName) { - return StringUtils::naturalCompare(leftPack.name, rightPack.name, Qt::CaseSensitive) >= 0; - } - - // Invalid sorting set, somehow... - qWarning() << "Invalid sorting set!"; - return true; -} - -} diff --git a/launcher/ui/pages/modplatform/ftb/FtbFilterModel.h b/launcher/ui/pages/modplatform/ftb/FtbFilterModel.h deleted file mode 100644 index 1be28e99..00000000 --- a/launcher/ui/pages/modplatform/ftb/FtbFilterModel.h +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright 2020-2021 Jamie Mansfield - * - * 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 - -namespace Ftb { - -class FilterModel : public QSortFilterProxyModel -{ - Q_OBJECT - -public: - FilterModel(QObject* parent = Q_NULLPTR); - enum Sorting { - ByPlays, - ByInstalls, - ByName, - }; - const QMap getAvailableSortings(); - QString translateCurrentSorting(); - void setSorting(Sorting sorting); - Sorting getCurrentSorting(); - void setSearchTerm(const QString& term); - -protected: - bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override; - bool lessThan(const QModelIndex &left, const QModelIndex &right) const override; - -private: - QMap sortings; - Sorting currentSorting; - QString searchTerm { "" }; - -}; - -} diff --git a/launcher/ui/pages/modplatform/ftb/FtbListModel.cpp b/launcher/ui/pages/modplatform/ftb/FtbListModel.cpp deleted file mode 100644 index e8065415..00000000 --- a/launcher/ui/pages/modplatform/ftb/FtbListModel.cpp +++ /dev/null @@ -1,304 +0,0 @@ -/* - * Copyright 2020-2021 Jamie Mansfield - * - * 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 "FtbListModel.h" - -#include "BuildConfig.h" -#include "Application.h" -#include "Json.h" - -#include - -namespace Ftb { - -ListModel::ListModel(QObject *parent) : QAbstractListModel(parent) -{ -} - -ListModel::~ListModel() -{ -} - -int ListModel::rowCount(const QModelIndex &parent) const -{ - return parent.isValid() ? 0 : modpacks.size(); -} - -int ListModel::columnCount(const QModelIndex &parent) const -{ - return parent.isValid() ? 0 : 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); - } - - ModpacksCH::Modpack pack = modpacks.at(pos); - if(role == Qt::DisplayRole) - { - return pack.name; - } - else if (role == Qt::ToolTipRole) - { - return pack.synopsis; - } - else if(role == Qt::DecorationRole) - { - QIcon placeholder = APPLICATION->getThemedIcon("screenshot-placeholder"); - - auto iter = m_logoMap.find(pack.name); - if (iter != m_logoMap.end()) { - auto & logo = *iter; - if(!logo.result.isNull()) { - return logo.result; - } - return placeholder; - } - - for(auto art : pack.art) { - if(art.type == "square") { - ((ListModel *)this)->requestLogo(pack.name, art.url); - } - } - return placeholder; - } - else if(role == Qt::UserRole) - { - QVariant v; - v.setValue(pack); - return v; - } - - return QVariant(); -} - -void ListModel::getLogo(const QString &logo, const QString &logoUrl, LogoCallback callback) -{ - if(m_logoMap.contains(logo)) - { - callback(APPLICATION->metacache()->resolveEntry("ModpacksCHPacks", QString("logos/%1").arg(logo.section(".", 0, 0)))->getFullPath()); - } - else - { - requestLogo(logo, logoUrl); - } -} - -void ListModel::request() -{ - m_aborted = false; - - beginResetModel(); - modpacks.clear(); - endResetModel(); - - auto netJob = makeShared("Ftb::Request", APPLICATION->network()); - auto url = QString(BuildConfig.MODPACKSCH_API_BASE_URL + "public/modpack/all"); - netJob->addNetAction(Net::Download::makeByteArray(QUrl(url), &response)); - jobPtr = netJob; - jobPtr->start(); - - QObject::connect(netJob.get(), &NetJob::succeeded, this, &ListModel::requestFinished); - QObject::connect(netJob.get(), &NetJob::failed, this, &ListModel::requestFailed); -} - -void ListModel::abortRequest() -{ - m_aborted = jobPtr->abort(); - jobPtr.reset(); -} - -void ListModel::requestFinished() -{ - jobPtr.reset(); - remainingPacks.clear(); - - QJsonParseError parse_error {}; - QJsonDocument doc = QJsonDocument::fromJson(response, &parse_error); - if(parse_error.error != QJsonParseError::NoError) { - qWarning() << "Error while parsing JSON response from ModpacksCH at " << parse_error.offset << " reason: " << parse_error.errorString(); - qWarning() << response; - return; - } - - auto packs = doc.object().value("packs").toArray(); - for(auto pack : packs) { - auto packId = pack.toInt(); - remainingPacks.append(packId); - } - - if(!remainingPacks.isEmpty()) { - currentPack = remainingPacks.at(0); - requestPack(); - } -} - -void ListModel::requestFailed(QString reason) -{ - jobPtr.reset(); - remainingPacks.clear(); -} - -void ListModel::requestPack() -{ - auto netJob = makeShared("Ftb::Search", APPLICATION->network()); - auto searchUrl = QString(BuildConfig.MODPACKSCH_API_BASE_URL + "public/modpack/%1").arg(currentPack); - netJob->addNetAction(Net::Download::makeByteArray(QUrl(searchUrl), &response)); - jobPtr = netJob; - jobPtr->start(); - - QObject::connect(netJob.get(), &NetJob::succeeded, this, &ListModel::packRequestFinished); - QObject::connect(netJob.get(), &NetJob::failed, this, &ListModel::packRequestFailed); -} - -void ListModel::packRequestFinished() -{ - if (!jobPtr || m_aborted) - return; - - jobPtr.reset(); - remainingPacks.removeOne(currentPack); - - QJsonParseError parse_error; - QJsonDocument doc = QJsonDocument::fromJson(response, &parse_error); - - if(parse_error.error != QJsonParseError::NoError) { - qWarning() << "Error while parsing JSON response from ModpacksCH at " << parse_error.offset << " reason: " << parse_error.errorString(); - qWarning() << response; - return; - } - - auto obj = doc.object(); - - ModpacksCH::Modpack pack; - try - { - ModpacksCH::loadModpack(pack, obj); - } - catch (const JSONValidationError &e) - { - qDebug() << QString::fromUtf8(response); - qWarning() << "Error while reading pack manifest from ModpacksCH: " << e.cause(); - return; - } - - // Since there is no guarantee that packs have a version, this will just - // ignore those "dud" packs. - if (pack.versions.empty()) - { - qWarning() << "ModpacksCH Pack " << pack.id << " ignored. reason: lacking any versions"; - } - else - { - beginInsertRows(QModelIndex(), modpacks.size(), modpacks.size()); - modpacks.append(pack); - endInsertRows(); - } - - if(!remainingPacks.isEmpty()) { - currentPack = remainingPacks.at(0); - requestPack(); - } -} - -void ListModel::packRequestFailed(QString reason) -{ - jobPtr.reset(); - remainingPacks.removeOne(currentPack); -} - -void ListModel::logoLoaded(QString logo, bool stale) -{ - auto & logoObj = m_logoMap[logo]; - logoObj.downloadJob.reset(); - QString smallPath = logoObj.fullpath + ".small"; - - QFileInfo smallInfo(smallPath); - - if(stale || !smallInfo.exists()) { - QImage image(logoObj.fullpath); - if (image.isNull()) - { - logoObj.failed = true; - return; - } - QImage small; - if (image.width() > image.height()) { - small = image.scaledToWidth(512).scaledToWidth(256, Qt::SmoothTransformation); - } - else { - small = image.scaledToHeight(512).scaledToHeight(256, Qt::SmoothTransformation); - } - QPoint offset((256 - small.width()) / 2, (256 - small.height()) / 2); - QImage square(QSize(256, 256), QImage::Format_ARGB32); - square.fill(Qt::transparent); - - QPainter painter(&square); - painter.drawImage(offset, small); - painter.end(); - - square.save(logoObj.fullpath + ".small", "PNG"); - } - - logoObj.result = QIcon(logoObj.fullpath + ".small"); - for(int i = 0; i < modpacks.size(); i++) { - if(modpacks[i].name == logo) { - emit dataChanged(createIndex(i, 0), createIndex(i, 0), {Qt::DecorationRole}); - } - } -} - -void ListModel::logoFailed(QString logo) -{ - m_logoMap[logo].failed = true; - m_logoMap[logo].downloadJob.reset(); -} - -void ListModel::requestLogo(QString logo, QString url) -{ - if(m_logoMap.contains(logo)) { - return; - } - - MetaEntryPtr entry = APPLICATION->metacache()->resolveEntry("ModpacksCHPacks", QString("logos/%1").arg(logo.section(".", 0, 0))); - - bool stale = entry->isStale(); - - auto job = makeShared(QString("ModpacksCH Icon Download %1").arg(logo), APPLICATION->network()); - job->addNetAction(Net::Download::makeCached(QUrl(url), entry)); - - auto fullPath = entry->getFullPath(); - QObject::connect(job.get(), &NetJob::finished, this, [this, logo, fullPath, stale] - { - logoLoaded(logo, stale); - }); - - QObject::connect(job.get(), &NetJob::failed, this, [this, logo] - { - logoFailed(logo); - }); - - auto &newLogoEntry = m_logoMap[logo]; - newLogoEntry.downloadJob = job; - newLogoEntry.fullpath = fullPath; - job->start(); -} - -} diff --git a/launcher/ui/pages/modplatform/ftb/FtbListModel.h b/launcher/ui/pages/modplatform/ftb/FtbListModel.h deleted file mode 100644 index d7a120f0..00000000 --- a/launcher/ui/pages/modplatform/ftb/FtbListModel.h +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright 2020-2021 Jamie Mansfield - * - * 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 "modplatform/modpacksch/FTBPackManifest.h" -#include "net/NetJob.h" -#include - -namespace Ftb { - -struct Logo { - QString fullpath; - NetJob::Ptr downloadJob; - QIcon result; - bool failed = false; -}; - -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; - - void request(); - void abortRequest(); - - void getLogo(const QString &logo, const QString &logoUrl, LogoCallback callback); - - [[nodiscard]] bool isMakingRequest() const { return jobPtr.get(); } - [[nodiscard]] bool wasAborted() const { return m_aborted; } - -private slots: - void requestFinished(); - void requestFailed(QString reason); - - void requestPack(); - void packRequestFinished(); - void packRequestFailed(QString reason); - - void logoFailed(QString logo); - void logoLoaded(QString logo, bool stale); - -private: - void requestLogo(QString file, QString url); - -private: - bool m_aborted = false; - - QList modpacks; - LogoMap m_logoMap; - - NetJob::Ptr jobPtr; - int currentPack; - QList remainingPacks; - QByteArray response; -}; - -} diff --git a/launcher/ui/pages/modplatform/ftb/FtbPage.cpp b/launcher/ui/pages/modplatform/ftb/FtbPage.cpp deleted file mode 100644 index 7d59a6ae..00000000 --- a/launcher/ui/pages/modplatform/ftb/FtbPage.cpp +++ /dev/null @@ -1,199 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-only -/* - * PolyMC - Minecraft Launcher - * Copyright (c) 2022 Jamie Mansfield - * Copyright (C) 2022 Sefa Eyeoglu - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, version 3. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * This file incorporates work covered by the following copyright and - * permission notice: - * - * Copyright 2020-2021 Jamie Mansfield - * Copyright 2021 Philip T - * - * 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 "FtbPage.h" -#include "ui_FtbPage.h" - -#include - -#include "ui/dialogs/NewInstanceDialog.h" -#include "modplatform/modpacksch/FTBPackInstallTask.h" - -#include "Markdown.h" - -FtbPage::FtbPage(NewInstanceDialog* dialog, QWidget *parent) - : QWidget(parent), ui(new Ui::FtbPage), dialog(dialog) -{ - ui->setupUi(this); - - filterModel = new Ftb::FilterModel(this); - listModel = new Ftb::ListModel(this); - filterModel->setSourceModel(listModel); - ui->packView->setModel(filterModel); - ui->packView->setSortingEnabled(true); - ui->packView->header()->hide(); - ui->packView->setIndentation(0); - - ui->searchEdit->installEventFilter(this); - - ui->versionSelectionBox->view()->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); - ui->versionSelectionBox->view()->parentWidget()->setMaximumHeight(300); - - for(int i = 0; i < filterModel->getAvailableSortings().size(); i++) - { - ui->sortByBox->addItem(filterModel->getAvailableSortings().keys().at(i)); - } - ui->sortByBox->setCurrentText(filterModel->translateCurrentSorting()); - - connect(ui->searchEdit, &QLineEdit::textChanged, this, &FtbPage::triggerSearch); - connect(ui->sortByBox, &QComboBox::currentTextChanged, this, &FtbPage::onSortingSelectionChanged); - connect(ui->packView->selectionModel(), &QItemSelectionModel::currentChanged, this, &FtbPage::onSelectionChanged); - connect(ui->versionSelectionBox, &QComboBox::currentTextChanged, this, &FtbPage::onVersionSelectionChanged); - - ui->packDescription->setMetaEntry("FTBPacks"); -} - -FtbPage::~FtbPage() -{ - delete ui; -} - -bool FtbPage::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 FtbPage::shouldDisplay() const -{ - return true; -} - -void FtbPage::retranslate() -{ - ui->retranslateUi(this); -} - -void FtbPage::openedImpl() -{ - if(!initialised || listModel->wasAborted()) - { - listModel->request(); - initialised = true; - } - - suggestCurrent(); -} - -void FtbPage::closedImpl() -{ - if (listModel->isMakingRequest()) - listModel->abortRequest(); -} - -void FtbPage::suggestCurrent() -{ - if(!isOpened) - { - return; - } - - if (selectedVersion.isEmpty()) - { - dialog->setSuggestedPack(); - return; - } - - dialog->setSuggestedPack(selected.name, selectedVersion, new ModpacksCH::PackInstallTask(selected, selectedVersion, this)); - for(auto art : selected.art) { - if(art.type == "square") { - QString editedLogoName; - editedLogoName = selected.name; - - listModel->getLogo(selected.name, art.url, [this, editedLogoName](QString logo) - { - dialog->setSuggestedIconFromFile(logo + ".small", editedLogoName); - }); - } - } -} - -void FtbPage::triggerSearch() -{ - filterModel->setSearchTerm(ui->searchEdit->text()); -} - -void FtbPage::onSortingSelectionChanged(QString data) -{ - auto toSet = filterModel->getAvailableSortings().value(data); - filterModel->setSorting(toSet); -} - -void FtbPage::onSelectionChanged(QModelIndex first, QModelIndex second) -{ - ui->versionSelectionBox->clear(); - - if(!first.isValid()) - { - if(isOpened) - { - dialog->setSuggestedPack(); - } - return; - } - - selected = filterModel->data(first, Qt::UserRole).value(); - - QString output = markdownToHTML(selected.description.toUtf8()); - ui->packDescription->setHtml(output); - - // reverse foreach, so that the newest versions are first - for (auto i = selected.versions.size(); i--;) { - ui->versionSelectionBox->addItem(selected.versions.at(i).name); - } - - suggestCurrent(); -} - -void FtbPage::onVersionSelectionChanged(QString data) -{ - if(data.isNull() || data.isEmpty()) - { - selectedVersion = ""; - return; - } - - selectedVersion = data; - suggestCurrent(); -} diff --git a/launcher/ui/pages/modplatform/ftb/FtbPage.h b/launcher/ui/pages/modplatform/ftb/FtbPage.h deleted file mode 100644 index 631ae7f5..00000000 --- a/launcher/ui/pages/modplatform/ftb/FtbPage.h +++ /dev/null @@ -1,105 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-only -/* - * PolyMC - Minecraft Launcher - * Copyright (c) 2022 Jamie Mansfield - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, version 3. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * This file incorporates work covered by the following copyright and - * permission notice: - * - * Copyright 2013-2021 MultiMC Contributors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include "FtbFilterModel.h" -#include "FtbListModel.h" - -#include - -#include "Application.h" -#include "ui/pages/BasePage.h" -#include "tasks/Task.h" - -namespace Ui -{ - class FtbPage; -} - -class NewInstanceDialog; - -class FtbPage : public QWidget, public BasePage -{ -Q_OBJECT - -public: - explicit FtbPage(NewInstanceDialog* dialog, QWidget *parent = 0); - virtual ~FtbPage(); - virtual QString displayName() const override - { - return "FTB"; - } - virtual QIcon icon() const override - { - return APPLICATION->getThemedIcon("ftb_logo"); - } - virtual QString id() const override - { - return "ftb"; - } - virtual QString helpPage() const override - { - return "FTB-platform"; - } - virtual bool shouldDisplay() const override; - void retranslate() override; - - void openedImpl() override; - void closedImpl() override; - - bool eventFilter(QObject * watched, QEvent * event) override; - -private: - void suggestCurrent(); - -private slots: - void triggerSearch(); - - void onSortingSelectionChanged(QString data); - void onSelectionChanged(QModelIndex first, QModelIndex second); - void onVersionSelectionChanged(QString data); - -private: - Ui::FtbPage *ui = nullptr; - NewInstanceDialog* dialog = nullptr; - Ftb::ListModel* listModel = nullptr; - Ftb::FilterModel* filterModel = nullptr; - - ModpacksCH::Modpack selected; - QString selectedVersion; - - bool initialised { false }; -}; diff --git a/launcher/ui/pages/modplatform/ftb/FtbPage.ui b/launcher/ui/pages/modplatform/ftb/FtbPage.ui deleted file mode 100644 index 8de0f4e6..00000000 --- a/launcher/ui/pages/modplatform/ftb/FtbPage.ui +++ /dev/null @@ -1,86 +0,0 @@ - - - FtbPage - - - - 0 - 0 - 875 - 745 - - - - - - - - - - - - Version selected: - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - - - - - - Search and filter... - - - true - - - - - - - - - true - - - - 48 - 48 - - - - - - - - true - - - true - - - - - - - - - - ProjectDescriptionPage - QTextBrowser -
ui/widgets/ProjectDescriptionPage.h
-
-
- - searchEdit - versionSelectionBox - - - -
-- cgit From d80dee2a54a172fa19c0bc21486ee43ef2e3514d Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Sat, 29 Apr 2023 19:38:51 -0700 Subject: refactor: pass instance ptr to resource models. use it to find instance root. Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- launcher/minecraft/MinecraftInstance.cpp | 14 +++++++------- launcher/minecraft/WorldList.cpp | 6 +++--- launcher/minecraft/WorldList.h | 4 +++- launcher/minecraft/mod/ModFolderModel.cpp | 3 ++- launcher/minecraft/mod/ModFolderModel.h | 2 +- launcher/minecraft/mod/ResourceFolderModel.cpp | 7 ++++--- launcher/minecraft/mod/ResourceFolderModel.h | 5 ++++- launcher/minecraft/mod/ResourcePackFolderModel.cpp | 3 ++- launcher/minecraft/mod/ResourcePackFolderModel.h | 2 +- launcher/minecraft/mod/ShaderPackFolderModel.h | 4 +++- launcher/minecraft/mod/TexturePackFolderModel.cpp | 4 +++- launcher/minecraft/mod/TexturePackFolderModel.h | 2 +- launcher/ui/MainWindow.cpp | 4 ++-- launcher/ui/dialogs/CopyInstanceDialog.cpp | 2 +- tests/ResourceFolderModel_test.cpp | 18 ++++++++++++------ 15 files changed, 49 insertions(+), 31 deletions(-) (limited to 'launcher/ui/dialogs') diff --git a/launcher/minecraft/MinecraftInstance.cpp b/launcher/minecraft/MinecraftInstance.cpp index af4da5d0..6b7ca84f 100644 --- a/launcher/minecraft/MinecraftInstance.cpp +++ b/launcher/minecraft/MinecraftInstance.cpp @@ -1111,7 +1111,7 @@ std::shared_ptr MinecraftInstance::loaderModList() const if (!m_loader_mod_list) { bool is_indexed = !APPLICATION->settings()->get("ModMetadataDisabled").toBool(); - m_loader_mod_list.reset(new ModFolderModel(modsRoot(), is_indexed)); + m_loader_mod_list.reset(new ModFolderModel(modsRoot(), shared_from_this(), is_indexed)); m_loader_mod_list->disableInteraction(isRunning()); connect(this, &BaseInstance::runningStatusChanged, m_loader_mod_list.get(), &ModFolderModel::disableInteraction); } @@ -1123,7 +1123,7 @@ std::shared_ptr MinecraftInstance::coreModList() const if (!m_core_mod_list) { bool is_indexed = !APPLICATION->settings()->get("ModMetadataDisabled").toBool(); - m_core_mod_list.reset(new ModFolderModel(coreModsDir(), is_indexed)); + m_core_mod_list.reset(new ModFolderModel(coreModsDir(), shared_from_this(), is_indexed)); m_core_mod_list->disableInteraction(isRunning()); connect(this, &BaseInstance::runningStatusChanged, m_core_mod_list.get(), &ModFolderModel::disableInteraction); } @@ -1135,7 +1135,7 @@ std::shared_ptr MinecraftInstance::nilModList() const if (!m_nil_mod_list) { bool is_indexed = !APPLICATION->settings()->get("ModMetadataDisabled").toBool(); - m_nil_mod_list.reset(new ModFolderModel(nilModsDir(), is_indexed, false)); + m_nil_mod_list.reset(new ModFolderModel(nilModsDir(), shared_from_this(), is_indexed, false)); m_nil_mod_list->disableInteraction(isRunning()); connect(this, &BaseInstance::runningStatusChanged, m_nil_mod_list.get(), &ModFolderModel::disableInteraction); } @@ -1146,7 +1146,7 @@ std::shared_ptr MinecraftInstance::resourcePackList() c { if (!m_resource_pack_list) { - m_resource_pack_list.reset(new ResourcePackFolderModel(resourcePacksDir())); + m_resource_pack_list.reset(new ResourcePackFolderModel(resourcePacksDir(), shared_from_this())); } return m_resource_pack_list; } @@ -1155,7 +1155,7 @@ std::shared_ptr MinecraftInstance::texturePackList() con { if (!m_texture_pack_list) { - m_texture_pack_list.reset(new TexturePackFolderModel(texturePacksDir())); + m_texture_pack_list.reset(new TexturePackFolderModel(texturePacksDir(), shared_from_this())); } return m_texture_pack_list; } @@ -1164,7 +1164,7 @@ std::shared_ptr MinecraftInstance::shaderPackList() const { if (!m_shader_pack_list) { - m_shader_pack_list.reset(new ShaderPackFolderModel(shaderPacksDir())); + m_shader_pack_list.reset(new ShaderPackFolderModel(shaderPacksDir(), shared_from_this())); } return m_shader_pack_list; } @@ -1173,7 +1173,7 @@ std::shared_ptr MinecraftInstance::worldList() const { if (!m_world_list) { - m_world_list.reset(new WorldList(worldDir())); + m_world_list.reset(new WorldList(worldDir(), shared_from_this())); } return m_world_list; } diff --git a/launcher/minecraft/WorldList.cpp b/launcher/minecraft/WorldList.cpp index 3681bcda..62e55cd4 100644 --- a/launcher/minecraft/WorldList.cpp +++ b/launcher/minecraft/WorldList.cpp @@ -45,8 +45,8 @@ #include #include -WorldList::WorldList(const QString &dir) - : QAbstractListModel(), m_dir(dir) +WorldList::WorldList(const QString &dir, std::shared_ptr instance) + : QAbstractListModel(), m_instance(instance), m_dir(dir) { FS::ensureFolderPathExists(m_dir.absolutePath()); m_dir.setFilter(QDir::Readable | QDir::NoDotAndDotDot | QDir::Files | QDir::Dirs); @@ -129,7 +129,7 @@ bool WorldList::isValid() } QString WorldList::instDirPath() const { - return QFileInfo(m_dir.filePath("../..")).absoluteFilePath(); + return QFileInfo(m_instance->instanceRoot()).absoluteFilePath(); } bool WorldList::deleteWorld(int index) diff --git a/launcher/minecraft/WorldList.h b/launcher/minecraft/WorldList.h index bd32dd4e..10fb4e3c 100644 --- a/launcher/minecraft/WorldList.h +++ b/launcher/minecraft/WorldList.h @@ -21,6 +21,7 @@ #include #include #include "minecraft/World.h" +#include "BaseInstance.h" class QFileSystemWatcher; @@ -49,7 +50,7 @@ public: IconFileRole }; - WorldList(const QString &dir); + WorldList(const QString &dir, std::shared_ptr instance); virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; @@ -127,6 +128,7 @@ signals: void changed(); protected: + std::shared_ptr m_instance; QFileSystemWatcher *m_watcher; bool is_watching; QDir m_dir; diff --git a/launcher/minecraft/mod/ModFolderModel.cpp b/launcher/minecraft/mod/ModFolderModel.cpp index 91d16175..6ae25d33 100644 --- a/launcher/minecraft/mod/ModFolderModel.cpp +++ b/launcher/minecraft/mod/ModFolderModel.cpp @@ -54,7 +54,8 @@ #include "minecraft/mod/tasks/ModFolderLoadTask.h" #include "modplatform/ModIndex.h" -ModFolderModel::ModFolderModel(const QString &dir, bool is_indexed, bool create_dir) : ResourceFolderModel(QDir(dir), nullptr, create_dir), m_is_indexed(is_indexed) +ModFolderModel::ModFolderModel(const QString& dir, std::shared_ptr instance, bool is_indexed, bool create_dir) + : ResourceFolderModel(QDir(dir), instance, nullptr, create_dir), m_is_indexed(is_indexed) { m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::VERSION, SortType::DATE, SortType::PROVIDER }; } diff --git a/launcher/minecraft/mod/ModFolderModel.h b/launcher/minecraft/mod/ModFolderModel.h index 84e70db9..46f5087f 100644 --- a/launcher/minecraft/mod/ModFolderModel.h +++ b/launcher/minecraft/mod/ModFolderModel.h @@ -75,7 +75,7 @@ public: Enable, Toggle }; - ModFolderModel(const QString &dir, bool is_indexed = false, bool create_dir = true); + ModFolderModel(const QString &dir, std::shared_ptr instance, bool is_indexed = false, bool create_dir = true); QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; diff --git a/launcher/minecraft/mod/ResourceFolderModel.cpp b/launcher/minecraft/mod/ResourceFolderModel.cpp index 29a0c736..e1973468 100644 --- a/launcher/minecraft/mod/ResourceFolderModel.cpp +++ b/launcher/minecraft/mod/ResourceFolderModel.cpp @@ -16,7 +16,8 @@ #include "tasks/Task.h" -ResourceFolderModel::ResourceFolderModel(QDir dir, QObject* parent, bool create_dir) : QAbstractListModel(parent), m_dir(dir), m_watcher(this) +ResourceFolderModel::ResourceFolderModel(QDir dir, std::shared_ptr instance, QObject* parent, bool create_dir) + : QAbstractListModel(parent), m_dir(dir), m_instance(instance), m_watcher(this) { if (create_dir) { FS::ensureFolderPathExists(m_dir.absolutePath()); @@ -26,7 +27,7 @@ ResourceFolderModel::ResourceFolderModel(QDir dir, QObject* parent, bool create_ m_dir.setSorting(QDir::Name | QDir::IgnoreCase | QDir::LocaleAware); connect(&m_watcher, &QFileSystemWatcher::directoryChanged, this, &ResourceFolderModel::directoryChanged); - connect(&m_helper_thread_task, &ConcurrentTask::finished, this, [this]{ m_helper_thread_task.clear(); }); + connect(&m_helper_thread_task, &ConcurrentTask::finished, this, [this] { m_helper_thread_task.clear(); }); } ResourceFolderModel::~ResourceFolderModel() @@ -556,5 +557,5 @@ void ResourceFolderModel::enableInteraction(bool enabled) } QString ResourceFolderModel::instDirPath() const { - return QFileInfo(m_dir.filePath("../..")).absoluteFilePath(); + return QFileInfo(m_instance->instanceRoot()).absoluteFilePath(); } diff --git a/launcher/minecraft/mod/ResourceFolderModel.h b/launcher/minecraft/mod/ResourceFolderModel.h index f840b2de..fdf5f331 100644 --- a/launcher/minecraft/mod/ResourceFolderModel.h +++ b/launcher/minecraft/mod/ResourceFolderModel.h @@ -9,6 +9,8 @@ #include "Resource.h" +#include "BaseInstance.h" + #include "tasks/Task.h" #include "tasks/ConcurrentTask.h" @@ -24,7 +26,7 @@ class QSortFilterProxyModel; class ResourceFolderModel : public QAbstractListModel { Q_OBJECT public: - ResourceFolderModel(QDir, QObject* parent = nullptr, bool create_dir = true); + ResourceFolderModel(QDir, std::shared_ptr, QObject* parent = nullptr, bool create_dir = true); ~ResourceFolderModel() override; /** Starts watching the paths for changes. @@ -189,6 +191,7 @@ class ResourceFolderModel : public QAbstractListModel { bool m_can_interact = true; QDir m_dir; + std::shared_ptr m_instance; QFileSystemWatcher m_watcher; bool m_is_watching = false; diff --git a/launcher/minecraft/mod/ResourcePackFolderModel.cpp b/launcher/minecraft/mod/ResourcePackFolderModel.cpp index 0480d8ba..6eba4e2e 100644 --- a/launcher/minecraft/mod/ResourcePackFolderModel.cpp +++ b/launcher/minecraft/mod/ResourcePackFolderModel.cpp @@ -45,7 +45,8 @@ #include "minecraft/mod/tasks/BasicFolderLoadTask.h" #include "minecraft/mod/tasks/LocalResourcePackParseTask.h" -ResourcePackFolderModel::ResourcePackFolderModel(const QString& dir) : ResourceFolderModel(QDir(dir)) +ResourcePackFolderModel::ResourcePackFolderModel(const QString& dir, std::shared_ptr instance) + : ResourceFolderModel(QDir(dir), instance) { m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::PACK_FORMAT, SortType::DATE }; } diff --git a/launcher/minecraft/mod/ResourcePackFolderModel.h b/launcher/minecraft/mod/ResourcePackFolderModel.h index cb620ce2..66d5a074 100644 --- a/launcher/minecraft/mod/ResourcePackFolderModel.h +++ b/launcher/minecraft/mod/ResourcePackFolderModel.h @@ -17,7 +17,7 @@ public: NUM_COLUMNS }; - explicit ResourcePackFolderModel(const QString &dir); + explicit ResourcePackFolderModel(const QString &dir, std::shared_ptr instance); [[nodiscard]] QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; diff --git a/launcher/minecraft/mod/ShaderPackFolderModel.h b/launcher/minecraft/mod/ShaderPackFolderModel.h index a3aa958f..6f3f2811 100644 --- a/launcher/minecraft/mod/ShaderPackFolderModel.h +++ b/launcher/minecraft/mod/ShaderPackFolderModel.h @@ -6,5 +6,7 @@ class ShaderPackFolderModel : public ResourceFolderModel { Q_OBJECT public: - explicit ShaderPackFolderModel(const QString& dir) : ResourceFolderModel(QDir(dir)) {} + explicit ShaderPackFolderModel(const QString& dir, std::shared_ptr instance) + : ResourceFolderModel(QDir(dir), instance) + {} }; diff --git a/launcher/minecraft/mod/TexturePackFolderModel.cpp b/launcher/minecraft/mod/TexturePackFolderModel.cpp index 5a32cfaf..1e218537 100644 --- a/launcher/minecraft/mod/TexturePackFolderModel.cpp +++ b/launcher/minecraft/mod/TexturePackFolderModel.cpp @@ -39,7 +39,9 @@ #include "minecraft/mod/tasks/BasicFolderLoadTask.h" #include "minecraft/mod/tasks/LocalTexturePackParseTask.h" -TexturePackFolderModel::TexturePackFolderModel(const QString &dir) : ResourceFolderModel(QDir(dir)) {} +TexturePackFolderModel::TexturePackFolderModel(const QString& dir, std::shared_ptr instance) + : ResourceFolderModel(QDir(dir), instance) +{} Task* TexturePackFolderModel::createUpdateTask() { diff --git a/launcher/minecraft/mod/TexturePackFolderModel.h b/launcher/minecraft/mod/TexturePackFolderModel.h index 261f83b4..246997bd 100644 --- a/launcher/minecraft/mod/TexturePackFolderModel.h +++ b/launcher/minecraft/mod/TexturePackFolderModel.h @@ -43,7 +43,7 @@ class TexturePackFolderModel : public ResourceFolderModel Q_OBJECT public: - explicit TexturePackFolderModel(const QString &dir); + explicit TexturePackFolderModel(const QString &dir, std::shared_ptr instance); [[nodiscard]] Task* createUpdateTask() override; [[nodiscard]] Task* createParseTask(Resource&) override; }; diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp index e20a7613..72b7db64 100644 --- a/launcher/ui/MainWindow.cpp +++ b/launcher/ui/MainWindow.cpp @@ -1341,10 +1341,10 @@ void MainWindow::on_actionDeleteInstance_triggered() if (!linkedInstances.empty()) { response = CustomMessageBox::selectable( this, tr("There are linked instances"), - tr("The folowing instance(s) might reference files in this instance:\n\n" + tr("The following instance(s) might reference files in this instance:\n\n" "%1\n\n" "Deleting it could break the other instance(s), \n\n" - "Do you wish to proceed?").arg(linkedInstances.join("\n")), + "Do you wish to proceed?", nullptr, linkedInstances.count()).arg(linkedInstances.join("\n")), QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No, QMessageBox::No )->exec(); if (response != QMessageBox::Yes) diff --git a/launcher/ui/dialogs/CopyInstanceDialog.cpp b/launcher/ui/dialogs/CopyInstanceDialog.cpp index 347bd39f..d75bb5fe 100644 --- a/launcher/ui/dialogs/CopyInstanceDialog.cpp +++ b/launcher/ui/dialogs/CopyInstanceDialog.cpp @@ -108,7 +108,7 @@ CopyInstanceDialog::CopyInstanceDialog(InstancePtr original, QWidget* parent) #if defined(Q_OS_WIN) ui->symbolicLinksCheckbox->setIcon(style()->standardIcon(QStyle::SP_VistaShield)); ui->symbolicLinksCheckbox->setToolTip(tr("Use symbolic links instead of copying files.") + - tr("\nOn windows symbolic links may require admin permission to create.")); + "\n" + tr("On Windows, symbolic links may require admin permission to create.")); #endif updateLinkOptions(); diff --git a/tests/ResourceFolderModel_test.cpp b/tests/ResourceFolderModel_test.cpp index e38b8e93..054d81c4 100644 --- a/tests/ResourceFolderModel_test.cpp +++ b/tests/ResourceFolderModel_test.cpp @@ -36,6 +36,7 @@ #include #include #include +#include "BaseInstance.h" #include @@ -89,7 +90,9 @@ slots: QEventLoop loop; - ModFolderModel m(tempDir.path(), true); + InstancePtr instance; + + ModFolderModel m(tempDir.path(), instance, true); connect(&m, &ModFolderModel::updateFinished, &loop, &QEventLoop::quit); @@ -113,7 +116,8 @@ slots: QString folder = source + '/'; QTemporaryDir tempDir; QEventLoop loop; - ModFolderModel m(tempDir.path(), true); + InstancePtr instance; + ModFolderModel m(tempDir.path(), instance, true); connect(&m, &ModFolderModel::updateFinished, &loop, &QEventLoop::quit); @@ -136,8 +140,8 @@ slots: void test_addFromWatch() { QString source = QFINDTESTDATA("testdata/ResourceFolderModel"); - - ModFolderModel model(source); + InstancePtr instance; + ModFolderModel model(source, instance); QCOMPARE(model.size(), 0); @@ -157,8 +161,9 @@ slots: QString file_mod = QFINDTESTDATA("testdata/ResourceFolderModel/supercoolmod.jar"); QTemporaryDir tmp; + InstancePtr instance; - ResourceFolderModel model(QDir(tmp.path())); + ResourceFolderModel model(QDir(tmp.path()), instance); QCOMPARE(model.size(), 0); @@ -209,7 +214,8 @@ slots: QString file_mod = QFINDTESTDATA("testdata/ResourceFolderModel/supercoolmod.jar"); QTemporaryDir tmp; - ResourceFolderModel model(tmp.path()); + InstancePtr instance; + ResourceFolderModel model(tmp.path(), instance); QCOMPARE(model.size(), 0); -- cgit From f997529cd4fb077b06d05da9c6ff0c23b85b4ebb Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Thu, 30 Mar 2023 11:22:55 -0700 Subject: feat: better task tracking Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- launcher/CMakeLists.txt | 3 + launcher/net/Download.cpp | 58 +++++++++------ launcher/net/Download.h | 10 +++ launcher/net/NetAction.h | 33 +++++++-- launcher/tasks/ConcurrentTask.cpp | 99 +++++++++++++++++++------- launcher/tasks/ConcurrentTask.h | 20 +++--- launcher/tasks/Task.cpp | 26 ++++--- launcher/tasks/Task.h | 35 +++++++-- launcher/ui/dialogs/ProgressDialog.cpp | 109 ++++++++++++++++++++++++----- launcher/ui/dialogs/ProgressDialog.h | 54 +++++++++++--- launcher/ui/dialogs/ProgressDialog.ui | 61 ++++++++-------- launcher/ui/widgets/SubTaskProgressBar.cpp | 58 +++++++++++++++ launcher/ui/widgets/SubTaskProgressBar.h | 50 +++++++++++++ launcher/ui/widgets/SubTaskProgressBar.ui | 70 ++++++++++++++++++ tests/Task_test.cpp | 4 +- 15 files changed, 552 insertions(+), 138 deletions(-) create mode 100644 launcher/ui/widgets/SubTaskProgressBar.cpp create mode 100644 launcher/ui/widgets/SubTaskProgressBar.h create mode 100644 launcher/ui/widgets/SubTaskProgressBar.ui (limited to 'launcher/ui/dialogs') diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index ee36175f..24330adf 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -909,6 +909,8 @@ SET(LAUNCHER_SOURCES ui/widgets/VariableSizedImageObject.cpp ui/widgets/ProjectItem.h ui/widgets/ProjectItem.cpp + ui/widgets/SubTaskProgressBar.h + ui/widgets/SubTaskProgressBar.cpp ui/widgets/VersionListView.cpp ui/widgets/VersionListView.h ui/widgets/VersionSelectWidget.cpp @@ -969,6 +971,7 @@ qt_wrap_ui(LAUNCHER_UI ui/widgets/CustomCommands.ui ui/widgets/InfoFrame.ui ui/widgets/ModFilterWidget.ui + ui/widgets/SubTaskProgressBar.ui ui/widgets/ThemeCustomizationWidget.ui ui/dialogs/CopyInstanceDialog.ui ui/dialogs/ProfileSetupDialog.ui diff --git a/launcher/net/Download.cpp b/launcher/net/Download.cpp index e8a1d0b0..26488a43 100644 --- a/launcher/net/Download.cpp +++ b/launcher/net/Download.cpp @@ -48,12 +48,15 @@ #include "BuildConfig.h" #include "Application.h" +Q_LOGGING_CATEGORY(DownloadLogC, "Task.Net.Download") + namespace Net { auto Download::makeCached(QUrl url, MetaEntryPtr entry, Options options) -> Download::Ptr { auto dl = makeShared(); dl->m_url = url; + dl->setObjectName(QString("CACHE:") + url.toString()); dl->m_options = options; auto md5Node = new ChecksumValidator(QCryptographicHash::Md5); auto cachedNode = new MetaCacheSink(entry, md5Node, options.testFlag(Option::MakeEternal)); @@ -65,6 +68,7 @@ auto Download::makeByteArray(QUrl url, QByteArray* output, Options options) -> D { auto dl = makeShared(); dl->m_url = url; + dl->setObjectName(QString("BYTES:") + url.toString()); dl->m_options = options; dl->m_sink.reset(new ByteArraySink(output)); return dl; @@ -74,6 +78,7 @@ auto Download::makeFile(QUrl url, QString path, Options options) -> Download::Pt { auto dl = makeShared(); dl->m_url = url; + dl->setObjectName(QString("FILE:") + url.toString()); dl->m_options = options; dl->m_sink.reset(new FileSink(path)); return dl; @@ -89,7 +94,7 @@ void Download::executeTask() setStatus(tr("Downloading %1").arg(m_url.toString())); if (getState() == Task::State::AbortedByUser) { - qWarning() << "Attempt to start an aborted Download:" << m_url.toString(); + qCWarning(DownloadLogC) << getUid().toString() << "Attempt to start an aborted Download:" << m_url.toString(); emitAborted(); return; } @@ -99,10 +104,10 @@ void Download::executeTask() switch (m_state) { case State::Succeeded: emit succeeded(); - qDebug() << "Download cache hit " << m_url.toString(); + qCDebug(DownloadLogC) << getUid().toString() << "Download cache hit " << m_url.toString(); return; case State::Running: - qDebug() << "Downloading " << m_url.toString(); + qCDebug(DownloadLogC) << getUid().toString() << "Downloading " << m_url.toString(); break; case State::Inactive: case State::Failed: @@ -123,9 +128,12 @@ void Download::executeTask() if (!token.isNull()) request.setRawHeader("Authorization", token.toUtf8()); } + + m_last_progress_time = m_clock.now(); + m_last_progress_bytes = 0; QNetworkReply* rep = m_network->get(request); - + m_reply.reset(rep); connect(rep, &QNetworkReply::downloadProgress, this, &Download::downloadProgress); connect(rep, &QNetworkReply::finished, this, &Download::downloadFinished); @@ -140,13 +148,21 @@ void Download::executeTask() void Download::downloadProgress(qint64 bytesReceived, qint64 bytesTotal) { + auto now = m_clock.now(); + auto elapsed = now - m_last_progress_time; + auto elapsed_ms = std::chrono::duration_cast(elapsed).count(); + auto bytes_recived_since = bytesReceived - m_last_progress_bytes; + + auto speed = humanReadableFileSize(bytes_recived_since / elapsed_ms * 1000) + "/s"; + m_details = speed; + setProgress(bytesReceived, bytesTotal); } void Download::downloadError(QNetworkReply::NetworkError error) { if (error == QNetworkReply::OperationCanceledError) { - qCritical() << "Aborted " << m_url.toString(); + qCCritical(DownloadLogC) << getUid().toString() << "Aborted " << m_url.toString(); m_state = State::AbortedByUser; } else { if (m_options & Option::AcceptLocalFiles) { @@ -156,7 +172,7 @@ void Download::downloadError(QNetworkReply::NetworkError error) } } // error happened during download. - qCritical() << "Failed " << m_url.toString() << " with reason " << error; + qCCritical(DownloadLogC) << getUid().toString() << "Failed " << m_url.toString() << " with reason " << error; m_state = State::Failed; } } @@ -165,9 +181,9 @@ void Download::sslErrors(const QList& errors) { int i = 1; for (auto error : errors) { - qCritical() << "Download" << m_url.toString() << "SSL Error #" << i << " : " << error.errorString(); + qCCritical(DownloadLogC) << getUid().toString() << "Download" << m_url.toString() << "SSL Error #" << i << " : " << error.errorString(); auto cert = error.certificate(); - qCritical() << "Certificate in question:\n" << cert.toText(); + qCCritical(DownloadLogC) << getUid().toString() << "Certificate in question:\n" << cert.toText(); i++; } } @@ -210,17 +226,17 @@ auto Download::handleRedirect() -> bool */ redirect = QUrl(redirectStr, QUrl::TolerantMode); if (!redirect.isValid()) { - qWarning() << "Failed to parse redirect URL:" << redirectStr; + qCWarning(DownloadLogC) << getUid().toString() << "Failed to parse redirect URL:" << redirectStr; downloadError(QNetworkReply::ProtocolFailure); return false; } - qDebug() << "Fixed location header:" << redirect; + qCDebug(DownloadLogC) << getUid().toString() << "Fixed location header:" << redirect; } else { - qDebug() << "Location header:" << redirect; + qCDebug(DownloadLogC) << getUid().toString() << "Location header:" << redirect; } m_url = QUrl(redirect.toString()); - qDebug() << "Following redirect to " << m_url.toString(); + qCDebug(DownloadLogC) << getUid().toString() << "Following redirect to " << m_url.toString(); startAction(m_network); return true; @@ -230,26 +246,26 @@ void Download::downloadFinished() { // handle HTTP redirection first if (handleRedirect()) { - qDebug() << "Download redirected:" << m_url.toString(); + qCDebug(DownloadLogC) << getUid().toString() << "Download redirected:" << m_url.toString(); return; } // if the download failed before this point ... if (m_state == State::Succeeded) // pretend to succeed so we continue processing :) { - qDebug() << "Download failed but we are allowed to proceed:" << m_url.toString(); + qCDebug(DownloadLogC) << getUid().toString() << "Download failed but we are allowed to proceed:" << m_url.toString(); m_sink->abort(); m_reply.reset(); emit succeeded(); return; } else if (m_state == State::Failed) { - qDebug() << "Download failed in previous step:" << m_url.toString(); + qCDebug(DownloadLogC) << getUid().toString() << "Download failed in previous step:" << m_url.toString(); m_sink->abort(); m_reply.reset(); emit failed(""); return; } else if (m_state == State::AbortedByUser) { - qDebug() << "Download aborted in previous step:" << m_url.toString(); + qCDebug(DownloadLogC) << getUid().toString() << "Download aborted in previous step:" << m_url.toString(); m_sink->abort(); m_reply.reset(); emit aborted(); @@ -259,14 +275,14 @@ void Download::downloadFinished() // make sure we got all the remaining data, if any auto data = m_reply->readAll(); if (data.size()) { - qDebug() << "Writing extra" << data.size() << "bytes"; + qCDebug(DownloadLogC) << getUid().toString() << "Writing extra" << data.size() << "bytes"; m_state = m_sink->write(data); } // otherwise, finalize the whole graph m_state = m_sink->finalize(*m_reply.get()); if (m_state != State::Succeeded) { - qDebug() << "Download failed to finalize:" << m_url.toString(); + qCDebug(DownloadLogC) << getUid().toString() << "Download failed to finalize:" << m_url.toString(); m_sink->abort(); m_reply.reset(); emit failed(""); @@ -274,7 +290,7 @@ void Download::downloadFinished() } m_reply.reset(); - qDebug() << "Download succeeded:" << m_url.toString(); + qCDebug(DownloadLogC) << getUid().toString() << "Download succeeded:" << m_url.toString(); emit succeeded(); } @@ -284,11 +300,11 @@ void Download::downloadReadyRead() auto data = m_reply->readAll(); m_state = m_sink->write(data); if (m_state == State::Failed) { - qCritical() << "Failed to process response chunk"; + qCCritical(DownloadLogC) << getUid().toString() << "Failed to process response chunk"; } // qDebug() << "Download" << m_url.toString() << "gained" << data.size() << "bytes"; } else { - qCritical() << "Cannot write download data! illegal status " << m_status; + qCCritical(DownloadLogC) << getUid().toString() << "Cannot write download data! illegal status " << m_status; } } diff --git a/launcher/net/Download.h b/launcher/net/Download.h index 7e1df322..cbee0d03 100644 --- a/launcher/net/Download.h +++ b/launcher/net/Download.h @@ -22,6 +22,7 @@ * 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 * @@ -36,6 +37,8 @@ #pragma once +#include + #include "HttpMetaCache.h" #include "NetAction.h" #include "Sink.h" @@ -63,6 +66,7 @@ class Download : public NetAction { void addValidator(Validator* v); auto abort() -> bool override; auto canAbort() const -> bool override { return true; }; + auto getDetails() const -> QString override {return m_details; }; private: auto handleRedirect() -> bool; @@ -80,6 +84,12 @@ class Download : public NetAction { private: std::unique_ptr m_sink; Options m_options; + + std::chrono::steady_clock m_clock; + std::chrono::time_point m_last_progress_time; + qint64 m_last_progress_bytes; + + QString m_details; }; } // namespace Net diff --git a/launcher/net/NetAction.h b/launcher/net/NetAction.h index 38fe058b..f9456bd6 100644 --- a/launcher/net/NetAction.h +++ b/launcher/net/NetAction.h @@ -35,18 +35,39 @@ #pragma once +#include + #include #include #include "QObjectPtr.h" #include "tasks/Task.h" +static const QStringList s_units_si {"kb", "MB", "GB", "TB"}; +static const QStringList s_units_kibi {"kiB", "MiB", "Gib", "TiB"}; + +inline QString humanReadableFileSize(qint64 bytes, bool use_si = false, int decimal_points = 1) { + const QStringList units = use_si ? s_units_si : s_units_kibi; + const int scale = use_si ? 1000 : 1024; + double size = bytes; + + int u = -1; + double r = pow(10, decimal_points); + + do { + size /= scale; + u++; + } while (round(abs(size) * r) / r >= scale && u < units.length() - 1); + + return QString::number(size, 'f', 2) + " " + units[u]; +} + class NetAction : public Task { Q_OBJECT - protected: +protected: explicit NetAction() : Task() {}; - public: +public: using Ptr = shared_qobject_ptr; virtual ~NetAction() = default; @@ -55,23 +76,23 @@ class NetAction : public Task { void setNetwork(shared_qobject_ptr network) { m_network = network; } - protected slots: +protected slots: virtual void downloadProgress(qint64 bytesReceived, qint64 bytesTotal) = 0; virtual void downloadError(QNetworkReply::NetworkError error) = 0; virtual void downloadFinished() = 0; virtual void downloadReadyRead() = 0; - public slots: +public slots: void startAction(shared_qobject_ptr network) { m_network = network; executeTask(); } - protected: +protected: void executeTask() override {}; - public: +public: shared_qobject_ptr m_network; /// the network reply diff --git a/launcher/tasks/ConcurrentTask.cpp b/launcher/tasks/ConcurrentTask.cpp index 3cc37b2a..48e1bc18 100644 --- a/launcher/tasks/ConcurrentTask.cpp +++ b/launcher/tasks/ConcurrentTask.cpp @@ -15,14 +15,13 @@ ConcurrentTask::~ConcurrentTask() } } -auto ConcurrentTask::getStepProgress() const -> qint64 +auto ConcurrentTask::getStepProgress() const -> QList { - return m_stepProgress; -} - -auto ConcurrentTask::getStepTotalProgress() const -> qint64 -{ - return m_stepTotalProgress; + QList task_progress; + for (auto progress : task_progress) { + task_progress.append(task_progress); + } + return task_progress; } void ConcurrentTask::addTask(Task::Ptr task) @@ -33,10 +32,13 @@ void ConcurrentTask::addTask(Task::Ptr task) void ConcurrentTask::executeTask() { // Start the least amount of tasks needed, but at least one - int num_starts = qMax(1, qMin(m_total_max_size, m_queue.size())); - for (int i = 0; i < num_starts; i++) { - QMetaObject::invokeMethod(this, &ConcurrentTask::startNext, Qt::QueuedConnection); - } + // int num_starts = qMax(1, qMin(m_total_max_size, m_queue.size())); + // for (int i = 0; i < num_starts; i++) { + // QMetaObject::invokeMethod(this, &ConcurrentTask::startNext, Qt::QueuedConnection); + // } + // Start One task, startNext hadles starting the up to the m_total_max_size + // while tracking the number currently being done + QMetaObject::invokeMethod(this, &ConcurrentTask::startNext, Qt::QueuedConnection); } bool ConcurrentTask::abort() @@ -97,17 +99,18 @@ void ConcurrentTask::startNext() Task::Ptr next = m_queue.dequeue(); - connect(next.get(), &Task::succeeded, this, [this, next] { subTaskSucceeded(next); }); + connect(next.get(), &Task::succeeded, this, [this, next](){ subTaskSucceeded(next); }); connect(next.get(), &Task::failed, this, [this, next](QString msg) { subTaskFailed(next, msg); }); - connect(next.get(), &Task::status, this, &ConcurrentTask::subTaskStatus); - connect(next.get(), &Task::stepStatus, this, &ConcurrentTask::subTaskStatus); + connect(next.get(), &Task::status, this, [this, next](QString msg){ subTaskStatus(next, msg); }); + connect(next.get(), &Task::stepProgress, this, [this, next](QList tp){ subTaskStepProgress(next, tp); }); - connect(next.get(), &Task::progress, this, &ConcurrentTask::subTaskProgress); + connect(next.get(), &Task::progress, this, [this, next](qint64 current, qint64 total){ subTaskProgress(next, current, total); }); m_doing.insert(next.get(), next); + m_task_progress.insert(next->getUid(), std::make_shared(TaskStepProgress({next->getUid()}))); + - setStepStatus(next->isMultiStep() ? next->getStepStatus() : next->getStatus()); updateState(); QCoreApplication::processEvents(); @@ -123,7 +126,10 @@ void ConcurrentTask::startNext() void ConcurrentTask::subTaskSucceeded(Task::Ptr task) { m_done.insert(task.get(), task); + m_succeeded.insert(task.get(), task); + m_doing.remove(task.get()); + m_task_progress.value(task->getUid())->state = TaskState::Succeeded; disconnect(task.get(), 0, this, 0); @@ -138,6 +144,7 @@ void ConcurrentTask::subTaskFailed(Task::Ptr task, const QString& msg) m_failed.insert(task.get(), task); m_doing.remove(task.get()); + m_task_progress.value(task->getUid())->state = TaskState::Failed; disconnect(task.get(), 0, this, 0); @@ -146,20 +153,64 @@ void ConcurrentTask::subTaskFailed(Task::Ptr task, const QString& msg) startNext(); } -void ConcurrentTask::subTaskStatus(const QString& msg) +void ConcurrentTask::subTaskStatus(Task::Ptr task, const QString& msg) +{ + auto taskProgress = m_task_progress.value(task->getUid()); + taskProgress->status = msg; + updateState(); +} + +void ConcurrentTask::subTaskProgress(Task::Ptr task, qint64 current, qint64 total) { - setStepStatus(msg); + auto taskProgress = m_task_progress.value(task->getUid()); + + taskProgress->current = current; + taskProgress->total = total; + + taskProgress->details = task->getDetails(); + + updateStepProgress(); + updateState(); } -void ConcurrentTask::subTaskProgress(qint64 current, qint64 total) +void ConcurrentTask::subTaskStepProgress(Task::Ptr task, QList task_step_progress) { - m_stepProgress = current; - m_stepTotalProgress = total; + for (auto progress : task_step_progress) { + if (!m_task_progress.contains(progress.uid)) + m_task_progress.insert(progress.uid, std::make_shared(progress)); + + + } + +} + +void ConcurrentTask::updateStepProgress() +{ + qint64 current = 0, total = 0; + for ( auto taskProgress : m_task_progress ) { + current += taskProgress->current; + total += taskProgress->total; + } + + m_stepProgress = current; + m_stepTotalProgress = total; } void ConcurrentTask::updateState() { - setProgress(m_done.count(), totalSize()); - setStatus(tr("Executing %1 task(s) (%2 out of %3 are done)") - .arg(QString::number(m_doing.count()), QString::number(m_done.count()), QString::number(totalSize()))); + if (totalSize() > 1) { + setProgress(m_done.count(), totalSize()); + setStatus(tr("Executing %1 task(s) (%2 out of %3 are done)").arg(QString::number(m_doing.count()), QString::number(m_done.count()), QString::number(totalSize()))); + } else { + setProgress(m_stepProgress, m_stepTotalProgress); + QString status = tr("Please wait ..."); + if (m_queue.size() > 0) { + status = tr("Waiting for 1 task to start ..."); + } else if (m_doing.size() > 0) { + status = tr("Executing 1 task:"); + } else if (m_done.size() > 0) { + status = tr("Task finished."); + } + setStatus(status); + } } diff --git a/launcher/tasks/ConcurrentTask.h b/launcher/tasks/ConcurrentTask.h index d074d2e2..93469766 100644 --- a/launcher/tasks/ConcurrentTask.h +++ b/launcher/tasks/ConcurrentTask.h @@ -1,7 +1,10 @@ #pragma once +#include +#include #include #include +#include #include "tasks/Task.h" @@ -16,10 +19,7 @@ public: bool canAbort() const override { return true; } inline auto isMultiStep() const -> bool override { return m_queue.size() > 1; }; - auto getStepProgress() const -> qint64 override; - auto getStepTotalProgress() const -> qint64 override; - - inline auto getStepStatus() const -> QString override { return m_step_status; } + auto getStepProgress() const -> QList override; void addTask(Task::Ptr task); @@ -39,14 +39,15 @@ slots: void subTaskSucceeded(Task::Ptr); void subTaskFailed(Task::Ptr, const QString &msg); - void subTaskStatus(const QString &msg); - void subTaskProgress(qint64 current, qint64 total); + void subTaskStatus(Task::Ptr task, const QString &msg); + void subTaskProgress(Task::Ptr task, qint64 current, qint64 total); + void subTaskStepProgress(Task::Ptr task, QList task_step_progress); protected: // NOTE: This is not thread-safe. [[nodiscard]] unsigned int totalSize() const { return m_queue.size() + m_doing.size() + m_done.size(); } - void setStepStatus(QString status) { m_step_status = status; emit stepStatus(status); }; + void updateStepProgress(); virtual void updateState(); @@ -56,9 +57,12 @@ protected: QQueue m_queue; - QHash m_doing; + QHash m_doing; QHash m_done; QHash m_failed; + QHash m_succeeded; + + QHash> m_task_progress; int m_total_max_size; diff --git a/launcher/tasks/Task.cpp b/launcher/tasks/Task.cpp index 9ea1bb26..452dc2e3 100644 --- a/launcher/tasks/Task.cpp +++ b/launcher/tasks/Task.cpp @@ -37,8 +37,11 @@ #include +Q_LOGGING_CATEGORY(TaskLogC, "Task") + Task::Task(QObject *parent, bool show_debug) : QObject(parent), m_show_debug(show_debug) { + m_uid = QUuid::createUuid(); setAutoDelete(false); } @@ -65,31 +68,31 @@ void Task::start() case State::Inactive: { if (m_show_debug) - qDebug() << "Task" << describe() << "starting for the first time"; + qCDebug(TaskLogC) << "Task" << describe() << "starting for the first time"; break; } case State::AbortedByUser: { if (m_show_debug) - qDebug() << "Task" << describe() << "restarting for after being aborted by user"; + qCDebug(TaskLogC) << "Task" << describe() << "restarting for after being aborted by user"; break; } case State::Failed: { if (m_show_debug) - qDebug() << "Task" << describe() << "restarting for after failing at first"; + qCDebug(TaskLogC) << "Task" << describe() << "restarting for after failing at first"; break; } case State::Succeeded: { if (m_show_debug) - qDebug() << "Task" << describe() << "restarting for after succeeding at first"; + qCDebug(TaskLogC) << "Task" << describe() << "restarting for after succeeding at first"; break; } case State::Running: { if (m_show_debug) - qWarning() << "The launcher tried to start task" << describe() << "while it was already running!"; + qCWarning(TaskLogC) << "The launcher tried to start task" << describe() << "while it was already running!"; return; } } @@ -104,12 +107,12 @@ void Task::emitFailed(QString reason) // Don't fail twice. if (!isRunning()) { - qCritical() << "Task" << describe() << "failed while not running!!!!: " << reason; + qCCritical(TaskLogC) << "Task" << describe() << "failed while not running!!!!: " << reason; return; } m_state = State::Failed; m_failReason = reason; - qCritical() << "Task" << describe() << "failed: " << reason; + qCCritical(TaskLogC) << "Task" << describe() << "failed: " << reason; emit failed(reason); emit finished(); } @@ -119,13 +122,13 @@ void Task::emitAborted() // Don't abort twice. if (!isRunning()) { - qCritical() << "Task" << describe() << "aborted while not running!!!!"; + qCCritical(TaskLogC) << "Task" << describe() << "aborted while not running!!!!"; return; } m_state = State::AbortedByUser; m_failReason = "Aborted."; if (m_show_debug) - qDebug() << "Task" << describe() << "aborted."; + qCDebug(TaskLogC) << "Task" << describe() << "aborted."; emit aborted(); emit finished(); } @@ -135,12 +138,12 @@ void Task::emitSucceeded() // Don't succeed twice. if (!isRunning()) { - qCritical() << "Task" << describe() << "succeeded while not running!!!!"; + qCCritical(TaskLogC) << "Task" << describe() << "succeeded while not running!!!!"; return; } m_state = State::Succeeded; if (m_show_debug) - qDebug() << "Task" << describe() << "succeeded"; + qCDebug(TaskLogC) << "Task" << describe() << "succeeded"; emit succeeded(); emit finished(); } @@ -159,6 +162,7 @@ QString Task::describe() { out << name; } + out << " ID: " << m_uid.toString(QUuid::WithoutBraces); out << QChar(')'); out.flush(); return outStr; diff --git a/launcher/tasks/Task.h b/launcher/tasks/Task.h index 3d607dca..a6ab15b8 100644 --- a/launcher/tasks/Task.h +++ b/launcher/tasks/Task.h @@ -1,7 +1,8 @@ // SPDX-License-Identifier: GPL-3.0-only /* - * PolyMC - Minecraft Launcher + * PrismLauncher - Minecraft Launcher * Copyright (c) 2022 flowln + * Copyright (c) 2023 Rachel Powers <508861+Ryex@users.noreply.github.com> * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -36,9 +37,29 @@ #pragma once #include +#include +#include #include "QObjectPtr.h" +enum class TaskState { + Waiting, + Running, + Failed, + Succeeded, + Finished +}; + +struct TaskStepProgress { + QUuid uid; + qint64 current; + qint64 total; + QString status; + QString details; + TaskState state = TaskState::Waiting; + bool isDone() { return (state == TaskState::Failed) || (state == TaskState::Succeeded) || (state == TaskState::Finished); } +}; + class Task : public QObject, public QRunnable { Q_OBJECT public: @@ -73,12 +94,14 @@ class Task : public QObject, public QRunnable { auto getState() const -> State { return m_state; } QString getStatus() { return m_status; } - virtual auto getStepStatus() const -> QString { return m_status; } qint64 getProgress() { return m_progress; } qint64 getTotalProgress() { return m_progressTotal; } - virtual auto getStepProgress() const -> qint64 { return 0; } - virtual auto getStepTotalProgress() const -> qint64 { return 100; } + virtual auto getStepProgress() const -> QList { return {}; } + + virtual auto getDetails() const -> QString { return ""; } + + QUuid getUid() { return m_uid; } protected: void logWarning(const QString& line); @@ -94,7 +117,7 @@ class Task : public QObject, public QRunnable { void aborted(); void failed(QString reason); void status(QString status); - void stepStatus(QString status); + void stepProgress(QList task_progress); // /** Emitted when the canAbort() status has changed. */ @@ -135,4 +158,6 @@ class Task : public QObject, public QRunnable { private: // Change using setAbortStatus bool m_can_abort = false; + QUuid m_uid; + }; diff --git a/launcher/ui/dialogs/ProgressDialog.cpp b/launcher/ui/dialogs/ProgressDialog.cpp index da73a449..da627af3 100644 --- a/launcher/ui/dialogs/ProgressDialog.cpp +++ b/launcher/ui/dialogs/ProgressDialog.cpp @@ -1,26 +1,66 @@ -/* Copyright 2013-2021 MultiMC Contributors +/// SPDX-License-Identifier: GPL-3.0-only +/* + * PrismLaucher - Minecraft Launcher + * Copyright (C) 2023 Rachel Powers <508861+Ryex@users.noreply.github.com> * - * 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 + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. * - * http://www.apache.org/licenses/LICENSE-2.0 + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. * - * 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. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * This file incorporates work covered by the following copyright and + * permission notice: + * + * Copyright 2013-2021 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ #include "ProgressDialog.h" #include "ui_ProgressDialog.h" +#include #include #include #include "tasks/Task.h" +#include "ui/widgets/SubTaskProgressBar.h" + + +template +int map_int_range(T value) +{ + auto type_min = std::numeric_limits::min(); + auto type_max = std::numeric_limits::max(); + + auto int_min = std::numeric_limits::min(); + auto int_max = std::numeric_limits::max(); + + auto type_range = type_max - type_min; + auto int_range = int_max - int_min; + + return static_cast((value - type_min) * int_range / type_range + int_min); +} + + ProgressDialog::ProgressDialog(QWidget* parent) : QDialog(parent), ui(new Ui::ProgressDialog) { ui->setupUi(this); @@ -79,7 +119,7 @@ int ProgressDialog::execWithTask(Task* task) connect(task, &Task::failed, this, &ProgressDialog::onTaskFailed); connect(task, &Task::succeeded, this, &ProgressDialog::onTaskSucceeded); connect(task, &Task::status, this, &ProgressDialog::changeStatus); - connect(task, &Task::stepStatus, this, &ProgressDialog::changeStatus); + connect(task, &Task::stepProgress, this, &ProgressDialog::changeStepProgress); connect(task, &Task::progress, this, &ProgressDialog::changeProgress); connect(task, &Task::aborted, this, &ProgressDialog::hide); @@ -149,23 +189,54 @@ void ProgressDialog::onTaskSucceeded() void ProgressDialog::changeStatus(const QString& status) { ui->globalStatusLabel->setText(task->getStatus()); - ui->statusLabel->setText(task->getStepStatus()); + // ui->statusLabel->setText(task->getStepStatus()); updateSize(); } +void ProgressDialog::addTaskProgress(TaskStepProgress progress) +{ + SubTaskProgressBar* task_bar = new SubTaskProgressBar(this); + taskProgress.insert(progress.uid, task_bar); + ui->taskProgressLayout->addWidget(task_bar); +} + +void ProgressDialog::changeStepProgress(QList task_progress) +{ + for (auto tp : task_progress) { + if (!taskProgress.contains(tp.uid)) + addTaskProgress(tp); + auto task_bar = taskProgress.value(tp.uid); + + if (tp.total < 0) { + task_bar->setRange(0, 0); + } else { + task_bar->setRange(0, map_int_range(tp.total)); + } + + task_bar->setValue(map_int_range(tp.current)); + task_bar->setStatus(tp.status); + task_bar->setDetails(tp.details); + + if (tp.isDone()) { + task_bar->setVisible(false); + } + + } +} + void ProgressDialog::changeProgress(qint64 current, qint64 total) { ui->globalProgressBar->setMaximum(total); ui->globalProgressBar->setValue(current); - if (!m_is_multi_step) { - ui->taskProgressBar->setMaximum(total); - ui->taskProgressBar->setValue(current); - } else { - ui->taskProgressBar->setMaximum(task->getStepProgress()); - ui->taskProgressBar->setValue(task->getStepTotalProgress()); - } + // if (!m_is_multi_step) { + // ui->taskProgressBar->setMaximum(total); + // ui->taskProgressBar->setValue(current); + // } else { + // ui->taskProgressBar->setMaximum(task->getStepProgress()); + // ui->taskProgressBar->setValue(task->getStepTotalProgress()); + // } } void ProgressDialog::keyPressEvent(QKeyEvent* e) diff --git a/launcher/ui/dialogs/ProgressDialog.h b/launcher/ui/dialogs/ProgressDialog.h index 0b4b78a4..a7e203fb 100644 --- a/launcher/ui/dialogs/ProgressDialog.h +++ b/launcher/ui/dialogs/ProgressDialog.h @@ -1,22 +1,50 @@ -/* Copyright 2013-2021 MultiMC Contributors +/// SPDX-License-Identifier: GPL-3.0-only +/* + * PrismLaucher - Minecraft Launcher + * Copyright (C) 2023 Rachel Powers <508861+Ryex@users.noreply.github.com> * - * 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 + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. * - * http://www.apache.org/licenses/LICENSE-2.0 + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. * - * 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. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * This file incorporates work covered by the following copyright and + * permission notice: + * + * Copyright 2013-2021 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ + #pragma once #include #include +#include +#include + +#include "QObjectPtr.h" +#include "tasks/Task.h" + +#include "ui/widgets/SubTaskProgressBar.h" class Task; class SequentialTask; @@ -52,6 +80,7 @@ slots: void changeStatus(const QString &status); void changeProgress(qint64 current, qint64 total); + void changeStepProgress(QList task_progress); private @@ -64,6 +93,7 @@ protected: private: bool handleImmediateResult(QDialog::DialogCode &result); + void addTaskProgress(TaskStepProgress progress); private: Ui::ProgressDialog *ui; @@ -71,4 +101,8 @@ private: Task *task; bool m_is_multi_step = false; + QHash taskProgress; + + }; + diff --git a/launcher/ui/dialogs/ProgressDialog.ui b/launcher/ui/dialogs/ProgressDialog.ui index 34ab71e3..0a998987 100644 --- a/launcher/ui/dialogs/ProgressDialog.ui +++ b/launcher/ui/dialogs/ProgressDialog.ui @@ -2,6 +2,20 @@ ProgressDialog + + + 0 + 0 + 400 + 109 + + + + + 0 + 0 + + 400 @@ -18,6 +32,16 @@ Please wait... + + + + true + + + 24 + + + @@ -31,15 +55,11 @@ - - - - Global Task Status... - - + + - - + + 0 @@ -47,30 +67,7 @@ - Task Status... - - - true - - - - - - - 24 - - - false - - - - - - - true - - - 24 + Global Task Status... diff --git a/launcher/ui/widgets/SubTaskProgressBar.cpp b/launcher/ui/widgets/SubTaskProgressBar.cpp new file mode 100644 index 00000000..84ea5f20 --- /dev/null +++ b/launcher/ui/widgets/SubTaskProgressBar.cpp @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * PrismLaucher - Minecraft Launcher + * Copyright (C) 2022 Rachel Powers <508861+Ryex@users.noreply.github.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#include "SubTaskProgressBar.h" +#include "ui_SubTaskProgressBar.h" + +unique_qobject_ptr SubTaskProgressBar::create(QWidget* parent) +{ + auto progress_bar = new SubTaskProgressBar(parent); + return unique_qobject_ptr(progress_bar); +} + +SubTaskProgressBar::SubTaskProgressBar(QWidget* parent) + : ui(new Ui::SubTaskProgressBar) +{ + ui->setupUi(this); +} +SubTaskProgressBar::~SubTaskProgressBar() +{ + delete ui; +} + +void SubTaskProgressBar::setRange(int min, int max) +{ + ui->progressBar->setRange(min, max); +} + +void SubTaskProgressBar::setValue(int value) +{ + ui->progressBar->setValue(value); +} + +void SubTaskProgressBar::setStatus(QString status) +{ + ui->statusLabel->setText(status); +} + +void SubTaskProgressBar::setDetails(QString details) +{ + ui->statusDetailsLabel->setText(details); +} + diff --git a/launcher/ui/widgets/SubTaskProgressBar.h b/launcher/ui/widgets/SubTaskProgressBar.h new file mode 100644 index 00000000..3375a0bc --- /dev/null +++ b/launcher/ui/widgets/SubTaskProgressBar.h @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * PrismLaucher - Minecraft Launcher + * Copyright (C) 2022 Rachel Powers <508861+Ryex@users.noreply.github.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ +#pragma once + +#include +#include +#include +#include "QObjectPtr.h" + +namespace Ui { +class SubTaskProgressBar; +} + +class SubTaskProgressBar : public QWidget +{ + Q_OBJECT + +public: + static unique_qobject_ptr create(QWidget* parent = nullptr); + + SubTaskProgressBar(QWidget* parent = nullptr); + ~SubTaskProgressBar(); + + void setRange(int min, int max); + void setValue(int value); + void setStatus(QString status); + void setDetails(QString details); + + + +private: + Ui::SubTaskProgressBar* ui; + +}; diff --git a/launcher/ui/widgets/SubTaskProgressBar.ui b/launcher/ui/widgets/SubTaskProgressBar.ui new file mode 100644 index 00000000..966fdb88 --- /dev/null +++ b/launcher/ui/widgets/SubTaskProgressBar.ui @@ -0,0 +1,70 @@ + + + SubTaskProgressBar + + + + 0 + 0 + 265 + 65 + + + + + 0 + 0 + + + + Form + + + + + + + + + 0 + 0 + + + + Sub Task Status... + + + + + + + + 0 + 0 + + + + Status Details + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + 24 + + + true + + + + + + + + diff --git a/tests/Task_test.cpp b/tests/Task_test.cpp index 95eb4a30..678382ba 100644 --- a/tests/Task_test.cpp +++ b/tests/Task_test.cpp @@ -99,7 +99,7 @@ class TaskTest : public QObject { t.setStatus(status); QCOMPARE(t.getStatus(), status); - QCOMPARE(t.getStepStatus(), status); + QCOMPARE(t.getStepProgress().isEmpty(), QList{}.isEmpty()); } void test_SetStatus_MultiStep(){ @@ -111,7 +111,7 @@ class TaskTest : public QObject { QCOMPARE(t.getStatus(), status); // Even though it is multi step, it does not override the getStepStatus method, // so it should remain the same. - QCOMPARE(t.getStepStatus(), status); + QCOMPARE(t.getStepProgress().isEmpty(), QList{}.isEmpty()); } void test_SetProgress(){ -- cgit From 9d2f0e4dc8fc3995052770c6a7948cb0372fdcbb Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Thu, 30 Mar 2023 23:50:29 -0700 Subject: feat: Propogated subtask progress Oh boy this is big. > TaskStepProgress struct is now QMetaObject compatabile and can be sent through signals > Task now has a method to propogates sub task progress it must be signal bound by each task containing a task wishing to report progress of it's children. > Downloads report speed > Tasks now have UUIDS to track them - use when reporting - use when logging - use when storeing them or objects related to them Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- launcher/InstanceImportTask.cpp | 3 + launcher/InstanceList.cpp | 1 + launcher/ResourceDownloadTask.cpp | 1 + launcher/launch/steps/Update.cpp | 5 +- launcher/minecraft/MinecraftLoadAndCheck.cpp | 1 + launcher/minecraft/MinecraftUpdate.cpp | 2 + launcher/minecraft/update/AssetUpdateTask.cpp | 2 + launcher/minecraft/update/FMLLibrariesTask.cpp | 1 + launcher/minecraft/update/LibrariesTask.cpp | 2 + .../modplatform/atlauncher/ATLPackInstallTask.cpp | 2 + .../flame/FlameInstanceCreationTask.cpp | 4 +- .../modplatform/legacy_ftb/PackInstallTask.cpp | 1 + .../modrinth/ModrinthInstanceCreationTask.cpp | 2 + .../technic/SingleZipPackInstallTask.cpp | 1 + .../modplatform/technic/SolderPackInstallTask.cpp | 1 + launcher/net/Download.cpp | 39 +++++++- launcher/tasks/ConcurrentTask.cpp | 44 +++++---- launcher/tasks/ConcurrentTask.h | 6 +- launcher/tasks/Task.cpp | 5 + launcher/tasks/Task.h | 26 +++-- launcher/ui/dialogs/ProgressDialog.cpp | 65 +++++++------ launcher/ui/dialogs/ProgressDialog.h | 4 +- launcher/ui/dialogs/ProgressDialog.ui | 108 +++++++++++++++++---- launcher/ui/widgets/SubTaskProgressBar.ui | 31 +++++- tests/Task_test.cpp | 9 +- 25 files changed, 269 insertions(+), 97 deletions(-) (limited to 'launcher/ui/dialogs') diff --git a/launcher/InstanceImportTask.cpp b/launcher/InstanceImportTask.cpp index 080828a8..c196396d 100644 --- a/launcher/InstanceImportTask.cpp +++ b/launcher/InstanceImportTask.cpp @@ -98,6 +98,7 @@ void InstanceImportTask::executeTask() connect(m_filesNetJob.get(), &NetJob::succeeded, this, &InstanceImportTask::downloadSucceeded); connect(m_filesNetJob.get(), &NetJob::progress, this, &InstanceImportTask::downloadProgressChanged); + connect(m_filesNetJob.get(), &NetJob::stepProgress, this, &InstanceImportTask::propogateStepProgress); connect(m_filesNetJob.get(), &NetJob::failed, this, &InstanceImportTask::downloadFailed); connect(m_filesNetJob.get(), &NetJob::aborted, this, &InstanceImportTask::downloadAborted); @@ -291,6 +292,7 @@ void InstanceImportTask::processFlame() }); connect(inst_creation_task, &Task::failed, this, &InstanceImportTask::emitFailed); connect(inst_creation_task, &Task::progress, this, &InstanceImportTask::setProgress); + connect(inst_creation_task, &Task::stepProgress, this, &InstanceImportTask::propogateStepProgress); connect(inst_creation_task, &Task::status, this, &InstanceImportTask::setStatus); connect(inst_creation_task, &Task::finished, inst_creation_task, &InstanceCreationTask::deleteLater); @@ -382,6 +384,7 @@ void InstanceImportTask::processModrinth() }); connect(inst_creation_task, &Task::failed, this, &InstanceImportTask::emitFailed); connect(inst_creation_task, &Task::progress, this, &InstanceImportTask::setProgress); + connect(inst_creation_task, &Task::stepProgress, this, &InstanceImportTask::propogateStepProgress); connect(inst_creation_task, &Task::status, this, &InstanceImportTask::setStatus); connect(inst_creation_task, &Task::finished, inst_creation_task, &InstanceCreationTask::deleteLater); diff --git a/launcher/InstanceList.cpp b/launcher/InstanceList.cpp index 68e3e92c..dbc891ff 100644 --- a/launcher/InstanceList.cpp +++ b/launcher/InstanceList.cpp @@ -788,6 +788,7 @@ class InstanceStaging : public Task { connect(child, &Task::abortStatusChanged, this, &InstanceStaging::setAbortable); connect(child, &Task::status, this, &InstanceStaging::setStatus); connect(child, &Task::progress, this, &InstanceStaging::setProgress); + connect(child, &Task::stepProgress, this, &InstanceStaging::propogateStepProgress); connect(&m_backoffTimer, &QTimer::timeout, this, &InstanceStaging::childSucceded); } diff --git a/launcher/ResourceDownloadTask.cpp b/launcher/ResourceDownloadTask.cpp index 98bcf259..61b918aa 100644 --- a/launcher/ResourceDownloadTask.cpp +++ b/launcher/ResourceDownloadTask.cpp @@ -53,6 +53,7 @@ ResourceDownloadTask::ResourceDownloadTask(ModPlatform::IndexedPack pack, m_filesNetJob->addNetAction(Net::Download::makeFile(m_pack_version.downloadUrl, dir.absoluteFilePath(getFilename()))); connect(m_filesNetJob.get(), &NetJob::succeeded, this, &ResourceDownloadTask::downloadSucceeded); connect(m_filesNetJob.get(), &NetJob::progress, this, &ResourceDownloadTask::downloadProgressChanged); + connect(m_filesNetJob.get(), &NetJob::stepProgress, this, &ResourceDownloadTask::propogateStepProgress); connect(m_filesNetJob.get(), &NetJob::failed, this, &ResourceDownloadTask::downloadFailed); addTask(m_filesNetJob); diff --git a/launcher/launch/steps/Update.cpp b/launcher/launch/steps/Update.cpp index 28bd153d..1640d115 100644 --- a/launcher/launch/steps/Update.cpp +++ b/launcher/launch/steps/Update.cpp @@ -27,8 +27,9 @@ void Update::executeTask() if(m_updateTask) { connect(m_updateTask.get(), SIGNAL(finished()), this, SLOT(updateFinished())); - connect(m_updateTask.get(), &Task::progress, this, &Task::setProgress); - connect(m_updateTask.get(), &Task::status, this, &Task::setStatus); + connect(m_updateTask.get(), &Task::progress, this, &Update::setProgress); + connect(m_updateTask.get(), &Task::stepProgress, this, &Update::propogateStepProgress); + connect(m_updateTask.get(), &Task::status, this, &Update::setStatus); emit progressReportingRequest(); return; } diff --git a/launcher/minecraft/MinecraftLoadAndCheck.cpp b/launcher/minecraft/MinecraftLoadAndCheck.cpp index d72bc7be..1c3f6fb7 100644 --- a/launcher/minecraft/MinecraftLoadAndCheck.cpp +++ b/launcher/minecraft/MinecraftLoadAndCheck.cpp @@ -22,6 +22,7 @@ void MinecraftLoadAndCheck::executeTask() connect(m_task.get(), &Task::failed, this, &MinecraftLoadAndCheck::subtaskFailed); connect(m_task.get(), &Task::aborted, this, [this]{ subtaskFailed(tr("Aborted")); }); connect(m_task.get(), &Task::progress, this, &MinecraftLoadAndCheck::progress); + connect(m_task.get(), &Task::stepProgress, this, &MinecraftLoadAndCheck::propogateStepProgress); connect(m_task.get(), &Task::status, this, &MinecraftLoadAndCheck::setStatus); } diff --git a/launcher/minecraft/MinecraftUpdate.cpp b/launcher/minecraft/MinecraftUpdate.cpp index 07ad4882..3ce808f8 100644 --- a/launcher/minecraft/MinecraftUpdate.cpp +++ b/launcher/minecraft/MinecraftUpdate.cpp @@ -100,6 +100,7 @@ void MinecraftUpdate::next() disconnect(task.get(), &Task::failed, this, &MinecraftUpdate::subtaskFailed); disconnect(task.get(), &Task::aborted, this, &Task::abort); disconnect(task.get(), &Task::progress, this, &MinecraftUpdate::progress); + disconnect(task.get(), &Task::stepProgress, this, &MinecraftUpdate::propogateStepProgress); disconnect(task.get(), &Task::status, this, &MinecraftUpdate::setStatus); } if(m_currentTask == m_tasks.size()) @@ -118,6 +119,7 @@ void MinecraftUpdate::next() connect(task.get(), &Task::failed, this, &MinecraftUpdate::subtaskFailed); connect(task.get(), &Task::aborted, this, &Task::abort); connect(task.get(), &Task::progress, this, &MinecraftUpdate::progress); + connect(task.get(), &Task::stepProgress, this, &MinecraftUpdate::propogateStepProgress); connect(task.get(), &Task::status, this, &MinecraftUpdate::setStatus); // if the task is already running, do not start it again if(!task->isRunning()) diff --git a/launcher/minecraft/update/AssetUpdateTask.cpp b/launcher/minecraft/update/AssetUpdateTask.cpp index 8ccb0e1d..31fd5eb1 100644 --- a/launcher/minecraft/update/AssetUpdateTask.cpp +++ b/launcher/minecraft/update/AssetUpdateTask.cpp @@ -45,6 +45,7 @@ void AssetUpdateTask::executeTask() connect(downloadJob.get(), &NetJob::failed, this, &AssetUpdateTask::assetIndexFailed); connect(downloadJob.get(), &NetJob::aborted, this, [this]{ emitFailed(tr("Aborted")); }); connect(downloadJob.get(), &NetJob::progress, this, &AssetUpdateTask::progress); + connect(downloadJob.get(), &NetJob::stepProgress, this, &AssetUpdateTask::propogateStepProgress); qDebug() << m_inst->name() << ": Starting asset index download"; downloadJob->start(); @@ -83,6 +84,7 @@ void AssetUpdateTask::assetIndexFinished() connect(downloadJob.get(), &NetJob::failed, this, &AssetUpdateTask::assetsFailed); connect(downloadJob.get(), &NetJob::aborted, this, [this]{ emitFailed(tr("Aborted")); }); connect(downloadJob.get(), &NetJob::progress, this, &AssetUpdateTask::progress); + connect(downloadJob.get(), &NetJob::stepProgress, this, &AssetUpdateTask::propogateStepProgress); downloadJob->start(); return; } diff --git a/launcher/minecraft/update/FMLLibrariesTask.cpp b/launcher/minecraft/update/FMLLibrariesTask.cpp index 96fd3ba3..75e5c572 100644 --- a/launcher/minecraft/update/FMLLibrariesTask.cpp +++ b/launcher/minecraft/update/FMLLibrariesTask.cpp @@ -75,6 +75,7 @@ void FMLLibrariesTask::executeTask() connect(dljob.get(), &NetJob::failed, this, &FMLLibrariesTask::fmllibsFailed); connect(dljob.get(), &NetJob::aborted, this, [this]{ emitFailed(tr("Aborted")); }); connect(dljob.get(), &NetJob::progress, this, &FMLLibrariesTask::progress); + connect(dljob.get(), &NetJob::stepProgress, this, &FMLLibrariesTask::propogateStepProgress); downloadJob.reset(dljob); downloadJob->start(); } diff --git a/launcher/minecraft/update/LibrariesTask.cpp b/launcher/minecraft/update/LibrariesTask.cpp index b9410111..415b9a66 100644 --- a/launcher/minecraft/update/LibrariesTask.cpp +++ b/launcher/minecraft/update/LibrariesTask.cpp @@ -70,6 +70,8 @@ void LibrariesTask::executeTask() connect(downloadJob.get(), &NetJob::failed, this, &LibrariesTask::jarlibFailed); connect(downloadJob.get(), &NetJob::aborted, this, [this]{ emitFailed(tr("Aborted")); }); connect(downloadJob.get(), &NetJob::progress, this, &LibrariesTask::progress); + connect(downloadJob.get(), &NetJob::stepProgress, this, &LibrariesTask::propogateStepProgress); + downloadJob->start(); } diff --git a/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp b/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp index 4bd8b7f2..28026732 100644 --- a/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp +++ b/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp @@ -683,6 +683,7 @@ void PackInstallTask::installConfigs() abortable = true; setProgress(current, total); }); + connect(jobPtr.get(), &NetJob::stepProgress, this, &PackInstallTask::propogateStepProgress); connect(jobPtr.get(), &NetJob::aborted, [&]{ abortable = false; jobPtr.reset(); @@ -849,6 +850,7 @@ void PackInstallTask::downloadMods() abortable = true; setProgress(current, total); }); + connect(jobPtr.get(), &NetJob::stepProgress, this, &PackInstallTask::propogateStepProgress); connect(jobPtr.get(), &NetJob::aborted, [&] { abortable = false; diff --git a/launcher/modplatform/flame/FlameInstanceCreationTask.cpp b/launcher/modplatform/flame/FlameInstanceCreationTask.cpp index 964b559c..3cb6b61a 100644 --- a/launcher/modplatform/flame/FlameInstanceCreationTask.cpp +++ b/launcher/modplatform/flame/FlameInstanceCreationTask.cpp @@ -35,6 +35,7 @@ #include "FlameInstanceCreationTask.h" +#include "modplatform/flame/FileResolvingTask.h" #include "modplatform/flame/FlameAPI.h" #include "modplatform/flame/PackManifest.h" @@ -382,7 +383,7 @@ bool FlameCreationTask::createInstance() }); connect(m_mod_id_resolver.get(), &Flame::FileResolvingTask::progress, this, &FlameCreationTask::setProgress); connect(m_mod_id_resolver.get(), &Flame::FileResolvingTask::status, this, &FlameCreationTask::setStatus); - + connect(m_mod_id_resolver.get(), &Flame::FileResolvingTask::stepProgress, this, &FlameCreationTask::propogateStepProgress); m_mod_id_resolver->start(); loop.exec(); @@ -497,6 +498,7 @@ void FlameCreationTask::setupDownloadJob(QEventLoop& loop) setError(reason); }); connect(m_files_job.get(), &NetJob::progress, this, &FlameCreationTask::setProgress); + connect(m_files_job.get(), &NetJob::stepProgress, this, &FlameCreationTask::propogateStepProgress); connect(m_files_job.get(), &NetJob::finished, &loop, &QEventLoop::quit); setStatus(tr("Downloading mods...")); diff --git a/launcher/modplatform/legacy_ftb/PackInstallTask.cpp b/launcher/modplatform/legacy_ftb/PackInstallTask.cpp index 8d45fc5c..36c142ac 100644 --- a/launcher/modplatform/legacy_ftb/PackInstallTask.cpp +++ b/launcher/modplatform/legacy_ftb/PackInstallTask.cpp @@ -81,6 +81,7 @@ void PackInstallTask::downloadPack() connect(netJobContainer.get(), &NetJob::succeeded, this, &PackInstallTask::onDownloadSucceeded); connect(netJobContainer.get(), &NetJob::failed, this, &PackInstallTask::onDownloadFailed); connect(netJobContainer.get(), &NetJob::progress, this, &PackInstallTask::onDownloadProgress); + connect(netJobContainer.get(), &NetJob::stepProgress, this, &PackInstallTask::propogateStepProgress); connect(netJobContainer.get(), &NetJob::aborted, this, &PackInstallTask::onDownloadAborted); netJobContainer->start(); diff --git a/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp b/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp index 6814e645..2fb656ea 100644 --- a/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp +++ b/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp @@ -11,6 +11,7 @@ #include "net/ChecksumValidator.h" +#include "net/NetJob.h" #include "settings/INISettingsObject.h" #include "ui/dialogs/CustomMessageBox.h" @@ -263,6 +264,7 @@ bool ModrinthCreationTask::createInstance() }); connect(m_files_job.get(), &NetJob::finished, &loop, &QEventLoop::quit); connect(m_files_job.get(), &NetJob::progress, [&](qint64 current, qint64 total) { setProgress(current, total); }); + connect(m_files_job.get(), &NetJob::stepProgress, this, &ModrinthCreationTask::propogateStepProgress); setStatus(tr("Downloading mods...")); m_files_job->start(); diff --git a/launcher/modplatform/technic/SingleZipPackInstallTask.cpp b/launcher/modplatform/technic/SingleZipPackInstallTask.cpp index 8fd43d21..f07ca24a 100644 --- a/launcher/modplatform/technic/SingleZipPackInstallTask.cpp +++ b/launcher/modplatform/technic/SingleZipPackInstallTask.cpp @@ -50,6 +50,7 @@ void Technic::SingleZipPackInstallTask::executeTask() auto job = m_filesNetJob.get(); connect(job, &NetJob::succeeded, this, &Technic::SingleZipPackInstallTask::downloadSucceeded); connect(job, &NetJob::progress, this, &Technic::SingleZipPackInstallTask::downloadProgressChanged); + connect(job, &NetJob::stepProgress, this, &Technic::SingleZipPackInstallTask::propogateStepProgress); connect(job, &NetJob::failed, this, &Technic::SingleZipPackInstallTask::downloadFailed); m_filesNetJob->start(); } diff --git a/launcher/modplatform/technic/SolderPackInstallTask.cpp b/launcher/modplatform/technic/SolderPackInstallTask.cpp index 77c503f0..c26d6a5a 100644 --- a/launcher/modplatform/technic/SolderPackInstallTask.cpp +++ b/launcher/modplatform/technic/SolderPackInstallTask.cpp @@ -127,6 +127,7 @@ void Technic::SolderPackInstallTask::fileListSucceeded() connect(m_filesNetJob.get(), &NetJob::succeeded, this, &Technic::SolderPackInstallTask::downloadSucceeded); connect(m_filesNetJob.get(), &NetJob::progress, this, &Technic::SolderPackInstallTask::downloadProgressChanged); + connect(m_filesNetJob.get(), &NetJob::stepProgress, this, &Technic::SolderPackInstallTask::propogateStepProgress); connect(m_filesNetJob.get(), &NetJob::failed, this, &Technic::SolderPackInstallTask::downloadFailed); connect(m_filesNetJob.get(), &NetJob::aborted, this, &Technic::SolderPackInstallTask::downloadAborted); m_filesNetJob->start(); diff --git a/launcher/net/Download.cpp b/launcher/net/Download.cpp index 26488a43..a4c3ebfc 100644 --- a/launcher/net/Download.cpp +++ b/launcher/net/Download.cpp @@ -36,6 +36,8 @@ */ #include "Download.h" +#include +#include #include #include @@ -52,6 +54,33 @@ Q_LOGGING_CATEGORY(DownloadLogC, "Task.Net.Download") namespace Net { +QString truncateUrlHumanFriendly(QUrl &url, int max_len, bool hard_limit = false) +{ + auto display_options = QUrl::RemoveUserInfo | QUrl::RemoveFragment | QUrl::NormalizePathSegments; + auto str_url = url.toDisplayString(display_options); + if (str_url.length() <= max_len) + return str_url; + + QRegularExpression re(R"(^([\w]+:\/\/)([\w._-]+\/)([\w._-]+\/).*(\/[^]+[^]+)$)"); + + auto url_compact = QString(str_url); + url_compact.replace(re, "\\1\\2\\3...\\4"); + if (url_compact.length() >= max_len) { + auto url_compact = QString(str_url); + url_compact.replace(re, "\\1\\2...\\4"); + } + + + if ((url_compact.length() >= max_len) && hard_limit) { + auto to_remove = url_compact.length() - max_len + 3; + url_compact.remove(url_compact.length() - to_remove - 1, to_remove); + url_compact.append("..."); + } + + return url_compact; + +} + auto Download::makeCached(QUrl url, MetaEntryPtr entry, Options options) -> Download::Ptr { auto dl = makeShared(); @@ -91,7 +120,7 @@ void Download::addValidator(Validator* v) void Download::executeTask() { - setStatus(tr("Downloading %1").arg(m_url.toString())); + setStatus(tr("Downloading %1").arg(truncateUrlHumanFriendly(m_url, 60))); if (getState() == Task::State::AbortedByUser) { qCWarning(DownloadLogC) << getUid().toString() << "Attempt to start an aborted Download:" << m_url.toString(); @@ -152,9 +181,11 @@ void Download::downloadProgress(qint64 bytesReceived, qint64 bytesTotal) auto elapsed = now - m_last_progress_time; auto elapsed_ms = std::chrono::duration_cast(elapsed).count(); auto bytes_recived_since = bytesReceived - m_last_progress_bytes; - - auto speed = humanReadableFileSize(bytes_recived_since / elapsed_ms * 1000) + "/s"; - m_details = speed; + if (elapsed_ms > 0) { + m_details = humanReadableFileSize(bytes_recived_since / elapsed_ms * 1000) + "/s"; + } else { + m_details = "0 b/s"; + } setProgress(bytesReceived, bytesTotal); } diff --git a/launcher/tasks/ConcurrentTask.cpp b/launcher/tasks/ConcurrentTask.cpp index 48e1bc18..fde7d0ad 100644 --- a/launcher/tasks/ConcurrentTask.cpp +++ b/launcher/tasks/ConcurrentTask.cpp @@ -2,6 +2,7 @@ #include #include +#include "tasks/Task.h" ConcurrentTask::ConcurrentTask(QObject* parent, QString task_name, int max_concurrent) : Task(parent), m_name(task_name), m_total_max_size(max_concurrent) @@ -15,13 +16,9 @@ ConcurrentTask::~ConcurrentTask() } } -auto ConcurrentTask::getStepProgress() const -> QList +auto ConcurrentTask::getStepProgress() const -> TaskStepProgressList { - QList task_progress; - for (auto progress : task_progress) { - task_progress.append(task_progress); - } - return task_progress; + return m_task_progress.values(); } void ConcurrentTask::addTask(Task::Ptr task) @@ -103,7 +100,7 @@ void ConcurrentTask::startNext() connect(next.get(), &Task::failed, this, [this, next](QString msg) { subTaskFailed(next, msg); }); connect(next.get(), &Task::status, this, [this, next](QString msg){ subTaskStatus(next, msg); }); - connect(next.get(), &Task::stepProgress, this, [this, next](QList tp){ subTaskStepProgress(next, tp); }); + connect(next.get(), &Task::stepProgress, this, [this, next](TaskStepProgressList tp){ subTaskStepProgress(next, tp); }); connect(next.get(), &Task::progress, this, [this, next](qint64 current, qint64 total){ subTaskProgress(next, current, total); }); @@ -112,6 +109,7 @@ void ConcurrentTask::startNext() updateState(); + updateStepProgress(); QCoreApplication::processEvents(); @@ -129,12 +127,12 @@ void ConcurrentTask::subTaskSucceeded(Task::Ptr task) m_succeeded.insert(task.get(), task); m_doing.remove(task.get()); - m_task_progress.value(task->getUid())->state = TaskState::Succeeded; + m_task_progress.value(task->getUid())->state = TaskStepState::Succeeded; disconnect(task.get(), 0, this, 0); updateState(); - + updateStepProgress(); startNext(); } @@ -144,20 +142,22 @@ void ConcurrentTask::subTaskFailed(Task::Ptr task, const QString& msg) m_failed.insert(task.get(), task); m_doing.remove(task.get()); - m_task_progress.value(task->getUid())->state = TaskState::Failed; + m_task_progress.value(task->getUid())->state = TaskStepState::Failed; disconnect(task.get(), 0, this, 0); updateState(); - + updateStepProgress(); startNext(); } void ConcurrentTask::subTaskStatus(Task::Ptr task, const QString& msg) { auto taskProgress = m_task_progress.value(task->getUid()); - taskProgress->status = msg; + taskProgress->status = msg; + taskProgress->state = TaskStepState::Running; updateState(); + updateStepProgress(); } void ConcurrentTask::subTaskProgress(Task::Ptr task, qint64 current, qint64 total) @@ -166,21 +166,28 @@ void ConcurrentTask::subTaskProgress(Task::Ptr task, qint64 current, qint64 tota taskProgress->current = current; taskProgress->total = total; - + taskProgress->state = TaskStepState::Running; taskProgress->details = task->getDetails(); updateStepProgress(); updateState(); } -void ConcurrentTask::subTaskStepProgress(Task::Ptr task, QList task_step_progress) +void ConcurrentTask::subTaskStepProgress(Task::Ptr task, TaskStepProgressList task_step_progress) { for (auto progress : task_step_progress) { - if (!m_task_progress.contains(progress.uid)) - m_task_progress.insert(progress.uid, std::make_shared(progress)); - - + if (!m_task_progress.contains(progress->uid)) { + m_task_progress.insert(progress->uid, progress); + } else { + auto tp = m_task_progress.value(progress->uid); + tp->current = progress->current; + tp->total = progress->total; + tp->status = progress->status; + tp->details = progress->details; + } } + + updateStepProgress(); } @@ -194,6 +201,7 @@ void ConcurrentTask::updateStepProgress() m_stepProgress = current; m_stepTotalProgress = total; + emit stepProgress(m_task_progress.values()); } void ConcurrentTask::updateState() diff --git a/launcher/tasks/ConcurrentTask.h b/launcher/tasks/ConcurrentTask.h index 93469766..9d4413c6 100644 --- a/launcher/tasks/ConcurrentTask.h +++ b/launcher/tasks/ConcurrentTask.h @@ -18,8 +18,8 @@ public: bool canAbort() const override { return true; } - inline auto isMultiStep() const -> bool override { return m_queue.size() > 1; }; - auto getStepProgress() const -> QList override; + inline auto isMultiStep() const -> bool override { return totalSize() > 1; }; + auto getStepProgress() const -> TaskStepProgressList override; void addTask(Task::Ptr task); @@ -41,7 +41,7 @@ slots: void subTaskFailed(Task::Ptr, const QString &msg); void subTaskStatus(Task::Ptr task, const QString &msg); void subTaskProgress(Task::Ptr task, qint64 current, qint64 total); - void subTaskStepProgress(Task::Ptr task, QList task_step_progress); + void subTaskStepProgress(Task::Ptr task, TaskStepProgressList task_step_progress); protected: // NOTE: This is not thread-safe. diff --git a/launcher/tasks/Task.cpp b/launcher/tasks/Task.cpp index 452dc2e3..5aada876 100644 --- a/launcher/tasks/Task.cpp +++ b/launcher/tasks/Task.cpp @@ -148,6 +148,11 @@ void Task::emitSucceeded() emit finished(); } +void Task::propogateStepProgress(TaskStepProgressList task_progress) +{ + emit stepProgress(task_progress); +} + QString Task::describe() { QString outStr; diff --git a/launcher/tasks/Task.h b/launcher/tasks/Task.h index a6ab15b8..863f8a4c 100644 --- a/launcher/tasks/Task.h +++ b/launcher/tasks/Task.h @@ -42,7 +42,7 @@ #include "QObjectPtr.h" -enum class TaskState { +enum class TaskStepState { Waiting, Running, Failed, @@ -50,16 +50,22 @@ enum class TaskState { Finished }; +Q_DECLARE_METATYPE(TaskStepState) + struct TaskStepProgress { QUuid uid; - qint64 current; - qint64 total; - QString status; - QString details; - TaskState state = TaskState::Waiting; - bool isDone() { return (state == TaskState::Failed) || (state == TaskState::Succeeded) || (state == TaskState::Finished); } + qint64 current = 0; + qint64 total = -1; + QString status = ""; + QString details = ""; + TaskStepState state = TaskStepState::Waiting; + bool isDone() { return (state == TaskStepState::Failed) || (state == TaskStepState::Succeeded) || (state == TaskStepState::Finished); } }; +Q_DECLARE_METATYPE(TaskStepProgress) + +typedef QList> TaskStepProgressList; + class Task : public QObject, public QRunnable { Q_OBJECT public: @@ -97,7 +103,7 @@ class Task : public QObject, public QRunnable { qint64 getProgress() { return m_progress; } qint64 getTotalProgress() { return m_progressTotal; } - virtual auto getStepProgress() const -> QList { return {}; } + virtual auto getStepProgress() const -> TaskStepProgressList { return {}; } virtual auto getDetails() const -> QString { return ""; } @@ -117,7 +123,7 @@ class Task : public QObject, public QRunnable { void aborted(); void failed(QString reason); void status(QString status); - void stepProgress(QList task_progress); // + void stepProgress(TaskStepProgressList task_progress); // /** Emitted when the canAbort() status has changed. */ @@ -140,6 +146,8 @@ class Task : public QObject, public QRunnable { virtual void emitAborted(); virtual void emitFailed(QString reason = ""); + virtual void propogateStepProgress(TaskStepProgressList task_progress); + public slots: void setStatus(const QString& status); void setProgress(qint64 current, qint64 total); diff --git a/launcher/ui/dialogs/ProgressDialog.cpp b/launcher/ui/dialogs/ProgressDialog.cpp index da627af3..f7a3a862 100644 --- a/launcher/ui/dialogs/ProgressDialog.cpp +++ b/launcher/ui/dialogs/ProgressDialog.cpp @@ -48,10 +48,12 @@ template int map_int_range(T value) { - auto type_min = std::numeric_limits::min(); + // auto type_min = std::numeric_limits::min(); + auto type_min = 0; auto type_max = std::numeric_limits::max(); - auto int_min = std::numeric_limits::min(); + // auto int_min = std::numeric_limits::min(); + auto int_min = 0; auto int_max = std::numeric_limits::max(); auto type_range = type_max - type_min; @@ -64,6 +66,7 @@ int map_int_range(T value) ProgressDialog::ProgressDialog(QWidget* parent) : QDialog(parent), ui(new Ui::ProgressDialog) { ui->setupUi(this); + ui->taskProgressScrollArea->setHidden(true); this->setWindowFlags(this->windowFlags() & ~Qt::WindowContextHelpButtonHint); setAttribute(Qt::WidgetAttribute::WA_QuitOnClose, true); setSkipButton(false); @@ -94,10 +97,17 @@ ProgressDialog::~ProgressDialog() } void ProgressDialog::updateSize() -{ +{ + QSize lastSize = this->size(); QSize qSize = QSize(480, minimumSizeHint().height()); resize(qSize); setFixedSize(qSize); + // keep the dialog in the center after a resize + if (lastSize != qSize) + this->move( + this->parentWidget()->x() + (this->parentWidget()->width() - this->width()) / 2, + this->parentWidget()->y() + (this->parentWidget()->height() - this->height()) / 2 + ); } int ProgressDialog::execWithTask(Task* task) @@ -126,10 +136,8 @@ int ProgressDialog::execWithTask(Task* task) connect(task, &Task::abortStatusChanged, ui->skipButton, &QPushButton::setEnabled); m_is_multi_step = task->isMultiStep(); - if (!m_is_multi_step) { - ui->globalStatusLabel->setHidden(true); - ui->globalProgressBar->setHidden(true); - } + ui->taskProgressScrollArea->setHidden(!m_is_multi_step); + updateSize(); // It's a good idea to start the task after we entered the dialog's event loop :^) if (!task->isRunning()) { @@ -139,6 +147,9 @@ int ProgressDialog::execWithTask(Task* task) changeProgress(task->getProgress(), task->getTotalProgress()); } + // auto size_hint = ui->verticalLayout->sizeHint(); + // resize(size_hint.width(), size_hint.height()); + return QDialog::exec(); } @@ -189,40 +200,45 @@ void ProgressDialog::onTaskSucceeded() void ProgressDialog::changeStatus(const QString& status) { ui->globalStatusLabel->setText(task->getStatus()); - // ui->statusLabel->setText(task->getStepStatus()); + ui->globalStatusDetailsLabel->setText(task->getDetails()); updateSize(); } -void ProgressDialog::addTaskProgress(TaskStepProgress progress) +void ProgressDialog::addTaskProgress(TaskStepProgress* progress) { SubTaskProgressBar* task_bar = new SubTaskProgressBar(this); - taskProgress.insert(progress.uid, task_bar); - ui->taskProgressLayout->addWidget(task_bar); + taskProgress.insert(progress->uid, task_bar); + ui->taskProgressLayout->insertWidget(0, task_bar); } -void ProgressDialog::changeStepProgress(QList task_progress) +void ProgressDialog::changeStepProgress(TaskStepProgressList task_progress) { + m_is_multi_step = true; + ui->taskProgressScrollArea->setHidden(false); + for (auto tp : task_progress) { - if (!taskProgress.contains(tp.uid)) - addTaskProgress(tp); - auto task_bar = taskProgress.value(tp.uid); + if (!taskProgress.contains(tp->uid)) + addTaskProgress(tp.get()); + auto task_bar = taskProgress.value(tp->uid); - if (tp.total < 0) { + if (tp->total < 0) { task_bar->setRange(0, 0); } else { - task_bar->setRange(0, map_int_range(tp.total)); + task_bar->setRange(0, map_int_range(tp->total)); } - task_bar->setValue(map_int_range(tp.current)); - task_bar->setStatus(tp.status); - task_bar->setDetails(tp.details); + task_bar->setValue(map_int_range(tp->current)); + task_bar->setStatus(tp->status); + task_bar->setDetails(tp->details); - if (tp.isDone()) { + if (tp->isDone()) { task_bar->setVisible(false); } } + + updateSize(); } void ProgressDialog::changeProgress(qint64 current, qint64 total) @@ -230,13 +246,6 @@ void ProgressDialog::changeProgress(qint64 current, qint64 total) ui->globalProgressBar->setMaximum(total); ui->globalProgressBar->setValue(current); - // if (!m_is_multi_step) { - // ui->taskProgressBar->setMaximum(total); - // ui->taskProgressBar->setValue(current); - // } else { - // ui->taskProgressBar->setMaximum(task->getStepProgress()); - // ui->taskProgressBar->setValue(task->getStepTotalProgress()); - // } } void ProgressDialog::keyPressEvent(QKeyEvent* e) diff --git a/launcher/ui/dialogs/ProgressDialog.h b/launcher/ui/dialogs/ProgressDialog.h index a7e203fb..95a4db16 100644 --- a/launcher/ui/dialogs/ProgressDialog.h +++ b/launcher/ui/dialogs/ProgressDialog.h @@ -80,7 +80,7 @@ slots: void changeStatus(const QString &status); void changeProgress(qint64 current, qint64 total); - void changeStepProgress(QList task_progress); + void changeStepProgress(TaskStepProgressList task_progress); private @@ -93,7 +93,7 @@ protected: private: bool handleImmediateResult(QDialog::DialogCode &result); - void addTaskProgress(TaskStepProgress progress); + void addTaskProgress(TaskStepProgress* progress); private: Ui::ProgressDialog *ui; diff --git a/launcher/ui/dialogs/ProgressDialog.ui b/launcher/ui/dialogs/ProgressDialog.ui index 0a998987..47597689 100644 --- a/launcher/ui/dialogs/ProgressDialog.ui +++ b/launcher/ui/dialogs/ProgressDialog.ui @@ -6,20 +6,20 @@ 0 0 - 400 - 109 + 600 + 260 - - 0 - 0 + + 1 + 1 - 400 - 0 + 600 + 260 @@ -31,43 +31,109 @@ Please wait... - - + + + + + + + + 0 + 0 + + + + + 0 + 15 + + + + Global Task Status... + + + + + + + Global Status Details... + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + true + + + 0 + 24 + + 24 - - + + - + 0 0 - - Skip + + + 0 + 100 + + + + QFrame::StyledPanel + + Qt::ScrollBarAsNeeded + + + QAbstractScrollArea::AdjustToContents + + + true + + + + + 0 + 0 + 584 + 146 + + + + + 2 + + + - - - - - + + - + 0 0 - Global Task Status... + Skip diff --git a/launcher/ui/widgets/SubTaskProgressBar.ui b/launcher/ui/widgets/SubTaskProgressBar.ui index 966fdb88..ceae5e26 100644 --- a/launcher/ui/widgets/SubTaskProgressBar.ui +++ b/launcher/ui/widgets/SubTaskProgressBar.ui @@ -6,12 +6,12 @@ 0 0 - 265 - 65 + 597 + 61 - + 0 0 @@ -20,29 +20,45 @@ Form + + 0 + - + 0 0 + + + 8 + + Sub Task Status... + + true + - + 0 0 + + + 8 + + Status Details @@ -55,6 +71,11 @@ + + + 8 + + 24 diff --git a/tests/Task_test.cpp b/tests/Task_test.cpp index 678382ba..dabe5da2 100644 --- a/tests/Task_test.cpp +++ b/tests/Task_test.cpp @@ -69,8 +69,9 @@ class BigConcurrentTaskThread : public QThread { auto sub_tasks = new BasicTask::Ptr[s_num_tasks]; for (unsigned i = 0; i < s_num_tasks; i++) { - sub_tasks[i] = makeShared(false); - big_task.addTask(sub_tasks[i]); + auto sub_task = makeShared(false); + sub_tasks[i] = sub_task; + big_task.addTask(sub_task); } big_task.run(); @@ -99,7 +100,7 @@ class TaskTest : public QObject { t.setStatus(status); QCOMPARE(t.getStatus(), status); - QCOMPARE(t.getStepProgress().isEmpty(), QList{}.isEmpty()); + QCOMPARE(t.getStepProgress().isEmpty(), TaskStepProgressList{}.isEmpty()); } void test_SetStatus_MultiStep(){ @@ -111,7 +112,7 @@ class TaskTest : public QObject { QCOMPARE(t.getStatus(), status); // Even though it is multi step, it does not override the getStepStatus method, // so it should remain the same. - QCOMPARE(t.getStepProgress().isEmpty(), QList{}.isEmpty()); + QCOMPARE(t.getStepProgress().isEmpty(), TaskStepProgressList{}.isEmpty()); } void test_SetProgress(){ -- cgit From f1028fa66d556b024765ba4e21ac76d4510a3e3e Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Fri, 31 Mar 2023 12:29:59 -0700 Subject: fix: properly map progress range - doument PCRE used for URL compacting Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- launcher/net/Download.cpp | 20 +++++++++--- launcher/tasks/ConcurrentTask.cpp | 7 +---- launcher/ui/dialogs/ProgressDialog.cpp | 56 ++++++++++++++++++---------------- launcher/ui/dialogs/ProgressDialog.ui | 21 ++++++------- 4 files changed, 55 insertions(+), 49 deletions(-) (limited to 'launcher/ui/dialogs') diff --git a/launcher/net/Download.cpp b/launcher/net/Download.cpp index a4c3ebfc..3eef9117 100644 --- a/launcher/net/Download.cpp +++ b/launcher/net/Download.cpp @@ -61,13 +61,23 @@ QString truncateUrlHumanFriendly(QUrl &url, int max_len, bool hard_limit = false if (str_url.length() <= max_len) return str_url; - QRegularExpression re(R"(^([\w]+:\/\/)([\w._-]+\/)([\w._-]+\/).*(\/[^]+[^]+)$)"); + /* this is a PCRE regular expression that splits a URL (given by the display rules above) into 5 capture groups + * the scheme (ie https://) is group 1 + * the host (with trailing /) is group 2 + * the first part of the path (with trailing /) is group 3 + * the last part of the path (with leading /) is group 5 + * the remainder of the URL is in the .* and in group 4 + * + * See: https://regex101.com/r/inHkek/1 + * for an interactive breakdown + */ + QRegularExpression re(R"(^([\w]+:\/\/)([\w._-]+\/)([\w._-]+\/)(.*)(\/[^]+[^]+)$)"); auto url_compact = QString(str_url); - url_compact.replace(re, "\\1\\2\\3...\\4"); + url_compact.replace(re, "\\1\\2\\3...\\5"); if (url_compact.length() >= max_len) { - auto url_compact = QString(str_url); - url_compact.replace(re, "\\1\\2...\\4"); + url_compact = QString(str_url); + url_compact.replace(re, "\\1\\2...\\5"); } @@ -120,7 +130,7 @@ void Download::addValidator(Validator* v) void Download::executeTask() { - setStatus(tr("Downloading %1").arg(truncateUrlHumanFriendly(m_url, 60))); + setStatus(tr("Downloading %1").arg(truncateUrlHumanFriendly(m_url, 100))); if (getState() == Task::State::AbortedByUser) { qCWarning(DownloadLogC) << getUid().toString() << "Attempt to start an aborted Download:" << m_url.toString(); diff --git a/launcher/tasks/ConcurrentTask.cpp b/launcher/tasks/ConcurrentTask.cpp index fde7d0ad..41c405db 100644 --- a/launcher/tasks/ConcurrentTask.cpp +++ b/launcher/tasks/ConcurrentTask.cpp @@ -28,12 +28,7 @@ void ConcurrentTask::addTask(Task::Ptr task) void ConcurrentTask::executeTask() { - // Start the least amount of tasks needed, but at least one - // int num_starts = qMax(1, qMin(m_total_max_size, m_queue.size())); - // for (int i = 0; i < num_starts; i++) { - // QMetaObject::invokeMethod(this, &ConcurrentTask::startNext, Qt::QueuedConnection); - // } - // Start One task, startNext hadles starting the up to the m_total_max_size + // Start One task, startNext hadels starting the up to the m_total_max_size // while tracking the number currently being done QMetaObject::invokeMethod(this, &ConcurrentTask::startNext, Qt::QueuedConnection); } diff --git a/launcher/ui/dialogs/ProgressDialog.cpp b/launcher/ui/dialogs/ProgressDialog.cpp index f7a3a862..7ab766e4 100644 --- a/launcher/ui/dialogs/ProgressDialog.cpp +++ b/launcher/ui/dialogs/ProgressDialog.cpp @@ -45,21 +45,18 @@ #include "ui/widgets/SubTaskProgressBar.h" -template -int map_int_range(T value) +// map a value in a numaric range of an arbatray type to between 0 and INT_MAX +// for getting the best percision out of the qt progress bar +template::value, T>::type> +std::tuple map_int_zero_max(T current, T range_max, T range_min) { - // auto type_min = std::numeric_limits::min(); - auto type_min = 0; - auto type_max = std::numeric_limits::max(); + int int_max = std::numeric_limits::max(); - // auto int_min = std::numeric_limits::min(); - auto int_min = 0; - auto int_max = std::numeric_limits::max(); + auto type_range = range_max - range_min; + double percentage = static_cast(current - range_min) / static_cast(type_range); + int mapped_current = percentage * int_max; - auto type_range = type_max - type_min; - auto int_range = int_max - int_min; - - return static_cast((value - type_min) * int_range / type_range + int_min); + return {mapped_current, int_max}; } @@ -100,14 +97,21 @@ void ProgressDialog::updateSize() { QSize lastSize = this->size(); QSize qSize = QSize(480, minimumSizeHint().height()); - resize(qSize); - setFixedSize(qSize); - // keep the dialog in the center after a resize - if (lastSize != qSize) + + // if the current window is too small + if ((lastSize != qSize) && (lastSize.height() < qSize.height())) + { + resize(qSize); + + // keep the dialog in the center after a resize this->move( this->parentWidget()->x() + (this->parentWidget()->width() - this->width()) / 2, this->parentWidget()->y() + (this->parentWidget()->height() - this->height()) / 2 ); + } + + setMinimumSize(qSize); + } int ProgressDialog::execWithTask(Task* task) @@ -147,9 +151,6 @@ int ProgressDialog::execWithTask(Task* task) changeProgress(task->getProgress(), task->getTotalProgress()); } - // auto size_hint = ui->verticalLayout->sizeHint(); - // resize(size_hint.width(), size_hint.height()); - return QDialog::exec(); } @@ -209,26 +210,31 @@ void ProgressDialog::addTaskProgress(TaskStepProgress* progress) { SubTaskProgressBar* task_bar = new SubTaskProgressBar(this); taskProgress.insert(progress->uid, task_bar); - ui->taskProgressLayout->insertWidget(0, task_bar); + ui->taskProgressLayout->addWidget(task_bar); } void ProgressDialog::changeStepProgress(TaskStepProgressList task_progress) { m_is_multi_step = true; - ui->taskProgressScrollArea->setHidden(false); + if(ui->taskProgressScrollArea->isHidden()) { + ui->taskProgressScrollArea->setHidden(false); + updateSize(); + } for (auto tp : task_progress) { if (!taskProgress.contains(tp->uid)) addTaskProgress(tp.get()); auto task_bar = taskProgress.value(tp->uid); - if (tp->total < 0) { + + auto const [mapped_current, mapped_total] = map_int_zero_max(tp->current, tp->total, 0); + if (tp->total <= 0) { task_bar->setRange(0, 0); } else { - task_bar->setRange(0, map_int_range(tp->total)); + task_bar->setRange(0, mapped_total); } - task_bar->setValue(map_int_range(tp->current)); + task_bar->setValue(mapped_current); task_bar->setStatus(tp->status); task_bar->setDetails(tp->details); @@ -237,8 +243,6 @@ void ProgressDialog::changeStepProgress(TaskStepProgressList task_progress) } } - - updateSize(); } void ProgressDialog::changeProgress(qint64 current, qint64 total) diff --git a/launcher/ui/dialogs/ProgressDialog.ui b/launcher/ui/dialogs/ProgressDialog.ui index 47597689..a4d08124 100644 --- a/launcher/ui/dialogs/ProgressDialog.ui +++ b/launcher/ui/dialogs/ProgressDialog.ui @@ -6,8 +6,8 @@ 0 0 - 600 - 260 + 480 + 210 @@ -18,19 +18,16 @@ - 600 - 260 - - - - - 600 - 16777215 + 480 + 210 Please wait... + + true + @@ -112,8 +109,8 @@ 0 0 - 584 - 146 + 464 + 96 -- cgit From b6452215c16f6b1ee45fea746f9498767e48d049 Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Fri, 31 Mar 2023 19:25:01 -0700 Subject: feat: add `details` signal to `Task` feat: add details to mod pack downloading feat: add logging rule sloading form `ligging.ini at data path root feat: add `launcher.task` `launcher.task.net` and `launcher.task.net.[down|up]load` logging categories fix: add new subtask progress to the end of the lay out not the beginning (cuts down on flickering) Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- launcher/Application.cpp | 19 +++++++++ launcher/CMakeLists.txt | 33 +++++++++++++++ launcher/InstanceImportTask.cpp | 2 + launcher/InstanceList.cpp | 1 + launcher/java/JavaInstallList.cpp | 1 + launcher/launch/steps/Update.cpp | 1 + launcher/minecraft/MinecraftUpdate.cpp | 2 + .../modplatform/atlauncher/ATLPackInstallTask.cpp | 3 +- .../flame/FlameInstanceCreationTask.cpp | 7 +++- .../modrinth/ModrinthInstanceCreationTask.cpp | 7 +++- launcher/net/Download.cpp | 47 +++++++++++----------- launcher/net/Download.h | 3 -- launcher/net/FileSink.cpp | 10 +++-- launcher/net/HttpMetaCache.cpp | 16 ++++---- launcher/net/MetaCacheSink.cpp | 10 +++-- launcher/net/PasteUpload.cpp | 22 +++++----- launcher/net/Upload.cpp | 38 ++++++++--------- launcher/net/logging.cpp | 24 +++++++++++ launcher/net/logging.h | 26 ++++++++++++ launcher/tasks/ConcurrentTask.cpp | 11 ++++- launcher/tasks/ConcurrentTask.h | 1 + launcher/tasks/Task.cpp | 42 ++++++++++++------- launcher/tasks/Task.h | 8 +++- launcher/ui/dialogs/ProgressDialog.cpp | 2 +- launcher/ui/widgets/ProgressWidget.cpp | 1 + launcher/ui/widgets/SubTaskProgressBar.ui | 7 +++- 26 files changed, 249 insertions(+), 95 deletions(-) create mode 100644 launcher/net/logging.cpp create mode 100644 launcher/net/logging.h (limited to 'launcher/ui/dialogs') diff --git a/launcher/Application.cpp b/launcher/Application.cpp index a7c97aa7..c8855cbc 100644 --- a/launcher/Application.cpp +++ b/launcher/Application.cpp @@ -46,6 +46,7 @@ #include "net/PasteUpload.h" #include "pathmatcher/MultiMatcher.h" #include "pathmatcher/SimplePrefixMatcher.h" +#include "settings/INIFile.h" #include "ui/MainWindow.h" #include "ui/InstanceWindow.h" @@ -410,6 +411,24 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv) " " "|" " " "%{if-category}[%{category}]: %{endif}" "%{message}"); + + if(QFile::exists("logging.ini")) { + // load and set logging rules + qDebug() << "Loading logging rules from:" << QString("%1/logging.ini").arg(dataPath); + INIFile loggingRules; + bool rulesLoaded = loggingRules.loadFile(QString("logging.ini")); + if (rulesLoaded) { + QStringList rules; + qDebug() << "Setting log rules:"; + for (auto it = loggingRules.begin(); it != loggingRules.end(); ++it) { + auto rule = it.key() + "=" + it.value().toString(); + rules.append(rule); + qDebug() << " " << rule; + } + auto rules_str = rules.join("\n"); + QLoggingCategory::setFilterRules(rules_str); + } + } qDebug() << "<> Log initialized."; } diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index 24330adf..e49a4562 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -123,6 +123,8 @@ set(NET_SOURCES net/HttpMetaCache.h net/MetaCacheSink.cpp net/MetaCacheSink.h + net/logging.h + net/logging.cpp net/NetAction.h net/NetJob.cpp net/NetJob.h @@ -563,6 +565,37 @@ ecm_qt_declare_logging_category(CORE_SOURCES EXPORT "${Launcher_Name}" ) +ecm_qt_export_logging_category( + IDENTIFIER taskLogC + CATEGORY_NAME "launcher.task" + DEFAULT_SEVERITY Debug + DESCRIPTION "Task actions" + EXPORT "${Launcher_Name}" +) + +ecm_qt_export_logging_category( + IDENTIFIER taskNetLogC + CATEGORY_NAME "launcher.task.net" + DEFAULT_SEVERITY Debug + DESCRIPTION "task network action" + EXPORT "${Launcher_Name}" +) + +ecm_qt_export_logging_category( + IDENTIFIER taskDownloadLogC + CATEGORY_NAME "launcher.task.net.download" + DEFAULT_SEVERITY Debug + DESCRIPTION "task network download actions" + EXPORT "${Launcher_Name}" +) +ecm_qt_export_logging_category( + IDENTIFIER taskUploadLogC + CATEGORY_NAME "launcher.task.net.upload" + DEFAULT_SEVERITY Debug + DESCRIPTION "task network upload actions" + EXPORT "${Launcher_Name}" +) + if(KDE_INSTALL_LOGGINGCATEGORIESDIR) # only install if there is a standard path for this ecm_qt_install_logging_categories( EXPORT "${Launcher_Name}" diff --git a/launcher/InstanceImportTask.cpp b/launcher/InstanceImportTask.cpp index c196396d..8a48873e 100644 --- a/launcher/InstanceImportTask.cpp +++ b/launcher/InstanceImportTask.cpp @@ -294,6 +294,7 @@ void InstanceImportTask::processFlame() connect(inst_creation_task, &Task::progress, this, &InstanceImportTask::setProgress); connect(inst_creation_task, &Task::stepProgress, this, &InstanceImportTask::propogateStepProgress); connect(inst_creation_task, &Task::status, this, &InstanceImportTask::setStatus); + connect(inst_creation_task, &Task::details, this, &InstanceImportTask::setDetails); connect(inst_creation_task, &Task::finished, inst_creation_task, &InstanceCreationTask::deleteLater); connect(this, &Task::aborted, inst_creation_task, &InstanceCreationTask::abort); @@ -386,6 +387,7 @@ void InstanceImportTask::processModrinth() connect(inst_creation_task, &Task::progress, this, &InstanceImportTask::setProgress); connect(inst_creation_task, &Task::stepProgress, this, &InstanceImportTask::propogateStepProgress); connect(inst_creation_task, &Task::status, this, &InstanceImportTask::setStatus); + connect(inst_creation_task, &Task::details, this, &InstanceImportTask::setDetails); connect(inst_creation_task, &Task::finished, inst_creation_task, &InstanceCreationTask::deleteLater); connect(this, &Task::aborted, inst_creation_task, &InstanceCreationTask::abort); diff --git a/launcher/InstanceList.cpp b/launcher/InstanceList.cpp index dbc891ff..5f98a184 100644 --- a/launcher/InstanceList.cpp +++ b/launcher/InstanceList.cpp @@ -787,6 +787,7 @@ class InstanceStaging : public Task { connect(child, &Task::aborted, this, &InstanceStaging::childAborted); connect(child, &Task::abortStatusChanged, this, &InstanceStaging::setAbortable); connect(child, &Task::status, this, &InstanceStaging::setStatus); + connect(child, &Task::details, this, &InstanceStaging::setDetails); connect(child, &Task::progress, this, &InstanceStaging::setProgress); connect(child, &Task::stepProgress, this, &InstanceStaging::propogateStepProgress); connect(&m_backoffTimer, &QTimer::timeout, this, &InstanceStaging::childSucceded); diff --git a/launcher/java/JavaInstallList.cpp b/launcher/java/JavaInstallList.cpp index b29af857..5f133622 100644 --- a/launcher/java/JavaInstallList.cpp +++ b/launcher/java/JavaInstallList.cpp @@ -170,6 +170,7 @@ void JavaListLoadTask::executeTask() m_job.reset(new JavaCheckerJob("Java detection")); connect(m_job.get(), &Task::finished, this, &JavaListLoadTask::javaCheckerFinished); connect(m_job.get(), &Task::progress, this, &Task::setProgress); + // stepProgress? qDebug() << "Probing the following Java paths: "; int id = 0; diff --git a/launcher/launch/steps/Update.cpp b/launcher/launch/steps/Update.cpp index 1640d115..c8e576a5 100644 --- a/launcher/launch/steps/Update.cpp +++ b/launcher/launch/steps/Update.cpp @@ -30,6 +30,7 @@ void Update::executeTask() connect(m_updateTask.get(), &Task::progress, this, &Update::setProgress); connect(m_updateTask.get(), &Task::stepProgress, this, &Update::propogateStepProgress); connect(m_updateTask.get(), &Task::status, this, &Update::setStatus); + connect(m_updateTask.get(), &Task::details, this, &Update::setDetails); emit progressReportingRequest(); return; } diff --git a/launcher/minecraft/MinecraftUpdate.cpp b/launcher/minecraft/MinecraftUpdate.cpp index 3ce808f8..35430bb0 100644 --- a/launcher/minecraft/MinecraftUpdate.cpp +++ b/launcher/minecraft/MinecraftUpdate.cpp @@ -102,6 +102,7 @@ void MinecraftUpdate::next() disconnect(task.get(), &Task::progress, this, &MinecraftUpdate::progress); disconnect(task.get(), &Task::stepProgress, this, &MinecraftUpdate::propogateStepProgress); disconnect(task.get(), &Task::status, this, &MinecraftUpdate::setStatus); + disconnect(task.get(), &Task::details, this, &MinecraftUpdate::setDetails); } if(m_currentTask == m_tasks.size()) { @@ -121,6 +122,7 @@ void MinecraftUpdate::next() connect(task.get(), &Task::progress, this, &MinecraftUpdate::progress); connect(task.get(), &Task::stepProgress, this, &MinecraftUpdate::propogateStepProgress); connect(task.get(), &Task::status, this, &MinecraftUpdate::setStatus); + connect(task.get(), &Task::details, this, &MinecraftUpdate::setDetails); // if the task is already running, do not start it again if(!task->isRunning()) { diff --git a/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp b/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp index 28026732..d130914f 100644 --- a/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp +++ b/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp @@ -846,7 +846,8 @@ void PackInstallTask::downloadMods() emitFailed(reason); }); connect(jobPtr.get(), &NetJob::progress, [&](qint64 current, qint64 total) - { + { + setDetails(tr("%1 out of %2 complete").arg(current).arg(total)); abortable = true; setProgress(current, total); }); diff --git a/launcher/modplatform/flame/FlameInstanceCreationTask.cpp b/launcher/modplatform/flame/FlameInstanceCreationTask.cpp index 3cb6b61a..86fd2ab4 100644 --- a/launcher/modplatform/flame/FlameInstanceCreationTask.cpp +++ b/launcher/modplatform/flame/FlameInstanceCreationTask.cpp @@ -453,7 +453,7 @@ void FlameCreationTask::idResolverSucceeded(QEventLoop& loop) void FlameCreationTask::setupDownloadJob(QEventLoop& loop) { - m_files_job.reset(new NetJob(tr("Mod download"), APPLICATION->network())); + m_files_job.reset(new NetJob(tr("Mod Download Flame"), APPLICATION->network())); for (const auto& result : m_mod_id_resolver->getResults().files) { QString filename = result.fileName; if (!result.required) { @@ -497,7 +497,10 @@ void FlameCreationTask::setupDownloadJob(QEventLoop& loop) m_files_job.reset(); setError(reason); }); - connect(m_files_job.get(), &NetJob::progress, this, &FlameCreationTask::setProgress); + connect(m_files_job.get(), &NetJob::progress, this, [this](qint64 current, qint64 total){ + setDetails(tr("%1 out of %2 complete").arg(current).arg(total)); + setProgress(current, total); + }); connect(m_files_job.get(), &NetJob::stepProgress, this, &FlameCreationTask::propogateStepProgress); connect(m_files_job.get(), &NetJob::finished, &loop, &QEventLoop::quit); diff --git a/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp b/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp index 2fb656ea..bb8227aa 100644 --- a/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp +++ b/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp @@ -224,7 +224,7 @@ bool ModrinthCreationTask::createInstance() instance.setName(name()); instance.saveNow(); - m_files_job.reset(new NetJob(tr("Mod download"), APPLICATION->network())); + m_files_job.reset(new NetJob(tr("Mod Download Modrinth"), APPLICATION->network())); auto root_modpack_path = FS::PathCombine(m_stagingPath, ".minecraft"); auto root_modpack_url = QUrl::fromLocalFile(root_modpack_path); @@ -263,7 +263,10 @@ bool ModrinthCreationTask::createInstance() setError(reason); }); connect(m_files_job.get(), &NetJob::finished, &loop, &QEventLoop::quit); - connect(m_files_job.get(), &NetJob::progress, [&](qint64 current, qint64 total) { setProgress(current, total); }); + connect(m_files_job.get(), &NetJob::progress, [&](qint64 current, qint64 total) { + setDetails(tr("%1 out of %2 complete").arg(current).arg(total)); + setProgress(current, total); + }); connect(m_files_job.get(), &NetJob::stepProgress, this, &ModrinthCreationTask::propogateStepProgress); setStatus(tr("Downloading mods...")); diff --git a/launcher/net/Download.cpp b/launcher/net/Download.cpp index 3eef9117..86e4bd97 100644 --- a/launcher/net/Download.cpp +++ b/launcher/net/Download.cpp @@ -4,6 +4,7 @@ * Copyright (c) 2022 flowln * Copyright (C) 2022 Sefa Eyeoglu * Copyright (C) 2023 TheKodeToad + * Copyright (C) 2023 Rachel Powers <508861+Ryex@users.noreply.github.com> * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -50,7 +51,7 @@ #include "BuildConfig.h" #include "Application.h" -Q_LOGGING_CATEGORY(DownloadLogC, "Task.Net.Download") +#include "logging.h" namespace Net { @@ -133,7 +134,7 @@ void Download::executeTask() setStatus(tr("Downloading %1").arg(truncateUrlHumanFriendly(m_url, 100))); if (getState() == Task::State::AbortedByUser) { - qCWarning(DownloadLogC) << getUid().toString() << "Attempt to start an aborted Download:" << m_url.toString(); + qCWarning(taskDownloadLogC) << getUid().toString() << "Attempt to start an aborted Download:" << m_url.toString(); emitAborted(); return; } @@ -143,10 +144,10 @@ void Download::executeTask() switch (m_state) { case State::Succeeded: emit succeeded(); - qCDebug(DownloadLogC) << getUid().toString() << "Download cache hit " << m_url.toString(); + qCDebug(taskDownloadLogC) << getUid().toString() << "Download cache hit " << m_url.toString(); return; case State::Running: - qCDebug(DownloadLogC) << getUid().toString() << "Downloading " << m_url.toString(); + qCDebug(taskDownloadLogC) << getUid().toString() << "Downloading " << m_url.toString(); break; case State::Inactive: case State::Failed: @@ -192,9 +193,9 @@ void Download::downloadProgress(qint64 bytesReceived, qint64 bytesTotal) auto elapsed_ms = std::chrono::duration_cast(elapsed).count(); auto bytes_recived_since = bytesReceived - m_last_progress_bytes; if (elapsed_ms > 0) { - m_details = humanReadableFileSize(bytes_recived_since / elapsed_ms * 1000) + "/s"; + setDetails(humanReadableFileSize(bytes_recived_since / elapsed_ms * 1000) + "/s"); } else { - m_details = "0 b/s"; + setDetails("0 b/s"); } setProgress(bytesReceived, bytesTotal); @@ -203,7 +204,7 @@ void Download::downloadProgress(qint64 bytesReceived, qint64 bytesTotal) void Download::downloadError(QNetworkReply::NetworkError error) { if (error == QNetworkReply::OperationCanceledError) { - qCCritical(DownloadLogC) << getUid().toString() << "Aborted " << m_url.toString(); + qCCritical(taskDownloadLogC) << getUid().toString() << "Aborted " << m_url.toString(); m_state = State::AbortedByUser; } else { if (m_options & Option::AcceptLocalFiles) { @@ -213,7 +214,7 @@ void Download::downloadError(QNetworkReply::NetworkError error) } } // error happened during download. - qCCritical(DownloadLogC) << getUid().toString() << "Failed " << m_url.toString() << " with reason " << error; + qCCritical(taskDownloadLogC) << getUid().toString() << "Failed " << m_url.toString() << " with reason " << error; m_state = State::Failed; } } @@ -222,9 +223,9 @@ void Download::sslErrors(const QList& errors) { int i = 1; for (auto error : errors) { - qCCritical(DownloadLogC) << getUid().toString() << "Download" << m_url.toString() << "SSL Error #" << i << " : " << error.errorString(); + qCCritical(taskDownloadLogC) << getUid().toString() << "Download" << m_url.toString() << "SSL Error #" << i << " : " << error.errorString(); auto cert = error.certificate(); - qCCritical(DownloadLogC) << getUid().toString() << "Certificate in question:\n" << cert.toText(); + qCCritical(taskDownloadLogC) << getUid().toString() << "Certificate in question:\n" << cert.toText(); i++; } } @@ -267,17 +268,17 @@ auto Download::handleRedirect() -> bool */ redirect = QUrl(redirectStr, QUrl::TolerantMode); if (!redirect.isValid()) { - qCWarning(DownloadLogC) << getUid().toString() << "Failed to parse redirect URL:" << redirectStr; + qCWarning(taskDownloadLogC) << getUid().toString() << "Failed to parse redirect URL:" << redirectStr; downloadError(QNetworkReply::ProtocolFailure); return false; } - qCDebug(DownloadLogC) << getUid().toString() << "Fixed location header:" << redirect; + qCDebug(taskDownloadLogC) << getUid().toString() << "Fixed location header:" << redirect; } else { - qCDebug(DownloadLogC) << getUid().toString() << "Location header:" << redirect; + qCDebug(taskDownloadLogC) << getUid().toString() << "Location header:" << redirect; } m_url = QUrl(redirect.toString()); - qCDebug(DownloadLogC) << getUid().toString() << "Following redirect to " << m_url.toString(); + qCDebug(taskDownloadLogC) << getUid().toString() << "Following redirect to " << m_url.toString(); startAction(m_network); return true; @@ -287,26 +288,26 @@ void Download::downloadFinished() { // handle HTTP redirection first if (handleRedirect()) { - qCDebug(DownloadLogC) << getUid().toString() << "Download redirected:" << m_url.toString(); + qCDebug(taskDownloadLogC) << getUid().toString() << "Download redirected:" << m_url.toString(); return; } // if the download failed before this point ... if (m_state == State::Succeeded) // pretend to succeed so we continue processing :) { - qCDebug(DownloadLogC) << getUid().toString() << "Download failed but we are allowed to proceed:" << m_url.toString(); + qCDebug(taskDownloadLogC) << getUid().toString() << "Download failed but we are allowed to proceed:" << m_url.toString(); m_sink->abort(); m_reply.reset(); emit succeeded(); return; } else if (m_state == State::Failed) { - qCDebug(DownloadLogC) << getUid().toString() << "Download failed in previous step:" << m_url.toString(); + qCDebug(taskDownloadLogC) << getUid().toString() << "Download failed in previous step:" << m_url.toString(); m_sink->abort(); m_reply.reset(); emit failed(""); return; } else if (m_state == State::AbortedByUser) { - qCDebug(DownloadLogC) << getUid().toString() << "Download aborted in previous step:" << m_url.toString(); + qCDebug(taskDownloadLogC) << getUid().toString() << "Download aborted in previous step:" << m_url.toString(); m_sink->abort(); m_reply.reset(); emit aborted(); @@ -316,14 +317,14 @@ void Download::downloadFinished() // make sure we got all the remaining data, if any auto data = m_reply->readAll(); if (data.size()) { - qCDebug(DownloadLogC) << getUid().toString() << "Writing extra" << data.size() << "bytes"; + qCDebug(taskDownloadLogC) << getUid().toString() << "Writing extra" << data.size() << "bytes"; m_state = m_sink->write(data); } // otherwise, finalize the whole graph m_state = m_sink->finalize(*m_reply.get()); if (m_state != State::Succeeded) { - qCDebug(DownloadLogC) << getUid().toString() << "Download failed to finalize:" << m_url.toString(); + qCDebug(taskDownloadLogC) << getUid().toString() << "Download failed to finalize:" << m_url.toString(); m_sink->abort(); m_reply.reset(); emit failed(""); @@ -331,7 +332,7 @@ void Download::downloadFinished() } m_reply.reset(); - qCDebug(DownloadLogC) << getUid().toString() << "Download succeeded:" << m_url.toString(); + qCDebug(taskDownloadLogC) << getUid().toString() << "Download succeeded:" << m_url.toString(); emit succeeded(); } @@ -341,11 +342,11 @@ void Download::downloadReadyRead() auto data = m_reply->readAll(); m_state = m_sink->write(data); if (m_state == State::Failed) { - qCCritical(DownloadLogC) << getUid().toString() << "Failed to process response chunk"; + qCCritical(taskDownloadLogC) << getUid().toString() << "Failed to process response chunk"; } // qDebug() << "Download" << m_url.toString() << "gained" << data.size() << "bytes"; } else { - qCCritical(DownloadLogC) << getUid().toString() << "Cannot write download data! illegal status " << m_status; + qCCritical(taskDownloadLogC) << getUid().toString() << "Cannot write download data! illegal status " << m_status; } } diff --git a/launcher/net/Download.h b/launcher/net/Download.h index cbee0d03..ae2b034c 100644 --- a/launcher/net/Download.h +++ b/launcher/net/Download.h @@ -66,7 +66,6 @@ class Download : public NetAction { void addValidator(Validator* v); auto abort() -> bool override; auto canAbort() const -> bool override { return true; }; - auto getDetails() const -> QString override {return m_details; }; private: auto handleRedirect() -> bool; @@ -88,8 +87,6 @@ class Download : public NetAction { std::chrono::steady_clock m_clock; std::chrono::time_point m_last_progress_time; qint64 m_last_progress_bytes; - - QString m_details; }; } // namespace Net diff --git a/launcher/net/FileSink.cpp b/launcher/net/FileSink.cpp index ba0caf6c..3c2948d4 100644 --- a/launcher/net/FileSink.cpp +++ b/launcher/net/FileSink.cpp @@ -37,6 +37,8 @@ #include "FileSystem.h" +#include "logging.h" + namespace Net { Task::State FileSink::init(QNetworkRequest& request) @@ -48,14 +50,14 @@ Task::State FileSink::init(QNetworkRequest& request) // create a new save file and open it for writing if (!FS::ensureFilePathExists(m_filename)) { - qCritical() << "Could not create folder for " + m_filename; + qCCritical(taskNetLogC) << "Could not create folder for " + m_filename; return Task::State::Failed; } wroteAnyData = false; m_output_file.reset(new QSaveFile(m_filename)); if (!m_output_file->open(QIODevice::WriteOnly)) { - qCritical() << "Could not open " + m_filename + " for writing"; + qCCritical(taskNetLogC) << "Could not open " + m_filename + " for writing"; return Task::State::Failed; } @@ -67,7 +69,7 @@ Task::State FileSink::init(QNetworkRequest& request) Task::State FileSink::write(QByteArray& data) { if (!writeAllValidators(data) || m_output_file->write(data) != data.size()) { - qCritical() << "Failed writing into " + m_filename; + qCCritical(taskNetLogC) << "Failed writing into " + m_filename; m_output_file->cancelWriting(); m_output_file.reset(); wroteAnyData = false; @@ -106,7 +108,7 @@ Task::State FileSink::finalize(QNetworkReply& reply) // nothing went wrong... if (!m_output_file->commit()) { - qCritical() << "Failed to commit changes to " << m_filename; + qCCritical(taskNetLogC) << "Failed to commit changes to " << m_filename; m_output_file->cancelWriting(); return Task::State::Failed; } diff --git a/launcher/net/HttpMetaCache.cpp b/launcher/net/HttpMetaCache.cpp index 0d7ca769..855211f7 100644 --- a/launcher/net/HttpMetaCache.cpp +++ b/launcher/net/HttpMetaCache.cpp @@ -44,6 +44,8 @@ #include +#include "logging.h" + auto MetaEntry::getFullPath() -> QString { // FIXME: make local? @@ -124,7 +126,7 @@ auto HttpMetaCache::resolveEntry(QString base, QString resource_path, QString ex // Get rid of old entries, to prevent cache problems auto current_time = QDateTime::currentSecsSinceEpoch(); if (entry->isExpired(current_time - ( file_last_changed / 1000 ))) { - qWarning() << "Removing cache entry because of old age!"; + qCWarning(taskNetLogC) << "[HttpMetaCache]" << "Removing cache entry because of old age!"; selected_base.entry_list.remove(resource_path); return staleEntry(base, resource_path); } @@ -137,12 +139,12 @@ auto HttpMetaCache::resolveEntry(QString base, QString resource_path, QString ex auto HttpMetaCache::updateEntry(MetaEntryPtr stale_entry) -> bool { if (!m_entries.contains(stale_entry->m_baseId)) { - qCritical() << "Cannot add entry with unknown base: " << stale_entry->m_baseId.toLocal8Bit(); + qCCritical(taskNetLogC) << "[HttpMetaCache]" << "Cannot add entry with unknown base: " << stale_entry->m_baseId.toLocal8Bit(); return false; } if (stale_entry->m_stale) { - qCritical() << "Cannot add stale entry: " << stale_entry->getFullPath().toLocal8Bit(); + qCCritical(taskNetLogC) << "[HttpMetaCache]" << "Cannot add stale entry: " << stale_entry->getFullPath().toLocal8Bit(); return false; } @@ -166,10 +168,10 @@ void HttpMetaCache::evictAll() { for (QString& base : m_entries.keys()) { EntryMap& map = m_entries[base]; - qDebug() << "Evicting base" << base; + qCDebug(taskNetLogC) << "[HttpMetaCache]" << "Evicting base" << base; for (MetaEntryPtr entry : map.entry_list) { if (!evictEntry(entry)) - qWarning() << "Unexpected missing cache entry" << entry->m_basePath; + qCWarning(taskNetLogC) << "[HttpMetaCache]" << "Unexpected missing cache entry" << entry->m_basePath; } } } @@ -267,7 +269,7 @@ void HttpMetaCache::SaveNow() if (m_index_file.isNull()) return; - qDebug() << "[HttpMetaCache]" << "Saving metacache with" << m_entries.size() << "entries"; + qCDebug(taskNetLogC) << "[HttpMetaCache]" << "Saving metacache with" << m_entries.size() << "entries"; QJsonObject toplevel; Json::writeString(toplevel, "version", "1"); @@ -302,6 +304,6 @@ void HttpMetaCache::SaveNow() try { Json::write(toplevel, m_index_file); } catch (const Exception& e) { - qWarning() << e.what(); + qCWarning(taskNetLogC) << "[HttpMetaCache]" << e.what(); } } diff --git a/launcher/net/MetaCacheSink.cpp b/launcher/net/MetaCacheSink.cpp index c730fdbf..46bfe37d 100644 --- a/launcher/net/MetaCacheSink.cpp +++ b/launcher/net/MetaCacheSink.cpp @@ -39,6 +39,8 @@ #include #include "Application.h" +#include "logging.h" + namespace Net { /** Maximum time to hold a cache entry @@ -97,11 +99,11 @@ Task::State MetaCacheSink::finalizeCache(QNetworkReply & reply) { // Cache lifetime if (m_is_eternal) { - qDebug() << "[MetaCache] Adding eternal cache entry:" << m_entry->getFullPath(); + qCDebug(taskNetLogC) << "[MetaCache] Adding eternal cache entry:" << m_entry->getFullPath(); m_entry->makeEternal(true); } else if (reply.hasRawHeader("Cache-Control")) { auto cache_control_header = reply.rawHeader("Cache-Control"); - // qDebug() << "[MetaCache] Parsing 'Cache-Control' header with" << cache_control_header; + // qCDebug(taskNetLogC) << "[MetaCache] Parsing 'Cache-Control' header with" << cache_control_header; QRegularExpression max_age_expr("max-age=([0-9]+)"); qint64 max_age = max_age_expr.match(cache_control_header).captured(1).toLongLong(); @@ -109,7 +111,7 @@ Task::State MetaCacheSink::finalizeCache(QNetworkReply & reply) } else if (reply.hasRawHeader("Expires")) { auto expires_header = reply.rawHeader("Expires"); - // qDebug() << "[MetaCache] Parsing 'Expires' header with" << expires_header; + // qCDebug(taskNetLogC) << "[MetaCache] Parsing 'Expires' header with" << expires_header; qint64 max_age = QDateTime::fromString(expires_header).toSecsSinceEpoch() - QDateTime::currentSecsSinceEpoch(); m_entry->setMaximumAge(max_age); @@ -119,7 +121,7 @@ Task::State MetaCacheSink::finalizeCache(QNetworkReply & reply) if (reply.hasRawHeader("Age")) { auto age_header = reply.rawHeader("Age"); - // qDebug() << "[MetaCache] Parsing 'Age' header with" << age_header; + // qCDebug(taskNetLogC) << "[MetaCache] Parsing 'Age' header with" << age_header; qint64 current_age = age_header.toLongLong(); m_entry->setCurrentAge(current_age); diff --git a/launcher/net/PasteUpload.cpp b/launcher/net/PasteUpload.cpp index d9e785c5..d5df3799 100644 --- a/launcher/net/PasteUpload.cpp +++ b/launcher/net/PasteUpload.cpp @@ -47,6 +47,8 @@ #include #include +#include "logging.h" + std::array PasteUpload::PasteTypes = { {{"0x0.st", "https://0x0.st", ""}, {"hastebin", "https://hst.sh", "/documents"}, @@ -147,7 +149,7 @@ void PasteUpload::executeTask() void PasteUpload::downloadError(QNetworkReply::NetworkError error) { // error happened during download. - qCritical() << "Network error: " << error; + qCCritical(taskUploadLogC) << "Network error: " << error; emitFailed(m_reply->errorString()); } @@ -166,7 +168,7 @@ void PasteUpload::downloadFinished() { QString reasonPhrase = m_reply->attribute(QNetworkRequest::HttpReasonPhraseAttribute).toString(); emitFailed(tr("Error: %1 returned unexpected status code %2 %3").arg(m_uploadUrl).arg(statusCode).arg(reasonPhrase)); - qCritical() << m_uploadUrl << " returned unexpected status code " << statusCode << " with body: " << data; + qCCritical(taskUploadLogC) << m_uploadUrl << " returned unexpected status code " << statusCode << " with body: " << data; m_reply.reset(); return; } @@ -187,7 +189,7 @@ void PasteUpload::downloadFinished() else { emitFailed(tr("Error: %1 returned a malformed response body").arg(m_uploadUrl)); - qCritical() << m_uploadUrl << " returned malformed response body: " << data; + qCCritical(taskUploadLogC) << m_uploadUrl << " returned malformed response body: " << data; return; } break; @@ -206,15 +208,15 @@ void PasteUpload::downloadFinished() { QString error = jsonObj["error"].toString(); emitFailed(tr("Error: %1 returned an error: %2").arg(m_uploadUrl, error)); - qCritical() << m_uploadUrl << " returned error: " << error; - qCritical() << "Response body: " << data; + qCCritical(taskUploadLogC) << m_uploadUrl << " returned error: " << error; + qCCritical(taskUploadLogC) << "Response body: " << data; return; } } else { emitFailed(tr("Error: %1 returned a malformed response body").arg(m_uploadUrl)); - qCritical() << m_uploadUrl << " returned malformed response body: " << data; + qCCritical(taskUploadLogC) << m_uploadUrl << " returned malformed response body: " << data; return; } break; @@ -234,16 +236,16 @@ void PasteUpload::downloadFinished() QString error = jsonObj["error"].toString(); QString message = (jsonObj.contains("message") && jsonObj["message"].isString()) ? jsonObj["message"].toString() : "none"; emitFailed(tr("Error: %1 returned an error code: %2\nError message: %3").arg(m_uploadUrl, error, message)); - qCritical() << m_uploadUrl << " returned error: " << error; - qCritical() << "Error message: " << message; - qCritical() << "Response body: " << data; + qCCritical(taskUploadLogC) << m_uploadUrl << " returned error: " << error; + qCCritical(taskUploadLogC) << "Error message: " << message; + qCCritical(taskUploadLogC) << "Response body: " << data; return; } } else { emitFailed(tr("Error: %1 returned a malformed response body").arg(m_uploadUrl)); - qCritical() << m_uploadUrl << " returned malformed response body: " << data; + qCCritical(taskUploadLogC) << m_uploadUrl << " returned malformed response body: " << data; return; } break; diff --git a/launcher/net/Upload.cpp b/launcher/net/Upload.cpp index ccf43c2d..518e302c 100644 --- a/launcher/net/Upload.cpp +++ b/launcher/net/Upload.cpp @@ -42,6 +42,8 @@ #include "BuildConfig.h" #include "Application.h" +#include "logging.h" + namespace Net { bool Upload::abort() @@ -60,11 +62,11 @@ namespace Net { void Upload::downloadError(QNetworkReply::NetworkError error) { if (error == QNetworkReply::OperationCanceledError) { - qCritical() << "Aborted " << m_url.toString(); + qCCritical(taskUploadLogC) << "Aborted " << m_url.toString(); m_state = State::AbortedByUser; } else { // error happened during download. - qCritical() << "Failed " << m_url.toString() << " with reason " << error; + qCCritical(taskUploadLogC) << "Failed " << m_url.toString() << " with reason " << error; m_state = State::Failed; } } @@ -72,9 +74,9 @@ namespace Net { void Upload::sslErrors(const QList &errors) { int i = 1; for (const auto& error : errors) { - qCritical() << "Upload" << m_url.toString() << "SSL Error #" << i << " : " << error.errorString(); + qCCritical(taskUploadLogC) << "Upload" << m_url.toString() << "SSL Error #" << i << " : " << error.errorString(); auto cert = error.certificate(); - qCritical() << "Certificate in question:\n" << cert.toText(); + qCCritical(taskUploadLogC) << "Certificate in question:\n" << cert.toText(); i++; } } @@ -117,17 +119,17 @@ namespace Net { */ redirect = QUrl(redirectStr, QUrl::TolerantMode); if (!redirect.isValid()) { - qWarning() << "Failed to parse redirect URL:" << redirectStr; + qCWarning(taskUploadLogC) << "Failed to parse redirect URL:" << redirectStr; downloadError(QNetworkReply::ProtocolFailure); return false; } - qDebug() << "Fixed location header:" << redirect; + qCDebug(taskUploadLogC) << "Fixed location header:" << redirect; } else { - qDebug() << "Location header:" << redirect; + qCDebug(taskUploadLogC) << "Location header:" << redirect; } m_url = QUrl(redirect.toString()); - qDebug() << "Following redirect to " << m_url.toString(); + qCDebug(taskUploadLogC) << "Following redirect to " << m_url.toString(); startAction(m_network); return true; } @@ -136,25 +138,25 @@ namespace Net { // handle HTTP redirection first // very unlikely for post requests, still can happen if (handleRedirect()) { - qDebug() << "Upload redirected:" << m_url.toString(); + qCDebug(taskUploadLogC) << "Upload redirected:" << m_url.toString(); return; } // if the download failed before this point ... if (m_state == State::Succeeded) { - qDebug() << "Upload failed but we are allowed to proceed:" << m_url.toString(); + qCDebug(taskUploadLogC) << "Upload failed but we are allowed to proceed:" << m_url.toString(); m_sink->abort(); m_reply.reset(); emit succeeded(); return; } else if (m_state == State::Failed) { - qDebug() << "Upload failed in previous step:" << m_url.toString(); + qCDebug(taskUploadLogC) << "Upload failed in previous step:" << m_url.toString(); m_sink->abort(); m_reply.reset(); emit failed(""); return; } else if (m_state == State::AbortedByUser) { - qDebug() << "Upload aborted in previous step:" << m_url.toString(); + qCDebug(taskUploadLogC) << "Upload aborted in previous step:" << m_url.toString(); m_sink->abort(); m_reply.reset(); emit aborted(); @@ -164,21 +166,21 @@ namespace Net { // make sure we got all the remaining data, if any auto data = m_reply->readAll(); if (data.size()) { - qDebug() << "Writing extra" << data.size() << "bytes"; + qCDebug(taskUploadLogC) << "Writing extra" << data.size() << "bytes"; m_state = m_sink->write(data); } // otherwise, finalize the whole graph m_state = m_sink->finalize(*m_reply.get()); if (m_state != State::Succeeded) { - qDebug() << "Upload failed to finalize:" << m_url.toString(); + qCDebug(taskUploadLogC) << "Upload failed to finalize:" << m_url.toString(); m_sink->abort(); m_reply.reset(); emit failed(""); return; } m_reply.reset(); - qDebug() << "Upload succeeded:" << m_url.toString(); + qCDebug(taskUploadLogC) << "Upload succeeded:" << m_url.toString(); emit succeeded(); } @@ -193,7 +195,7 @@ namespace Net { setStatus(tr("Uploading %1").arg(m_url.toString())); if (m_state == State::AbortedByUser) { - qWarning() << "Attempt to start an aborted Upload:" << m_url.toString(); + qCWarning(taskUploadLogC) << "Attempt to start an aborted Upload:" << m_url.toString(); emit aborted(); return; } @@ -202,10 +204,10 @@ namespace Net { switch (m_state) { case State::Succeeded: emitSucceeded(); - qDebug() << "Upload cache hit " << m_url.toString(); + qCDebug(taskUploadLogC) << "Upload cache hit " << m_url.toString(); return; case State::Running: - qDebug() << "Uploading " << m_url.toString(); + qCDebug(taskUploadLogC) << "Uploading " << m_url.toString(); break; case State::Inactive: case State::Failed: diff --git a/launcher/net/logging.cpp b/launcher/net/logging.cpp new file mode 100644 index 00000000..e5b42bc4 --- /dev/null +++ b/launcher/net/logging.cpp @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (C) 2023 Rachel Powers <508861+Ryex@users.noreply.github.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#include "logging.h" + +Q_LOGGING_CATEGORY(taskNetLogC, "launcher.task.net") +Q_LOGGING_CATEGORY(taskDownloadLogC, "launcher.task.net.download") +Q_LOGGING_CATEGORY(taskUploadLogC, "launcher.task.net.upload") \ No newline at end of file diff --git a/launcher/net/logging.h b/launcher/net/logging.h new file mode 100644 index 00000000..e65e328c --- /dev/null +++ b/launcher/net/logging.h @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (C) 2023 Rachel Powers <508861+Ryex@users.noreply.github.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#pragma once + +#include + +Q_DECLARE_LOGGING_CATEGORY(taskNetLogC) +Q_DECLARE_LOGGING_CATEGORY(taskDownloadLogC) +Q_DECLARE_LOGGING_CATEGORY(taskUploadLogC) \ No newline at end of file diff --git a/launcher/tasks/ConcurrentTask.cpp b/launcher/tasks/ConcurrentTask.cpp index 41c405db..8d4f94ed 100644 --- a/launcher/tasks/ConcurrentTask.cpp +++ b/launcher/tasks/ConcurrentTask.cpp @@ -95,6 +95,7 @@ void ConcurrentTask::startNext() connect(next.get(), &Task::failed, this, [this, next](QString msg) { subTaskFailed(next, msg); }); connect(next.get(), &Task::status, this, [this, next](QString msg){ subTaskStatus(next, msg); }); + connect(next.get(), &Task::details, this, [this, next](QString msg){ subTaskDetails(next, msg); }); connect(next.get(), &Task::stepProgress, this, [this, next](TaskStepProgressList tp){ subTaskStepProgress(next, tp); }); connect(next.get(), &Task::progress, this, [this, next](qint64 current, qint64 total){ subTaskProgress(next, current, total); }); @@ -151,7 +152,14 @@ void ConcurrentTask::subTaskStatus(Task::Ptr task, const QString& msg) auto taskProgress = m_task_progress.value(task->getUid()); taskProgress->status = msg; taskProgress->state = TaskStepState::Running; - updateState(); + updateStepProgress(); +} + +void ConcurrentTask::subTaskDetails(Task::Ptr task, const QString& msg) +{ + auto taskProgress = m_task_progress.value(task->getUid()); + taskProgress->details = msg; + taskProgress->state = TaskStepState::Running; updateStepProgress(); } @@ -162,7 +170,6 @@ void ConcurrentTask::subTaskProgress(Task::Ptr task, qint64 current, qint64 tota taskProgress->current = current; taskProgress->total = total; taskProgress->state = TaskStepState::Running; - taskProgress->details = task->getDetails(); updateStepProgress(); updateState(); diff --git a/launcher/tasks/ConcurrentTask.h b/launcher/tasks/ConcurrentTask.h index 9d4413c6..43e9f866 100644 --- a/launcher/tasks/ConcurrentTask.h +++ b/launcher/tasks/ConcurrentTask.h @@ -40,6 +40,7 @@ slots: void subTaskSucceeded(Task::Ptr); void subTaskFailed(Task::Ptr, const QString &msg); void subTaskStatus(Task::Ptr task, const QString &msg); + void subTaskDetails(Task::Ptr task, const QString &msg); void subTaskProgress(Task::Ptr task, qint64 current, qint64 total); void subTaskStepProgress(Task::Ptr task, TaskStepProgressList task_step_progress); diff --git a/launcher/tasks/Task.cpp b/launcher/tasks/Task.cpp index 5aada876..ffde4a10 100644 --- a/launcher/tasks/Task.cpp +++ b/launcher/tasks/Task.cpp @@ -37,7 +37,7 @@ #include -Q_LOGGING_CATEGORY(TaskLogC, "Task") +Q_LOGGING_CATEGORY(taskLogC, "launcher.task") Task::Task(QObject *parent, bool show_debug) : QObject(parent), m_show_debug(show_debug) { @@ -54,11 +54,23 @@ void Task::setStatus(const QString &new_status) } } +void Task::setDetails(const QString& new_details) +{ + if (m_details != new_details) + { + m_details = new_details; + emit details(m_details); + } +} + void Task::setProgress(qint64 current, qint64 total) { - m_progress = current; - m_progressTotal = total; - emit progress(m_progress, m_progressTotal); + if ((m_progress != current) || (m_progressTotal != total)) { + m_progress = current; + m_progressTotal = total; + + emit progress(m_progress, m_progressTotal); + } } void Task::start() @@ -68,31 +80,31 @@ void Task::start() case State::Inactive: { if (m_show_debug) - qCDebug(TaskLogC) << "Task" << describe() << "starting for the first time"; + qCDebug(taskLogC) << "Task" << describe() << "starting for the first time"; break; } case State::AbortedByUser: { if (m_show_debug) - qCDebug(TaskLogC) << "Task" << describe() << "restarting for after being aborted by user"; + qCDebug(taskLogC) << "Task" << describe() << "restarting for after being aborted by user"; break; } case State::Failed: { if (m_show_debug) - qCDebug(TaskLogC) << "Task" << describe() << "restarting for after failing at first"; + qCDebug(taskLogC) << "Task" << describe() << "restarting for after failing at first"; break; } case State::Succeeded: { if (m_show_debug) - qCDebug(TaskLogC) << "Task" << describe() << "restarting for after succeeding at first"; + qCDebug(taskLogC) << "Task" << describe() << "restarting for after succeeding at first"; break; } case State::Running: { if (m_show_debug) - qCWarning(TaskLogC) << "The launcher tried to start task" << describe() << "while it was already running!"; + qCWarning(taskLogC) << "The launcher tried to start task" << describe() << "while it was already running!"; return; } } @@ -107,12 +119,12 @@ void Task::emitFailed(QString reason) // Don't fail twice. if (!isRunning()) { - qCCritical(TaskLogC) << "Task" << describe() << "failed while not running!!!!: " << reason; + qCCritical(taskLogC) << "Task" << describe() << "failed while not running!!!!: " << reason; return; } m_state = State::Failed; m_failReason = reason; - qCCritical(TaskLogC) << "Task" << describe() << "failed: " << reason; + qCCritical(taskLogC) << "Task" << describe() << "failed: " << reason; emit failed(reason); emit finished(); } @@ -122,13 +134,13 @@ void Task::emitAborted() // Don't abort twice. if (!isRunning()) { - qCCritical(TaskLogC) << "Task" << describe() << "aborted while not running!!!!"; + qCCritical(taskLogC) << "Task" << describe() << "aborted while not running!!!!"; return; } m_state = State::AbortedByUser; m_failReason = "Aborted."; if (m_show_debug) - qCDebug(TaskLogC) << "Task" << describe() << "aborted."; + qCDebug(taskLogC) << "Task" << describe() << "aborted."; emit aborted(); emit finished(); } @@ -138,12 +150,12 @@ void Task::emitSucceeded() // Don't succeed twice. if (!isRunning()) { - qCCritical(TaskLogC) << "Task" << describe() << "succeeded while not running!!!!"; + qCCritical(taskLogC) << "Task" << describe() << "succeeded while not running!!!!"; return; } m_state = State::Succeeded; if (m_show_debug) - qCDebug(TaskLogC) << "Task" << describe() << "succeeded"; + qCDebug(taskLogC) << "Task" << describe() << "succeeded"; emit succeeded(); emit finished(); } diff --git a/launcher/tasks/Task.h b/launcher/tasks/Task.h index 863f8a4c..96b3b855 100644 --- a/launcher/tasks/Task.h +++ b/launcher/tasks/Task.h @@ -42,6 +42,8 @@ #include "QObjectPtr.h" +Q_DECLARE_LOGGING_CATEGORY(taskLogC) + enum class TaskStepState { Waiting, Running, @@ -100,12 +102,13 @@ class Task : public QObject, public QRunnable { auto getState() const -> State { return m_state; } QString getStatus() { return m_status; } + QString getDetails() { return m_details; } qint64 getProgress() { return m_progress; } qint64 getTotalProgress() { return m_progressTotal; } virtual auto getStepProgress() const -> TaskStepProgressList { return {}; } - virtual auto getDetails() const -> QString { return ""; } + QUuid getUid() { return m_uid; } @@ -123,6 +126,7 @@ class Task : public QObject, public QRunnable { void aborted(); void failed(QString reason); void status(QString status); + void details(QString details); void stepProgress(TaskStepProgressList task_progress); // /** Emitted when the canAbort() status has changed. @@ -150,6 +154,7 @@ class Task : public QObject, public QRunnable { public slots: void setStatus(const QString& status); + void setDetails(const QString& details); void setProgress(qint64 current, qint64 total); protected: @@ -157,6 +162,7 @@ class Task : public QObject, public QRunnable { QStringList m_Warnings; QString m_failReason = ""; QString m_status; + QString m_details; int m_progress = 0; int m_progressTotal = 100; diff --git a/launcher/ui/dialogs/ProgressDialog.cpp b/launcher/ui/dialogs/ProgressDialog.cpp index 7ab766e4..1937c553 100644 --- a/launcher/ui/dialogs/ProgressDialog.cpp +++ b/launcher/ui/dialogs/ProgressDialog.cpp @@ -133,9 +133,9 @@ int ProgressDialog::execWithTask(Task* task) connect(task, &Task::failed, this, &ProgressDialog::onTaskFailed); connect(task, &Task::succeeded, this, &ProgressDialog::onTaskSucceeded); connect(task, &Task::status, this, &ProgressDialog::changeStatus); + connect(task, &Task::details, this, &ProgressDialog::changeStatus); connect(task, &Task::stepProgress, this, &ProgressDialog::changeStepProgress); connect(task, &Task::progress, this, &ProgressDialog::changeProgress); - connect(task, &Task::aborted, this, &ProgressDialog::hide); connect(task, &Task::abortStatusChanged, ui->skipButton, &QPushButton::setEnabled); diff --git a/launcher/ui/widgets/ProgressWidget.cpp b/launcher/ui/widgets/ProgressWidget.cpp index f736af08..9181de7f 100644 --- a/launcher/ui/widgets/ProgressWidget.cpp +++ b/launcher/ui/widgets/ProgressWidget.cpp @@ -51,6 +51,7 @@ void ProgressWidget::watch(const Task* task) connect(m_task, &Task::finished, this, &ProgressWidget::handleTaskFinish); connect(m_task, &Task::status, this, &ProgressWidget::handleTaskStatus); + // TODO: should we connect &Task::details connect(m_task, &Task::progress, this, &ProgressWidget::handleTaskProgress); connect(m_task, &Task::destroyed, this, &ProgressWidget::taskDestroyed); diff --git a/launcher/ui/widgets/SubTaskProgressBar.ui b/launcher/ui/widgets/SubTaskProgressBar.ui index ceae5e26..5431eab6 100644 --- a/launcher/ui/widgets/SubTaskProgressBar.ui +++ b/launcher/ui/widgets/SubTaskProgressBar.ui @@ -6,8 +6,8 @@ 0 0 - 597 - 61 + 312 + 86 @@ -25,6 +25,9 @@ + + 8 + -- cgit From 0fb6a2836be2fe51e27a47595950923dca329006 Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Sun, 2 Apr 2023 21:51:07 -0700 Subject: refactor: propogate only only one StepProgress at a time Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- launcher/tasks/ConcurrentTask.cpp | 99 ++++++++++++++++++---------------- launcher/tasks/ConcurrentTask.h | 2 +- launcher/tasks/Task.cpp | 2 +- launcher/tasks/Task.h | 9 ++-- launcher/ui/dialogs/ProgressDialog.cpp | 38 +++++++------ launcher/ui/dialogs/ProgressDialog.h | 2 +- 6 files changed, 79 insertions(+), 73 deletions(-) (limited to 'launcher/ui/dialogs') diff --git a/launcher/tasks/ConcurrentTask.cpp b/launcher/tasks/ConcurrentTask.cpp index 3c62cf4d..8434ecdc 100644 --- a/launcher/tasks/ConcurrentTask.cpp +++ b/launcher/tasks/ConcurrentTask.cpp @@ -35,13 +35,15 @@ */ #include "ConcurrentTask.h" -#include #include +#include #include "tasks/Task.h" ConcurrentTask::ConcurrentTask(QObject* parent, QString task_name, int max_concurrent) : Task(parent), m_name(task_name), m_total_max_size(max_concurrent) -{ setObjectName(task_name); } +{ + setObjectName(task_name); +} ConcurrentTask::~ConcurrentTask() { @@ -126,18 +128,17 @@ void ConcurrentTask::startNext() Task::Ptr next = m_queue.dequeue(); - connect(next.get(), &Task::succeeded, this, [this, next](){ subTaskSucceeded(next); }); + connect(next.get(), &Task::succeeded, this, [this, next]() { subTaskSucceeded(next); }); connect(next.get(), &Task::failed, this, [this, next](QString msg) { subTaskFailed(next, msg); }); - connect(next.get(), &Task::status, this, [this, next](QString msg){ subTaskStatus(next, msg); }); - connect(next.get(), &Task::details, this, [this, next](QString msg){ subTaskDetails(next, msg); }); - connect(next.get(), &Task::stepProgress, this, [this, next](TaskStepProgressList tp){ subTaskStepProgress(next, tp); }); + connect(next.get(), &Task::status, this, [this, next](QString msg) { subTaskStatus(next, msg); }); + connect(next.get(), &Task::details, this, [this, next](QString msg) { subTaskDetails(next, msg); }); + connect(next.get(), &Task::stepProgress, this, [this, next](TaskStepProgress tp) { subTaskStepProgress(next, tp); }); - connect(next.get(), &Task::progress, this, [this, next](qint64 current, qint64 total){ subTaskProgress(next, current, total); }); + connect(next.get(), &Task::progress, this, [this, next](qint64 current, qint64 total) { subTaskProgress(next, current, total); }); m_doing.insert(next.get(), next); - m_task_progress.insert(next->getUid(), std::make_shared(TaskStepProgress({next->getUid()}))); - + m_task_progress.insert(next->getUid(), std::make_shared(TaskStepProgress({ next->getUid() }))); updateState(); updateStepProgress(); @@ -158,10 +159,12 @@ void ConcurrentTask::subTaskSucceeded(Task::Ptr task) m_succeeded.insert(task.get(), task); m_doing.remove(task.get()); - m_task_progress.value(task->getUid())->state = TaskStepState::Succeeded; + auto task_progress = m_task_progress.value(task->getUid()); + task_progress->state = TaskStepState::Succeeded; disconnect(task.get(), 0, this, 0); + emit stepProgress(*task_progress.get()); updateState(); updateStepProgress(); startNext(); @@ -173,10 +176,13 @@ void ConcurrentTask::subTaskFailed(Task::Ptr task, const QString& msg) m_failed.insert(task.get(), task); m_doing.remove(task.get()); - m_task_progress.value(task->getUid())->state = TaskStepState::Failed; + + auto task_progress = m_task_progress.value(task->getUid()); + task_progress->state = TaskStepState::Failed; disconnect(task.get(), 0, this, 0); + emit stepProgress(*task_progress.get()); updateState(); updateStepProgress(); startNext(); @@ -184,68 +190,71 @@ void ConcurrentTask::subTaskFailed(Task::Ptr task, const QString& msg) void ConcurrentTask::subTaskStatus(Task::Ptr task, const QString& msg) { - auto taskProgress = m_task_progress.value(task->getUid()); - taskProgress->status = msg; - taskProgress->state = TaskStepState::Running; + auto task_progress = m_task_progress.value(task->getUid()); + task_progress->status = msg; + task_progress->state = TaskStepState::Running; + + emit stepProgress(*task_progress.get()); updateStepProgress(); } void ConcurrentTask::subTaskDetails(Task::Ptr task, const QString& msg) { - auto taskProgress = m_task_progress.value(task->getUid()); - taskProgress->details = msg; - taskProgress->state = TaskStepState::Running; + auto task_progress = m_task_progress.value(task->getUid()); + task_progress->details = msg; + task_progress->state = TaskStepState::Running; + + emit stepProgress(*task_progress.get()); updateStepProgress(); } void ConcurrentTask::subTaskProgress(Task::Ptr task, qint64 current, qint64 total) { - auto taskProgress = m_task_progress.value(task->getUid()); - - taskProgress->current = current; - taskProgress->total = total; - taskProgress->state = TaskStepState::Running; + auto task_progress = m_task_progress.value(task->getUid()); + task_progress->current = current; + task_progress->total = total; + task_progress->state = TaskStepState::Running; + + emit stepProgress(*task_progress.get()); updateStepProgress(); updateState(); } -void ConcurrentTask::subTaskStepProgress(Task::Ptr task, TaskStepProgressList task_step_progress) +void ConcurrentTask::subTaskStepProgress(Task::Ptr task, TaskStepProgress task_progress) { - for (auto progress : task_step_progress) { - if (!m_task_progress.contains(progress->uid)) { - m_task_progress.insert(progress->uid, progress); - } else { - auto tp = m_task_progress.value(progress->uid); - tp->current = progress->current; - tp->total = progress->total; - tp->status = progress->status; - tp->details = progress->details; - } + if (!m_task_progress.contains(task_progress.uid)) { + m_task_progress.insert(task_progress.uid, std::make_shared(task_progress)); + } else { + auto tp = m_task_progress.value(task_progress.uid); + tp->current = task_progress.current; + tp->total = task_progress.total; + tp->status = task_progress.status; + tp->details = task_progress.details; } - - updateStepProgress(); + emit stepProgress(task_progress); + updateStepProgress(); } void ConcurrentTask::updateStepProgress() { - qint64 current = 0, total = 0; - for ( auto taskProgress : m_task_progress ) { - current += taskProgress->current; - total += taskProgress->total; - } - - m_stepProgress = current; - m_stepTotalProgress = total; - emit stepProgress(m_task_progress.values()); + qint64 current = 0, total = 0; + for (auto taskProgress : m_task_progress) { + current += taskProgress->current; + total += taskProgress->total; + } + + m_stepProgress = current; + m_stepTotalProgress = total; } void ConcurrentTask::updateState() { if (totalSize() > 1) { setProgress(m_done.count(), totalSize()); - setStatus(tr("Executing %1 task(s) (%2 out of %3 are done)").arg(QString::number(m_doing.count()), QString::number(m_done.count()), QString::number(totalSize()))); + setStatus(tr("Executing %1 task(s) (%2 out of %3 are done)") + .arg(QString::number(m_doing.count()), QString::number(m_done.count()), QString::number(totalSize()))); } else { setProgress(m_stepProgress, m_stepTotalProgress); QString status = tr("Please wait ..."); diff --git a/launcher/tasks/ConcurrentTask.h b/launcher/tasks/ConcurrentTask.h index 1cf1520e..974e8d4d 100644 --- a/launcher/tasks/ConcurrentTask.h +++ b/launcher/tasks/ConcurrentTask.h @@ -77,7 +77,7 @@ slots: void subTaskStatus(Task::Ptr task, const QString &msg); void subTaskDetails(Task::Ptr task, const QString &msg); void subTaskProgress(Task::Ptr task, qint64 current, qint64 total); - void subTaskStepProgress(Task::Ptr task, TaskStepProgressList task_step_progress); + void subTaskStepProgress(Task::Ptr task, TaskStepProgress task_step_progress); protected: // NOTE: This is not thread-safe. diff --git a/launcher/tasks/Task.cpp b/launcher/tasks/Task.cpp index e5f61919..fd1b8898 100644 --- a/launcher/tasks/Task.cpp +++ b/launcher/tasks/Task.cpp @@ -161,7 +161,7 @@ void Task::emitSucceeded() emit finished(); } -void Task::propogateStepProgress(TaskStepProgressList task_progress) +void Task::propogateStepProgress(TaskStepProgress task_progress) { emit stepProgress(task_progress); } diff --git a/launcher/tasks/Task.h b/launcher/tasks/Task.h index 96b3b855..9ae70270 100644 --- a/launcher/tasks/Task.h +++ b/launcher/tasks/Task.h @@ -48,8 +48,7 @@ enum class TaskStepState { Waiting, Running, Failed, - Succeeded, - Finished + Succeeded }; Q_DECLARE_METATYPE(TaskStepState) @@ -61,7 +60,7 @@ struct TaskStepProgress { QString status = ""; QString details = ""; TaskStepState state = TaskStepState::Waiting; - bool isDone() { return (state == TaskStepState::Failed) || (state == TaskStepState::Succeeded) || (state == TaskStepState::Finished); } + bool isDone() { return (state == TaskStepState::Failed) || (state == TaskStepState::Succeeded); } }; Q_DECLARE_METATYPE(TaskStepProgress) @@ -127,7 +126,7 @@ class Task : public QObject, public QRunnable { void failed(QString reason); void status(QString status); void details(QString details); - void stepProgress(TaskStepProgressList task_progress); // + void stepProgress(TaskStepProgress task_progress); // /** Emitted when the canAbort() status has changed. */ @@ -150,7 +149,7 @@ class Task : public QObject, public QRunnable { virtual void emitAborted(); virtual void emitFailed(QString reason = ""); - virtual void propogateStepProgress(TaskStepProgressList task_progress); + virtual void propogateStepProgress(TaskStepProgress task_progress); public slots: void setStatus(const QString& status); diff --git a/launcher/ui/dialogs/ProgressDialog.cpp b/launcher/ui/dialogs/ProgressDialog.cpp index 1937c553..7594484e 100644 --- a/launcher/ui/dialogs/ProgressDialog.cpp +++ b/launcher/ui/dialogs/ProgressDialog.cpp @@ -46,8 +46,8 @@ // map a value in a numaric range of an arbatray type to between 0 and INT_MAX -// for getting the best percision out of the qt progress bar -template::value, T>::type> +// for getting the best precision out of the qt progress bar +template, bool> = true> std::tuple map_int_zero_max(T current, T range_max, T range_min) { int int_max = std::numeric_limits::max(); @@ -213,7 +213,7 @@ void ProgressDialog::addTaskProgress(TaskStepProgress* progress) ui->taskProgressLayout->addWidget(task_bar); } -void ProgressDialog::changeStepProgress(TaskStepProgressList task_progress) +void ProgressDialog::changeStepProgress(TaskStepProgress task_progress) { m_is_multi_step = true; if(ui->taskProgressScrollArea->isHidden()) { @@ -221,28 +221,26 @@ void ProgressDialog::changeStepProgress(TaskStepProgressList task_progress) updateSize(); } - for (auto tp : task_progress) { - if (!taskProgress.contains(tp->uid)) - addTaskProgress(tp.get()); - auto task_bar = taskProgress.value(tp->uid); + if (!taskProgress.contains(task_progress.uid)) + addTaskProgress(&task_progress); + auto task_bar = taskProgress.value(task_progress.uid); - auto const [mapped_current, mapped_total] = map_int_zero_max(tp->current, tp->total, 0); - if (tp->total <= 0) { - task_bar->setRange(0, 0); - } else { - task_bar->setRange(0, mapped_total); - } - - task_bar->setValue(mapped_current); - task_bar->setStatus(tp->status); - task_bar->setDetails(tp->details); + auto const [mapped_current, mapped_total] = map_int_zero_max(task_progress.current, task_progress.total, 0); + if (task_progress.total <= 0) { + task_bar->setRange(0, 0); + } else { + task_bar->setRange(0, mapped_total); + } - if (tp->isDone()) { - task_bar->setVisible(false); - } + task_bar->setValue(mapped_current); + task_bar->setStatus(task_progress.status); + task_bar->setDetails(task_progress.details); + if (task_progress.isDone()) { + task_bar->setVisible(false); } + } void ProgressDialog::changeProgress(qint64 current, qint64 total) diff --git a/launcher/ui/dialogs/ProgressDialog.h b/launcher/ui/dialogs/ProgressDialog.h index 95a4db16..6779b949 100644 --- a/launcher/ui/dialogs/ProgressDialog.h +++ b/launcher/ui/dialogs/ProgressDialog.h @@ -80,7 +80,7 @@ slots: void changeStatus(const QString &status); void changeProgress(qint64 current, qint64 total); - void changeStepProgress(TaskStepProgressList task_progress); + void changeStepProgress(TaskStepProgress task_progress); private -- cgit From 236764adf6cb985dfc6d00b9cbcba8eb176510ed Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Fri, 7 Apr 2023 19:44:57 -0700 Subject: refactor: Qt can handle const& in signals and slots While most Qt types cna use implicit data sharing pasing our own structs means copies. const& ensure it's only copied as needed by Qt. Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- launcher/tasks/ConcurrentTask.cpp | 10 ++++++---- launcher/tasks/ConcurrentTask.h | 2 +- launcher/tasks/Task.cpp | 2 +- launcher/tasks/Task.h | 6 +++--- launcher/ui/dialogs/ProgressDialog.cpp | 8 ++++---- launcher/ui/dialogs/ProgressDialog.h | 4 ++-- 6 files changed, 17 insertions(+), 15 deletions(-) (limited to 'launcher/ui/dialogs') diff --git a/launcher/tasks/ConcurrentTask.cpp b/launcher/tasks/ConcurrentTask.cpp index 439879e6..37435739 100644 --- a/launcher/tasks/ConcurrentTask.cpp +++ b/launcher/tasks/ConcurrentTask.cpp @@ -133,7 +133,7 @@ void ConcurrentTask::startNext() connect(next.get(), &Task::status, this, [this, next](QString msg) { subTaskStatus(next, msg); }); connect(next.get(), &Task::details, this, [this, next](QString msg) { subTaskDetails(next, msg); }); - connect(next.get(), &Task::stepProgress, this, [this, next](TaskStepProgress tp) { subTaskStepProgress(next, tp); }); + connect(next.get(), &Task::stepProgress, this, [this, next](TaskStepProgress const& tp) { subTaskStepProgress(next, tp); }); connect(next.get(), &Task::progress, this, [this, next](qint64 current, qint64 total) { subTaskProgress(next, current, total); }); @@ -224,13 +224,15 @@ void ConcurrentTask::subTaskProgress(Task::Ptr task, qint64 current, qint64 tota updateState(); } -void ConcurrentTask::subTaskStepProgress(Task::Ptr task, TaskStepProgress task_progress) +void ConcurrentTask::subTaskStepProgress(Task::Ptr task, TaskStepProgress const& task_progress) { Operation op = Operation::ADDED; if (!m_task_progress.contains(task_progress.uid)) { m_task_progress.insert(task_progress.uid, std::make_shared(task_progress)); op = Operation::ADDED; + emit stepProgress(task_progress); + updateStepProgress(task_progress, op); } else { auto tp = m_task_progress.value(task_progress.uid); @@ -243,10 +245,10 @@ void ConcurrentTask::subTaskStepProgress(Task::Ptr task, TaskStepProgress task_p tp->details = task_progress.details; op = Operation::CHANGED; + emit stepProgress(*tp.get()); + updateStepProgress(*tp.get(), op); } - emit stepProgress(task_progress); - updateStepProgress(task_progress, op); } void ConcurrentTask::updateStepProgress(TaskStepProgress const& changed_progress, Operation op) diff --git a/launcher/tasks/ConcurrentTask.h b/launcher/tasks/ConcurrentTask.h index 284fa345..aec707bc 100644 --- a/launcher/tasks/ConcurrentTask.h +++ b/launcher/tasks/ConcurrentTask.h @@ -77,7 +77,7 @@ slots: void subTaskStatus(Task::Ptr task, const QString &msg); void subTaskDetails(Task::Ptr task, const QString &msg); void subTaskProgress(Task::Ptr task, qint64 current, qint64 total); - void subTaskStepProgress(Task::Ptr task, TaskStepProgress task_step_progress); + void subTaskStepProgress(Task::Ptr task, TaskStepProgress const& task_step_progress); protected: // NOTE: This is not thread-safe. diff --git a/launcher/tasks/Task.cpp b/launcher/tasks/Task.cpp index fd1b8898..b0addd46 100644 --- a/launcher/tasks/Task.cpp +++ b/launcher/tasks/Task.cpp @@ -161,7 +161,7 @@ void Task::emitSucceeded() emit finished(); } -void Task::propogateStepProgress(TaskStepProgress task_progress) +void Task::propogateStepProgress(TaskStepProgress const& task_progress) { emit stepProgress(task_progress); } diff --git a/launcher/tasks/Task.h b/launcher/tasks/Task.h index d71eaf6b..04a09187 100644 --- a/launcher/tasks/Task.h +++ b/launcher/tasks/Task.h @@ -64,7 +64,7 @@ struct TaskStepProgress { QString status = ""; QString details = ""; TaskStepState state = TaskStepState::Waiting; - bool isDone() { return (state == TaskStepState::Failed) || (state == TaskStepState::Succeeded); } + bool isDone() const { return (state == TaskStepState::Failed) || (state == TaskStepState::Succeeded); } }; Q_DECLARE_METATYPE(TaskStepProgress) @@ -130,7 +130,7 @@ class Task : public QObject, public QRunnable { void failed(QString reason); void status(QString status); void details(QString details); - void stepProgress(TaskStepProgress task_progress); // + void stepProgress(TaskStepProgress const& task_progress); // /** Emitted when the canAbort() status has changed. */ @@ -153,7 +153,7 @@ class Task : public QObject, public QRunnable { virtual void emitAborted(); virtual void emitFailed(QString reason = ""); - virtual void propogateStepProgress(TaskStepProgress task_progress); + virtual void propogateStepProgress(TaskStepProgress const& task_progress); public slots: void setStatus(const QString& status); diff --git a/launcher/ui/dialogs/ProgressDialog.cpp b/launcher/ui/dialogs/ProgressDialog.cpp index 7594484e..94feee44 100644 --- a/launcher/ui/dialogs/ProgressDialog.cpp +++ b/launcher/ui/dialogs/ProgressDialog.cpp @@ -206,14 +206,14 @@ void ProgressDialog::changeStatus(const QString& status) updateSize(); } -void ProgressDialog::addTaskProgress(TaskStepProgress* progress) +void ProgressDialog::addTaskProgress(TaskStepProgress const& progress) { SubTaskProgressBar* task_bar = new SubTaskProgressBar(this); - taskProgress.insert(progress->uid, task_bar); + taskProgress.insert(progress.uid, task_bar); ui->taskProgressLayout->addWidget(task_bar); } -void ProgressDialog::changeStepProgress(TaskStepProgress task_progress) +void ProgressDialog::changeStepProgress(TaskStepProgress const& task_progress) { m_is_multi_step = true; if(ui->taskProgressScrollArea->isHidden()) { @@ -222,7 +222,7 @@ void ProgressDialog::changeStepProgress(TaskStepProgress task_progress) } if (!taskProgress.contains(task_progress.uid)) - addTaskProgress(&task_progress); + addTaskProgress(task_progress); auto task_bar = taskProgress.value(task_progress.uid); diff --git a/launcher/ui/dialogs/ProgressDialog.h b/launcher/ui/dialogs/ProgressDialog.h index 6779b949..fc9a0fbc 100644 --- a/launcher/ui/dialogs/ProgressDialog.h +++ b/launcher/ui/dialogs/ProgressDialog.h @@ -80,7 +80,7 @@ slots: void changeStatus(const QString &status); void changeProgress(qint64 current, qint64 total); - void changeStepProgress(TaskStepProgress task_progress); + void changeStepProgress(TaskStepProgress const& task_progress); private @@ -93,7 +93,7 @@ protected: private: bool handleImmediateResult(QDialog::DialogCode &result); - void addTaskProgress(TaskStepProgress* progress); + void addTaskProgress(TaskStepProgress const& progress); private: Ui::ProgressDialog *ui; -- cgit From 5ec4cbf1cbf660d992ddd73c67d31de27c097f54 Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Wed, 3 May 2023 20:31:15 -0700 Subject: fix add an addtion lax file name match with ` ` replaced with `+` Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- launcher/ui/dialogs/BlockedModsDialog.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'launcher/ui/dialogs') diff --git a/launcher/ui/dialogs/BlockedModsDialog.cpp b/launcher/ui/dialogs/BlockedModsDialog.cpp index ba453df6..3e1137c2 100644 --- a/launcher/ui/dialogs/BlockedModsDialog.cpp +++ b/launcher/ui/dialogs/BlockedModsDialog.cpp @@ -305,6 +305,9 @@ bool BlockedModsDialog::checkValidPath(QString path) QString laxFilename(filename); laxFilename.replace('+', ' '); + QString laxFilename2(filename); + laxFilename.replace(' ', '+'); + auto compare = [](QString fsfilename, QString metadataFilename) { return metadataFilename.compare(fsfilename, Qt::CaseInsensitive) == 0; }; @@ -314,7 +317,7 @@ bool BlockedModsDialog::checkValidPath(QString path) qDebug() << "[Blocked Mods Dialog] Name match found:" << mod.name << "| From path:" << path; return true; } - if (compare(laxFilename, mod.name)) { + if (compare(laxFilename, mod.name) || compare(laxFilename2, mod.name)) { qDebug() << "[Blocked Mods Dialog] Lax name match found:" << mod.name << "| From path:" << path; return true; } -- cgit From e0635955df2d1bf79e1ba61eb02074801b60953f Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Thu, 4 May 2023 13:30:39 -0700 Subject: fix: super lax compare Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- launcher/ui/dialogs/BlockedModsDialog.cpp | 40 +++++++++++++++++++++++-------- 1 file changed, 30 insertions(+), 10 deletions(-) (limited to 'launcher/ui/dialogs') diff --git a/launcher/ui/dialogs/BlockedModsDialog.cpp b/launcher/ui/dialogs/BlockedModsDialog.cpp index 3e1137c2..fdfae597 100644 --- a/launcher/ui/dialogs/BlockedModsDialog.cpp +++ b/launcher/ui/dialogs/BlockedModsDialog.cpp @@ -195,7 +195,7 @@ void BlockedModsDialog::watchPath(QString path, bool watch_recursive) auto to_watch = QFileInfo(path); auto to_watch_path = to_watch.canonicalFilePath(); if (m_watcher.directories().contains(to_watch_path)) - return; // don't watch the same path twice (no loops!) + return; // don't watch the same path twice (no loops!) qDebug() << "[Blocked Mods Dialog] Adding Watch Path:" << path; m_watcher.addPath(to_watch_path); @@ -203,10 +203,9 @@ void BlockedModsDialog::watchPath(QString path, bool watch_recursive) if (!to_watch.isDir() || !watch_recursive) return; - QDirIterator it(to_watch_path, QDir::Filter::Dirs | QDir::Filter::NoDotAndDotDot, QDirIterator::NoIteratorFlags); while (it.hasNext()) { - QString watch_dir = QDir(it.next()).canonicalPath(); // resolve symlinks and relative paths + QString watch_dir = QDir(it.next()).canonicalPath(); // resolve symlinks and relative paths watchPath(watch_dir, watch_recursive); } } @@ -302,14 +301,35 @@ bool BlockedModsDialog::checkValidPath(QString path) { const QFileInfo file = QFileInfo(path); const QString filename = file.fileName(); - QString laxFilename(filename); - laxFilename.replace('+', ' '); - QString laxFilename2(filename); - laxFilename.replace(' ', '+'); + auto compare = [](QString fsFilename, QString metadataFilename) { + return metadataFilename.compare(fsFilename, Qt::CaseInsensitive) == 0; + }; + + // super lax compare (but not fuzzy) + // convert to lowercase + // convert all speratores to whitespace + // simplify sequence of internal whitespace to a single space + // efectivly compare two strings ignoring all separators and case + auto laxCompare = [](QString fsfilename, QString metadataFilename) { + // allowed character seperators + QList allowedSeperators = { '-', '+', '.' , '_'}; + + // copy in lowercase + auto fsName = fsfilename.toLower(); + auto metaName = metadataFilename.toLower(); + + // replace all potential allowed seperatores with whitespace + for (auto sep : allowedSeperators) { + fsName = fsName.replace(sep, ' '); + metaName = metaName.replace(sep, ' '); + } + + // remove extraneous whitespace + fsName = fsName.simplified(); + metaName = metaName.simplified(); - auto compare = [](QString fsfilename, QString metadataFilename) { - return metadataFilename.compare(fsfilename, Qt::CaseInsensitive) == 0; + return fsName.compare(metaName) == 0; }; for (auto& mod : m_mods) { @@ -317,7 +337,7 @@ bool BlockedModsDialog::checkValidPath(QString path) qDebug() << "[Blocked Mods Dialog] Name match found:" << mod.name << "| From path:" << path; return true; } - if (compare(laxFilename, mod.name) || compare(laxFilename2, mod.name)) { + if (laxCompare(filename, mod.name)) { qDebug() << "[Blocked Mods Dialog] Lax name match found:" << mod.name << "| From path:" << path; return true; } -- cgit From b266068644d2caab4f103b0adf7a491b95f52369 Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Fri, 5 May 2023 14:07:10 -0700 Subject: Apply suggestions from code review Co-authored-by: flow Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- launcher/java/JavaInstallList.cpp | 1 - launcher/net/Download.cpp | 13 +++++++------ launcher/net/NetAction.h | 4 ++-- launcher/tasks/ConcurrentTask.cpp | 6 +++--- launcher/tasks/Task.h | 2 +- launcher/ui/dialogs/ProgressDialog.cpp | 2 +- launcher/ui/widgets/SubTaskProgressBar.h | 2 -- 7 files changed, 14 insertions(+), 16 deletions(-) (limited to 'launcher/ui/dialogs') diff --git a/launcher/java/JavaInstallList.cpp b/launcher/java/JavaInstallList.cpp index 5f133622..b29af857 100644 --- a/launcher/java/JavaInstallList.cpp +++ b/launcher/java/JavaInstallList.cpp @@ -170,7 +170,6 @@ void JavaListLoadTask::executeTask() m_job.reset(new JavaCheckerJob("Java detection")); connect(m_job.get(), &Task::finished, this, &JavaListLoadTask::javaCheckerFinished); connect(m_job.get(), &Task::progress, this, &Task::setProgress); - // stepProgress? qDebug() << "Probing the following Java paths: "; int id = 0; diff --git a/launcher/net/Download.cpp b/launcher/net/Download.cpp index bf0e5c26..082f963d 100644 --- a/launcher/net/Download.cpp +++ b/launcher/net/Download.cpp @@ -247,19 +247,20 @@ void Download::downloadProgress(qint64 bytesReceived, qint64 bytesTotal) // use milliseconds for speed precision auto elapsed_ms = std::chrono::duration_cast(elapsed); - auto bytes_recived_since = bytesReceived - m_last_progress_bytes; - auto dl_speed_bps = (double)bytes_recived_since / elapsed_ms.count() * 1000; + auto bytes_received_since = bytesReceived - m_last_progress_bytes; + auto dl_speed_bps = (double)bytes_received_since / elapsed_ms.count() * 1000; auto remaing_time_s = (bytesTotal - bytesReceived) / dl_speed_bps; - // current bytes out of total bytes + //: Current amount of bytes downloaded, out of the total amount of bytes in the download QString dl_progress = tr("%1 / %2").arg(humanReadableFileSize(bytesReceived)).arg(humanReadableFileSize(bytesTotal)); QString dl_speed_str; if (elapsed_ms.count() > 0) { - // bytes per second - dl_speed_str = tr("%1/s (%2)").arg(humanReadableFileSize(dl_speed_bps)).arg(humanReadableDuration(remaing_time_s)); + //: Download speed, in bytes per second (remaining download time in parenthesis) + dl_speed_str = tr("%1 /s (%2)").arg(humanReadableFileSize(dl_speed_bps)).arg(humanReadableDuration(remaing_time_s)); } else { - dl_speed_str = tr("0 b/s"); + //: Download speed at 0 bytes per second + dl_speed_str = tr("0 B/s"); } setDetails(dl_progress + "\n" + dl_speed_str); diff --git a/launcher/net/NetAction.h b/launcher/net/NetAction.h index df6ed995..a13d115f 100644 --- a/launcher/net/NetAction.h +++ b/launcher/net/NetAction.h @@ -44,8 +44,8 @@ #include "QObjectPtr.h" #include "tasks/Task.h" -static const QStringList s_units_si {"kb", "MB", "GB", "TB"}; -static const QStringList s_units_kibi {"kiB", "MiB", "Gib", "TiB"}; +static const QStringList s_units_si {"kB", "MB", "GB", "TB"}; +static const QStringList s_units_kibi {"KiB", "MiB", "Gib", "TiB"}; inline QString humanReadableFileSize(double bytes, bool use_si = false, int decimal_points = 1) { const QStringList units = use_si ? s_units_si : s_units_kibi; diff --git a/launcher/tasks/ConcurrentTask.cpp b/launcher/tasks/ConcurrentTask.cpp index dbb4d94d..fae2f3dc 100644 --- a/launcher/tasks/ConcurrentTask.cpp +++ b/launcher/tasks/ConcurrentTask.cpp @@ -65,7 +65,7 @@ void ConcurrentTask::addTask(Task::Ptr task) void ConcurrentTask::executeTask() { - // Start One task, startNext hadels starting the up to the m_total_max_size + // Start one task, startNext handles starting the up to the m_total_max_size // while tracking the number currently being done QMetaObject::invokeMethod(this, &ConcurrentTask::startNext, Qt::QueuedConnection); } @@ -293,9 +293,9 @@ void ConcurrentTask::updateState() .arg(QString::number(m_doing.count()), QString::number(m_done.count()), QString::number(totalSize()))); } else { setProgress(m_stepProgress, m_stepTotalProgress); - QString status = tr("Please wait ..."); + QString status = tr("Please wait..."); if (m_queue.size() > 0) { - status = tr("Waiting for 1 task to start ..."); + status = tr("Waiting for a task to start..."); } else if (m_doing.size() > 0) { status = tr("Executing 1 task:"); } else if (m_done.size() > 0) { diff --git a/launcher/tasks/Task.h b/launcher/tasks/Task.h index 04a09187..799ed945 100644 --- a/launcher/tasks/Task.h +++ b/launcher/tasks/Task.h @@ -130,7 +130,7 @@ class Task : public QObject, public QRunnable { void failed(QString reason); void status(QString status); void details(QString details); - void stepProgress(TaskStepProgress const& task_progress); // + void stepProgress(TaskStepProgress const& task_progress); /** Emitted when the canAbort() status has changed. */ diff --git a/launcher/ui/dialogs/ProgressDialog.cpp b/launcher/ui/dialogs/ProgressDialog.cpp index 94feee44..246a0fd4 100644 --- a/launcher/ui/dialogs/ProgressDialog.cpp +++ b/launcher/ui/dialogs/ProgressDialog.cpp @@ -45,7 +45,7 @@ #include "ui/widgets/SubTaskProgressBar.h" -// map a value in a numaric range of an arbatray type to between 0 and INT_MAX +// map a value in a numeric range of an arbitrary type to between 0 and INT_MAX // for getting the best precision out of the qt progress bar template, bool> = true> std::tuple map_int_zero_max(T current, T range_max, T range_min) diff --git a/launcher/ui/widgets/SubTaskProgressBar.h b/launcher/ui/widgets/SubTaskProgressBar.h index 3375a0bc..8f8aeea2 100644 --- a/launcher/ui/widgets/SubTaskProgressBar.h +++ b/launcher/ui/widgets/SubTaskProgressBar.h @@ -18,8 +18,6 @@ */ #pragma once -#include -#include #include #include "QObjectPtr.h" -- cgit From a04a6f1e0d0d551506a86964c51e5ce6af5587b4 Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Sun, 28 May 2023 02:15:39 -0700 Subject: fix(memory leak): don't give shared pointers out to foldermodels (causes cyclic refrence) Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- launcher/minecraft/MinecraftInstance.cpp | 30 +++++++++++----------- launcher/minecraft/MinecraftInstance.h | 16 ++++++------ launcher/minecraft/WorldList.cpp | 2 +- launcher/minecraft/WorldList.h | 4 +-- launcher/minecraft/mod/ModFolderModel.cpp | 2 +- launcher/minecraft/mod/ModFolderModel.h | 2 +- launcher/minecraft/mod/ResourceFolderModel.cpp | 2 +- launcher/minecraft/mod/ResourceFolderModel.h | 4 +-- launcher/minecraft/mod/ResourcePackFolderModel.cpp | 2 +- launcher/minecraft/mod/ResourcePackFolderModel.h | 2 +- launcher/minecraft/mod/ShaderPackFolderModel.h | 2 +- launcher/minecraft/mod/TexturePackFolderModel.cpp | 2 +- launcher/minecraft/mod/TexturePackFolderModel.h | 2 +- launcher/ui/dialogs/NewInstanceDialog.cpp | 2 +- launcher/ui/dialogs/ResourceDownloadDialog.cpp | 2 +- launcher/ui/widgets/PageContainer.cpp | 4 ++- tests/ResourceFolderModel_test.cpp | 17 ++++-------- 17 files changed, 46 insertions(+), 51 deletions(-) (limited to 'launcher/ui/dialogs') diff --git a/launcher/minecraft/MinecraftInstance.cpp b/launcher/minecraft/MinecraftInstance.cpp index 35bef05e..2c624a36 100644 --- a/launcher/minecraft/MinecraftInstance.cpp +++ b/launcher/minecraft/MinecraftInstance.cpp @@ -1109,79 +1109,79 @@ JavaVersion MinecraftInstance::getJavaVersion() return JavaVersion(settings()->get("JavaVersion").toString()); } -std::shared_ptr MinecraftInstance::loaderModList() const +std::shared_ptr MinecraftInstance::loaderModList() { if (!m_loader_mod_list) { bool is_indexed = !APPLICATION->settings()->get("ModMetadataDisabled").toBool(); - m_loader_mod_list.reset(new ModFolderModel(modsRoot(), shared_from_this(), is_indexed)); + m_loader_mod_list.reset(new ModFolderModel(modsRoot(), this, is_indexed)); m_loader_mod_list->disableInteraction(isRunning()); connect(this, &BaseInstance::runningStatusChanged, m_loader_mod_list.get(), &ModFolderModel::disableInteraction); } return m_loader_mod_list; } -std::shared_ptr MinecraftInstance::coreModList() const +std::shared_ptr MinecraftInstance::coreModList() { if (!m_core_mod_list) { bool is_indexed = !APPLICATION->settings()->get("ModMetadataDisabled").toBool(); - m_core_mod_list.reset(new ModFolderModel(coreModsDir(), shared_from_this(), is_indexed)); + m_core_mod_list.reset(new ModFolderModel(coreModsDir(), this, is_indexed)); m_core_mod_list->disableInteraction(isRunning()); connect(this, &BaseInstance::runningStatusChanged, m_core_mod_list.get(), &ModFolderModel::disableInteraction); } return m_core_mod_list; } -std::shared_ptr MinecraftInstance::nilModList() const +std::shared_ptr MinecraftInstance::nilModList() { if (!m_nil_mod_list) { bool is_indexed = !APPLICATION->settings()->get("ModMetadataDisabled").toBool(); - m_nil_mod_list.reset(new ModFolderModel(nilModsDir(), shared_from_this(), is_indexed, false)); + m_nil_mod_list.reset(new ModFolderModel(nilModsDir(), this, is_indexed, false)); m_nil_mod_list->disableInteraction(isRunning()); connect(this, &BaseInstance::runningStatusChanged, m_nil_mod_list.get(), &ModFolderModel::disableInteraction); } return m_nil_mod_list; } -std::shared_ptr MinecraftInstance::resourcePackList() const +std::shared_ptr MinecraftInstance::resourcePackList() { if (!m_resource_pack_list) { - m_resource_pack_list.reset(new ResourcePackFolderModel(resourcePacksDir(), shared_from_this())); + m_resource_pack_list.reset(new ResourcePackFolderModel(resourcePacksDir(), this)); } return m_resource_pack_list; } -std::shared_ptr MinecraftInstance::texturePackList() const +std::shared_ptr MinecraftInstance::texturePackList() { if (!m_texture_pack_list) { - m_texture_pack_list.reset(new TexturePackFolderModel(texturePacksDir(), shared_from_this())); + m_texture_pack_list.reset(new TexturePackFolderModel(texturePacksDir(), this)); } return m_texture_pack_list; } -std::shared_ptr MinecraftInstance::shaderPackList() const +std::shared_ptr MinecraftInstance::shaderPackList() { if (!m_shader_pack_list) { - m_shader_pack_list.reset(new ShaderPackFolderModel(shaderPacksDir(), shared_from_this())); + m_shader_pack_list.reset(new ShaderPackFolderModel(shaderPacksDir(), this)); } return m_shader_pack_list; } -std::shared_ptr MinecraftInstance::worldList() const +std::shared_ptr MinecraftInstance::worldList() { if (!m_world_list) { - m_world_list.reset(new WorldList(worldDir(), shared_from_this())); + m_world_list.reset(new WorldList(worldDir(), this)); } return m_world_list; } -std::shared_ptr MinecraftInstance::gameOptionsModel() const +std::shared_ptr MinecraftInstance::gameOptionsModel() { if (!m_game_options) { diff --git a/launcher/minecraft/MinecraftInstance.h b/launcher/minecraft/MinecraftInstance.h index a75fa481..068b3008 100644 --- a/launcher/minecraft/MinecraftInstance.h +++ b/launcher/minecraft/MinecraftInstance.h @@ -115,14 +115,14 @@ public: std::shared_ptr getPackProfile() const; ////// Mod Lists ////// - std::shared_ptr loaderModList() const; - std::shared_ptr coreModList() const; - std::shared_ptr nilModList() const; - std::shared_ptr resourcePackList() const; - std::shared_ptr texturePackList() const; - std::shared_ptr shaderPackList() const; - std::shared_ptr worldList() const; - std::shared_ptr gameOptionsModel() const; + std::shared_ptr loaderModList(); + std::shared_ptr coreModList(); + std::shared_ptr nilModList(); + std::shared_ptr resourcePackList(); + std::shared_ptr texturePackList(); + std::shared_ptr shaderPackList(); + std::shared_ptr worldList(); + std::shared_ptr gameOptionsModel(); ////// Launch stuff ////// Task::Ptr createUpdateTask(Net::Mode mode) override; diff --git a/launcher/minecraft/WorldList.cpp b/launcher/minecraft/WorldList.cpp index df6b4ecc..0feee299 100644 --- a/launcher/minecraft/WorldList.cpp +++ b/launcher/minecraft/WorldList.cpp @@ -45,7 +45,7 @@ #include #include -WorldList::WorldList(const QString &dir, std::shared_ptr instance) +WorldList::WorldList(const QString &dir, BaseInstance* instance) : QAbstractListModel(), m_instance(instance), m_dir(dir) { FS::ensureFolderPathExists(m_dir.absolutePath()); diff --git a/launcher/minecraft/WorldList.h b/launcher/minecraft/WorldList.h index 10fb4e3c..96b64193 100644 --- a/launcher/minecraft/WorldList.h +++ b/launcher/minecraft/WorldList.h @@ -50,7 +50,7 @@ public: IconFileRole }; - WorldList(const QString &dir, std::shared_ptr instance); + WorldList(const QString &dir, BaseInstance* instance); virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; @@ -128,7 +128,7 @@ signals: void changed(); protected: - std::shared_ptr m_instance; + BaseInstance* m_instance; QFileSystemWatcher *m_watcher; bool is_watching; QDir m_dir; diff --git a/launcher/minecraft/mod/ModFolderModel.cpp b/launcher/minecraft/mod/ModFolderModel.cpp index 6ae25d33..5e3b31e0 100644 --- a/launcher/minecraft/mod/ModFolderModel.cpp +++ b/launcher/minecraft/mod/ModFolderModel.cpp @@ -54,7 +54,7 @@ #include "minecraft/mod/tasks/ModFolderLoadTask.h" #include "modplatform/ModIndex.h" -ModFolderModel::ModFolderModel(const QString& dir, std::shared_ptr instance, bool is_indexed, bool create_dir) +ModFolderModel::ModFolderModel(const QString& dir, BaseInstance* instance, bool is_indexed, bool create_dir) : ResourceFolderModel(QDir(dir), instance, nullptr, create_dir), m_is_indexed(is_indexed) { m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::VERSION, SortType::DATE, SortType::PROVIDER }; diff --git a/launcher/minecraft/mod/ModFolderModel.h b/launcher/minecraft/mod/ModFolderModel.h index 46f5087f..d337fe29 100644 --- a/launcher/minecraft/mod/ModFolderModel.h +++ b/launcher/minecraft/mod/ModFolderModel.h @@ -75,7 +75,7 @@ public: Enable, Toggle }; - ModFolderModel(const QString &dir, std::shared_ptr instance, bool is_indexed = false, bool create_dir = true); + ModFolderModel(const QString &dir, BaseInstance* instance, bool is_indexed = false, bool create_dir = true); QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; diff --git a/launcher/minecraft/mod/ResourceFolderModel.cpp b/launcher/minecraft/mod/ResourceFolderModel.cpp index e1973468..d2d875e4 100644 --- a/launcher/minecraft/mod/ResourceFolderModel.cpp +++ b/launcher/minecraft/mod/ResourceFolderModel.cpp @@ -16,7 +16,7 @@ #include "tasks/Task.h" -ResourceFolderModel::ResourceFolderModel(QDir dir, std::shared_ptr instance, QObject* parent, bool create_dir) +ResourceFolderModel::ResourceFolderModel(QDir dir, BaseInstance* instance, QObject* parent, bool create_dir) : QAbstractListModel(parent), m_dir(dir), m_instance(instance), m_watcher(this) { if (create_dir) { diff --git a/launcher/minecraft/mod/ResourceFolderModel.h b/launcher/minecraft/mod/ResourceFolderModel.h index fdf5f331..0a35e1bc 100644 --- a/launcher/minecraft/mod/ResourceFolderModel.h +++ b/launcher/minecraft/mod/ResourceFolderModel.h @@ -26,7 +26,7 @@ class QSortFilterProxyModel; class ResourceFolderModel : public QAbstractListModel { Q_OBJECT public: - ResourceFolderModel(QDir, std::shared_ptr, QObject* parent = nullptr, bool create_dir = true); + ResourceFolderModel(QDir, BaseInstance* instance, QObject* parent = nullptr, bool create_dir = true); ~ResourceFolderModel() override; /** Starts watching the paths for changes. @@ -191,7 +191,7 @@ class ResourceFolderModel : public QAbstractListModel { bool m_can_interact = true; QDir m_dir; - std::shared_ptr m_instance; + BaseInstance* m_instance; QFileSystemWatcher m_watcher; bool m_is_watching = false; diff --git a/launcher/minecraft/mod/ResourcePackFolderModel.cpp b/launcher/minecraft/mod/ResourcePackFolderModel.cpp index 6eba4e2e..c12d1f23 100644 --- a/launcher/minecraft/mod/ResourcePackFolderModel.cpp +++ b/launcher/minecraft/mod/ResourcePackFolderModel.cpp @@ -45,7 +45,7 @@ #include "minecraft/mod/tasks/BasicFolderLoadTask.h" #include "minecraft/mod/tasks/LocalResourcePackParseTask.h" -ResourcePackFolderModel::ResourcePackFolderModel(const QString& dir, std::shared_ptr instance) +ResourcePackFolderModel::ResourcePackFolderModel(const QString& dir, BaseInstance* instance) : ResourceFolderModel(QDir(dir), instance) { m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::PACK_FORMAT, SortType::DATE }; diff --git a/launcher/minecraft/mod/ResourcePackFolderModel.h b/launcher/minecraft/mod/ResourcePackFolderModel.h index 66d5a074..db4b14fb 100644 --- a/launcher/minecraft/mod/ResourcePackFolderModel.h +++ b/launcher/minecraft/mod/ResourcePackFolderModel.h @@ -17,7 +17,7 @@ public: NUM_COLUMNS }; - explicit ResourcePackFolderModel(const QString &dir, std::shared_ptr instance); + explicit ResourcePackFolderModel(const QString &dir, BaseInstance* instance); [[nodiscard]] QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; diff --git a/launcher/minecraft/mod/ShaderPackFolderModel.h b/launcher/minecraft/mod/ShaderPackFolderModel.h index 6f3f2811..dc5acf80 100644 --- a/launcher/minecraft/mod/ShaderPackFolderModel.h +++ b/launcher/minecraft/mod/ShaderPackFolderModel.h @@ -6,7 +6,7 @@ class ShaderPackFolderModel : public ResourceFolderModel { Q_OBJECT public: - explicit ShaderPackFolderModel(const QString& dir, std::shared_ptr instance) + explicit ShaderPackFolderModel(const QString& dir, BaseInstance* instance) : ResourceFolderModel(QDir(dir), instance) {} }; diff --git a/launcher/minecraft/mod/TexturePackFolderModel.cpp b/launcher/minecraft/mod/TexturePackFolderModel.cpp index 1e218537..c6609ed1 100644 --- a/launcher/minecraft/mod/TexturePackFolderModel.cpp +++ b/launcher/minecraft/mod/TexturePackFolderModel.cpp @@ -39,7 +39,7 @@ #include "minecraft/mod/tasks/BasicFolderLoadTask.h" #include "minecraft/mod/tasks/LocalTexturePackParseTask.h" -TexturePackFolderModel::TexturePackFolderModel(const QString& dir, std::shared_ptr instance) +TexturePackFolderModel::TexturePackFolderModel(const QString& dir, BaseInstance* instance) : ResourceFolderModel(QDir(dir), instance) {} diff --git a/launcher/minecraft/mod/TexturePackFolderModel.h b/launcher/minecraft/mod/TexturePackFolderModel.h index 246997bd..425a71e4 100644 --- a/launcher/minecraft/mod/TexturePackFolderModel.h +++ b/launcher/minecraft/mod/TexturePackFolderModel.h @@ -43,7 +43,7 @@ class TexturePackFolderModel : public ResourceFolderModel Q_OBJECT public: - explicit TexturePackFolderModel(const QString &dir, std::shared_ptr instance); + explicit TexturePackFolderModel(const QString &dir, BaseInstance* instance); [[nodiscard]] Task* createUpdateTask() override; [[nodiscard]] Task* createParseTask(Resource&) override; }; diff --git a/launcher/ui/dialogs/NewInstanceDialog.cpp b/launcher/ui/dialogs/NewInstanceDialog.cpp index 64ed7673..aafaf220 100644 --- a/launcher/ui/dialogs/NewInstanceDialog.cpp +++ b/launcher/ui/dialogs/NewInstanceDialog.cpp @@ -99,7 +99,7 @@ NewInstanceDialog::NewInstanceDialog(const QString & initialGroup, const QString // 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 = new PageContainer(this, {}, this); m_container->setSizePolicy(QSizePolicy::Policy::Preferred, QSizePolicy::Policy::Expanding); m_container->layout()->setContentsMargins(0, 0, 0, 0); ui->verticalLayout->insertWidget(2, m_container); diff --git a/launcher/ui/dialogs/ResourceDownloadDialog.cpp b/launcher/ui/dialogs/ResourceDownloadDialog.cpp index edb7d063..d2a8d33e 100644 --- a/launcher/ui/dialogs/ResourceDownloadDialog.cpp +++ b/launcher/ui/dialogs/ResourceDownloadDialog.cpp @@ -89,7 +89,7 @@ void ResourceDownloadDialog::reject() // won't work with subclasses if we put it in this ctor. void ResourceDownloadDialog::initializeContainer() { - m_container = new PageContainer(this); + m_container = new PageContainer(this, {}, this); m_container->setSizePolicy(QSizePolicy::Policy::Preferred, QSizePolicy::Policy::Expanding); m_container->layout()->setContentsMargins(0, 0, 0, 0); m_vertical_layout.addWidget(m_container); diff --git a/launcher/ui/widgets/PageContainer.cpp b/launcher/ui/widgets/PageContainer.cpp index 0a06a351..b9b17b42 100644 --- a/launcher/ui/widgets/PageContainer.cpp +++ b/launcher/ui/widgets/PageContainer.cpp @@ -87,7 +87,9 @@ PageContainer::PageContainer(BasePageProvider *pageProvider, QString defaultId, auto pages = pageProvider->getPages(); for (auto page : pages) { - page->stackIndex = m_pageStack->addWidget(dynamic_cast(page)); + auto widget = dynamic_cast(page); + widget->setParent(this); + page->stackIndex = m_pageStack->addWidget(widget); page->listIndex = counter; page->setParentContainer(this); counter++; diff --git a/tests/ResourceFolderModel_test.cpp b/tests/ResourceFolderModel_test.cpp index 054d81c4..962d89f1 100644 --- a/tests/ResourceFolderModel_test.cpp +++ b/tests/ResourceFolderModel_test.cpp @@ -90,9 +90,7 @@ slots: QEventLoop loop; - InstancePtr instance; - - ModFolderModel m(tempDir.path(), instance, true); + ModFolderModel m(tempDir.path(), nullptr, true); connect(&m, &ModFolderModel::updateFinished, &loop, &QEventLoop::quit); @@ -116,8 +114,7 @@ slots: QString folder = source + '/'; QTemporaryDir tempDir; QEventLoop loop; - InstancePtr instance; - ModFolderModel m(tempDir.path(), instance, true); + ModFolderModel m(tempDir.path(), nullptr, true); connect(&m, &ModFolderModel::updateFinished, &loop, &QEventLoop::quit); @@ -140,8 +137,7 @@ slots: void test_addFromWatch() { QString source = QFINDTESTDATA("testdata/ResourceFolderModel"); - InstancePtr instance; - ModFolderModel model(source, instance); + ModFolderModel model(source, nullptr); QCOMPARE(model.size(), 0); @@ -161,9 +157,7 @@ slots: QString file_mod = QFINDTESTDATA("testdata/ResourceFolderModel/supercoolmod.jar"); QTemporaryDir tmp; - InstancePtr instance; - - ResourceFolderModel model(QDir(tmp.path()), instance); + ResourceFolderModel model(QDir(tmp.path()), nullptr); QCOMPARE(model.size(), 0); @@ -214,8 +208,7 @@ slots: QString file_mod = QFINDTESTDATA("testdata/ResourceFolderModel/supercoolmod.jar"); QTemporaryDir tmp; - InstancePtr instance; - ResourceFolderModel model(tmp.path(), instance); + ResourceFolderModel model(tmp.path(), nullptr); QCOMPARE(model.size(), 0); -- cgit