From fff52cb24773c369bd02af2710611290a0a2380f Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Wed, 11 Jan 2023 13:50:57 +0100 Subject: [PATCH 001/185] feat: add button to import component JSONs Signed-off-by: Sefa Eyeoglu --- launcher/minecraft/PackProfile.cpp | 32 ++++++++++++++++++++++ launcher/minecraft/PackProfile.h | 4 +++ launcher/ui/pages/instance/VersionPage.cpp | 11 ++++++++ launcher/ui/pages/instance/VersionPage.h | 1 + launcher/ui/pages/instance/VersionPage.ui | 13 +++++++-- 5 files changed, 59 insertions(+), 2 deletions(-) diff --git a/launcher/minecraft/PackProfile.cpp b/launcher/minecraft/PackProfile.cpp index 43fa3f8d..2028b236 100644 --- a/launcher/minecraft/PackProfile.cpp +++ b/launcher/minecraft/PackProfile.cpp @@ -49,6 +49,7 @@ #include "minecraft/OneSixVersionFormat.h" #include "FileSystem.h" #include "minecraft/MinecraftInstance.h" +#include "minecraft/ProfileUtils.h" #include "Json.h" #include "PackProfile.h" @@ -737,6 +738,11 @@ void PackProfile::installCustomJar(QString selectedFile) installCustomJar_internal(selectedFile); } +void PackProfile::installComponents(QStringList selectedFiles) +{ + installComponents_internal(selectedFiles); +} + void PackProfile::installAgents(QStringList selectedFiles) { installAgents_internal(selectedFiles); @@ -939,6 +945,32 @@ bool PackProfile::installCustomJar_internal(QString filepath) return true; } +bool PackProfile::installComponents_internal(QStringList filepaths) +{ + const QString patchDir = FS::PathCombine(d->m_instance->instanceRoot(), "patches"); + if (!FS::ensureFolderPathExists(patchDir)) + return false; + + for (const QString& source : filepaths) { + const QFileInfo sourceInfo(source); + + auto versionFile = ProfileUtils::parseJsonFile(sourceInfo, false); + const QString target = FS::PathCombine(patchDir, versionFile->uid + ".json"); + + if (!QFile::copy(source, target)) + { + return false; + } + + appendComponent(new Component(this, versionFile->uid, versionFile)); + } + + scheduleSave(); + invalidateLaunchProfile(); + + return true; +} + bool PackProfile::installAgents_internal(QStringList filepaths) { // FIXME code duplication diff --git a/launcher/minecraft/PackProfile.h b/launcher/minecraft/PackProfile.h index 2330cca1..35af9a56 100644 --- a/launcher/minecraft/PackProfile.h +++ b/launcher/minecraft/PackProfile.h @@ -86,6 +86,9 @@ public: /// install a jar/zip as a replacement for the main jar void installCustomJar(QString selectedFile); + /// install MMC/Prism component files + void installComponents(QStringList selectedFiles); + /// install Java agent files void installAgents(QStringList selectedFiles); @@ -171,6 +174,7 @@ private: bool load(); bool installJarMods_internal(QStringList filepaths); bool installCustomJar_internal(QString filepath); + bool installComponents_internal(QStringList filepaths); bool installAgents_internal(QStringList filepaths); bool removeComponent_internal(ComponentPtr patch); diff --git a/launcher/ui/pages/instance/VersionPage.cpp b/launcher/ui/pages/instance/VersionPage.cpp index d200652a..07a97813 100644 --- a/launcher/ui/pages/instance/VersionPage.cpp +++ b/launcher/ui/pages/instance/VersionPage.cpp @@ -282,6 +282,7 @@ void VersionPage::updateButtons(int row) ui->actionRevert->setEnabled(controlsEnabled && patch && patch->isRevertible()); ui->actionDownload_All->setEnabled(controlsEnabled); ui->actionAdd_Empty->setEnabled(controlsEnabled); + ui->actionImport_Components->setEnabled(controlsEnabled); ui->actionReload->setEnabled(controlsEnabled); ui->actionInstall_mods->setEnabled(controlsEnabled); ui->actionReplace_Minecraft_jar->setEnabled(controlsEnabled); @@ -375,6 +376,16 @@ void VersionPage::on_actionReplace_Minecraft_jar_triggered() updateButtons(); } +void VersionPage::on_actionImport_Components_triggered() +{ + QStringList list = GuiUtil::BrowseForFiles("component", tr("Select components"), tr("Components (*.json)"), + APPLICATION->settings()->get("CentralModsDir").toString(), this->parentWidget()); + + if (!list.isEmpty()) + m_profile->installComponents(list); + + updateButtons(); +} void VersionPage::on_actionAdd_Agents_triggered() { diff --git a/launcher/ui/pages/instance/VersionPage.h b/launcher/ui/pages/instance/VersionPage.h index 166f36bb..a56f016d 100644 --- a/launcher/ui/pages/instance/VersionPage.h +++ b/launcher/ui/pages/instance/VersionPage.h @@ -86,6 +86,7 @@ private slots: void on_actionMove_down_triggered(); void on_actionAdd_to_Minecraft_jar_triggered(); void on_actionReplace_Minecraft_jar_triggered(); + void on_actionImport_Components_triggered(); void on_actionAdd_Agents_triggered(); void on_actionRevert_triggered(); void on_actionEdit_triggered(); diff --git a/launcher/ui/pages/instance/VersionPage.ui b/launcher/ui/pages/instance/VersionPage.ui index 4cd50885..4777eafe 100644 --- a/launcher/ui/pages/instance/VersionPage.ui +++ b/launcher/ui/pages/instance/VersionPage.ui @@ -108,6 +108,7 @@ + @@ -226,10 +227,10 @@ - Add Agents + Add Agents - Add Java agents. + Add Java agents. @@ -272,6 +273,14 @@ Open the instance's local libraries folder. + + + Import Components + + + Import existing component JSON files. + + From cd893e18d24d61c62f048d0c82c85b981f6e9a65 Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Sat, 14 Jan 2023 17:21:52 +0100 Subject: [PATCH 002/185] chore: update license headers Signed-off-by: Sefa Eyeoglu --- launcher/minecraft/PackProfile.cpp | 7 +++++-- launcher/minecraft/PackProfile.h | 7 +++++-- launcher/ui/pages/instance/VersionPage.cpp | 7 +++++-- launcher/ui/pages/instance/VersionPage.h | 6 +++++- 4 files changed, 20 insertions(+), 7 deletions(-) diff --git a/launcher/minecraft/PackProfile.cpp b/launcher/minecraft/PackProfile.cpp index 2028b236..270f3d22 100644 --- a/launcher/minecraft/PackProfile.cpp +++ b/launcher/minecraft/PackProfile.cpp @@ -1,7 +1,10 @@ -// SPDX-License-Identifier: GPL-3.0-only +// SPDX-FileCopyrightText: 2022-2023 Sefa Eyeoglu +// +// SPDX-License-Identifier: GPL-3.0-only AND Apache-2.0 + /* * Prism Launcher - Minecraft Launcher - * Copyright (C) 2022 Sefa Eyeoglu + * Copyright (C) 2022-2023 Sefa Eyeoglu * Copyright (C) 2022 TheKodeToad * * This program is free software: you can redistribute it and/or modify diff --git a/launcher/minecraft/PackProfile.h b/launcher/minecraft/PackProfile.h index 35af9a56..8b885aa8 100644 --- a/launcher/minecraft/PackProfile.h +++ b/launcher/minecraft/PackProfile.h @@ -1,7 +1,10 @@ -// SPDX-License-Identifier: GPL-3.0-only +// SPDX-FileCopyrightText: 2022-2023 Sefa Eyeoglu +// +// SPDX-License-Identifier: GPL-3.0-only AND Apache-2.0 + /* * Prism Launcher - Minecraft Launcher - * Copyright (C) 2022 Sefa Eyeoglu + * Copyright (C) 2022-2023 Sefa Eyeoglu * Copyright (C) 2022 TheKodeToad * * This program is free software: you can redistribute it and/or modify diff --git a/launcher/ui/pages/instance/VersionPage.cpp b/launcher/ui/pages/instance/VersionPage.cpp index 07a97813..bce50a09 100644 --- a/launcher/ui/pages/instance/VersionPage.cpp +++ b/launcher/ui/pages/instance/VersionPage.cpp @@ -1,8 +1,11 @@ -// SPDX-License-Identifier: GPL-3.0-only +// SPDX-FileCopyrightText: 2022-2023 Sefa Eyeoglu +// +// SPDX-License-Identifier: GPL-3.0-only AND Apache-2.0 + /* * Prism Launcher - Minecraft Launcher * Copyright (c) 2022 Jamie Mansfield - * Copyright (C) 2022 Sefa Eyeoglu + * Copyright (C) 2022-2023 Sefa Eyeoglu * Copyright (C) 2022 TheKodeToad * * This program is free software: you can redistribute it and/or modify diff --git a/launcher/ui/pages/instance/VersionPage.h b/launcher/ui/pages/instance/VersionPage.h index a56f016d..ca98dfd1 100644 --- a/launcher/ui/pages/instance/VersionPage.h +++ b/launcher/ui/pages/instance/VersionPage.h @@ -1,7 +1,11 @@ -// SPDX-License-Identifier: GPL-3.0-only +// SPDX-FileCopyrightText: 2022-2023 Sefa Eyeoglu +// +// SPDX-License-Identifier: GPL-3.0-only AND Apache-2.0 + /* * Prism Launcher - Minecraft Launcher * Copyright (c) 2022 Jamie Mansfield + * Copyright (C) 2022-2023 Sefa Eyeoglu * Copyright (C) 2022 TheKodeToad * * This program is free software: you can redistribute it and/or modify From 199a7df807994ded1469cc893e6c68c21307444f Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Wed, 25 Jan 2023 10:43:23 +0100 Subject: [PATCH 003/185] refactor: add error handling to component import Signed-off-by: Sefa Eyeoglu --- launcher/minecraft/PackProfile.cpp | 56 +++++++++++----------- launcher/minecraft/PackProfile.h | 3 +- launcher/ui/pages/instance/VersionPage.cpp | 8 +++- 3 files changed, 35 insertions(+), 32 deletions(-) diff --git a/launcher/minecraft/PackProfile.cpp b/launcher/minecraft/PackProfile.cpp index 270f3d22..54fbf7f3 100644 --- a/launcher/minecraft/PackProfile.cpp +++ b/launcher/minecraft/PackProfile.cpp @@ -733,21 +733,47 @@ void PackProfile::invalidateLaunchProfile() void PackProfile::installJarMods(QStringList selectedFiles) { + // FIXME: get rid of _internal installJarMods_internal(selectedFiles); } void PackProfile::installCustomJar(QString selectedFile) { + // FIXME: get rid of _internal installCustomJar_internal(selectedFile); } -void PackProfile::installComponents(QStringList selectedFiles) +bool PackProfile::installComponents(QStringList selectedFiles) { - installComponents_internal(selectedFiles); + const QString patchDir = FS::PathCombine(d->m_instance->instanceRoot(), "patches"); + if (!FS::ensureFolderPathExists(patchDir)) + return false; + + bool result = true; + for (const QString& source : selectedFiles) { + const QFileInfo sourceInfo(source); + + auto versionFile = ProfileUtils::parseJsonFile(sourceInfo, false); + const QString target = FS::PathCombine(patchDir, versionFile->uid + ".json"); + + if (!QFile::copy(source, target)) { + qWarning() << "Component" << source << "could not be copied to target" << target; + result = false; + continue; + } + + appendComponent(new Component(this, versionFile->uid, versionFile)); + } + + scheduleSave(); + invalidateLaunchProfile(); + + return result; } void PackProfile::installAgents(QStringList selectedFiles) { + // FIXME: get rid of _internal installAgents_internal(selectedFiles); } @@ -948,32 +974,6 @@ bool PackProfile::installCustomJar_internal(QString filepath) return true; } -bool PackProfile::installComponents_internal(QStringList filepaths) -{ - const QString patchDir = FS::PathCombine(d->m_instance->instanceRoot(), "patches"); - if (!FS::ensureFolderPathExists(patchDir)) - return false; - - for (const QString& source : filepaths) { - const QFileInfo sourceInfo(source); - - auto versionFile = ProfileUtils::parseJsonFile(sourceInfo, false); - const QString target = FS::PathCombine(patchDir, versionFile->uid + ".json"); - - if (!QFile::copy(source, target)) - { - return false; - } - - appendComponent(new Component(this, versionFile->uid, versionFile)); - } - - scheduleSave(); - invalidateLaunchProfile(); - - return true; -} - bool PackProfile::installAgents_internal(QStringList filepaths) { // FIXME code duplication diff --git a/launcher/minecraft/PackProfile.h b/launcher/minecraft/PackProfile.h index 8b885aa8..e5b398db 100644 --- a/launcher/minecraft/PackProfile.h +++ b/launcher/minecraft/PackProfile.h @@ -90,7 +90,7 @@ public: void installCustomJar(QString selectedFile); /// install MMC/Prism component files - void installComponents(QStringList selectedFiles); + bool installComponents(QStringList selectedFiles); /// install Java agent files void installAgents(QStringList selectedFiles); @@ -177,7 +177,6 @@ private: bool load(); bool installJarMods_internal(QStringList filepaths); bool installCustomJar_internal(QString filepath); - bool installComponents_internal(QStringList filepaths); bool installAgents_internal(QStringList filepaths); bool removeComponent_internal(ComponentPtr patch); diff --git a/launcher/ui/pages/instance/VersionPage.cpp b/launcher/ui/pages/instance/VersionPage.cpp index bce50a09..f92a7660 100644 --- a/launcher/ui/pages/instance/VersionPage.cpp +++ b/launcher/ui/pages/instance/VersionPage.cpp @@ -384,8 +384,12 @@ void VersionPage::on_actionImport_Components_triggered() QStringList list = GuiUtil::BrowseForFiles("component", tr("Select components"), tr("Components (*.json)"), APPLICATION->settings()->get("CentralModsDir").toString(), this->parentWidget()); - if (!list.isEmpty()) - m_profile->installComponents(list); + if (!list.isEmpty()) { + if (!m_profile->installComponents(list)) { + QMessageBox::warning(this, tr("Failed to import components"), + tr("Some components could not be imported. Check logs for details")); + } + } updateButtons(); } From edaa66f6223b1a4fc21cb26ae5e78f23893e56d7 Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Sat, 4 Feb 2023 01:06:15 +0100 Subject: [PATCH 004/185] fix: use /usr/bin/env bash in launch script This should make it possible to run these scripts on any system, as /bin/bash is not standard! Notably this fixes the script on NixOS. Signed-off-by: Sefa Eyeoglu --- launcher/Launcher.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/Launcher.in b/launcher/Launcher.in index 68fac26a..1a23f255 100755 --- a/launcher/Launcher.in +++ b/launcher/Launcher.in @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash # Basic start script for running the launcher with the libs packaged with it. function printerror { From c3ea303a3742c886aae9e05d2e5f5fbb497260a1 Mon Sep 17 00:00:00 2001 From: flow Date: Fri, 16 Dec 2022 20:26:10 -0300 Subject: [PATCH 005/185] 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 + .../ui/dialogs/ResourceDownloadDialog.cpp | 27 +++++ launcher/ui/dialogs/ResourceDownloadDialog.h | 20 ++++ .../ui/pages/instance/ResourcePackPage.cpp | 102 ++++++++++++++++++ launcher/ui/pages/instance/ResourcePackPage.h | 18 +--- .../pages/modplatform/ResourcePackModel.cpp | 42 ++++++++ .../ui/pages/modplatform/ResourcePackModel.h | 39 +++++++ .../ui/pages/modplatform/ResourcePackPage.cpp | 42 ++++++++ .../ui/pages/modplatform/ResourcePackPage.h | 48 +++++++++ .../modplatform/flame/FlameResourceModels.cpp | 24 +++++ .../modplatform/flame/FlameResourceModels.h | 19 ++++ .../modplatform/flame/FlameResourcePages.cpp | 57 ++++++++-- .../modplatform/flame/FlameResourcePages.h | 28 +++++ .../modrinth/ModrinthResourceModels.cpp | 22 ++++ .../modrinth/ModrinthResourceModels.h | 21 +++- .../modrinth/ModrinthResourcePages.cpp | 19 ++++ .../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 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 From b724607e31d102c50cb42225b4a31f2932b2eb61 Mon Sep 17 00:00:00 2001 From: flow Date: Fri, 30 Dec 2022 14:06:07 -0300 Subject: [PATCH 006/185] 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 + .../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 ++-- .../ui/pages/modplatform/ShaderPackModel.cpp | 42 ++++++++ .../ui/pages/modplatform/ShaderPackModel.h | 39 ++++++++ .../ui/pages/modplatform/ShaderPackPage.cpp | 50 ++++++++++ .../ui/pages/modplatform/ShaderPackPage.h | 50 ++++++++++ .../modplatform/flame/FlameResourcePages.h | 1 + .../modrinth/ModrinthResourceModels.cpp | 22 +++++ .../modrinth/ModrinthResourceModels.h | 18 ++++ .../modrinth/ModrinthResourcePages.cpp | 19 ++++ .../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 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 From ada5e88eb933a41691121316c78cd2e564965fa0 Mon Sep 17 00:00:00 2001 From: flow Date: Sun, 29 Jan 2023 18:07:49 -0300 Subject: [PATCH 007/185] 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 + .../ui/dialogs/ResourceDownloadDialog.cpp | 27 +++++ launcher/ui/dialogs/ResourceDownloadDialog.h | 20 ++++ .../ui/pages/instance/TexturePackPage.cpp | 104 ++++++++++++++++++ launcher/ui/pages/instance/TexturePackPage.h | 19 +--- launcher/ui/pages/modplatform/ResourceModel.h | 2 +- .../ui/pages/modplatform/ResourcePackPage.cpp | 2 +- .../ui/pages/modplatform/ResourcePackPage.h | 2 +- .../ui/pages/modplatform/TexturePackModel.cpp | 80 ++++++++++++++ .../ui/pages/modplatform/TexturePackModel.h | 23 ++++ .../ui/pages/modplatform/TexturePackPage.h | 46 ++++++++ .../modplatform/flame/FlameResourceModels.cpp | 58 ++++++++++ .../modplatform/flame/FlameResourceModels.h | 21 ++++ .../modplatform/flame/FlameResourcePages.cpp | 40 +++++++ .../modplatform/flame/FlameResourcePages.h | 29 ++++- .../modrinth/ModrinthResourceModels.cpp | 22 ++++ .../modrinth/ModrinthResourceModels.h | 18 +++ .../modrinth/ModrinthResourcePages.cpp | 19 ++++ .../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 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 From 46c6cc2d2b59ee3c81abe690218d8f60e6e02c76 Mon Sep 17 00:00:00 2001 From: flow Date: Sun, 29 Jan 2023 20:40:55 -0300 Subject: [PATCH 008/185] chore: add my copyright headers .-. Signed-off-by: flow --- launcher/ui/pages/instance/ResourcePackPage.cpp | 6 ++++-- launcher/ui/pages/instance/ResourcePackPage.h | 6 ++++-- launcher/ui/pages/instance/ShaderPackPage.cpp | 6 ++++-- launcher/ui/pages/instance/ShaderPackPage.h | 6 ++++-- launcher/ui/pages/instance/TexturePackPage.cpp | 6 ++++-- launcher/ui/pages/instance/TexturePackPage.h | 6 ++++-- launcher/ui/pages/modplatform/ResourcePackModel.cpp | 4 ++++ launcher/ui/pages/modplatform/ResourcePackModel.h | 4 ++++ launcher/ui/pages/modplatform/ResourcePackPage.cpp | 4 ++++ launcher/ui/pages/modplatform/ResourcePackPage.h | 4 ++++ launcher/ui/pages/modplatform/ShaderPackModel.cpp | 4 ++++ launcher/ui/pages/modplatform/ShaderPackModel.h | 4 ++++ launcher/ui/pages/modplatform/ShaderPackPage.cpp | 4 ++++ launcher/ui/pages/modplatform/ShaderPackPage.h | 4 ++++ launcher/ui/pages/modplatform/TexturePackModel.cpp | 4 ++++ launcher/ui/pages/modplatform/TexturePackModel.h | 4 ++++ launcher/ui/pages/modplatform/TexturePackPage.h | 4 ++++ 17 files changed, 68 insertions(+), 12 deletions(-) diff --git a/launcher/ui/pages/instance/ResourcePackPage.cpp b/launcher/ui/pages/instance/ResourcePackPage.cpp index e705f29e..f46a7939 100644 --- a/launcher/ui/pages/instance/ResourcePackPage.cpp +++ b/launcher/ui/pages/instance/ResourcePackPage.cpp @@ -1,6 +1,8 @@ -// SPDX-License-Identifier: GPL-3.0-only +// SPDX-FileCopyrightText: 2023 flowln +// +// SPDX-License-Identifier: GPL-3.0-only AND Apache-2.0 /* - * PolyMC - Minecraft Launcher + * Prism Launcher - Minecraft Launcher * Copyright (c) 2022 Jamie Mansfield * * This program is free software: you can redistribute it and/or modify diff --git a/launcher/ui/pages/instance/ResourcePackPage.h b/launcher/ui/pages/instance/ResourcePackPage.h index fc05158c..b04aa2e9 100644 --- a/launcher/ui/pages/instance/ResourcePackPage.h +++ b/launcher/ui/pages/instance/ResourcePackPage.h @@ -1,6 +1,8 @@ -// SPDX-License-Identifier: GPL-3.0-only +// SPDX-FileCopyrightText: 2023 flowln +// +// SPDX-License-Identifier: GPL-3.0-only AND Apache-2.0 /* - * PolyMC - Minecraft Launcher + * Prism Launcher - Minecraft Launcher * Copyright (c) 2022 Jamie Mansfield * * This program is free software: you can redistribute it and/or modify diff --git a/launcher/ui/pages/instance/ShaderPackPage.cpp b/launcher/ui/pages/instance/ShaderPackPage.cpp index 2dde4dc7..2d0c10aa 100644 --- a/launcher/ui/pages/instance/ShaderPackPage.cpp +++ b/launcher/ui/pages/instance/ShaderPackPage.cpp @@ -1,6 +1,8 @@ -// SPDX-License-Identifier: GPL-3.0-only +// SPDX-FileCopyrightText: 2023 flowln +// +// SPDX-License-Identifier: GPL-3.0-only AND Apache-2.0 /* - * PolyMC - Minecraft Launcher + * Prism Launcher - Minecraft Launcher * Copyright (c) 2022 Jamie Mansfield * * This program is free software: you can redistribute it and/or modify diff --git a/launcher/ui/pages/instance/ShaderPackPage.h b/launcher/ui/pages/instance/ShaderPackPage.h index 09895949..a779fd8c 100644 --- a/launcher/ui/pages/instance/ShaderPackPage.h +++ b/launcher/ui/pages/instance/ShaderPackPage.h @@ -1,6 +1,8 @@ -// SPDX-License-Identifier: GPL-3.0-only +// SPDX-FileCopyrightText: 2023 flowln +// +// SPDX-License-Identifier: GPL-3.0-only AND Apache-2.0 /* - * PolyMC - Minecraft Launcher + * Prism Launcher - Minecraft Launcher * Copyright (c) 2022 Jamie Mansfield * * This program is free software: you can redistribute it and/or modify diff --git a/launcher/ui/pages/instance/TexturePackPage.cpp b/launcher/ui/pages/instance/TexturePackPage.cpp index 5b68c102..76fc04e7 100644 --- a/launcher/ui/pages/instance/TexturePackPage.cpp +++ b/launcher/ui/pages/instance/TexturePackPage.cpp @@ -1,6 +1,8 @@ -// SPDX-License-Identifier: GPL-3.0-only +// SPDX-FileCopyrightText: 2023 flowln +// +// SPDX-License-Identifier: GPL-3.0-only AND Apache-2.0 /* - * PolyMC - Minecraft Launcher + * Prism Launcher - Minecraft Launcher * Copyright (c) 2022 Jamie Mansfield * * This program is free software: you can redistribute it and/or modify diff --git a/launcher/ui/pages/instance/TexturePackPage.h b/launcher/ui/pages/instance/TexturePackPage.h index 5712f7d6..47a8fa60 100644 --- a/launcher/ui/pages/instance/TexturePackPage.h +++ b/launcher/ui/pages/instance/TexturePackPage.h @@ -1,6 +1,8 @@ -// SPDX-License-Identifier: GPL-3.0-only +// SPDX-FileCopyrightText: 2023 flowln +// +// SPDX-License-Identifier: GPL-3.0-only AND Apache-2.0 /* - * PolyMC - Minecraft Launcher + * Prism Launcher - Minecraft Launcher * Copyright (c) 2022 Jamie Mansfield * * This program is free software: you can redistribute it and/or modify diff --git a/launcher/ui/pages/modplatform/ResourcePackModel.cpp b/launcher/ui/pages/modplatform/ResourcePackModel.cpp index fd1afa0d..3df9a787 100644 --- a/launcher/ui/pages/modplatform/ResourcePackModel.cpp +++ b/launcher/ui/pages/modplatform/ResourcePackModel.cpp @@ -1,3 +1,7 @@ +// SPDX-FileCopyrightText: 2023 flowln +// +// SPDX-License-Identifier: GPL-3.0-only + #include "ResourcePackModel.h" #include diff --git a/launcher/ui/pages/modplatform/ResourcePackModel.h b/launcher/ui/pages/modplatform/ResourcePackModel.h index 63aa533c..e2b4a195 100644 --- a/launcher/ui/pages/modplatform/ResourcePackModel.h +++ b/launcher/ui/pages/modplatform/ResourcePackModel.h @@ -1,3 +1,7 @@ +// SPDX-FileCopyrightText: 2023 flowln +// +// SPDX-License-Identifier: GPL-3.0-only + #pragma once #include diff --git a/launcher/ui/pages/modplatform/ResourcePackPage.cpp b/launcher/ui/pages/modplatform/ResourcePackPage.cpp index c2de9e3b..52fb4802 100644 --- a/launcher/ui/pages/modplatform/ResourcePackPage.cpp +++ b/launcher/ui/pages/modplatform/ResourcePackPage.cpp @@ -1,3 +1,7 @@ +// SPDX-FileCopyrightText: 2023 flowln +// +// SPDX-License-Identifier: GPL-3.0-only + #include "ResourcePackPage.h" #include "ui_ResourcePage.h" diff --git a/launcher/ui/pages/modplatform/ResourcePackPage.h b/launcher/ui/pages/modplatform/ResourcePackPage.h index 97780047..c01c89c4 100644 --- a/launcher/ui/pages/modplatform/ResourcePackPage.h +++ b/launcher/ui/pages/modplatform/ResourcePackPage.h @@ -1,3 +1,7 @@ +// SPDX-FileCopyrightText: 2023 flowln +// +// SPDX-License-Identifier: GPL-3.0-only + #pragma once #include "ui/pages/modplatform/ResourcePage.h" diff --git a/launcher/ui/pages/modplatform/ShaderPackModel.cpp b/launcher/ui/pages/modplatform/ShaderPackModel.cpp index 36993288..2101b394 100644 --- a/launcher/ui/pages/modplatform/ShaderPackModel.cpp +++ b/launcher/ui/pages/modplatform/ShaderPackModel.cpp @@ -1,3 +1,7 @@ +// SPDX-FileCopyrightText: 2023 flowln +// +// SPDX-License-Identifier: GPL-3.0-only + #include "ShaderPackModel.h" #include diff --git a/launcher/ui/pages/modplatform/ShaderPackModel.h b/launcher/ui/pages/modplatform/ShaderPackModel.h index c84df15a..f3c695e9 100644 --- a/launcher/ui/pages/modplatform/ShaderPackModel.h +++ b/launcher/ui/pages/modplatform/ShaderPackModel.h @@ -1,3 +1,7 @@ +// SPDX-FileCopyrightText: 2023 flowln +// +// SPDX-License-Identifier: GPL-3.0-only + #pragma once #include diff --git a/launcher/ui/pages/modplatform/ShaderPackPage.cpp b/launcher/ui/pages/modplatform/ShaderPackPage.cpp index 364d0a54..251c07e7 100644 --- a/launcher/ui/pages/modplatform/ShaderPackPage.cpp +++ b/launcher/ui/pages/modplatform/ShaderPackPage.cpp @@ -1,3 +1,7 @@ +// SPDX-FileCopyrightText: 2023 flowln +// +// SPDX-License-Identifier: GPL-3.0-only + #include "ShaderPackPage.h" #include "ui_ResourcePage.h" diff --git a/launcher/ui/pages/modplatform/ShaderPackPage.h b/launcher/ui/pages/modplatform/ShaderPackPage.h index 92a9c3ee..972419a8 100644 --- a/launcher/ui/pages/modplatform/ShaderPackPage.h +++ b/launcher/ui/pages/modplatform/ShaderPackPage.h @@ -1,3 +1,7 @@ +// SPDX-FileCopyrightText: 2023 flowln +// +// SPDX-License-Identifier: GPL-3.0-only + #pragma once #include "ui/pages/modplatform/ResourcePage.h" diff --git a/launcher/ui/pages/modplatform/TexturePackModel.cpp b/launcher/ui/pages/modplatform/TexturePackModel.cpp index 076a200d..fa636951 100644 --- a/launcher/ui/pages/modplatform/TexturePackModel.cpp +++ b/launcher/ui/pages/modplatform/TexturePackModel.cpp @@ -1,3 +1,7 @@ +// SPDX-FileCopyrightText: 2023 flowln +// +// SPDX-License-Identifier: GPL-3.0-only + #include "TexturePackModel.h" #include "Application.h" diff --git a/launcher/ui/pages/modplatform/TexturePackModel.h b/launcher/ui/pages/modplatform/TexturePackModel.h index 0ae5bdd4..bb2db5cf 100644 --- a/launcher/ui/pages/modplatform/TexturePackModel.h +++ b/launcher/ui/pages/modplatform/TexturePackModel.h @@ -1,3 +1,7 @@ +// SPDX-FileCopyrightText: 2023 flowln +// +// SPDX-License-Identifier: GPL-3.0-only + #pragma once #include "meta/VersionList.h" diff --git a/launcher/ui/pages/modplatform/TexturePackPage.h b/launcher/ui/pages/modplatform/TexturePackPage.h index 3466d3ea..0bdce2f9 100644 --- a/launcher/ui/pages/modplatform/TexturePackPage.h +++ b/launcher/ui/pages/modplatform/TexturePackPage.h @@ -1,3 +1,7 @@ +// SPDX-FileCopyrightText: 2023 flowln +// +// SPDX-License-Identifier: GPL-3.0-only + #pragma once #include "ui_ResourcePage.h" From 8114d8778f460633224adb64227eb466d3a86982 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 6 Feb 2023 15:31:42 +0000 Subject: [PATCH 009/185] chore(deps): update cachix/install-nix-action action to v19 --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 86e88fa1..9cc07ee5 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -575,7 +575,7 @@ jobs: submodules: 'true' - name: Install nix if: inputs.build_type == 'Debug' - uses: cachix/install-nix-action@v18 + uses: cachix/install-nix-action@v19 with: install_url: https://nixos.org/nix/install extra_nix_config: | From 8440c2819ba27f77d968809180b5fec5b3682dc8 Mon Sep 17 00:00:00 2001 From: ktheticdev <64607352+ktheticdev@users.noreply.github.com> Date: Tue, 7 Feb 2023 11:10:43 +0400 Subject: [PATCH 010/185] Fix README.md typo Signed-off-by: ktheticdev <64607352+ktheticdev@users.noreply.github.com> --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8765da93..6271dff3 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ Prebuilt Development builds are provided for **Linux**, **Windows** and **macOS* For **Arch**, **Debian**, **Fedora**, **OpenSUSE (Tumbleweed)** and **Gentoo**, respectively, you can use these packages for the latest development versions: -[![prismlauncher-git](https://img.shields.io/badge/aur-prismlauncher--git-1793D1?label=AUR&logo=archlinux&logoColor=white)](https://aur.archlinux.org/packages/prismlauncher-git) [![prismlauncher-git](https://img.shields.io/badge/aur-prismlauncher--qt5--git-1793D1?label=AUR&logo=archlinux&logoColor=white)](https://aur.archlinux.org/packages/prismlauncher-qt5-git) [![prismlauncher-git](https://img.shields.io/badge/mpr-prismlauncher--git-A80030?label=MPR&logo=debian&logoColor=white)](https://mpr.makedeb.org/packages/prismlauncher-git)
[![prismlauncher-nightly](https://img.shields.io/badge/copr-prismlauncher--nightly-51A2DA?label=CORP&logo=fedora&logoColor=white)](https://copr.fedorainfracloud.org/coprs/g3tchoo/prismlauncher/) [![prismlauncher-nightly](https://img.shields.io/badge/OBS-prismlauncher--nightly-3AB6A9?logo=opensuse&logoColor=white)](https://build.opensuse.org/project/show/home:getchoo) [![prismlauncher-9999](https://img.shields.io/badge/gentoo-prismlauncher--9999-4D4270?label=Gentoo&logo=gentoo&logoColor=white)](https://packages.gentoo.org/packages/games-action/prismlauncher) +[![prismlauncher-git](https://img.shields.io/badge/aur-prismlauncher--git-1793D1?label=AUR&logo=archlinux&logoColor=white)](https://aur.archlinux.org/packages/prismlauncher-git) [![prismlauncher-git](https://img.shields.io/badge/aur-prismlauncher--qt5--git-1793D1?label=AUR&logo=archlinux&logoColor=white)](https://aur.archlinux.org/packages/prismlauncher-qt5-git) [![prismlauncher-git](https://img.shields.io/badge/mpr-prismlauncher--git-A80030?label=MPR&logo=debian&logoColor=white)](https://mpr.makedeb.org/packages/prismlauncher-git)
[![prismlauncher-nightly](https://img.shields.io/badge/copr-prismlauncher--nightly-51A2DA?label=COPR&logo=fedora&logoColor=white)](https://copr.fedorainfracloud.org/coprs/g3tchoo/prismlauncher/) [![prismlauncher-nightly](https://img.shields.io/badge/OBS-prismlauncher--nightly-3AB6A9?logo=opensuse&logoColor=white)](https://build.opensuse.org/project/show/home:getchoo) [![prismlauncher-9999](https://img.shields.io/badge/gentoo-prismlauncher--9999-4D4270?label=Gentoo&logo=gentoo&logoColor=white)](https://packages.gentoo.org/packages/games-action/prismlauncher) These packages are also availiable to all the distributions based on the ones mentioned above. From d886d32bd844734a8195af459b7293879866205e Mon Sep 17 00:00:00 2001 From: PandaNinjas Date: Tue, 7 Feb 2023 17:21:00 +0000 Subject: [PATCH 011/185] Replace potentially ReDOSable regex Signed-off-by: PandaNinjas --- launcher/InstanceImportTask.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/InstanceImportTask.cpp b/launcher/InstanceImportTask.cpp index 70bf5784..6b5317e5 100644 --- a/launcher/InstanceImportTask.cpp +++ b/launcher/InstanceImportTask.cpp @@ -361,7 +361,7 @@ void InstanceImportTask::processModrinth() } else { QString pack_id; if (!m_sourceUrl.isEmpty()) { - QRegularExpression regex(R"(data\/(.*)\/versions)"); + QRegularExpression regex(R"(data\/([^\/]*)\/versions)"); pack_id = regex.match(m_sourceUrl.toString()).captured(1); } From 104863846b9b47dab5e01413b6d177b8a647b2c3 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 10 Feb 2023 06:24:45 +0000 Subject: [PATCH 012/185] chore(deps): update actions/cache action to v3.2.5 --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 86e88fa1..2eaee450 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -151,7 +151,7 @@ jobs: - name: Retrieve ccache cache (Windows MinGW-w64) if: runner.os == 'Windows' && matrix.msystem != '' && inputs.build_type == 'Debug' - uses: actions/cache@v3.2.4 + uses: actions/cache@v3.2.5 with: path: '${{ github.workspace }}\.ccache' key: ${{ matrix.os }}-mingw-w64-ccache-${{ github.run_id }} From 6be7eed878bc701407c6c3efd93d9944e1079490 Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Fri, 10 Feb 2023 09:17:48 +0100 Subject: [PATCH 013/185] fix: don't extract files outside of target path This should fix a security issue regarding path traversal in zip files. Signed-off-by: Sefa Eyeoglu --- launcher/MMCZip.cpp | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/launcher/MMCZip.cpp b/launcher/MMCZip.cpp index f6600343..734eacd8 100644 --- a/launcher/MMCZip.cpp +++ b/launcher/MMCZip.cpp @@ -275,7 +275,8 @@ bool MMCZip::findFilesInZip(QuaZip * zip, const QString & what, QStringList & re // ours std::optional MMCZip::extractSubDir(QuaZip *zip, const QString & subdir, const QString &target) { - QDir directory(target); + auto absDirectoryUrl = QUrl::fromLocalFile(target); + QStringList extracted; qDebug() << "Extracting subdir" << subdir << "from" << zip->getZipName() << "to" << target; @@ -317,11 +318,16 @@ std::optional MMCZip::extractSubDir(QuaZip *zip, const QString & su QString absFilePath; if(name.isEmpty()) { - absFilePath = directory.absoluteFilePath(name) + "/"; + absFilePath = FS::PathCombine(target, "/"); // FIXME this seems weird } else { - absFilePath = directory.absoluteFilePath(path + name); + absFilePath = FS::PathCombine(target, path + name); + } + + if (!absDirectoryUrl.isParentOf(QUrl::fromLocalFile(absFilePath))) { + qWarning() << "Extracting" << name << "was cancelled, because it was effectively outside of the target path" << target; + return std::nullopt; } if (!JlCompress::extractFile(zip, "", absFilePath)) From e70a5a47ee8bf67715a46a6ac668dad7685d08be Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Fri, 10 Feb 2023 10:46:21 +0100 Subject: [PATCH 014/185] fix: ignore absolute paths when extracting Signed-off-by: Sefa Eyeoglu --- launcher/MMCZip.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/launcher/MMCZip.cpp b/launcher/MMCZip.cpp index 734eacd8..31460bf4 100644 --- a/launcher/MMCZip.cpp +++ b/launcher/MMCZip.cpp @@ -306,6 +306,11 @@ std::optional MMCZip::extractSubDir(QuaZip *zip, const QString & su name.remove(0, subdir.size()); auto original_name = name; + // Fix subdirs/files ending with a / getting transformed into absolute paths + if(name.startsWith('/')){ + name = name.mid(1); + } + // Fix weird "folders with a single file get squashed" thing QString path; if(name.contains('/') && !name.endsWith('/')){ From 381d7413c800b3121a4665994be0fb2cc33137cc Mon Sep 17 00:00:00 2001 From: RaptaG <77157639+RaptaG@users.noreply.github.com> Date: Fri, 10 Feb 2023 19:47:08 +0200 Subject: [PATCH 015/185] Link license in the shield badge So that no trash URL shows when hovering! Signed-off-by: RaptaG <77157639+RaptaG@users.noreply.github.com> --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 6271dff3..aaa1fd4c 100644 --- a/README.md +++ b/README.md @@ -96,7 +96,7 @@ Be aware that if you build this software without removing the provided API keys If you do not agree with these terms and conditions, then remove the associated API keys from the [CMakeLists.txt](CMakeLists.txt) file by setting them to an empty string (`""`). -## License ![https://github.com/PrismLauncher/PrismLauncher/blob/develop/LICENSE](https://img.shields.io/github/license/PrismLauncher/PrismLauncher?label=License&logo=gnu&color=C4282D) +## License [![https://github.com/PrismLauncher/PrismLauncher/blob/develop/LICENSE](https://img.shields.io/github/license/PrismLauncher/PrismLauncher?label=License&logo=gnu&color=C4282D)](LICENSE) All launcher code is available under the GPL-3.0-only license. From 4331eaae2e5ae7e82b7d0f74c508bfc842c5a644 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 11 Feb 2023 15:48:00 +0000 Subject: [PATCH 016/185] chore(deps): update vedantmgoyal2009/winget-releaser action to v2 --- .github/workflows/winget.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/winget.yml b/.github/workflows/winget.yml index ef9561cf..eacf2309 100644 --- a/.github/workflows/winget.yml +++ b/.github/workflows/winget.yml @@ -7,7 +7,7 @@ jobs: publish: runs-on: windows-latest steps: - - uses: vedantmgoyal2009/winget-releaser@v1 + - uses: vedantmgoyal2009/winget-releaser@v2 with: identifier: PrismLauncher.PrismLauncher version: ${{ github.event.release.tag_name }} From f5f2d33f930674e03f8c81c1eaa3d35ddabc2886 Mon Sep 17 00:00:00 2001 From: kumquat-ir <66188216+kumquat-ir@users.noreply.github.com> Date: Sat, 11 Feb 2023 17:35:08 -0500 Subject: [PATCH 017/185] parse nil metadata Signed-off-by: kumquat-ir <66188216+kumquat-ir@users.noreply.github.com> --- launcher/InstancePageProvider.h | 2 +- .../minecraft/mod/tasks/LocalModParseTask.cpp | 72 +++++++++++++++++++ 2 files changed, 73 insertions(+), 1 deletion(-) diff --git a/launcher/InstancePageProvider.h b/launcher/InstancePageProvider.h index 5d8beca9..286298cc 100644 --- a/launcher/InstancePageProvider.h +++ b/launcher/InstancePageProvider.h @@ -36,7 +36,7 @@ public: values.append(new VersionPage(onesix.get())); values.append(ManagedPackPage::createPage(onesix.get())); auto modsPage = new ModFolderPage(onesix.get(), onesix->loaderModList()); - modsPage->setFilter("%1 (*.zip *.jar *.litemod)"); + modsPage->setFilter("%1 (*.zip *.jar *.litemod *.nilmod)"); values.append(modsPage); values.append(new CoreModFolderPage(onesix.get(), onesix->coreModList())); values.append(new ResourcePackPage(onesix.get(), onesix->resourcePackList())); diff --git a/launcher/minecraft/mod/tasks/LocalModParseTask.cpp b/launcher/minecraft/mod/tasks/LocalModParseTask.cpp index 91cb747f..03b599ea 100644 --- a/launcher/minecraft/mod/tasks/LocalModParseTask.cpp +++ b/launcher/minecraft/mod/tasks/LocalModParseTask.cpp @@ -285,6 +285,48 @@ ModDetails ReadLiteModInfo(QByteArray contents) return details; } +// https://git.sleeping.town/unascribed/NilLoader/src/commit/d7fc87b255fc31019ff90f80d45894927fac6efc/src/main/java/nilloader/api/NilMetadata.java#L64 +ModDetails ReadNilModInfo(QByteArray contents, QString fname) +{ + ModDetails details; + + // this is a css file (why) but we only care about a couple key/value pairs from it + // hence this instead of a css parser lib + // could be made a lot better but it works(tm) + // (does css require properties to be on their own lines? if so, the code can get less horrible looking) + QString contentStr = QString(contents).trimmed(); + int firstidx = contentStr.indexOf("@nilmod"); + firstidx = contentStr.indexOf("{", firstidx); + int lastidx = contentStr.indexOf("}", firstidx); + int nameidx = contentStr.indexOf("name:", firstidx); + int descidx = contentStr.indexOf("description:", firstidx); + int authorsidx = contentStr.indexOf("authors:", firstidx); + int versionidx = contentStr.indexOf("version:", firstidx); + + if (nameidx != -1 && nameidx < lastidx) { + nameidx = contentStr.indexOf('"', nameidx); + details.name = contentStr.mid(nameidx + 1, contentStr.indexOf('"', nameidx + 1) - nameidx - 1); + } + if (descidx != -1 && descidx < lastidx) { + descidx = contentStr.indexOf('"', descidx); + details.description = contentStr.mid(descidx + 1, contentStr.indexOf('"', descidx + 1) - descidx - 1); + } + if (authorsidx != -1 && authorsidx < lastidx) { + authorsidx = contentStr.indexOf('"', authorsidx); + details.authors.append(contentStr.mid(authorsidx + 1, contentStr.indexOf('"', authorsidx + 1) - authorsidx - 1)); + } + if (versionidx != -1 && versionidx < lastidx) { + versionidx = contentStr.indexOf('"', versionidx); + details.version = contentStr.mid(versionidx + 1, contentStr.indexOf('"', versionidx + 1) - versionidx - 1); + } else { + details.version = "?"; + } + + details.mod_id = fname.remove(".nilmod.css"); + + return details; +} + bool process(Mod& mod, ProcessingLevel level) { switch (mod.type()) { @@ -401,6 +443,36 @@ bool processZIP(Mod& mod, ProcessingLevel level) mod.setDetails(details); return true; + } else if (zip.setCurrentFile("META-INF/nil/mappings.json")) { + // nilloader uses the filename of the metadata file for the modid, so we can't know the exact filename + // thankfully, there is a good file to use as a canary so we don't look for nil meta all the time + + QStringList foundNilMetas; + for (auto& fname : zip.getFileNameList()) { + if (fname.endsWith(".nilmod.css")) { + foundNilMetas.append(fname); + } + } + + if (foundNilMetas.size() > 1 && foundNilMetas.at(0) == "nilloader.nilmod.css") { + // nilmods can shade nilloader to be able to run as a standalone agent - which includes nilloader's own meta file + // so if there is more than one meta file, ignore nilloader's meta, since it's not the actual mod + foundNilMetas.removeFirst(); + } + + if (zip.setCurrentFile(foundNilMetas.at(0))) { + if (!file.open(QIODevice::ReadOnly)) { + zip.close(); + return false; + } + + details = ReadNilModInfo(file.readAll(), foundNilMetas.at(0)); + file.close(); + zip.close(); + + mod.setDetails(details); + return true; + } } zip.close(); From 7896dd19c12c0276551ba188adc6184dcf0a3184 Mon Sep 17 00:00:00 2001 From: kumquat-ir <66188216+kumquat-ir@users.noreply.github.com> Date: Sat, 11 Feb 2023 17:36:06 -0500 Subject: [PATCH 018/185] nilmods instance page mostly copied from the coremod page impl Signed-off-by: kumquat-ir <66188216+kumquat-ir@users.noreply.github.com> --- launcher/InstancePageProvider.h | 1 + launcher/minecraft/MinecraftInstance.cpp | 17 +++++++++++++++++ launcher/minecraft/MinecraftInstance.h | 3 +++ launcher/minecraft/launch/ScanModFolders.cpp | 14 +++++++++++++- launcher/minecraft/launch/ScanModFolders.h | 2 ++ launcher/ui/pages/instance/ModFolderPage.cpp | 9 +++++++++ launcher/ui/pages/instance/ModFolderPage.h | 13 +++++++++++++ 7 files changed, 58 insertions(+), 1 deletion(-) diff --git a/launcher/InstancePageProvider.h b/launcher/InstancePageProvider.h index 286298cc..b4b6e739 100644 --- a/launcher/InstancePageProvider.h +++ b/launcher/InstancePageProvider.h @@ -39,6 +39,7 @@ public: modsPage->setFilter("%1 (*.zip *.jar *.litemod *.nilmod)"); values.append(modsPage); values.append(new CoreModFolderPage(onesix.get(), onesix->coreModList())); + values.append(new NilModFolderPage(onesix.get(), onesix->nilModList())); values.append(new ResourcePackPage(onesix.get(), onesix->resourcePackList())); values.append(new TexturePackPage(onesix.get(), onesix->texturePackList())); values.append(new ShaderPackPage(onesix.get(), onesix->shaderPackList())); diff --git a/launcher/minecraft/MinecraftInstance.cpp b/launcher/minecraft/MinecraftInstance.cpp index 4fe234c4..0737a648 100644 --- a/launcher/minecraft/MinecraftInstance.cpp +++ b/launcher/minecraft/MinecraftInstance.cpp @@ -290,6 +290,11 @@ QString MinecraftInstance::coreModsDir() const return FS::PathCombine(gameRoot(), "coremods"); } +QString MinecraftInstance::nilModsDir() const +{ + return FS::PathCombine(gameRoot(), "nilmods"); +} + QString MinecraftInstance::resourcePacksDir() const { return FS::PathCombine(gameRoot(), "resourcepacks"); @@ -1125,6 +1130,18 @@ std::shared_ptr MinecraftInstance::coreModList() const return m_core_mod_list; } +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)); + 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 { if (!m_resource_pack_list) diff --git a/launcher/minecraft/MinecraftInstance.h b/launcher/minecraft/MinecraftInstance.h index 1bbd7b83..a75fa481 100644 --- a/launcher/minecraft/MinecraftInstance.h +++ b/launcher/minecraft/MinecraftInstance.h @@ -84,6 +84,7 @@ public: QString shaderPacksDir() const; QString modsRoot() const override; QString coreModsDir() const; + QString nilModsDir() const; QString modsCacheLocation() const; QString libDir() const; QString worldDir() const; @@ -116,6 +117,7 @@ public: ////// 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; @@ -170,6 +172,7 @@ protected: // data std::shared_ptr m_components; mutable std::shared_ptr m_loader_mod_list; mutable std::shared_ptr m_core_mod_list; + mutable std::shared_ptr m_nil_mod_list; mutable std::shared_ptr m_resource_pack_list; mutable std::shared_ptr m_shader_pack_list; mutable std::shared_ptr m_texture_pack_list; diff --git a/launcher/minecraft/launch/ScanModFolders.cpp b/launcher/minecraft/launch/ScanModFolders.cpp index bdffeadd..71e7638c 100644 --- a/launcher/minecraft/launch/ScanModFolders.cpp +++ b/launcher/minecraft/launch/ScanModFolders.cpp @@ -55,6 +55,12 @@ void ScanModFolders::executeTask() if(!cores->update()) { m_coreModsDone = true; } + + auto nils = m_inst->nilModList(); + connect(nils.get(), &ModFolderModel::updateFinished, this, &ScanModFolders::nilModsDone); + if(!nils->update()) { + m_nilModsDone = true; + } checkDone(); } @@ -70,9 +76,15 @@ void ScanModFolders::coreModsDone() checkDone(); } +void ScanModFolders::nilModsDone() +{ + m_nilModsDone = true; + checkDone(); +} + void ScanModFolders::checkDone() { - if(m_modsDone && m_coreModsDone) { + if(m_modsDone && m_coreModsDone && m_nilModsDone) { emitSucceeded(); } } diff --git a/launcher/minecraft/launch/ScanModFolders.h b/launcher/minecraft/launch/ScanModFolders.h index d5989170..111a5850 100644 --- a/launcher/minecraft/launch/ScanModFolders.h +++ b/launcher/minecraft/launch/ScanModFolders.h @@ -33,10 +33,12 @@ public: private slots: void coreModsDone(); void modsDone(); + void nilModsDone(); private: void checkDone(); private: // DATA bool m_modsDone = false; + bool m_nilModsDone = false; bool m_coreModsDone = false; }; diff --git a/launcher/ui/pages/instance/ModFolderPage.cpp b/launcher/ui/pages/instance/ModFolderPage.cpp index d9069915..8c87a8a0 100644 --- a/launcher/ui/pages/instance/ModFolderPage.cpp +++ b/launcher/ui/pages/instance/ModFolderPage.cpp @@ -273,3 +273,12 @@ bool CoreModFolderPage::shouldDisplay() const } return false; } + +NilModFolderPage::NilModFolderPage(BaseInstance* inst, std::shared_ptr mods, QWidget* parent) + : ModFolderPage(inst, mods, parent) +{} + +bool NilModFolderPage::shouldDisplay() const +{ + return !m_model->dir().isEmpty(); +} diff --git a/launcher/ui/pages/instance/ModFolderPage.h b/launcher/ui/pages/instance/ModFolderPage.h index ff58b38a..cad240bd 100644 --- a/launcher/ui/pages/instance/ModFolderPage.h +++ b/launcher/ui/pages/instance/ModFolderPage.h @@ -81,3 +81,16 @@ class CoreModFolderPage : public ModFolderPage { virtual bool shouldDisplay() const override; }; + +class NilModFolderPage : public ModFolderPage { + public: + explicit NilModFolderPage(BaseInstance* inst, std::shared_ptr mods, QWidget* parent = 0); + virtual ~NilModFolderPage() = default; + + virtual QString displayName() const override { return tr("Nilmods"); } + virtual QIcon icon() const override { return APPLICATION->getThemedIcon("coremods"); } + virtual QString id() const override { return "nilmods"; } + virtual QString helpPage() const override { return "Nil-mods"; } + + virtual bool shouldDisplay() const override; +}; From c07fff750354e23149470d493a7c96624fe2ab26 Mon Sep 17 00:00:00 2001 From: kumquat-ir <66188216+kumquat-ir@users.noreply.github.com> Date: Sun, 12 Feb 2023 17:23:15 -0500 Subject: [PATCH 019/185] switch to qdcss for parsing make it not horrible to look at Signed-off-by: kumquat-ir <66188216+kumquat-ir@users.noreply.github.com> --- CMakeLists.txt | 1 + launcher/CMakeLists.txt | 1 + .../minecraft/mod/tasks/LocalModParseTask.cpp | 39 +++++---------- libraries/README.md | 8 +++ libraries/qdcss/CMakeLists.txt | 18 +++++++ libraries/qdcss/include/qdcss.h | 21 ++++++++ libraries/qdcss/src/qdcss.cpp | 49 +++++++++++++++++++ 7 files changed, 110 insertions(+), 27 deletions(-) create mode 100644 libraries/qdcss/CMakeLists.txt create mode 100644 libraries/qdcss/include/qdcss.h create mode 100644 libraries/qdcss/src/qdcss.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 37bb49ba..6ce8143c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -448,6 +448,7 @@ if (NOT ghc_filesystem_FOUND) else() message(STATUS "Using system ghc_filesystem") endif() +add_subdirectory(libraries/qdcss) # css parser ############################### Built Artifacts ############################### diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index 1bfe9cbc..c6b9e380 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -1025,6 +1025,7 @@ target_link_libraries(Launcher_logic nbt++ ${ZLIB_LIBRARIES} tomlplusplus::tomlplusplus + qdcss BuildConfig Katabasis Qt${QT_VERSION_MAJOR}::Widgets diff --git a/launcher/minecraft/mod/tasks/LocalModParseTask.cpp b/launcher/minecraft/mod/tasks/LocalModParseTask.cpp index 03b599ea..2263f8ec 100644 --- a/launcher/minecraft/mod/tasks/LocalModParseTask.cpp +++ b/launcher/minecraft/mod/tasks/LocalModParseTask.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include @@ -290,37 +291,21 @@ ModDetails ReadNilModInfo(QByteArray contents, QString fname) { ModDetails details; - // this is a css file (why) but we only care about a couple key/value pairs from it - // hence this instead of a css parser lib - // could be made a lot better but it works(tm) - // (does css require properties to be on their own lines? if so, the code can get less horrible looking) - QString contentStr = QString(contents).trimmed(); - int firstidx = contentStr.indexOf("@nilmod"); - firstidx = contentStr.indexOf("{", firstidx); - int lastidx = contentStr.indexOf("}", firstidx); - int nameidx = contentStr.indexOf("name:", firstidx); - int descidx = contentStr.indexOf("description:", firstidx); - int authorsidx = contentStr.indexOf("authors:", firstidx); - int versionidx = contentStr.indexOf("version:", firstidx); + QDCSS cssData = QDCSS(contents); + auto name = cssData.get("@nilmod.name"); + auto desc = cssData.get("@nilmod.description"); + auto authors = cssData.get("@nilmod.authors"); - if (nameidx != -1 && nameidx < lastidx) { - nameidx = contentStr.indexOf('"', nameidx); - details.name = contentStr.mid(nameidx + 1, contentStr.indexOf('"', nameidx + 1) - nameidx - 1); + if (name->has_value()) { + details.name = name->value(); } - if (descidx != -1 && descidx < lastidx) { - descidx = contentStr.indexOf('"', descidx); - details.description = contentStr.mid(descidx + 1, contentStr.indexOf('"', descidx + 1) - descidx - 1); + if (desc->has_value()) { + details.description = desc->value(); } - if (authorsidx != -1 && authorsidx < lastidx) { - authorsidx = contentStr.indexOf('"', authorsidx); - details.authors.append(contentStr.mid(authorsidx + 1, contentStr.indexOf('"', authorsidx + 1) - authorsidx - 1)); - } - if (versionidx != -1 && versionidx < lastidx) { - versionidx = contentStr.indexOf('"', versionidx); - details.version = contentStr.mid(versionidx + 1, contentStr.indexOf('"', versionidx + 1) - versionidx - 1); - } else { - details.version = "?"; + if (authors->has_value()) { + details.authors.append(authors->value()); } + details.version = cssData.get("@nilmod.version")->value_or("?"); details.mod_id = fname.remove(".nilmod.css"); diff --git a/libraries/README.md b/libraries/README.md index 95be8740..4da11314 100644 --- a/libraries/README.md +++ b/libraries/README.md @@ -140,3 +140,11 @@ A TOML language parser. Used by Forge 1.14+ to store mod metadata. See [github repo](https://github.com/marzer/tomlplusplus). Licenced under the MIT licence. + +## qdcss + +A quick and dirty css parser, used by NilLoader to store mod metadata. + +Translated (and heavily trimmed down) from [the original Java code](https://github.com/unascribed/NilLoader/blob/trunk/src/main/java/nilloader/api/lib/qdcss/QDCSS.java) from NilLoader + +Licensed under LGPL version 3. diff --git a/libraries/qdcss/CMakeLists.txt b/libraries/qdcss/CMakeLists.txt new file mode 100644 index 00000000..0afdef32 --- /dev/null +++ b/libraries/qdcss/CMakeLists.txt @@ -0,0 +1,18 @@ +cmake_minimum_required(VERSION 3.9.4) +project(qdcss) + +if(QT_VERSION_MAJOR EQUAL 5) + find_package(Qt5 COMPONENTS Core REQUIRED) +elseif(Launcher_QT_VERSION_MAJOR EQUAL 6) + find_package(Qt6 COMPONENTS Core Core5Compat REQUIRED) + list(APPEND qdcss_LIBS Qt${QT_VERSION_MAJOR}::Core5Compat) +endif() + +set(QDCSS_SOURCES + include/qdcss.h + src/qdcss.cpp +) + +add_library(qdcss STATIC ${QDCSS_SOURCES}) +target_include_directories(qdcss PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include) +target_link_libraries(qdcss Qt${QT_VERSION_MAJOR}::Core ${qdcss_LIBS}) diff --git a/libraries/qdcss/include/qdcss.h b/libraries/qdcss/include/qdcss.h new file mode 100644 index 00000000..2bcac36f --- /dev/null +++ b/libraries/qdcss/include/qdcss.h @@ -0,0 +1,21 @@ +#ifndef QDCSS_H +#define QDCSS_H + +#include +#include +#include +#include + +class QDCSS { + // these are all we need to parse a couple string values out of a css string + // lots more in the original code, yet to be ported + // https://github.com/unascribed/NilLoader/blob/trunk/src/main/java/nilloader/api/lib/qdcss/QDCSS.java + public: + QDCSS(QString); + std::optional* get(QString); + + private: + QMap m_data; +}; + +#endif // QDCSS_H diff --git a/libraries/qdcss/src/qdcss.cpp b/libraries/qdcss/src/qdcss.cpp new file mode 100644 index 00000000..4426dcae --- /dev/null +++ b/libraries/qdcss/src/qdcss.cpp @@ -0,0 +1,49 @@ +#include "qdcss.h" + +#include +#include +#include + +QRegularExpression ruleset_re = QRegularExpression(R"([#.]?(@?\w+?)\s*\{(.*?)\})", QRegularExpression::DotMatchesEverythingOption); +QRegularExpression rule_re = QRegularExpression(R"((\S+?)\s*:\s*(?:\"(.*?)(?append(value); + } + } +} + +std::optional* QDCSS::get(QString key) +{ + auto found = m_data.find(key); + + if (found == m_data.end() || found->empty()) { + return new std::optional; + } + + return new std::optional(found->back()); +} From d93cb751b074447709ad323f8ca5b379498ab470 Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Sun, 12 Feb 2023 16:23:33 -0700 Subject: [PATCH 020/185] fix: oversight caused prism to be `.zip` windows default Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- program_info/win_install.nsi.in | 103 +++++++++++++++++++------------- 1 file changed, 62 insertions(+), 41 deletions(-) diff --git a/program_info/win_install.nsi.in b/program_info/win_install.nsi.in index 42a9e762..5ad6078e 100644 --- a/program_info/win_install.nsi.in +++ b/program_info/win_install.nsi.in @@ -114,7 +114,8 @@ VIAddVersionKey /LANG=${LANG_ENGLISH} "ProductVersion" "@Launcher_VERSION_NAME4@ ;-------------------------------- ; Shell Associate Macros -!macro APP_SETUP DESCRIPTION ICON APP_ID APP_NAME APP_EXE COMMANDTEXT COMMAND ; VERB APP_COMPANY +!macro APP_SETUP_Def DESCRIPTION ICON APP_ID APP_NAME APP_EXE COMMANDTEXT COMMAND + ; setup APP_ID WriteRegStr ShCtx "Software\Classes\${APP_ID}" "" `${DESCRIPTION}` WriteRegStr ShCtx "Software\Classes\${APP_ID}\DefaultIcon" "" `${ICON}` @@ -123,24 +124,28 @@ VIAddVersionKey /LANG=${LANG_ENGLISH} "ProductVersion" "@Launcher_VERSION_NAME4@ WriteRegStr ShCtx "Software\Classes\${APP_ID}\shell\open" "" `${COMMANDTEXT}` WriteRegStr ShCtx "Software\Classes\${APP_ID}\shell\open\command" "" `${COMMAND}` - ; if you want the app to use it's own implementation of a verb - ;WriteRegStr ShCtx "Software\Classes\${APP_ID}\shell\${VERB}" "" "${DESCRIPTION}" - ;WriteRegStr ShCtx "Software\Classes\${APP_ID}\shell\${VERB}\command" "" `${COMMAND}` - WriteRegStr ShCtx "Software\Classes\Applications\${APP_EXE}\shell\open\command" "" `${COMMAND}` WriteRegStr ShCtx "Software\Classes\Applications\${APP_EXE}" "FriendlyAppName" `${APP_NAME}` ; [Optional] - ;WriteRegStr ShCtx "Software\Classes\Applications\${APP_EXE}" "ApplicationCompany" `${APP_COMPANY}` ; [Optional] - ;WriteRegNone ShCtx "Software\Classes\Applications\${APP_EXE}\SupportedTypes" ".${EXT}" ; [Optional] Only allow "Open With" with specific extension(s) on WinXP+ - - # Register "Default Programs" [Optional] - !ifdef REGISTER_DEFAULTPROGRAMS - WriteRegStr ShCtx "Software\Classes\Applications\${APP_EXE}\Capabilities" "ApplicationDescription" `${DESCRIPTION}` - WriteRegStr ShCtx "Software\RegisteredApplications" `${APP_NAME}` "Software\Classes\Applications\${APP_EXE}\Capabilities" - !endif !macroend -!macro APP_ASSOCIATE EXT APP_ID APP_EXE OVERWIRTE +!macro APP_SETUP DESCRIPTION ICON APP_ID APP_NAME APP_EXE COMMANDTEXT COMMAND + + !insertmacro APP_SETUP_Def `${DESCRIPTION}` `${ICON}` `${APP_ID}` `${APP_NAME}` `${APP_EXE}` `${COMMANDTEXT}` `${COMMAND}` + +!macroend + +!macro APP_SETUP_DEFAULT DESCRIPTION ICON APP_ID APP_NAME APP_EXE COMMANDTEXT COMMAND + + !insertmacro APP_SETUP_Def `${DESCRIPTION}` `${ICON}` `${APP_ID}` `${APP_NAME}` `${APP_EXE}` `${COMMANDTEXT}` `${COMMAND}` + + # Register "Default Programs" + WriteRegStr ShCtx "Software\Classes\Applications\${APP_EXE}\Capabilities" "ApplicationDescription" `${DESCRIPTION}` + WriteRegStr ShCtx "Software\RegisteredApplications" `${APP_NAME}` "Software\Classes\Applications\${APP_EXE}\Capabilities" + +!macroend + +!macro APP_ASSOCIATE_Def EXT APP_ID APP_EXE OVERWIRTE ; Backup the previously associated file class ${If} ${OVERWIRTE} == true ReadRegStr $R0 ShCtx "Software\Classes\${EXT}" "" @@ -151,10 +156,20 @@ VIAddVersionKey /LANG=${LANG_ENGLISH} "ProductVersion" "@Launcher_VERSION_NAME4@ WriteRegNone ShCtx "Software\Classes\${EXT}\OpenWithList" "${APP_EXE}" ; Win2000+ WriteRegNone ShCtx "Software\Classes\${EXT}\OpenWithProgids" "${APP_ID}" ; WinXP+ - # Register "Default Programs" [Optional] - !ifdef REGISTER_DEFAULTPROGRAMS +!macroend + +!macro APP_ASSOCIATE EXT APP_ID APP_EXE OVERWIRTE + + !insertmacro APP_ASSOCIATE_Def `${EXT}` `${APP_ID}` `${APP_EXE}` `${OVERWIRTE}` + +!macroend + +!macro APP_ASSOCIATE_DEFAULT EXT APP_ID APP_EXE OVERWIRTE + + !insertmacro APP_ASSOCIATE_Def `${EXT}` `${APP_ID}` `${APP_EXE}` `${OVERWIRTE}` + + # Register "Default Programs" WriteRegStr ShCtx "Software\Classes\Applications\${APP_EXE}\Capabilities\FileAssociations" "${EXT}" "${APP_ID}" - !endif !macroend @@ -191,12 +206,10 @@ VIAddVersionKey /LANG=${LANG_ENGLISH} "ProductVersion" "@Launcher_VERSION_NAME4@ DeleteRegKey /IfEmpty HKCU "Software\Microsoft\Windows\CurrentVersion\Explorer\FileExts\${EXT}\OpenWithProgids" DeleteRegKey /IfEmpty HKCU "Software\Microsoft\Windows\CurrentVersion\Explorer\FileExts\${EXT}\OpenWithList" DeleteRegKey /IfEmpty HKCU "Software\Microsoft\Windows\CurrentVersion\Explorer\FileExts\${EXT}" - ;DeleteRegKey HKCU "Software\Microsoft\Windows\Roaming\OpenWith\FileExts\.${EXT}" - ;DeleteRegKey HKCU "Software\Microsoft\Windows\CurrentVersion\Explorer\RecentDocs\.${EXT}" !macroend -!macro APP_TEARDOWN APP_ID APP_NAME APP_EXE +!macro APP_TEARDOWN_Def APP_ID APP_NAME APP_EXE # Unregister file type ClearErrors @@ -208,13 +221,6 @@ VIAddVersionKey /LANG=${LANG_ENGLISH} "ProductVersion" "@Launcher_VERSION_NAME4@ # Unregister "Open With" DeleteRegKey ShCtx "Software\Classes\Applications\${APP_EXE}" - # Unregister "Default Programs" - !ifdef REGISTER_DEFAULTPROGRAMS - DeleteRegValue ShCtx "Software\RegisteredApplications" `${APP_NAME}` - DeleteRegKey ShCtx "Software\Classes\Applications\${APP_EXE}\Capabilities" - DeleteRegKey /IfEmpty ShCtx "Software\Classes\Applications\${APP_EXE}" - !endif - DeleteRegKey ShCtx `Software\Classes\${APP_ID}` DeleteRegKey ShCtx "Software\Classes\Applications\${APP_EXE}" @@ -227,6 +233,23 @@ VIAddVersionKey /LANG=${LANG_ENGLISH} "ProductVersion" "@Launcher_VERSION_NAME4@ !macroend +!macro APP_TEARDOWN APP_ID APP_NAME APP_EXE + + !insertmacro APP_TEARDOWN_Def `${APP_ID}` `${APP_NAME}` `${APP_EXE}` + +!macroend + +!macro APP_TEARDOWN_DEFAULT APP_ID APP_NAME APP_EXE + + !insertmacro APP_TEARDOWN_Def `${APP_ID}` `${APP_NAME}` `${APP_EXE}` + + # Unregister "Default Programs" + DeleteRegValue ShCtx "Software\RegisteredApplications" `${APP_NAME}` + DeleteRegKey ShCtx "Software\Classes\Applications\${APP_EXE}\Capabilities" + DeleteRegKey /IfEmpty ShCtx "Software\Classes\Applications\${APP_EXE}" + +!macroend + ; !defines for use with SHChangeNotify !ifdef SHCNE_ASSOCCHANGED !undef SHCNE_ASSOCCHANGED @@ -308,21 +331,19 @@ Section /o "Desktop Shortcut" DESKTOP_SHORTCUTS SectionEnd -!define APP_ID "@Launcher_CommonName@.App" -!define APP_EXE "@Launcher_APP_BINARY_NAME@.exe" -!define APP_ICON "$INSTDIR\${APP_EXE},0" -!define APP_DESCRIPTION "@Launcher_DisplayName@" -!define APP_NAME "@Launcher_DisplayName@" -!define APP_CMD_TEXT "Minecraft Modpack" - -!define REGISTER_DEFAULTPROGRAMS ; value doesn't matter +!define APPID "@Launcher_CommonName@.App" +!define APPEXE "@Launcher_APP_BINARY_NAME@.exe" +!define APPICON "$INSTDIR\${APPEXE},0" +!define APPDESCRIPTION "@Launcher_DisplayName@" +!define APPNAME "@Launcher_DisplayName@" +!define APPCMDTEXT "Minecraft Modpack" Section -ShellAssoc - !insertmacro APP_SETUP `${APP_DESCRIPTION}` `${APP_ICON}` `${APP_ID}` `${APP_CMD_TEXT}` `${APP_EXE}` `${APP_CMD_TEXT}` '$INSTDIR\${APP_EXE} -I "%1"' + !insertmacro APP_SETUP `${APPDESCRIPTION}` `${APPICON}` `${APPID}` `${APPCMDTEXT}` `${APPEXE}` `${APPCMDTEXT}` '$INSTDIR\${APPEXE} -I "%1"' - !insertmacro APP_ASSOCIATE ".zip" `${APP_ID}` `${APP_EXE}` false - !insertmacro APP_ASSOCIATE ".mrpack" `${APP_ID}` `${APP_EXE}` true + !insertmacro APP_ASSOCIATE_DEFAULT ".mrpack" `${APPID}` `${APPEXE}` true + !insertmacro APP_ASSOCIATE ".zip" `${APPID}` `${APPEXE}` false !insertmacro NotifyShell_AssocChanged SectionEnd @@ -361,10 +382,10 @@ SectionEnd Section -un.ShellAssoc - !insertmacro APP_TEARDOWN `${APP_ID}` `${APP_NAME}` `${APP_EXE}` + !insertmacro APP_TEARDOWN_DEFAULT `${APPID}` `${APPNAME}` `${APPEXE}` - !insertmacro APP_UNASSOCIATE ".zip" `${APP_ID}` - !insertmacro APP_UNASSOCIATE ".mrpack" `${APP_ID}` + !insertmacro APP_UNASSOCIATE ".zip" `${APPID}` + !insertmacro APP_UNASSOCIATE ".mrpack" `${APPID}` !insertmacro NotifyShell_AssocChanged SectionEnd From 7d02e1f8660193e88086c0e80b6749e4d34af0a7 Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Sun, 12 Feb 2023 17:34:37 -0700 Subject: [PATCH 021/185] feat: make shell association an optional section Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- program_info/win_install.nsi.in | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/program_info/win_install.nsi.in b/program_info/win_install.nsi.in index 5ad6078e..49e22500 100644 --- a/program_info/win_install.nsi.in +++ b/program_info/win_install.nsi.in @@ -336,9 +336,9 @@ SectionEnd !define APPICON "$INSTDIR\${APPEXE},0" !define APPDESCRIPTION "@Launcher_DisplayName@" !define APPNAME "@Launcher_DisplayName@" -!define APPCMDTEXT "Minecraft Modpack" +!define APPCMDTEXT "@Launcher_DisplayName@" -Section -ShellAssoc +Section /o "Shell Association (Open-With dialog)" SHELL_ASSOC !insertmacro APP_SETUP `${APPDESCRIPTION}` `${APPICON}` `${APPID}` `${APPCMDTEXT}` `${APPEXE}` `${APPCMDTEXT}` '$INSTDIR\${APPEXE} -I "%1"' From 80840f1fdb14f2972e6bc487b061f419334894da Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Sun, 12 Feb 2023 22:32:34 -0700 Subject: [PATCH 022/185] fix: add missing header to Application.cpp fails to compile on KISS Linux without Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- launcher/Application.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/launcher/Application.cpp b/launcher/Application.cpp index 0d3b086f..caaa74c8 100644 --- a/launcher/Application.cpp +++ b/launcher/Application.cpp @@ -79,6 +79,7 @@ #include #include +#include #include #include #include From 9c2a3231c5ee1b15b09bae9b064827ad3dcb86e0 Mon Sep 17 00:00:00 2001 From: kumquat-ir <66188216+kumquat-ir@users.noreply.github.com> Date: Mon, 13 Feb 2023 01:45:23 -0500 Subject: [PATCH 023/185] do not create nilmods folder "it cant be that easy" - me, clueless Signed-off-by: kumquat-ir <66188216+kumquat-ir@users.noreply.github.com> --- launcher/minecraft/MinecraftInstance.cpp | 2 +- launcher/minecraft/mod/ModFolderModel.cpp | 2 +- launcher/minecraft/mod/ModFolderModel.h | 2 +- launcher/minecraft/mod/ResourceFolderModel.cpp | 6 ++++-- launcher/minecraft/mod/ResourceFolderModel.h | 3 ++- launcher/ui/pages/instance/ModFolderPage.cpp | 2 +- launcher/ui/pages/instance/ModFolderPage.h | 2 +- 7 files changed, 11 insertions(+), 8 deletions(-) diff --git a/launcher/minecraft/MinecraftInstance.cpp b/launcher/minecraft/MinecraftInstance.cpp index 0737a648..af4da5d0 100644 --- a/launcher/minecraft/MinecraftInstance.cpp +++ b/launcher/minecraft/MinecraftInstance.cpp @@ -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)); + m_nil_mod_list.reset(new ModFolderModel(nilModsDir(), is_indexed, false)); m_nil_mod_list->disableInteraction(isRunning()); connect(this, &BaseInstance::runningStatusChanged, m_nil_mod_list.get(), &ModFolderModel::disableInteraction); } diff --git a/launcher/minecraft/mod/ModFolderModel.cpp b/launcher/minecraft/mod/ModFolderModel.cpp index f258ad69..2d777656 100644 --- a/launcher/minecraft/mod/ModFolderModel.cpp +++ b/launcher/minecraft/mod/ModFolderModel.cpp @@ -50,7 +50,7 @@ #include "minecraft/mod/tasks/ModFolderLoadTask.h" #include "modplatform/ModIndex.h" -ModFolderModel::ModFolderModel(const QString &dir, bool is_indexed) : ResourceFolderModel(QDir(dir)), m_is_indexed(is_indexed) +ModFolderModel::ModFolderModel(const QString &dir, bool is_indexed, bool create_dir) : ResourceFolderModel(QDir(dir), 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 6898f6eb..84e70db9 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); + ModFolderModel(const QString &dir, 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 fdfb434b..b8353133 100644 --- a/launcher/minecraft/mod/ResourceFolderModel.cpp +++ b/launcher/minecraft/mod/ResourceFolderModel.cpp @@ -12,9 +12,11 @@ #include "tasks/Task.h" -ResourceFolderModel::ResourceFolderModel(QDir dir, QObject* parent) : QAbstractListModel(parent), m_dir(dir), m_watcher(this) +ResourceFolderModel::ResourceFolderModel(QDir dir, bool create_dir, QObject* parent) : QAbstractListModel(parent), m_dir(dir), m_watcher(this) { - FS::ensureFolderPathExists(m_dir.absolutePath()); + if (create_dir) { + FS::ensureFolderPathExists(m_dir.absolutePath()); + } m_dir.setFilter(QDir::Readable | QDir::NoDotAndDotDot | QDir::Files | QDir::Dirs); m_dir.setSorting(QDir::Name | QDir::IgnoreCase | QDir::LocaleAware); diff --git a/launcher/minecraft/mod/ResourceFolderModel.h b/launcher/minecraft/mod/ResourceFolderModel.h index f1bc2dd7..e4227a33 100644 --- a/launcher/minecraft/mod/ResourceFolderModel.h +++ b/launcher/minecraft/mod/ResourceFolderModel.h @@ -24,7 +24,8 @@ class QSortFilterProxyModel; class ResourceFolderModel : public QAbstractListModel { Q_OBJECT public: - ResourceFolderModel(QDir, QObject* parent = nullptr); + ResourceFolderModel(QDir, bool, QObject* parent = nullptr); + ResourceFolderModel(QDir dir, QObject* parent = nullptr) : ResourceFolderModel(dir, true, parent) {}; ~ResourceFolderModel() override; /** Starts watching the paths for changes. diff --git a/launcher/ui/pages/instance/ModFolderPage.cpp b/launcher/ui/pages/instance/ModFolderPage.cpp index 8c87a8a0..4548af59 100644 --- a/launcher/ui/pages/instance/ModFolderPage.cpp +++ b/launcher/ui/pages/instance/ModFolderPage.cpp @@ -280,5 +280,5 @@ NilModFolderPage::NilModFolderPage(BaseInstance* inst, std::shared_ptrdir().isEmpty(); + return m_model->dir().exists(); } diff --git a/launcher/ui/pages/instance/ModFolderPage.h b/launcher/ui/pages/instance/ModFolderPage.h index cad240bd..2fc7b574 100644 --- a/launcher/ui/pages/instance/ModFolderPage.h +++ b/launcher/ui/pages/instance/ModFolderPage.h @@ -90,7 +90,7 @@ class NilModFolderPage : public ModFolderPage { virtual QString displayName() const override { return tr("Nilmods"); } virtual QIcon icon() const override { return APPLICATION->getThemedIcon("coremods"); } virtual QString id() const override { return "nilmods"; } - virtual QString helpPage() const override { return "Nil-mods"; } + virtual QString helpPage() const override { return "Nilmods"; } virtual bool shouldDisplay() const override; }; From 89c945ecc8de579e8f93ae302a7dabf4629e188f Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Tue, 14 Feb 2023 11:10:29 +0100 Subject: [PATCH 024/185] feat(ci): add Windows codesigning Signed-off-by: Sefa Eyeoglu --- .github/workflows/build.yml | 27 ++++++++++++++++++++++++++- .github/workflows/trigger_builds.yml | 2 ++ .github/workflows/trigger_release.yml | 3 +++ 3 files changed, 31 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 625ac099..c3b9f206 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -15,6 +15,12 @@ on: SPARKLE_ED25519_KEY: description: Private key for signing Sparkle updates required: false + WINDOWS_CODESIGN_CERT: + description: Certificate for signing Windows builds + required: false + WINDOWS_CODESIGN_PASSWORD: + description: Password for signing Windows builds + required: false CACHIX_AUTH_TOKEN: description: Private token for authenticating against Cachix cache required: false @@ -40,6 +46,7 @@ jobs: - os: windows-2022 name: "Windows-MinGW-w64" msystem: clang64 + vcvars_arch: 'amd64_x86' - os: windows-2022 name: "Windows-MSVC-Legacy" @@ -225,7 +232,7 @@ jobs: cache: ${{ inputs.is_qt_cached }} - name: Install MSVC (Windows MSVC) - if: runner.os == 'Windows' && matrix.msystem == '' + if: runner.os == 'Windows' # We want this for MinGW builds as well, as we need SignTool uses: ilammy/msvc-dev-cmd@v1 with: vsversion: 2022 @@ -377,6 +384,19 @@ jobs: Copy-Item D:/a/PrismLauncher/Qt/Tools/OpenSSL/Win_x86/bin/libssl-1_1.dll -Destination libssl-1_1.dll } + - name: Fetch codesign certificate (Windows) + if: runner.os == 'Windows' + shell: bash # yes, we are not using MSYS2 or PowerShell here + run: | + echo '${{ secrets.WINDOWS_CODESIGN_CERT }}' | base64 --decode > codesign.pfx + + - name: Sign executable (Windows) + if: runner.os == 'Windows' + run: | + cd ${{ env.INSTALL_DIR }} + # We ship the exact same executable for portable and non-portable editions, so signing just once is fine + SignTool sign /fd sha256 /td sha256 /f ../codesign.pfx /p '${{ secrets.WINDOWS_CODESIGN_PASSWORD }}' /tr http://timestamp.digicert.com prismlauncher.exe + - name: Package (Windows MinGW-w64, portable) if: runner.os == 'Windows' && matrix.msystem != '' shell: msys2 {0} @@ -396,6 +416,11 @@ jobs: cd ${{ env.INSTALL_DIR }} makensis -NOCD "${{ github.workspace }}/${{ env.BUILD_DIR }}/program_info/win_install.nsi" + - name: Sign installer (Windows) + if: runner.os == 'Windows' + run: | + SignTool sign /fd sha256 /td sha256 /f codesign.pfx /p '${{ secrets.WINDOWS_CODESIGN_PASSWORD }}' /tr http://timestamp.digicert.com PrismLauncher-Setup.exe + - name: Package (Linux) if: runner.os == 'Linux' run: | diff --git a/.github/workflows/trigger_builds.yml b/.github/workflows/trigger_builds.yml index a08193a0..26ee4380 100644 --- a/.github/workflows/trigger_builds.yml +++ b/.github/workflows/trigger_builds.yml @@ -31,4 +31,6 @@ jobs: is_qt_cached: true secrets: SPARKLE_ED25519_KEY: ${{ secrets.SPARKLE_ED25519_KEY }} + WINDOWS_CODESIGN_CERT: ${{ secrets.WINDOWS_CODESIGN_CERT }} + WINDOWS_CODESIGN_PASSWORD: ${{ secrets.WINDOWS_CODESIGN_PASSWORD }} CACHIX_AUTH_TOKEN: ${{ secrets.CACHIX_AUTH_TOKEN }} diff --git a/.github/workflows/trigger_release.yml b/.github/workflows/trigger_release.yml index a2f89819..3c56a38e 100644 --- a/.github/workflows/trigger_release.yml +++ b/.github/workflows/trigger_release.yml @@ -15,6 +15,9 @@ jobs: is_qt_cached: false secrets: SPARKLE_ED25519_KEY: ${{ secrets.SPARKLE_ED25519_KEY }} + WINDOWS_CODESIGN_CERT: ${{ secrets.WINDOWS_CODESIGN_CERT }} + WINDOWS_CODESIGN_PASSWORD: ${{ secrets.WINDOWS_CODESIGN_PASSWORD }} + CACHIX_AUTH_TOKEN: ${{ secrets.CACHIX_AUTH_TOKEN }} create_release: needs: build_release From 33bf85a387e535a27ad23e42b72c5fe7cce7f64c Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Wed, 15 Feb 2023 21:34:12 +0100 Subject: [PATCH 025/185] fix(actions): don't fail if code signing certificate is missing Signed-off-by: Sefa Eyeoglu --- .github/workflows/build.yml | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c3b9f206..c844f356 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -393,9 +393,13 @@ jobs: - name: Sign executable (Windows) if: runner.os == 'Windows' run: | - cd ${{ env.INSTALL_DIR }} - # We ship the exact same executable for portable and non-portable editions, so signing just once is fine - SignTool sign /fd sha256 /td sha256 /f ../codesign.pfx /p '${{ secrets.WINDOWS_CODESIGN_PASSWORD }}' /tr http://timestamp.digicert.com prismlauncher.exe + if (Get-Content ./codesign.pfx){ + cd ${{ env.INSTALL_DIR }} + # We ship the exact same executable for portable and non-portable editions, so signing just once is fine + SignTool sign /fd sha256 /td sha256 /f ../codesign.pfx /p '${{ secrets.WINDOWS_CODESIGN_PASSWORD }}' /tr http://timestamp.digicert.com prismlauncher.exe + } else { + ":warning: Skipped code signing for Windows, as certificate was not present." >> $env:GITHUB_STEP_SUMMARY + } - name: Package (Windows MinGW-w64, portable) if: runner.os == 'Windows' && matrix.msystem != '' @@ -419,7 +423,11 @@ jobs: - name: Sign installer (Windows) if: runner.os == 'Windows' run: | - SignTool sign /fd sha256 /td sha256 /f codesign.pfx /p '${{ secrets.WINDOWS_CODESIGN_PASSWORD }}' /tr http://timestamp.digicert.com PrismLauncher-Setup.exe + if (Get-Content ./codesign.pfx){ + SignTool sign /fd sha256 /td sha256 /f codesign.pfx /p '${{ secrets.WINDOWS_CODESIGN_PASSWORD }}' /tr http://timestamp.digicert.com PrismLauncher-Setup.exe + } else { + ":warning: Skipped code signing for Windows, as certificate was not present." >> $env:GITHUB_STEP_SUMMARY + } - name: Package (Linux) if: runner.os == 'Linux' From 256fc322a8decfd113ea73bf61ec15ce31a6f474 Mon Sep 17 00:00:00 2001 From: kumquat-ir <66188216+kumquat-ir@users.noreply.github.com> Date: Thu, 16 Feb 2023 12:57:35 -0500 Subject: [PATCH 026/185] minor cleanup Signed-off-by: kumquat-ir <66188216+kumquat-ir@users.noreply.github.com> --- launcher/minecraft/mod/tasks/LocalModParseTask.cpp | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/launcher/minecraft/mod/tasks/LocalModParseTask.cpp b/launcher/minecraft/mod/tasks/LocalModParseTask.cpp index 2263f8ec..945ccda7 100644 --- a/launcher/minecraft/mod/tasks/LocalModParseTask.cpp +++ b/launcher/minecraft/mod/tasks/LocalModParseTask.cpp @@ -434,17 +434,12 @@ bool processZIP(Mod& mod, ProcessingLevel level) QStringList foundNilMetas; for (auto& fname : zip.getFileNameList()) { - if (fname.endsWith(".nilmod.css")) { + // nilmods can shade nilloader to be able to run as a standalone agent - which includes nilloader's own meta file + if (fname.endsWith(".nilmod.css") && fname != "nilloader.nilmod.css") { foundNilMetas.append(fname); } } - if (foundNilMetas.size() > 1 && foundNilMetas.at(0) == "nilloader.nilmod.css") { - // nilmods can shade nilloader to be able to run as a standalone agent - which includes nilloader's own meta file - // so if there is more than one meta file, ignore nilloader's meta, since it's not the actual mod - foundNilMetas.removeFirst(); - } - if (zip.setCurrentFile(foundNilMetas.at(0))) { if (!file.open(QIODevice::ReadOnly)) { zip.close(); From 7973b01e81af29f0de7c6873ef119f15a2fb0791 Mon Sep 17 00:00:00 2001 From: kumquat-ir <66188216+kumquat-ir@users.noreply.github.com> Date: Sun, 19 Feb 2023 17:21:53 -0500 Subject: [PATCH 027/185] fix metadata for mods with `.nilmod` extension Signed-off-by: kumquat-ir <66188216+kumquat-ir@users.noreply.github.com> --- launcher/minecraft/mod/Resource.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/launcher/minecraft/mod/Resource.cpp b/launcher/minecraft/mod/Resource.cpp index 7c572d92..0d35d755 100644 --- a/launcher/minecraft/mod/Resource.cpp +++ b/launcher/minecraft/mod/Resource.cpp @@ -37,6 +37,9 @@ void Resource::parseFile() if (file_name.endsWith(".zip") || file_name.endsWith(".jar")) { m_type = ResourceType::ZIPFILE; file_name.chop(4); + } else if (file_name.endsWith(".nilmod")) { + m_type = ResourceType::ZIPFILE; + file_name.chop(7); } else if (file_name.endsWith(".litemod")) { m_type = ResourceType::LITEMOD; file_name.chop(8); From c50e9ac418cd286ebecf2213f1b65af5ecc893f5 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 21 Feb 2023 12:33:23 +0000 Subject: [PATCH 028/185] chore(deps): update actions/cache action to v3.2.6 --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c844f356..8934f584 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -158,7 +158,7 @@ jobs: - name: Retrieve ccache cache (Windows MinGW-w64) if: runner.os == 'Windows' && matrix.msystem != '' && inputs.build_type == 'Debug' - uses: actions/cache@v3.2.5 + uses: actions/cache@v3.2.6 with: path: '${{ github.workspace }}\.ccache' key: ${{ matrix.os }}-mingw-w64-ccache-${{ github.run_id }} From dc8109658c3c178e91acfc316467d1bdffe0bf40 Mon Sep 17 00:00:00 2001 From: kumquat-ir <66188216+kumquat-ir@users.noreply.github.com> Date: Wed, 22 Feb 2023 13:20:13 -0500 Subject: [PATCH 029/185] review fixes Signed-off-by: kumquat-ir <66188216+kumquat-ir@users.noreply.github.com> --- launcher/minecraft/mod/ModFolderModel.cpp | 2 +- launcher/minecraft/mod/ResourceFolderModel.cpp | 2 +- launcher/minecraft/mod/ResourceFolderModel.h | 3 +-- launcher/minecraft/mod/tasks/LocalModParseTask.cpp | 9 +++++---- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/launcher/minecraft/mod/ModFolderModel.cpp b/launcher/minecraft/mod/ModFolderModel.cpp index 2d777656..3f31b93c 100644 --- a/launcher/minecraft/mod/ModFolderModel.cpp +++ b/launcher/minecraft/mod/ModFolderModel.cpp @@ -50,7 +50,7 @@ #include "minecraft/mod/tasks/ModFolderLoadTask.h" #include "modplatform/ModIndex.h" -ModFolderModel::ModFolderModel(const QString &dir, bool is_indexed, bool create_dir) : ResourceFolderModel(QDir(dir), create_dir), m_is_indexed(is_indexed) +ModFolderModel::ModFolderModel(const QString &dir, bool is_indexed, bool create_dir) : ResourceFolderModel(QDir(dir), 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/ResourceFolderModel.cpp b/launcher/minecraft/mod/ResourceFolderModel.cpp index b8353133..f2a77c12 100644 --- a/launcher/minecraft/mod/ResourceFolderModel.cpp +++ b/launcher/minecraft/mod/ResourceFolderModel.cpp @@ -12,7 +12,7 @@ #include "tasks/Task.h" -ResourceFolderModel::ResourceFolderModel(QDir dir, bool create_dir, QObject* parent) : QAbstractListModel(parent), m_dir(dir), m_watcher(this) +ResourceFolderModel::ResourceFolderModel(QDir dir, QObject* parent, bool create_dir) : QAbstractListModel(parent), m_dir(dir), m_watcher(this) { if (create_dir) { FS::ensureFolderPathExists(m_dir.absolutePath()); diff --git a/launcher/minecraft/mod/ResourceFolderModel.h b/launcher/minecraft/mod/ResourceFolderModel.h index e4227a33..3bd78870 100644 --- a/launcher/minecraft/mod/ResourceFolderModel.h +++ b/launcher/minecraft/mod/ResourceFolderModel.h @@ -24,8 +24,7 @@ class QSortFilterProxyModel; class ResourceFolderModel : public QAbstractListModel { Q_OBJECT public: - ResourceFolderModel(QDir, bool, QObject* parent = nullptr); - ResourceFolderModel(QDir dir, QObject* parent = nullptr) : ResourceFolderModel(dir, true, parent) {}; + ResourceFolderModel(QDir, QObject* parent = nullptr, bool create_dir = true); ~ResourceFolderModel() override; /** Starts watching the paths for changes. diff --git a/launcher/minecraft/mod/tasks/LocalModParseTask.cpp b/launcher/minecraft/mod/tasks/LocalModParseTask.cpp index 945ccda7..da27a505 100644 --- a/launcher/minecraft/mod/tasks/LocalModParseTask.cpp +++ b/launcher/minecraft/mod/tasks/LocalModParseTask.cpp @@ -432,21 +432,22 @@ bool processZIP(Mod& mod, ProcessingLevel level) // nilloader uses the filename of the metadata file for the modid, so we can't know the exact filename // thankfully, there is a good file to use as a canary so we don't look for nil meta all the time - QStringList foundNilMetas; + QString foundNilMeta; for (auto& fname : zip.getFileNameList()) { // nilmods can shade nilloader to be able to run as a standalone agent - which includes nilloader's own meta file if (fname.endsWith(".nilmod.css") && fname != "nilloader.nilmod.css") { - foundNilMetas.append(fname); + foundNilMeta = fname; + break; } } - if (zip.setCurrentFile(foundNilMetas.at(0))) { + if (zip.setCurrentFile(foundNilMeta)) { if (!file.open(QIODevice::ReadOnly)) { zip.close(); return false; } - details = ReadNilModInfo(file.readAll(), foundNilMetas.at(0)); + details = ReadNilModInfo(file.readAll(), foundNilMeta); file.close(); zip.close(); From 01c4ed232e67c1b980a0c091c4c277722811d3dc Mon Sep 17 00:00:00 2001 From: kumquat-ir <66188216+kumquat-ir@users.noreply.github.com> Date: Fri, 24 Feb 2023 15:01:59 -0500 Subject: [PATCH 030/185] license stuff Signed-off-by: kumquat-ir <66188216+kumquat-ir@users.noreply.github.com> --- libraries/qdcss/LICENSE | 72 +++++++++++++++++++++++++++++++++ libraries/qdcss/include/qdcss.h | 4 ++ libraries/qdcss/src/qdcss.cpp | 4 ++ 3 files changed, 80 insertions(+) create mode 100644 libraries/qdcss/LICENSE diff --git a/libraries/qdcss/LICENSE b/libraries/qdcss/LICENSE new file mode 100644 index 00000000..0e547029 --- /dev/null +++ b/libraries/qdcss/LICENSE @@ -0,0 +1,72 @@ +GNU LESSER GENERAL PUBLIC LICENSE +Version 3, 29 June 2007 + +Copyright (C) 2007 Free Software Foundation, Inc. + +Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. + +This version of the GNU Lesser General Public License incorporates the terms and conditions of version 3 of the GNU General Public License, supplemented by the additional permissions listed below. + +0. Additional Definitions. + +As used herein, "this License" refers to version 3 of the GNU Lesser General Public License, and the "GNU GPL" refers to version 3 of the GNU General Public License. + +"The Library" refers to a covered work governed by this License, other than an Application or a Combined Work as defined below. + +An "Application" is any work that makes use of an interface provided by the Library, but which is not otherwise based on the Library. Defining a subclass of a class defined by the Library is deemed a mode of using an interface provided by the Library. + +A "Combined Work" is a work produced by combining or linking an Application with the Library. The particular version of the Library with which the Combined Work was made is also called the "Linked Version". + +The "Minimal Corresponding Source" for a Combined Work means the Corresponding Source for the Combined Work, excluding any source code for portions of the Combined Work that, considered in isolation, are based on the Application, and not on the Linked Version. + +The "Corresponding Application Code" for a Combined Work means the object code and/or source code for the Application, including any data and utility programs needed for reproducing the Combined Work from the Application, but excluding the System Libraries of the Combined Work. + +1. Exception to Section 3 of the GNU GPL. +You may convey a covered work under sections 3 and 4 of this License without being bound by section 3 of the GNU GPL. + +2. Conveying Modified Versions. +If you modify a copy of the Library, and, in your modifications, a facility refers to a function or data to be supplied by an Application that uses the facility (other than as an argument passed when the facility is invoked), then you may convey a copy of the modified version: + + a) under this License, provided that you make a good faith effort to ensure that, in the event an Application does not supply the function or data, the facility still operates, and performs whatever part of its purpose remains meaningful, or + + b) under the GNU GPL, with none of the additional permissions of this License applicable to that copy. + +3. Object Code Incorporating Material from Library Header Files. +The object code form of an Application may incorporate material from a header file that is part of the Library. You may convey such object code under terms of your choice, provided that, if the incorporated material is not limited to numerical parameters, data structure layouts and accessors, or small macros, inline functions and templates (ten or fewer lines in length), you do both of the following: + + a) Give prominent notice with each copy of the object code that the Library is used in it and that the Library and its use are covered by this License. + + b) Accompany the object code with a copy of the GNU GPL and this license document. + +4. Combined Works. +You may convey a Combined Work under terms of your choice that, taken together, effectively do not restrict modification of the portions of the Library contained in the Combined Work and reverse engineering for debugging such modifications, if you also do each of the following: + + a) Give prominent notice with each copy of the Combined Work that the Library is used in it and that the Library and its use are covered by this License. + + b) Accompany the Combined Work with a copy of the GNU GPL and this license document. + + c) For a Combined Work that displays copyright notices during execution, include the copyright notice for the Library among these notices, as well as a reference directing the user to the copies of the GNU GPL and this license document. + + d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of this License, and the Corresponding Application Code in a form suitable for, and under terms that permit, the user to recombine or relink the Application with a modified version of the Linked Version to produce a modified Combined Work, in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (a) uses at run time a copy of the Library already present on the user's computer system, and (b) will operate properly with a modified version of the Library that is interface-compatible with the Linked Version. + + e) Provide Installation Information, but only if you would otherwise be required to provide such information under section 6 of the GNU GPL, and only to the extent that such information is necessary to install and execute a modified version of the Combined Work produced by recombining or relinking the Application with a modified version of the Linked Version. (If you use option 4d0, the Installation Information must accompany the Minimal Corresponding Source and Corresponding Application Code. If you use option 4d1, you must provide the Installation Information in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source.) + +5. Combined Libraries. +You may place library facilities that are a work based on the Library side by side in a single library together with other library facilities that are not Applications and are not covered by this License, and convey such a combined library under terms of your choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities, conveyed under the terms of this License. + + b) Give prominent notice with the combined library that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. + +6. Revised Versions of the GNU Lesser General Public License. +The Free Software Foundation may publish revised and/or new versions of the GNU Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library as you received it specifies that a certain numbered version of the GNU Lesser General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that published version or of any later version published by the Free Software Foundation. If the Library as you received it does not specify a version number of the GNU Lesser General Public License, you may choose any version of the GNU Lesser General Public License ever published by the Free Software Foundation. + +If the Library as you received it specifies that a proxy can decide whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is permanent authorization for you to choose that version for the Library. + diff --git a/libraries/qdcss/include/qdcss.h b/libraries/qdcss/include/qdcss.h index 2bcac36f..a7fac34e 100644 --- a/libraries/qdcss/include/qdcss.h +++ b/libraries/qdcss/include/qdcss.h @@ -1,3 +1,7 @@ +// SPDX-FileCopyrightText: 2023 kumquat-ir 66188216+kumquat-ir@users.noreply.github.com +// +// SPDX-License-Identifier: LGPL-3.0-only + #ifndef QDCSS_H #define QDCSS_H diff --git a/libraries/qdcss/src/qdcss.cpp b/libraries/qdcss/src/qdcss.cpp index 4426dcae..c531fb63 100644 --- a/libraries/qdcss/src/qdcss.cpp +++ b/libraries/qdcss/src/qdcss.cpp @@ -1,3 +1,7 @@ +// SPDX-FileCopyrightText: 2023 kumquat-ir 66188216+kumquat-ir@users.noreply.github.com +// +// SPDX-License-Identifier: LGPL-3.0-only + #include "qdcss.h" #include From fda9ca1e34eda3133f46218735b1178e9a7eb854 Mon Sep 17 00:00:00 2001 From: flow Date: Fri, 24 Feb 2023 20:08:29 -0300 Subject: [PATCH 031/185] fix+refactor: fix fail to import modpacks and clean up a bit QDir::cleanPath called by FS::PathCombine removes the trailing '/'... Signed-off-by: flow --- launcher/MMCZip.cpp | 61 +++++++++++++++++++++------------------------ 1 file changed, 28 insertions(+), 33 deletions(-) diff --git a/launcher/MMCZip.cpp b/launcher/MMCZip.cpp index 31460bf4..c6d56543 100644 --- a/launcher/MMCZip.cpp +++ b/launcher/MMCZip.cpp @@ -275,7 +275,7 @@ bool MMCZip::findFilesInZip(QuaZip * zip, const QString & what, QStringList & re // ours std::optional MMCZip::extractSubDir(QuaZip *zip, const QString & subdir, const QString &target) { - auto absDirectoryUrl = QUrl::fromLocalFile(target); + auto target_top_dir = QUrl::fromLocalFile(target); QStringList extracted; @@ -295,58 +295,53 @@ std::optional MMCZip::extractSubDir(QuaZip *zip, const QString & su return std::nullopt; } - do - { - QString name = zip->getCurrentFileName(); - if(!name.startsWith(subdir)) - { + do { + QString file_name = zip->getCurrentFileName(); + if (!file_name.startsWith(subdir)) continue; - } - name.remove(0, subdir.size()); - auto original_name = name; + auto relative_file_name = QDir::fromNativeSeparators(file_name.remove(0, subdir.size())); + auto original_name = relative_file_name; // Fix subdirs/files ending with a / getting transformed into absolute paths - if(name.startsWith('/')){ - name = name.mid(1); - } + if (relative_file_name.startsWith('/')) + relative_file_name = relative_file_name.mid(1); // Fix weird "folders with a single file get squashed" thing - QString path; - if(name.contains('/') && !name.endsWith('/')){ - path = name.section('/', 0, -2) + "/"; - FS::ensureFolderPathExists(FS::PathCombine(target, path)); + QString sub_path; + if (relative_file_name.contains('/') && !relative_file_name.endsWith('/')) { + sub_path = relative_file_name.section('/', 0, -2) + '/'; + FS::ensureFolderPathExists(FS::PathCombine(target, sub_path)); - name = name.split('/').last(); + relative_file_name = relative_file_name.split('/').last(); } - QString absFilePath; - if(name.isEmpty()) - { - absFilePath = FS::PathCombine(target, "/"); // FIXME this seems weird - } - else - { - absFilePath = FS::PathCombine(target, path + name); + QString target_file_path; + if (relative_file_name.isEmpty()) { + target_file_path = target + '/'; + } else { + target_file_path = FS::PathCombine(target_top_dir.path(), sub_path, relative_file_name); + if (relative_file_name.endsWith('/') && !target_file_path.endsWith('/')) + target_file_path += '/'; } - if (!absDirectoryUrl.isParentOf(QUrl::fromLocalFile(absFilePath))) { - qWarning() << "Extracting" << name << "was cancelled, because it was effectively outside of the target path" << target; + if (!target_top_dir.isParentOf(QUrl::fromLocalFile(target_file_path))) { + qWarning() << "Extracting" << relative_file_name << "was cancelled, because it was effectively outside of the target path" << target; return std::nullopt; } - if (!JlCompress::extractFile(zip, "", absFilePath)) - { - qWarning() << "Failed to extract file" << original_name << "to" << absFilePath; + if (!JlCompress::extractFile(zip, "", target_file_path)) { + qWarning() << "Failed to extract file" << original_name << "to" << target_file_path; JlCompress::removeFile(extracted); return std::nullopt; } - extracted.append(absFilePath); - QFile::setPermissions(absFilePath, QFileDevice::Permission::ReadUser | QFileDevice::Permission::WriteUser | QFileDevice::Permission::ExeUser); + extracted.append(target_file_path); + QFile::setPermissions(target_file_path, QFileDevice::Permission::ReadUser | QFileDevice::Permission::WriteUser | QFileDevice::Permission::ExeUser); - qDebug() << "Extracted file" << name << "to" << absFilePath; + qDebug() << "Extracted file" << relative_file_name << "to" << target_file_path; } while (zip->goToNextFile()); + return extracted; } From d5c6704475e2571a8d084f7ecf8bee3ea2c7a173 Mon Sep 17 00:00:00 2001 From: flow Date: Fri, 24 Feb 2023 20:34:43 -0300 Subject: [PATCH 032/185] fix: prevent crash when aborting import task while extracting pack Signed-off-by: flow --- launcher/InstanceImportTask.cpp | 20 +++++++++++--------- launcher/InstanceImportTask.h | 1 - 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/launcher/InstanceImportTask.cpp b/launcher/InstanceImportTask.cpp index 6b5317e5..080828a8 100644 --- a/launcher/InstanceImportTask.cpp +++ b/launcher/InstanceImportTask.cpp @@ -66,7 +66,12 @@ bool InstanceImportTask::abort() if (m_filesNetJob) m_filesNetJob->abort(); - m_extractFuture.cancel(); + if (m_extractFuture.isRunning()) { + // NOTE: The tasks created by QtConcurrent::run() can't actually get cancelled, + // but we can use this call to check the state when the extraction finishes. + m_extractFuture.cancel(); + m_extractFuture.waitForFinished(); + } return Task::abort(); } @@ -185,18 +190,20 @@ void InstanceImportTask::processZipPack() // make sure we extract just the pack m_extractFuture = QtConcurrent::run(QThreadPool::globalInstance(), MMCZip::extractSubDir, m_packZip.get(), root, extractDir.absolutePath()); connect(&m_extractFutureWatcher, &QFutureWatcher::finished, this, &InstanceImportTask::extractFinished); - connect(&m_extractFutureWatcher, &QFutureWatcher::canceled, this, &InstanceImportTask::extractAborted); m_extractFutureWatcher.setFuture(m_extractFuture); } void InstanceImportTask::extractFinished() { m_packZip.reset(); - if (!m_extractFuture.result()) - { + + if (m_extractFuture.isCanceled()) + return; + if (!m_extractFuture.result().has_value()) { emitFailed(tr("Failed to extract modpack")); return; } + QDir extractDir(m_stagingPath); qDebug() << "Fixing permissions for extracted pack files..."; @@ -250,11 +257,6 @@ void InstanceImportTask::extractFinished() } } -void InstanceImportTask::extractAborted() -{ - emitAborted(); -} - void InstanceImportTask::processFlame() { FlameCreationTask* inst_creation_task = nullptr; diff --git a/launcher/InstanceImportTask.h b/launcher/InstanceImportTask.h index 6b8ac966..7fda439f 100644 --- a/launcher/InstanceImportTask.h +++ b/launcher/InstanceImportTask.h @@ -81,7 +81,6 @@ private slots: void downloadProgressChanged(qint64 current, qint64 total); void downloadAborted(); void extractFinished(); - void extractAborted(); private: /* data */ NetJob::Ptr m_filesNetJob; From f986b4cd56a901295230fc84758910a2bed215cf Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 1 Mar 2023 07:20:11 +0000 Subject: [PATCH 033/185] chore(deps): update cachix/install-nix-action action to v20 --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 8934f584..41eb5eb7 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -608,7 +608,7 @@ jobs: submodules: 'true' - name: Install nix if: inputs.build_type == 'Debug' - uses: cachix/install-nix-action@v19 + uses: cachix/install-nix-action@v20 with: install_url: https://nixos.org/nix/install extra_nix_config: | From 651e511ff06a54934573764906de176a397ef5a0 Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Wed, 1 Mar 2023 10:19:59 +0100 Subject: [PATCH 034/185] fix: use makeShared for importing components Signed-off-by: Sefa Eyeoglu --- launcher/minecraft/PackProfile.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/minecraft/PackProfile.cpp b/launcher/minecraft/PackProfile.cpp index 13da57d9..aff05dbc 100644 --- a/launcher/minecraft/PackProfile.cpp +++ b/launcher/minecraft/PackProfile.cpp @@ -763,7 +763,7 @@ bool PackProfile::installComponents(QStringList selectedFiles) continue; } - appendComponent(new Component(this, versionFile->uid, versionFile)); + appendComponent(makeShared(this, versionFile->uid, versionFile)); } scheduleSave(); From 1feb63052020db941a3c1a10d14c9a4791169c1a Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Wed, 1 Mar 2023 09:54:49 +0000 Subject: [PATCH 035/185] Allow custom Modrinth API token Signed-off-by: TheKodeToad --- launcher/Application.cpp | 15 ++++++++-- launcher/Application.h | 2 ++ launcher/net/Download.cpp | 7 ++++- launcher/net/Upload.cpp | 6 ++++ launcher/ui/pages/global/APIPage.cpp | 5 ++++ launcher/ui/pages/global/APIPage.ui | 45 ++++++++++++++++++++++++++++ 6 files changed, 77 insertions(+), 3 deletions(-) diff --git a/launcher/Application.cpp b/launcher/Application.cpp index caaa74c8..d6de6236 100644 --- a/launcher/Application.cpp +++ b/launcher/Application.cpp @@ -7,6 +7,7 @@ * Copyright (C) 2022 Sefa Eyeoglu * Copyright (C) 2022 Lenny McLennington * Copyright (C) 2022 Tayou + * Copyright (C) 2023 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 @@ -225,7 +226,7 @@ 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_instanceIdToShowWindowOf = parser.value("show"); for (auto zip_path : parser.values("import")){ @@ -346,7 +347,7 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv) import.command = "import"; import.args.insert("path", zip_url.toString()); m_peerInstance->sendMessage(import.serialize(), timeout); - } + } } } else @@ -657,6 +658,7 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv) m_settings->set("FlameKeyOverride", flameKey); m_settings->reset("CFKeyOverride"); } + m_settings->registerSetting("ModrinthToken", ""); m_settings->registerSetting("UserAgentOverride", ""); // Init page provider @@ -1545,6 +1547,15 @@ QString Application::getFlameAPIKey() return BuildConfig.FLAME_API_KEY; } +QString Application::getModrinthAPIToken() +{ + QString tokenOverride = m_settings->get("ModrinthToken").toString(); + if (!tokenOverride.isEmpty()) + return tokenOverride; + + return QString(); +} + QString Application::getUserAgent() { QString uaOverride = m_settings->get("UserAgentOverride").toString(); diff --git a/launcher/Application.h b/launcher/Application.h index 1b3dc499..91c5fc63 100644 --- a/launcher/Application.h +++ b/launcher/Application.h @@ -3,6 +3,7 @@ * Prism Launcher - Minecraft Launcher * Copyright (C) 2022 Sefa Eyeoglu * Copyright (C) 2022 Tayou + * Copyright (C) 2023 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 @@ -177,6 +178,7 @@ public: QString getMSAClientID(); QString getFlameAPIKey(); + QString getModrinthAPIToken(); QString getUserAgent(); QString getUserAgentUncached(); diff --git a/launcher/net/Download.cpp b/launcher/net/Download.cpp index 5982c8c9..0816e926 100644 --- a/launcher/net/Download.cpp +++ b/launcher/net/Download.cpp @@ -3,6 +3,7 @@ * PolyMC - Minecraft Launcher * Copyright (c) 2022 flowln * Copyright (C) 2022 Sefa Eyeoglu + * Copyright (C) 2023 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 @@ -116,7 +117,11 @@ void Download::executeTask() if (APPLICATION->capabilities() & Application::SupportsFlame && request.url().host().contains("api.curseforge.com")) { request.setRawHeader("x-api-key", APPLICATION->getFlameAPIKey().toUtf8()); - }; + } else if (request.url().host().contains("api.modrinth.com")) { + QString token = APPLICATION->getModrinthAPIToken(); + if (!token.isNull()) + request.setRawHeader("Authorization", token.toUtf8()); + } QNetworkReply* rep = m_network->get(request); diff --git a/launcher/net/Upload.cpp b/launcher/net/Upload.cpp index 79b6af8d..36d50c1e 100644 --- a/launcher/net/Upload.cpp +++ b/launcher/net/Upload.cpp @@ -3,6 +3,7 @@ * PolyMC - Minecraft Launcher * Copyright (c) 2022 flowln * Copyright (C) 2022 Sefa Eyeoglu + * Copyright (C) 2023 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 @@ -219,7 +220,12 @@ namespace Net { if (APPLICATION->capabilities() & Application::SupportsFlame && request.url().host().contains("api.curseforge.com")) { request.setRawHeader("x-api-key", APPLICATION->getFlameAPIKey().toUtf8()); + } else if (request.url().host().contains("api.modrinth.com")) { + QString token = APPLICATION->getModrinthAPIToken(); + if (!token.isNull()) + request.setRawHeader("Authorization", token.toUtf8()); } + //TODO other types of post requests ? request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); QNetworkReply* rep = m_network->post(request, m_post_data); diff --git a/launcher/ui/pages/global/APIPage.cpp b/launcher/ui/pages/global/APIPage.cpp index e3d30475..ac74b3a1 100644 --- a/launcher/ui/pages/global/APIPage.cpp +++ b/launcher/ui/pages/global/APIPage.cpp @@ -4,6 +4,7 @@ * Copyright (C) 2022 Sefa Eyeoglu * Copyright (c) 2022 Jamie Mansfield * Copyright (c) 2022 Lenny McLennington + * Copyright (C) 2023 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 @@ -147,6 +148,8 @@ void APIPage::loadSettings() ui->metaURL->setText(metaURL); QString flameKey = s->get("FlameKeyOverride").toString(); ui->flameKey->setText(flameKey); + QString modrinthToken = s->get("ModrinthToken").toString(); + ui->modrinthToken->setText(modrinthToken); QString customUserAgent = s->get("UserAgentOverride").toString(); ui->userAgentLineEdit->setText(customUserAgent); } @@ -177,6 +180,8 @@ void APIPage::applySettings() s->set("MetaURLOverride", metaURL); QString flameKey = ui->flameKey->text(); s->set("FlameKeyOverride", flameKey); + QString modrinthToken = ui->modrinthToken->text(); + s->set("ModrinthToken", modrinthToken); s->set("UserAgentOverride", ui->userAgentLineEdit->text()); } diff --git a/launcher/ui/pages/global/APIPage.ui b/launcher/ui/pages/global/APIPage.ui index d56a9ef6..b09a86ee 100644 --- a/launcher/ui/pages/global/APIPage.ui +++ b/launcher/ui/pages/global/APIPage.ui @@ -240,6 +240,51 @@ + + + + true + + + &Modrinth API + + + + + + Note: you only need to set this to access private data. + + + + + + + Enter a custom API token for Modrinth here. + + + Qt::RichText + + + true + + + true + + + + + + + true + + + (None) + + + + + + From a7b80922967744efa6c9ffba457ac6850d4f660a Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Wed, 1 Mar 2023 14:41:13 +0000 Subject: [PATCH 036/185] Add link Signed-off-by: TheKodeToad --- launcher/ui/pages/global/APIPage.ui | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/ui/pages/global/APIPage.ui b/launcher/ui/pages/global/APIPage.ui index b09a86ee..431fd8c6 100644 --- a/launcher/ui/pages/global/APIPage.ui +++ b/launcher/ui/pages/global/APIPage.ui @@ -252,7 +252,7 @@ - Note: you only need to set this to access private data. + <html><head/><body><p>Note: you only need to set this to access private data. Read the <a href="https://docs.modrinth.com/api-spec/#section/Authentication"><span style=" text-decoration: underline; color:#0000ff;">documentation</span></a> for more information.</p></body></html> From 89255e34bd0651ab226518090667bef92066d80c Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Thu, 2 Mar 2023 09:42:28 +0100 Subject: [PATCH 037/185] fix: change wording of TP/RP download buttons Signed-off-by: Sefa Eyeoglu --- launcher/ui/pages/instance/ResourcePackPage.cpp | 4 ++-- launcher/ui/pages/instance/TexturePackPage.cpp | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/launcher/ui/pages/instance/ResourcePackPage.cpp b/launcher/ui/pages/instance/ResourcePackPage.cpp index f46a7939..24bfb38d 100644 --- a/launcher/ui/pages/instance/ResourcePackPage.cpp +++ b/launcher/ui/pages/instance/ResourcePackPage.cpp @@ -46,8 +46,8 @@ 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->setText(tr("Download packs")); + ui->actionDownloadItem->setToolTip(tr("Download resource packs from online platforms")); ui->actionDownloadItem->setEnabled(true); connect(ui->actionDownloadItem, &QAction::triggered, this, &ResourcePackPage::downloadRPs); ui->actionsToolbar->insertActionBefore(ui->actionAddItem, ui->actionDownloadItem); diff --git a/launcher/ui/pages/instance/TexturePackPage.cpp b/launcher/ui/pages/instance/TexturePackPage.cpp index 76fc04e7..427aba11 100644 --- a/launcher/ui/pages/instance/TexturePackPage.cpp +++ b/launcher/ui/pages/instance/TexturePackPage.cpp @@ -48,8 +48,8 @@ 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->setText(tr("Download packs")); + ui->actionDownloadItem->setToolTip(tr("Download texture packs from online platforms")); ui->actionDownloadItem->setEnabled(true); connect(ui->actionDownloadItem, &QAction::triggered, this, &TexturePackPage::downloadTPs); ui->actionsToolbar->insertActionBefore(ui->actionAddItem, ui->actionDownloadItem); From 1f3d18ec12bb7a971962101570cbad8e18788614 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Thu, 2 Mar 2023 09:26:00 +0000 Subject: [PATCH 038/185] Replace with exact match and add TODO to improve Signed-off-by: TheKodeToad --- launcher/net/Download.cpp | 5 +++-- launcher/net/Upload.cpp | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/launcher/net/Download.cpp b/launcher/net/Download.cpp index 0816e926..adb3d6fa 100644 --- a/launcher/net/Download.cpp +++ b/launcher/net/Download.cpp @@ -114,10 +114,11 @@ void Download::executeTask() } request.setHeader(QNetworkRequest::UserAgentHeader, APPLICATION->getUserAgent().toUtf8()); + // TODO remove duplication and use constant if (APPLICATION->capabilities() & Application::SupportsFlame - && request.url().host().contains("api.curseforge.com")) { + && request.url().host() == "api.curseforge.com") { request.setRawHeader("x-api-key", APPLICATION->getFlameAPIKey().toUtf8()); - } else if (request.url().host().contains("api.modrinth.com")) { + } else if (request.url().host() == "api.modrinth.com") { QString token = APPLICATION->getModrinthAPIToken(); if (!token.isNull()) request.setRawHeader("Authorization", token.toUtf8()); diff --git a/launcher/net/Upload.cpp b/launcher/net/Upload.cpp index 36d50c1e..cd0de945 100644 --- a/launcher/net/Upload.cpp +++ b/launcher/net/Upload.cpp @@ -217,10 +217,11 @@ namespace Net { } request.setHeader(QNetworkRequest::UserAgentHeader, APPLICATION->getUserAgent().toUtf8()); + // TODO remove duplication and use constant if (APPLICATION->capabilities() & Application::SupportsFlame - && request.url().host().contains("api.curseforge.com")) { + && request.url().host() == "api.curseforge.com") { request.setRawHeader("x-api-key", APPLICATION->getFlameAPIKey().toUtf8()); - } else if (request.url().host().contains("api.modrinth.com")) { + } else if (request.url().host() == "api.modrinth.com") { QString token = APPLICATION->getModrinthAPIToken(); if (!token.isNull()) request.setRawHeader("Authorization", token.toUtf8()); From cbe6eff2fa917401b913a96c551429caad852b3b Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Thu, 2 Mar 2023 09:28:14 +0000 Subject: [PATCH 039/185] Move Modrinth before CurseForge for consistency Signed-off-by: TheKodeToad --- launcher/ui/pages/global/APIPage.ui | 90 ++++++++++++++--------------- 1 file changed, 45 insertions(+), 45 deletions(-) diff --git a/launcher/ui/pages/global/APIPage.ui b/launcher/ui/pages/global/APIPage.ui index 431fd8c6..8c515f3a 100644 --- a/launcher/ui/pages/global/APIPage.ui +++ b/launcher/ui/pages/global/APIPage.ui @@ -195,51 +195,6 @@ - - - - true - - - &CurseForge Core API - - - - - - Note: you probably don't need to set this if CurseForge already works. - - - - - - - Enter a custom API Key for CurseForge here. - - - Qt::RichText - - - true - - - true - - - - - - - true - - - (Default) - - - - - - @@ -285,6 +240,51 @@ + + + + true + + + &CurseForge Core API + + + + + + Note: you probably don't need to set this if CurseForge already works. + + + + + + + Enter a custom API Key for CurseForge here. + + + Qt::RichText + + + true + + + true + + + + + + + true + + + (Default) + + + + + + From 4efbf22089bc1fc49409cb31bfffe26cdb9b6b36 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Thu, 2 Mar 2023 10:19:26 +0000 Subject: [PATCH 040/185] Prism-ify more license headers Signed-off-by: TheKodeToad --- launcher/net/Download.cpp | 2 +- launcher/net/Upload.cpp | 2 +- launcher/ui/pages/global/APIPage.cpp | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/launcher/net/Download.cpp b/launcher/net/Download.cpp index adb3d6fa..f199c0b2 100644 --- a/launcher/net/Download.cpp +++ b/launcher/net/Download.cpp @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-3.0-only /* - * PolyMC - Minecraft Launcher + * Prism Launcher - Minecraft Launcher * Copyright (c) 2022 flowln * Copyright (C) 2022 Sefa Eyeoglu * Copyright (C) 2023 TheKodeToad diff --git a/launcher/net/Upload.cpp b/launcher/net/Upload.cpp index cd0de945..b90c71ba 100644 --- a/launcher/net/Upload.cpp +++ b/launcher/net/Upload.cpp @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-3.0-only /* - * PolyMC - Minecraft Launcher + * Prism Launcher - Minecraft Launcher * Copyright (c) 2022 flowln * Copyright (C) 2022 Sefa Eyeoglu * Copyright (C) 2023 TheKodeToad diff --git a/launcher/ui/pages/global/APIPage.cpp b/launcher/ui/pages/global/APIPage.cpp index ac74b3a1..f662ee1c 100644 --- a/launcher/ui/pages/global/APIPage.cpp +++ b/launcher/ui/pages/global/APIPage.cpp @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-3.0-only /* - * PolyMC - Minecraft Launcher + * Prism Launcher - Minecraft Launcher * Copyright (C) 2022 Sefa Eyeoglu * Copyright (c) 2022 Jamie Mansfield * Copyright (c) 2022 Lenny McLennington From 06de728aa7fc5db842f03a53fb8a0f8d2b131924 Mon Sep 17 00:00:00 2001 From: flow Date: Sun, 5 Mar 2023 08:24:56 -0300 Subject: [PATCH 041/185] fix: use `toLocalFile()` instead of `path()` QUrl::path() adds a '/' at the beginning of the path on Windows, causing the world to explode every once in a while. Signed-off-by: flow --- launcher/MMCZip.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/MMCZip.cpp b/launcher/MMCZip.cpp index c6d56543..1eda43fe 100644 --- a/launcher/MMCZip.cpp +++ b/launcher/MMCZip.cpp @@ -320,7 +320,7 @@ std::optional MMCZip::extractSubDir(QuaZip *zip, const QString & su if (relative_file_name.isEmpty()) { target_file_path = target + '/'; } else { - target_file_path = FS::PathCombine(target_top_dir.path(), sub_path, relative_file_name); + target_file_path = FS::PathCombine(target_top_dir.toLocalFile(), sub_path, relative_file_name); if (relative_file_name.endsWith('/') && !target_file_path.endsWith('/')) target_file_path += '/'; } From b9dfcf6d2f03eb859959287c552fcbf22e928565 Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Sun, 5 Mar 2023 14:21:07 +0100 Subject: [PATCH 042/185] chore: bump ghc-filesystem Signed-off-by: Sefa Eyeoglu --- libraries/filesystem | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/filesystem b/libraries/filesystem index cd6805e9..8a2edd6d 160000 --- a/libraries/filesystem +++ b/libraries/filesystem @@ -1 +1 @@ -Subproject commit cd6805e94dd5d6346be1b75a54cdc27787319dd2 +Subproject commit 8a2edd6d92ed820521d42c94d179462bf06b5ed3 From 07702d3be7755ecf536561e7ef1848d7085ca771 Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Sun, 5 Mar 2023 14:23:40 +0100 Subject: [PATCH 043/185] fix: drop unneeded GHC_FILESYSTEM_WITH_INSTALL Signed-off-by: Sefa Eyeoglu --- CMakeLists.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 6ce8143c..23269559 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -442,7 +442,6 @@ add_subdirectory(libraries/gamemode) add_subdirectory(libraries/murmur2) # Hash for usage with the CurseForge API if (NOT ghc_filesystem_FOUND) message(STATUS "Using bundled ghc_filesystem") - set(GHC_FILESYSTEM_WITH_INSTALL OFF) # Workaround ghc::filesystem bug add_subdirectory(libraries/filesystem) # Implementation of std::filesystem for old C++, for usage in old macOS add_library(ghcFilesystem::ghc_filesystem ALIAS ghc_filesystem) else() From dd96e1819d3fb72e01c268a1e4707f88b0df97c7 Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Sun, 5 Mar 2023 14:24:40 +0100 Subject: [PATCH 044/185] fix: remove unneeded alias for ghc_filesystem Signed-off-by: Sefa Eyeoglu --- CMakeLists.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 23269559..40936e44 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -443,7 +443,6 @@ add_subdirectory(libraries/murmur2) # Hash for usage with the CurseForge API if (NOT ghc_filesystem_FOUND) message(STATUS "Using bundled ghc_filesystem") add_subdirectory(libraries/filesystem) # Implementation of std::filesystem for old C++, for usage in old macOS - add_library(ghcFilesystem::ghc_filesystem ALIAS ghc_filesystem) else() message(STATUS "Using system ghc_filesystem") endif() From 76f66148db09b6110d2d916da5c9f9f4dae139b8 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Sun, 5 Mar 2023 18:21:24 +0000 Subject: [PATCH 045/185] Apply flowln's suggestion Signed-off-by: TheKodeToad --- launcher/net/Download.cpp | 8 ++++---- launcher/net/Upload.cpp | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/launcher/net/Download.cpp b/launcher/net/Download.cpp index f199c0b2..e8a1d0b0 100644 --- a/launcher/net/Download.cpp +++ b/launcher/net/Download.cpp @@ -114,11 +114,11 @@ void Download::executeTask() } request.setHeader(QNetworkRequest::UserAgentHeader, APPLICATION->getUserAgent().toUtf8()); - // TODO remove duplication and use constant - if (APPLICATION->capabilities() & Application::SupportsFlame - && request.url().host() == "api.curseforge.com") { + // TODO remove duplication + if (APPLICATION->capabilities() & Application::SupportsFlame && request.url().host() == QUrl(BuildConfig.FLAME_BASE_URL).host()) { request.setRawHeader("x-api-key", APPLICATION->getFlameAPIKey().toUtf8()); - } else if (request.url().host() == "api.modrinth.com") { + } else if (request.url().host() == QUrl(BuildConfig.MODRINTH_PROD_URL).host() || + request.url().host() == QUrl(BuildConfig.MODRINTH_STAGING_URL).host()) { QString token = APPLICATION->getModrinthAPIToken(); if (!token.isNull()) request.setRawHeader("Authorization", token.toUtf8()); diff --git a/launcher/net/Upload.cpp b/launcher/net/Upload.cpp index b90c71ba..ccf43c2d 100644 --- a/launcher/net/Upload.cpp +++ b/launcher/net/Upload.cpp @@ -217,11 +217,11 @@ namespace Net { } request.setHeader(QNetworkRequest::UserAgentHeader, APPLICATION->getUserAgent().toUtf8()); - // TODO remove duplication and use constant - if (APPLICATION->capabilities() & Application::SupportsFlame - && request.url().host() == "api.curseforge.com") { + // TODO remove duplication + if (APPLICATION->capabilities() & Application::SupportsFlame && request.url().host() == QUrl(BuildConfig.FLAME_BASE_URL).host()) { request.setRawHeader("x-api-key", APPLICATION->getFlameAPIKey().toUtf8()); - } else if (request.url().host() == "api.modrinth.com") { + } else if (request.url().host() == QUrl(BuildConfig.MODRINTH_PROD_URL).host() || + request.url().host() == QUrl(BuildConfig.MODRINTH_STAGING_URL).host()) { QString token = APPLICATION->getModrinthAPIToken(); if (!token.isNull()) request.setRawHeader("Authorization", token.toUtf8()); From 088be050b245dc60e799e677369cb4d9d941c0fe Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Tue, 7 Mar 2023 20:46:27 +0000 Subject: [PATCH 046/185] Apply flowln's suggestion :) Co-authored-by: flow Signed-off-by: TheKodeToad --- launcher/ui/pages/global/APIPage.ui | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/ui/pages/global/APIPage.ui b/launcher/ui/pages/global/APIPage.ui index 8c515f3a..91f867e0 100644 --- a/launcher/ui/pages/global/APIPage.ui +++ b/launcher/ui/pages/global/APIPage.ui @@ -207,7 +207,7 @@ - <html><head/><body><p>Note: you only need to set this to access private data. Read the <a href="https://docs.modrinth.com/api-spec/#section/Authentication"><span style=" text-decoration: underline; color:#0000ff;">documentation</span></a> for more information.</p></body></html> + <html><head/><body><p>Note: you only need to set this to access private data. Read the <a href="https://docs.modrinth.com/api-spec/#section/Authentication">documentation</a> for more information.</p></body></html> From 59cf8f678f6e3448dae1af99a59eb4d0157408c9 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Tue, 7 Mar 2023 20:50:19 +0000 Subject: [PATCH 047/185] (hopefully) Fix duplicates names Signed-off-by: TheKodeToad --- launcher/ui/pages/global/APIPage.ui | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/launcher/ui/pages/global/APIPage.ui b/launcher/ui/pages/global/APIPage.ui index 91f867e0..d866d7f9 100644 --- a/launcher/ui/pages/global/APIPage.ui +++ b/launcher/ui/pages/global/APIPage.ui @@ -250,14 +250,14 @@ - + Note: you probably don't need to set this if CurseForge already works. - + Enter a custom API Key for CurseForge here. From 59b15c5f08e00e2da0f77b9544881e93158320ed Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Tue, 7 Mar 2023 21:03:09 +0000 Subject: [PATCH 048/185] Fix another duplicate name :facepalm: Signed-off-by: TheKodeToad --- launcher/ui/pages/global/APIPage.ui | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/ui/pages/global/APIPage.ui b/launcher/ui/pages/global/APIPage.ui index d866d7f9..40b89d91 100644 --- a/launcher/ui/pages/global/APIPage.ui +++ b/launcher/ui/pages/global/APIPage.ui @@ -248,7 +248,7 @@ &CurseForge Core API - + From a8ffdeca2b1592bc13f11ab4203b94bf6d8ae1d2 Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Wed, 8 Mar 2023 11:03:50 +0100 Subject: [PATCH 049/185] chore!: switch to Qt 6 by default Signed-off-by: Sefa Eyeoglu --- CMakeLists.txt | 2 +- flatpak/org.prismlauncher.PrismLauncher.yml | 1 + nix/default.nix | 2 +- snap/snapcraft.yaml | 1 + 4 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 40936e44..05c69c89 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -174,7 +174,7 @@ set(Launcher_SUBREDDIT_URL "https://www.reddit.com/r/PrismLauncher/" CACHE STRIN # Builds set(Launcher_FORCE_BUNDLED_LIBS OFF CACHE BOOL "Prevent using system libraries, if they are available as submodules") -set(Launcher_QT_VERSION_MAJOR "5" CACHE STRING "Major Qt version to build against") +set(Launcher_QT_VERSION_MAJOR "6" CACHE STRING "Major Qt version to build against") # API Keys # NOTE: These API keys are here for convenience. If you rebrand this software or intend to break the terms of service diff --git a/flatpak/org.prismlauncher.PrismLauncher.yml b/flatpak/org.prismlauncher.PrismLauncher.yml index 071772c6..0524946f 100644 --- a/flatpak/org.prismlauncher.PrismLauncher.yml +++ b/flatpak/org.prismlauncher.PrismLauncher.yml @@ -32,6 +32,7 @@ modules: config-opts: - -DLauncher_BUILD_PLATFORM=flatpak - -DCMAKE_BUILD_TYPE=Debug + - -DLauncher_QT_VERSION_MAJOR=5 build-options: env: JAVA_HOME: /usr/lib/sdk/openjdk17/jvm/openjdk-17 diff --git a/nix/default.nix b/nix/default.nix index f6ab1332..99bb2231 100644 --- a/nix/default.nix +++ b/nix/default.nix @@ -46,7 +46,7 @@ stdenv.mkDerivation rec { ] ++ lib.optional (lib.versionAtLeast qtbase.version "6") qtwayland; cmakeFlags = lib.optionals (msaClientID != "") [ "-DLauncher_MSA_CLIENT_ID=${msaClientID}" ] - ++ lib.optionals (lib.versionAtLeast qtbase.version "6") [ "-DLauncher_QT_VERSION_MAJOR=6" ]; + ++ lib.optionals (lib.versionOlder qtbase.version "6") [ "-DLauncher_QT_VERSION_MAJOR=5" ]; dontWrapQtApps = true; postUnpack = '' diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index 8e647eea..d4eb646c 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -39,6 +39,7 @@ parts: - "-DCMAKE_BUILD_TYPE=RelWithDebInfo" - "-DENABLE_LTO=ON" - "-DLauncher_BUILD_PLATFORM=snap" + - "-DLauncher_QT_VERSION_MAJOR=5" apps: prismlauncher: From c638fd9056f7a9b86e899d32a8e85eae23e321e4 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 9 Mar 2023 14:41:24 +0000 Subject: [PATCH 050/185] chore(deps): update actions/cache action to v3.3.0 --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 41eb5eb7..8a71d1c9 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -158,7 +158,7 @@ jobs: - name: Retrieve ccache cache (Windows MinGW-w64) if: runner.os == 'Windows' && matrix.msystem != '' && inputs.build_type == 'Debug' - uses: actions/cache@v3.2.6 + uses: actions/cache@v3.3.0 with: path: '${{ github.workspace }}\.ccache' key: ${{ matrix.os }}-mingw-w64-ccache-${{ github.run_id }} From 5ac3e5c95890c60765fa4ee4e40e7a2ac2dc4768 Mon Sep 17 00:00:00 2001 From: Kenneth Chew Date: Sun, 12 Mar 2023 18:31:38 -0400 Subject: [PATCH 051/185] Update `tomlplusplus` library Fix build on MinGW g++ 10 Signed-off-by: Kenneth Chew --- libraries/tomlplusplus | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/tomlplusplus b/libraries/tomlplusplus index 0a90913a..7eb2ffcc 160000 --- a/libraries/tomlplusplus +++ b/libraries/tomlplusplus @@ -1 +1 @@ -Subproject commit 0a90913abf9390b9e08ab6d3b40ac11634553f38 +Subproject commit 7eb2ffcc09f8e9890dc0b77ff8ab00fc53b1f2b8 From 430369feb8712b314b0c31deef43435fe94a4f45 Mon Sep 17 00:00:00 2001 From: Kenneth Chew Date: Sun, 12 Mar 2023 18:32:42 -0400 Subject: [PATCH 052/185] fix: explicit conversion to C string to fix MinGW g++ 10 build Signed-off-by: Kenneth Chew --- launcher/modplatform/helpers/HashUtils.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/modplatform/helpers/HashUtils.cpp b/launcher/modplatform/helpers/HashUtils.cpp index 2177ddad..81c94e1b 100644 --- a/launcher/modplatform/helpers/HashUtils.cpp +++ b/launcher/modplatform/helpers/HashUtils.cpp @@ -79,7 +79,7 @@ void FlameHasher::executeTask() // CF-specific auto should_filter_out = [](char c) { return (c == 9 || c == 10 || c == 13 || c == 32); }; - std::ifstream file_stream(StringUtils::toStdString(m_path), std::ifstream::binary); + std::ifstream file_stream(StringUtils::toStdString(m_path).c_str(), std::ifstream::binary); // TODO: This is very heavy work, but apparently QtConcurrent can't use move semantics, so we can't boop this to another thread. // How do we make this non-blocking then? m_hash = QString::number(MurmurHash2(std::move(file_stream), 4 * MiB, should_filter_out)); From 20525bec295042bb93a6aa256a3f4394eeda3613 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 13 Mar 2023 10:50:27 +0000 Subject: [PATCH 053/185] chore(deps): update actions/cache action to v3.3.1 --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 8a71d1c9..1c27a8c3 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -158,7 +158,7 @@ jobs: - name: Retrieve ccache cache (Windows MinGW-w64) if: runner.os == 'Windows' && matrix.msystem != '' && inputs.build_type == 'Debug' - uses: actions/cache@v3.3.0 + uses: actions/cache@v3.3.1 with: path: '${{ github.workspace }}\.ccache' key: ${{ matrix.os }}-mingw-w64-ccache-${{ github.run_id }} From 4b36d1e3ead7af49fa176419249b65616853413c Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Mon, 13 Mar 2023 15:33:23 +0100 Subject: [PATCH 054/185] chore: drop Snap packaging We do not have a lot of expertise dealing with Snap and as it is currently breaking our CI, it might be good to drop support for it. This does not mean that it won't come back in the future, but as it stands, it was effectively unmaintained and was only used for nightly builds anyway. Signed-off-by: Sefa Eyeoglu --- .github/workflows/build.yml | 25 ----------------- snap/snapcraft.yaml | 56 ------------------------------------- 2 files changed, 81 deletions(-) delete mode 100644 snap/snapcraft.yaml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 8a71d1c9..ac522e2c 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -550,31 +550,6 @@ jobs: run: | ccache -s - snap: - runs-on: ubuntu-20.04 - steps: - - name: Checkout - if: inputs.build_type == 'Debug' - uses: actions/checkout@v3 - with: - submodules: 'true' - - name: Set short version - shell: bash - if: inputs.build_type == 'Debug' - run: | - ver_short=`git rev-parse --short HEAD` - echo "VERSION=$ver_short" >> $GITHUB_ENV - - name: Package Snap (Linux) - id: snapcraft - if: inputs.build_type == 'Debug' - uses: snapcore/action-build@v1 - - name: Upload Snap (Linux) - if: inputs.build_type == 'Debug' - uses: actions/upload-artifact@v3 - with: - name: prismlauncher_${{ env.VERSION }}_amd64.snap - path: ${{ steps.snapcraft.outputs.snap }} - flatpak: runs-on: ubuntu-latest container: diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml deleted file mode 100644 index d4eb646c..00000000 --- a/snap/snapcraft.yaml +++ /dev/null @@ -1,56 +0,0 @@ -name: prismlauncher -license: GPL-3.0-only -base: core20 -website: https://prismlauncher.org/ -source-code: https://github.com/PrismLauncher/PrismLauncher -issues: https://github.com/PrismLauncher/PrismLauncher/issues -donation: https://opencollective.com/prismlauncher -contact: https://discord.gg/prismlauncher -summary: A custom Minecraft launcher with modpack support -adopt-info: prismlauncher - -grade: devel -confinement: strict - -architectures: - - build-on: amd64 - - build-on: arm64 - -parts: - prismlauncher: - parse-info: - - usr/share/metainfo/org.prismlauncher.PrismLauncher.metainfo.xml - plugin: cmake - build-packages: - - default-jdk-headless - stage-packages: - - openjdk-17-jre - - openjdk-8-jre - source: . - override-pull: | - snapcraftctl pull - # Fix the icon reference in the desktop file - sed -i.bak -e 's|Icon=org.prismlauncher.PrismLauncher|Icon=/usr/share/icons/hicolor/scalable/apps/org.prismlauncher.PrismLauncher.svg|g' program_info/org.prismlauncher.PrismLauncher.desktop.in - # Remove the build directory so that local development doesn't interfere with Snap compilation - rm -rf build - cmake-generator: Ninja - cmake-parameters: - - "-DCMAKE_INSTALL_PREFIX=/usr" - - "-DCMAKE_BUILD_TYPE=RelWithDebInfo" - - "-DENABLE_LTO=ON" - - "-DLauncher_BUILD_PLATFORM=snap" - - "-DLauncher_QT_VERSION_MAJOR=5" - -apps: - prismlauncher: - common-id: org.prismlauncher.PrismLauncher - desktop: usr/share/applications/org.prismlauncher.PrismLauncher.desktop - command: usr/bin/prismlauncher - extensions: - - kde-neon - plugs: - - home - - opengl - - network - - network-bind - - audio-playback From c17f6e37e5e32138f778cedf66c80e20f1eacd2e Mon Sep 17 00:00:00 2001 From: Janrupf Date: Mon, 13 Mar 2023 16:37:45 +0100 Subject: [PATCH 055/185] 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 | 71 ++++++++++++++--------- 5 files changed, 63 insertions(+), 30 deletions(-) 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,13 +67,6 @@ Folders - - - - ... - - - @@ -81,29 +74,12 @@ - - - - ... - - + + - - - - &Icons: - - - iconsDirTextBox - - - - - - @@ -117,6 +93,20 @@ + + + + ... + + + + + + + ... + + + @@ -127,6 +117,33 @@ + + + + &Icons: + + + iconsDirTextBox + + + + + + + Downloads: + + + + + + + + + + ... + + + From 9dff1bac838b5a827b1e323bdc983b0f7d1e61bd Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Tue, 14 Mar 2023 10:48:19 +0100 Subject: [PATCH 056/185] chore(nix): format code using alejandra Signed-off-by: Sefa Eyeoglu --- flake.nix | 59 ++++++++++++-------- nix/default.nix | 129 +++++++++++++++++++++---------------------- nix/flake-compat.nix | 2 +- 3 files changed, 100 insertions(+), 90 deletions(-) diff --git a/flake.nix b/flake.nix index 5615a758..f173511c 100644 --- a/flake.nix +++ b/flake.nix @@ -3,35 +3,46 @@ inputs = { nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable"; - flake-compat = { url = "github:edolstra/flake-compat"; flake = false; }; - libnbtplusplus = { url = "github:PrismLauncher/libnbtplusplus"; flake = false; }; + flake-compat = { + url = "github:edolstra/flake-compat"; + flake = false; + }; + libnbtplusplus = { + url = "github:PrismLauncher/libnbtplusplus"; + flake = false; + }; }; - outputs = { self, nixpkgs, libnbtplusplus, ... }: - let - # User-friendly version number. - version = builtins.substring 0 8 self.lastModifiedDate; + outputs = { + self, + nixpkgs, + libnbtplusplus, + ... + }: let + # User-friendly version number. + version = builtins.substring 0 8 self.lastModifiedDate; - # Supported systems (qtbase is currently broken for "aarch64-darwin") - supportedSystems = [ "x86_64-linux" "x86_64-darwin" "aarch64-linux" ]; + # Supported systems (qtbase is currently broken for "aarch64-darwin") + supportedSystems = ["x86_64-linux" "x86_64-darwin" "aarch64-linux"]; - # Helper function to generate an attrset '{ x86_64-linux = f "x86_64-linux"; ... }'. - forAllSystems = nixpkgs.lib.genAttrs supportedSystems; + # Helper function to generate an attrset '{ x86_64-linux = f "x86_64-linux"; ... }'. + forAllSystems = nixpkgs.lib.genAttrs supportedSystems; - # Nixpkgs instantiated for supported systems. - pkgs = forAllSystems (system: nixpkgs.legacyPackages.${system}); + # Nixpkgs instantiated for supported systems. + pkgs = forAllSystems (system: nixpkgs.legacyPackages.${system}); - packagesFn = pkgs: rec { - prismlauncher-qt5 = pkgs.libsForQt5.callPackage ./nix { inherit version self libnbtplusplus; }; - prismlauncher = pkgs.qt6Packages.callPackage ./nix { inherit version self libnbtplusplus; }; - }; - in - { - packages = forAllSystems (system: - let packages = packagesFn pkgs.${system}; in - packages // { default = packages.prismlauncher; } - ); - - overlay = final: packagesFn; + packagesFn = pkgs: rec { + prismlauncher-qt5 = pkgs.libsForQt5.callPackage ./nix {inherit version self libnbtplusplus;}; + prismlauncher = pkgs.qt6Packages.callPackage ./nix {inherit version self libnbtplusplus;}; }; + in { + packages = forAllSystems ( + system: let + packages = packagesFn pkgs.${system}; + in + packages // {default = packages.prismlauncher;} + ); + + overlay = final: packagesFn; + }; } diff --git a/nix/default.nix b/nix/default.nix index 99bb2231..6d4f3f24 100644 --- a/nix/default.nix +++ b/nix/default.nix @@ -1,52 +1,54 @@ -{ lib -, stdenv -, cmake -, jdk8 -, jdk17 -, zlib -, file -, wrapQtAppsHook -, xorg -, libpulseaudio -, qtbase -, qtsvg -, qtwayland -, libGL -, quazip -, glfw -, openal -, extra-cmake-modules -, tomlplusplus -, ghc_filesystem -, cmark -, msaClientID ? "" -, jdks ? [ jdk17 jdk8 ] - +{ + lib, + stdenv, + cmake, + jdk8, + jdk17, + zlib, + file, + wrapQtAppsHook, + xorg, + libpulseaudio, + qtbase, + qtsvg, + qtwayland, + libGL, + quazip, + glfw, + openal, + extra-cmake-modules, + tomlplusplus, + ghc_filesystem, + cmark, + msaClientID ? "", + jdks ? [jdk17 jdk8], # flake -, self -, version -, libnbtplusplus + self, + version, + libnbtplusplus, }: - stdenv.mkDerivation rec { pname = "prismlauncher"; inherit version; src = lib.cleanSource self; - nativeBuildInputs = [ extra-cmake-modules cmake file jdk17 wrapQtAppsHook ]; - buildInputs = [ - qtbase - qtsvg - zlib - quazip - ghc_filesystem - tomlplusplus - cmark - ] ++ lib.optional (lib.versionAtLeast qtbase.version "6") qtwayland; + nativeBuildInputs = [extra-cmake-modules cmake file jdk17 wrapQtAppsHook]; + buildInputs = + [ + qtbase + qtsvg + zlib + quazip + ghc_filesystem + tomlplusplus + cmark + ] + ++ lib.optional (lib.versionAtLeast qtbase.version "6") qtwayland; - cmakeFlags = lib.optionals (msaClientID != "") [ "-DLauncher_MSA_CLIENT_ID=${msaClientID}" ] - ++ lib.optionals (lib.versionOlder qtbase.version "6") [ "-DLauncher_QT_VERSION_MAJOR=5" ]; + cmakeFlags = + lib.optionals (msaClientID != "") ["-DLauncher_MSA_CLIENT_ID=${msaClientID}"] + ++ lib.optionals (lib.versionOlder qtbase.version "6") ["-DLauncher_QT_VERSION_MAJOR=5"]; dontWrapQtApps = true; postUnpack = '' @@ -57,30 +59,27 @@ stdenv.mkDerivation rec { chown -R $USER: source/libraries/libnbtplusplus ''; - postInstall = - let - libpath = with xorg; - lib.makeLibraryPath [ - libX11 - libXext - libXcursor - libXrandr - libXxf86vm - libpulseaudio - libGL - glfw - openal - stdenv.cc.cc.lib - ]; - in - '' - # xorg.xrandr needed for LWJGL [2.9.2, 3) https://github.com/LWJGL/lwjgl/issues/128 - wrapQtApp $out/bin/prismlauncher \ - --set LD_LIBRARY_PATH /run/opengl-driver/lib:${libpath} \ - --prefix PRISMLAUNCHER_JAVA_PATHS : ${lib.makeSearchPath "bin/java" jdks} \ - --prefix PATH : ${lib.makeBinPath [xorg.xrandr]} - ''; - + postInstall = let + libpath = with xorg; + lib.makeLibraryPath [ + libX11 + libXext + libXcursor + libXrandr + libXxf86vm + libpulseaudio + libGL + glfw + openal + stdenv.cc.cc.lib + ]; + in '' + # xorg.xrandr needed for LWJGL [2.9.2, 3) https://github.com/LWJGL/lwjgl/issues/128 + wrapQtApp $out/bin/prismlauncher \ + --set LD_LIBRARY_PATH /run/opengl-driver/lib:${libpath} \ + --prefix PRISMLAUNCHER_JAVA_PATHS : ${lib.makeSearchPath "bin/java" jdks} \ + --prefix PATH : ${lib.makeBinPath [xorg.xrandr]} + ''; meta = with lib; { homepage = "https://prismlauncher.org/"; @@ -93,6 +92,6 @@ stdenv.mkDerivation rec { platforms = platforms.linux; changelog = "https://github.com/PrismLauncher/PrismLauncher/releases/tag/${version}"; license = licenses.gpl3Only; - maintainers = with maintainers; [ minion3665 Scrumplex ]; + maintainers = with maintainers; [minion3665 Scrumplex]; }; } diff --git a/nix/flake-compat.nix b/nix/flake-compat.nix index 8b6cb99c..7162a6cf 100644 --- a/nix/flake-compat.nix +++ b/nix/flake-compat.nix @@ -6,4 +6,4 @@ let sha256 = narHash; }; in -import flake-compat { src = ../.; } + import flake-compat {src = ../.;} From 950f921c09930076a19621bea9730cc866c3c482 Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Tue, 14 Mar 2023 10:50:07 +0100 Subject: [PATCH 057/185] refactor(nix): use flake-utils Signed-off-by: Sefa Eyeoglu --- flake.lock | 16 ++++++++++++++++ flake.nix | 39 ++++++++++++++++++++++----------------- 2 files changed, 38 insertions(+), 17 deletions(-) diff --git a/flake.lock b/flake.lock index 051e1664..a0f503cd 100644 --- a/flake.lock +++ b/flake.lock @@ -16,6 +16,21 @@ "type": "github" } }, + "flake-utils": { + "locked": { + "lastModified": 1676283394, + "narHash": "sha256-XX2f9c3iySLCw54rJ/CZs+ZK6IQy7GXNY4nSOyu2QG4=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "3db36a8b464d0c4532ba1c7dda728f4576d6d073", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, "libnbtplusplus": { "flake": false, "locked": { @@ -51,6 +66,7 @@ "root": { "inputs": { "flake-compat": "flake-compat", + "flake-utils": "flake-utils", "libnbtplusplus": "libnbtplusplus", "nixpkgs": "nixpkgs" } diff --git a/flake.nix b/flake.nix index f173511c..f476fdf9 100644 --- a/flake.nix +++ b/flake.nix @@ -3,6 +3,7 @@ inputs = { nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable"; + flake-utils.url = "github:numtide/flake-utils"; flake-compat = { url = "github:edolstra/flake-compat"; flake = false; @@ -16,6 +17,7 @@ outputs = { self, nixpkgs, + flake-utils, libnbtplusplus, ... }: let @@ -23,26 +25,29 @@ version = builtins.substring 0 8 self.lastModifiedDate; # Supported systems (qtbase is currently broken for "aarch64-darwin") - supportedSystems = ["x86_64-linux" "x86_64-darwin" "aarch64-linux"]; - - # Helper function to generate an attrset '{ x86_64-linux = f "x86_64-linux"; ... }'. - forAllSystems = nixpkgs.lib.genAttrs supportedSystems; - - # Nixpkgs instantiated for supported systems. - pkgs = forAllSystems (system: nixpkgs.legacyPackages.${system}); + supportedSystems = with flake-utils.lib.system; [ + x86_64-linux + x86_64-darwin + aarch64-linux + ]; packagesFn = pkgs: rec { - prismlauncher-qt5 = pkgs.libsForQt5.callPackage ./nix {inherit version self libnbtplusplus;}; - prismlauncher = pkgs.qt6Packages.callPackage ./nix {inherit version self libnbtplusplus;}; + prismlauncher-qt5 = pkgs.libsForQt5.callPackage ./nix { + inherit version self libnbtplusplus; + }; + prismlauncher = pkgs.qt6Packages.callPackage ./nix { + inherit version self libnbtplusplus; + }; }; - in { - packages = forAllSystems ( - system: let - packages = packagesFn pkgs.${system}; + in + flake-utils.lib.eachSystem supportedSystems (system: let + pkgs = nixpkgs.legacyPackages.${system}; + in { + packages = let + packages = packagesFn pkgs; in - packages // {default = packages.prismlauncher;} - ); + packages // {default = packages.prismlauncher;}; - overlay = final: packagesFn; - }; + overlay = final: packagesFn; + }); } From b324778be7d5713947d70dbf1f095f541a20345b Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Tue, 14 Mar 2023 10:51:47 +0100 Subject: [PATCH 058/185] feat(nix): add devShell Signed-off-by: Sefa Eyeoglu --- .envrc | 1 + .gitignore | 3 +++ flake.nix | 5 +++++ 3 files changed, 9 insertions(+) create mode 100644 .envrc diff --git a/.envrc b/.envrc new file mode 100644 index 00000000..3550a30f --- /dev/null +++ b/.envrc @@ -0,0 +1 @@ +use flake diff --git a/.gitignore b/.gitignore index 010bc323..778024e3 100644 --- a/.gitignore +++ b/.gitignore @@ -22,6 +22,9 @@ Debug build /build-* +# direnv / Nix +.direnv/ + # Install dirs install /install-* diff --git a/flake.nix b/flake.nix index f476fdf9..3294c799 100644 --- a/flake.nix +++ b/flake.nix @@ -48,6 +48,11 @@ in packages // {default = packages.prismlauncher;}; + devShells.default = pkgs.mkShell { + inputsFrom = [self.packages.${system}.default]; + buildInputs = with pkgs; [ccache ninja]; + }; + overlay = final: packagesFn; }); } From 95a0bd61a9788751e78a5cad9f8ee810f174b81e Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Tue, 14 Mar 2023 10:53:51 +0100 Subject: [PATCH 059/185] feat(nix): add pre-commit-hooks.nix Signed-off-by: Sefa Eyeoglu --- .gitignore | 1 + flake.lock | 82 +++++++++++++++++++++++++++++++++++++++++++++++++++++- flake.nix | 29 +++++++++++++++++++ 3 files changed, 111 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 778024e3..635d1c53 100644 --- a/.gitignore +++ b/.gitignore @@ -24,6 +24,7 @@ build # direnv / Nix .direnv/ +.pre-commit-config.yaml # Install dirs install diff --git a/flake.lock b/flake.lock index a0f503cd..5820e098 100644 --- a/flake.lock +++ b/flake.lock @@ -16,6 +16,22 @@ "type": "github" } }, + "flake-compat_2": { + "flake": false, + "locked": { + "lastModified": 1673956053, + "narHash": "sha256-4gtG9iQuiKITOjNQQeQIpoIB6b16fm+504Ch3sNKLd8=", + "owner": "edolstra", + "repo": "flake-compat", + "rev": "35bb57c0c8d8b62bbfd284272c928ceb64ddbde9", + "type": "github" + }, + "original": { + "owner": "edolstra", + "repo": "flake-compat", + "type": "github" + } + }, "flake-utils": { "locked": { "lastModified": 1676283394, @@ -31,6 +47,27 @@ "type": "github" } }, + "gitignore": { + "inputs": { + "nixpkgs": [ + "pre-commit-hooks", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1660459072, + "narHash": "sha256-8DFJjXG8zqoONA1vXtgeKXy68KdJL5UaXR8NtVMUbx8=", + "owner": "hercules-ci", + "repo": "gitignore.nix", + "rev": "a20de23b925fd8264fd7fad6454652e142fd7f73", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "gitignore.nix", + "type": "github" + } + }, "libnbtplusplus": { "flake": false, "locked": { @@ -63,12 +100,55 @@ "type": "github" } }, + "nixpkgs-stable": { + "locked": { + "lastModified": 1673800717, + "narHash": "sha256-SFHraUqLSu5cC6IxTprex/nTsI81ZQAtDvlBvGDWfnA=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "2f9fd351ec37f5d479556cd48be4ca340da59b8f", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-22.11", + "repo": "nixpkgs", + "type": "github" + } + }, + "pre-commit-hooks": { + "inputs": { + "flake-compat": "flake-compat_2", + "flake-utils": [ + "flake-utils" + ], + "gitignore": "gitignore", + "nixpkgs": [ + "nixpkgs" + ], + "nixpkgs-stable": "nixpkgs-stable" + }, + "locked": { + "lastModified": 1678376203, + "narHash": "sha256-3tyYGyC8h7fBwncLZy5nCUjTJPrHbmNwp47LlNLOHSM=", + "owner": "cachix", + "repo": "pre-commit-hooks.nix", + "rev": "1a20b9708962096ec2481eeb2ddca29ed747770a", + "type": "github" + }, + "original": { + "owner": "cachix", + "repo": "pre-commit-hooks.nix", + "type": "github" + } + }, "root": { "inputs": { "flake-compat": "flake-compat", "flake-utils": "flake-utils", "libnbtplusplus": "libnbtplusplus", - "nixpkgs": "nixpkgs" + "nixpkgs": "nixpkgs", + "pre-commit-hooks": "pre-commit-hooks" } } }, diff --git a/flake.nix b/flake.nix index 3294c799..6173670a 100644 --- a/flake.nix +++ b/flake.nix @@ -4,6 +4,11 @@ inputs = { nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable"; flake-utils.url = "github:numtide/flake-utils"; + pre-commit-hooks = { + url = "github:cachix/pre-commit-hooks.nix"; + inputs.nixpkgs.follows = "nixpkgs"; + inputs.flake-utils.follows = "flake-utils"; + }; flake-compat = { url = "github:edolstra/flake-compat"; flake = false; @@ -18,6 +23,7 @@ self, nixpkgs, flake-utils, + pre-commit-hooks, libnbtplusplus, ... }: let @@ -43,12 +49,35 @@ flake-utils.lib.eachSystem supportedSystems (system: let pkgs = nixpkgs.legacyPackages.${system}; in { + checks = { + pre-commit-check = pre-commit-hooks.lib.${system}.run { + src = ./.; + hooks = { + markdownlint.enable = true; + alejandra.enable = true; + + clang-format = { + enable = + false; # As most of the codebase is **not** formatted, we don't want clang-format yet + types_or = ["c" "c++"]; + }; + }; + }; + }; + packages = let packages = packagesFn pkgs; in packages // {default = packages.prismlauncher;}; devShells.default = pkgs.mkShell { + inherit (self.checks.${system}.pre-commit-check) shellHook; + packages = with pkgs; [ + nodePackages.markdownlint-cli + alejandra + clang-tools + ]; + inputsFrom = [self.packages.${system}.default]; buildInputs = with pkgs; [ccache ninja]; }; From a1130dace0315f3a1894af7d066a333aef6b9205 Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Tue, 14 Mar 2023 10:54:50 +0100 Subject: [PATCH 060/185] chore: format markdown files Signed-off-by: Sefa Eyeoglu --- BUILD.md | 52 +------------------------ libraries/katabasis/acknowledgements.md | 12 +++--- 2 files changed, 7 insertions(+), 57 deletions(-) diff --git a/BUILD.md b/BUILD.md index 2443ac56..a139039d 100644 --- a/BUILD.md +++ b/BUILD.md @@ -1,53 +1,3 @@ # Build Instructions -Full build instructions will be available on [the website](https://prismlauncher.org/wiki/development/build-instructions/). - -If you would like to contribute or fix an issue with the Build instructions you will be able to do so [here](https://github.com/PrismLauncher/website/blob/master/src/wiki/development/build-instructions.md). - -## Getting the source - -Clone the source code using git, and grab all the submodules. This is generic for all platforms you want to build on. -``` -git clone --recursive https://github.com/PrismLauncher/PrismLauncher -cd PrismLauncher -``` - -## Linux - -This guide will mostly mention dependant packages by their Debian naming and commands are done by a user in the sudoers file. -### Dependencies - -- A C++ compiler capable of building C++17 code (can be found in the package `build-essential`). -- Qt Development tools 5.12 or newer (on Debian 11 or Debian-based distributions, `qtbase5-dev qtchooser qt5-qmake qtbase5-dev-tools libqt5core5a libqt5network5 libqt5gui5`). -- `cmake` 3.15 or newer. -- `extra-cmake-modules`. -- zlib (`zlib1g-dev` on Debian 11 or Debian-based distributions). -- Java Development Kit (Java JDK) (`openjdk-17-jdk` on Debian 11 or Debian-based distributions). -- Mesa GL headers (`libgl1-mesa-dev` on Debian 11 or Debian-based distributions). -- (Optional) `scdoc` to generate man pages. - -In conclusion, to check if all you need is installed (including optional): - -``` -sudo apt install build-essential qtbase5-dev qtchooser qt5-qmake qtbase5-dev-tools libqt5core5a libqt5network5 libqt5gui5 cmake extra-cmake-modules zlib1g-dev openjdk-17-jdk libgl1-mesa-dev scdoc -``` - -### Compiling -#### Building and installing on the system -This is usually the suggested way to build the client. - -``` -cmake -S . -B build -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX="/usr" -DENABLE_LTO=ON -cmake --build build -j$(nproc) -sudo cmake --install build -``` - -#### Building a portable binary - -``` -cmake -S . -B build -DCMAKE_INSTALL_PREFIX=install -cmake --build build -j$(nproc) -cmake --install build -cmake --install build --component portable -``` - +Full build instructions are available on [the website](https://prismlauncher.org/wiki/development/build-instructions/). diff --git a/libraries/katabasis/acknowledgements.md b/libraries/katabasis/acknowledgements.md index ccc7c263..a6989d15 100644 --- a/libraries/katabasis/acknowledgements.md +++ b/libraries/katabasis/acknowledgements.md @@ -8,10 +8,10 @@ > Redistribution and use in source and binary forms, with or without > modification, are permitted provided that the following conditions are met: > -> * Redistributions of source code must retain the above copyright notice, this +> * Redistributions of source code must retain the above copyright notice, this > list of conditions and the following disclaimer. > -> * Redistributions in binary form must reproduce the above copyright notice, +> * Redistributions in binary form must reproduce the above copyright notice, > this list of conditions and the following disclaimer in the documentation > and/or other materials provided with the distribution. > @@ -36,12 +36,12 @@ Cryptographic methods for Qt. > Redistribution and use in source and binary forms, with or without > modification, are permitted provided that the following conditions are met: > -> * Redistributions of source code must retain the above copyright +> * Redistributions of source code must retain the above copyright > notice, this list of conditions and the following disclaimer. -> * Redistributions in binary form must reproduce the above copyright +> * Redistributions in binary form must reproduce the above copyright > notice, this list of conditions and the following disclaimer in the > documentation and/or other materials provided with the distribution. -> * Neither the name of the Rathenau Instituut, Andre Somers nor the +> * Neither the name of the Rathenau Instituut, Andre Somers nor the > names of its contributors may be used to endorse or promote products > derived from this software without specific prior written permission. > @@ -62,7 +62,7 @@ Configurable settings storage, Twitter XAuth specialization, new demos, cleanups > "Hi Akos, > -> I'm writing this mail to confirm that my contributions to the O2 library, available here https://github.com/pipacs/o2, can be freely distributed according to the project's license (as shown in the LICENSE file). +> I'm writing this mail to confirm that my contributions to the O2 library, available here , can be freely distributed according to the project's license (as shown in the LICENSE file). > > Regards, > -mandeep" From cfca82ceb3aafd1e24ff4eeb943d97e0c69e6f3d Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Tue, 14 Mar 2023 10:58:41 +0100 Subject: [PATCH 061/185] chore(nix)!: use overlays.default Signed-off-by: Sefa Eyeoglu --- flake.nix | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/flake.nix b/flake.nix index 6173670a..0f736308 100644 --- a/flake.nix +++ b/flake.nix @@ -37,7 +37,7 @@ aarch64-linux ]; - packagesFn = pkgs: rec { + packagesFn = pkgs: { prismlauncher-qt5 = pkgs.libsForQt5.callPackage ./nix { inherit version self libnbtplusplus; }; @@ -81,7 +81,8 @@ inputsFrom = [self.packages.${system}.default]; buildInputs = with pkgs; [ccache ninja]; }; - - overlay = final: packagesFn; - }); + }) + // { + overlays.default = final: _: (packagesFn final); + }; } From 5db4fabcad8def43a1185be125443b7d067f0fe9 Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Tue, 14 Mar 2023 10:59:43 +0100 Subject: [PATCH 062/185] chore(nix): add deadnix Signed-off-by: Sefa Eyeoglu --- flake.nix | 3 +++ 1 file changed, 3 insertions(+) diff --git a/flake.nix b/flake.nix index 0f736308..f656703c 100644 --- a/flake.nix +++ b/flake.nix @@ -54,7 +54,9 @@ src = ./.; hooks = { markdownlint.enable = true; + alejandra.enable = true; + deadnix.enable = true; clang-format = { enable = @@ -75,6 +77,7 @@ packages = with pkgs; [ nodePackages.markdownlint-cli alejandra + deadnix clang-tools ]; From 7707af08e3b6eca9cfd3678c10ef449f8523e640 Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Tue, 14 Mar 2023 11:19:23 +0100 Subject: [PATCH 063/185] chore(nix): update sources Signed-off-by: Sefa Eyeoglu --- flake.lock | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/flake.lock b/flake.lock index 5820e098..ad9196a9 100644 --- a/flake.lock +++ b/flake.lock @@ -3,11 +3,11 @@ "flake-compat": { "flake": false, "locked": { - "lastModified": 1668681692, - "narHash": "sha256-Ht91NGdewz8IQLtWZ9LCeNXMSXHUss+9COoqu6JLmXU=", + "lastModified": 1673956053, + "narHash": "sha256-4gtG9iQuiKITOjNQQeQIpoIB6b16fm+504Ch3sNKLd8=", "owner": "edolstra", "repo": "flake-compat", - "rev": "009399224d5e398d03b22badca40a37ac85412a1", + "rev": "35bb57c0c8d8b62bbfd284272c928ceb64ddbde9", "type": "github" }, "original": { @@ -86,11 +86,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1671417167, - "narHash": "sha256-JkHam6WQOwZN1t2C2sbp1TqMv3TVRjzrdoejqfefwrM=", + "lastModified": 1678693419, + "narHash": "sha256-bbSv5yqZAW6dz+3f3f3pOUZbxpPN+3OgCljgn7P+nnQ=", "owner": "nixos", "repo": "nixpkgs", - "rev": "bb31220cca6d044baa6dc2715b07497a2a7c4bc7", + "rev": "8e3fad82be64c06fbfb9fd43993aec9ef4623936", "type": "github" }, "original": { From af949f5cdd74e0f86ba2bcea510765f883ae2aee Mon Sep 17 00:00:00 2001 From: Nikhil B <59918974+heftymouse@users.noreply.github.com> Date: Tue, 14 Mar 2023 16:52:06 +0530 Subject: [PATCH 064/185] Add visual studio files to gitignore Signed-off-by: Nikhil B <59918974+heftymouse@users.noreply.github.com> --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 010bc323..c72f7a1d 100644 --- a/.gitignore +++ b/.gitignore @@ -11,10 +11,12 @@ html/ *.pro.user CMakeLists.txt.user CMakeLists.txt.user.* +CMakeSettings.json /.project /.settings /.idea /.vscode +/.vs cmake-build-*/ Debug From 05b6969ee507fce515d4f961814487239a946055 Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Tue, 14 Mar 2023 13:35:16 +0100 Subject: [PATCH 065/185] fix: add mnemonic to downloads directory setting Signed-off-by: Sefa Eyeoglu --- launcher/ui/pages/global/LauncherPage.ui | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/launcher/ui/pages/global/LauncherPage.ui b/launcher/ui/pages/global/LauncherPage.ui index 1fe55678..923b7f95 100644 --- a/launcher/ui/pages/global/LauncherPage.ui +++ b/launcher/ui/pages/global/LauncherPage.ui @@ -130,7 +130,10 @@ - Downloads: + &Downloads: + + + downloadsDirTextBox From d2f674e08fa1b2d55cc9a7340e66e6a9d09b1a9f Mon Sep 17 00:00:00 2001 From: DioEgizio <83089242+DioEgizio@users.noreply.github.com> Date: Sun, 19 Mar 2023 12:34:48 +0100 Subject: [PATCH 066/185] chore: update qt6 to 6.4.3 on windows and macos Signed-off-by: DioEgizio <83089242+DioEgizio@users.noreply.github.com> --- .github/workflows/build.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 9337f3c7..022a04f8 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -68,7 +68,7 @@ jobs: qt_ver: 6 qt_host: windows qt_arch: '' - qt_version: '6.4.2' + qt_version: '6.4.3' qt_modules: 'qt5compat qtimageformats' qt_tools: '' @@ -80,7 +80,7 @@ jobs: qt_ver: 6 qt_host: windows qt_arch: 'win64_msvc2019_arm64' - qt_version: '6.4.2' + qt_version: '6.4.3' qt_modules: 'qt5compat qtimageformats' qt_tools: '' @@ -90,7 +90,7 @@ jobs: qt_ver: 6 qt_host: mac qt_arch: '' - qt_version: '6.3.0' + qt_version: '6.4.3' qt_modules: 'qt5compat qtimageformats' qt_tools: '' 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: [PATCH 067/185] 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 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 @@ - - - + + + 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 + + + + + + + + + + - Disabling this will still keep the mod loader (ex: Fabric, Quilt, etc.) but erase the mods folder and their configs. + Use symbolic links instead of copying files. - - Copy mods + + Link files instead of copying them - - - - - - Copy the in-game options like FOV, max framerate, etc. + + false - - Copy game options - - - - - - - Copy saves - - - - - - - Copy shader packs - - - - - - - Copy servers - - - - - - + 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) From 485f156e57b0fb30e51d1014de745bc6f90b7e3e Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Tue, 7 Feb 2023 02:56:16 -0700 Subject: [PATCH 068/185] working outside windows Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- tests/FileSystem_test.cpp | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/tests/FileSystem_test.cpp b/tests/FileSystem_test.cpp index ce83aa49..84671889 100644 --- a/tests/FileSystem_test.cpp +++ b/tests/FileSystem_test.cpp @@ -278,12 +278,13 @@ slots: { qDebug() << entry; QFileInfo entry_lnk_info(target_dir.filePath(entry)); - QVERIFY(!entry_lnk_info.isSymbolicLink()); + if (!entry_lnk_info.isDir()) + QVERIFY(!entry_lnk_info.isSymLink()); } QFileInfo lnk_info(target_dir.path()); QVERIFY(lnk_info.exists()); - QVERIFY(lnk_info.isSymbolicLink()); + QVERIFY(lnk_info.isSymLink()); QVERIFY(target_dir.entryList().contains("pack.mcmeta")); QVERIFY(target_dir.entryList().contains("assets")); @@ -303,8 +304,9 @@ slots: { QString folder = QFINDTESTDATA("testdata/FileSystem/test_folder"); auto f = [&folder]() - { - QTemporaryDir tempDir; + { + // use working dir to prevent makeing a hard link to a tmpfs or across devices + QTemporaryDir tempDir("./tmp"); tempDir.setAutoRemove(true); qDebug() << "From:" << folder << "To:" << tempDir.path(); @@ -322,7 +324,7 @@ slots: { qDebug() << entry; QFileInfo entry_lnk_info(target_dir.filePath(entry)); - QVERIFY(!entry_lnk_info.isSymbolicLink()); + QVERIFY(!entry_lnk_info.isSymLink()); 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(); @@ -332,7 +334,7 @@ slots: QFileInfo lnk_info(target_dir.path()); QVERIFY(lnk_info.exists()); - QVERIFY(!lnk_info.isSymbolicLink()); + QVERIFY(!lnk_info.isSymLink()); QVERIFY(target_dir.entryList().contains("pack.mcmeta")); QVERIFY(target_dir.entryList().contains("assets")); @@ -377,12 +379,12 @@ slots: { qDebug() << entry; QFileInfo entry_lnk_info(target_dir.filePath(entry)); - QVERIFY(entry_lnk_info.isSymbolicLink()); + if (!entry_lnk_info.isDir()) + QVERIFY(entry_lnk_info.isSymLink()); } 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")); @@ -428,12 +430,12 @@ slots: { qDebug() << entry; QFileInfo entry_lnk_info(target_dir.filePath(entry)); - QVERIFY(entry_lnk_info.isSymbolicLink()); + if (!entry_lnk_info.isDir()) + QVERIFY(entry_lnk_info.isSymLink()); } 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")); @@ -478,12 +480,12 @@ slots: for (auto entry: target_dir.entryList(filter)) { qDebug() << entry; QFileInfo entry_lnk_info(target_dir.filePath(entry)); - QVERIFY(entry_lnk_info.isSymbolicLink()); + if (!entry_lnk_info.isDir()) + QVERIFY(entry_lnk_info.isSymLink()); } 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"); @@ -532,7 +534,7 @@ slots: QFileInfo lnk_info(target_dir.filePath("pack.mcmeta")); QVERIFY(lnk_info.exists()); - QVERIFY(lnk_info.isSymbolicLink()); + QVERIFY(lnk_info.isSymLink()); QVERIFY(target_dir.entryList(filter).contains("pack.mcmeta")); } From 2ceefea5f346985bcc3a61c1562e0d836f1a0a83 Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Tue, 7 Feb 2023 03:27:49 -0700 Subject: [PATCH 069/185] qt5 compatability Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- tests/FileSystem_test.cpp | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/tests/FileSystem_test.cpp b/tests/FileSystem_test.cpp index 84671889..395ca5c0 100644 --- a/tests/FileSystem_test.cpp +++ b/tests/FileSystem_test.cpp @@ -3,6 +3,26 @@ #include #include +#include + +// 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__ + +#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 + +#ifndef GHC_USE_STD_FS +#include +namespace fs = ghc::filesystem; +#endif #include @@ -328,7 +348,10 @@ slots: 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())); + QVERIFY(fs::equivalent( + fs::path(StringUtils::toStdString(entry_lnk_info.absoluteFilePath())), + fs::path(StringUtils::toStdString(entry_orig_info.absoluteFilePath())) + )); } } From 32409a361b797342d625bfc6d0726cc330ced760 Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Tue, 7 Feb 2023 03:31:46 -0700 Subject: [PATCH 070/185] fix CMakeLits.txt for non MSVC windows builds Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- launcher/CMakeLists.txt | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index 2216aa1b..18d4ce0b 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -559,7 +559,7 @@ set(ATLAUNCHER_SOURCES modplatform/atlauncher/ATLShareCode.h ) -set(LINKDAEMON_SOURCES +set(LINKEXE_SOURCES filelink/FileLink.h filelink/FileLink.cpp ) @@ -1113,7 +1113,7 @@ install(TARGETS ${Launcher_Name} ) if(WIN32) - add_library(filelink_logic STATIC ${LINKDAEMON_SOURCES}) + add_library(filelink_logic STATIC ${LINKEXE_SOURCES}) target_include_directories(filelink_logic PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) target_link_libraries(filelink_logic systeminfo @@ -1144,10 +1144,11 @@ 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() - SET_TARGET_PROPERTIES("${Launcher_Name}_filelink" PROPERTIES LINK_FLAGS "/MANIFESTUAC:\"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() From 6d160a7b7e31034c7a657f30003562c20f9b9c21 Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Wed, 8 Feb 2023 00:35:03 -0800 Subject: [PATCH 071/185] feat: successful process elevation and comunication! Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- launcher/CMakeLists.txt | 9 +- launcher/DesktopServices.cpp | 2 +- launcher/FileSystem.cpp | 140 ++++++++++++++++++++++--- launcher/FileSystem.h | 57 +++++++++-- launcher/StringUtils.cpp | 15 +++ launcher/StringUtils.h | 2 + launcher/filelink/FileLink.cpp | 106 ++++++++++++++++++- launcher/filelink/FileLink.h | 15 +++ tests/FileSystem_test.cpp | 181 ++++++++++++++++++++++----------- 9 files changed, 437 insertions(+), 90 deletions(-) diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index 18d4ce0b..dd62893c 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -562,6 +562,13 @@ set(ATLAUNCHER_SOURCES set(LINKEXE_SOURCES filelink/FileLink.h filelink/FileLink.cpp + FileSystem.h + FileSystem.cpp + Exception.h + StringUtils.h + StringUtils.cpp + DesktopServices.h + DesktopServices.cpp ) ######## Logging categories ######## @@ -1126,8 +1133,6 @@ if(WIN32) 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} ) diff --git a/launcher/DesktopServices.cpp b/launcher/DesktopServices.cpp index 302eaf96..69770e99 100644 --- a/launcher/DesktopServices.cpp +++ b/launcher/DesktopServices.cpp @@ -37,7 +37,7 @@ #include #include #include -#include "Application.h" +//#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 ec4af98c..9e51f932 100644 --- a/launcher/FileSystem.cpp +++ b/launcher/FileSystem.cpp @@ -36,6 +36,8 @@ #include "FileSystem.h" +#include "BuildConfig.h" + #include #include #include @@ -45,6 +47,7 @@ #include #include #include +#include #include "DesktopServices.h" #include "StringUtils.h" @@ -61,6 +64,11 @@ #include #include #include +//for ShellExecute +#include +//#include +#include +#include #else #include #endif @@ -218,19 +226,29 @@ bool copy::operator()(const QString& offset, bool dryRun) } +bool create_link::operator()(const QString& offset, bool dryRun) +{ + + for (auto pair : m_path_pairs) { + if (!make_link(pair.src, pair.dst, offset, dryRun)) { + return false; + } + } + return true; +} + + /** * @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) +bool create_link::make_link(const QString& srcPath, const QString& dstPath, 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; + auto src = PathCombine(QDir(srcPath).absolutePath(), offset); + auto dst = PathCombine(QDir(dstPath).absolutePath(), offset); // you can't hard link a directory so make sure if we deal with a directory we do so recursively if (m_useHardLinks) @@ -248,26 +266,25 @@ bool create_link::operator()(const QString& offset, bool dryRun) 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); + fs::create_hard_link(StringUtils::toStdString(src_path), StringUtils::toStdString(dst_path), m_os_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); + fs::create_directory_symlink(StringUtils::toStdString(src_path), StringUtils::toStdString(dst_path), m_os_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); + fs::create_symlink(StringUtils::toStdString(src_path), StringUtils::toStdString(dst_path), m_os_err); } } - if (err) { - qWarning() << "Failed to link files:" << QString::fromStdString(err.message()); + if (m_os_err) { + qWarning() << "Failed to link files:" << QString::fromStdString(m_os_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); + qDebug() << "Error catagory:" << m_os_err.category().name(); + qDebug() << "Error code:" << m_os_err.value(); + emit linkFailed(src_path, dst_path, m_os_err); } else { m_linked++; emit fileLinked(relative_dst_path); @@ -290,10 +307,103 @@ bool create_link::operator()(const QString& offset, bool dryRun) auto relative_path = src_dir.relativeFilePath(src_path); link_file(src_path, relative_path); + if (m_os_err) return false; } } - return err.value() == 0; + return m_os_err.value() == 0; +} + +bool create_link::runPrivlaged(const QString& offset) +{ + + QString serverName = BuildConfig.LAUNCHER_APP_BINARY_NAME + "_filelink_server" + StringUtils::getRandomAlphaNumeric(8); + + connect(&m_linkServer, &QLocalServer::newConnection, this, [&](){ + + qDebug() << "Client connected, sending out pairs"; + // construct block of data to send + QByteArray block; + QDataStream out(&block, QIODevice::WriteOnly); + out.setVersion(QDataStream::Qt_5_15); // choose correct version better? + + qint32 blocksize = quint32(sizeof(quint32)); + for (auto pair : m_path_pairs) { + blocksize += quint32(pair.src.size()); + blocksize += quint32(pair.dst.size()); + } + qDebug() << "About to write block of size:" << blocksize; + out << blocksize; + + out << quint32(m_path_pairs.length()); + for (auto pair : m_path_pairs) { + out << pair.src; + out << pair.dst; + } + + QLocalSocket *clientConnection = m_linkServer.nextPendingConnection(); + connect(clientConnection, &QLocalSocket::disconnected, + clientConnection, &QLocalSocket::deleteLater); + + qint64 byteswritten = clientConnection->write(block); + bool bytesflushed = clientConnection->flush(); + qDebug() << "block flushed" << byteswritten << bytesflushed; + //clientConnection->disconnectFromServer(); + }); + + qDebug() << "Listening on pipe" << serverName; + if (!m_linkServer.listen(serverName)) { + qDebug() << "Unable to start local pipe server on" << serverName << ":" << m_linkServer.errorString(); + return false; + } + + ExternalLinkFileProcess *linkFileProcess = new ExternalLinkFileProcess(serverName, this); + connect(linkFileProcess, &ExternalLinkFileProcess::processExited, this, [&](){ + emit finishedPrivlaged(); + }); + connect(linkFileProcess, &ExternalLinkFileProcess::finished, linkFileProcess, &QObject::deleteLater); + + linkFileProcess->start(); + + // linkFileProcess->wait(); + + return true; +} + + +void ExternalLinkFileProcess::runLinkFile() { + QString fileLinkExe = PathCombine(QCoreApplication::instance()->applicationDirPath(), BuildConfig.LAUNCHER_APP_BINARY_NAME + "_filelink"); + QString params = "-s " + m_server; + +#if defined Q_OS_WIN32 + SHELLEXECUTEINFO ShExecInfo; + HRESULT hr; + + fileLinkExe = fileLinkExe + ".exe"; + + qDebug() << "Running: runas" << fileLinkExe << params; + + LPCWSTR programNameWin = (const wchar_t*) fileLinkExe.utf16(); + LPCWSTR paramsWin = (const wchar_t*) params.utf16(); + + // https://learn.microsoft.com/en-us/windows/win32/api/shellapi/ns-shellapi-shellexecuteinfoa + ShExecInfo.cbSize = sizeof(SHELLEXECUTEINFO); + ShExecInfo.fMask = SEE_MASK_NOCLOSEPROCESS; + ShExecInfo.hwnd = NULL; // Optional. A handle to the owner window, used to display and position any UI that the system might produce while executing this function. + ShExecInfo.lpVerb = L"runas"; // elevate to admin, show UAC + ShExecInfo.lpFile = programNameWin; + ShExecInfo.lpParameters = paramsWin; + ShExecInfo.lpDirectory = NULL; + ShExecInfo.nShow = SW_NORMAL; + ShExecInfo.hInstApp = NULL; + + ShellExecuteEx(&ShExecInfo); + + WaitForSingleObject(ShExecInfo.hProcess, INFINITE); + CloseHandle(ShExecInfo.hProcess); +#endif + + qDebug() << "Process exited"; } bool move(const QString& source, const QString& dest) diff --git a/launcher/FileSystem.h b/launcher/FileSystem.h index 98f55f96..b15d1685 100644 --- a/launcher/FileSystem.h +++ b/launcher/FileSystem.h @@ -39,9 +39,13 @@ #include "Exception.h" #include "pathmatcher/IPathMatcher.h" +#include + #include #include #include +#include +#include namespace FS { @@ -124,16 +128,45 @@ class copy : public QObject { int m_copied; }; +struct LinkPair { + QString src; + QString dst; +}; + +class ExternalLinkFileProcess : public QThread +{ + Q_OBJECT + public: + ExternalLinkFileProcess(QString server, QObject* parent = nullptr) : QThread(parent), m_server(server) {} + + void run() override { + runLinkFile(); + emit processExited(); + } + + signals: + void processExited(); + + private: + void runLinkFile(); + + QString m_server; +}; + /** - * @brief Copies a directory and it's contents from src to dest + * @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 QString& src, const QString& dst, QObject* parent = nullptr) : QObject(parent) { - m_src.setPath(src); - m_dst.setPath(dst); + LinkPair pair = {src, dst}; + m_path_pairs.append(pair); } create_link& useHardLinks(const bool useHard) { @@ -161,31 +194,39 @@ class create_link : public QObject { return *this; } - int getLastOSError() { - return m_last_os_err; + std::error_code getOSError() { + return m_os_err; } bool operator()(bool dryRun = false) { return operator()(QString(), dryRun); } + bool runPrivlaged() { return runPrivlaged(QString()); } + bool runPrivlaged(const QString& offset); + int totalLinked() { return m_linked; } signals: void fileLinked(const QString& relativeName); void linkFailed(const QString& srcName, const QString& dstName, std::error_code err); + void finishedPrivlaged(); private: bool operator()(const QString& offset, bool dryRun = false); + bool make_link(const QString& src_path, const QString& dst_path, const QString& offset, bool dryRun); private: bool m_useHardLinks = false; const IPathMatcher* m_matcher = nullptr; bool m_whitelist = false; bool m_recursive = true; - QDir m_src; - QDir m_dst; + + QList m_path_pairs; + int m_linked; bool m_debug = false; - int m_last_os_err = 0; + std::error_code m_os_err; + + QLocalServer m_linkServer; }; /** diff --git a/launcher/StringUtils.cpp b/launcher/StringUtils.cpp index 0f3c3669..93a44d4c 100644 --- a/launcher/StringUtils.cpp +++ b/launcher/StringUtils.cpp @@ -1,5 +1,7 @@ #include "StringUtils.h" +#include + /// If you're wondering where these came from exactly, then know you're not the only one =D /// TAKEN FROM Qt, because it doesn't expose it intelligently @@ -74,3 +76,16 @@ int StringUtils::naturalCompare(const QString& s1, const QString& s2, Qt::CaseSe // The two strings are the same (02 == 2) so fall back to the normal sort return QString::compare(s1, s2, cs); } + +QString StringUtils::getRandomAlphaNumeric(const int length) +{ + const QString possibleCharacters("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"); + QString randomString; + for(int i=0; i < length; ++i) + { + int index = QRandomGenerator::global()->bounded(0, possibleCharacters.length()); + QChar nextChar = possibleCharacters.at(index); + randomString.append(nextChar); + } + return randomString; +} diff --git a/launcher/StringUtils.h b/launcher/StringUtils.h index 1799605b..1ba19555 100644 --- a/launcher/StringUtils.h +++ b/launcher/StringUtils.h @@ -29,4 +29,6 @@ inline QString fromStdString(string s) #endif int naturalCompare(const QString& s1, const QString& s2, Qt::CaseSensitivity cs); + +QString getRandomAlphaNumeric(const int length); } // namespace StringUtils diff --git a/launcher/filelink/FileLink.cpp b/launcher/filelink/FileLink.cpp index 9b5589ab..78486507 100644 --- a/launcher/filelink/FileLink.cpp +++ b/launcher/filelink/FileLink.cpp @@ -31,8 +31,6 @@ #include - -#include #include #include @@ -48,7 +46,7 @@ -FileLinkApp::FileLinkApp(int &argc, char **argv) : QCoreApplication(argc, argv) +FileLinkApp::FileLinkApp(int &argc, char **argv) : QCoreApplication(argc, argv), socket(new QLocalSocket(this)) { #if defined Q_OS_WIN32 // attach the parent console @@ -81,18 +79,116 @@ FileLinkApp::FileLinkApp(int &argc, char **argv) : QCoreApplication(argc, argv) // Commandline parsing QCommandLineParser parser; - parser.setApplicationDescription(QObject::tr("a batch MKLINK program for windows to be useed with prismlauncher")); + 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"} }); parser.addHelpOption(); parser.addVersionOption(); parser.process(arguments()); + QString serverToJoin = parser.value("server"); + qDebug() << "link program launched"; + if (!serverToJoin.isEmpty()) { + qDebug() << "joining server" << serverToJoin; + joinServer(serverToJoin); + } else { + qDebug() << "no server to join"; + exit(); + } + +} + +void FileLinkApp::joinServer(QString server) +{ + + blockSize = 0; + + in.setDevice(&socket); + in.setVersion(QDataStream::Qt_5_15); + + connect(&socket, &QLocalSocket::connected, this, [&](){ + qDebug() << "connected to server"; + }); + + connect(&socket, &QLocalSocket::readyRead, this, &FileLinkApp::readPathPairs); + + connect(&socket, &QLocalSocket::errorOccurred, this, [&](QLocalSocket::LocalSocketError socketError){ + switch (socketError) { + case QLocalSocket::ServerNotFoundError: + qDebug() << tr("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() << tr("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: + break; + default: + qDebug() << tr("The following error occurred: %1.").arg(socket.errorString()); + } + }); + + connect(&socket, &QLocalSocket::disconnected, this, [&](){ + qDebug() << "dissconnected from server"; + }); + + socket.connectToServer(server); + + +} + +void FileLinkApp::runLink() +{ + qDebug() << "creating link"; + FS::create_link lnk(m_path_pairs); + lnk.debug(true); + if (!lnk()) { + qDebug() << "Link Failed!" << lnk.getOSError().value() << lnk.getOSError().message().c_str(); + } + //exit(); + qDebug() << "done, should exit"; +} + +void FileLinkApp::readPathPairs() +{ + m_path_pairs.clear(); + qDebug() << "Reading path pairs from server"; + qDebug() << "bytes avalible" << socket.bytesAvailable(); + if (blockSize == 0) { + // Relies on the fact that QDataStream serializes a quint32 into + // sizeof(quint32) bytes + if (socket.bytesAvailable() < (int)sizeof(quint32)) + return; + qDebug() << "reading block size"; + in >> blockSize; + } + qDebug() << "blocksize is" << blockSize; + qDebug() << "bytes avalible" << socket.bytesAvailable(); + if (socket.bytesAvailable() < blockSize || in.atEnd()) + return; + + quint32 numPairs; + in >> numPairs; + qDebug() << "numPairs" << numPairs; + + for(int i = 0; i < numPairs; i++) { + FS::LinkPair pair; + in >> pair.src; + in >> pair.dst; + qDebug() << "link" << pair.src << "to" << pair.dst; + m_path_pairs.append(pair); + } + + runLink(); } diff --git a/launcher/filelink/FileLink.h b/launcher/filelink/FileLink.h index 253d1394..5d0ba123 100644 --- a/launcher/filelink/FileLink.h +++ b/launcher/filelink/FileLink.h @@ -32,6 +32,11 @@ #include #include #include +#include +#include + +#define PRISM_EXTERNAL_EXE +#include "FileSystem.h" class FileLinkApp : public QCoreApplication { @@ -43,7 +48,17 @@ public: virtual ~FileLinkApp(); private: + + void joinServer(QString server); + void readPathPairs(); + void runLink(); + QDateTime m_startTime; + QLocalSocket socket; + QDataStream in; + quint32 blockSize; + + QList m_path_pairs; #if defined Q_OS_WIN32 // used on Windows to attach the standard IO streams diff --git a/tests/FileSystem_test.cpp b/tests/FileSystem_test.cpp index 395ca5c0..be0a4be0 100644 --- a/tests/FileSystem_test.cpp +++ b/tests/FileSystem_test.cpp @@ -2,6 +2,8 @@ #include #include +#include + #include #include @@ -26,6 +28,66 @@ namespace fs = ghc::filesystem; #include + + +class LinkTask : public Task { + Q_OBJECT + + friend class FileSystemTest; + + LinkTask(QString src, QString dst) + { + m_lnk = new FS::create_link(src, dst, this); + m_lnk->debug(true); + } + + void matcher(const IPathMatcher *filter) + { + m_lnk->matcher(filter); + } + + void linkRecursively(bool recursive) + { + m_lnk->linkRecursively(recursive); + m_linkRecursive = recursive; + } + + void whitelist(bool b) + { + m_lnk->whitelist(b); + } + + private: + void executeTask() override + { + if(!(*m_lnk)()){ +#if defined Q_OS_WIN32 + if (!m_useHard) { + qDebug() << "EXPECTED: Link failure, Windows requires permissions for symlinks"; + + qDebug() << "atempting to run with privelage"; + connect(m_lnk, &FS::create_link::finishedPrivlaged, this, [&](){ + emitSucceeded(); + }); + m_lnk->runPrivlaged(); + } else { + qDebug() << "Link Failed!" << m_lnk->getOSError().value() << m_lnk->getOSError().message().c_str(); + } +#else + qDebug() << "Link Failed!" << m_lnk->getOSError().value() << m_lnk->getOSError().message().c_str(); +#endif + } else { + emitSucceeded(); + } + + }; + + FS::create_link *m_lnk; + bool m_useHard = false; + bool m_linkRecursive = true; +}; + + class FileSystemTest : public QObject { Q_OBJECT @@ -273,7 +335,7 @@ slots: void test_link() { QString folder = QFINDTESTDATA("testdata/FileSystem/test_folder"); - auto f = [&folder]() + auto f = [&folder, this]() { QTemporaryDir tempDir; tempDir.setAutoRemove(true); @@ -282,17 +344,17 @@ slots: 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(); - } + + LinkTask lnk_tsk(folder, target_dir.path()); + lnk_tsk.linkRecursively(false); + 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."); for(auto entry: target_dir.entryList()) { @@ -337,7 +399,7 @@ slots: lnk.useHardLinks(true); lnk.debug(true); if(!lnk()){ - qDebug() << "Link Failed!" << lnk.getLastOSError(); + qDebug() << "Link Failed!" << lnk.getOSError().value() << lnk.getOSError().message().c_str(); } for(auto entry: target_dir.entryList()) @@ -385,18 +447,19 @@ slots: 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(); - } + + LinkTask lnk_tsk(folder, target_dir.path()); + lnk_tsk.matcher(new RegexpMatcher("[.]?mcmeta")); + lnk_tsk.linkRecursively(true); + 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."); + for(auto entry: target_dir.entryList()) { @@ -435,19 +498,19 @@ slots: 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(); - } + + LinkTask lnk_tsk(folder, target_dir.path()); + lnk_tsk.matcher(new RegexpMatcher("[.]?mcmeta")); + lnk_tsk.linkRecursively(true); + lnk_tsk.whitelist(true); + 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."); for(auto entry: target_dir.entryList()) { @@ -486,17 +549,17 @@ slots: 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(); - } + + LinkTask lnk_tsk(folder, target_dir.path()); + lnk_tsk.linkRecursively(true); + 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."); auto filter = QDir::Filter::Files | QDir::Filter::Dirs | QDir::Filter::Hidden; @@ -538,16 +601,16 @@ slots: 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(); - } + + LinkTask lnk_tsk(file, target_dir.filePath("pack.mcmeta")); + 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."); auto filter = QDir::Filter::Files; From 8ba51c790098ec9ebe3d2ef686f823b61c8a3645 Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Wed, 8 Feb 2023 12:36:15 -0800 Subject: [PATCH 072/185] refactor: make complete list of links to make and send that. Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- launcher/FileSystem.cpp | 223 +++++++++++++++++++++------------ launcher/FileSystem.h | 35 ++++-- launcher/filelink/FileLink.cpp | 136 ++++++++++++++++---- launcher/filelink/FileLink.h | 6 +- tests/FileSystem_test.cpp | 11 +- 5 files changed, 295 insertions(+), 116 deletions(-) diff --git a/launcher/FileSystem.cpp b/launcher/FileSystem.cpp index 9e51f932..c48a3bba 100644 --- a/launcher/FileSystem.cpp +++ b/launcher/FileSystem.cpp @@ -66,7 +66,6 @@ #include //for ShellExecute #include -//#include #include #include #else @@ -228,94 +227,120 @@ bool copy::operator()(const QString& offset, bool dryRun) bool create_link::operator()(const QString& offset, bool dryRun) { + m_linked = 0; // reset counter + m_path_results.clear(); + m_links_to_make.clear(); + + m_path_results.clear(); + + make_link_list(offset); + + if (!dryRun) + return make_links(); - for (auto pair : m_path_pairs) { - if (!make_link(pair.src, pair.dst, offset, dryRun)) { - return false; - } - } return true; } /** - * @brief links a directory and it's contents from src to dest + * @brief make a list off all the links ot make * @param offset subdirectory form src to link to dest * @return if there was an error during the attempt to link */ -bool create_link::make_link(const QString& srcPath, const QString& dstPath, const QString& offset, bool dryRun) +void create_link::make_link_list( const QString& offset) { - m_linked = 0; // reset counter + for (auto pair : m_path_pairs) { + const QString& srcPath = pair.src; + const QString& dstPath = pair.dst; - auto src = PathCombine(QDir(srcPath).absolutePath(), offset); - auto dst = PathCombine(QDir(dstPath).absolutePath(), offset); + auto src = PathCombine(QDir(srcPath).absolutePath(), offset); + auto dst = PathCombine(QDir(dstPath).absolutePath(), offset); - // 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; + // 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), m_os_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), m_os_err); - } else { - if (m_debug) - qDebug() << "making symlink:" << src_path << "to" << dst_path; - fs::create_symlink(StringUtils::toStdString(src_path), StringUtils::toStdString(dst_path), m_os_err); + // 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)) { + qDebug() << "path" << relative_dst_path << "in black list or not in whitelist"; + return; + } + + + auto dst_path = PathCombine(dst, relative_dst_path); + LinkPair link = {src_path, dst_path}; + m_links_to_make.append(link); + }; + + 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); } - } + } +} + +bool create_link::make_links() +{ + for (auto link : m_links_to_make) { + + QString src_path = link.src; + QString dst_path = link.dst; + + 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), m_os_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), m_os_err); + } else { + if (m_debug) + qDebug() << "making symlink:" << src_path << "to" << dst_path; + fs::create_symlink(StringUtils::toStdString(src_path), StringUtils::toStdString(dst_path), m_os_err); + } + + if (m_os_err) { 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 code:" << m_os_err.value(); - emit linkFailed(src_path, dst_path, m_os_err); + emit linkFailed(src_path, dst_path, QString::fromStdString(m_os_err.message()), m_os_err.value()); } 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); - if (m_os_err) return false; + emit fileLinked(src_path, dst_path); } + if (m_os_err) return false; } - - return m_os_err.value() == 0; + return true; } -bool create_link::runPrivlaged(const QString& offset) +void create_link::runPrivlaged(const QString& offset) { + m_linked = 0; // reset counter + m_path_results.clear(); + m_links_to_make.clear(); + + bool gotResults = false; + + make_link_list(offset); QString serverName = BuildConfig.LAUNCHER_APP_BINARY_NAME + "_filelink_server" + StringUtils::getRandomAlphaNumeric(8); @@ -325,25 +350,72 @@ bool create_link::runPrivlaged(const QString& offset) // construct block of data to send QByteArray block; QDataStream out(&block, QIODevice::WriteOnly); - out.setVersion(QDataStream::Qt_5_15); // choose correct version better? + out.setVersion(QDataStream::Qt_5_0); // choose correct version better? qint32 blocksize = quint32(sizeof(quint32)); - for (auto pair : m_path_pairs) { - blocksize += quint32(pair.src.size()); - blocksize += quint32(pair.dst.size()); + for (auto link : m_links_to_make) { + blocksize += quint32(link.src.size()); + blocksize += quint32(link.dst.size()); } qDebug() << "About to write block of size:" << blocksize; out << blocksize; - out << quint32(m_path_pairs.length()); - for (auto pair : m_path_pairs) { - out << pair.src; - out << pair.dst; + out << quint32(m_links_to_make.length()); + for (auto link : m_links_to_make) { + out << link.src; + out << link.dst; } QLocalSocket *clientConnection = m_linkServer.nextPendingConnection(); connect(clientConnection, &QLocalSocket::disconnected, clientConnection, &QLocalSocket::deleteLater); + + connect(clientConnection, &QLocalSocket::readyRead, this, [&, clientConnection](){ + QDataStream in; + quint32 blockSize = 0; + in.setDevice(clientConnection); + in.setVersion(QDataStream::Qt_5_0); + qDebug() << "Reading path results from client"; + qDebug() << "bytes avalible" << clientConnection->bytesAvailable(); + + // Relies on the fact that QDataStream serializes a quint32 into + // sizeof(quint32) bytes + if (clientConnection->bytesAvailable() < (int)sizeof(quint32)) + return; + qDebug() << "reading block size"; + in >> blockSize; + + qDebug() << "blocksize is" << blockSize; + qDebug() << "bytes avalible" << clientConnection->bytesAvailable(); + if (clientConnection->bytesAvailable() < blockSize || in.atEnd()) + return; + + quint32 numResults; + in >> numResults; + qDebug() << "numResults" << numResults; + + for(int i = 0; i < numResults; i++) { + FS::LinkResult result; + in >> result.src; + in >> result.dst; + in >> result.err_msg; + qint32 err_value; + 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; + emit linkFailed(result.src, result.dst, result.err_msg, result.err_value); + } else { + qDebug() << "privlaged link success" << result.src << "to" << result.dst; + m_linked++; + emit fileLinked(result.src, result.dst); + } + m_path_results.append(result); + } + gotResults = true; + qDebug() << "results recieved, closing connection"; + clientConnection->close(); + }); qint64 byteswritten = clientConnection->write(block); bool bytesflushed = clientConnection->flush(); @@ -354,20 +426,15 @@ bool create_link::runPrivlaged(const QString& offset) qDebug() << "Listening on pipe" << serverName; if (!m_linkServer.listen(serverName)) { qDebug() << "Unable to start local pipe server on" << serverName << ":" << m_linkServer.errorString(); - return false; + return; } - ExternalLinkFileProcess *linkFileProcess = new ExternalLinkFileProcess(serverName, this); - connect(linkFileProcess, &ExternalLinkFileProcess::processExited, this, [&](){ - emit finishedPrivlaged(); - }); + ExternalLinkFileProcess* linkFileProcess = new ExternalLinkFileProcess(serverName, m_useHardLinks, this); + connect(linkFileProcess, &ExternalLinkFileProcess::processExited, this, [&]() { emit finishedPrivlaged(gotResults); }); connect(linkFileProcess, &ExternalLinkFileProcess::finished, linkFileProcess, &QObject::deleteLater); linkFileProcess->start(); - // linkFileProcess->wait(); - - return true; } @@ -375,6 +442,8 @@ void ExternalLinkFileProcess::runLinkFile() { QString fileLinkExe = PathCombine(QCoreApplication::instance()->applicationDirPath(), BuildConfig.LAUNCHER_APP_BINARY_NAME + "_filelink"); QString params = "-s " + m_server; + params += " -H " + QVariant(m_useHardLinks).toString(); + #if defined Q_OS_WIN32 SHELLEXECUTEINFO ShExecInfo; HRESULT hr; diff --git a/launcher/FileSystem.h b/launcher/FileSystem.h index b15d1685..2e739298 100644 --- a/launcher/FileSystem.h +++ b/launcher/FileSystem.h @@ -133,13 +133,22 @@ struct LinkPair { QString dst; }; -class ExternalLinkFileProcess : public QThread -{ +struct LinkResult { + QString src; + QString dst; + QString err_msg; + int err_value; +}; + +class ExternalLinkFileProcess : public QThread { Q_OBJECT public: - ExternalLinkFileProcess(QString server, QObject* parent = nullptr) : QThread(parent), m_server(server) {} + ExternalLinkFileProcess(QString server, bool useHardLinks, QObject* parent = nullptr) + : QThread(parent), m_server(server), m_useHardLinks(useHardLinks) + {} - void run() override { + void run() override + { runLinkFile(); emit processExited(); } @@ -150,6 +159,8 @@ class ExternalLinkFileProcess : public QThread private: void runLinkFile(); + bool m_useHardLinks = false; + QString m_server; }; @@ -200,19 +211,21 @@ class create_link : public QObject { bool operator()(bool dryRun = false) { return operator()(QString(), dryRun); } - bool runPrivlaged() { return runPrivlaged(QString()); } - bool runPrivlaged(const QString& offset); + void runPrivlaged() { runPrivlaged(QString()); } + void runPrivlaged(const QString& offset); int totalLinked() { return m_linked; } signals: - void fileLinked(const QString& relativeName); - void linkFailed(const QString& srcName, const QString& dstName, std::error_code err); - void finishedPrivlaged(); + 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(); private: bool operator()(const QString& offset, bool dryRun = false); - bool make_link(const QString& src_path, const QString& dst_path, const QString& offset, bool dryRun); + void make_link_list(const QString& offset); + bool make_links(); private: bool m_useHardLinks = false; @@ -221,6 +234,8 @@ class create_link : public QObject { bool m_recursive = true; QList m_path_pairs; + QList m_path_results; + QList m_links_to_make; int m_linked; bool m_debug = false; diff --git a/launcher/filelink/FileLink.cpp b/launcher/filelink/FileLink.cpp index 78486507..a731ecdb 100644 --- a/launcher/filelink/FileLink.cpp +++ b/launcher/filelink/FileLink.cpp @@ -23,6 +23,8 @@ #include "FileLink.h" #include "BuildConfig.h" +#include "StringUtils.h" + #include @@ -43,6 +45,24 @@ #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__ + +#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 + +#ifndef GHC_USE_STD_FS +#include +namespace fs = ghc::filesystem; +#endif @@ -82,7 +102,8 @@ FileLinkApp::FileLinkApp(int &argc, char **argv) : QCoreApplication(argc, argv), 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"} + {{"s", "server"}, "Join the specified server on launch", "pipe name"}, + {{"H", "hard"}, "use hard links insted of symbolic", "true/false"} }); parser.addHelpOption(); parser.addVersionOption(); @@ -90,6 +111,7 @@ FileLinkApp::FileLinkApp(int &argc, char **argv) : QCoreApplication(argc, argv), parser.process(arguments()); QString serverToJoin = parser.value("server"); + m_useHardLinks = QVariant(parser.value("hard")).toBool(); qDebug() << "link program launched"; @@ -109,7 +131,7 @@ void FileLinkApp::joinServer(QString server) blockSize = 0; in.setDevice(&socket); - in.setVersion(QDataStream::Qt_5_15); + in.setVersion(QDataStream::Qt_5_0); connect(&socket, &QLocalSocket::connected, this, [&](){ qDebug() << "connected to server"; @@ -120,25 +142,27 @@ void FileLinkApp::joinServer(QString server) connect(&socket, &QLocalSocket::errorOccurred, this, [&](QLocalSocket::LocalSocketError socketError){ switch (socketError) { case QLocalSocket::ServerNotFoundError: - qDebug() << tr("The host was not found. Please make sure " - "that the server is running and that the " - "server name is correct."); + 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() << tr("The connection was refused by the peer. " - "Make sure the server is running, " - "and check that the server name " - "is correct."); + 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() << tr("The following error occurred: %1.").arg(socket.errorString()); + qDebug() << "The following error occurred: " << socket.errorString(); } }); connect(&socket, &QLocalSocket::disconnected, this, [&](){ - qDebug() << "dissconnected from server"; + qDebug() << "dissconnected from server, should exit"; + exit(); }); socket.connectToServer(server); @@ -147,20 +171,82 @@ void FileLinkApp::joinServer(QString server) } void FileLinkApp::runLink() -{ - qDebug() << "creating link"; - FS::create_link lnk(m_path_pairs); - lnk.debug(true); - if (!lnk()) { - qDebug() << "Link Failed!" << lnk.getOSError().value() << lnk.getOSError().message().c_str(); +{ + + 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; + + FS::ensureFilePathExists(dst_path); + if (m_useHardLinks) { + qDebug() << "making hard link:" << src_path << "to" << dst_path; + fs::create_hard_link(StringUtils::toStdString(src_path), StringUtils::toStdString(dst_path), os_err); + } else if (fs::is_directory(StringUtils::toStdString(src_path))) { + qDebug() << "making directory_symlink:" << src_path << "to" << dst_path; + fs::create_directory_symlink(StringUtils::toStdString(src_path), StringUtils::toStdString(dst_path), os_err); + } 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 code:" << 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}; + m_path_results.append(result); + } } - //exit(); - qDebug() << "done, should exit"; + + sendResults(); + qDebug() << "done, should exit soon"; + +} + +void FileLinkApp::sendResults() +{ + // construct block of data to send + QByteArray block; + QDataStream out(&block, QIODevice::WriteOnly); + out.setVersion(QDataStream::Qt_5_0); + + qint32 blocksize = quint32(sizeof(quint32)); + for (auto result : m_path_results) { + blocksize += quint32(result.src.size()); + blocksize += quint32(result.dst.size()); + blocksize += quint32(result.err_msg.size()); + blocksize += quint32(sizeof(quint32)); + } + qDebug() << "About to write block of size:" << blocksize; + out << blocksize; + + out << quint32(m_path_results.length()); + for (auto result : m_path_results) { + out << result.src; + out << result.dst; + out << result.err_msg; + out << quint32(result.err_value); + } + + qint64 byteswritten = socket.write(block); + bool bytesflushed = socket.flush(); + qDebug() << "block flushed" << byteswritten << bytesflushed; } void FileLinkApp::readPathPairs() { - m_path_pairs.clear(); + m_links_to_make.clear(); qDebug() << "Reading path pairs from server"; qDebug() << "bytes avalible" << socket.bytesAvailable(); if (blockSize == 0) { @@ -176,16 +262,16 @@ void FileLinkApp::readPathPairs() if (socket.bytesAvailable() < blockSize || in.atEnd()) return; - quint32 numPairs; - in >> numPairs; - qDebug() << "numPairs" << numPairs; + quint32 numLinks; + in >> numLinks; + qDebug() << "numLinks" << numLinks; - for(int i = 0; i < numPairs; i++) { + for(int i = 0; i < numLinks; i++) { FS::LinkPair pair; in >> pair.src; in >> pair.dst; qDebug() << "link" << pair.src << "to" << pair.dst; - m_path_pairs.append(pair); + m_links_to_make.append(pair); } runLink(); diff --git a/launcher/filelink/FileLink.h b/launcher/filelink/FileLink.h index 5d0ba123..d146b8d9 100644 --- a/launcher/filelink/FileLink.h +++ b/launcher/filelink/FileLink.h @@ -52,13 +52,17 @@ private: void joinServer(QString server); void readPathPairs(); void runLink(); + void sendResults(); + + bool m_useHardLinks = false; QDateTime m_startTime; QLocalSocket socket; QDataStream in; quint32 blockSize; - QList m_path_pairs; + QList m_links_to_make; + QList m_path_results; #if defined Q_OS_WIN32 // used on Windows to attach the standard IO streams diff --git a/tests/FileSystem_test.cpp b/tests/FileSystem_test.cpp index be0a4be0..4ccc4003 100644 --- a/tests/FileSystem_test.cpp +++ b/tests/FileSystem_test.cpp @@ -66,12 +66,17 @@ 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, [&](){ - emitSucceeded(); + connect(m_lnk, &FS::create_link::finishedPrivlaged, this, [&](bool gotResults){ + if (gotResults) { + emitSucceeded(); + } else { + qDebug() << "Privlaged run exited without results!"; + emitFailed(); + } }); m_lnk->runPrivlaged(); } else { - qDebug() << "Link Failed!" << m_lnk->getOSError().value() << m_lnk->getOSError().message().c_str(); + qDebug() << "Link Failed!" << m_lnk->getOSError().value() << m_lnk->getOSError().message().c_str(); } #else qDebug() << "Link Failed!" << m_lnk->getOSError().value() << m_lnk->getOSError().message().c_str(); 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: [PATCH 073/185] 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(-) 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 folderCopy(); + 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(); + } }); 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 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: [PATCH 074/185] 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(-) 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 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: [PATCH 075/185] 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(-) 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; From c5bbe42b57075a4b428d0be1c1ca9f51701a1a7c Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Wed, 8 Feb 2023 23:42:13 -0700 Subject: [PATCH 076/185] feat: reflink / Clone support! Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- launcher/FileSystem.cpp | 206 ++++++++++++++++++++++++++++++++++++++++ launcher/FileSystem.h | 141 +++++++++++++++++++++++++++ 2 files changed, 347 insertions(+) diff --git a/launcher/FileSystem.cpp b/launcher/FileSystem.cpp index c94770ee..7b7fc80b 100644 --- a/launcher/FileSystem.cpp +++ b/launcher/FileSystem.cpp @@ -3,6 +3,7 @@ * Prism Launcher - Minecraft Launcher * Copyright (C) 2022 Sefa Eyeoglu * Copyright (C) 2022 TheKodeToad + * 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 @@ -35,6 +36,9 @@ */ #include "FileSystem.h" +#include +#include +#include #include "BuildConfig.h" @@ -48,6 +52,7 @@ #include #include #include +#include #include "DesktopServices.h" #include "StringUtils.h" @@ -91,6 +96,18 @@ namespace fs = std::filesystem; namespace fs = ghc::filesystem; #endif + +// clone +#if defined(Q_OS_LINUX) +#include +#include /* Definition of FICLONE* constants */ +#include +#include +#elif defined(Q_OS_MACOS) || defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD) +#include +#include +#endif + namespace FS { void ensureExists(const QDir& dir) @@ -831,4 +848,193 @@ bool overrideFolder(QString overwritten_path, QString override_path) return err.value() == 0; } + +/** + * @brief colect information about the filesystem under a file + * + */ +FilesystemInfo statFS(QString path) +{ + + FilesystemInfo info; + + QStorageInfo storage_info(path); + + QString fsTypeName = QString::fromStdString(storage_info.fileSystemType().toStdString()); + + for (auto fs_type_pair : s_filesystem_type_names_inverse.toStdMap()) { + auto fs_type_name = fs_type_pair.first; + auto fs_type = fs_type_pair.second; + + if(fsTypeName.contains(fs_type_name.toLower())) { + info.fsType = fs_type; + break; + } + } + + info.blockSize = storage_info.blockSize(); + info.bytesAvailable = storage_info.bytesAvailable(); + info.bytesFree = storage_info.bytesFree(); + info.bytesTotal = storage_info.bytesTotal(); + + info.name = storage_info.name(); + info.rootPath = storage_info.rootPath(); + + return info; +} + +/** + * @brief if the Filesystem is reflink/clone capable + * + */ +bool canCloneOnFS(const QString& path) +{ + FilesystemInfo info = statFS(path); + return canCloneOnFS(info); +} +bool canCloneOnFS(const FilesystemInfo& info) +{ + return canCloneOnFS(info.fsType); +} +bool canCloneOnFS(FilesystemType type) +{ + return s_clone_filesystems.contains(type); +} + +/** + * @brief if the Filesystem is reflink/clone capable and both paths are on the same device + * + */ +bool canClone(const QString& src, const QString& dst) +{ + auto srcVInfo = statFS(src); + auto dstVInfo = statFS(dst); + + bool sameDevice = srcVInfo.rootPath == dstVInfo.rootPath; + + return sameDevice && canCloneOnFS(srcVInfo) && canCloneOnFS(dstVInfo); +} + +/** + * @brief reflink/clones 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 clone::operator()(const QString& offset, bool dryRun) +{ + + if (!canClone(m_src.absolutePath(), m_dst.absolutePath())) { + qWarning() << "Can not clone: not same device or not clone/reflink filesystem"; + qDebug() << "Source path:" << m_src.absolutePath(); + qDebug() << "Destination path:" << m_dst.absolutePath(); + emit cloneFailed(m_src.absolutePath(), m_dst.absolutePath()); + return false; + } + + m_cloned = 0; // reset counter + + auto src = PathCombine(m_src.absolutePath(), offset); + auto dst = PathCombine(m_dst.absolutePath(), offset); + + std::error_code err; + + // Function that'll do the actual cloneing + auto cloneFile = [&](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); + clone_file(src_path, dst_path, err); + } + if (err) { + qWarning() << "Failed to clone files:" << QString::fromStdString(err.message()); + qDebug() << "Source file:" << src_path; + qDebug() << "Destination file:" << dst_path; + } + m_cloned++; + emit fileCloned(src_path, dst_path); + }; + + // We can't use copy_opts::recursive because we need to take into account the + // blacklisted paths, so we iterate over the source directory, and if there's no blacklist + // match, we copy the file. + 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); + + cloneFile(src_path, relative_path); + } + + // If the root src is not a directory, the previous iterator won't run. + if (!fs::is_directory(StringUtils::toStdString(src))) + cloneFile(src, ""); + + return err.value() == 0; +} + +/** + * @brief clone/reflink file from src to dst + * + */ +bool clone_file(const QString& src, const QString& dst, std::error_code& ec) +{ + auto src_path = StringUtils::toStdString(QFileInfo(src).absoluteFilePath()); + auto dst_path = StringUtils::toStdString(QFileInfo(dst).absoluteFilePath()); + +#if defined(Q_OS_WIN) + qWarning("clone/reflink not supported on windows!"); + ec = std::make_error_code(std::errc::not_supported); + return false; +#elif defined(Q_OS_LINUX) + + // https://man7.org/linux/man-pages/man2/ioctl_ficlone.2.html + + int src_fd = open(src_path.c_str(), O_RDONLY); + if(!src_fd) { + 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) { + qWarning() << "Failed to open file:" << dst_path.c_str(); + qDebug() << "Error:" << strerror(errno); + ec = std::make_error_code(static_cast(errno)); + return false; + } + // attempt to clone + if(!ioctl(dst_fd, FICLONE, src_fd)){ + 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)); + return false; + } + +#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)) { + 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)); + return false; + } + +#else + qWarning("clone/reflink not supported! unknown OS"); + ec = std::make_error_code(std::errc::not_supported); + return false; +#endif + + return true; +} + } diff --git a/launcher/FileSystem.h b/launcher/FileSystem.h index 782a2f40..531036dd 100644 --- a/launcher/FileSystem.h +++ b/launcher/FileSystem.h @@ -3,6 +3,7 @@ * Prism Launcher - Minecraft Launcher * Copyright (C) 2022 Sefa Eyeoglu * Copyright (C) 2022 TheKodeToad + * 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 @@ -313,4 +314,144 @@ bool overrideFolder(QString overwritten_path, QString override_path); * Creates a shortcut to the specified target file at the specified destination path. */ bool createShortcut(QString destination, QString target, QStringList args, QString name, QString icon); + +enum class FilesystemType { + FAT, + NTFS, + EXT, + EXT_2_OLD, + EXT_2_3_4, + XFS, + BTRFS, + NFS, + ZFS, + APFS, + HFS, + HFSPLUS, + HFSX, + UNKNOWN +}; + +static const QMap s_filesystem_type_names = { + {FilesystemType::FAT, QString("FAT")}, + {FilesystemType::NTFS, QString("NTFS")}, + {FilesystemType::EXT, QString("EXT")}, + {FilesystemType::EXT_2_OLD, QString("EXT2_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::UNKNOWN, QString("UNKNOWN")} +}; + +static const QMap s_filesystem_type_names_inverse = { + {QString("FAT"), FilesystemType::FAT}, + {QString("NTFS"), FilesystemType::NTFS}, + {QString("EXT2_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("EXT"), FilesystemType::EXT}, + {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("UNKNOWN"), FilesystemType::UNKNOWN} +}; + +inline QString getFilesystemTypeName(FilesystemType type) { + return s_filesystem_type_names.constFind(type).value(); +} + +struct FilesystemInfo { + FilesystemType fsType = FilesystemType::UNKNOWN; + int blockSize; + qint64 bytesAvailable; + qint64 bytesFree; + qint64 bytesTotal; + QString name; + QString rootPath; +}; + +/** + * @brief colect information about the filesystem under a file + * + */ +FilesystemInfo statFS(QString path); + + +static const QList s_clone_filesystems = { + FilesystemType::BTRFS, FilesystemType::APFS, FilesystemType::ZFS, FilesystemType::XFS +}; + +/** + * @brief if the Filesystem is reflink/clone capable + * + */ +bool canCloneOnFS(const QString& path); +bool canCloneOnFS(const FilesystemInfo& info); +bool canCloneOnFS(FilesystemType type); + +/** + * @brief if the Filesystem is 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: + clone(const QString& src, const QString& dst, QObject* parent = nullptr) : QObject(parent) + { + m_src.setPath(src); + m_dst.setPath(dst); + } + clone& matcher(const IPathMatcher* filter) + { + m_matcher = filter; + return *this; + } + clone& whitelist(bool whitelist) + { + m_whitelist = whitelist; + return *this; + } + + bool operator()(bool dryRun = false) { return operator()(QString(), dryRun); } + + int totalCloned() { return m_cloned; } + + signals: + void fileCloned(const QString& src, const QString& dst); + void cloneFailed(const QString& src, const QString& dst); + + private: + bool operator()(const QString& offset, bool dryRun = false); + + private: + const IPathMatcher* m_matcher = nullptr; + bool m_whitelist = false; + QDir m_src; + QDir m_dst; + int m_cloned; +}; + +/** + * @brief clone/reflink file from src to dst + * + */ +bool clone_file(const QString& src, const QString& dst, std::error_code& ec); + } 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: [PATCH 077/185] 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(-) 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 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: [PATCH 078/185] 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(-) 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 + Links are supported on most filesystems except FAT + + + Qt::AlignCenter - - - false + + + 6 - - Use hard links instead of symbolic links + + 6 - - Use hard links + + 6 - - - - - - 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 - - - false - - + + + + 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 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: [PATCH 079/185] 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(-) 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) From 34ac8b3ec345a0c1c909111e8e0a89fa84c13673 Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Thu, 9 Feb 2023 20:02:20 -0700 Subject: [PATCH 080/185] fix: Qt < 5.14.0 compat Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- launcher/FileSystem.cpp | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/launcher/FileSystem.cpp b/launcher/FileSystem.cpp index 4ee3899b..57b796ad 100644 --- a/launcher/FileSystem.cpp +++ b/launcher/FileSystem.cpp @@ -38,6 +38,7 @@ #include "FileSystem.h" #include #include +#include #include #include "BuildConfig.h" @@ -587,9 +588,13 @@ int PathDepth(const QString& path) QFileInfo info(path); +#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0) + auto parts = QDir::toNativeSeparators(info.path()).split(QDir::separator(), QString::SkipEmptyParts); +#else auto parts = QDir::toNativeSeparators(info.path()).split(QDir::separator(), Qt::SkipEmptyParts); +#endif - int numParts = QDir::toNativeSeparators(info.path()).split(QDir::separator(), Qt::SkipEmptyParts).length(); + int numParts = parts.length(); numParts -= parts.count("."); numParts -= parts.count("..") * 2; @@ -606,7 +611,12 @@ QString PathTruncate(const QString& path, int depth) return PathTruncate(trunc, depth); } +#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0) + auto parts = QDir::toNativeSeparators(trunc).split(QDir::separator(), QString::SkipEmptyParts); +#else auto parts = QDir::toNativeSeparators(trunc).split(QDir::separator(), Qt::SkipEmptyParts); +#endif + if (parts.startsWith(".") && !path.startsWith(".")) { parts.removeFirst(); } From cd2419137d68781354325d77c0392ab0ee1b65de Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Thu, 9 Feb 2023 20:12:36 -0700 Subject: [PATCH 081/185] fix: better test compareison (also qt5 compat) Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- tests/FileSystem_test.cpp | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/tests/FileSystem_test.cpp b/tests/FileSystem_test.cpp index 4418dd62..ab78c150 100644 --- a/tests/FileSystem_test.cpp +++ b/tests/FileSystem_test.cpp @@ -755,28 +755,28 @@ slots: } 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); + QCOMPARE(FS::PathDepth(""), 0); + QCOMPARE(FS::PathDepth("."), 0); + QCOMPARE(FS::PathDepth("foo.txt"), 0); + QCOMPARE(FS::PathDepth("./foo.txt"), 0); + QCOMPARE(FS::PathDepth("./bar/foo.txt"), 1); + QCOMPARE(FS::PathDepth("../bar/foo.txt"), 0); + QCOMPARE(FS::PathDepth("/bar/foo.txt"), 1); + QCOMPARE(FS::PathDepth("baz/bar/foo.txt"), 2); + QCOMPARE(FS::PathDepth("/baz/bar/foo.txt"), 2); + QCOMPARE(FS::PathDepth("./baz/bar/foo.txt"), 2); + QCOMPARE(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"); + QCOMPARE(FS::PathTruncate("", 0), ""); + QCOMPARE(FS::PathTruncate("foo.txt", 0), ""); + QCOMPARE(FS::PathTruncate("foo.txt", 1), ""); + QCOMPARE(FS::PathTruncate("./bar/foo.txt", 0), "./bar"); + QCOMPARE(FS::PathTruncate("./bar/foo.txt", 1), "./bar"); + QCOMPARE(FS::PathTruncate("/bar/foo.txt", 1), "/bar"); + QCOMPARE(FS::PathTruncate("bar/foo.txt", 1), "bar"); + QCOMPARE(FS::PathTruncate("baz/bar/foo.txt", 2), "baz/bar"); } }; From 3a0e4546c2a1914c18f71622727997a2a7518ad2 Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Thu, 9 Feb 2023 22:07:07 -0800 Subject: [PATCH 082/185] fix: windows test compat fix: compiler warning on int qint32 compare Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- launcher/FileSystem.cpp | 5 ++--- tests/FileSystem_test.cpp | 20 ++++++++++++-------- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/launcher/FileSystem.cpp b/launcher/FileSystem.cpp index 57b796ad..c3a0c273 100644 --- a/launcher/FileSystem.cpp +++ b/launcher/FileSystem.cpp @@ -433,7 +433,7 @@ void create_link::runPrivlaged(const QString& offset) in >> numResults; qDebug() << "numResults" << numResults; - for(int i = 0; i < numResults; i++) { + for(quint32 i = 0; i < numResults; i++) { FS::LinkResult result; in >> result.src; in >> result.dst; @@ -484,7 +484,6 @@ void ExternalLinkFileProcess::runLinkFile() { #if defined Q_OS_WIN32 SHELLEXECUTEINFO ShExecInfo; - HRESULT hr; fileLinkExe = fileLinkExe + ".exe"; @@ -620,7 +619,7 @@ QString PathTruncate(const QString& path, int depth) if (parts.startsWith(".") && !path.startsWith(".")) { parts.removeFirst(); } - if (path.startsWith(QDir::separator())) { + if (QDir::toNativeSeparators(path).startsWith(QDir::separator())) { parts.prepend(""); } diff --git a/tests/FileSystem_test.cpp b/tests/FileSystem_test.cpp index ab78c150..169f0669 100644 --- a/tests/FileSystem_test.cpp +++ b/tests/FileSystem_test.cpp @@ -1,4 +1,5 @@ #include +#include #include #include @@ -769,14 +770,17 @@ slots: } void test_path_trunc() { - QCOMPARE(FS::PathTruncate("", 0), ""); - QCOMPARE(FS::PathTruncate("foo.txt", 0), ""); - QCOMPARE(FS::PathTruncate("foo.txt", 1), ""); - QCOMPARE(FS::PathTruncate("./bar/foo.txt", 0), "./bar"); - QCOMPARE(FS::PathTruncate("./bar/foo.txt", 1), "./bar"); - QCOMPARE(FS::PathTruncate("/bar/foo.txt", 1), "/bar"); - QCOMPARE(FS::PathTruncate("bar/foo.txt", 1), "bar"); - QCOMPARE(FS::PathTruncate("baz/bar/foo.txt", 2), "baz/bar"); + QCOMPARE(FS::PathTruncate("", 0), QDir::toNativeSeparators("")); + QCOMPARE(FS::PathTruncate("foo.txt", 0), QDir::toNativeSeparators("")); + QCOMPARE(FS::PathTruncate("foo.txt", 1), QDir::toNativeSeparators("")); + QCOMPARE(FS::PathTruncate("./bar/foo.txt", 0), QDir::toNativeSeparators("./bar")); + QCOMPARE(FS::PathTruncate("./bar/foo.txt", 1), QDir::toNativeSeparators("./bar")); + QCOMPARE(FS::PathTruncate("/bar/foo.txt", 1), QDir::toNativeSeparators("/bar")); + QCOMPARE(FS::PathTruncate("bar/foo.txt", 1), QDir::toNativeSeparators("bar")); + QCOMPARE(FS::PathTruncate("baz/bar/foo.txt", 2), QDir::toNativeSeparators("baz/bar")); +#if defined(Q_OS_WIN) + QCOMPARE(FS::PathTruncate("C:\\bar\\foo.txt", 1), QDir::toNativeSeparators("C:\\bar")); +#endif } }; From 2e8d04aad0d4fe3e742e4b29f4e23dc91b8ef838 Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Fri, 10 Feb 2023 05:34:48 -0800 Subject: [PATCH 083/185] feat: support reflink on windows via winbtrfs! https://github.com/maharmstone/btrfs Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- launcher/FileSystem.cpp | 200 ++++++++++++++++++++++++++++++++++---- launcher/FileSystem.h | 21 +++- launcher/InstanceList.cpp | 2 +- 3 files changed, 200 insertions(+), 23 deletions(-) diff --git a/launcher/FileSystem.cpp b/launcher/FileSystem.cpp index c3a0c273..4037d346 100644 --- a/launcher/FileSystem.cpp +++ b/launcher/FileSystem.cpp @@ -108,6 +108,13 @@ namespace fs = ghc::filesystem; #elif defined(Q_OS_MACOS) || defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD) #include #include +#elif defined(Q_OS_WIN) +// winbtrfs clone vs rundll32 shellbtrfs.dll,ReflinkCopy +#include +#include +#include +// refs +#include #endif namespace FS { @@ -916,25 +923,44 @@ bool overrideFolder(QString overwritten_path, QString override_path) return err.value() == 0; } +/** + * @brief path to the near ancestor that exsists + * + */ +QString NearestExistentAncestor(const QString& path) +{ + if(QFileInfo::exists(path)) return path; + + QDir dir(path); + if(!dir.makeAbsolute()) return {}; + do + { + dir.setPath(QDir::cleanPath(dir.filePath(QStringLiteral("..")))); + } + while(!dir.exists() && !dir.isRoot()); + + return dir.exists() ? dir.path() : QString(); +} /** * @brief colect information about the filesystem under a file * */ -FilesystemInfo statFS(QString path) +FilesystemInfo statFS(const QString& path) { FilesystemInfo info; - QStorageInfo storage_info(path); + QStorageInfo storage_info(NearestExistentAncestor(path)); QString fsTypeName = QString::fromStdString(storage_info.fileSystemType().toStdString()); + qDebug() << "Qt reports Filesystem at" << path << "root:" << storage_info.rootPath() << "as" << fsTypeName; for (auto fs_type_pair : s_filesystem_type_names_inverse.toStdMap()) { auto fs_type_name = fs_type_pair.first; auto fs_type = fs_type_pair.second; - if(fsTypeName.contains(fs_type_name.toLower())) { + if(fsTypeName.toLower().contains(fs_type_name.toLower())) { info.fsType = fs_type; break; } @@ -948,9 +974,6 @@ 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; } @@ -1054,15 +1077,156 @@ bool clone::operator()(const QString& offset, bool dryRun) */ bool clone_file(const QString& src, const QString& dst, std::error_code& ec) { - auto src_path = StringUtils::toStdString(QFileInfo(src).absoluteFilePath()); - auto dst_path = StringUtils::toStdString(QFileInfo(dst).absoluteFilePath()); + auto src_path = StringUtils::toStdString(QDir::toNativeSeparators(QFileInfo(src).absoluteFilePath())); + auto dst_path = StringUtils::toStdString(QDir::toNativeSeparators(QFileInfo(dst).absoluteFilePath())); #if defined(Q_OS_WIN) - qWarning("clone/reflink not supported on windows!"); - ec = std::make_error_code(std::errc::not_supported); - return false; + FilesystemInfo srcinfo = statFS(src); + if (srcinfo.fsType == FilesystemType::BTRFS) { + FilesystemInfo dstinfo = statFS(dst); + if (dstinfo.fsType != FilesystemType::BTRFS || (srcinfo.rootPath != dstinfo.rootPath)){ + qWarning() << "winbtrfs clone must be to the same device! src and dst root paths do not match."; + qWarning() << "check out https://github.com/maharmstone/btrfs for btrfs support!"; + ec = std::make_error_code(std::errc::not_supported); + return false; + } + + qWarning() << "clone/reflink of btrfs on windows! assuming winbtrfs is in use and calling shellbtrfs.dll via rundll32.exe"; + + if (!winbtrfs_clone(src_path, dst_path, ec)) + return false; + + // There is no return value from rundll32.exe so we must check if the file exsists ourselves + + QFileInfo dstInfo(dst); + if (!dstInfo.exists() || !dstInfo.isFile() || dstInfo.isSymLink()) { + // shellbtrfs.dll,ReflinkCopyW is curently broken https://github.com/maharmstone/btrfs/issues/556 + // lets try a little workaround + // find the misnamed file + qDebug() << dst << "is missing. ReflinkCopyW may still be broken, trying workaround."; + QString badDst = QDir(dstInfo.absolutePath()).path() + dstInfo.fileName(); + qDebug() << "trying" << badDst; + QFileInfo badDstInfo(badDst); + if (badDstInfo.exists() && badDstInfo.isFile()) { + qDebug() << badDst << "exists! moving it to the correct location."; + if(!move(badDstInfo.absoluteFilePath(), dstInfo.absoluteFilePath())) { + qDebug() << "move from" << badDst << "to" << dst << "failed"; + ec = std::make_error_code(std::errc::no_such_file_or_directory); + return false; + } + } else { + // oof, clone failure? + qWarning() << "clone/reflink on winbtrfs did not succeed: file" << dst << "does not appear to exsist"; + ec = std::make_error_code(std::errc::no_such_file_or_directory); + return false; + } + } + + } else if (srcinfo.fsType == FilesystemType::REFS) { + qWarning() << "clone/reflink not yet supported on windows ReFS!"; + ec = std::make_error_code(std::errc::not_supported); + return false; + } else { + qWarning() << "clone/reflink not supported on windows outside of winbtrfs or ReFS!"; + qWarning() << "check out https://github.com/maharmstone/btrfs for btrfs support!"; + ec = std::make_error_code(std::errc::not_supported); + return false; + } #elif defined(Q_OS_LINUX) + if(!linux_ficlone(src_path, dst_path, ec)) + return false; + +#elif defined(Q_OS_MACOS) || defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD) + + if(!macos_bsd_clonefile(src_path, dst_path, ec)) + return false; + +#else + qWarning() << "clone/reflink not supported! unknown OS"; + ec = std::make_error_code(std::errc::not_supported); + return false; +#endif + + return true; +} + +#if defined(Q_OS_WIN) +typedef void (__stdcall *f_ReflinkCopyW)(HWND hwnd, HINSTANCE hinst, LPWSTR lpszCmdLine, int nCmdShow); + +bool winbtrfs_clone(const std::wstring& src_path, const std::wstring& dst_path, std::error_code& ec) +{ + // https://github.com/maharmstone/btrfs + QString cmdLine = QString("\"%1\" \"%2\"").arg(src_path, dst_path); + + std::wstring wstr = cmdLine.toStdWString(); // temp buffer to copy the data and avoid side effect of non const cast + + LPWSTR cmdLineWin = (wchar_t*)wstr.c_str(); + + // https://github.com/maharmstone/btrfs/blob/9da54911dd6f3713a1c4c7be40338a3da126f4e6/src/shellext/contextmenu.cpp#L1609 + HINSTANCE shellbtrfsDLL = LoadLibrary(L"shellbtrfs.dll"); + + if (shellbtrfsDLL == NULL) { + ec = std::make_error_code(std::errc::not_supported); + qWarning() << "cannot locate the shellbtrfs.dll file, reflink copy not supported"; + return false; + } + + f_ReflinkCopyW ReflinkCopyW = (f_ReflinkCopyW)GetProcAddress(shellbtrfsDLL, "ReflinkCopyW"); + + if (!ReflinkCopyW) { + ec = std::make_error_code(std::errc::not_supported); + qWarning() << "cannot locate the ReflinkCopyW function from shellbtrfs.dll, reflink copy not supported"; + return false; + } + + qDebug() << "Calling ReflinkCopyW from shellbtrfs.dll with:" << cmdLine; + + ReflinkCopyW(0, 0, cmdLineWin, 1); + + FreeLibrary(shellbtrfsDLL); + + return true; +} + +bool refs_clone(const std::wstring& src_path, const std::wstring& dst_path, std::error_code& ec) +{ + //https://learn.microsoft.com/en-us/windows/win32/api/winioctl/ni-winioctl-fsctl_duplicate_extents_to_file + //https://github.com/microsoft/CopyOnWrite/blob/main/lib/Windows/WindowsCopyOnWriteFilesystem.cs#L94 + std::wstring existingFile = src_path.c_str(); + std::wstring newLink = dst_path.c_str(); + + + HANDLE hExistingFile = CreateFile(src_path.c_str(), GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL); + if (hExistingFile == INVALID_HANDLE_VALUE) + { + return false; + } + + HANDLE hNewFile = CreateFile(dst_path.c_str(), GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, 0, NULL); + if (hNewFile == INVALID_HANDLE_VALUE) + { + CloseHandle(hExistingFile); + return false; + } + + DWORD bytesReturned; + + // FIXME: ReFS requires that cloned regions reside on a disk cluster boundary. + // FIXME: ERROR_BLOCK_TOO_MANY_REFERENCES can occure https://docs.microsoft.com/en-us/windows-server/storage/refs/block-cloning#functionality-restrictions-and-remarks + BOOL result = DeviceIoControl(hExistingFile, FSCTL_DUPLICATE_EXTENTS_TO_FILE, hNewFile, sizeof(hNewFile), NULL, 0, &bytesReturned, NULL); + + CloseHandle(hNewFile); + CloseHandle(hExistingFile); + + return (result != 0); + +} + + +#elif defined(Q_OS_LINUX) +bool linux_ficlone(const std::string& src_path, const std::string& dst_path, 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); @@ -1097,9 +1261,12 @@ bool clone_file(const QString& src, const QString& dst, std::error_code& ec) qWarning() << "Failed to close file:" << dst_path.c_str(); qDebug() << "Error:" << strerror(errno); } + return true; +} #elif defined(Q_OS_MACOS) || defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD) - // TODO: use clonefile +bool macos_bsd_clonefile(const std::string& src_path, const std::string& dst_path, std::error_code& ec) +{ // clonefile(const char * src, const char * dst, int flags); // https://www.manpagez.com/man/2/clonefile/ @@ -1110,16 +1277,9 @@ bool clone_file(const QString& src, const QString& dst, std::error_code& ec) ec = std::make_error_code(static_cast(errno)); return false; } - -#else - qWarning("clone/reflink not supported! unknown OS"); - ec = std::make_error_code(std::errc::not_supported); - return false; -#endif - return true; } - +#endif /** * @brief if the Filesystem is symlink capable diff --git a/launcher/FileSystem.h b/launcher/FileSystem.h index 7485206a..3f6b78e5 100644 --- a/launcher/FileSystem.h +++ b/launcher/FileSystem.h @@ -344,6 +344,7 @@ bool createShortcut(QString destination, QString target, QStringList args, QStri enum class FilesystemType { FAT, NTFS, + REFS, EXT, EXT_2_OLD, EXT_2_3_4, @@ -363,6 +364,7 @@ enum class FilesystemType { 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("EXT2_OLD")}, {FilesystemType::EXT_2_3_4, QString("EXT2/3/4")}, @@ -382,6 +384,7 @@ static const QMap s_filesystem_type_names = { 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("EXT2"), FilesystemType::EXT_2_3_4}, {QString("EXT3"), FilesystemType::EXT_2_3_4}, @@ -414,15 +417,21 @@ struct FilesystemInfo { QString rootPath; }; +/** + * @brief path to the near ancestor that exsists + * + */ +QString NearestExistentAncestor(const QString& path); + /** * @brief colect information about the filesystem under a file * */ -FilesystemInfo statFS(QString path); +FilesystemInfo statFS(const QString& path); static const QList s_clone_filesystems = { - FilesystemType::BTRFS, FilesystemType::APFS, FilesystemType::ZFS, FilesystemType::XFS + FilesystemType::BTRFS, FilesystemType::APFS, FilesystemType::ZFS, FilesystemType::XFS, FilesystemType::REFS }; /** @@ -486,6 +495,14 @@ class clone : public QObject { */ bool clone_file(const QString& src, const QString& dst, std::error_code& ec); +#if defined(Q_OS_WIN) +bool winbtrfs_clone(const std::wstring& src_path, const std::wstring& dst_path, std::error_code& ec); +bool refs_clone(const std::wstring& src_path, const std::wstring& dst_path, std::error_code& ec); +#elif defined(Q_OS_LINUX) +bool linux_ficlone(const std::string& src_path, const std::string& dst_path, std::error_code& ec); +#elif defined(Q_OS_MACOS) || defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD) +bool macos_bsd_clonefile(const std::string& src_path, const std::string& dst_path, std::error_code& ec); +#endif static const QList s_non_link_filesystems = { FilesystemType::FAT, diff --git a/launcher/InstanceList.cpp b/launcher/InstanceList.cpp index 68e3e92c..1ca16ae9 100644 --- a/launcher/InstanceList.cpp +++ b/launcher/InstanceList.cpp @@ -865,7 +865,7 @@ Task* InstanceList::wrapInstanceTask(InstanceTask* task) QString InstanceList::getStagedInstancePath() { - QString key = QUuid::createUuid().toString(); + QString key = QUuid::createUuid().toString().remove("{").remove("}"); QString tempDir = ".LAUNCHER_TEMP/"; QString relPath = FS::PathCombine(tempDir, key); QDir rootPath(m_instDir); From 1210c3256d20133cc793b2a5b4f22f02e6519818 Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Fri, 10 Feb 2023 06:49:52 -0700 Subject: [PATCH 084/185] fix: macos compat after refactor of `clonefile` Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- launcher/FileSystem.cpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/launcher/FileSystem.cpp b/launcher/FileSystem.cpp index 4037d346..69e2d36a 100644 --- a/launcher/FileSystem.cpp +++ b/launcher/FileSystem.cpp @@ -1191,6 +1191,7 @@ bool winbtrfs_clone(const std::wstring& src_path, const std::wstring& dst_path, bool refs_clone(const std::wstring& src_path, const std::wstring& dst_path, std::error_code& ec) { +#if defined(FSCTL_DUPLICATE_EXTENTS_TO_FILE) //https://learn.microsoft.com/en-us/windows/win32/api/winioctl/ni-winioctl-fsctl_duplicate_extents_to_file //https://github.com/microsoft/CopyOnWrite/blob/main/lib/Windows/WindowsCopyOnWriteFilesystem.cs#L94 std::wstring existingFile = src_path.c_str(); @@ -1220,7 +1221,11 @@ bool refs_clone(const std::wstring& src_path, const std::wstring& dst_path, std: CloseHandle(hExistingFile); return (result != 0); - +#else + ec = std::make_error_code(std::errc::not_supported); + qWarning() << "not built with refs support"; + return false; +#endif } @@ -1270,7 +1275,7 @@ bool macos_bsd_clonefile(const std::string& src_path, const std::string& dst_pat // clonefile(const char * src, const char * dst, int flags); // https://www.manpagez.com/man/2/clonefile/ - qDebug() << "attempting file clone via clonefile" << src << "to" << dst; + qDebug() << "attempting file clone via clonefile" << src_path.c_str() << "to" << dst_path.c_str(); 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); From 9939367db7568249efd47701105323a4801a9413 Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Fri, 10 Feb 2023 16:41:48 -0800 Subject: [PATCH 085/185] feat(reflink): ioctl_clone for winbtrfs & ReFS Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- launcher/FileSystem.cpp | 356 ++++++++++++++++++++++++++++++++++++---- launcher/FileSystem.h | 10 ++ 2 files changed, 334 insertions(+), 32 deletions(-) diff --git a/launcher/FileSystem.cpp b/launcher/FileSystem.cpp index 69e2d36a..948ec55e 100644 --- a/launcher/FileSystem.cpp +++ b/launcher/FileSystem.cpp @@ -59,6 +59,7 @@ #include "StringUtils.h" #if defined Q_OS_WIN32 +#define NOMINMAX #define WIN32_LEAN_AND_MEAN #include #include @@ -113,6 +114,7 @@ namespace fs = ghc::filesystem; #include #include #include +#include // refs #include #endif @@ -953,14 +955,13 @@ FilesystemInfo statFS(const QString& path) QStorageInfo storage_info(NearestExistentAncestor(path)); - QString fsTypeName = QString::fromStdString(storage_info.fileSystemType().toStdString()); - qDebug() << "Qt reports Filesystem at" << path << "root:" << storage_info.rootPath() << "as" << fsTypeName; + info.fsTypeName = QString::fromStdString(storage_info.fileSystemType().toStdString()); for (auto fs_type_pair : s_filesystem_type_names_inverse.toStdMap()) { auto fs_type_name = fs_type_pair.first; auto fs_type = fs_type_pair.second; - if(fsTypeName.toLower().contains(fs_type_name.toLower())) { + if(info.fsTypeName.toLower().contains(fs_type_name.toLower())) { info.fsType = fs_type; break; } @@ -1043,7 +1044,7 @@ bool clone::operator()(const QString& offset, bool dryRun) clone_file(src_path, dst_path, err); } if (err) { - qWarning() << "Failed to clone files:" << QString::fromStdString(err.message()); + qDebug() << "Failed to clone files:" << QString::fromStdString(err.message()); qDebug() << "Source file:" << src_path; qDebug() << "Destination file:" << dst_path; } @@ -1082,8 +1083,23 @@ bool clone_file(const QString& src, const QString& dst, std::error_code& ec) #if defined(Q_OS_WIN) FilesystemInfo srcinfo = statFS(src); - if (srcinfo.fsType == FilesystemType::BTRFS) { - FilesystemInfo dstinfo = statFS(dst); + FilesystemInfo dstinfo = statFS(dst); + + if (((srcinfo.fsType == FilesystemType::BTRFS && dstinfo.fsType == FilesystemType::BTRFS) || + (srcinfo.fsType == FilesystemType::REFS && dstinfo.fsType == FilesystemType::REFS)) && + USE_IOCTL_CLONE) + { + if (srcinfo.rootPath != dstinfo.rootPath) { + qWarning() << "clones must be to the same device! src and dst root paths do not match."; + ec = std::make_error_code(std::errc::not_supported); + return false; + } + + qDebug() << "ioctl clone" << src << "to" << dst; + if (!ioctl_clone(src_path, dst_path, ec)) + return false; + + } else if (srcinfo.fsType == FilesystemType::BTRFS) { if (dstinfo.fsType != FilesystemType::BTRFS || (srcinfo.rootPath != dstinfo.rootPath)){ qWarning() << "winbtrfs clone must be to the same device! src and dst root paths do not match."; qWarning() << "check out https://github.com/maharmstone/btrfs for btrfs support!"; @@ -1091,12 +1107,12 @@ bool clone_file(const QString& src, const QString& dst, std::error_code& ec) return false; } - qWarning() << "clone/reflink of btrfs on windows! assuming winbtrfs is in use and calling shellbtrfs.dll via rundll32.exe"; + qDebug() << "clone/reflink of btrfs on windows! assuming winbtrfs is in use and calling shellbtrfs.dll,ReflinkCopyW"; if (!winbtrfs_clone(src_path, dst_path, ec)) return false; - // There is no return value from rundll32.exe so we must check if the file exsists ourselves + // There is no return value from ReflinkCopyW so we must check if the file exsists ourselves QFileInfo dstInfo(dst); if (!dstInfo.exists() || !dstInfo.isFile() || dstInfo.isSymLink()) { @@ -1116,16 +1132,24 @@ bool clone_file(const QString& src, const QString& dst, std::error_code& ec) } } else { // oof, clone failure? - qWarning() << "clone/reflink on winbtrfs did not succeed: file" << dst << "does not appear to exsist"; + qDebug() << "clone/reflink on winbtrfs did not succeed: file" << dst << "does not appear to exist"; ec = std::make_error_code(std::errc::no_such_file_or_directory); return false; } } } else if (srcinfo.fsType == FilesystemType::REFS) { - qWarning() << "clone/reflink not yet supported on windows ReFS!"; - ec = std::make_error_code(std::errc::not_supported); - return false; + if (dstinfo.fsType != FilesystemType::REFS || (srcinfo.rootPath != dstinfo.rootPath)){ + qWarning() << "ReFS clone must be to the same device! src and dst root paths do not match."; + ec = std::make_error_code(std::errc::not_supported); + return false; + } + + qDebug() << "clone/reflink of ReFS on windows!"; + + if (!refs_clone(src_path, dst_path, ec)) + return false; + } else { qWarning() << "clone/reflink not supported on windows outside of winbtrfs or ReFS!"; qWarning() << "check out https://github.com/maharmstone/btrfs for btrfs support!"; @@ -1154,6 +1178,14 @@ bool clone_file(const QString& src, const QString& dst, std::error_code& ec) #if defined(Q_OS_WIN) typedef void (__stdcall *f_ReflinkCopyW)(HWND hwnd, HINSTANCE hinst, LPWSTR lpszCmdLine, int nCmdShow); +const long WinMaxChunkSize = 1L << 31; // 2GB + +static long RoundUpToPowerOf2(long originalValue, long roundingMultiplePowerOf2) +{ + long mask = roundingMultiplePowerOf2 - 1; + return (originalValue + mask) & ~mask; +} + bool winbtrfs_clone(const std::wstring& src_path, const std::wstring& dst_path, std::error_code& ec) { // https://github.com/maharmstone/btrfs @@ -1194,33 +1226,118 @@ bool refs_clone(const std::wstring& src_path, const std::wstring& dst_path, std: #if defined(FSCTL_DUPLICATE_EXTENTS_TO_FILE) //https://learn.microsoft.com/en-us/windows/win32/api/winioctl/ni-winioctl-fsctl_duplicate_extents_to_file //https://github.com/microsoft/CopyOnWrite/blob/main/lib/Windows/WindowsCopyOnWriteFilesystem.cs#L94 - std::wstring existingFile = src_path.c_str(); - std::wstring newLink = dst_path.c_str(); + QString qSourcePath = StringUtils::fromStdString(src_path); + QString sourceVolumePath = statFS(qSourcePath).rootPath; + std::wstring source_volume_path = StringUtils::toStdString(sourceVolumePath); + unsigned long sectorsPerCluster; + unsigned long bytesPerSector; + unsigned long numberOfFreeClusters; + unsigned long totalNumberOfClusters; + + if(!GetDiskFreeSpace(source_volume_path.c_str(), §orsPerCluster, &bytesPerSector, &numberOfFreeClusters, &totalNumberOfClusters )){ + ec = std::error_code(GetLastError(), std::system_category()); + qDebug() << "Failed to get disk info for source volume" << sourceVolumePath; + return false; + } + + long srcClusterSize = (long)(sectorsPerCluster * bytesPerSector); - HANDLE hExistingFile = CreateFile(src_path.c_str(), GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL); - if (hExistingFile == INVALID_HANDLE_VALUE) + HANDLE hSourceFile = CreateFile(src_path.c_str(), GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL); + if (hSourceFile == INVALID_HANDLE_VALUE) { + ec = std::error_code(GetLastError(), std::system_category()); + qDebug() << "Failed to open source file" << src_path.c_str(); return false; } - HANDLE hNewFile = CreateFile(dst_path.c_str(), GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, 0, NULL); - if (hNewFile == INVALID_HANDLE_VALUE) + HANDLE hDestFile = CreateFile(dst_path.c_str(), GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, 0, NULL); + if (hDestFile == INVALID_HANDLE_VALUE) { - CloseHandle(hExistingFile); + ec = std::error_code(GetLastError(), std::system_category()); + CloseHandle(hSourceFile); + qDebug() << "Failed to open dest file" << dst_path.c_str(); return false; } - DWORD bytesReturned; + DWORD bytesReturned = 0; - // FIXME: ReFS requires that cloned regions reside on a disk cluster boundary. - // FIXME: ERROR_BLOCK_TOO_MANY_REFERENCES can occure https://docs.microsoft.com/en-us/windows-server/storage/refs/block-cloning#functionality-restrictions-and-remarks - BOOL result = DeviceIoControl(hExistingFile, FSCTL_DUPLICATE_EXTENTS_TO_FILE, hNewFile, sizeof(hNewFile), NULL, 0, &bytesReturned, NULL); + // Set the destination to be sparse while we clone. + // Important to avoid allocating zero-backed real storage when calling SetFileInformationByHandle() + // below which will just be released when cloning file extents. + + if (!DeviceIoControl(hDestFile, FSCTL_SET_SPARSE, nullptr, 0, nullptr, 0, &bytesReturned, nullptr)) { + qDebug() << "Failed to set file sparseness for destination file" << dst_path.c_str(); + ec = std::error_code(GetLastError(), std::system_category()); + return false; + } - CloseHandle(hNewFile); - CloseHandle(hExistingFile); + LARGE_INTEGER sourceFileLengthStruct; + if (!GetFileSizeEx(hSourceFile, &sourceFileLengthStruct)) { + ec = std::error_code(GetLastError(), std::system_category()); + qDebug() << "Failed to get file info for source file" << src_path.c_str(); + return false; + } - return (result != 0); + long sourceFileLength = sourceFileLengthStruct.QuadPart; + + // Set the destination on-disk size the same as the source. + FILE_END_OF_FILE_INFO fileSizeInfo{sourceFileLength}; + if (!SetFileInformationByHandle(hDestFile, FILE_INFO_BY_HANDLE_CLASS::FileEndOfFileInfo, + &fileSizeInfo, sizeof(FILE_END_OF_FILE_INFO))) + { + ec = std::error_code(GetLastError(), std::system_category()); + qDebug() << "Failed to set end of file on destination file" << dst_path.c_str(); + return false; + } + + DUPLICATE_EXTENTS_DATA duplicateExtentsData = DUPLICATE_EXTENTS_DATA{ hSourceFile }; + + long fileSizeRoundedUpToClusterBoundary = RoundUpToPowerOf2(sourceFileLength, srcClusterSize); + long sourceOffset = 0; + while(sourceOffset < sourceFileLength) + { + duplicateExtentsData.SourceFileOffset.QuadPart = sourceOffset; + duplicateExtentsData.TargetFileOffset.QuadPart = sourceOffset; + long thisChunkSize = std::min(fileSizeRoundedUpToClusterBoundary - sourceOffset, WinMaxChunkSize); + duplicateExtentsData.ByteCount.QuadPart = thisChunkSize; + + DWORD numBytesReturned = 0; + bool ioctlResult = DeviceIoControl( + hDestFile, + FSCTL_DUPLICATE_EXTENTS_TO_FILE, + &duplicateExtentsData, + sizeof(DUPLICATE_EXTENTS_DATA), + nullptr, + 0, + &numBytesReturned, + nullptr); + if (!ioctlResult) + { + DWORD err = GetLastError(); + ec = std::error_code(err, std::system_category()); + QString additionalMessage; + if (err == ERROR_BLOCK_TOO_MANY_REFERENCES) + { + static const int MaxClonesPerFile = 8175; + additionalMessage = QString( + " This is ERROR_BLOCK_TOO_MANY_REFERENCES and may mean you have surpassed the maximum " + "allowed %1 references for a single file. " + "See https://docs.microsoft.com/en-us/windows-server/storage/refs/block-cloning#functionality-restrictions-and-remarks" + ).arg(MaxClonesPerFile); + + } + qWarning() << "Failed copy-on-write cloning from source file" << src_path.c_str() << "to" << dst_path.c_str() << "." << additionalMessage; + return false; + } + + sourceOffset += thisChunkSize; + } + + CloseHandle(hDestFile); + CloseHandle(hSourceFile); + + return true; #else ec = std::make_error_code(std::errc::not_supported); qWarning() << "not built with refs support"; @@ -1228,6 +1345,181 @@ bool refs_clone(const std::wstring& src_path, const std::wstring& dst_path, std: #endif } +bool ioctl_clone(const std::wstring& src_path, const std::wstring& dst_path, std::error_code& ec) +{ + /** + * This algorithm inspired from https://github.com/0xbadfca11/reflink + * LICENSE MIT + * + */ + + HANDLE hSourceFile = CreateFileW(src_path.c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, 0, nullptr); + if (hSourceFile == INVALID_HANDLE_VALUE) + { + ec = std::error_code(GetLastError(), std::system_category()); + qDebug() << "Failed to open source file" << src_path.c_str(); + return false; + } + + ULONG fs_flags; + if (!GetVolumeInformationByHandleW(hSourceFile, nullptr, 0, nullptr, nullptr, &fs_flags, nullptr, 0)) + { + ec = std::error_code(GetLastError(), std::system_category()); + qDebug() << "Failed to get Filesystem information for " << src_path.c_str(); + CloseHandle(hSourceFile); + return false; + } + if (!(fs_flags & FILE_SUPPORTS_BLOCK_REFCOUNTING)) + { + SetLastError(ERROR_NOT_CAPABLE); + ec = std::error_code(GetLastError(), std::system_category()); + qWarning() << "Filesystem at " << src_path.c_str() << " does not support reflink"; + CloseHandle(hSourceFile); + return false; + } + + FILE_END_OF_FILE_INFO sourceFileLength; + if (!GetFileSizeEx(hSourceFile, &sourceFileLength.EndOfFile)) + { + ec = std::error_code(GetLastError(), std::system_category()); + qDebug() << "Failed to size of source file" << src_path.c_str(); + CloseHandle(hSourceFile); + return false; + } + FILE_BASIC_INFO sourceFileBasicInfo; + if (!GetFileInformationByHandleEx(hSourceFile, FileBasicInfo, &sourceFileBasicInfo, sizeof(sourceFileBasicInfo))) + { + ec = std::error_code(GetLastError(), std::system_category()); + qDebug() << "Failed to source file info" << src_path.c_str(); + CloseHandle(hSourceFile); + return false; + } + ULONG junk; + FSCTL_GET_INTEGRITY_INFORMATION_BUFFER sourceFileIntegrity; + if (!DeviceIoControl(hSourceFile, FSCTL_GET_INTEGRITY_INFORMATION, nullptr, 0, &sourceFileIntegrity, sizeof(sourceFileIntegrity), &junk, nullptr)) + { + ec = std::error_code(GetLastError(), std::system_category()); + qDebug() << "Failed to source file integrity info" << src_path.c_str(); + CloseHandle(hSourceFile); + return false; + } + + HANDLE hDestFile = CreateFileW(dst_path.c_str(), GENERIC_READ | GENERIC_WRITE | DELETE, 0, nullptr, CREATE_NEW, 0, hSourceFile); + + if (hDestFile == INVALID_HANDLE_VALUE) + { + ec = std::error_code(GetLastError(), std::system_category()); + qDebug() << "Failed to open dest file" << dst_path.c_str(); + CloseHandle(hSourceFile); + return false; + } + FILE_DISPOSITION_INFO destFileDispose = { TRUE }; + if (!SetFileInformationByHandle(hDestFile, FileDispositionInfo, &destFileDispose, sizeof(destFileDispose))) + { + ec = std::error_code(GetLastError(), std::system_category()); + qDebug() << "Failed to set dest file info" << dst_path.c_str(); + CloseHandle(hSourceFile); + CloseHandle(hDestFile); + return false; + } + + if (!DeviceIoControl(hDestFile, FSCTL_SET_SPARSE, nullptr, 0, nullptr, 0, &junk, nullptr)) + { + ec = std::error_code(GetLastError(), std::system_category()); + qDebug() << "Failed to set dest sparseness" << dst_path.c_str(); + CloseHandle(hSourceFile); + CloseHandle(hDestFile); + return false; + } + FSCTL_SET_INTEGRITY_INFORMATION_BUFFER setDestFileintegrity = { sourceFileIntegrity.ChecksumAlgorithm, sourceFileIntegrity.Reserved, sourceFileIntegrity.Flags }; + if (!DeviceIoControl(hDestFile, FSCTL_SET_INTEGRITY_INFORMATION, &setDestFileintegrity, sizeof(setDestFileintegrity), nullptr, 0, nullptr, nullptr)) + { + ec = std::error_code(GetLastError(), std::system_category()); + qDebug() << "Failed to set dest file integrity info" << dst_path.c_str(); + CloseHandle(hSourceFile); + CloseHandle(hDestFile); + return false; + } + if (!SetFileInformationByHandle(hDestFile, FileEndOfFileInfo, &sourceFileLength, sizeof(sourceFileLength))) + { + ec = std::error_code(GetLastError(), std::system_category()); + qDebug() << "Failed to set dest file size" << dst_path.c_str(); + CloseHandle(hSourceFile); + CloseHandle(hDestFile); + return false; + } + + const LONG64 splitThreshold = (1LL << 32) - sourceFileIntegrity.ClusterSizeInBytes; + + DUPLICATE_EXTENTS_DATA dupExtent; + dupExtent.FileHandle = hSourceFile; + for (LONG64 offset = 0, remain = RoundUpToPowerOf2(sourceFileLength.EndOfFile.QuadPart, sourceFileIntegrity.ClusterSizeInBytes); remain > 0; offset += splitThreshold, remain -= splitThreshold) + { + dupExtent.SourceFileOffset.QuadPart = dupExtent.TargetFileOffset.QuadPart = offset; + dupExtent.ByteCount.QuadPart = std::min(splitThreshold, remain); + + _ASSERTE(dupExtent.SourceFileOffset.QuadPart % sourceFileIntegrity.ClusterSizeInBytes == 0); + _ASSERTE(dupExtent.ByteCount.QuadPart % sourceFileIntegrity.ClusterSizeInBytes == 0); + _ASSERTE(dupExtent.ByteCount.QuadPart <= UINT32_MAX); + _RPT3(_CRT_WARN, "Remain=%llx\nOffset=%llx\nLength=%llx\n\n", remain, dupExtent.SourceFileOffset.QuadPart, dupExtent.ByteCount.QuadPart); + + if (!DeviceIoControl(hDestFile, FSCTL_DUPLICATE_EXTENTS_TO_FILE, &dupExtent, sizeof(dupExtent), nullptr, 0, &junk, nullptr)) + { + DWORD err = GetLastError(); + QString additionalMessage; + if (err == ERROR_BLOCK_TOO_MANY_REFERENCES) + { + static const int MaxClonesPerFile = 8175; + additionalMessage = QString( + " This is ERROR_BLOCK_TOO_MANY_REFERENCES and may mean you have surpassed the maximum " + "allowed %1 references for a single file. " + "See https://docs.microsoft.com/en-us/windows-server/storage/refs/block-cloning#functionality-restrictions-and-remarks" + ).arg(MaxClonesPerFile); + + } + ec = std::error_code(err, std::system_category()); + qDebug() << "Failed copy-on-write cloning of" << src_path.c_str() << "to" << dst_path.c_str() << "with error" << err << additionalMessage; + CloseHandle(hSourceFile); + CloseHandle(hDestFile); + return false; + } + } + + if (!(sourceFileBasicInfo.FileAttributes & FILE_ATTRIBUTE_SPARSE_FILE)) + { + FILE_SET_SPARSE_BUFFER setDestSparse = { FALSE }; + if (!DeviceIoControl(hDestFile, FSCTL_SET_SPARSE, &setDestSparse, sizeof(setDestSparse), nullptr, 0, &junk, nullptr)) + { + qDebug() << "Failed to set dest file sparseness" << dst_path.c_str(); + CloseHandle(hSourceFile); + CloseHandle(hDestFile); + return false; + } + } + + sourceFileBasicInfo.CreationTime.QuadPart = 0; + if (!SetFileInformationByHandle(hDestFile, FileBasicInfo, &sourceFileBasicInfo, sizeof(sourceFileBasicInfo))) + { + qDebug() << "Failed to set dest file creation time" << dst_path.c_str(); + CloseHandle(hSourceFile); + CloseHandle(hDestFile); + return false; + } + if (!FlushFileBuffers(hDestFile)) + { + qDebug() << "Failed to flush dest file buffer" << dst_path.c_str(); + CloseHandle(hSourceFile); + CloseHandle(hDestFile); + return false; + } + destFileDispose = { FALSE }; + bool result = !!SetFileInformationByHandle(hDestFile, FileDispositionInfo, &destFileDispose, sizeof(destFileDispose)); + + CloseHandle(hSourceFile); + CloseHandle(hDestFile); + + return result; +} #elif defined(Q_OS_LINUX) bool linux_ficlone(const std::string& src_path, const std::string& dst_path, std::error_code& ec) @@ -1236,14 +1528,14 @@ bool linux_ficlone(const std::string& src_path, const std::string& dst_path, std int src_fd = open(src_path.c_str(), O_RDONLY); if(src_fd == -1) { - qWarning() << "Failed to open file:" << src_path.c_str(); + qDebug() << "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_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() << "Failed to open file:" << dst_path.c_str(); qDebug() << "Error:" << strerror(errno); ec = std::make_error_code(static_cast(errno)); close(src_fd); @@ -1251,7 +1543,7 @@ bool linux_ficlone(const std::string& src_path, const std::string& dst_path, std } // attempt to clone if(ioctl(dst_fd, FICLONE, src_fd) == -1){ - qWarning() << "Failed to clone file:" << src_path.c_str() << "to" << dst_path.c_str(); + qDebug() << "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); @@ -1259,11 +1551,11 @@ bool linux_ficlone(const std::string& src_path, const std::string& dst_path, std return false; } if(close(src_fd)) { - qWarning() << "Failed to close file:" << src_path.c_str(); + qDebug() << "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() << "Failed to close file:" << dst_path.c_str(); qDebug() << "Error:" << strerror(errno); } return true; @@ -1277,7 +1569,7 @@ bool macos_bsd_clonefile(const std::string& src_path, const std::string& dst_pat qDebug() << "attempting file clone via clonefile" << src_path.c_str() << "to" << dst_path.c_str(); 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() << "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)); return false; diff --git a/launcher/FileSystem.h b/launcher/FileSystem.h index 3f6b78e5..4c011309 100644 --- a/launcher/FileSystem.h +++ b/launcher/FileSystem.h @@ -409,6 +409,7 @@ inline QString getFilesystemTypeName(FilesystemType type) { struct FilesystemInfo { FilesystemType fsType = FilesystemType::UNKNOWN; + QString fsTypeName; int blockSize; qint64 bytesAvailable; qint64 bytesFree; @@ -496,8 +497,17 @@ class clone : public QObject { bool clone_file(const QString& src, const QString& dst, std::error_code& ec); #if defined(Q_OS_WIN) + +#ifndef FSCTL_DUPLICATE_EXTENTS_TO_FILE +#define FSCTL_DUPLICATE_EXTENTS_TO_FILE CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 209, METHOD_BUFFERED, FILE_WRITE_DATA ) +#endif + bool winbtrfs_clone(const std::wstring& src_path, const std::wstring& dst_path, std::error_code& ec); bool refs_clone(const std::wstring& src_path, const std::wstring& dst_path, std::error_code& ec); +bool ioctl_clone(const std::wstring& src_path, const std::wstring& dst_path, std::error_code& ec); + +#define USE_IOCTL_CLONE true + #elif defined(Q_OS_LINUX) bool linux_ficlone(const std::string& src_path, const std::string& dst_path, std::error_code& ec); #elif defined(Q_OS_MACOS) || defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD) From 7870cf28e55c090543591304b05a7ef5031e1157 Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Fri, 10 Feb 2023 19:06:49 -0800 Subject: [PATCH 086/185] fix: add missing mingw defs Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- launcher/FileSystem.cpp | 39 ++++++++++++++++++++++++++++++++++++++- launcher/FileSystem.h | 4 ---- 2 files changed, 38 insertions(+), 5 deletions(-) diff --git a/launcher/FileSystem.cpp b/launcher/FileSystem.cpp index 948ec55e..a9461bb0 100644 --- a/launcher/FileSystem.cpp +++ b/launcher/FileSystem.cpp @@ -117,8 +117,45 @@ namespace fs = ghc::filesystem; #include // refs #include +# if defined(__MINGW32__) +# include +# endif #endif +#if defined(Q_OS_WIN) && defined(__MINGW32__) + +typedef struct _DUPLICATE_EXTENTS_DATA { + HANDLE FileHandle; + LARGE_INTEGER SourceFileOffset; + LARGE_INTEGER TargetFileOffset; + LARGE_INTEGER ByteCount; +} DUPLICATE_EXTENTS_DATA, *PDUPLICATE_EXTENTS_DATA; + +typedef struct _FSCTL_GET_INTEGRITY_INFORMATION_BUFFER { + WORD ChecksumAlgorithm; // Checksum algorithm. e.g. CHECKSUM_TYPE_UNCHANGED, CHECKSUM_TYPE_NONE, CHECKSUM_TYPE_CRC32 + WORD Reserved; // Must be 0 + DWORD Flags; // FSCTL_INTEGRITY_FLAG_xxx + DWORD ChecksumChunkSizeInBytes; + DWORD ClusterSizeInBytes; +} FSCTL_GET_INTEGRITY_INFORMATION_BUFFER, *PFSCTL_GET_INTEGRITY_INFORMATION_BUFFER; + +typedef struct _FSCTL_SET_INTEGRITY_INFORMATION_BUFFER { + WORD ChecksumAlgorithm; // Checksum algorithm. e.g. CHECKSUM_TYPE_UNCHANGED, CHECKSUM_TYPE_NONE, CHECKSUM_TYPE_CRC32 + WORD Reserved; // Must be 0 + DWORD Flags; // FSCTL_INTEGRITY_FLAG_xxx +} FSCTL_SET_INTEGRITY_INFORMATION_BUFFER, *PFSCTL_SET_INTEGRITY_INFORMATION_BUFFER; + +#define FSCTL_DUPLICATE_EXTENTS_TO_FILE CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 209, METHOD_BUFFERED, FILE_WRITE_DATA ) +#define FSCTL_GET_INTEGRITY_INFORMATION CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 159, METHOD_BUFFERED, FILE_ANY_ACCESS) // FSCTL_GET_INTEGRITY_INFORMATION_BUFFER +#define FSCTL_SET_INTEGRITY_INFORMATION CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 160, METHOD_BUFFERED, FILE_READ_DATA | FILE_WRITE_DATA) // FSCTL_SET_INTEGRITY_INFORMATION_BUFFER + + +#define ERROR_NOT_CAPABLE 775L +#define ERROR_BLOCK_TOO_MANY_REFERENCES 347L + +#endif + + namespace FS { void ensureExists(const QDir& dir) @@ -1279,7 +1316,7 @@ bool refs_clone(const std::wstring& src_path, const std::wstring& dst_path, std: return false; } - long sourceFileLength = sourceFileLengthStruct.QuadPart; + DWORD sourceFileLength = sourceFileLengthStruct.QuadPart; // Set the destination on-disk size the same as the source. FILE_END_OF_FILE_INFO fileSizeInfo{sourceFileLength}; diff --git a/launcher/FileSystem.h b/launcher/FileSystem.h index 4c011309..cafbd2a8 100644 --- a/launcher/FileSystem.h +++ b/launcher/FileSystem.h @@ -498,10 +498,6 @@ bool clone_file(const QString& src, const QString& dst, std::error_code& ec); #if defined(Q_OS_WIN) -#ifndef FSCTL_DUPLICATE_EXTENTS_TO_FILE -#define FSCTL_DUPLICATE_EXTENTS_TO_FILE CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 209, METHOD_BUFFERED, FILE_WRITE_DATA ) -#endif - bool winbtrfs_clone(const std::wstring& src_path, const std::wstring& dst_path, std::error_code& ec); bool refs_clone(const std::wstring& src_path, const std::wstring& dst_path, std::error_code& ec); bool ioctl_clone(const std::wstring& src_path, const std::wstring& dst_path, std::error_code& ec); 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: [PATCH 087/185] 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(-) 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. + From a1053a4c5ac8651be3b57814918e28179eb2a1f9 Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Sun, 12 Feb 2023 02:44:39 -0700 Subject: [PATCH 088/185] feat: warnings when instance resources are linked Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- launcher/FileSystem.cpp | 4 +++ launcher/FileSystem.h | 2 ++ launcher/minecraft/World.cpp | 24 +++++++++++++++ launcher/minecraft/World.h | 15 ++++++++++ launcher/minecraft/WorldList.cpp | 30 +++++++++++++++++-- launcher/minecraft/WorldList.h | 5 +++- launcher/minecraft/mod/ModFolderModel.cpp | 20 +++++++++++++ launcher/minecraft/mod/Resource.cpp | 20 +++++++++++++ launcher/minecraft/mod/Resource.h | 13 ++++++++ .../minecraft/mod/ResourceFolderModel.cpp | 26 ++++++++++++++++ launcher/minecraft/mod/ResourceFolderModel.h | 2 ++ .../minecraft/mod/ResourcePackFolderModel.cpp | 20 +++++++++++++ launcher/ui/pages/instance/WorldListPage.cpp | 1 + 13 files changed, 179 insertions(+), 3 deletions(-) diff --git a/launcher/FileSystem.cpp b/launcher/FileSystem.cpp index a9461bb0..af2ba299 100644 --- a/launcher/FileSystem.cpp +++ b/launcher/FileSystem.cpp @@ -1641,5 +1641,9 @@ bool canLink(const QString& src, const QString& dst) return canLinkOnFS(src) && canLinkOnFS(dst); } +uintmax_t hardLinkCount(const QString& path) +{ + return fs::hard_link_count(StringUtils::toStdString(path)); +} } diff --git a/launcher/FileSystem.h b/launcher/FileSystem.h index cafbd2a8..361993eb 100644 --- a/launcher/FileSystem.h +++ b/launcher/FileSystem.h @@ -528,4 +528,6 @@ bool canLinkOnFS(FilesystemType type); */ bool canLink(const QString& src, const QString& dst); +uintmax_t hardLinkCount(const QString& path); + } diff --git a/launcher/minecraft/World.cpp b/launcher/minecraft/World.cpp index d310f8b9..54fb9434 100644 --- a/launcher/minecraft/World.cpp +++ b/launcher/minecraft/World.cpp @@ -56,6 +56,8 @@ #include +#include "FileSystem.h" + using std::optional; using std::nullopt; @@ -567,3 +569,25 @@ bool World::operator==(const World &other) const { return is_valid == other.is_valid && folderName() == other.folderName(); } + +bool World::isSymLinkUnder(const QString& instPath) const +{ + if (isSymLink()) + return true; + + auto instDir = QDir(instPath); + + auto relAbsPath = instDir.relativeFilePath(m_containerFile.absoluteFilePath()); + auto relCanonPath = instDir.relativeFilePath(m_containerFile.canonicalFilePath()); + + return relAbsPath != relCanonPath; +} + +bool World::isMoreThanOneHardLink() const +{ + if (m_containerFile.isDir()) + { + return FS::hardLinkCount(QDir(m_containerFile.absoluteFilePath()).filePath("level.dat")) > 1; + } + return FS::hardLinkCount(m_containerFile.absoluteFilePath()) > 1; +} diff --git a/launcher/minecraft/World.h b/launcher/minecraft/World.h index 8327253a..10328cce 100644 --- a/launcher/minecraft/World.h +++ b/launcher/minecraft/World.h @@ -95,6 +95,21 @@ public: // WEAK compare operator - used for replacing worlds bool operator==(const World &other) const; + [[nodiscard]] auto isSymLink() const -> bool{ return m_containerFile.isSymLink(); } + + /** + * @brief Take a instance path, checks if the file pointed to by the resource is a symlink or under a symlink in that instance + * + * @param instPath path to an instance directory + * @return true + * @return false + */ + [[nodiscard]] bool isSymLinkUnder(const QString& instPath) const; + + [[nodiscard]] bool isMoreThanOneHardLink() const; + + QString canonicalFilePath() const { return m_containerFile.canonicalFilePath(); } + private: void readFromZip(const QFileInfo &file); void readFromFS(const QFileInfo &file); diff --git a/launcher/minecraft/WorldList.cpp b/launcher/minecraft/WorldList.cpp index ae29a972..1262fa1d 100644 --- a/launcher/minecraft/WorldList.cpp +++ b/launcher/minecraft/WorldList.cpp @@ -128,6 +128,10 @@ bool WorldList::isValid() return m_dir.exists() && m_dir.isReadable(); } +QString WorldList::instDirPath() const { + return QFileInfo(m_dir.filePath("../..")).absoluteFilePath(); +} + bool WorldList::deleteWorld(int index) { if (index >= worlds.size() || index < 0) @@ -173,7 +177,7 @@ bool WorldList::resetIcon(int row) int WorldList::columnCount(const QModelIndex &parent) const { - return parent.isValid()? 0 : 4; + return parent.isValid()? 0 : 5; } QVariant WorldList::data(const QModelIndex &index, int role) const @@ -207,6 +211,14 @@ QVariant WorldList::data(const QModelIndex &index, int role) const case SizeColumn: return locale.formattedDataSize(world.bytes()); + case InfoColumn: + if (world.isSymLinkUnder(instDirPath())) { + return tr("This world is symbolicly linked from elsewhere."); + } + if (world.isMoreThanOneHardLink()) { + return tr("\nThis world is hard linked elsewhere."); + } + return ""; default: return QVariant(); } @@ -222,7 +234,16 @@ QVariant WorldList::data(const QModelIndex &index, int role) const } case Qt::ToolTipRole: - { + { + if (column == InfoColumn) { + if (world.isSymLinkUnder(instDirPath())) { + return tr("Warning: This world is symbolicly linked from elsewhere. Editing it will also change the origonal") + + 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 world.folderName(); } case ObjectRole: @@ -274,6 +295,9 @@ QVariant WorldList::headerData(int section, Qt::Orientation orientation, int rol case SizeColumn: //: World size on disk return tr("Size"); + case InfoColumn: + //: special warnings? + return tr("Info"); default: return QVariant(); } @@ -289,6 +313,8 @@ QVariant WorldList::headerData(int section, Qt::Orientation orientation, int rol return tr("Date and time the world was last played."); case SizeColumn: return tr("Size of the world on disk."); + case InfoColumn: + return tr("Information and warnings about the world."); default: return QVariant(); } diff --git a/launcher/minecraft/WorldList.h b/launcher/minecraft/WorldList.h index 08294755..bd32dd4e 100644 --- a/launcher/minecraft/WorldList.h +++ b/launcher/minecraft/WorldList.h @@ -33,7 +33,8 @@ public: NameColumn, GameModeColumn, LastPlayedColumn, - SizeColumn + SizeColumn, + InfoColumn }; enum Roles @@ -112,6 +113,8 @@ public: return m_dir; } + QString instDirPath() const; + const QList &allWorlds() const { return worlds; diff --git a/launcher/minecraft/mod/ModFolderModel.cpp b/launcher/minecraft/mod/ModFolderModel.cpp index 3f31b93c..e2053f92 100644 --- a/launcher/minecraft/mod/ModFolderModel.cpp +++ b/launcher/minecraft/mod/ModFolderModel.cpp @@ -39,13 +39,17 @@ #include #include #include +#include #include #include +#include #include #include #include #include +#include "Application.h" + #include "minecraft/mod/tasks/LocalModParseTask.h" #include "minecraft/mod/tasks/ModFolderLoadTask.h" #include "modplatform/ModIndex.h" @@ -97,8 +101,24 @@ QVariant ModFolderModel::data(const QModelIndex &index, int role) const } case Qt::ToolTipRole: + 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("\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"); + } + } return m_resources[row]->internal_id(); + case Qt::DecorationRole: { + if (column == NAME_COLUMN && (at(row)->isSymLinkUnder(instDirPath()) || at(row)->isMoreThanOneHardLink())) + return APPLICATION->getThemedIcon("status-yellow"); + return {}; + } case Qt::CheckStateRole: switch (column) { diff --git a/launcher/minecraft/mod/Resource.cpp b/launcher/minecraft/mod/Resource.cpp index 0d35d755..a0b8a4bb 100644 --- a/launcher/minecraft/mod/Resource.cpp +++ b/launcher/minecraft/mod/Resource.cpp @@ -1,6 +1,8 @@ #include "Resource.h" + #include +#include #include "FileSystem.h" @@ -152,3 +154,21 @@ bool Resource::destroy() return FS::deletePath(m_file_info.filePath()); } + +bool Resource::isSymLinkUnder(const QString& instPath) const +{ + if (isSymLink()) + return true; + + auto instDir = QDir(instPath); + + auto relAbsPath = instDir.relativeFilePath(m_file_info.absoluteFilePath()); + auto relCanonPath = instDir.relativeFilePath(m_file_info.canonicalFilePath()); + + return relAbsPath != relCanonPath; +} + +bool Resource::isMoreThanOneHardLink() const +{ + return FS::hardLinkCount(m_file_info.absoluteFilePath()) > 1; +} diff --git a/launcher/minecraft/mod/Resource.h b/launcher/minecraft/mod/Resource.h index 0c37f3a3..a5e9ae91 100644 --- a/launcher/minecraft/mod/Resource.h +++ b/launcher/minecraft/mod/Resource.h @@ -94,6 +94,19 @@ class Resource : public QObject { // Delete all files of this resource. bool destroy(); + [[nodiscard]] auto isSymLink() const -> bool { return m_file_info.isSymLink(); } + + /** + * @brief Take a instance path, checks if the file pointed to by the resource is a symlink or under a symlink in that instance + * + * @param instPath path to an instance directory + * @return true + * @return false + */ + [[nodiscard]] bool isSymLinkUnder(const QString& instPath) const; + + [[nodiscard]] bool isMoreThanOneHardLink() const; + protected: /* The file corresponding to this resource. */ QFileInfo m_file_info; diff --git a/launcher/minecraft/mod/ResourceFolderModel.cpp b/launcher/minecraft/mod/ResourceFolderModel.cpp index f2a77c12..d1748c1c 100644 --- a/launcher/minecraft/mod/ResourceFolderModel.cpp +++ b/launcher/minecraft/mod/ResourceFolderModel.cpp @@ -2,10 +2,14 @@ #include #include +#include +#include #include +#include #include #include +#include "Application.h" #include "FileSystem.h" #include "minecraft/mod/tasks/BasicFolderLoadTask.h" @@ -417,7 +421,25 @@ QVariant ResourceFolderModel::data(const QModelIndex& index, int role) const return {}; } case Qt::ToolTipRole: + 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("\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"); + } + } + return m_resources[row]->internal_id(); + case Qt::DecorationRole: { + if (column == NAME_COLUMN && (at(row).isSymLinkUnder(instDirPath()) || at(row).isMoreThanOneHardLink())) + return APPLICATION->getThemedIcon("status-yellow"); + + return {}; + } case Qt::CheckStateRole: switch (column) { case ACTIVE_COLUMN: @@ -531,3 +553,7 @@ void ResourceFolderModel::enableInteraction(bool enabled) return (compare_result.first < 0); return (compare_result.first > 0); } + +QString ResourceFolderModel::instDirPath() const { + return QFileInfo(m_dir.filePath("../..")).absoluteFilePath(); +} diff --git a/launcher/minecraft/mod/ResourceFolderModel.h b/launcher/minecraft/mod/ResourceFolderModel.h index 3bd78870..f840b2de 100644 --- a/launcher/minecraft/mod/ResourceFolderModel.h +++ b/launcher/minecraft/mod/ResourceFolderModel.h @@ -125,6 +125,8 @@ class ResourceFolderModel : public QAbstractListModel { [[nodiscard]] bool lessThan(const QModelIndex& source_left, const QModelIndex& source_right) const override; }; + QString instDirPath() const; + public slots: void enableInteraction(bool enabled); void disableInteraction(bool disabled) { enableInteraction(!disabled); } diff --git a/launcher/minecraft/mod/ResourcePackFolderModel.cpp b/launcher/minecraft/mod/ResourcePackFolderModel.cpp index da4bd091..56584a34 100644 --- a/launcher/minecraft/mod/ResourcePackFolderModel.cpp +++ b/launcher/minecraft/mod/ResourcePackFolderModel.cpp @@ -36,6 +36,10 @@ #include "ResourcePackFolderModel.h" +#include +#include + +#include "Application.h" #include "Version.h" #include "minecraft/mod/tasks/BasicFolderLoadTask.h" @@ -78,12 +82,28 @@ QVariant ResourcePackFolderModel::data(const QModelIndex& index, int role) const default: return {}; } + case Qt::DecorationRole: { + if (column == NAME_COLUMN && (at(row)->isSymLinkUnder(instDirPath()) || at(row)->isMoreThanOneHardLink())) + return APPLICATION->getThemedIcon("status-yellow"); + return {}; + } case Qt::ToolTipRole: { if (column == PackFormatColumn) { //: The string being explained by this is in the format: ID (Lower version - Upper version) return tr("The resource pack format ID, as well as the Minecraft versions it was designed for."); } + 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("\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"); + } + } return m_resources[row]->internal_id(); } case Qt::CheckStateRole: diff --git a/launcher/ui/pages/instance/WorldListPage.cpp b/launcher/ui/pages/instance/WorldListPage.cpp index d4a395d9..b6ad159e 100644 --- a/launcher/ui/pages/instance/WorldListPage.cpp +++ b/launcher/ui/pages/instance/WorldListPage.cpp @@ -107,6 +107,7 @@ WorldListPage::WorldListPage(BaseInstance *inst, std::shared_ptr worl auto head = ui->worldTreeView->header(); head->setSectionResizeMode(0, QHeaderView::Stretch); head->setSectionResizeMode(1, QHeaderView::ResizeToContents); + head->setSectionResizeMode(4, QHeaderView::ResizeToContents); connect(ui->worldTreeView->selectionModel(), &QItemSelectionModel::currentChanged, this, &WorldListPage::worldChanged); worldChanged(QModelIndex(), QModelIndex()); 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: [PATCH 089/185] 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(-) 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. From 536da704fc1ad66ac754b9fae119df7eb6fa1268 Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Mon, 13 Feb 2023 16:48:58 -0700 Subject: [PATCH 090/185] refactor: cleanupFilesystem.cpp * remove now redundant reflink/clone code for windows * remove unnessacery debug code that could slow things down Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- launcher/FileSystem.cpp | 315 +++++++++------------------------------- launcher/FileSystem.h | 67 +++++++-- 2 files changed, 117 insertions(+), 265 deletions(-) diff --git a/launcher/FileSystem.cpp b/launcher/FileSystem.cpp index af2ba299..d6e6b92f 100644 --- a/launcher/FileSystem.cpp +++ b/launcher/FileSystem.cpp @@ -962,6 +962,37 @@ bool overrideFolder(QString overwritten_path, QString override_path) return err.value() == 0; } +QString getFilesystemTypeName(FilesystemType type) { + auto iter = s_filesystem_type_names.constFind(type); + if (iter != s_filesystem_type_names.constEnd()){ + return iter.value(); + } + return getFilesystemTypeName(FilesystemType::UNKNOWN); +} + +FilesystemType getFilesystemTypeFuzzy(const QString& name) +{ + auto iter = s_filesystem_type_names_inverse.constFind(name.toUpper()); + if (iter != s_filesystem_type_names_inverse.constEnd()){ + return iter.value(); + } + return FilesystemType::UNKNOWN; +} + +FilesystemType getFilesystemType(const QString& name) +{ + for (auto fs_type_pair : s_filesystem_type_names_inverse.toStdMap()) { + auto fs_type_name = fs_type_pair.first; + auto fs_type = fs_type_pair.second; + + if(name.toUpper().contains(fs_type_name.toUpper())) { + return fs_type; + + } + } + return FilesystemType::UNKNOWN; +} + /** * @brief path to the near ancestor that exsists * @@ -994,15 +1025,7 @@ FilesystemInfo statFS(const QString& path) info.fsTypeName = QString::fromStdString(storage_info.fileSystemType().toStdString()); - for (auto fs_type_pair : s_filesystem_type_names_inverse.toStdMap()) { - auto fs_type_name = fs_type_pair.first; - auto fs_type = fs_type_pair.second; - - if(info.fsTypeName.toLower().contains(fs_type_name.toLower())) { - info.fsType = fs_type; - break; - } - } + info.fsType = getFilesystemTypeFuzzy(info.fsTypeName); info.blockSize = storage_info.blockSize(); info.bytesAvailable = storage_info.bytesAvailable(); @@ -1029,7 +1052,7 @@ bool canCloneOnFS(const FilesystemInfo& info) return canCloneOnFS(info.fsType); } bool canCloneOnFS(FilesystemType type) -{ +{ return s_clone_filesystems.contains(type); } @@ -1081,7 +1104,7 @@ bool clone::operator()(const QString& offset, bool dryRun) clone_file(src_path, dst_path, err); } if (err) { - qDebug() << "Failed to clone files:" << QString::fromStdString(err.message()); + qDebug() << "Failed to clone files: error" << err.value() << "message" << QString::fromStdString(err.message()); qDebug() << "Source file:" << src_path; qDebug() << "Destination file:" << dst_path; } @@ -1118,104 +1141,55 @@ bool clone_file(const QString& src, const QString& dst, std::error_code& ec) auto src_path = StringUtils::toStdString(QDir::toNativeSeparators(QFileInfo(src).absoluteFilePath())); auto dst_path = StringUtils::toStdString(QDir::toNativeSeparators(QFileInfo(dst).absoluteFilePath())); -#if defined(Q_OS_WIN) FilesystemInfo srcinfo = statFS(src); FilesystemInfo dstinfo = statFS(dst); - if (((srcinfo.fsType == FilesystemType::BTRFS && dstinfo.fsType == FilesystemType::BTRFS) || - (srcinfo.fsType == FilesystemType::REFS && dstinfo.fsType == FilesystemType::REFS)) && - USE_IOCTL_CLONE) + + + if ((srcinfo.rootPath != dstinfo.rootPath) || (srcinfo.fsType != dstinfo.fsType)) { - if (srcinfo.rootPath != dstinfo.rootPath) { - qWarning() << "clones must be to the same device! src and dst root paths do not match."; - ec = std::make_error_code(std::errc::not_supported); - return false; - } - - qDebug() << "ioctl clone" << src << "to" << dst; - if (!ioctl_clone(src_path, dst_path, ec)) - return false; - - } else if (srcinfo.fsType == FilesystemType::BTRFS) { - if (dstinfo.fsType != FilesystemType::BTRFS || (srcinfo.rootPath != dstinfo.rootPath)){ - qWarning() << "winbtrfs clone must be to the same device! src and dst root paths do not match."; - qWarning() << "check out https://github.com/maharmstone/btrfs for btrfs support!"; - ec = std::make_error_code(std::errc::not_supported); - return false; - } - - qDebug() << "clone/reflink of btrfs on windows! assuming winbtrfs is in use and calling shellbtrfs.dll,ReflinkCopyW"; - - if (!winbtrfs_clone(src_path, dst_path, ec)) - return false; - - // There is no return value from ReflinkCopyW so we must check if the file exsists ourselves - - QFileInfo dstInfo(dst); - if (!dstInfo.exists() || !dstInfo.isFile() || dstInfo.isSymLink()) { - // shellbtrfs.dll,ReflinkCopyW is curently broken https://github.com/maharmstone/btrfs/issues/556 - // lets try a little workaround - // find the misnamed file - qDebug() << dst << "is missing. ReflinkCopyW may still be broken, trying workaround."; - QString badDst = QDir(dstInfo.absolutePath()).path() + dstInfo.fileName(); - qDebug() << "trying" << badDst; - QFileInfo badDstInfo(badDst); - if (badDstInfo.exists() && badDstInfo.isFile()) { - qDebug() << badDst << "exists! moving it to the correct location."; - if(!move(badDstInfo.absoluteFilePath(), dstInfo.absoluteFilePath())) { - qDebug() << "move from" << badDst << "to" << dst << "failed"; - ec = std::make_error_code(std::errc::no_such_file_or_directory); - return false; - } - } else { - // oof, clone failure? - qDebug() << "clone/reflink on winbtrfs did not succeed: file" << dst << "does not appear to exist"; - ec = std::make_error_code(std::errc::no_such_file_or_directory); - return false; - } - } - - } else if (srcinfo.fsType == FilesystemType::REFS) { - if (dstinfo.fsType != FilesystemType::REFS || (srcinfo.rootPath != dstinfo.rootPath)){ - qWarning() << "ReFS clone must be to the same device! src and dst root paths do not match."; - ec = std::make_error_code(std::errc::not_supported); - return false; - } - - qDebug() << "clone/reflink of ReFS on windows!"; - - if (!refs_clone(src_path, dst_path, ec)) - return false; - - } else { - qWarning() << "clone/reflink not supported on windows outside of winbtrfs or ReFS!"; - qWarning() << "check out https://github.com/maharmstone/btrfs for btrfs support!"; ec = std::make_error_code(std::errc::not_supported); + qWarning() << "reflink/clone must be to the same device and filesystem! src and dst root filesystems do not match."; return false; } + +#if defined(Q_OS_WIN) + + if (!win_ioctl_clone(src_path, dst_path, ec)) { + qDebug() << "failed win_ioctl_clone"; + qWarning() << "clone/reflink not supported on windows outside of btrfs or ReFS!"; + qWarning() << "check out https://github.com/maharmstone/btrfs for btrfs support!"; + return false; + } + + + #elif defined(Q_OS_LINUX) - if(!linux_ficlone(src_path, dst_path, ec)) + if(!linux_ficlone(src_path, dst_path, ec)) { + qDebug() << "failed linux_ficlone:"; return false; - + } + #elif defined(Q_OS_MACOS) || defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD) - if(!macos_bsd_clonefile(src_path, dst_path, ec)) + if(!macos_bsd_clonefile(src_path, dst_path, ec)) { + qDebug() << "failed macos_bsd_clonefile:"; return false; - + } + #else + qWarning() << "clone/reflink not supported! unknown OS"; ec = std::make_error_code(std::errc::not_supported); return false; + #endif return true; } #if defined(Q_OS_WIN) -typedef void (__stdcall *f_ReflinkCopyW)(HWND hwnd, HINSTANCE hinst, LPWSTR lpszCmdLine, int nCmdShow); - -const long WinMaxChunkSize = 1L << 31; // 2GB static long RoundUpToPowerOf2(long originalValue, long roundingMultiplePowerOf2) { @@ -1223,171 +1197,15 @@ static long RoundUpToPowerOf2(long originalValue, long roundingMultiplePowerOf2) return (originalValue + mask) & ~mask; } -bool winbtrfs_clone(const std::wstring& src_path, const std::wstring& dst_path, std::error_code& ec) -{ - // https://github.com/maharmstone/btrfs - QString cmdLine = QString("\"%1\" \"%2\"").arg(src_path, dst_path); - - std::wstring wstr = cmdLine.toStdWString(); // temp buffer to copy the data and avoid side effect of non const cast - - LPWSTR cmdLineWin = (wchar_t*)wstr.c_str(); - - // https://github.com/maharmstone/btrfs/blob/9da54911dd6f3713a1c4c7be40338a3da126f4e6/src/shellext/contextmenu.cpp#L1609 - HINSTANCE shellbtrfsDLL = LoadLibrary(L"shellbtrfs.dll"); - - if (shellbtrfsDLL == NULL) { - ec = std::make_error_code(std::errc::not_supported); - qWarning() << "cannot locate the shellbtrfs.dll file, reflink copy not supported"; - return false; - } - - f_ReflinkCopyW ReflinkCopyW = (f_ReflinkCopyW)GetProcAddress(shellbtrfsDLL, "ReflinkCopyW"); - - if (!ReflinkCopyW) { - ec = std::make_error_code(std::errc::not_supported); - qWarning() << "cannot locate the ReflinkCopyW function from shellbtrfs.dll, reflink copy not supported"; - return false; - } - - qDebug() << "Calling ReflinkCopyW from shellbtrfs.dll with:" << cmdLine; - - ReflinkCopyW(0, 0, cmdLineWin, 1); - - FreeLibrary(shellbtrfsDLL); - - return true; -} - -bool refs_clone(const std::wstring& src_path, const std::wstring& dst_path, std::error_code& ec) -{ -#if defined(FSCTL_DUPLICATE_EXTENTS_TO_FILE) - //https://learn.microsoft.com/en-us/windows/win32/api/winioctl/ni-winioctl-fsctl_duplicate_extents_to_file - //https://github.com/microsoft/CopyOnWrite/blob/main/lib/Windows/WindowsCopyOnWriteFilesystem.cs#L94 - QString qSourcePath = StringUtils::fromStdString(src_path); - QString sourceVolumePath = statFS(qSourcePath).rootPath; - std::wstring source_volume_path = StringUtils::toStdString(sourceVolumePath); - - unsigned long sectorsPerCluster; - unsigned long bytesPerSector; - unsigned long numberOfFreeClusters; - unsigned long totalNumberOfClusters; - - if(!GetDiskFreeSpace(source_volume_path.c_str(), §orsPerCluster, &bytesPerSector, &numberOfFreeClusters, &totalNumberOfClusters )){ - ec = std::error_code(GetLastError(), std::system_category()); - qDebug() << "Failed to get disk info for source volume" << sourceVolumePath; - return false; - } - - long srcClusterSize = (long)(sectorsPerCluster * bytesPerSector); - - HANDLE hSourceFile = CreateFile(src_path.c_str(), GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL); - if (hSourceFile == INVALID_HANDLE_VALUE) - { - ec = std::error_code(GetLastError(), std::system_category()); - qDebug() << "Failed to open source file" << src_path.c_str(); - return false; - } - - HANDLE hDestFile = CreateFile(dst_path.c_str(), GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, 0, NULL); - if (hDestFile == INVALID_HANDLE_VALUE) - { - ec = std::error_code(GetLastError(), std::system_category()); - CloseHandle(hSourceFile); - qDebug() << "Failed to open dest file" << dst_path.c_str(); - return false; - } - - DWORD bytesReturned = 0; - - // Set the destination to be sparse while we clone. - // Important to avoid allocating zero-backed real storage when calling SetFileInformationByHandle() - // below which will just be released when cloning file extents. - - if (!DeviceIoControl(hDestFile, FSCTL_SET_SPARSE, nullptr, 0, nullptr, 0, &bytesReturned, nullptr)) { - qDebug() << "Failed to set file sparseness for destination file" << dst_path.c_str(); - ec = std::error_code(GetLastError(), std::system_category()); - return false; - } - - LARGE_INTEGER sourceFileLengthStruct; - if (!GetFileSizeEx(hSourceFile, &sourceFileLengthStruct)) { - ec = std::error_code(GetLastError(), std::system_category()); - qDebug() << "Failed to get file info for source file" << src_path.c_str(); - return false; - } - - DWORD sourceFileLength = sourceFileLengthStruct.QuadPart; - - // Set the destination on-disk size the same as the source. - FILE_END_OF_FILE_INFO fileSizeInfo{sourceFileLength}; - if (!SetFileInformationByHandle(hDestFile, FILE_INFO_BY_HANDLE_CLASS::FileEndOfFileInfo, - &fileSizeInfo, sizeof(FILE_END_OF_FILE_INFO))) - { - ec = std::error_code(GetLastError(), std::system_category()); - qDebug() << "Failed to set end of file on destination file" << dst_path.c_str(); - return false; - } - - DUPLICATE_EXTENTS_DATA duplicateExtentsData = DUPLICATE_EXTENTS_DATA{ hSourceFile }; - - long fileSizeRoundedUpToClusterBoundary = RoundUpToPowerOf2(sourceFileLength, srcClusterSize); - long sourceOffset = 0; - while(sourceOffset < sourceFileLength) - { - duplicateExtentsData.SourceFileOffset.QuadPart = sourceOffset; - duplicateExtentsData.TargetFileOffset.QuadPart = sourceOffset; - long thisChunkSize = std::min(fileSizeRoundedUpToClusterBoundary - sourceOffset, WinMaxChunkSize); - duplicateExtentsData.ByteCount.QuadPart = thisChunkSize; - - DWORD numBytesReturned = 0; - bool ioctlResult = DeviceIoControl( - hDestFile, - FSCTL_DUPLICATE_EXTENTS_TO_FILE, - &duplicateExtentsData, - sizeof(DUPLICATE_EXTENTS_DATA), - nullptr, - 0, - &numBytesReturned, - nullptr); - if (!ioctlResult) - { - DWORD err = GetLastError(); - ec = std::error_code(err, std::system_category()); - QString additionalMessage; - if (err == ERROR_BLOCK_TOO_MANY_REFERENCES) - { - static const int MaxClonesPerFile = 8175; - additionalMessage = QString( - " This is ERROR_BLOCK_TOO_MANY_REFERENCES and may mean you have surpassed the maximum " - "allowed %1 references for a single file. " - "See https://docs.microsoft.com/en-us/windows-server/storage/refs/block-cloning#functionality-restrictions-and-remarks" - ).arg(MaxClonesPerFile); - - } - qWarning() << "Failed copy-on-write cloning from source file" << src_path.c_str() << "to" << dst_path.c_str() << "." << additionalMessage; - return false; - } - - sourceOffset += thisChunkSize; - } - - CloseHandle(hDestFile); - CloseHandle(hSourceFile); - - return true; -#else - ec = std::make_error_code(std::errc::not_supported); - qWarning() << "not built with refs support"; - return false; -#endif -} - bool ioctl_clone(const std::wstring& src_path, const std::wstring& dst_path, std::error_code& ec) { /** * This algorithm inspired from https://github.com/0xbadfca11/reflink * LICENSE MIT * + * Additional references + * https://learn.microsoft.com/en-us/windows/win32/api/winioctl/ni-winioctl-fsctl_duplicate_extents_to_file + * https://github.com/microsoft/CopyOnWrite/blob/main/lib/Windows/WindowsCopyOnWriteFilesystem.cs#L94 */ HANDLE hSourceFile = CreateFileW(src_path.c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, 0, nullptr); @@ -1495,11 +1313,6 @@ bool ioctl_clone(const std::wstring& src_path, const std::wstring& dst_path, std dupExtent.SourceFileOffset.QuadPart = dupExtent.TargetFileOffset.QuadPart = offset; dupExtent.ByteCount.QuadPart = std::min(splitThreshold, remain); - _ASSERTE(dupExtent.SourceFileOffset.QuadPart % sourceFileIntegrity.ClusterSizeInBytes == 0); - _ASSERTE(dupExtent.ByteCount.QuadPart % sourceFileIntegrity.ClusterSizeInBytes == 0); - _ASSERTE(dupExtent.ByteCount.QuadPart <= UINT32_MAX); - _RPT3(_CRT_WARN, "Remain=%llx\nOffset=%llx\nLength=%llx\n\n", remain, dupExtent.SourceFileOffset.QuadPart, dupExtent.ByteCount.QuadPart); - if (!DeviceIoControl(hDestFile, FSCTL_DUPLICATE_EXTENTS_TO_FILE, &dupExtent, sizeof(dupExtent), nullptr, 0, &junk, nullptr)) { DWORD err = GetLastError(); @@ -1559,6 +1372,7 @@ bool ioctl_clone(const std::wstring& src_path, const std::wstring& dst_path, std } #elif defined(Q_OS_LINUX) + bool linux_ficlone(const std::string& src_path, const std::string& dst_path, std::error_code& ec) { // https://man7.org/linux/man-pages/man2/ioctl_ficlone.2.html @@ -1599,6 +1413,7 @@ bool linux_ficlone(const std::string& src_path, const std::string& dst_path, std } #elif defined(Q_OS_MACOS) || defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD) + bool macos_bsd_clonefile(const std::string& src_path, const std::string& dst_path, std::error_code& ec) { // clonefile(const char * src, const char * dst, int flags); diff --git a/launcher/FileSystem.h b/launcher/FileSystem.h index 361993eb..84526c11 100644 --- a/launcher/FileSystem.h +++ b/launcher/FileSystem.h @@ -361,12 +361,20 @@ enum class FilesystemType { UNKNOWN }; +/** + * @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 . + * 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("EXT2_OLD")}, + {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")}, @@ -381,15 +389,27 @@ static const QMap s_filesystem_type_names = { {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. + * 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("EXT"), FilesystemType::EXT}, + {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}, @@ -403,9 +423,32 @@ static const QMap s_filesystem_type_names_inverse = { {QString("UNKNOWN"), FilesystemType::UNKNOWN} }; -inline QString getFilesystemTypeName(FilesystemType type) { - return s_filesystem_type_names.constFind(type).value(); -} +/** + * @brief Get the string name of Filesystem enum object + * + * @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 + * + * @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 + */ +FilesystemType getFilesystemTypeFuzzy(const QString& name); + struct FilesystemInfo { FilesystemType fsType = FilesystemType::UNKNOWN; @@ -430,9 +473,9 @@ QString NearestExistentAncestor(const QString& path); */ 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 }; /** @@ -497,13 +540,7 @@ class clone : public QObject { bool clone_file(const QString& src, const QString& dst, std::error_code& ec); #if defined(Q_OS_WIN) - -bool winbtrfs_clone(const std::wstring& src_path, const std::wstring& dst_path, std::error_code& ec); -bool refs_clone(const std::wstring& src_path, const std::wstring& dst_path, std::error_code& ec); -bool ioctl_clone(const std::wstring& src_path, const std::wstring& dst_path, std::error_code& ec); - -#define USE_IOCTL_CLONE true - +bool win_ioctl_clone(const std::wstring& src_path, const std::wstring& dst_path, std::error_code& ec); #elif defined(Q_OS_LINUX) bool linux_ficlone(const std::string& src_path, const std::string& dst_path, std::error_code& ec); #elif defined(Q_OS_MACOS) || defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD) From 72292f4e03e75f04ddddffc08bb5842e7ae8c071 Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Mon, 13 Feb 2023 17:20:44 -0700 Subject: [PATCH 091/185] fix: windows compile broke move winapi defs into #ifndef blocks don't check explicitly for __mingw__ define function name win_ioctl_clone didn't get updated in teh last commit Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- launcher/FileSystem.cpp | 30 ++++++++++++++++++++++++------ 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/launcher/FileSystem.cpp b/launcher/FileSystem.cpp index d6e6b92f..3d66d844 100644 --- a/launcher/FileSystem.cpp +++ b/launcher/FileSystem.cpp @@ -122,7 +122,12 @@ namespace fs = ghc::filesystem; # endif #endif -#if defined(Q_OS_WIN) && defined(__MINGW32__) +#if defined(Q_OS_WIN) + + +#ifndef FSCTL_DUPLICATE_EXTENTS_TO_FILE + +#define FSCTL_DUPLICATE_EXTENTS_TO_FILE CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 209, METHOD_BUFFERED, FILE_WRITE_DATA ) typedef struct _DUPLICATE_EXTENTS_DATA { HANDLE FileHandle; @@ -131,6 +136,12 @@ typedef struct _DUPLICATE_EXTENTS_DATA { LARGE_INTEGER ByteCount; } DUPLICATE_EXTENTS_DATA, *PDUPLICATE_EXTENTS_DATA; +#endif + +#ifndef FSCTL_GET_INTEGRITY_INFORMATION + +#define FSCTL_GET_INTEGRITY_INFORMATION CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 159, METHOD_BUFFERED, FILE_ANY_ACCESS) // FSCTL_GET_INTEGRITY_INFORMATION_BUFFER + typedef struct _FSCTL_GET_INTEGRITY_INFORMATION_BUFFER { WORD ChecksumAlgorithm; // Checksum algorithm. e.g. CHECKSUM_TYPE_UNCHANGED, CHECKSUM_TYPE_NONE, CHECKSUM_TYPE_CRC32 WORD Reserved; // Must be 0 @@ -139,19 +150,26 @@ typedef struct _FSCTL_GET_INTEGRITY_INFORMATION_BUFFER { DWORD ClusterSizeInBytes; } FSCTL_GET_INTEGRITY_INFORMATION_BUFFER, *PFSCTL_GET_INTEGRITY_INFORMATION_BUFFER; +#endif + +#ifndef FSCTL_SET_INTEGRITY_INFORMATION + +#define FSCTL_SET_INTEGRITY_INFORMATION CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 160, METHOD_BUFFERED, FILE_READ_DATA | FILE_WRITE_DATA) // FSCTL_SET_INTEGRITY_INFORMATION_BUFFER + typedef struct _FSCTL_SET_INTEGRITY_INFORMATION_BUFFER { WORD ChecksumAlgorithm; // Checksum algorithm. e.g. CHECKSUM_TYPE_UNCHANGED, CHECKSUM_TYPE_NONE, CHECKSUM_TYPE_CRC32 WORD Reserved; // Must be 0 DWORD Flags; // FSCTL_INTEGRITY_FLAG_xxx } FSCTL_SET_INTEGRITY_INFORMATION_BUFFER, *PFSCTL_SET_INTEGRITY_INFORMATION_BUFFER; -#define FSCTL_DUPLICATE_EXTENTS_TO_FILE CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 209, METHOD_BUFFERED, FILE_WRITE_DATA ) -#define FSCTL_GET_INTEGRITY_INFORMATION CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 159, METHOD_BUFFERED, FILE_ANY_ACCESS) // FSCTL_GET_INTEGRITY_INFORMATION_BUFFER -#define FSCTL_SET_INTEGRITY_INFORMATION CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 160, METHOD_BUFFERED, FILE_READ_DATA | FILE_WRITE_DATA) // FSCTL_SET_INTEGRITY_INFORMATION_BUFFER - +#endif +#ifndef ERROR_NOT_CAPABLE #define ERROR_NOT_CAPABLE 775L +#endif +#ifndef ERROR_BLOCK_TOO_MANY_REFERENCES #define ERROR_BLOCK_TOO_MANY_REFERENCES 347L +#endif #endif @@ -1197,7 +1215,7 @@ static long RoundUpToPowerOf2(long originalValue, long roundingMultiplePowerOf2) return (originalValue + mask) & ~mask; } -bool ioctl_clone(const std::wstring& src_path, const std::wstring& dst_path, std::error_code& ec) +bool win_ioctl_clone(const std::wstring& src_path, const std::wstring& dst_path, std::error_code& ec) { /** * This algorithm inspired from https://github.com/0xbadfca11/reflink From 562ae676a5467611c86d04d1da4124df242c8194 Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Mon, 13 Feb 2023 17:39:56 -0700 Subject: [PATCH 092/185] fix: mingw still missing typedefs Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- launcher/FileSystem.cpp | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/launcher/FileSystem.cpp b/launcher/FileSystem.cpp index 3d66d844..6ef1eea7 100644 --- a/launcher/FileSystem.cpp +++ b/launcher/FileSystem.cpp @@ -122,12 +122,9 @@ namespace fs = ghc::filesystem; # endif #endif -#if defined(Q_OS_WIN) +#if defined(Q_OS_WIN) - -#ifndef FSCTL_DUPLICATE_EXTENTS_TO_FILE - -#define FSCTL_DUPLICATE_EXTENTS_TO_FILE CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 209, METHOD_BUFFERED, FILE_WRITE_DATA ) +#if defined(__MINGW32__) typedef struct _DUPLICATE_EXTENTS_DATA { HANDLE FileHandle; @@ -136,12 +133,6 @@ typedef struct _DUPLICATE_EXTENTS_DATA { LARGE_INTEGER ByteCount; } DUPLICATE_EXTENTS_DATA, *PDUPLICATE_EXTENTS_DATA; -#endif - -#ifndef FSCTL_GET_INTEGRITY_INFORMATION - -#define FSCTL_GET_INTEGRITY_INFORMATION CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 159, METHOD_BUFFERED, FILE_ANY_ACCESS) // FSCTL_GET_INTEGRITY_INFORMATION_BUFFER - typedef struct _FSCTL_GET_INTEGRITY_INFORMATION_BUFFER { WORD ChecksumAlgorithm; // Checksum algorithm. e.g. CHECKSUM_TYPE_UNCHANGED, CHECKSUM_TYPE_NONE, CHECKSUM_TYPE_CRC32 WORD Reserved; // Must be 0 @@ -150,11 +141,6 @@ typedef struct _FSCTL_GET_INTEGRITY_INFORMATION_BUFFER { DWORD ClusterSizeInBytes; } FSCTL_GET_INTEGRITY_INFORMATION_BUFFER, *PFSCTL_GET_INTEGRITY_INFORMATION_BUFFER; -#endif - -#ifndef FSCTL_SET_INTEGRITY_INFORMATION - -#define FSCTL_SET_INTEGRITY_INFORMATION CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 160, METHOD_BUFFERED, FILE_READ_DATA | FILE_WRITE_DATA) // FSCTL_SET_INTEGRITY_INFORMATION_BUFFER typedef struct _FSCTL_SET_INTEGRITY_INFORMATION_BUFFER { WORD ChecksumAlgorithm; // Checksum algorithm. e.g. CHECKSUM_TYPE_UNCHANGED, CHECKSUM_TYPE_NONE, CHECKSUM_TYPE_CRC32 @@ -164,9 +150,23 @@ typedef struct _FSCTL_SET_INTEGRITY_INFORMATION_BUFFER { #endif + +#ifndef FSCTL_DUPLICATE_EXTENTS_TO_FILE +#define FSCTL_DUPLICATE_EXTENTS_TO_FILE CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 209, METHOD_BUFFERED, FILE_WRITE_DATA ) +#endif + +#ifndef FSCTL_GET_INTEGRITY_INFORMATION +#define FSCTL_GET_INTEGRITY_INFORMATION CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 159, METHOD_BUFFERED, FILE_ANY_ACCESS) // FSCTL_GET_INTEGRITY_INFORMATION_BUFFER +#endif + +#ifndef FSCTL_SET_INTEGRITY_INFORMATION +#define FSCTL_SET_INTEGRITY_INFORMATION CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 160, METHOD_BUFFERED, FILE_READ_DATA | FILE_WRITE_DATA) // FSCTL_SET_INTEGRITY_INFORMATION_BUFFER +#endif + #ifndef ERROR_NOT_CAPABLE #define ERROR_NOT_CAPABLE 775L #endif + #ifndef ERROR_BLOCK_TOO_MANY_REFERENCES #define ERROR_BLOCK_TOO_MANY_REFERENCES 347L #endif From 656bfd36f607579021a4eaa0893350640b6a3882 Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Wed, 15 Feb 2023 17:58:14 -0800 Subject: [PATCH 093/185] fix: ensure filelink.exe is included in setup.exe Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- program_info/win_install.nsi.in | 2 ++ 1 file changed, 2 insertions(+) diff --git a/program_info/win_install.nsi.in b/program_info/win_install.nsi.in index 49e22500..f8918c58 100644 --- a/program_info/win_install.nsi.in +++ b/program_info/win_install.nsi.in @@ -281,6 +281,7 @@ Section "@Launcher_DisplayName@" SetOutPath $INSTDIR File "@Launcher_APP_BINARY_NAME@.exe" + File "@Launcher_APP_BINARY_NAME@_filelink.exe" File "qt.conf" File *.dll File /r "iconengines" @@ -361,6 +362,7 @@ Section "Uninstall" DeleteRegKey HKCU SOFTWARE\@Launcher_CommonName@ Delete $INSTDIR\@Launcher_APP_BINARY_NAME@.exe + Delete $INSTDIR\@Launcher_APP_BINARY_NAME@_filelink.exe Delete $INSTDIR\qt.conf Delete $INSTDIR\*.dll From 3ec92acfe7a843a34018fc142439888c4ca5dba0 Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Thu, 16 Feb 2023 20:01:59 -0800 Subject: [PATCH 094/185] fix: use noexcept overload of std::filesystem::hard_link_count Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- launcher/FileSystem.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/launcher/FileSystem.cpp b/launcher/FileSystem.cpp index 6ef1eea7..45c73d24 100644 --- a/launcher/FileSystem.cpp +++ b/launcher/FileSystem.cpp @@ -1476,7 +1476,13 @@ bool canLink(const QString& src, const QString& dst) uintmax_t hardLinkCount(const QString& path) { - return fs::hard_link_count(StringUtils::toStdString(path)); + std::error_code err; + int count = fs::hard_link_count(StringUtils::toStdString(path), err); + if (err) { + qWarning() << "Failed to count hard links for" << path << ":" << QString::fromStdString(err.message()); + count = 0; + } + return count; } } From 1ca2c59f2ed7739b4b7d50c7212e292a4432da93 Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Wed, 15 Feb 2023 22:01:27 -0700 Subject: [PATCH 095/185] feat: track instance copies that use links confirm deleations when other instances link to it Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- launcher/BaseInstance.cpp | 36 ++++++++++++++++++++++++ launcher/BaseInstance.h | 6 ++++ launcher/InstanceCopyTask.cpp | 2 ++ launcher/InstanceList.cpp | 10 +++++++ launcher/InstanceList.h | 2 ++ launcher/settings/INIFile.cpp | 18 ++++++++++++ launcher/settings/INIFile.h | 35 ++++++++++++++++++++++++ launcher/settings/SettingsObject.cpp | 13 +++++++++ launcher/settings/SettingsObject.h | 41 ++++++++++++++++++++++++++++ launcher/ui/MainWindow.cpp | 14 ++++++++++ tests/INIFile_test.cpp | 38 ++++++++++++++++++++++++-- 11 files changed, 213 insertions(+), 2 deletions(-) diff --git a/launcher/BaseInstance.cpp b/launcher/BaseInstance.cpp index 8680361c..6428be43 100644 --- a/launcher/BaseInstance.cpp +++ b/launcher/BaseInstance.cpp @@ -40,6 +40,8 @@ #include #include #include +#include +#include #include "settings/INISettingsObject.h" #include "settings/Setting.h" @@ -64,6 +66,8 @@ BaseInstance::BaseInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr s m_settings->registerSetting("totalTimePlayed", 0); m_settings->registerSetting("lastTimePlayed", 0); + m_settings->registerSetting("linkedInstancesList", "[]"); + // Game time override auto gameTimeOverride = m_settings->registerSetting("OverrideGameTime", false); m_settings->registerOverride(globalSettings->getSetting("ShowGameTime"), gameTimeOverride); @@ -182,6 +186,38 @@ bool BaseInstance::shouldStopOnConsoleOverflow() const return m_settings->get("ConsoleOverflowStop").toBool(); } +QStringList BaseInstance::getLinkedInstances() const +{ + return m_settings->getList("linkedInstancesList"); +} + +void BaseInstance::setLinkedInstances(const QStringList& list) +{ + auto linkedInstancesList = m_settings->getList("linkedInstancesList"); + m_settings->setList("linkedInstancesList", list); +} + +void BaseInstance::addLinkedInstanceId(const QString& id) +{ + auto linkedInstancesList = m_settings->getList("linkedInstancesList"); + linkedInstancesList.append(id); + setLinkedInstances(linkedInstancesList); +} + +bool BaseInstance::removeLinkedInstanceId(const QString& id) +{ + auto linkedInstancesList = m_settings->getList("linkedInstancesList"); + int numRemoved = linkedInstancesList.removeAll(id); + setLinkedInstances(linkedInstancesList); + return numRemoved > 0; +} + +bool BaseInstance::isLinkedToInstanceId(const QString& id) const +{ + auto linkedInstancesList = m_settings->getList("linkedInstancesList"); + return linkedInstancesList.contains(id); +} + void BaseInstance::iconUpdated(QString key) { if(iconKey() == key) diff --git a/launcher/BaseInstance.h b/launcher/BaseInstance.h index a2a4f824..83a8064f 100644 --- a/launcher/BaseInstance.h +++ b/launcher/BaseInstance.h @@ -282,6 +282,12 @@ public: int getConsoleMaxLines() const; bool shouldStopOnConsoleOverflow() const; + QStringList getLinkedInstances() const; + void setLinkedInstances(const QStringList& list); + void addLinkedInstanceId(const QString& id); + bool removeLinkedInstanceId(const QString& id); + bool isLinkedToInstanceId(const QString& id) const; + protected: void changeStatus(Status newStatus); diff --git a/launcher/InstanceCopyTask.cpp b/launcher/InstanceCopyTask.cpp index 40babd0f..e0a4de0b 100644 --- a/launcher/InstanceCopyTask.cpp +++ b/launcher/InstanceCopyTask.cpp @@ -137,6 +137,8 @@ void InstanceCopyTask::copyFinished() if(!m_keepPlaytime) { inst->resetTimePlayed(); } + if (m_useLinks) + inst->addLinkedInstanceId(m_origInstance->id()); emitSucceeded(); } diff --git a/launcher/InstanceList.cpp b/launcher/InstanceList.cpp index 1ca16ae9..179bfb9a 100644 --- a/launcher/InstanceList.cpp +++ b/launcher/InstanceList.cpp @@ -129,6 +129,16 @@ QMimeData* InstanceList::mimeData(const QModelIndexList& indexes) const return mimeData; } +QStringList InstanceList::getLinkedInstancesById(const QString &id) const +{ + QStringList linkedInstances; + for (auto inst : m_instances) { + if (inst->isLinkedToInstanceId(id)) + linkedInstances.append(inst->id()); + } + return linkedInstances; +} + int InstanceList::rowCount(const QModelIndex& parent) const { Q_UNUSED(parent); diff --git a/launcher/InstanceList.h b/launcher/InstanceList.h index edacba3c..48bede07 100644 --- a/launcher/InstanceList.h +++ b/launcher/InstanceList.h @@ -154,6 +154,8 @@ public: QStringList mimeTypes() const override; QMimeData *mimeData(const QModelIndexList &indexes) const override; + QStringList getLinkedInstancesById(const QString &id) const; + signals: void dataIsInvalid(); void instancesChanged(); diff --git a/launcher/settings/INIFile.cpp b/launcher/settings/INIFile.cpp index 733cd444..e48e6f47 100644 --- a/launcher/settings/INIFile.cpp +++ b/launcher/settings/INIFile.cpp @@ -183,3 +183,21 @@ void INIFile::set(QString key, QVariant val) { this->operator[](key) = val; } + +void INIFile::setList(QString key, QVariantList val) +{ + QString stringList = QJsonDocument(QVariant(val).toJsonArray()).toJson(QJsonDocument::Compact); + + this->operator[](key) = stringList; +} + +QVariantList INIFile::getList(QString key, QVariantList def) const +{ + if (this->contains(key)) { + auto src = this->operator[](key); + + return QJsonDocument::fromJson(src.toByteArray()).toVariant().toList(); + } + + return def; +} diff --git a/launcher/settings/INIFile.h b/launcher/settings/INIFile.h index 4313e829..86bf0898 100644 --- a/launcher/settings/INIFile.h +++ b/launcher/settings/INIFile.h @@ -19,6 +19,9 @@ #include #include +#include +#include + // Sectionless INI parser (for instance config files) class INIFile : public QMap { @@ -33,4 +36,36 @@ public: void set(QString key, QVariant val); static QString unescape(QString orig); static QString escape(QString orig); + + void setList(QString key, QVariantList val); + template void setList(QString key, QList val) + { + QVariantList variantList; + variantList.reserve(val.size()); + for (const T& v : val) + { + variantList.append(v); + } + + this->setList(key, variantList); + } + + QVariantList getList(QString key, QVariantList def) const; + template QList getList(QString key, QList def) const + { + if (this->contains(key)) { + QVariant src = this->operator[](key); + QVariantList variantList = QJsonDocument::fromJson(src.toByteArray()).toVariant().toList(); + + QListTList; + TList.reserve(variantList.size()); + for (const QVariant& v : variantList) + { + TList.append(v.value()); + } + return TList; + } + + return def; + } }; diff --git a/launcher/settings/SettingsObject.cpp b/launcher/settings/SettingsObject.cpp index 8a0bc045..4c51d6e9 100644 --- a/launcher/settings/SettingsObject.cpp +++ b/launcher/settings/SettingsObject.cpp @@ -121,6 +121,19 @@ bool SettingsObject::contains(const QString &id) return m_settings.contains(id); } +bool SettingsObject::setList(const QString &id, QVariantList value) +{ + QString stringList = QJsonDocument(QVariant(value).toJsonArray()).toJson(QJsonDocument::Compact); + + return set(id, stringList); +} + +QVariantList SettingsObject::getList(const QString &id) +{ + QVariant value = this->get(id); + return QJsonDocument::fromJson(value.toByteArray()).toVariant().toList(); +} + bool SettingsObject::reload() { for (auto setting : m_settings.values()) diff --git a/launcher/settings/SettingsObject.h b/launcher/settings/SettingsObject.h index 6200bc3a..ff430172 100644 --- a/launcher/settings/SettingsObject.h +++ b/launcher/settings/SettingsObject.h @@ -19,6 +19,8 @@ #include #include #include +#include +#include #include class Setting; @@ -142,6 +144,45 @@ public: */ bool contains(const QString &id); + /*! + * \brief Sets the value of the setting with the given ID with a json list. + * If no setting with the given ID exists, returns false + * \param id The ID of the setting to change. + * \param value The new value of the setting. + */ + bool setList(const QString &id, QVariantList value); + template bool setList(const QString &id, QList val) + { + QVariantList variantList; + variantList.reserve(val.size()); + for (const T& v : val) + { + variantList.append(v); + } + + return setList(id, variantList); + } + + /** + * \brief Gets the value of the setting with the given ID as if it were a json list. + * \param id The ID of the setting to change. + * \return The setting's value as a QVariantList. + * If no setting with the given ID exists, returns an empty QVariantList. + */ + QVariantList getList(const QString &id); + template QList getList(const QString &id) + { + QVariantList variantList = this->getList(id); + + QListTList; + TList.reserve(variantList.size()); + for (const QVariant& v : variantList) + { + TList.append(v.value()); + } + return TList; + } + /*! * \brief Reloads the settings and emit signals for changed settings * \return True if reloading was successful diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp index 8490b292..a6aa8320 100644 --- a/launcher/ui/MainWindow.cpp +++ b/launcher/ui/MainWindow.cpp @@ -1337,6 +1337,20 @@ void MainWindow::on_actionDeleteInstance_triggered() if (response != QMessageBox::Yes) return; + auto linkedInstances = APPLICATION->instances()->getLinkedInstancesById(id); + 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" + "%1\n\n" + "Deleting it could break the other instance(s), \n\n" + "Are you sure?").arg(linkedInstances.join("\n")), + QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No, QMessageBox::No + )->exec(); + if (response != QMessageBox::Yes) + return; + } + if (APPLICATION->instances()->trashInstance(id)) { ui->actionUndoTrashInstance->setEnabled(APPLICATION->instances()->trashedSomething()); return; diff --git a/tests/INIFile_test.cpp b/tests/INIFile_test.cpp index b64b031b..d13937c0 100644 --- a/tests/INIFile_test.cpp +++ b/tests/INIFile_test.cpp @@ -1,7 +1,10 @@ #include +#include +#include #include + class IniFileTest : public QObject { Q_OBJECT @@ -52,8 +55,39 @@ slots: // load INIFile f2; f2.loadFile(filename); - QCOMPARE(a, f2.get("a","NOT SET").toString()); - QCOMPARE(b, f2.get("b","NOT SET").toString()); + QCOMPARE(f2.get("a","NOT SET").toString(), a); + QCOMPARE(f2.get("b","NOT SET").toString(), b); + } + + void test_SaveLoadLists() + { + QString slist_strings = "[\"a\",\"b\",\"c\"]"; + QStringList list_strings = {"a", "b", "c"}; + + QString slist_numbers = "[1,2,3,10]"; + QList list_numbers = {1, 2, 3, 10}; + + QString filename = "test_SaveLoadLists.ini"; + + INIFile f; + f.setList("list_strings", list_strings); + f.setList("list_numbers", list_numbers); + f.saveFile(filename); + + // load + INIFile f2; + f2.loadFile(filename); + + QStringList out_list_strings = f2.getList("list_strings", QStringList()); + qDebug() << "OutStringList" << out_list_strings; + + QList out_list_numbers = f2.getList("list_numbers", QList()); + qDebug() << "OutNumbersList" << out_list_numbers; + + QCOMPARE(f2.get("list_strings","NOT SET").toString(), slist_strings); + QCOMPARE(out_list_strings, list_strings); + QCOMPARE(f2.get("list_numbers","NOT SET").toString(), slist_numbers); + QCOMPARE(out_list_numbers, list_numbers); } }; 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: [PATCH 096/185] 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(+) 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(); 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: [PATCH 097/185] 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 | 133 +++++++++++----------- tests/FileSystem_test.cpp | 6 +- 5 files changed, 84 insertions(+), 82 deletions(-) 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,20 +169,10 @@ - - - - true - + + - Copy resource packs - - - - - - - Keep play time + Copy saves @@ -206,9 +183,35 @@ + + + + + 0 + 0 + + + + Qt::LeftToRight + + + 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 @@ - - - - Use symbolic links + + + + true + + Use hard links instead of copying files. + + + 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(); } From dc5402349e8c65945d46f1539fd92823a05a391c Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Thu, 23 Feb 2023 19:08:35 -0700 Subject: [PATCH 098/185] refactor: use UUID toString mode Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- launcher/FileSystem.cpp | 2 +- launcher/InstanceList.cpp | 2 +- launcher/StringUtils.cpp | 14 +++----------- launcher/StringUtils.h | 2 +- 4 files changed, 6 insertions(+), 14 deletions(-) diff --git a/launcher/FileSystem.cpp b/launcher/FileSystem.cpp index 2bd0cf52..714af4a0 100644 --- a/launcher/FileSystem.cpp +++ b/launcher/FileSystem.cpp @@ -442,7 +442,7 @@ void create_link::runPrivileged(const QString& offset) make_link_list(offset); - QString serverName = BuildConfig.LAUNCHER_APP_BINARY_NAME + "_filelink_server" + StringUtils::getRandomAlphaNumeric(8); + QString serverName = BuildConfig.LAUNCHER_APP_BINARY_NAME + "_filelink_server" + StringUtils::getRandomAlphaNumeric(); connect(&m_linkServer, &QLocalServer::newConnection, this, [&](){ diff --git a/launcher/InstanceList.cpp b/launcher/InstanceList.cpp index 179bfb9a..5b33d678 100644 --- a/launcher/InstanceList.cpp +++ b/launcher/InstanceList.cpp @@ -875,7 +875,7 @@ Task* InstanceList::wrapInstanceTask(InstanceTask* task) QString InstanceList::getStagedInstancePath() { - QString key = QUuid::createUuid().toString().remove("{").remove("}"); + QString key = QUuid::createUuid().toString(QUuid::WithoutBraces); QString tempDir = ".LAUNCHER_TEMP/"; QString relPath = FS::PathCombine(tempDir, key); QDir rootPath(m_instDir); diff --git a/launcher/StringUtils.cpp b/launcher/StringUtils.cpp index 93a44d4c..2fa56501 100644 --- a/launcher/StringUtils.cpp +++ b/launcher/StringUtils.cpp @@ -1,6 +1,6 @@ #include "StringUtils.h" -#include +#include /// If you're wondering where these came from exactly, then know you're not the only one =D @@ -77,15 +77,7 @@ int StringUtils::naturalCompare(const QString& s1, const QString& s2, Qt::CaseSe return QString::compare(s1, s2, cs); } -QString StringUtils::getRandomAlphaNumeric(const int length) +QString StringUtils::getRandomAlphaNumeric() { - const QString possibleCharacters("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"); - QString randomString; - for(int i=0; i < length; ++i) - { - int index = QRandomGenerator::global()->bounded(0, possibleCharacters.length()); - QChar nextChar = possibleCharacters.at(index); - randomString.append(nextChar); - } - return randomString; + return QUuid::createUuid().toString(QUuid::Id128); } diff --git a/launcher/StringUtils.h b/launcher/StringUtils.h index 1ba19555..c4a6ab31 100644 --- a/launcher/StringUtils.h +++ b/launcher/StringUtils.h @@ -30,5 +30,5 @@ inline QString fromStdString(string s) int naturalCompare(const QString& s1, const QString& s2, Qt::CaseSensitivity cs); -QString getRandomAlphaNumeric(const int length); +QString getRandomAlphaNumeric(); } // namespace StringUtils 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: [PATCH 099/185] 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 +++--- .../minecraft/mod/ResourceFolderModel.cpp | 7 +++--- .../minecraft/mod/ResourcePackFolderModel.cpp | 7 +++--- launcher/ui/dialogs/CopyInstanceDialog.cpp | 2 +- 9 files changed, 30 insertions(+), 31 deletions(-) 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 +} From a96519cbdc61387a79182fb81b016e5d73105713 Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Fri, 3 Mar 2023 06:39:13 -0700 Subject: [PATCH 100/185] workflow: add filelink.exe to SignTool call Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 022a04f8..df1a38fc 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -396,7 +396,7 @@ jobs: if (Get-Content ./codesign.pfx){ cd ${{ env.INSTALL_DIR }} # We ship the exact same executable for portable and non-portable editions, so signing just once is fine - SignTool sign /fd sha256 /td sha256 /f ../codesign.pfx /p '${{ secrets.WINDOWS_CODESIGN_PASSWORD }}' /tr http://timestamp.digicert.com prismlauncher.exe + SignTool sign /fd sha256 /td sha256 /f ../codesign.pfx /p '${{ secrets.WINDOWS_CODESIGN_PASSWORD }}' /tr http://timestamp.digicert.com prismlauncher.exe prismlauncher_filelink.exe } else { ":warning: Skipped code signing for Windows, as certificate was not present." >> $env:GITHUB_STEP_SUMMARY } From 0bec0046bbab911909aacb4d02525b3b85597447 Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Fri, 3 Mar 2023 07:28:59 -0700 Subject: [PATCH 101/185] format: clang-format to fix windows fallout it looked fine over in vscod on windows but as soon as I opened it on linux via Helix the chaos was clear Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- launcher/FileSystem.cpp | 487 ++++++++++++++++++---------------------- 1 file changed, 220 insertions(+), 267 deletions(-) diff --git a/launcher/FileSystem.cpp b/launcher/FileSystem.cpp index 714af4a0..c913b43e 100644 --- a/launcher/FileSystem.cpp +++ b/launcher/FileSystem.cpp @@ -44,9 +44,9 @@ #include #include #include -#include #include #include +#include #include #include #include @@ -68,10 +68,10 @@ #include #include #include -//for ShellExecute -#include -#include +// for ShellExecute #include +#include +#include #else #include #endif @@ -79,47 +79,46 @@ // 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 - // clone #if defined(Q_OS_LINUX) -#include -#include /* Definition of FICLONE* constants */ -#include #include +#include /* Definition of FICLONE* constants */ +#include +#include #include #elif defined(Q_OS_MACOS) || defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD) #include #include #elif defined(Q_OS_WIN) // winbtrfs clone vs rundll32 shellbtrfs.dll,ReflinkCopy -#include +#include #include #include -#include +#include // refs #include -# if defined(__MINGW32__) -# include -# endif +#if defined(__MINGW32__) +#include +#endif #endif -#if defined(Q_OS_WIN) +#if defined(Q_OS_WIN) #if defined(__MINGW32__) @@ -131,46 +130,45 @@ typedef struct _DUPLICATE_EXTENTS_DATA { } DUPLICATE_EXTENTS_DATA, *PDUPLICATE_EXTENTS_DATA; typedef struct _FSCTL_GET_INTEGRITY_INFORMATION_BUFFER { - WORD ChecksumAlgorithm; // Checksum algorithm. e.g. CHECKSUM_TYPE_UNCHANGED, CHECKSUM_TYPE_NONE, CHECKSUM_TYPE_CRC32 - WORD Reserved; // Must be 0 - DWORD Flags; // FSCTL_INTEGRITY_FLAG_xxx + WORD ChecksumAlgorithm; // Checksum algorithm. e.g. CHECKSUM_TYPE_UNCHANGED, CHECKSUM_TYPE_NONE, CHECKSUM_TYPE_CRC32 + WORD Reserved; // Must be 0 + DWORD Flags; // FSCTL_INTEGRITY_FLAG_xxx DWORD ChecksumChunkSizeInBytes; DWORD ClusterSizeInBytes; } FSCTL_GET_INTEGRITY_INFORMATION_BUFFER, *PFSCTL_GET_INTEGRITY_INFORMATION_BUFFER; - typedef struct _FSCTL_SET_INTEGRITY_INFORMATION_BUFFER { - WORD ChecksumAlgorithm; // Checksum algorithm. e.g. CHECKSUM_TYPE_UNCHANGED, CHECKSUM_TYPE_NONE, CHECKSUM_TYPE_CRC32 - WORD Reserved; // Must be 0 - DWORD Flags; // FSCTL_INTEGRITY_FLAG_xxx + WORD ChecksumAlgorithm; // Checksum algorithm. e.g. CHECKSUM_TYPE_UNCHANGED, CHECKSUM_TYPE_NONE, CHECKSUM_TYPE_CRC32 + WORD Reserved; // Must be 0 + DWORD Flags; // FSCTL_INTEGRITY_FLAG_xxx } FSCTL_SET_INTEGRITY_INFORMATION_BUFFER, *PFSCTL_SET_INTEGRITY_INFORMATION_BUFFER; #endif - #ifndef FSCTL_DUPLICATE_EXTENTS_TO_FILE -#define FSCTL_DUPLICATE_EXTENTS_TO_FILE CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 209, METHOD_BUFFERED, FILE_WRITE_DATA ) +#define FSCTL_DUPLICATE_EXTENTS_TO_FILE CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 209, METHOD_BUFFERED, FILE_WRITE_DATA) #endif #ifndef FSCTL_GET_INTEGRITY_INFORMATION -#define FSCTL_GET_INTEGRITY_INFORMATION CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 159, METHOD_BUFFERED, FILE_ANY_ACCESS) // FSCTL_GET_INTEGRITY_INFORMATION_BUFFER +#define FSCTL_GET_INTEGRITY_INFORMATION \ + CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 159, METHOD_BUFFERED, FILE_ANY_ACCESS) // FSCTL_GET_INTEGRITY_INFORMATION_BUFFER #endif #ifndef FSCTL_SET_INTEGRITY_INFORMATION -#define FSCTL_SET_INTEGRITY_INFORMATION CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 160, METHOD_BUFFERED, FILE_READ_DATA | FILE_WRITE_DATA) // FSCTL_SET_INTEGRITY_INFORMATION_BUFFER +#define FSCTL_SET_INTEGRITY_INFORMATION \ + CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 160, METHOD_BUFFERED, FILE_READ_DATA | FILE_WRITE_DATA) // FSCTL_SET_INTEGRITY_INFORMATION_BUFFER #endif #ifndef ERROR_NOT_CAPABLE -#define ERROR_NOT_CAPABLE 775L +#define ERROR_NOT_CAPABLE 775L #endif #ifndef ERROR_BLOCK_TOO_MANY_REFERENCES -#define ERROR_BLOCK_TOO_MANY_REFERENCES 347L +#define ERROR_BLOCK_TOO_MANY_REFERENCES 347L #endif #endif - namespace FS { void ensureExists(const QDir& dir) @@ -313,23 +311,22 @@ QDebug operator<<(QDebug debug, const LinkPair& lp) return debug; } -bool create_link::operator()(const QString& offset, bool dryRun) +bool create_link::operator()(const QString& offset, bool dryRun) { m_linked = 0; // reset counter m_path_results.clear(); m_links_to_make.clear(); m_path_results.clear(); - + make_link_list(offset); - + if (!dryRun) return make_links(); return true; } - /** * @brief make a list off all the links ot make * @param offset subdirectory form src to link to dest @@ -354,13 +351,12 @@ void create_link::make_link_list(const QString& offset) qDebug() << "path" << relative_dst_path << "in black list or not in whitelist"; return; } - auto dst_path = PathCombine(dst, relative_dst_path); - LinkPair link = {src_path, dst_path}; - m_links_to_make.append(link); + LinkPair link = { src_path, dst_path }; + m_links_to_make.append(link); }; - + if ((!m_recursive) || !fs::is_directory(StringUtils::toStdString(src))) { if (m_debug) qDebug() << "linking single file or dir:" << src << "to" << dst; @@ -377,7 +373,7 @@ void create_link::make_link_list(const QString& offset) 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){ + 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)) { @@ -390,13 +386,12 @@ void create_link::make_link_list(const QString& offset) link_file(src_path, relative_path); } } - } + } } bool create_link::make_links() -{ +{ for (auto link : m_links_to_make) { - QString src_path = link.src; QString dst_path = link.dst; @@ -414,7 +409,6 @@ bool create_link::make_links() qDebug() << "making symlink:" << src_path << "to" << dst_path; fs::create_symlink(StringUtils::toStdString(src_path), StringUtils::toStdString(dst_path), m_os_err); } - if (m_os_err) { qWarning() << "Failed to link files:" << QString::fromStdString(m_os_err.message()); @@ -427,7 +421,8 @@ bool create_link::make_links() m_linked++; emit fileLinked(src_path, dst_path); } - if (m_os_err) return false; + if (m_os_err) + return false; } return true; } @@ -444,17 +439,16 @@ void create_link::runPrivileged(const QString& offset) QString serverName = BuildConfig.LAUNCHER_APP_BINARY_NAME + "_filelink_server" + StringUtils::getRandomAlphaNumeric(); - connect(&m_linkServer, &QLocalServer::newConnection, this, [&](){ - + connect(&m_linkServer, &QLocalServer::newConnection, this, [&]() { qDebug() << "Client connected, sending out pairs"; // construct block of data to send QByteArray block; QDataStream out(&block, QIODevice::WriteOnly); - out.setVersion(QDataStream::Qt_5_0); // choose correct version better? + out.setVersion(QDataStream::Qt_5_0); // choose correct version better? qint32 blocksize = quint32(sizeof(quint32)); for (auto link : m_links_to_make) { - blocksize += quint32(link.src.size()); + blocksize += quint32(link.src.size()); blocksize += quint32(link.dst.size()); } qDebug() << "About to write block of size:" << blocksize; @@ -462,15 +456,14 @@ void create_link::runPrivileged(const QString& offset) out << quint32(m_links_to_make.length()); for (auto link : m_links_to_make) { - out << link.src; + out << link.src; out << link.dst; } - QLocalSocket *clientConnection = m_linkServer.nextPendingConnection(); - connect(clientConnection, &QLocalSocket::disconnected, - clientConnection, &QLocalSocket::deleteLater); - - connect(clientConnection, &QLocalSocket::readyRead, this, [&, clientConnection](){ + QLocalSocket* clientConnection = m_linkServer.nextPendingConnection(); + connect(clientConnection, &QLocalSocket::disconnected, clientConnection, &QLocalSocket::deleteLater); + + connect(clientConnection, &QLocalSocket::readyRead, this, [&, clientConnection]() { QDataStream in; quint32 blockSize = 0; in.setDevice(clientConnection); @@ -489,18 +482,18 @@ void create_link::runPrivileged(const QString& offset) qDebug() << "bytes avalible" << clientConnection->bytesAvailable(); if (clientConnection->bytesAvailable() < blockSize || in.atEnd()) return; - + quint32 numResults; in >> numResults; qDebug() << "numResults" << numResults; - for(quint32 i = 0; i < numResults; i++) { + for (quint32 i = 0; i < numResults; i++) { FS::LinkResult result; in >> result.src; in >> result.dst; in >> result.err_msg; qint32 err_value; - in >> err_value; + in >> err_value; result.err_value = err_value; if (result.err_value) { qDebug() << "privileged link fail" << result.src << "to" << result.dst << "code" << result.err_value << result.err_msg; @@ -520,7 +513,6 @@ void create_link::runPrivileged(const QString& offset) qint64 byteswritten = clientConnection->write(block); bool bytesflushed = clientConnection->flush(); qDebug() << "block flushed" << byteswritten << bytesflushed; - }); qDebug() << "Listening on pipe" << serverName; @@ -534,11 +526,12 @@ void create_link::runPrivileged(const QString& offset) connect(linkFileProcess, &ExternalLinkFileProcess::finished, linkFileProcess, &QObject::deleteLater); linkFileProcess->start(); - } -void ExternalLinkFileProcess::runLinkFile() { - QString fileLinkExe = PathCombine(QCoreApplication::instance()->applicationDirPath(), BuildConfig.LAUNCHER_APP_BINARY_NAME + "_filelink"); +void ExternalLinkFileProcess::runLinkFile() +{ + QString fileLinkExe = + PathCombine(QCoreApplication::instance()->applicationDirPath(), BuildConfig.LAUNCHER_APP_BINARY_NAME + "_filelink"); QString params = "-s " + m_server; params += " -H " + QVariant(m_useHardLinks).toString(); @@ -550,14 +543,15 @@ void ExternalLinkFileProcess::runLinkFile() { qDebug() << "Running: runas" << fileLinkExe << params; - LPCWSTR programNameWin = (const wchar_t*) fileLinkExe.utf16(); - LPCWSTR paramsWin = (const wchar_t*) params.utf16(); + LPCWSTR programNameWin = (const wchar_t*)fileLinkExe.utf16(); + LPCWSTR paramsWin = (const wchar_t*)params.utf16(); // https://learn.microsoft.com/en-us/windows/win32/api/shellapi/ns-shellapi-shellexecuteinfoa ShExecInfo.cbSize = sizeof(SHELLEXECUTEINFO); ShExecInfo.fMask = SEE_MASK_NOCLOSEPROCESS; - ShExecInfo.hwnd = NULL; // Optional. A handle to the owner window, used to display and position any UI that the system might produce while executing this function. - ShExecInfo.lpVerb = L"runas"; // elevate to admin, show UAC + ShExecInfo.hwnd = NULL; // Optional. A handle to the owner window, used to display and position any UI that the system might produce + // while executing this function. + ShExecInfo.lpVerb = L"runas"; // elevate to admin, show UAC ShExecInfo.lpFile = programNameWin; ShExecInfo.lpParameters = paramsWin; ShExecInfo.lpDirectory = NULL; @@ -602,7 +596,7 @@ bool deletePath(QString path) return err.value() == 0; } -bool trash(QString path, QString *pathInTrash) +bool trash(QString path, QString* pathInTrash) { #if QT_VERSION < QT_VERSION_CHECK(5, 15, 0) return false; @@ -644,7 +638,8 @@ QString AbsolutePath(const QString& path) int PathDepth(const QString& path) { - if (path.isEmpty()) return 0; + if (path.isEmpty()) + return 0; QFileInfo info(path); @@ -657,17 +652,18 @@ int PathDepth(const QString& path) int numParts = parts.length(); numParts -= parts.count("."); numParts -= parts.count("..") * 2; - + return numParts; } QString PathTruncate(const QString& path, int depth) { - if (path.isEmpty() || (depth < 0) ) return ""; + if (path.isEmpty() || (depth < 0)) + return ""; QString trunc = QFileInfo(path).path(); - if (PathDepth(trunc) > depth ) { + if (PathDepth(trunc) > depth) { return PathTruncate(trunc, depth); } @@ -786,11 +782,7 @@ bool createShortcut(QString destination, QString target, QStringList args, QStri stream << "#!/bin/bash" << "\n"; - stream << "\"" - << target - << "\" " - << argstring - << "\n"; + stream << "\"" << target << "\" " << argstring << "\n"; stream.flush(); f.close(); @@ -813,8 +805,7 @@ bool createShortcut(QString destination, QString target, QStringList args, QStri << "\n"; stream << "Exec=\"" << target.toLocal8Bit() << "\"" << argstring.toLocal8Bit() << "\n"; stream << "Name=" << name.toLocal8Bit() << "\n"; - if (!icon.isEmpty()) - { + if (!icon.isEmpty()) { stream << "Icon=" << icon.toLocal8Bit() << "\n"; } @@ -827,55 +818,45 @@ bool createShortcut(QString destination, QString target, QStringList args, QStri #elif defined(Q_OS_WIN) QFileInfo targetInfo(target); - if (!targetInfo.exists()) - { + if (!targetInfo.exists()) { qWarning() << "Target file does not exist!"; return false; } target = targetInfo.absoluteFilePath(); - if (target.length() >= MAX_PATH) - { + if (target.length() >= MAX_PATH) { qWarning() << "Target file path is too long!"; return false; } - if (!icon.isEmpty() && icon.length() >= MAX_PATH) - { + if (!icon.isEmpty() && icon.length() >= MAX_PATH) { qWarning() << "Icon path is too long!"; return false; } destination += ".lnk"; - if (destination.length() >= MAX_PATH) - { + if (destination.length() >= MAX_PATH) { qWarning() << "Destination path is too long!"; return false; } QString argStr; int argCount = args.count(); - for (int i = 0; i < argCount; i++) - { - if (args[i].contains(' ')) - { + for (int i = 0; i < argCount; i++) { + if (args[i].contains(' ')) { argStr.append('"').append(args[i]).append('"'); - } - else - { + } else { argStr.append(args[i]); } - if (i < argCount - 1) - { + if (i < argCount - 1) { argStr.append(" "); } } - if (argStr.length() >= MAX_PATH) - { + if (argStr.length() >= MAX_PATH) { qWarning() << "Arguments string is too long!"; return false; } @@ -884,8 +865,7 @@ bool createShortcut(QString destination, QString target, QStringList args, QStri // ...yes, you need to initialize the entire COM stack just to make a shortcut hres = CoInitialize(nullptr); - if (FAILED(hres)) - { + if (FAILED(hres)) { qWarning() << "Failed to initialize COM!"; return false; } @@ -896,8 +876,7 @@ bool createShortcut(QString destination, QString target, QStringList args, QStri // create an IShellLink instance - this stores the shortcut's attributes hres = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLink, (LPVOID*)&psl); - if (SUCCEEDED(hres)) - { + if (SUCCEEDED(hres)) { wmemset(wsz, 0, MAX_PATH); target.toWCharArray(wsz); psl->SetPath(wsz); @@ -908,10 +887,9 @@ bool createShortcut(QString destination, QString target, QStringList args, QStri wmemset(wsz, 0, MAX_PATH); targetInfo.absolutePath().toWCharArray(wsz); - psl->SetWorkingDirectory(wsz); // "Starts in" attribute + psl->SetWorkingDirectory(wsz); // "Starts in" attribute - if (!icon.isEmpty()) - { + if (!icon.isEmpty()) { wmemset(wsz, 0, MAX_PATH); icon.toWCharArray(wsz); psl->SetIconLocation(wsz, 0); @@ -921,27 +899,21 @@ bool createShortcut(QString destination, QString target, QStringList args, QStri // this is the interface that will actually let us save the shortcut to disk! IPersistFile* ppf; hres = psl->QueryInterface(IID_IPersistFile, (LPVOID*)&ppf); - if (SUCCEEDED(hres)) - { + if (SUCCEEDED(hres)) { wmemset(wsz, 0, MAX_PATH); destination.toWCharArray(wsz); hres = ppf->Save(wsz, TRUE); - if (FAILED(hres)) - { + if (FAILED(hres)) { qWarning() << "IPresistFile->Save() failed"; qWarning() << "hres = " << hres; } ppf->Release(); - } - else - { + } else { qWarning() << "Failed to query IPersistFile interface from IShellLink instance"; qWarning() << "hres = " << hres; } psl->Release(); - } - else - { + } else { qWarning() << "Failed to create IShellLink instance"; qWarning() << "hres = " << hres; } @@ -977,32 +949,32 @@ bool overrideFolder(QString overwritten_path, QString override_path) return err.value() == 0; } -QString getFilesystemTypeName(FilesystemType type) { +QString getFilesystemTypeName(FilesystemType type) +{ auto iter = s_filesystem_type_names.constFind(type); - if (iter != s_filesystem_type_names.constEnd()){ + if (iter != s_filesystem_type_names.constEnd()) { return iter.value(); } - return getFilesystemTypeName(FilesystemType::UNKNOWN); + return getFilesystemTypeName(FilesystemType::UNKNOWN); } FilesystemType getFilesystemTypeFuzzy(const QString& name) { auto iter = s_filesystem_type_names_inverse.constFind(name.toUpper()); - if (iter != s_filesystem_type_names_inverse.constEnd()){ + if (iter != s_filesystem_type_names_inverse.constEnd()) { return iter.value(); } - return FilesystemType::UNKNOWN; + return FilesystemType::UNKNOWN; } FilesystemType getFilesystemType(const QString& name) -{ +{ for (auto fs_type_pair : s_filesystem_type_names_inverse.toStdMap()) { auto fs_type_name = fs_type_pair.first; auto fs_type = fs_type_pair.second; - if(name.toUpper().contains(fs_type_name.toUpper())) { + if (name.toUpper().contains(fs_type_name.toUpper())) { return fs_type; - } } return FilesystemType::UNKNOWN; @@ -1010,30 +982,29 @@ FilesystemType getFilesystemType(const QString& name) /** * @brief path to the near ancestor that exsists - * + * */ QString NearestExistentAncestor(const QString& path) { - if(QFileInfo::exists(path)) return path; + if (QFileInfo::exists(path)) + return path; QDir dir(path); - if(!dir.makeAbsolute()) return {}; - do - { + if (!dir.makeAbsolute()) + return {}; + do { dir.setPath(QDir::cleanPath(dir.filePath(QStringLiteral("..")))); - } - while(!dir.exists() && !dir.isRoot()); + } while (!dir.exists() && !dir.isRoot()); return dir.exists() ? dir.path() : QString(); } /** * @brief colect information about the filesystem under a file - * + * */ FilesystemInfo statFS(const QString& path) { - FilesystemInfo info; QStorageInfo storage_info(NearestExistentAncestor(path)); @@ -1054,11 +1025,11 @@ FilesystemInfo statFS(const QString& path) } /** - * @brief if the Filesystem is reflink/clone capable - * + * @brief if the Filesystem is reflink/clone capable + * */ bool canCloneOnFS(const QString& path) -{ +{ FilesystemInfo info = statFS(path); return canCloneOnFS(info); } @@ -1067,13 +1038,13 @@ bool canCloneOnFS(const FilesystemInfo& info) return canCloneOnFS(info.fsType); } bool canCloneOnFS(FilesystemType type) -{ +{ return s_clone_filesystems.contains(type); } /** * @brief if the Filesystem is reflink/clone capable and both paths are on the same device - * + * */ bool canClone(const QString& src, const QString& dst) { @@ -1092,7 +1063,6 @@ bool canClone(const QString& src, const QString& dst) */ bool clone::operator()(const QString& offset, bool dryRun) { - if (!canClone(m_src.absolutePath(), m_dst.absolutePath())) { qWarning() << "Can not clone: not same device or not clone/reflink filesystem"; qDebug() << "Source path:" << m_src.absolutePath(); @@ -1149,20 +1119,17 @@ bool clone::operator()(const QString& offset, bool dryRun) /** * @brief clone/reflink file from src to dst - * + * */ bool clone_file(const QString& src, const QString& dst, std::error_code& ec) -{ +{ auto src_path = StringUtils::toStdString(QDir::toNativeSeparators(QFileInfo(src).absoluteFilePath())); auto dst_path = StringUtils::toStdString(QDir::toNativeSeparators(QFileInfo(dst).absoluteFilePath())); FilesystemInfo srcinfo = statFS(src); FilesystemInfo dstinfo = statFS(dst); - - - if ((srcinfo.rootPath != dstinfo.rootPath) || (srcinfo.fsType != dstinfo.fsType)) - { + if ((srcinfo.rootPath != dstinfo.rootPath) || (srcinfo.fsType != dstinfo.fsType)) { ec = std::make_error_code(std::errc::not_supported); qWarning() << "reflink/clone must be to the same device and filesystem! src and dst root filesystems do not match."; return false; @@ -1177,22 +1144,20 @@ bool clone_file(const QString& src, const QString& dst, std::error_code& ec) return false; } - - #elif defined(Q_OS_LINUX) - if(!linux_ficlone(src_path, dst_path, ec)) { + if (!linux_ficlone(src_path, dst_path, ec)) { qDebug() << "failed linux_ficlone:"; return false; } - + #elif defined(Q_OS_MACOS) || defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD) - - if(!macos_bsd_clonefile(src_path, dst_path, ec)) { + + if (!macos_bsd_clonefile(src_path, dst_path, ec)) { qDebug() << "failed macos_bsd_clonefile:"; return false; } - + #else qWarning() << "clone/reflink not supported! unknown OS"; @@ -1217,168 +1182,156 @@ bool win_ioctl_clone(const std::wstring& src_path, const std::wstring& dst_path, /** * This algorithm inspired from https://github.com/0xbadfca11/reflink * LICENSE MIT - * + * * Additional references * https://learn.microsoft.com/en-us/windows/win32/api/winioctl/ni-winioctl-fsctl_duplicate_extents_to_file * https://github.com/microsoft/CopyOnWrite/blob/main/lib/Windows/WindowsCopyOnWriteFilesystem.cs#L94 */ HANDLE hSourceFile = CreateFileW(src_path.c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, 0, nullptr); - if (hSourceFile == INVALID_HANDLE_VALUE) - { - ec = std::error_code(GetLastError(), std::system_category()); + if (hSourceFile == INVALID_HANDLE_VALUE) { + ec = std::error_code(GetLastError(), std::system_category()); qDebug() << "Failed to open source file" << src_path.c_str(); return false; - } + } - ULONG fs_flags; - if (!GetVolumeInformationByHandleW(hSourceFile, nullptr, 0, nullptr, nullptr, &fs_flags, nullptr, 0)) - { - ec = std::error_code(GetLastError(), std::system_category()); + ULONG fs_flags; + if (!GetVolumeInformationByHandleW(hSourceFile, nullptr, 0, nullptr, nullptr, &fs_flags, nullptr, 0)) { + ec = std::error_code(GetLastError(), std::system_category()); qDebug() << "Failed to get Filesystem information for " << src_path.c_str(); CloseHandle(hSourceFile); - return false; - } - if (!(fs_flags & FILE_SUPPORTS_BLOCK_REFCOUNTING)) - { - SetLastError(ERROR_NOT_CAPABLE); - ec = std::error_code(GetLastError(), std::system_category()); + return false; + } + if (!(fs_flags & FILE_SUPPORTS_BLOCK_REFCOUNTING)) { + SetLastError(ERROR_NOT_CAPABLE); + ec = std::error_code(GetLastError(), std::system_category()); qWarning() << "Filesystem at " << src_path.c_str() << " does not support reflink"; CloseHandle(hSourceFile); return false; - } + } - FILE_END_OF_FILE_INFO sourceFileLength; - if (!GetFileSizeEx(hSourceFile, &sourceFileLength.EndOfFile)) - { - ec = std::error_code(GetLastError(), std::system_category()); + FILE_END_OF_FILE_INFO sourceFileLength; + if (!GetFileSizeEx(hSourceFile, &sourceFileLength.EndOfFile)) { + ec = std::error_code(GetLastError(), std::system_category()); qDebug() << "Failed to size of source file" << src_path.c_str(); CloseHandle(hSourceFile); return false; - } - FILE_BASIC_INFO sourceFileBasicInfo; - if (!GetFileInformationByHandleEx(hSourceFile, FileBasicInfo, &sourceFileBasicInfo, sizeof(sourceFileBasicInfo))) - { - ec = std::error_code(GetLastError(), std::system_category()); + } + FILE_BASIC_INFO sourceFileBasicInfo; + if (!GetFileInformationByHandleEx(hSourceFile, FileBasicInfo, &sourceFileBasicInfo, sizeof(sourceFileBasicInfo))) { + ec = std::error_code(GetLastError(), std::system_category()); qDebug() << "Failed to source file info" << src_path.c_str(); CloseHandle(hSourceFile); - return false; - } - ULONG junk; - FSCTL_GET_INTEGRITY_INFORMATION_BUFFER sourceFileIntegrity; - if (!DeviceIoControl(hSourceFile, FSCTL_GET_INTEGRITY_INFORMATION, nullptr, 0, &sourceFileIntegrity, sizeof(sourceFileIntegrity), &junk, nullptr)) - { - ec = std::error_code(GetLastError(), std::system_category()); + return false; + } + ULONG junk; + FSCTL_GET_INTEGRITY_INFORMATION_BUFFER sourceFileIntegrity; + if (!DeviceIoControl(hSourceFile, FSCTL_GET_INTEGRITY_INFORMATION, nullptr, 0, &sourceFileIntegrity, sizeof(sourceFileIntegrity), &junk, + nullptr)) { + ec = std::error_code(GetLastError(), std::system_category()); qDebug() << "Failed to source file integrity info" << src_path.c_str(); CloseHandle(hSourceFile); - return false; - } + return false; + } - HANDLE hDestFile = CreateFileW(dst_path.c_str(), GENERIC_READ | GENERIC_WRITE | DELETE, 0, nullptr, CREATE_NEW, 0, hSourceFile); + HANDLE hDestFile = CreateFileW(dst_path.c_str(), GENERIC_READ | GENERIC_WRITE | DELETE, 0, nullptr, CREATE_NEW, 0, hSourceFile); - if (hDestFile == INVALID_HANDLE_VALUE) - { - ec = std::error_code(GetLastError(), std::system_category()); + if (hDestFile == INVALID_HANDLE_VALUE) { + ec = std::error_code(GetLastError(), std::system_category()); qDebug() << "Failed to open dest file" << dst_path.c_str(); CloseHandle(hSourceFile); - return false; - } - FILE_DISPOSITION_INFO destFileDispose = { TRUE }; - if (!SetFileInformationByHandle(hDestFile, FileDispositionInfo, &destFileDispose, sizeof(destFileDispose))) - { - ec = std::error_code(GetLastError(), std::system_category()); + return false; + } + FILE_DISPOSITION_INFO destFileDispose = { TRUE }; + if (!SetFileInformationByHandle(hDestFile, FileDispositionInfo, &destFileDispose, sizeof(destFileDispose))) { + ec = std::error_code(GetLastError(), std::system_category()); qDebug() << "Failed to set dest file info" << dst_path.c_str(); CloseHandle(hSourceFile); CloseHandle(hDestFile); - return false; - } + return false; + } - if (!DeviceIoControl(hDestFile, FSCTL_SET_SPARSE, nullptr, 0, nullptr, 0, &junk, nullptr)) - { - ec = std::error_code(GetLastError(), std::system_category()); + if (!DeviceIoControl(hDestFile, FSCTL_SET_SPARSE, nullptr, 0, nullptr, 0, &junk, nullptr)) { + ec = std::error_code(GetLastError(), std::system_category()); qDebug() << "Failed to set dest sparseness" << dst_path.c_str(); CloseHandle(hSourceFile); CloseHandle(hDestFile); - return false; - } - FSCTL_SET_INTEGRITY_INFORMATION_BUFFER setDestFileintegrity = { sourceFileIntegrity.ChecksumAlgorithm, sourceFileIntegrity.Reserved, sourceFileIntegrity.Flags }; - if (!DeviceIoControl(hDestFile, FSCTL_SET_INTEGRITY_INFORMATION, &setDestFileintegrity, sizeof(setDestFileintegrity), nullptr, 0, nullptr, nullptr)) - { - ec = std::error_code(GetLastError(), std::system_category()); + return false; + } + FSCTL_SET_INTEGRITY_INFORMATION_BUFFER setDestFileintegrity = { sourceFileIntegrity.ChecksumAlgorithm, sourceFileIntegrity.Reserved, + sourceFileIntegrity.Flags }; + if (!DeviceIoControl(hDestFile, FSCTL_SET_INTEGRITY_INFORMATION, &setDestFileintegrity, sizeof(setDestFileintegrity), nullptr, 0, + nullptr, nullptr)) { + ec = std::error_code(GetLastError(), std::system_category()); qDebug() << "Failed to set dest file integrity info" << dst_path.c_str(); CloseHandle(hSourceFile); CloseHandle(hDestFile); - return false; - } - if (!SetFileInformationByHandle(hDestFile, FileEndOfFileInfo, &sourceFileLength, sizeof(sourceFileLength))) - { - ec = std::error_code(GetLastError(), std::system_category()); + return false; + } + if (!SetFileInformationByHandle(hDestFile, FileEndOfFileInfo, &sourceFileLength, sizeof(sourceFileLength))) { + ec = std::error_code(GetLastError(), std::system_category()); qDebug() << "Failed to set dest file size" << dst_path.c_str(); CloseHandle(hSourceFile); CloseHandle(hDestFile); - return false; - } + return false; + } - const LONG64 splitThreshold = (1LL << 32) - sourceFileIntegrity.ClusterSizeInBytes; + const LONG64 splitThreshold = (1LL << 32) - sourceFileIntegrity.ClusterSizeInBytes; - DUPLICATE_EXTENTS_DATA dupExtent; - dupExtent.FileHandle = hSourceFile; - for (LONG64 offset = 0, remain = RoundUpToPowerOf2(sourceFileLength.EndOfFile.QuadPart, sourceFileIntegrity.ClusterSizeInBytes); remain > 0; offset += splitThreshold, remain -= splitThreshold) - { - dupExtent.SourceFileOffset.QuadPart = dupExtent.TargetFileOffset.QuadPart = offset; - dupExtent.ByteCount.QuadPart = std::min(splitThreshold, remain); + DUPLICATE_EXTENTS_DATA dupExtent; + dupExtent.FileHandle = hSourceFile; + for (LONG64 offset = 0, remain = RoundUpToPowerOf2(sourceFileLength.EndOfFile.QuadPart, sourceFileIntegrity.ClusterSizeInBytes); + remain > 0; offset += splitThreshold, remain -= splitThreshold) { + dupExtent.SourceFileOffset.QuadPart = dupExtent.TargetFileOffset.QuadPart = offset; + dupExtent.ByteCount.QuadPart = std::min(splitThreshold, remain); - if (!DeviceIoControl(hDestFile, FSCTL_DUPLICATE_EXTENTS_TO_FILE, &dupExtent, sizeof(dupExtent), nullptr, 0, &junk, nullptr)) - { + if (!DeviceIoControl(hDestFile, FSCTL_DUPLICATE_EXTENTS_TO_FILE, &dupExtent, sizeof(dupExtent), nullptr, 0, &junk, nullptr)) { DWORD err = GetLastError(); QString additionalMessage; - if (err == ERROR_BLOCK_TOO_MANY_REFERENCES) - { + if (err == ERROR_BLOCK_TOO_MANY_REFERENCES) { static const int MaxClonesPerFile = 8175; - additionalMessage = QString( - " This is ERROR_BLOCK_TOO_MANY_REFERENCES and may mean you have surpassed the maximum " - "allowed %1 references for a single file. " - "See https://docs.microsoft.com/en-us/windows-server/storage/refs/block-cloning#functionality-restrictions-and-remarks" - ).arg(MaxClonesPerFile); - + additionalMessage = + QString( + " This is ERROR_BLOCK_TOO_MANY_REFERENCES and may mean you have surpassed the maximum " + "allowed %1 references for a single file. " + "See " + "https://docs.microsoft.com/en-us/windows-server/storage/refs/block-cloning#functionality-restrictions-and-remarks") + .arg(MaxClonesPerFile); } - ec = std::error_code(err, std::system_category()); - qDebug() << "Failed copy-on-write cloning of" << src_path.c_str() << "to" << dst_path.c_str() << "with error" << err << additionalMessage; + ec = std::error_code(err, std::system_category()); + qDebug() << "Failed copy-on-write cloning of" << src_path.c_str() << "to" << dst_path.c_str() << "with error" << err + << additionalMessage; CloseHandle(hSourceFile); CloseHandle(hDestFile); - return false; - } - } + return false; + } + } - if (!(sourceFileBasicInfo.FileAttributes & FILE_ATTRIBUTE_SPARSE_FILE)) - { - FILE_SET_SPARSE_BUFFER setDestSparse = { FALSE }; - if (!DeviceIoControl(hDestFile, FSCTL_SET_SPARSE, &setDestSparse, sizeof(setDestSparse), nullptr, 0, &junk, nullptr)) - { + if (!(sourceFileBasicInfo.FileAttributes & FILE_ATTRIBUTE_SPARSE_FILE)) { + FILE_SET_SPARSE_BUFFER setDestSparse = { FALSE }; + if (!DeviceIoControl(hDestFile, FSCTL_SET_SPARSE, &setDestSparse, sizeof(setDestSparse), nullptr, 0, &junk, nullptr)) { qDebug() << "Failed to set dest file sparseness" << dst_path.c_str(); CloseHandle(hSourceFile); CloseHandle(hDestFile); - return false; - } - } + return false; + } + } - sourceFileBasicInfo.CreationTime.QuadPart = 0; - if (!SetFileInformationByHandle(hDestFile, FileBasicInfo, &sourceFileBasicInfo, sizeof(sourceFileBasicInfo))) - { + sourceFileBasicInfo.CreationTime.QuadPart = 0; + if (!SetFileInformationByHandle(hDestFile, FileBasicInfo, &sourceFileBasicInfo, sizeof(sourceFileBasicInfo))) { qDebug() << "Failed to set dest file creation time" << dst_path.c_str(); CloseHandle(hSourceFile); CloseHandle(hDestFile); - return false; - } - if (!FlushFileBuffers(hDestFile)) - { + return false; + } + if (!FlushFileBuffers(hDestFile)) { qDebug() << "Failed to flush dest file buffer" << dst_path.c_str(); CloseHandle(hSourceFile); CloseHandle(hDestFile); - return false; - } - destFileDispose = { FALSE }; - bool result = !!SetFileInformationByHandle(hDestFile, FileDispositionInfo, &destFileDispose, sizeof(destFileDispose)); + return false; + } + destFileDispose = { FALSE }; + bool result = !!SetFileInformationByHandle(hDestFile, FileDispositionInfo, &destFileDispose, sizeof(destFileDispose)); CloseHandle(hSourceFile); CloseHandle(hDestFile); @@ -1393,14 +1346,14 @@ bool linux_ficlone(const std::string& src_path, const std::string& dst_path, std // https://man7.org/linux/man-pages/man2/ioctl_ficlone.2.html int src_fd = open(src_path.c_str(), O_RDONLY); - if(src_fd == -1) { + if (src_fd == -1) { qDebug() << "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_CREAT | O_WRONLY | O_TRUNC, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH); - if(dst_fd == -1) { + if (dst_fd == -1) { qDebug() << "Failed to open file:" << dst_path.c_str(); qDebug() << "Error:" << strerror(errno); ec = std::make_error_code(static_cast(errno)); @@ -1408,7 +1361,7 @@ bool linux_ficlone(const std::string& src_path, const std::string& dst_path, std return false; } // attempt to clone - if(ioctl(dst_fd, FICLONE, src_fd) == -1){ + if (ioctl(dst_fd, FICLONE, src_fd) == -1) { qDebug() << "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)); @@ -1416,11 +1369,11 @@ bool linux_ficlone(const std::string& src_path, const std::string& dst_path, std close(dst_fd); return false; } - if(close(src_fd)) { + if (close(src_fd)) { qDebug() << "Failed to close file:" << src_path.c_str(); qDebug() << "Error:" << strerror(errno); } - if(close(dst_fd)) { + if (close(dst_fd)) { qDebug() << "Failed to close file:" << dst_path.c_str(); qDebug() << "Error:" << strerror(errno); } @@ -1446,8 +1399,8 @@ bool macos_bsd_clonefile(const std::string& src_path, const std::string& dst_pat #endif /** - * @brief if the Filesystem is symlink capable - * + * @brief if the Filesystem is symlink capable + * */ bool canLinkOnFS(const QString& path) { @@ -1464,22 +1417,22 @@ bool canLinkOnFS(FilesystemType type) } /** * @brief if the Filesystem is symlink capable on both ends - * + * */ bool canLink(const QString& src, const QString& dst) { - return canLinkOnFS(src) && canLinkOnFS(dst); + return canLinkOnFS(src) && canLinkOnFS(dst); } uintmax_t hardLinkCount(const QString& path) { std::error_code err; - int count = fs::hard_link_count(StringUtils::toStdString(path), err); + int count = fs::hard_link_count(StringUtils::toStdString(path), err); if (err) { - qWarning() << "Failed to count hard links for" << path << ":" << QString::fromStdString(err.message()); - count = 0; + qWarning() << "Failed to count hard links for" << path << ":" << QString::fromStdString(err.message()); + count = 0; } return count; } -} +} // namespace FS 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: [PATCH 102/185] 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(-) 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 + + + + 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: [PATCH 103/185] 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(-) 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; From 44bf32e729294d8ede069f9fd952b9104b34b695 Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Tue, 21 Mar 2023 18:28:32 +0100 Subject: [PATCH 104/185] fix: handle partial lines in LoggedProcess Fixes PrismLauncher/PrismLauncher#930 Co-authored-by: flow Signed-off-by: Sefa Eyeoglu --- launcher/LoggedProcess.cpp | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/launcher/LoggedProcess.cpp b/launcher/LoggedProcess.cpp index 6447f5c6..43a9c4f6 100644 --- a/launcher/LoggedProcess.cpp +++ b/launcher/LoggedProcess.cpp @@ -1,7 +1,8 @@ // SPDX-License-Identifier: GPL-3.0-only /* - * PolyMC - Minecraft Launcher - * Copyright (C) 2022 Sefa Eyeoglu + * Prism Launcher - Minecraft Launcher + * Copyright (C) 2022,2023 Sefa Eyeoglu + * Copyright (c) 2023 flowln * * 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 @@ -60,14 +61,26 @@ LoggedProcess::~LoggedProcess() } } +static QString m_leftover_line; + QStringList reprocess(const QByteArray& data, QTextDecoder& decoder) { auto str = decoder.toUnicode(data); + + // FIXME: Flush this out when process exits + if (!m_leftover_line.isEmpty()) { + str.prepend(m_leftover_line); + m_leftover_line = ""; + } + #if QT_VERSION < QT_VERSION_CHECK(5, 14, 0) auto lines = str.remove(QChar::CarriageReturn).split(QChar::LineFeed, QString::SkipEmptyParts); #else auto lines = str.remove(QChar::CarriageReturn).split(QChar::LineFeed, Qt::SkipEmptyParts); #endif + + if (!str.endsWith(QChar::LineFeed)) + m_leftover_line = lines.takeLast(); return lines; } From 77932061bc244b301a9697281880ed2a8bae9e31 Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Tue, 21 Mar 2023 18:33:41 +0100 Subject: [PATCH 105/185] chore: update ignores for Nix Signed-off-by: Sefa Eyeoglu --- .gitignore | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/.gitignore b/.gitignore index 3340670b..5c589a5f 100644 --- a/.gitignore +++ b/.gitignore @@ -24,10 +24,6 @@ Debug build /build-* -# direnv / Nix -.direnv/ -.pre-commit-config.yaml - # Install dirs install /install-* @@ -48,7 +44,9 @@ run/ .cache/ # Nix/NixOS -result/ +.direnv/ +.pre-commit-config.yaml +result # Flatpak .flatpak-builder 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: [PATCH 106/185] 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(-) 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); 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: [PATCH 107/185] 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(-) 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); } } From 9418c62d95d8a9e829b087386cba2e2b6137daf1 Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Wed, 22 Mar 2023 10:32:17 +0100 Subject: [PATCH 108/185] refactor: reprocess log lines per instance Signed-off-by: Sefa Eyeoglu --- launcher/LoggedProcess.cpp | 5 +---- launcher/LoggedProcess.h | 7 +++++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/launcher/LoggedProcess.cpp b/launcher/LoggedProcess.cpp index 43a9c4f6..c8d5c34e 100644 --- a/launcher/LoggedProcess.cpp +++ b/launcher/LoggedProcess.cpp @@ -61,13 +61,10 @@ LoggedProcess::~LoggedProcess() } } -static QString m_leftover_line; - -QStringList reprocess(const QByteArray& data, QTextDecoder& decoder) +QStringList LoggedProcess::reprocess(const QByteArray& data, QTextDecoder& decoder) { auto str = decoder.toUnicode(data); - // FIXME: Flush this out when process exits if (!m_leftover_line.isEmpty()) { str.prepend(m_leftover_line); m_leftover_line = ""; diff --git a/launcher/LoggedProcess.h b/launcher/LoggedProcess.h index 2360d1ea..af3ed79f 100644 --- a/launcher/LoggedProcess.h +++ b/launcher/LoggedProcess.h @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-3.0-only /* - * PolyMC - Minecraft Launcher - * Copyright (C) 2022 Sefa Eyeoglu + * Prism Launcher - Minecraft Launcher + * Copyright (C) 2022,2023 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 @@ -88,9 +88,12 @@ private slots: private: void changeState(LoggedProcess::State state); + QStringList reprocess(const QByteArray& data, QTextDecoder& decoder); + private: QTextDecoder m_err_decoder = QTextDecoder(QTextCodec::codecForLocale()); QTextDecoder m_out_decoder = QTextDecoder(QTextCodec::codecForLocale()); + QString m_leftover_line; bool m_killed = false; State m_state = NotRunning; int m_exit_code = 0; From 4c013e59f0e9a481bc63281c0d9e349827419d37 Mon Sep 17 00:00:00 2001 From: DioEgizio <83089242+DioEgizio@users.noreply.github.com> Date: Sat, 25 Mar 2023 10:45:34 +0100 Subject: [PATCH 109/185] divide minecraftpage into tabs this way small screen users can use the launcher settings without having window a bigger than their actual screens Signed-off-by: DioEgizio <83089242+DioEgizio@users.noreply.github.com> --- launcher/ui/pages/global/MinecraftPage.cpp | 1 - launcher/ui/pages/global/MinecraftPage.ui | 153 +++++++++++---------- 2 files changed, 84 insertions(+), 70 deletions(-) diff --git a/launcher/ui/pages/global/MinecraftPage.cpp b/launcher/ui/pages/global/MinecraftPage.cpp index cc597fe0..eca3e865 100644 --- a/launcher/ui/pages/global/MinecraftPage.cpp +++ b/launcher/ui/pages/global/MinecraftPage.cpp @@ -46,7 +46,6 @@ MinecraftPage::MinecraftPage(QWidget *parent) : QWidget(parent), ui(new Ui::MinecraftPage) { ui->setupUi(this); - ui->tabWidget->tabBar()->hide(); loadSettings(); updateCheckboxStuff(); } diff --git a/launcher/ui/pages/global/MinecraftPage.ui b/launcher/ui/pages/global/MinecraftPage.ui index 640f436d..cff071bf 100644 --- a/launcher/ui/pages/global/MinecraftPage.ui +++ b/launcher/ui/pages/global/MinecraftPage.ui @@ -7,7 +7,7 @@ 0 0 936 - 1134 + 541 @@ -39,7 +39,7 @@ - Minecraft + General @@ -111,68 +111,6 @@ - - - - Native library workarounds - - - - - - Use system installation of &GLFW - - - - - - - Use system installation of &OpenAL - - - - - - - - - - Performance - - - - - - <html><head/><body><p>Enable Feral Interactive's GameMode, to potentially improve gaming performance.</p></body></html> - - - Enable Feral GameMode - - - - - - - <html><head/><body><p>Enable MangoHud's advanced performance overlay.</p></body></html> - - - Enable MangoHud - - - - - - - <html><head/><body><p>Use the discrete GPU instead of the primary GPU.</p></body></html> - - - Use discrete GPU - - - - - - @@ -247,6 +185,88 @@ + + + System-related tweaks + + + + + + Native library workarounds + + + + + + Use system installation of &GLFW + + + + + + + Use system installation of &OpenAL + + + + + + + + + + Performance + + + + + + <html><head/><body><p>Enable Feral Interactive's GameMode, to potentially improve gaming performance.</p></body></html> + + + Enable Feral GameMode + + + + + + + <html><head/><body><p>Enable MangoHud's advanced performance overlay.</p></body></html> + + + Enable MangoHud + + + + + + + <html><head/><body><p>Use the discrete GPU instead of the primary GPU.</p></body></html> + + + Use discrete GPU + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + @@ -255,11 +275,6 @@ maximizedCheckBox windowWidthSpinBox windowHeightSpinBox - useNativeGLFWCheck - useNativeOpenALCheck - enableFeralGamemodeCheck - enableMangoHud - useDiscreteGpuCheck 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: [PATCH 110/185] 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(-) 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,6 +123,20 @@ + + + + ... + + + + + + + ... + + + @@ -127,23 +147,13 @@ - - - - &Downloads: + + + + when looking for mods in places like the blocked mods dialog Prismlauncher will check in sub folders of your downloads folder too. - - downloadsDirTextBox - - - - - - - - - ... + Check downloads folder recursively 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: [PATCH 111/185] 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(-) 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); } } From c7dc115365006a8bff8ca93a01a6df4134eb92e9 Mon Sep 17 00:00:00 2001 From: seth Date: Fri, 31 Mar 2023 11:13:18 -0400 Subject: [PATCH 112/185] fix: search for newer mangohud vulkan layers Signed-off-by: seth --- launcher/MangoHud.cpp | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/launcher/MangoHud.cpp b/launcher/MangoHud.cpp index d635518e..076d3064 100644 --- a/launcher/MangoHud.cpp +++ b/launcher/MangoHud.cpp @@ -75,9 +75,23 @@ QString getLibraryString() } for (QString vkLayer : vkLayerList) { - QString filePath = FS::PathCombine(vkLayer, "MangoHud.json"); - if (!QFile::exists(filePath)) - continue; + QStringList vkArchitectures = { "x86_64", "aarch64" }; + + QString filePath = ""; + // prefer to use architecture specific vulkan layers + for (QString arch: vkArchitectures) { + QString tryPath = FS::PathCombine(vkLayer, QString("MangoHud.%1.json").arg(arch)); + if (QFile::exists(tryPath)) { + filePath = tryPath; + } + } + + if (filePath.isEmpty()) { + filePath = FS::PathCombine(vkLayer, "MangoHud.json"); + // bail out if no mangohud layers are found + if (!QFile::exists(filePath)) + continue; + } auto conf = Json::requireDocument(filePath, vkLayer); auto confObject = Json::requireObject(conf, vkLayer); From 3e3b92d4c1a93f1430b7d222622758d0da1f4ac8 Mon Sep 17 00:00:00 2001 From: seth Date: Fri, 31 Mar 2023 20:09:26 +0000 Subject: [PATCH 113/185] chore: improve detection of newer vulkan layers i've been scrump'd Co-authored-by: Sefa Eyeoglu Signed-off-by: seth --- launcher/MangoHud.cpp | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/launcher/MangoHud.cpp b/launcher/MangoHud.cpp index 076d3064..890aca57 100644 --- a/launcher/MangoHud.cpp +++ b/launcher/MangoHud.cpp @@ -75,23 +75,20 @@ QString getLibraryString() } for (QString vkLayer : vkLayerList) { - QStringList vkArchitectures = { "x86_64", "aarch64" }; + // prefer to use architecture specific vulkan layers + QStringList manifestNames = { "MangoHud.x86_64.json", "MangoHud.aarch64.json", "MangoHud.json" }; QString filePath = ""; - // prefer to use architecture specific vulkan layers - for (QString arch: vkArchitectures) { - QString tryPath = FS::PathCombine(vkLayer, QString("MangoHud.%1.json").arg(arch)); + for (QString manifestName : manifestNames) { + QString tryPath = FS::PathCombine(vkLayer, manifestName); if (QFile::exists(tryPath)) { - filePath = tryPath; + filePath = tryPath; + break; } } - if (filePath.isEmpty()) { - filePath = FS::PathCombine(vkLayer, "MangoHud.json"); - // bail out if no mangohud layers are found - if (!QFile::exists(filePath)) - continue; - } + if (filePath.isEmpty()) + continue; auto conf = Json::requireDocument(filePath, vkLayer); auto confObject = Json::requireObject(conf, vkLayer); From 4df4b4390086171b9ee78a8cd7efb32412292485 Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Fri, 31 Mar 2023 18:25:29 -0700 Subject: [PATCH 114/185] fix: Apply suggestions from code review (string changes) Co-authored-by: flow Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- launcher/ui/MainWindow.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp index a6aa8320..e20a7613 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 folowing instance(s) might reference files in this instance:\n\n" "%1\n\n" "Deleting it could break the other instance(s), \n\n" - "Are you sure?").arg(linkedInstances.join("\n")), + "Do you wish to proceed?").arg(linkedInstances.join("\n")), QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No, QMessageBox::No )->exec(); if (response != QMessageBox::Yes) From 538092b72728fa34bafc873e16abaa7f318a945c Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Fri, 31 Mar 2023 20:22:07 -0700 Subject: [PATCH 115/185] fix: typos, CamelCase to camelCase the new names Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- launcher/FileSystem.cpp | 30 +++++++------- launcher/FileSystem.h | 87 ++++++++++++++++++++------------------- tests/FileSystem_test.cpp | 40 +++++++++--------- 3 files changed, 81 insertions(+), 76 deletions(-) diff --git a/launcher/FileSystem.cpp b/launcher/FileSystem.cpp index 640cf9be..c046ee86 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 to + * @brief Make a list of all the links to make * @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 recursively:" << 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); @@ -373,8 +373,8 @@ void create_link::make_link_list(const QString& offset) 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); + 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; @@ -394,20 +394,22 @@ bool create_link::make_links() for (auto link : m_links_to_make) { QString src_path = link.src; QString dst_path = link.dst; + auto src_path_std = StringUtils::toStdString(link.src); + auto dst_path_std = StringUtils::toStdString(link.dst); 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), m_os_err); - } else if (fs::is_directory(StringUtils::toStdString(src_path))) { + fs::create_hard_link(src_path_std, dst_path_std, m_os_err); + } else if (fs::is_directory(src_path_std)) { if (m_debug) qDebug() << "making directory_symlink:" << src_path << "to" << dst_path; - fs::create_directory_symlink(StringUtils::toStdString(src_path), StringUtils::toStdString(dst_path), m_os_err); + fs::create_directory_symlink(src_path_std, dst_path_std, m_os_err); } else { if (m_debug) qDebug() << "making symlink:" << src_path << "to" << dst_path; - fs::create_symlink(StringUtils::toStdString(src_path), StringUtils::toStdString(dst_path), m_os_err); + fs::create_symlink(src_path_std, dst_path_std, m_os_err); } if (m_os_err) { @@ -636,7 +638,7 @@ QString AbsolutePath(const QString& path) return QFileInfo(path).absolutePath(); } -int PathDepth(const QString& path) +int pathDepth(const QString& path) { if (path.isEmpty()) return 0; @@ -656,15 +658,15 @@ int PathDepth(const QString& path) return numParts; } -QString PathTruncate(const QString& path, int depth) +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); + if (pathDepth(trunc) > depth ) { + return pathTruncate(trunc, depth); } #if QT_VERSION < QT_VERSION_CHECK(5, 14, 0) @@ -984,7 +986,7 @@ FilesystemType getFilesystemType(const QString& name) * @brief path to the near ancestor that exists * */ -QString NearestExistentAncestor(const QString& path) +QString nearestExistentAncestor(const QString& path) { if (QFileInfo::exists(path)) return path; @@ -1007,7 +1009,7 @@ FilesystemInfo statFS(const QString& path) { FilesystemInfo info; - QStorageInfo storage_info(NearestExistentAncestor(path)); + QStorageInfo storage_info(nearestExistentAncestor(path)); info.fsTypeName = storage_info.fileSystemType(); diff --git a/launcher/FileSystem.h b/launcher/FileSystem.h index 673c3563..47044d93 100644 --- a/launcher/FileSystem.h +++ b/launcher/FileSystem.h @@ -280,7 +280,7 @@ QString AbsolutePath(const QString& path); * @param path path to measure * @return int number of components before base path */ -int PathDepth(const QString& path); +int pathDepth(const QString& path); /** * @brief cut off segments of path until it is a max of length depth @@ -289,7 +289,7 @@ int PathDepth(const QString& path); * @param depth max depth of new path * @return QString truncated path */ -QString PathTruncate(const QString& path, int depth); +QString pathTruncate(const QString& path, int depth); /** * Resolve an executable @@ -360,23 +360,26 @@ enum class FilesystemType { * 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, QStringLiteral("FAT")}, + {FilesystemType::NTFS, QStringLiteral("NTFS")}, + {FilesystemType::REFS, QStringLiteral("REFS")}, + {FilesystemType::EXT, QStringLiteral("EXT")}, + {FilesystemType::EXT_2_OLD, QStringLiteral("EXT_2_OLD")}, + {FilesystemType::EXT_2_3_4, QStringLiteral("EXT2/3/4")}, + {FilesystemType::XFS, QStringLiteral("XFS")}, + {FilesystemType::BTRFS, QStringLiteral("BTRFS")}, + {FilesystemType::NFS, QStringLiteral("NFS")}, + {FilesystemType::ZFS, QStringLiteral("ZFS")}, + {FilesystemType::APFS, QStringLiteral("APFS")}, + {FilesystemType::HFS, QStringLiteral("HFS")}, + {FilesystemType::HFSPLUS, QStringLiteral("HFSPLUS")}, + {FilesystemType::HFSX, QStringLiteral("HFSX")}, + {FilesystemType::FUSEBLK, QStringLiteral("FUSEBLK")}, + {FilesystemType::F2FS, QStringLiteral("F2FS")}, + {FilesystemType::UNKNOWN, QStringLiteral("UNKNOWN")} +}; + /** * @brief Ordered Mapping of reported filesystem names to enum types @@ -387,28 +390,28 @@ static const QMap s_filesystem_type_names = { { Filesys * */ 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 } + {QStringLiteral("FAT"), FilesystemType::FAT}, + {QStringLiteral("NTFS"), FilesystemType::NTFS}, + {QStringLiteral("REFS"), FilesystemType::REFS}, + {QStringLiteral("EXT2_OLD"), FilesystemType::EXT_2_OLD}, + {QStringLiteral("EXT_2_OLD"), FilesystemType::EXT_2_OLD}, + {QStringLiteral("EXT2"), FilesystemType::EXT_2_3_4}, + {QStringLiteral("EXT3"), FilesystemType::EXT_2_3_4}, + {QStringLiteral("EXT4"), FilesystemType::EXT_2_3_4}, + {QStringLiteral("EXT2/3/4"), FilesystemType::EXT_2_3_4}, + {QStringLiteral("EXT_2_3_4"), FilesystemType::EXT_2_3_4}, + {QStringLiteral("EXT"), FilesystemType::EXT}, // must come after all other EXT variants to prevent greedy detection + {QStringLiteral("XFS"), FilesystemType::XFS}, + {QStringLiteral("BTRFS"), FilesystemType::BTRFS}, + {QStringLiteral("NFS"), FilesystemType::NFS}, + {QStringLiteral("ZFS"), FilesystemType::ZFS}, + {QStringLiteral("APFS"), FilesystemType::APFS}, + {QStringLiteral("HFSPLUS"), FilesystemType::HFSPLUS}, + {QStringLiteral("HFSX"), FilesystemType::HFSX}, + {QStringLiteral("HFS"), FilesystemType::HFS}, + {QStringLiteral("FUSEBLK"), FilesystemType::FUSEBLK}, + {QStringLiteral("F2FS"), FilesystemType::F2FS}, + {QStringLiteral("UNKNOWN"), FilesystemType::UNKNOWN} }; /** @@ -452,7 +455,7 @@ struct FilesystemInfo { * @brief path to the near ancestor that exists * */ -QString NearestExistentAncestor(const QString& path); +QString nearestExistentAncestor(const QString& path); /** * @brief colect information about the filesystem under a file diff --git a/tests/FileSystem_test.cpp b/tests/FileSystem_test.cpp index 19565a99..ec1f0bcf 100644 --- a/tests/FileSystem_test.cpp +++ b/tests/FileSystem_test.cpp @@ -756,30 +756,30 @@ slots: } void test_path_depth() { - QCOMPARE(FS::PathDepth(""), 0); - QCOMPARE(FS::PathDepth("."), 0); - QCOMPARE(FS::PathDepth("foo.txt"), 0); - QCOMPARE(FS::PathDepth("./foo.txt"), 0); - QCOMPARE(FS::PathDepth("./bar/foo.txt"), 1); - QCOMPARE(FS::PathDepth("../bar/foo.txt"), 0); - QCOMPARE(FS::PathDepth("/bar/foo.txt"), 1); - QCOMPARE(FS::PathDepth("baz/bar/foo.txt"), 2); - QCOMPARE(FS::PathDepth("/baz/bar/foo.txt"), 2); - QCOMPARE(FS::PathDepth("./baz/bar/foo.txt"), 2); - QCOMPARE(FS::PathDepth("/baz/../bar/foo.txt"), 1); + QCOMPARE(FS::pathDepth(""), 0); + QCOMPARE(FS::pathDepth("."), 0); + QCOMPARE(FS::pathDepth("foo.txt"), 0); + QCOMPARE(FS::pathDepth("./foo.txt"), 0); + QCOMPARE(FS::pathDepth("./bar/foo.txt"), 1); + QCOMPARE(FS::pathDepth("../bar/foo.txt"), 0); + QCOMPARE(FS::pathDepth("/bar/foo.txt"), 1); + QCOMPARE(FS::pathDepth("baz/bar/foo.txt"), 2); + QCOMPARE(FS::pathDepth("/baz/bar/foo.txt"), 2); + QCOMPARE(FS::pathDepth("./baz/bar/foo.txt"), 2); + QCOMPARE(FS::pathDepth("/baz/../bar/foo.txt"), 1); } void test_path_trunc() { - QCOMPARE(FS::PathTruncate("", 0), QDir::toNativeSeparators("")); - QCOMPARE(FS::PathTruncate("foo.txt", 0), QDir::toNativeSeparators("")); - QCOMPARE(FS::PathTruncate("foo.txt", 1), QDir::toNativeSeparators("")); - QCOMPARE(FS::PathTruncate("./bar/foo.txt", 0), QDir::toNativeSeparators("./bar")); - QCOMPARE(FS::PathTruncate("./bar/foo.txt", 1), QDir::toNativeSeparators("./bar")); - QCOMPARE(FS::PathTruncate("/bar/foo.txt", 1), QDir::toNativeSeparators("/bar")); - QCOMPARE(FS::PathTruncate("bar/foo.txt", 1), QDir::toNativeSeparators("bar")); - QCOMPARE(FS::PathTruncate("baz/bar/foo.txt", 2), QDir::toNativeSeparators("baz/bar")); + QCOMPARE(FS::pathTruncate("", 0), QDir::toNativeSeparators("")); + QCOMPARE(FS::pathTruncate("foo.txt", 0), QDir::toNativeSeparators("")); + QCOMPARE(FS::pathTruncate("foo.txt", 1), QDir::toNativeSeparators("")); + QCOMPARE(FS::pathTruncate("./bar/foo.txt", 0), QDir::toNativeSeparators("./bar")); + QCOMPARE(FS::pathTruncate("./bar/foo.txt", 1), QDir::toNativeSeparators("./bar")); + QCOMPARE(FS::pathTruncate("/bar/foo.txt", 1), QDir::toNativeSeparators("/bar")); + QCOMPARE(FS::pathTruncate("bar/foo.txt", 1), QDir::toNativeSeparators("bar")); + QCOMPARE(FS::pathTruncate("baz/bar/foo.txt", 2), QDir::toNativeSeparators("baz/bar")); #if defined(Q_OS_WIN) - QCOMPARE(FS::PathTruncate("C:\\bar\\foo.txt", 1), QDir::toNativeSeparators("C:\\bar")); + QCOMPARE(FS::pathTruncate("C:\\bar\\foo.txt", 1), QDir::toNativeSeparators("C:\\bar")); #endif } }; From 81b140629070c96853ccdc50fe30256ffe1c0636 Mon Sep 17 00:00:00 2001 From: seth Date: Sat, 1 Apr 2023 12:58:15 -0400 Subject: [PATCH 116/185] feat(nix): use ninja for builds Signed-off-by: seth --- nix/default.nix | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/nix/default.nix b/nix/default.nix index 6d4f3f24..cd782f90 100644 --- a/nix/default.nix +++ b/nix/default.nix @@ -2,6 +2,7 @@ lib, stdenv, cmake, + ninja, jdk8, jdk17, zlib, @@ -33,7 +34,7 @@ stdenv.mkDerivation rec { src = lib.cleanSource self; - nativeBuildInputs = [extra-cmake-modules cmake file jdk17 wrapQtAppsHook]; + nativeBuildInputs = [extra-cmake-modules cmake file jdk17 ninja wrapQtAppsHook]; buildInputs = [ qtbase From 4055e34320e738753a6e8f5431790f078daf974b Mon Sep 17 00:00:00 2001 From: seth Date: Sat, 1 Apr 2023 12:56:09 -0400 Subject: [PATCH 117/185] chore: use system architecture to detect vulkan layers Signed-off-by: seth --- launcher/MangoHud.cpp | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/launcher/MangoHud.cpp b/launcher/MangoHud.cpp index 890aca57..90e48e29 100644 --- a/launcher/MangoHud.cpp +++ b/launcher/MangoHud.cpp @@ -19,6 +19,7 @@ #include #include #include +#include #include #include "MangoHud.h" @@ -76,7 +77,13 @@ QString getLibraryString() for (QString vkLayer : vkLayerList) { // prefer to use architecture specific vulkan layers - QStringList manifestNames = { "MangoHud.x86_64.json", "MangoHud.aarch64.json", "MangoHud.json" }; + QString currentArch = QSysInfo::currentCpuArchitecture(); + + if (currentArch == "arm64") { + currentArch = "aarch64"; + } + + QStringList manifestNames = { QString("MangoHud.%1.json").arg(currentArch), "MangoHud.json" }; QString filePath = ""; for (QString manifestName : manifestNames) { @@ -87,8 +94,9 @@ QString getLibraryString() } } - if (filePath.isEmpty()) + if (filePath.isEmpty()) { continue; + } auto conf = Json::requireDocument(filePath, vkLayer); auto confObject = Json::requireObject(conf, vkLayer); From 732bc536926d86015ff702c2ec46606787432009 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 2 Apr 2023 03:14:42 +0000 Subject: [PATCH 118/185] chore(deps): update flatpak/flatpak-github-actions action to v6 --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 022a04f8..ae683795 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -563,7 +563,7 @@ jobs: submodules: 'true' - name: Build Flatpak (Linux) if: inputs.build_type == 'Debug' - uses: flatpak/flatpak-github-actions/flatpak-builder@v5 + uses: flatpak/flatpak-github-actions/flatpak-builder@v6 with: bundle: "Prism Launcher.flatpak" manifest-path: flatpak/org.prismlauncher.PrismLauncher.yml From ea7f03770c86076751ddea96d9b7b89358a2d746 Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Sun, 2 Apr 2023 11:33:04 +0200 Subject: [PATCH 119/185] refactor(nix): use qtWrapperArgs Signed-off-by: Sefa Eyeoglu --- nix/default.nix | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/nix/default.nix b/nix/default.nix index cd782f90..f219d5d2 100644 --- a/nix/default.nix +++ b/nix/default.nix @@ -50,7 +50,6 @@ stdenv.mkDerivation rec { cmakeFlags = lib.optionals (msaClientID != "") ["-DLauncher_MSA_CLIENT_ID=${msaClientID}"] ++ lib.optionals (lib.versionOlder qtbase.version "6") ["-DLauncher_QT_VERSION_MAJOR=5"]; - dontWrapQtApps = true; postUnpack = '' rm -rf source/libraries/libnbtplusplus @@ -60,7 +59,7 @@ stdenv.mkDerivation rec { chown -R $USER: source/libraries/libnbtplusplus ''; - postInstall = let + qtWrapperArgs = let libpath = with xorg; lib.makeLibraryPath [ libX11 @@ -74,13 +73,12 @@ stdenv.mkDerivation rec { openal stdenv.cc.cc.lib ]; - in '' + in [ + "--set LD_LIBRARY_PATH /run/opengl-driver/lib:${libpath}" + "--prefix PRISMLAUNCHER_JAVA_PATHS : ${lib.makeSearchPath "bin/java" jdks}" # xorg.xrandr needed for LWJGL [2.9.2, 3) https://github.com/LWJGL/lwjgl/issues/128 - wrapQtApp $out/bin/prismlauncher \ - --set LD_LIBRARY_PATH /run/opengl-driver/lib:${libpath} \ - --prefix PRISMLAUNCHER_JAVA_PATHS : ${lib.makeSearchPath "bin/java" jdks} \ - --prefix PATH : ${lib.makeBinPath [xorg.xrandr]} - ''; + "--prefix PATH : ${lib.makeBinPath [xorg.xrandr]}" + ]; meta = with lib; { homepage = "https://prismlauncher.org/"; From 70364884a963d35234711c750fd6abdc6ebfb635 Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Sun, 2 Apr 2023 11:35:35 +0200 Subject: [PATCH 120/185] feat(nix): add support for GameMode Signed-off-by: Sefa Eyeoglu --- nix/default.nix | 30 +++++++++++++++++------------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/nix/default.nix b/nix/default.nix index f219d5d2..e0616b6e 100644 --- a/nix/default.nix +++ b/nix/default.nix @@ -23,6 +23,8 @@ cmark, msaClientID ? "", jdks ? [jdk17 jdk8], + gamemodeSupport ? true, + gamemode, # flake self, version, @@ -45,7 +47,8 @@ stdenv.mkDerivation rec { tomlplusplus cmark ] - ++ lib.optional (lib.versionAtLeast qtbase.version "6") qtwayland; + ++ lib.optional (lib.versionAtLeast qtbase.version "6") qtwayland + ++ lib.optional gamemodeSupport gamemode.dev; cmakeFlags = lib.optionals (msaClientID != "") ["-DLauncher_MSA_CLIENT_ID=${msaClientID}"] @@ -61,18 +64,19 @@ stdenv.mkDerivation rec { qtWrapperArgs = let libpath = with xorg; - lib.makeLibraryPath [ - libX11 - libXext - libXcursor - libXrandr - libXxf86vm - libpulseaudio - libGL - glfw - openal - stdenv.cc.cc.lib - ]; + lib.makeLibraryPath ([ + libX11 + libXext + libXcursor + libXrandr + libXxf86vm + libpulseaudio + libGL + glfw + openal + stdenv.cc.cc.lib + ] + ++ lib.optional gamemodeSupport gamemode.lib); in [ "--set LD_LIBRARY_PATH /run/opengl-driver/lib:${libpath}" "--prefix PRISMLAUNCHER_JAVA_PATHS : ${lib.makeSearchPath "bin/java" jdks}" From ba2b5c3a65089e3807ebd57a1504591f8dab4049 Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Sun, 2 Apr 2023 16:39:13 -0700 Subject: [PATCH 121/185] fix: Apply suggestions from code review -expand columspan on new UI element -improve tooltip Co-authored-by: flow Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- launcher/ui/pages/global/LauncherPage.ui | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/launcher/ui/pages/global/LauncherPage.ui b/launcher/ui/pages/global/LauncherPage.ui index 6279d879..906efd1a 100644 --- a/launcher/ui/pages/global/LauncherPage.ui +++ b/launcher/ui/pages/global/LauncherPage.ui @@ -147,10 +147,10 @@ - + - when looking for mods in places like the blocked mods dialog Prismlauncher will check in sub folders of your downloads folder too. + When enabled, in addition to the downloads folder, its sub folders will also be searched when looking for resources (e.g. when looking for blocked mods on CurseForge). Check downloads folder recursively 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: [PATCH 122/185] 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(-) 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 From 0cf1fc72a37f6c46e0e3fd2b3dd7a62e78a58574 Mon Sep 17 00:00:00 2001 From: DioEgizio <83089242+DioEgizio@users.noreply.github.com> Date: Mon, 3 Apr 2023 16:01:07 +0200 Subject: [PATCH 123/185] chore: update to qt 6.5.0 on macos Signed-off-by: DioEgizio <83089242+DioEgizio@users.noreply.github.com> --- .github/workflows/build.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ae683795..43c71a76 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -90,7 +90,7 @@ jobs: qt_ver: 6 qt_host: mac qt_arch: '' - qt_version: '6.4.3' + qt_version: '6.5.0' qt_modules: 'qt5compat qtimageformats' qt_tools: '' @@ -208,6 +208,8 @@ jobs: if: runner.os == 'Windows' && matrix.architecture == 'arm64' uses: jurplel/install-qt-action@v3 with: + aqtversion: '==3.1.*' + py7zrversion: '>=0.20.2' version: ${{ matrix.qt_version }} host: 'windows' target: 'desktop' @@ -223,6 +225,8 @@ jobs: if: runner.os == 'Linux' && matrix.qt_ver == 6 || runner.os == 'macOS' || (runner.os == 'Windows' && matrix.msystem == '') uses: jurplel/install-qt-action@v3 with: + aqtversion: '==3.1.*' + py7zrversion: '>=0.20.2' version: ${{ matrix.qt_version }} host: ${{ matrix.qt_host }} target: 'desktop' 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: [PATCH 124/185] 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(-) 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); From 5b50b806ec5954aa3822443969d22ea79faa07c5 Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Mon, 3 Apr 2023 17:14:06 -0700 Subject: [PATCH 125/185] refactor: remove data duplication in statis FS Names Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- launcher/CMakeLists.txt | 12 +++---- launcher/FileSystem.cpp | 22 ++++++------- launcher/FileSystem.h | 70 +++++++++++------------------------------ 3 files changed, 35 insertions(+), 69 deletions(-) diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index b47f5746..03d3fcbf 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -1123,17 +1123,17 @@ if(WIN32) add_library(filelink_logic STATIC ${LINKEXE_SOURCES}) target_include_directories(filelink_logic PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) target_link_libraries(filelink_logic - systeminfo + # systeminfo BuildConfig - Qt${QT_VERSION_MAJOR}::Widgets + # 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 - ${Launcher_QT_LIBS} + # Qt${QT_VERSION_MAJOR}::Xml + # Qt${QT_VERSION_MAJOR}::Network + # Qt${QT_VERSION_MAJOR}::Concurrent + # ${Launcher_QT_LIBS} ) add_executable("${Launcher_Name}_filelink" WIN32 filelink/main.cpp) diff --git a/launcher/FileSystem.cpp b/launcher/FileSystem.cpp index c046ee86..869fbe36 100644 --- a/launcher/FileSystem.cpp +++ b/launcher/FileSystem.cpp @@ -955,29 +955,29 @@ QString getFilesystemTypeName(FilesystemType type) { auto iter = s_filesystem_type_names.constFind(type); if (iter != s_filesystem_type_names.constEnd()) { - return iter.value(); + return iter.value().constFirst(); } return getFilesystemTypeName(FilesystemType::UNKNOWN); } FilesystemType getFilesystemTypeFuzzy(const QString& name) { - auto iter = s_filesystem_type_names_inverse.constFind(name.toUpper()); - if (iter != s_filesystem_type_names_inverse.constEnd()) { - return iter.value(); + for (auto iter = s_filesystem_type_names.constBegin(); iter != s_filesystem_type_names.constEnd(); ++iter) { + auto fs_names = iter.value(); + for (auto fs_name : fs_names) { + if (name.toUpper().contains(fs_name.toUpper())) + return iter.key(); + } } return FilesystemType::UNKNOWN; } FilesystemType getFilesystemType(const QString& name) { - for (auto fs_type_pair : s_filesystem_type_names_inverse.toStdMap()) { - auto fs_type_name = fs_type_pair.first; - auto fs_type = fs_type_pair.second; - - if (name.toUpper().contains(fs_type_name.toUpper())) { - return fs_type; - } + for (auto iter = s_filesystem_type_names.constBegin(); iter != s_filesystem_type_names.constEnd(); ++iter) { + auto fs_names = iter.value(); + if(fs_names.contains(name.toUpper())) + return iter.key(); } return FilesystemType::UNKNOWN; } diff --git a/launcher/FileSystem.h b/launcher/FileSystem.h index 47044d93..cb581d0c 100644 --- a/launcher/FileSystem.h +++ b/launcher/FileSystem.h @@ -360,58 +360,24 @@ enum class FilesystemType { * QMap is ordered * */ -static const QMap s_filesystem_type_names = { - {FilesystemType::FAT, QStringLiteral("FAT")}, - {FilesystemType::NTFS, QStringLiteral("NTFS")}, - {FilesystemType::REFS, QStringLiteral("REFS")}, - {FilesystemType::EXT, QStringLiteral("EXT")}, - {FilesystemType::EXT_2_OLD, QStringLiteral("EXT_2_OLD")}, - {FilesystemType::EXT_2_3_4, QStringLiteral("EXT2/3/4")}, - {FilesystemType::XFS, QStringLiteral("XFS")}, - {FilesystemType::BTRFS, QStringLiteral("BTRFS")}, - {FilesystemType::NFS, QStringLiteral("NFS")}, - {FilesystemType::ZFS, QStringLiteral("ZFS")}, - {FilesystemType::APFS, QStringLiteral("APFS")}, - {FilesystemType::HFS, QStringLiteral("HFS")}, - {FilesystemType::HFSPLUS, QStringLiteral("HFSPLUS")}, - {FilesystemType::HFSX, QStringLiteral("HFSX")}, - {FilesystemType::FUSEBLK, QStringLiteral("FUSEBLK")}, - {FilesystemType::F2FS, QStringLiteral("F2FS")}, - {FilesystemType::UNKNOWN, QStringLiteral("UNKNOWN")} -}; - - -/** - * @brief Ordered Mapping of reported filesystem names to enum types - * 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 = { - {QStringLiteral("FAT"), FilesystemType::FAT}, - {QStringLiteral("NTFS"), FilesystemType::NTFS}, - {QStringLiteral("REFS"), FilesystemType::REFS}, - {QStringLiteral("EXT2_OLD"), FilesystemType::EXT_2_OLD}, - {QStringLiteral("EXT_2_OLD"), FilesystemType::EXT_2_OLD}, - {QStringLiteral("EXT2"), FilesystemType::EXT_2_3_4}, - {QStringLiteral("EXT3"), FilesystemType::EXT_2_3_4}, - {QStringLiteral("EXT4"), FilesystemType::EXT_2_3_4}, - {QStringLiteral("EXT2/3/4"), FilesystemType::EXT_2_3_4}, - {QStringLiteral("EXT_2_3_4"), FilesystemType::EXT_2_3_4}, - {QStringLiteral("EXT"), FilesystemType::EXT}, // must come after all other EXT variants to prevent greedy detection - {QStringLiteral("XFS"), FilesystemType::XFS}, - {QStringLiteral("BTRFS"), FilesystemType::BTRFS}, - {QStringLiteral("NFS"), FilesystemType::NFS}, - {QStringLiteral("ZFS"), FilesystemType::ZFS}, - {QStringLiteral("APFS"), FilesystemType::APFS}, - {QStringLiteral("HFSPLUS"), FilesystemType::HFSPLUS}, - {QStringLiteral("HFSX"), FilesystemType::HFSX}, - {QStringLiteral("HFS"), FilesystemType::HFS}, - {QStringLiteral("FUSEBLK"), FilesystemType::FUSEBLK}, - {QStringLiteral("F2FS"), FilesystemType::F2FS}, - {QStringLiteral("UNKNOWN"), FilesystemType::UNKNOWN} +static const QMap s_filesystem_type_names = { + {FilesystemType::FAT, { "FAT" }}, + {FilesystemType::NTFS, { "NTFS" }}, + {FilesystemType::REFS, { "REFS" }}, + {FilesystemType::EXT_2_OLD, { "EXT_2_OLD", "EXT2_OLD" }}, + {FilesystemType::EXT_2_3_4, { "EXT2/3/4", "EXT_2_3_4", "EXT2", "EXT3", "EXT4" }}, + {FilesystemType::EXT, { "EXT" }}, + {FilesystemType::XFS, { "XFS" }}, + {FilesystemType::BTRFS, { "BTRFS" }}, + {FilesystemType::NFS, { "NFS" }}, + {FilesystemType::ZFS, { "ZFS" }}, + {FilesystemType::APFS, { "APFS" }}, + {FilesystemType::HFS, { "HFS" }}, + {FilesystemType::HFSPLUS, { "HFSPLUS" }}, + {FilesystemType::HFSX, { "HFSX" }}, + {FilesystemType::FUSEBLK, { "FUSEBLK" }}, + {FilesystemType::F2FS, { "F2FS" }}, + {FilesystemType::UNKNOWN, { "UNKNOWN" }} }; /** From 197be9cfd0299edba57fe6fe1d7a7841ed0bb771 Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Mon, 3 Apr 2023 18:00:56 -0700 Subject: [PATCH 126/185] fix: remove fixed datastream version Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- launcher/FileSystem.cpp | 3 +-- launcher/filelink/FileLink.cpp | 2 -- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/launcher/FileSystem.cpp b/launcher/FileSystem.cpp index 869fbe36..bd985c5b 100644 --- a/launcher/FileSystem.cpp +++ b/launcher/FileSystem.cpp @@ -446,7 +446,6 @@ void create_link::runPrivileged(const QString& offset) // construct block of data to send QByteArray block; QDataStream out(&block, QIODevice::WriteOnly); - out.setVersion(QDataStream::Qt_5_0); // choose correct version better? qint32 blocksize = quint32(sizeof(quint32)); for (auto link : m_links_to_make) { @@ -469,7 +468,7 @@ void create_link::runPrivileged(const QString& offset) QDataStream in; quint32 blockSize = 0; in.setDevice(clientConnection); - in.setVersion(QDataStream::Qt_5_0); + qDebug() << "Reading path results from client"; qDebug() << "bytes available" << clientConnection->bytesAvailable(); diff --git a/launcher/filelink/FileLink.cpp b/launcher/filelink/FileLink.cpp index ded46061..c9599b82 100644 --- a/launcher/filelink/FileLink.cpp +++ b/launcher/filelink/FileLink.cpp @@ -120,7 +120,6 @@ void FileLinkApp::joinServer(QString server) blockSize = 0; in.setDevice(&socket); - in.setVersion(QDataStream::Qt_5_0); connect(&socket, &QLocalSocket::connected, this, [&]() { qDebug() << "connected to server"; }); @@ -203,7 +202,6 @@ void FileLinkApp::sendResults() // construct block of data to send QByteArray block; QDataStream out(&block, QIODevice::WriteOnly); - out.setVersion(QDataStream::Qt_5_0); qint32 blocksize = quint32(sizeof(quint32)); for (auto result : m_path_results) { From 41c5e523b294501ca96f0c283fe8e837f01e13a3 Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Mon, 3 Apr 2023 18:09:01 -0700 Subject: [PATCH 127/185] fix: add back QT::Widgets link Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- launcher/CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index 03d3fcbf..71e54c85 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -1125,7 +1125,7 @@ if(WIN32) target_link_libraries(filelink_logic # systeminfo BuildConfig - # Qt${QT_VERSION_MAJOR}::Widgets + Qt${QT_VERSION_MAJOR}::Widgets ghcFilesystem::ghc_filesystem ) target_link_libraries(filelink_logic @@ -1133,7 +1133,7 @@ if(WIN32) # Qt${QT_VERSION_MAJOR}::Xml # Qt${QT_VERSION_MAJOR}::Network # Qt${QT_VERSION_MAJOR}::Concurrent - # ${Launcher_QT_LIBS} + ${Launcher_QT_LIBS} ) add_executable("${Launcher_Name}_filelink" WIN32 filelink/main.cpp) From de20258aa7b846e7c7e5c7ef5abf9613157cb4a0 Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Mon, 3 Apr 2023 18:30:28 -0700 Subject: [PATCH 128/185] fix: filelink needs network for local socket Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- launcher/CMakeLists.txt | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index 71e54c85..4dbb9e6e 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -1123,15 +1123,13 @@ if(WIN32) add_library(filelink_logic STATIC ${LINKEXE_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}::Network # Qt${QT_VERSION_MAJOR}::Concurrent ${Launcher_QT_LIBS} ) From 0ce30495796627e7591587ca1e977be98178f225 Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Mon, 3 Apr 2023 18:48:28 -0700 Subject: [PATCH 129/185] fix: sysinfo libs needed too Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- launcher/CMakeLists.txt | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index 4dbb9e6e..4de0f73b 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -1123,11 +1123,10 @@ if(WIN32) add_library(filelink_logic STATIC ${LINKEXE_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}::Widgets Qt${QT_VERSION_MAJOR}::Core Qt${QT_VERSION_MAJOR}::Network # Qt${QT_VERSION_MAJOR}::Concurrent 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: [PATCH 130/185] 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(-) 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!) From c56db0408bc7bb01e9807fd02858f27328ca6b79 Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Sat, 8 Apr 2023 07:26:56 -0700 Subject: [PATCH 131/185] fix: load setting state with page. don't translate "..." Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- launcher/ui/pages/global/LauncherPage.cpp | 1 + launcher/ui/pages/global/LauncherPage.ui | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/launcher/ui/pages/global/LauncherPage.cpp b/launcher/ui/pages/global/LauncherPage.cpp index 51652320..816dde72 100644 --- a/launcher/ui/pages/global/LauncherPage.cpp +++ b/launcher/ui/pages/global/LauncherPage.cpp @@ -275,6 +275,7 @@ void LauncherPage::loadSettings() ui->modsDirTextBox->setText(s->get("CentralModsDir").toString()); ui->iconsDirTextBox->setText(s->get("IconsDir").toString()); ui->downloadsDirTextBox->setText(s->get("DownloadsDir").toString()); + ui->downloadsDirWatchRecursiveCheckBox->setChecked(s->get("DownloadsDirWatchRecursive").toBool()); QString sortMode = s->get("InstSortMode").toString(); diff --git a/launcher/ui/pages/global/LauncherPage.ui b/launcher/ui/pages/global/LauncherPage.ui index 906efd1a..55bd3eea 100644 --- a/launcher/ui/pages/global/LauncherPage.ui +++ b/launcher/ui/pages/global/LauncherPage.ui @@ -99,7 +99,7 @@ - ... + ... From a9881115073cc73e668f14b23851914ddfa9b4e7 Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Sat, 8 Apr 2023 18:48:02 +0200 Subject: [PATCH 132/185] fix: do not apply system theme on launch Closes PrismLauncher/PrismLauncher#490 Regression introduced by PrismLauncher/PrismLauncher#249 Signed-off-by: Sefa Eyeoglu --- launcher/Application.cpp | 8 +++----- launcher/Application.h | 2 +- launcher/ui/themes/ITheme.cpp | 2 +- launcher/ui/themes/ITheme.h | 2 +- launcher/ui/themes/SystemTheme.cpp | 8 ++++++-- launcher/ui/themes/SystemTheme.h | 2 +- launcher/ui/themes/ThemeManager.cpp | 8 ++++---- launcher/ui/themes/ThemeManager.h | 4 ++-- 8 files changed, 19 insertions(+), 17 deletions(-) diff --git a/launcher/Application.cpp b/launcher/Application.cpp index 879af535..c33694e9 100644 --- a/launcher/Application.cpp +++ b/launcher/Application.cpp @@ -830,9 +830,7 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv) } }); - { - applyCurrentlySelectedTheme(); - } + applyCurrentlySelectedTheme(true); updateCapabilities(); @@ -1107,9 +1105,9 @@ QList Application::getValidApplicationThemes() return m_themeManager->getValidApplicationThemes(); } -void Application::applyCurrentlySelectedTheme() +void Application::applyCurrentlySelectedTheme(bool initial) { - m_themeManager->applyCurrentlySelectedTheme(); + m_themeManager->applyCurrentlySelectedTheme(initial); } void Application::setApplicationTheme(const QString& name) diff --git a/launcher/Application.h b/launcher/Application.h index 91c5fc63..0d24a4e9 100644 --- a/launcher/Application.h +++ b/launcher/Application.h @@ -120,7 +120,7 @@ public: void setIconTheme(const QString& name); - void applyCurrentlySelectedTheme(); + void applyCurrentlySelectedTheme(bool initial = false); QList getValidApplicationThemes(); diff --git a/launcher/ui/themes/ITheme.cpp b/launcher/ui/themes/ITheme.cpp index 22043e44..8f0757e1 100644 --- a/launcher/ui/themes/ITheme.cpp +++ b/launcher/ui/themes/ITheme.cpp @@ -38,7 +38,7 @@ #include #include "Application.h" -void ITheme::apply() +void ITheme::apply(bool) { APPLICATION->setStyleSheet(QString()); QApplication::setStyle(QStyleFactory::create(qtTheme())); diff --git a/launcher/ui/themes/ITheme.h b/launcher/ui/themes/ITheme.h index 2e5b7f25..a0a638bd 100644 --- a/launcher/ui/themes/ITheme.h +++ b/launcher/ui/themes/ITheme.h @@ -41,7 +41,7 @@ class QStyle; class ITheme { public: virtual ~ITheme() {} - virtual void apply(); + virtual void apply(bool initial); virtual QString id() = 0; virtual QString name() = 0; virtual bool hasStyleSheet() = 0; diff --git a/launcher/ui/themes/SystemTheme.cpp b/launcher/ui/themes/SystemTheme.cpp index 24875e33..a95bc875 100644 --- a/launcher/ui/themes/SystemTheme.cpp +++ b/launcher/ui/themes/SystemTheme.cpp @@ -60,9 +60,13 @@ SystemTheme::SystemTheme() themeDebugLog() << "System theme not found, defaulted to Fusion"; } -void SystemTheme::apply() +void SystemTheme::apply(bool initial) { - ITheme::apply(); + // See https://github.com/MultiMC/Launcher/issues/1790 + // or https://github.com/PrismLauncher/PrismLauncher/issues/490 + if (initial) + return; + ITheme::apply(initial); } QString SystemTheme::id() diff --git a/launcher/ui/themes/SystemTheme.h b/launcher/ui/themes/SystemTheme.h index b5c03def..05f31233 100644 --- a/launcher/ui/themes/SystemTheme.h +++ b/launcher/ui/themes/SystemTheme.h @@ -40,7 +40,7 @@ class SystemTheme : public ITheme { public: SystemTheme(); virtual ~SystemTheme() {} - void apply() override; + void apply(bool initial) override; QString id() override; QString name() override; diff --git a/launcher/ui/themes/ThemeManager.cpp b/launcher/ui/themes/ThemeManager.cpp index 13406485..94ac8a24 100644 --- a/launcher/ui/themes/ThemeManager.cpp +++ b/launcher/ui/themes/ThemeManager.cpp @@ -116,22 +116,22 @@ void ThemeManager::setIconTheme(const QString& name) QIcon::setThemeName(name); } -void ThemeManager::applyCurrentlySelectedTheme() +void ThemeManager::applyCurrentlySelectedTheme(bool initial) { setIconTheme(APPLICATION->settings()->get("IconTheme").toString()); themeDebugLog() << "<> Icon theme set."; - setApplicationTheme(APPLICATION->settings()->get("ApplicationTheme").toString()); + setApplicationTheme(APPLICATION->settings()->get("ApplicationTheme").toString(), initial); themeDebugLog() << "<> Application theme set."; } -void ThemeManager::setApplicationTheme(const QString& name) +void ThemeManager::setApplicationTheme(const QString& name, bool initial) { auto systemPalette = qApp->palette(); auto themeIter = m_themes.find(name); if (themeIter != m_themes.end()) { auto& theme = themeIter->second; themeDebugLog() << "applying theme" << theme->name(); - theme->apply(); + theme->apply(initial); } else { themeWarningLog() << "Tried to set invalid theme:" << name; } diff --git a/launcher/ui/themes/ThemeManager.h b/launcher/ui/themes/ThemeManager.h index 9af44b5a..87f36d9c 100644 --- a/launcher/ui/themes/ThemeManager.h +++ b/launcher/ui/themes/ThemeManager.h @@ -37,8 +37,8 @@ class ThemeManager { QList getValidApplicationThemes(); void setIconTheme(const QString& name); - void applyCurrentlySelectedTheme(); - void setApplicationTheme(const QString& name); + void applyCurrentlySelectedTheme(bool initial = false); + void setApplicationTheme(const QString& name, bool initial = false); /// /// Returns the cat based on selected cat and with events (Birthday, XMas, etc.) From 371c8395731152ba198849d03e491760256c3232 Mon Sep 17 00:00:00 2001 From: DioEgizio <83089242+DioEgizio@users.noreply.github.com> Date: Sat, 8 Apr 2023 19:38:46 +0200 Subject: [PATCH 133/185] chore: update Qt to 6.5.0 on Windows Signed-off-by: DioEgizio <83089242+DioEgizio@users.noreply.github.com> --- .github/workflows/build.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 43c71a76..4369d249 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -68,7 +68,7 @@ jobs: qt_ver: 6 qt_host: windows qt_arch: '' - qt_version: '6.4.3' + qt_version: '6.5.0' qt_modules: 'qt5compat qtimageformats' qt_tools: '' @@ -80,7 +80,7 @@ jobs: qt_ver: 6 qt_host: windows qt_arch: 'win64_msvc2019_arm64' - qt_version: '6.4.3' + qt_version: '6.5.0' qt_modules: 'qt5compat qtimageformats' qt_tools: '' 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: [PATCH 134/185] 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(-) 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); From 51095c5a2722ed693c3ac3e2ec597601d00f491e Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 14 Apr 2023 06:31:06 +0000 Subject: [PATCH 135/185] chore(deps): update hendrikmuhs/ccache-action action to v1.2.9 --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 4369d249..663bfc5f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -152,7 +152,7 @@ jobs: - name: Setup ccache if: (runner.os != 'Windows' || matrix.msystem == '') && inputs.build_type == 'Debug' - uses: hendrikmuhs/ccache-action@v1.2.8 + uses: hendrikmuhs/ccache-action@v1.2.9 with: key: ${{ matrix.os }}-qt${{ matrix.qt_ver }}-${{ matrix.architecture }} From 12f0d51c0cd03d660425566264b502736b104310 Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Mon, 17 Apr 2023 17:51:34 -0700 Subject: [PATCH 136/185] Fix: signal/slot macro -> func pointer & network fixes - convert qt connect calls to use function pointers instead of the signal/slot macros wherever practical (UI classes were mostly left alone, target was tasks and processes) - give signals an explicit receivers to use the static method over the instance method wherever practical - ensure networks tasks are using the `errorOccured` signal added in Qt5.15 over the deprecated `error` signal - ensure all networks tasks have an sslErrors signal connected - add seemingly missing `MinecraftAccount::authSucceeded` connection for `MSAInteractive` login flow Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- launcher/JavaCommon.cpp | 6 ++-- launcher/LoggedProcess.cpp | 8 ++--- launcher/icons/IconList.cpp | 5 ++- launcher/java/JavaChecker.cpp | 14 ++++----- launcher/java/JavaCheckerJob.cpp | 2 +- launcher/launch/steps/Update.cpp | 6 ++-- launcher/minecraft/WorldList.cpp | 3 +- launcher/minecraft/auth/AuthRequest.cpp | 22 ++++++------- launcher/minecraft/auth/MinecraftAccount.cpp | 16 +++++----- launcher/minecraft/services/CapeChange.cpp | 33 +++++++++++++++----- launcher/minecraft/services/CapeChange.h | 1 + launcher/minecraft/services/SkinDelete.cpp | 22 ++++++++++--- launcher/minecraft/services/SkinDelete.h | 1 + launcher/minecraft/services/SkinUpload.cpp | 22 ++++++++++--- launcher/minecraft/services/SkinUpload.h | 1 + launcher/net/Download.cpp | 6 ++-- launcher/net/Download.h | 2 +- launcher/net/HttpMetaCache.cpp | 2 +- launcher/net/NetAction.h | 11 +++++++ launcher/net/Upload.cpp | 10 ++++-- launcher/net/Upload.h | 2 +- launcher/screenshots/ImgurAlbumCreation.cpp | 10 ++++-- launcher/screenshots/ImgurUpload.cpp | 10 ++++-- launcher/settings/SettingsObject.cpp | 9 +++--- launcher/tools/JProfiler.cpp | 4 +-- launcher/tools/JVisualVM.cpp | 4 +-- launcher/ui/pages/instance/VersionPage.cpp | 2 +- 27 files changed, 148 insertions(+), 86 deletions(-) diff --git a/launcher/JavaCommon.cpp b/launcher/JavaCommon.cpp index 52cc868a..e29e2270 100644 --- a/launcher/JavaCommon.cpp +++ b/launcher/JavaCommon.cpp @@ -122,8 +122,7 @@ void JavaCommon::TestCheck::run() return; } checker.reset(new JavaChecker()); - connect(checker.get(), SIGNAL(checkFinished(JavaCheckResult)), this, - SLOT(checkFinished(JavaCheckResult))); + connect(checker.get(), &JavaChecker::checkFinished, this, &JavaCommon::TestCheck::checkFinished); checker->m_path = m_path; checker->performCheck(); } @@ -137,8 +136,7 @@ void JavaCommon::TestCheck::checkFinished(JavaCheckResult result) return; } checker.reset(new JavaChecker()); - connect(checker.get(), SIGNAL(checkFinished(JavaCheckResult)), this, - SLOT(checkFinishedWithArgs(JavaCheckResult))); + connect(checker.get(), &JavaChecker::checkFinished, this, &JavaCommon::TestCheck::checkFinishedWithArgs); checker->m_path = m_path; checker->m_args = m_args; checker->m_minMem = m_minMem; diff --git a/launcher/LoggedProcess.cpp b/launcher/LoggedProcess.cpp index c8d5c34e..763a9b5c 100644 --- a/launcher/LoggedProcess.cpp +++ b/launcher/LoggedProcess.cpp @@ -44,11 +44,11 @@ LoggedProcess::LoggedProcess(QObject *parent) : QProcess(parent) // QProcess has a strange interface... let's map a lot of those into a few. connect(this, &QProcess::readyReadStandardOutput, this, &LoggedProcess::on_stdOut); connect(this, &QProcess::readyReadStandardError, this, &LoggedProcess::on_stdErr); - connect(this, SIGNAL(finished(int,QProcess::ExitStatus)), SLOT(on_exit(int,QProcess::ExitStatus))); -#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) - connect(this, SIGNAL(errorOccurred(QProcess::ProcessError)), this, SLOT(on_error(QProcess::ProcessError))); + connect(this, QOverload::of(&QProcess::finished), this, &LoggedProcess::on_exit); +#if QT_VERSION >= QT_VERSION_CHECK(5, 6, 0) // &QProcess::errorOccurred added in 5.6 + connect(this, &QProcess::errorOccurred, this, &LoggedProcess::on_error); #else - connect(this, SIGNAL(error(QProcess::ProcessError)), this, SLOT(on_error(QProcess::ProcessError))); + connect(this, QOverload::of(&QProcess::error), this, &LoggedProcess::on_error); #endif connect(this, &QProcess::stateChanged, this, &LoggedProcess::on_stateChange); } diff --git a/launcher/icons/IconList.cpp b/launcher/icons/IconList.cpp index 1dfc6432..13174f6e 100644 --- a/launcher/icons/IconList.cpp +++ b/launcher/icons/IconList.cpp @@ -66,9 +66,8 @@ IconList::IconList(const QStringList &builtinPaths, QString path, QObject *paren m_watcher.reset(new QFileSystemWatcher()); is_watching = false; - connect(m_watcher.get(), SIGNAL(directoryChanged(QString)), - SLOT(directoryChanged(QString))); - connect(m_watcher.get(), SIGNAL(fileChanged(QString)), SLOT(fileChanged(QString))); + connect(m_watcher.get(), &QFileSystemWatcher::directoryChanged, this, &IconList::directoryChanged); + connect(m_watcher.get(), &QFileSystemWatcher::fileChanged, this, &IconList::fileChanged); directoryChanged(path); diff --git a/launcher/java/JavaChecker.cpp b/launcher/java/JavaChecker.cpp index 041583d1..922580ce 100644 --- a/launcher/java/JavaChecker.cpp +++ b/launcher/java/JavaChecker.cpp @@ -87,15 +87,15 @@ void JavaChecker::performCheck() process->setProcessEnvironment(CleanEnviroment()); qDebug() << "Running java checker: " + m_path + args.join(" ");; - connect(process.get(), SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(finished(int, QProcess::ExitStatus))); -#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) - connect(process.get(), SIGNAL(errorOccurred(QProcess::ProcessError)), this, SLOT(error(QProcess::ProcessError))); + connect(process.get(), QOverload::of(&QProcess::finished), this, &JavaChecker::finished); +#if QT_VERSION >= QT_VERSION_CHECK(5, 6, 0) // &QProcess::errorOccurred added in 5.6 + connect(process.get(), &QProcess::errorOccurred, this, &JavaChecker::error); #else - connect(process.get(), SIGNAL(error(QProcess::ProcessError)), this, SLOT(error(QProcess::ProcessError))); + connect(process.get(), &QProcess::error, this, &JavaChecker::error); #endif - connect(process.get(), SIGNAL(readyReadStandardOutput()), this, SLOT(stdoutReady())); - connect(process.get(), SIGNAL(readyReadStandardError()), this, SLOT(stderrReady())); - connect(&killTimer, SIGNAL(timeout()), SLOT(timeout())); + connect(process.get(), &QProcess::readyReadStandardOutput, this, &JavaChecker::stdoutReady); + connect(process.get(), &QProcess::readyReadStandardError, this, &JavaChecker::stderrReady); + connect(&killTimer, &QTimer::timeout, this, &JavaChecker::timeout); killTimer.setSingleShot(true); killTimer.start(15000); process->start(); diff --git a/launcher/java/JavaCheckerJob.cpp b/launcher/java/JavaCheckerJob.cpp index 67d70066..48274974 100644 --- a/launcher/java/JavaCheckerJob.cpp +++ b/launcher/java/JavaCheckerJob.cpp @@ -38,7 +38,7 @@ void JavaCheckerJob::executeTask() for (auto iter : javacheckers) { javaresults.append(JavaCheckResult()); - connect(iter.get(), SIGNAL(checkFinished(JavaCheckResult)), SLOT(partFinished(JavaCheckResult))); + connect(iter.get(), &JavaChecker::checkFinished, this, &JavaCheckerJob::partFinished); iter->performCheck(); } } diff --git a/launcher/launch/steps/Update.cpp b/launcher/launch/steps/Update.cpp index 28bd153d..b67316b0 100644 --- a/launcher/launch/steps/Update.cpp +++ b/launcher/launch/steps/Update.cpp @@ -26,9 +26,9 @@ void Update::executeTask() m_updateTask.reset(m_parent->instance()->createUpdateTask(m_mode)); 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::finished, this, &Update::updateFinished); + connect(m_updateTask.get(), &Task::progress, this, &Update::setProgress); + connect(m_updateTask.get(), &Task::status, this, &Update::setStatus); emit progressReportingRequest(); return; } diff --git a/launcher/minecraft/WorldList.cpp b/launcher/minecraft/WorldList.cpp index ae29a972..de21c474 100644 --- a/launcher/minecraft/WorldList.cpp +++ b/launcher/minecraft/WorldList.cpp @@ -53,8 +53,7 @@ WorldList::WorldList(const QString &dir) m_dir.setSorting(QDir::Name | QDir::IgnoreCase | QDir::LocaleAware); m_watcher = new QFileSystemWatcher(this); is_watching = false; - connect(m_watcher, SIGNAL(directoryChanged(QString)), this, - SLOT(directoryChanged(QString))); + connect(m_watcher, &QFileSystemWatcher::directoryChanged, this, &WorldList::directoryChanged); } void WorldList::startWatching() diff --git a/launcher/minecraft/auth/AuthRequest.cpp b/launcher/minecraft/auth/AuthRequest.cpp index bb82e1e2..a21634b7 100644 --- a/launcher/minecraft/auth/AuthRequest.cpp +++ b/launcher/minecraft/auth/AuthRequest.cpp @@ -55,12 +55,12 @@ void AuthRequest::get(const QNetworkRequest &req, int timeout/* = 60*1000*/) { reply_ = APPLICATION->network()->get(request_); status_ = Requesting; timedReplies_.add(new Katabasis::Reply(reply_, timeout)); -#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) - connect(reply_, SIGNAL(errorOccurred(QNetworkReply::NetworkError)), this, SLOT(onRequestError(QNetworkReply::NetworkError))); -#else - connect(reply_, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(onRequestError(QNetworkReply::NetworkError))); +#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) // QNetworkReply::errorOccurred added in 5.15 + connect(reply_, &QNetworkReply::errorOccurred, this, &AuthRequest::onRequestError); +#else // &QNetworkReply::error SIGNAL depricated + connect(reply_, QOverload::of(&QNetworkReply::error), this, &AuthRequest::onRequestError); #endif - connect(reply_, SIGNAL(finished()), this, SLOT(onRequestFinished())); + connect(reply_, &QNetworkReply::finished, this, &AuthRequest::onRequestFinished); connect(reply_, &QNetworkReply::sslErrors, this, &AuthRequest::onSslErrors); } @@ -70,14 +70,14 @@ void AuthRequest::post(const QNetworkRequest &req, const QByteArray &data, int t status_ = Requesting; reply_ = APPLICATION->network()->post(request_, data_); timedReplies_.add(new Katabasis::Reply(reply_, timeout)); -#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) - connect(reply_, SIGNAL(errorOccurred(QNetworkReply::NetworkError)), this, SLOT(onRequestError(QNetworkReply::NetworkError))); -#else - connect(reply_, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(onRequestError(QNetworkReply::NetworkError))); +#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) // QNetworkReply::errorOccurred added in 5.15 + connect(reply_, &QNetworkReply::errorOccurred, this, &AuthRequest::onRequestError); +#else // &QNetworkReply::error SIGNAL depricated + connect(reply_, QOverload::of(&QNetworkReply::error), this, &AuthRequest::onRequestError); #endif - connect(reply_, SIGNAL(finished()), this, SLOT(onRequestFinished())); + connect(reply_, &QNetworkReply::finished, this, &AuthRequest::onRequestFinished); connect(reply_, &QNetworkReply::sslErrors, this, &AuthRequest::onSslErrors); - connect(reply_, SIGNAL(uploadProgress(qint64,qint64)), this, SLOT(onUploadProgress(qint64,qint64))); + connect(reply_, &QNetworkReply::uploadProgress, this, &AuthRequest::onUploadProgress); } void AuthRequest::onRequestFinished() { diff --git a/launcher/minecraft/auth/MinecraftAccount.cpp b/launcher/minecraft/auth/MinecraftAccount.cpp index 48cf5d42..3b050ac0 100644 --- a/launcher/minecraft/auth/MinecraftAccount.cpp +++ b/launcher/minecraft/auth/MinecraftAccount.cpp @@ -133,8 +133,8 @@ shared_qobject_ptr MinecraftAccount::login(QString password) { Q_ASSERT(m_currentTask.get() == nullptr); m_currentTask.reset(new MojangLogin(&data, password)); - connect(m_currentTask.get(), SIGNAL(succeeded()), SLOT(authSucceeded())); - connect(m_currentTask.get(), SIGNAL(failed(QString)), SLOT(authFailed(QString))); + connect(m_currentTask.get(), &Task::succeeded, this, &MinecraftAccount::authSucceeded); + connect(m_currentTask.get(), &Task::failed, this, &MinecraftAccount::authFailed); connect(m_currentTask.get(), &Task::aborted, this, [this]{ authFailed(tr("Aborted")); }); emit activityChanged(true); return m_currentTask; @@ -144,8 +144,8 @@ shared_qobject_ptr MinecraftAccount::loginMSA() { Q_ASSERT(m_currentTask.get() == nullptr); m_currentTask.reset(new MSAInteractive(&data)); - connect(m_currentTask.get(), SIGNAL(succeeded()), SLOT(authSucceeded())); - connect(m_currentTask.get(), SIGNAL(failed(QString)), SLOT(authFailed(QString))); + connect(m_currentTask.get(), &Task::succeeded, this, &MinecraftAccount::authSucceeded); + connect(m_currentTask.get(), &Task::failed, this, &MinecraftAccount::authFailed); connect(m_currentTask.get(), &Task::aborted, this, [this]{ authFailed(tr("Aborted")); }); emit activityChanged(true); return m_currentTask; @@ -155,8 +155,8 @@ shared_qobject_ptr MinecraftAccount::loginOffline() { Q_ASSERT(m_currentTask.get() == nullptr); m_currentTask.reset(new OfflineLogin(&data)); - connect(m_currentTask.get(), SIGNAL(succeeded()), SLOT(authSucceeded())); - connect(m_currentTask.get(), SIGNAL(failed(QString)), SLOT(authFailed(QString))); + connect(m_currentTask.get(), &Task::succeeded, this, &MinecraftAccount::authSucceeded); + connect(m_currentTask.get(), &Task::failed, this, &MinecraftAccount::authFailed); connect(m_currentTask.get(), &Task::aborted, this, [this]{ authFailed(tr("Aborted")); }); emit activityChanged(true); return m_currentTask; @@ -177,8 +177,8 @@ shared_qobject_ptr MinecraftAccount::refresh() { m_currentTask.reset(new MojangRefresh(&data)); } - connect(m_currentTask.get(), SIGNAL(succeeded()), SLOT(authSucceeded())); - connect(m_currentTask.get(), SIGNAL(failed(QString)), SLOT(authFailed(QString))); + connect(m_currentTask.get(), &Task::succeeded, this, &MinecraftAccount::authSucceeded); + connect(m_currentTask.get(), &Task::failed, this, &MinecraftAccount::authFailed); connect(m_currentTask.get(), &Task::aborted, this, [this]{ authFailed(tr("Aborted")); }); emit activityChanged(true); return m_currentTask; diff --git a/launcher/minecraft/services/CapeChange.cpp b/launcher/minecraft/services/CapeChange.cpp index c73a11b6..1d5ea36d 100644 --- a/launcher/minecraft/services/CapeChange.cpp +++ b/launcher/minecraft/services/CapeChange.cpp @@ -54,9 +54,14 @@ void CapeChange::setCape(QString& cape) { setStatus(tr("Equipping cape")); m_reply = shared_qobject_ptr(rep); - connect(rep, &QNetworkReply::uploadProgress, this, &Task::setProgress); - connect(rep, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(downloadError(QNetworkReply::NetworkError))); - connect(rep, SIGNAL(finished()), this, SLOT(downloadFinished())); + connect(rep, &QNetworkReply::uploadProgress, this, &CapeChange::setProgress); +#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) // QNetworkReply::errorOccurred added in 5.15 + connect(rep, &QNetworkReply::errorOccurred, this, &CapeChange::downloadError); +#else + connect(rep, QOverload::of(&QNetworkReply::error), this, &CapeChange::downloadError); +#endif + connect(rep, &QNetworkReply::sslErrors, this, &CapeChange::sslErrors); + connect(rep, &QNetworkReply::finished, this, &CapeChange::downloadFinished); } void CapeChange::clearCape() { @@ -68,13 +73,14 @@ void CapeChange::clearCape() { setStatus(tr("Removing cape")); m_reply = shared_qobject_ptr(rep); - connect(rep, &QNetworkReply::uploadProgress, this, &Task::setProgress); -#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) - connect(rep, SIGNAL(errorOccurred(QNetworkReply::NetworkError)), this, SLOT(downloadError(QNetworkReply::NetworkError))); + connect(rep, &QNetworkReply::uploadProgress, this, &CapeChange::setProgress); +#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) // QNetworkReply::errorOccurred added in 5.15 + connect(rep, &QNetworkReply::errorOccurred, this, &CapeChange::downloadError); #else - connect(rep, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(downloadError(QNetworkReply::NetworkError))); + connect(rep, QOverload::of(&QNetworkReply::error), this, &CapeChange::downloadError); #endif - connect(rep, SIGNAL(finished()), this, SLOT(downloadFinished())); + connect(rep, &QNetworkReply::sslErrors, this, &CapeChange::sslErrors); + connect(rep, &QNetworkReply::finished, this, &CapeChange::downloadFinished); } @@ -95,6 +101,17 @@ void CapeChange::downloadError(QNetworkReply::NetworkError error) emitFailed(m_reply->errorString()); } +void CapeChange::sslErrors(const QList& errors) +{ + int i = 1; + for (auto error : errors) { + qCritical() << "Cape change SSL Error #" << i << " : " << error.errorString(); + auto cert = error.certificate(); + qCritical() << "Certificate in question:\n" << cert.toText(); + i++; + } +} + void CapeChange::downloadFinished() { // if the download failed diff --git a/launcher/minecraft/services/CapeChange.h b/launcher/minecraft/services/CapeChange.h index 185d69b6..38069f90 100644 --- a/launcher/minecraft/services/CapeChange.h +++ b/launcher/minecraft/services/CapeChange.h @@ -27,6 +27,7 @@ protected: public slots: void downloadError(QNetworkReply::NetworkError); + void sslErrors(const QList& errors); void downloadFinished(); }; diff --git a/launcher/minecraft/services/SkinDelete.cpp b/launcher/minecraft/services/SkinDelete.cpp index 921bd094..fbaaeacb 100644 --- a/launcher/minecraft/services/SkinDelete.cpp +++ b/launcher/minecraft/services/SkinDelete.cpp @@ -53,13 +53,14 @@ void SkinDelete::executeTask() m_reply = shared_qobject_ptr(rep); setStatus(tr("Deleting skin")); - connect(rep, &QNetworkReply::uploadProgress, this, &Task::setProgress); -#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) - connect(rep, SIGNAL(errorOccurred(QNetworkReply::NetworkError)), this, SLOT(downloadError(QNetworkReply::NetworkError))); + connect(rep, &QNetworkReply::uploadProgress, this, &SkinDelete::setProgress); +#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) // QNetworkReply::errorOccurred added in 5.15 + connect(rep, &QNetworkReply::errorOccurred, this, &SkinDelete::downloadError); #else - connect(rep, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(downloadError(QNetworkReply::NetworkError))); + connect(rep, QOverload::of(&QNetworkReply::error), this, &SkinDelete::downloadError); #endif - connect(rep, SIGNAL(finished()), this, SLOT(downloadFinished())); + connect(rep, &QNetworkReply::sslErrors, this, &SkinDelete::sslErrors); + connect(rep, &QNetworkReply::finished, this, &SkinDelete::downloadFinished); } void SkinDelete::downloadError(QNetworkReply::NetworkError error) @@ -69,6 +70,17 @@ void SkinDelete::downloadError(QNetworkReply::NetworkError error) emitFailed(m_reply->errorString()); } +void SkinDelete::sslErrors(const QList& errors) +{ + int i = 1; + for (auto error : errors) { + qCritical() << "Skin Delete SSL Error #" << i << " : " << error.errorString(); + auto cert = error.certificate(); + qCritical() << "Certificate in question:\n" << cert.toText(); + i++; + } +} + void SkinDelete::downloadFinished() { // if the download failed diff --git a/launcher/minecraft/services/SkinDelete.h b/launcher/minecraft/services/SkinDelete.h index 83a84685..b9a1c9d3 100644 --- a/launcher/minecraft/services/SkinDelete.h +++ b/launcher/minecraft/services/SkinDelete.h @@ -22,5 +22,6 @@ protected: public slots: void downloadError(QNetworkReply::NetworkError); + void sslErrors(const QList& errors); void downloadFinished(); }; diff --git a/launcher/minecraft/services/SkinUpload.cpp b/launcher/minecraft/services/SkinUpload.cpp index c7987875..711f8739 100644 --- a/launcher/minecraft/services/SkinUpload.cpp +++ b/launcher/minecraft/services/SkinUpload.cpp @@ -78,13 +78,14 @@ void SkinUpload::executeTask() m_reply = shared_qobject_ptr(rep); setStatus(tr("Uploading skin")); - connect(rep, &QNetworkReply::uploadProgress, this, &Task::setProgress); -#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) - connect(rep, SIGNAL(errorOccurred(QNetworkReply::NetworkError)), this, SLOT(downloadError(QNetworkReply::NetworkError))); + connect(rep, &QNetworkReply::uploadProgress, this, &SkinUpload::setProgress); +#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) // QNetworkReply::errorOccurred added in 5.15 + connect(rep, &QNetworkReply::errorOccurred, this, &SkinUpload::downloadError); #else - connect(rep, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(downloadError(QNetworkReply::NetworkError))); + connect(rep, QOverload::of(&QNetworkReply::error), this, &SkinUpload::downloadError); #endif - connect(rep, SIGNAL(finished()), this, SLOT(downloadFinished())); + connect(rep, &QNetworkReply::sslErrors, this, &SkinUpload::sslErrors); + connect(rep, &QNetworkReply::finished, this, &SkinUpload::downloadFinished); } void SkinUpload::downloadError(QNetworkReply::NetworkError error) @@ -94,6 +95,17 @@ void SkinUpload::downloadError(QNetworkReply::NetworkError error) emitFailed(m_reply->errorString()); } +void SkinUpload::sslErrors(const QList& errors) +{ + int i = 1; + for (auto error : errors) { + qCritical() << "Skin Upload SSL Error #" << i << " : " << error.errorString(); + auto cert = error.certificate(); + qCritical() << "Certificate in question:\n" << cert.toText(); + i++; + } +} + void SkinUpload::downloadFinished() { // if the download failed diff --git a/launcher/minecraft/services/SkinUpload.h b/launcher/minecraft/services/SkinUpload.h index 2c1f0a2e..ac8c5b36 100644 --- a/launcher/minecraft/services/SkinUpload.h +++ b/launcher/minecraft/services/SkinUpload.h @@ -32,6 +32,7 @@ protected: public slots: void downloadError(QNetworkReply::NetworkError); + void sslErrors(const QList& errors); void downloadFinished(); }; diff --git a/launcher/net/Download.cpp b/launcher/net/Download.cpp index e8a1d0b0..30c1953f 100644 --- a/launcher/net/Download.cpp +++ b/launcher/net/Download.cpp @@ -129,10 +129,10 @@ void Download::executeTask() m_reply.reset(rep); connect(rep, &QNetworkReply::downloadProgress, this, &Download::downloadProgress); connect(rep, &QNetworkReply::finished, this, &Download::downloadFinished); -#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) - connect(rep, SIGNAL(errorOccurred(QNetworkReply::NetworkError)), SLOT(downloadError(QNetworkReply::NetworkError))); +#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) // QNetworkReply::errorOccurred added in 5.15 + connect(rep, &QNetworkReply::errorOccurred, this, &Download::downloadError); #else - connect(rep, SIGNAL(error(QNetworkReply::NetworkError)), SLOT(downloadError(QNetworkReply::NetworkError))); + connect(rep, QOverload::of(&QNetworkReply::error), this, &Download::downloadError); #endif connect(rep, &QNetworkReply::sslErrors, this, &Download::sslErrors); connect(rep, &QNetworkReply::readyRead, this, &Download::downloadReadyRead); diff --git a/launcher/net/Download.h b/launcher/net/Download.h index 7e1df322..01ec46db 100644 --- a/launcher/net/Download.h +++ b/launcher/net/Download.h @@ -70,7 +70,7 @@ class Download : public NetAction { protected slots: void downloadProgress(qint64 bytesReceived, qint64 bytesTotal) override; void downloadError(QNetworkReply::NetworkError error) override; - void sslErrors(const QList& errors); + void sslErrors(const QList& errors) override; void downloadFinished() override; void downloadReadyRead() override; diff --git a/launcher/net/HttpMetaCache.cpp b/launcher/net/HttpMetaCache.cpp index 0d7ca769..0ec82251 100644 --- a/launcher/net/HttpMetaCache.cpp +++ b/launcher/net/HttpMetaCache.cpp @@ -55,7 +55,7 @@ HttpMetaCache::HttpMetaCache(QString path) : QObject(), m_index_file(path) saveBatchingTimer.setSingleShot(true); saveBatchingTimer.setTimerType(Qt::VeryCoarseTimer); - connect(&saveBatchingTimer, SIGNAL(timeout()), SLOT(SaveNow())); + connect(&saveBatchingTimer, &QTimer::timeout, this, &HttpMetaCache::SaveNow); } HttpMetaCache::~HttpMetaCache() diff --git a/launcher/net/NetAction.h b/launcher/net/NetAction.h index 38fe058b..1ff8f601 100644 --- a/launcher/net/NetAction.h +++ b/launcher/net/NetAction.h @@ -61,6 +61,17 @@ class NetAction : public Task { virtual void downloadFinished() = 0; virtual void downloadReadyRead() = 0; + virtual void sslErrors(const QList& errors) { + int i = 1; + for (auto error : errors) { + qCritical() << "Network SSL Error #" << i << " : " << error.errorString(); + auto cert = error.certificate(); + qCritical() << "Certificate in question:\n" << cert.toText(); + i++; + } + + }; + public slots: void startAction(shared_qobject_ptr network) { diff --git a/launcher/net/Upload.cpp b/launcher/net/Upload.cpp index ccf43c2d..f3cdb786 100644 --- a/launcher/net/Upload.cpp +++ b/launcher/net/Upload.cpp @@ -232,9 +232,13 @@ namespace Net { QNetworkReply* rep = m_network->post(request, m_post_data); m_reply.reset(rep); - connect(rep, SIGNAL(downloadProgress(qint64, qint64)), SLOT(downloadProgress(qint64, qint64))); - connect(rep, SIGNAL(finished()), SLOT(downloadFinished())); - connect(rep, SIGNAL(error(QNetworkReply::NetworkError)), SLOT(downloadError(QNetworkReply::NetworkError))); + connect(rep, &QNetworkReply::downloadProgress, this, &Upload::downloadProgress); + connect(rep, &QNetworkReply::finished, this, &Upload::downloadFinished); +#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) // QNetworkReply::errorOccurred added in 5.15 + connect(rep, &QNetworkReply::errorOccurred, this, &Upload::downloadError); +#else + connect(rep, QOverload::of(&QNetworkReply::error), this, &Upload::downloadError); +#endif connect(rep, &QNetworkReply::sslErrors, this, &Upload::sslErrors); connect(rep, &QNetworkReply::readyRead, this, &Upload::downloadReadyRead); } diff --git a/launcher/net/Upload.h b/launcher/net/Upload.h index 5a0b2e74..f58b746a 100644 --- a/launcher/net/Upload.h +++ b/launcher/net/Upload.h @@ -54,7 +54,7 @@ namespace Net { protected slots: void downloadProgress(qint64 bytesReceived, qint64 bytesTotal) override; void downloadError(QNetworkReply::NetworkError error) override; - void sslErrors(const QList & errors); + void sslErrors(const QList & errors) override; void downloadFinished() override; void downloadReadyRead() override; diff --git a/launcher/screenshots/ImgurAlbumCreation.cpp b/launcher/screenshots/ImgurAlbumCreation.cpp index a72c32d3..ab425f1a 100644 --- a/launcher/screenshots/ImgurAlbumCreation.cpp +++ b/launcher/screenshots/ImgurAlbumCreation.cpp @@ -74,17 +74,20 @@ void ImgurAlbumCreation::executeTask() m_reply.reset(rep); connect(rep, &QNetworkReply::uploadProgress, this, &ImgurAlbumCreation::downloadProgress); connect(rep, &QNetworkReply::finished, this, &ImgurAlbumCreation::downloadFinished); -#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) - connect(rep, SIGNAL(errorOccurred(QNetworkReply::NetworkError)), SLOT(downloadError(QNetworkReply::NetworkError))); +#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) // QNetworkReply::errorOccurred added in 5.15 + connect(rep, &QNetworkReply::errorOccurred, this, &ImgurAlbumCreation::downloadError); #else - connect(rep, SIGNAL(error(QNetworkReply::NetworkError)), SLOT(downloadError(QNetworkReply::NetworkError))); + connect(rep, QOverload::of(&QNetworkReply::error), this, &ImgurAlbumCreation::downloadError); #endif + connect(rep, &QNetworkReply::sslErrors, this, &ImgurAlbumCreation::sslErrors); } + void ImgurAlbumCreation::downloadError(QNetworkReply::NetworkError error) { qDebug() << m_reply->errorString(); m_state = State::Failed; } + void ImgurAlbumCreation::downloadFinished() { if (m_state != State::Failed) @@ -120,6 +123,7 @@ void ImgurAlbumCreation::downloadFinished() return; } } + void ImgurAlbumCreation::downloadProgress(qint64 bytesReceived, qint64 bytesTotal) { setProgress(bytesReceived, bytesTotal); diff --git a/launcher/screenshots/ImgurUpload.cpp b/launcher/screenshots/ImgurUpload.cpp index f8ac9bc2..a50f9afa 100644 --- a/launcher/screenshots/ImgurUpload.cpp +++ b/launcher/screenshots/ImgurUpload.cpp @@ -89,12 +89,14 @@ void ImgurUpload::executeTask() m_reply.reset(rep); connect(rep, &QNetworkReply::uploadProgress, this, &ImgurUpload::downloadProgress); connect(rep, &QNetworkReply::finished, this, &ImgurUpload::downloadFinished); -#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) - connect(rep, SIGNAL(errorOccurred(QNetworkReply::NetworkError)), SLOT(downloadError(QNetworkReply::NetworkError))); +#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) // QNetworkReply::errorOccurred added in 5.15 + connect(rep, &QNetworkReply::errorOccurred, this, &ImgurUpload::downloadError); #else - connect(rep, SIGNAL(error(QNetworkReply::NetworkError)), SLOT(downloadError(QNetworkReply::NetworkError))); + connect(rep, QOverload::of(&QNetworkReply::error), this, &ImgurUpload::downloadError); #endif + connect(rep, &QNetworkReply::sslErrors, this, &ImgurUpload::sslErrors); } + void ImgurUpload::downloadError(QNetworkReply::NetworkError error) { qCritical() << "ImgurUpload failed with error" << m_reply->errorString() << "Server reply:\n" << m_reply->readAll(); @@ -108,6 +110,7 @@ void ImgurUpload::downloadError(QNetworkReply::NetworkError error) m_reply.reset(); emitFailed(); } + void ImgurUpload::downloadFinished() { if(finished) @@ -144,6 +147,7 @@ void ImgurUpload::downloadFinished() emit succeeded(); return; } + void ImgurUpload::downloadProgress(qint64 bytesReceived, qint64 bytesTotal) { setProgress(bytesReceived, bytesTotal); diff --git a/launcher/settings/SettingsObject.cpp b/launcher/settings/SettingsObject.cpp index 8a0bc045..634acd34 100644 --- a/launcher/settings/SettingsObject.cpp +++ b/launcher/settings/SettingsObject.cpp @@ -132,11 +132,10 @@ bool SettingsObject::reload() void SettingsObject::connectSignals(const Setting &setting) { - connect(&setting, SIGNAL(SettingChanged(const Setting &, QVariant)), - SLOT(changeSetting(const Setting &, QVariant))); - connect(&setting, SIGNAL(SettingChanged(const Setting &, QVariant)), + connect(&setting, &Setting::SettingChanged, this, &SettingsObject::changeSetting); + connect(&setting, SIGNAL(SettingChanged(const Setting &, QVariant)), this, SIGNAL(SettingChanged(const Setting &, QVariant))); - connect(&setting, SIGNAL(settingReset(Setting)), SLOT(resetSetting(const Setting &))); - connect(&setting, SIGNAL(settingReset(Setting)), SIGNAL(settingReset(const Setting &))); + connect(&setting, &Setting::settingReset, this, &SettingsObject::resetSetting); + connect(&setting, SIGNAL(settingReset(Setting)), this, SIGNAL(settingReset(const Setting &))); } diff --git a/launcher/tools/JProfiler.cpp b/launcher/tools/JProfiler.cpp index 1dc0d109..15c0cab6 100644 --- a/launcher/tools/JProfiler.cpp +++ b/launcher/tools/JProfiler.cpp @@ -68,8 +68,8 @@ void JProfiler::beginProfilingImpl(shared_qobject_ptr process) profiler->setArguments(profilerArgs); profiler->setProgram(profilerProgram); - connect(profiler, SIGNAL(started()), SLOT(profilerStarted())); - connect(profiler, SIGNAL(finished(int, QProcess::ExitStatus)), SLOT(profilerFinished(int,QProcess::ExitStatus))); + connect(profiler, &QProcess::started, this, &JProfiler::profilerStarted); + connect(profiler, QOverload::of(&QProcess::finished), this, &JProfiler::profilerFinished); m_profilerProcess = profiler; profiler->start(); diff --git a/launcher/tools/JVisualVM.cpp b/launcher/tools/JVisualVM.cpp index b1acc3c0..28ffb9cd 100644 --- a/launcher/tools/JVisualVM.cpp +++ b/launcher/tools/JVisualVM.cpp @@ -57,8 +57,8 @@ void JVisualVM::beginProfilingImpl(shared_qobject_ptr process) profiler->setArguments(profilerArgs); profiler->setProgram(programPath); - connect(profiler, SIGNAL(started()), SLOT(profilerStarted())); - connect(profiler, SIGNAL(finished(int, QProcess::ExitStatus)), SLOT(profilerFinished(int,QProcess::ExitStatus))); + connect(profiler, &QProcess::started, this, &JVisualVM::profilerStarted); + connect(profiler, QOverload::of(&QProcess::finished), this, &JVisualVM::profilerFinished); profiler->start(); m_profilerProcess = profiler; diff --git a/launcher/ui/pages/instance/VersionPage.cpp b/launcher/ui/pages/instance/VersionPage.cpp index 7fff3b93..fffb96f2 100644 --- a/launcher/ui/pages/instance/VersionPage.cpp +++ b/launcher/ui/pages/instance/VersionPage.cpp @@ -501,7 +501,7 @@ void VersionPage::on_actionDownload_All_triggered() return; } ProgressDialog tDialog(this); - connect(updateTask.get(), SIGNAL(failed(QString)), SLOT(onGameUpdateError(QString))); + connect(updateTask.get(), &Task::failed, this, &VersionPage::onGameUpdateError); // FIXME: unused return value tDialog.execWithTask(updateTask.get()); updateButtons(); From f41426f3945ff60a26b34935bb1b0b43d8de800b Mon Sep 17 00:00:00 2001 From: Japa Date: Fri, 21 Apr 2023 00:30:38 -0300 Subject: [PATCH 137/185] Instance Description displays the last launch date Initial Draft using the Standard C++ Library, still requires testing. Signed-off-by: Japa --- launcher/minecraft/MinecraftInstance.cpp | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/launcher/minecraft/MinecraftInstance.cpp b/launcher/minecraft/MinecraftInstance.cpp index af4da5d0..f9f4a56b 100644 --- a/launcher/minecraft/MinecraftInstance.cpp +++ b/launcher/minecraft/MinecraftInstance.cpp @@ -925,7 +925,18 @@ QString MinecraftInstance::getStatusbarDescription() if(m_settings->get("ShowGameTime").toBool()) { if (lastTimePlayed() > 0) { - description.append(tr(", last played for %1").arg(Time::prettifyDuration(lastTimePlayed()))); + struct tm * localTime_format; + localTime_format = localtime(lastLaunchTime()); + + char lastLaunchTime_formatted[13]; + strftime(lastLaunchTime_formatted,13,"%Ex",localTime_format); + + description.append( + tr(", last played at %1 for %2").arg( + lastLaunchTime_formatted, + Time::prettifyDuration(lastTimePlayed()) + ) + ); } if (totalTimePlayed() > 0) { From 92cda68480f3f9bd5b5184314a2dfcdbdda7543b Mon Sep 17 00:00:00 2001 From: Japa Date: Fri, 21 Apr 2023 11:18:17 -0300 Subject: [PATCH 138/185] Update launcher/minecraft/MinecraftInstance.cpp Co-authored-by: Rachel Powers <508861+Ryex@users.noreply.github.com> Signed-off-by: Japa --- launcher/minecraft/MinecraftInstance.cpp | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/launcher/minecraft/MinecraftInstance.cpp b/launcher/minecraft/MinecraftInstance.cpp index f9f4a56b..3ab8b3af 100644 --- a/launcher/minecraft/MinecraftInstance.cpp +++ b/launcher/minecraft/MinecraftInstance.cpp @@ -925,18 +925,10 @@ QString MinecraftInstance::getStatusbarDescription() if(m_settings->get("ShowGameTime").toBool()) { if (lastTimePlayed() > 0) { - struct tm * localTime_format; - localTime_format = localtime(lastLaunchTime()); - - char lastLaunchTime_formatted[13]; - strftime(lastLaunchTime_formatted,13,"%Ex",localTime_format); - - description.append( - tr(", last played at %1 for %2").arg( - lastLaunchTime_formatted, - Time::prettifyDuration(lastTimePlayed()) - ) - ); + QDateTime lastLaunchTime = QDateTime::fromMSecsSinceEpoch(lastLaunch()); + description.append(tr(", last played at %1 for %2") + .arg(QLocale::system().toString(lastLaunchTime, QLocale::ShortFormat)) + .arg(Time::prettifyDuration(lastTimePlayed()))); } if (totalTimePlayed() > 0) { From 672f5cf160b8630210f07d250989278f99c4e0ad Mon Sep 17 00:00:00 2001 From: Japa Date: Fri, 21 Apr 2023 19:46:33 -0300 Subject: [PATCH 139/185] Update launcher/minecraft/MinecraftInstance.cpp Co-authored-by: Sefa Eyeoglu Signed-off-by: Japa --- launcher/minecraft/MinecraftInstance.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/minecraft/MinecraftInstance.cpp b/launcher/minecraft/MinecraftInstance.cpp index 3ab8b3af..3183b41d 100644 --- a/launcher/minecraft/MinecraftInstance.cpp +++ b/launcher/minecraft/MinecraftInstance.cpp @@ -927,7 +927,7 @@ QString MinecraftInstance::getStatusbarDescription() if (lastTimePlayed() > 0) { QDateTime lastLaunchTime = QDateTime::fromMSecsSinceEpoch(lastLaunch()); description.append(tr(", last played at %1 for %2") - .arg(QLocale::system().toString(lastLaunchTime, QLocale::ShortFormat)) + .arg(QLocale().toString(lastLaunchTime, QLocale::ShortFormat)) .arg(Time::prettifyDuration(lastTimePlayed()))); } From 7298f9c273bbfe31bf6c81af7fb6405b8baaeb13 Mon Sep 17 00:00:00 2001 From: Japa Date: Wed, 26 Apr 2023 16:06:17 -0300 Subject: [PATCH 140/185] Fixed typo Signed-off-by: Japa --- launcher/minecraft/MinecraftInstance.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/minecraft/MinecraftInstance.cpp b/launcher/minecraft/MinecraftInstance.cpp index 3183b41d..b2171a85 100644 --- a/launcher/minecraft/MinecraftInstance.cpp +++ b/launcher/minecraft/MinecraftInstance.cpp @@ -926,7 +926,7 @@ QString MinecraftInstance::getStatusbarDescription() { if (lastTimePlayed() > 0) { QDateTime lastLaunchTime = QDateTime::fromMSecsSinceEpoch(lastLaunch()); - description.append(tr(", last played at %1 for %2") + description.append(tr(", last played on %1 for %2") .arg(QLocale().toString(lastLaunchTime, QLocale::ShortFormat)) .arg(Time::prettifyDuration(lastTimePlayed()))); } From ff07714e8c955003bb3deec84ec6cce6fc3ef5e6 Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Thu, 27 Apr 2023 16:01:40 +0200 Subject: [PATCH 141/185] 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 - .../modpacksch/FTBPackInstallTask.cpp | 387 ------------------ .../modpacksch/FTBPackInstallTask.h | 101 ----- .../modpacksch/FTBPackManifest.cpp | 195 --------- .../modplatform/modpacksch/FTBPackManifest.h | 168 -------- launcher/ui/dialogs/NewInstanceDialog.cpp | 2 - .../pages/modplatform/ftb/FtbFilterModel.cpp | 93 ----- .../ui/pages/modplatform/ftb/FtbFilterModel.h | 51 --- .../ui/pages/modplatform/ftb/FtbListModel.cpp | 304 -------------- .../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 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 - - - -
From 788fa40c2ae0e9786c070f4593a4e7ff6efc77d3 Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Sat, 29 Apr 2023 18:05:48 -0700 Subject: [PATCH 142/185] refactor: Move ini to use QSettings && drop get/setList functions Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- launcher/BaseInstance.cpp | 12 +- launcher/CMakeLists.txt | 1 + launcher/FileSystem.cpp | 3 +- launcher/QVariantUtils.h | 70 ++++++++ launcher/StringUtils.cpp | 36 ++++ launcher/StringUtils.h | 36 ++++ .../minecraft/mod/tasks/LocalModParseTask.cpp | 6 +- launcher/settings/INIFile.cpp | 155 ++++-------------- launcher/settings/INIFile.h | 76 ++++----- launcher/settings/SettingsObject.cpp | 13 -- launcher/settings/SettingsObject.h | 39 ----- tests/INIFile_test.cpp | 24 +-- 12 files changed, 220 insertions(+), 251 deletions(-) create mode 100644 launcher/QVariantUtils.h diff --git a/launcher/BaseInstance.cpp b/launcher/BaseInstance.cpp index ad45aa2d..a8fce879 100644 --- a/launcher/BaseInstance.cpp +++ b/launcher/BaseInstance.cpp @@ -188,25 +188,25 @@ bool BaseInstance::shouldStopOnConsoleOverflow() const QStringList BaseInstance::getLinkedInstances() const { - return m_settings->getList("linkedInstances"); + return m_settings->get("linkedInstances").toStringList(); } void BaseInstance::setLinkedInstances(const QStringList& list) { - auto linkedInstances = m_settings->getList("linkedInstances"); - m_settings->setList("linkedInstances", list); + auto linkedInstances = m_settings->get("linkedInstances").toStringList(); + m_settings->set("linkedInstances", list); } void BaseInstance::addLinkedInstanceId(const QString& id) { - auto linkedInstances = m_settings->getList("linkedInstances"); + auto linkedInstances = m_settings->get("linkedInstances").toStringList(); linkedInstances.append(id); setLinkedInstances(linkedInstances); } bool BaseInstance::removeLinkedInstanceId(const QString& id) { - auto linkedInstances = m_settings->getList("linkedInstances"); + auto linkedInstances = m_settings->get("linkedInstances").toStringList(); int numRemoved = linkedInstances.removeAll(id); setLinkedInstances(linkedInstances); return numRemoved > 0; @@ -214,7 +214,7 @@ bool BaseInstance::removeLinkedInstanceId(const QString& id) bool BaseInstance::isLinkedToInstanceId(const QString& id) const { - auto linkedInstances = m_settings->getList("linkedInstances"); + auto linkedInstances = m_settings->get("linkedInstances").toStringList(); return linkedInstances.contains(id); } diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index 4de0f73b..def73b85 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -26,6 +26,7 @@ set(CORE_SOURCES MMCZip.cpp StringUtils.h StringUtils.cpp + QVariantUtils.h RuntimeContext.h # Basic instance manipulation tasks (derived from InstanceTask) diff --git a/launcher/FileSystem.cpp b/launcher/FileSystem.cpp index bd985c5b..d98526df 100644 --- a/launcher/FileSystem.cpp +++ b/launcher/FileSystem.cpp @@ -329,8 +329,7 @@ bool create_link::operator()(const QString& offset, bool dryRun) /** * @brief Make a list of all the links to make - * @param offset subdirectory form src to link to dest - * @return if there was an error during the attempt to link + * @param offset subdirectory of src to link to dest */ void create_link::make_link_list(const QString& offset) { diff --git a/launcher/QVariantUtils.h b/launcher/QVariantUtils.h new file mode 100644 index 00000000..7e422c3e --- /dev/null +++ b/launcher/QVariantUtils.h @@ -0,0 +1,70 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (C) 2022 Sefa Eyeoglu + * Copyright (C) 2023 flowln + * + * 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 + +namespace QVariantUtils { + +template +inline QList toList(QVariant src) { + QVariantList variantList = src.toList(); + + QList list_t; + list_t.reserve(variantList.size()); + for (const QVariant& v : variantList) + { + list_t.append(v.value()); + } + return list_t; +} + +template +inline QVariant fromList(QList val) { + QVariantList variantList; + variantList.reserve(val.size()); + for (const T& v : val) + { + variantList.append(v); + } + + return variantList; +} + +} \ No newline at end of file diff --git a/launcher/StringUtils.cpp b/launcher/StringUtils.cpp index 2fa56501..d109d63d 100644 --- a/launcher/StringUtils.cpp +++ b/launcher/StringUtils.cpp @@ -1,3 +1,39 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (C) 2022 Sefa Eyeoglu + * Copyright (C) 2023 flowln + * + * 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 "StringUtils.h" #include diff --git a/launcher/StringUtils.h b/launcher/StringUtils.h index c4a6ab31..36c8cf8f 100644 --- a/launcher/StringUtils.h +++ b/launcher/StringUtils.h @@ -1,3 +1,39 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (C) 2022 Sefa Eyeoglu + * Copyright (C) 2023 flowln + * + * 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 diff --git a/launcher/minecraft/mod/tasks/LocalModParseTask.cpp b/launcher/minecraft/mod/tasks/LocalModParseTask.cpp index da27a505..5342d693 100644 --- a/launcher/minecraft/mod/tasks/LocalModParseTask.cpp +++ b/launcher/minecraft/mod/tasks/LocalModParseTask.cpp @@ -242,7 +242,7 @@ ModDetails ReadQuiltModInfo(QByteArray contents) return details; } -ModDetails ReadForgeInfo(QByteArray contents) +ModDetails ReadForgeInfo(QString fileName) { ModDetails details; // Read the data @@ -250,7 +250,7 @@ ModDetails ReadForgeInfo(QByteArray contents) details.mod_id = "Forge"; details.homeurl = "http://www.minecraftforge.net/forum/"; INIFile ini; - if (!ini.loadFile(contents)) + if (!ini.loadFile(fileName)) return details; QString major = ini.get("forge.major.number", "0").toString(); @@ -422,7 +422,7 @@ bool processZIP(Mod& mod, ProcessingLevel level) return false; } - details = ReadForgeInfo(file.readAll()); + details = ReadForgeInfo(file.getFileName()); file.close(); zip.close(); diff --git a/launcher/settings/INIFile.cpp b/launcher/settings/INIFile.cpp index e48e6f47..f0347cab 100644 --- a/launcher/settings/INIFile.cpp +++ b/launcher/settings/INIFile.cpp @@ -1,7 +1,8 @@ // SPDX-License-Identifier: GPL-3.0-only /* - * PolyMC - Minecraft Launcher + * Prism Launcher - Minecraft Launcher * Copyright (C) 2022 Sefa Eyeoglu + * Copyright (C) 2023 flowln * * 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 @@ -42,132 +43,51 @@ #include #include +#include + INIFile::INIFile() { } -QString INIFile::unescape(QString orig) -{ - QString out; - QChar prev = QChar::Null; - for(auto c: orig) - { - if(prev == '\\') - { - if(c == 'n') - out += '\n'; - else if(c == 't') - out += '\t'; - else if(c == '#') - out += '#'; - else - out += c; - prev = QChar::Null; - } - else - { - if(c == '\\') - { - prev = c; - continue; - } - out += c; - prev = QChar::Null; - } - } - return out; -} - -QString INIFile::escape(QString orig) -{ - QString out; - for(auto c: orig) - { - if(c == '\n') - out += "\\n"; - else if (c == '\t') - out += "\\t"; - else if(c == '\\') - out += "\\\\"; - else if(c == '#') - out += "\\#"; - else - out += c; - } - return out; -} - bool INIFile::saveFile(QString fileName) { - QByteArray outArray; - for (Iterator iter = begin(); iter != end(); iter++) - { - QString value = iter.value().toString(); - value = escape(value); - outArray.append(iter.key().toUtf8()); - outArray.append('='); - outArray.append(value.toUtf8()); - outArray.append('\n'); - } + QSettings _settings_obj{ fileName, QSettings::Format::IniFormat }; + _settings_obj.setFallbacksEnabled(false); + + for (Iterator iter = begin(); iter != end(); iter++) + _settings_obj.setValue(iter.key(), iter.value()); + + _settings_obj.sync(); + + if (auto status = _settings_obj.status(); status != QSettings::Status::NoError) { + // Shouldn't be possible! + Q_ASSERT(status != QSettings::Status::FormatError); + + if (status == QSettings::Status::AccessError) + qCritical() << "An access error occurred (e.g. trying to write to a read-only file)."; - try - { - FS::write(fileName, outArray); - } - catch (const Exception &e) - { - qCritical() << e.what(); return false; } return true; } - bool INIFile::loadFile(QString fileName) { - QFile file(fileName); - if (!file.open(QIODevice::ReadOnly)) + QSettings _settings_obj{ fileName, QSettings::Format::IniFormat }; + _settings_obj.setFallbacksEnabled(false); + + if (auto status = _settings_obj.status(); status != QSettings::Status::NoError) { + if (status == QSettings::Status::AccessError) + qCritical() << "An access error occurred (e.g. trying to write to a read-only file)."; + if (status == QSettings::Status::FormatError) + qCritical() << "A format error occurred (e.g. loading a malformed INI file)."; return false; - bool success = loadFile(file.readAll()); - file.close(); - return success; -} - -bool INIFile::loadFile(QByteArray file) -{ - QTextStream in(file); -#if QT_VERSION <= QT_VERSION_CHECK(6, 0, 0) - in.setCodec("UTF-8"); -#endif - - QStringList lines = in.readAll().split('\n'); - for (int i = 0; i < lines.count(); i++) - { - QString &lineRaw = lines[i]; - // Ignore comments. - int commentIndex = 0; - QString line = lineRaw; - // Search for comments until no more escaped # are available - while((commentIndex = line.indexOf('#', commentIndex + 1)) != -1) { - if(commentIndex > 0 && line.at(commentIndex - 1) == '\\') { - continue; - } - line = line.left(lineRaw.indexOf('#')).trimmed(); - } - - int eqPos = line.indexOf('='); - if (eqPos == -1) - continue; - QString key = line.left(eqPos).trimmed(); - QString valueStr = line.right(line.length() - eqPos - 1).trimmed(); - - valueStr = unescape(valueStr); - - QVariant value(valueStr); - this->operator[](key) = value; } + for (auto&& key : _settings_obj.allKeys()) + insert(key, _settings_obj.value(key)); + return true; } @@ -184,20 +104,3 @@ void INIFile::set(QString key, QVariant val) this->operator[](key) = val; } -void INIFile::setList(QString key, QVariantList val) -{ - QString stringList = QJsonDocument(QVariant(val).toJsonArray()).toJson(QJsonDocument::Compact); - - this->operator[](key) = stringList; -} - -QVariantList INIFile::getList(QString key, QVariantList def) const -{ - if (this->contains(key)) { - auto src = this->operator[](key); - - return QJsonDocument::fromJson(src.toByteArray()).toVariant().toList(); - } - - return def; -} diff --git a/launcher/settings/INIFile.h b/launcher/settings/INIFile.h index 86bf0898..0d5c05eb 100644 --- a/launcher/settings/INIFile.h +++ b/launcher/settings/INIFile.h @@ -1,16 +1,37 @@ -/* Copyright 2013-2021 MultiMC Contributors +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (C) 2022 Sefa Eyeoglu + * Copyright (C) 2023 flowln * - * 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 @@ -28,44 +49,9 @@ class INIFile : public QMap public: explicit INIFile(); - bool loadFile(QByteArray file); bool loadFile(QString fileName); bool saveFile(QString fileName); QVariant get(QString key, QVariant def) const; void set(QString key, QVariant val); - static QString unescape(QString orig); - static QString escape(QString orig); - - void setList(QString key, QVariantList val); - template void setList(QString key, QList val) - { - QVariantList variantList; - variantList.reserve(val.size()); - for (const T& v : val) - { - variantList.append(v); - } - - this->setList(key, variantList); - } - - QVariantList getList(QString key, QVariantList def) const; - template QList getList(QString key, QList def) const - { - if (this->contains(key)) { - QVariant src = this->operator[](key); - QVariantList variantList = QJsonDocument::fromJson(src.toByteArray()).toVariant().toList(); - - QListTList; - TList.reserve(variantList.size()); - for (const QVariant& v : variantList) - { - TList.append(v.value()); - } - return TList; - } - - return def; - } }; diff --git a/launcher/settings/SettingsObject.cpp b/launcher/settings/SettingsObject.cpp index 4c51d6e9..8a0bc045 100644 --- a/launcher/settings/SettingsObject.cpp +++ b/launcher/settings/SettingsObject.cpp @@ -121,19 +121,6 @@ bool SettingsObject::contains(const QString &id) return m_settings.contains(id); } -bool SettingsObject::setList(const QString &id, QVariantList value) -{ - QString stringList = QJsonDocument(QVariant(value).toJsonArray()).toJson(QJsonDocument::Compact); - - return set(id, stringList); -} - -QVariantList SettingsObject::getList(const QString &id) -{ - QVariant value = this->get(id); - return QJsonDocument::fromJson(value.toByteArray()).toVariant().toList(); -} - bool SettingsObject::reload() { for (auto setting : m_settings.values()) diff --git a/launcher/settings/SettingsObject.h b/launcher/settings/SettingsObject.h index ff430172..4d735511 100644 --- a/launcher/settings/SettingsObject.h +++ b/launcher/settings/SettingsObject.h @@ -144,45 +144,6 @@ public: */ bool contains(const QString &id); - /*! - * \brief Sets the value of the setting with the given ID with a json list. - * If no setting with the given ID exists, returns false - * \param id The ID of the setting to change. - * \param value The new value of the setting. - */ - bool setList(const QString &id, QVariantList value); - template bool setList(const QString &id, QList val) - { - QVariantList variantList; - variantList.reserve(val.size()); - for (const T& v : val) - { - variantList.append(v); - } - - return setList(id, variantList); - } - - /** - * \brief Gets the value of the setting with the given ID as if it were a json list. - * \param id The ID of the setting to change. - * \return The setting's value as a QVariantList. - * If no setting with the given ID exists, returns an empty QVariantList. - */ - QVariantList getList(const QString &id); - template QList getList(const QString &id) - { - QVariantList variantList = this->getList(id); - - QListTList; - TList.reserve(variantList.size()); - for (const QVariant& v : variantList) - { - TList.append(v.value()); - } - return TList; - } - /*! * \brief Reloads the settings and emit signals for changed settings * \return True if reloading was successful diff --git a/tests/INIFile_test.cpp b/tests/INIFile_test.cpp index d13937c0..4be8133c 100644 --- a/tests/INIFile_test.cpp +++ b/tests/INIFile_test.cpp @@ -4,6 +4,7 @@ #include #include +#include class IniFileTest : public QObject { @@ -30,15 +31,6 @@ slots: QTest::newRow("Escape sequences 2") << "\"\n\n\""; QTest::newRow("Hashtags") << "some data#something"; } - void test_Escape() - { - QFETCH(QString, through); - - QString there = INIFile::escape(through); - QString back = INIFile::unescape(there); - - QCOMPARE(back, through); - } void test_SaveLoad() { @@ -61,32 +53,30 @@ slots: void test_SaveLoadLists() { - QString slist_strings = "[\"a\",\"b\",\"c\"]"; + QString slist_strings = "(\"a\",\"b\",\"c\")"; QStringList list_strings = {"a", "b", "c"}; - QString slist_numbers = "[1,2,3,10]"; + QString slist_numbers = "(1,2,3,10)"; QList list_numbers = {1, 2, 3, 10}; QString filename = "test_SaveLoadLists.ini"; INIFile f; - f.setList("list_strings", list_strings); - f.setList("list_numbers", list_numbers); + f.set("list_strings", list_strings); + f.set("list_numbers", QVariantUtils::fromList(list_numbers)); f.saveFile(filename); // load INIFile f2; f2.loadFile(filename); - QStringList out_list_strings = f2.getList("list_strings", QStringList()); + QStringList out_list_strings = f2.get("list_strings", QStringList()).toStringList(); qDebug() << "OutStringList" << out_list_strings; - QList out_list_numbers = f2.getList("list_numbers", QList()); + QList out_list_numbers = QVariantUtils::toList(f2.get("list_numbers", QVariantUtils::fromList(QList()))); qDebug() << "OutNumbersList" << out_list_numbers; - QCOMPARE(f2.get("list_strings","NOT SET").toString(), slist_strings); QCOMPARE(out_list_strings, list_strings); - QCOMPARE(f2.get("list_numbers","NOT SET").toString(), slist_numbers); QCOMPARE(out_list_numbers, list_numbers); } }; 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: [PATCH 143/185] 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 ++++- .../minecraft/mod/ResourcePackFolderModel.cpp | 3 ++- .../minecraft/mod/ResourcePackFolderModel.h | 2 +- launcher/minecraft/mod/ShaderPackFolderModel.h | 4 +++- .../minecraft/mod/TexturePackFolderModel.cpp | 4 +++- .../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(-) 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); From 495103f72e85e3664458e6425172bfeb8acf7a97 Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Sat, 29 Apr 2023 14:30:57 -0700 Subject: [PATCH 144/185] fix: set `x-xbl-contract-version` header during xbox auth step Refrencing GDlauncher and ATLauncher code for auth as well as https://learn.microsoft.com/en-us/gaming/gdk/_content/gc/reference/live/rest/additional/httpstandardheaders it is possible some of microsoft's server's are rejecting our request because of this missing header? Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- launcher/minecraft/auth/steps/XboxUserStep.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/launcher/minecraft/auth/steps/XboxUserStep.cpp b/launcher/minecraft/auth/steps/XboxUserStep.cpp index 53069597..842eb60f 100644 --- a/launcher/minecraft/auth/steps/XboxUserStep.cpp +++ b/launcher/minecraft/auth/steps/XboxUserStep.cpp @@ -38,6 +38,10 @@ void XboxUserStep::perform() { QNetworkRequest request = QNetworkRequest(QUrl("https://user.auth.xboxlive.com/user/authenticate")); request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); request.setRawHeader("Accept", "application/json"); + // set contract-verison header (prevent err 400 bad-request?) + // https://learn.microsoft.com/en-us/gaming/gdk/_content/gc/reference/live/rest/additional/httpstandardheaders + request.setRawHeader("x-xbl-contract-version", "1"); + auto *requestor = new AuthRequest(this); connect(requestor, &AuthRequest::finished, this, &XboxUserStep::onRequestDone); requestor->post(request, xbox_auth_data.toUtf8()); 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: [PATCH 145/185] 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 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) { - setStepStatus(msg); + auto taskProgress = m_task_progress.value(task->getUid()); + taskProgress->status = msg; + updateState(); } -void ConcurrentTask::subTaskProgress(qint64 current, qint64 total) +void ConcurrentTask::subTaskProgress(Task::Ptr task, qint64 current, qint64 total) { - m_stepProgress = current; - m_stepTotalProgress = total; + auto taskProgress = m_task_progress.value(task->getUid()); + + taskProgress->current = current; + taskProgress->total = total; + + taskProgress->details = task->getDetails(); + + updateStepProgress(); + updateState(); +} + +void ConcurrentTask::subTaskStepProgress(Task::Ptr task, QList 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)); + + + } + +} + +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(){ 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: [PATCH 146/185] 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 + .../minecraft/update/FMLLibrariesTask.cpp | 1 + launcher/minecraft/update/LibrariesTask.cpp | 2 + .../atlauncher/ATLPackInstallTask.cpp | 2 + .../flame/FlameInstanceCreationTask.cpp | 4 +- .../legacy_ftb/PackInstallTask.cpp | 1 + .../modrinth/ModrinthInstanceCreationTask.cpp | 2 + .../technic/SingleZipPackInstallTask.cpp | 1 + .../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 | 120 ++++++++++++++---- launcher/ui/widgets/SubTaskProgressBar.ui | 31 ++++- tests/Task_test.cpp | 9 +- 25 files changed, 275 insertions(+), 103 deletions(-) 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,21 +31,103 @@ Please wait... - - + + + + + + + + 0 + 0 + + + + + 0 + 15 + + + + Global Task Status... + + + + + + + Global Status Details... + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + true + + + 0 + 24 + + 24 - + + + + + 0 + 0 + + + + + 0 + 100 + + + + QFrame::StyledPanel + + + Qt::ScrollBarAsNeeded + + + QAbstractScrollArea::AdjustToContents + + + true + + + + + 0 + 0 + 584 + 146 + + + + + 2 + + + + + + - + 0 0 @@ -55,22 +137,6 @@ - - - - - - - - 0 - 0 - - - - Global Task Status... - - - 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(){ 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: [PATCH 147/185] 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(-) 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 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: [PATCH 148/185] 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 + .../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 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 + From cdccb25fe30cdf772ffdbf73b6c283c98f152075 Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Sat, 1 Apr 2023 10:41:07 -0700 Subject: [PATCH 149/185] feat: add download size to download details alongside speed Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- launcher/net/Download.cpp | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/launcher/net/Download.cpp b/launcher/net/Download.cpp index 86e4bd97..e7536dc9 100644 --- a/launcher/net/Download.cpp +++ b/launcher/net/Download.cpp @@ -52,6 +52,7 @@ #include "Application.h" #include "logging.h" +#include "net/NetAction.h" namespace Net { @@ -190,13 +191,23 @@ void Download::downloadProgress(qint64 bytesReceived, qint64 bytesTotal) { auto now = m_clock.now(); auto elapsed = now - m_last_progress_time; + + // use milliseconds for speed precision auto elapsed_ms = std::chrono::duration_cast(elapsed).count(); auto bytes_recived_since = bytesReceived - m_last_progress_bytes; + + // current bytes out of total bytes + QString dl_progress = tr("%1 / %2").arg(humanReadableFileSize(bytesReceived)).arg(humanReadableFileSize(bytesTotal)); + + QString dl_speed; if (elapsed_ms > 0) { - setDetails(humanReadableFileSize(bytes_recived_since / elapsed_ms * 1000) + "/s"); + // bytes per second + dl_speed = tr("%1/s").arg(humanReadableFileSize(bytes_recived_since / elapsed_ms * 1000)); } else { - setDetails("0 b/s"); - } + dl_speed = tr("0 b/s"); + } + + setDetails(dl_progress + "\n" + dl_speed); setProgress(bytesReceived, bytesTotal); } From 6306fb564b8f9fcc3ef0078c55850d6acb2507cf Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Sat, 1 Apr 2023 10:57:55 -0700 Subject: [PATCH 150/185] feat: add UID to debug lines of upload tasks Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- launcher/net/PasteUpload.cpp | 20 ++++++++++---------- launcher/net/Upload.cpp | 36 ++++++++++++++++++------------------ 2 files changed, 28 insertions(+), 28 deletions(-) diff --git a/launcher/net/PasteUpload.cpp b/launcher/net/PasteUpload.cpp index d5df3799..24f456e3 100644 --- a/launcher/net/PasteUpload.cpp +++ b/launcher/net/PasteUpload.cpp @@ -149,7 +149,7 @@ void PasteUpload::executeTask() void PasteUpload::downloadError(QNetworkReply::NetworkError error) { // error happened during download. - qCCritical(taskUploadLogC) << "Network error: " << error; + qCCritical(taskUploadLogC) << getUid().toString() << "Network error: " << error; emitFailed(m_reply->errorString()); } @@ -168,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)); - qCCritical(taskUploadLogC) << m_uploadUrl << " returned unexpected status code " << statusCode << " with body: " << data; + qCCritical(taskUploadLogC) << getUid().toString() << m_uploadUrl << " returned unexpected status code " << statusCode << " with body: " << data; m_reply.reset(); return; } @@ -189,7 +189,7 @@ void PasteUpload::downloadFinished() else { emitFailed(tr("Error: %1 returned a malformed response body").arg(m_uploadUrl)); - qCCritical(taskUploadLogC) << m_uploadUrl << " returned malformed response body: " << data; + qCCritical(taskUploadLogC) << getUid().toString() << getUid().toString() << m_uploadUrl << " returned malformed response body: " << data; return; } break; @@ -208,15 +208,15 @@ void PasteUpload::downloadFinished() { QString error = jsonObj["error"].toString(); emitFailed(tr("Error: %1 returned an error: %2").arg(m_uploadUrl, error)); - qCCritical(taskUploadLogC) << m_uploadUrl << " returned error: " << error; - qCCritical(taskUploadLogC) << "Response body: " << data; + qCCritical(taskUploadLogC) << getUid().toString() << m_uploadUrl << " returned error: " << error; + qCCritical(taskUploadLogC) << getUid().toString() << "Response body: " << data; return; } } else { emitFailed(tr("Error: %1 returned a malformed response body").arg(m_uploadUrl)); - qCCritical(taskUploadLogC) << m_uploadUrl << " returned malformed response body: " << data; + qCCritical(taskUploadLogC) << getUid().toString() << m_uploadUrl << " returned malformed response body: " << data; return; } break; @@ -236,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)); - qCCritical(taskUploadLogC) << m_uploadUrl << " returned error: " << error; - qCCritical(taskUploadLogC) << "Error message: " << message; - qCCritical(taskUploadLogC) << "Response body: " << data; + qCCritical(taskUploadLogC) << getUid().toString() << m_uploadUrl << " returned error: " << error; + qCCritical(taskUploadLogC) << getUid().toString() << "Error message: " << message; + qCCritical(taskUploadLogC) << getUid().toString() << "Response body: " << data; return; } } else { emitFailed(tr("Error: %1 returned a malformed response body").arg(m_uploadUrl)); - qCCritical(taskUploadLogC) << m_uploadUrl << " returned malformed response body: " << data; + qCCritical(taskUploadLogC) << getUid().toString() << m_uploadUrl << " returned malformed response body: " << data; return; } break; diff --git a/launcher/net/Upload.cpp b/launcher/net/Upload.cpp index 518e302c..195e1679 100644 --- a/launcher/net/Upload.cpp +++ b/launcher/net/Upload.cpp @@ -62,11 +62,11 @@ namespace Net { void Upload::downloadError(QNetworkReply::NetworkError error) { if (error == QNetworkReply::OperationCanceledError) { - qCCritical(taskUploadLogC) << "Aborted " << m_url.toString(); + qCCritical(taskUploadLogC) << getUid().toString() << "Aborted " << m_url.toString(); m_state = State::AbortedByUser; } else { // error happened during download. - qCCritical(taskUploadLogC) << "Failed " << m_url.toString() << " with reason " << error; + qCCritical(taskUploadLogC) << getUid().toString() << "Failed " << m_url.toString() << " with reason " << error; m_state = State::Failed; } } @@ -74,9 +74,9 @@ namespace Net { void Upload::sslErrors(const QList &errors) { int i = 1; for (const auto& error : errors) { - qCCritical(taskUploadLogC) << "Upload" << m_url.toString() << "SSL Error #" << i << " : " << error.errorString(); + qCCritical(taskUploadLogC) << getUid().toString() << "Upload" << m_url.toString() << "SSL Error #" << i << " : " << error.errorString(); auto cert = error.certificate(); - qCCritical(taskUploadLogC) << "Certificate in question:\n" << cert.toText(); + qCCritical(taskUploadLogC) << getUid().toString() << "Certificate in question:\n" << cert.toText(); i++; } } @@ -119,17 +119,17 @@ namespace Net { */ redirect = QUrl(redirectStr, QUrl::TolerantMode); if (!redirect.isValid()) { - qCWarning(taskUploadLogC) << "Failed to parse redirect URL:" << redirectStr; + qCWarning(taskUploadLogC) << getUid().toString() << "Failed to parse redirect URL:" << redirectStr; downloadError(QNetworkReply::ProtocolFailure); return false; } - qCDebug(taskUploadLogC) << "Fixed location header:" << redirect; + qCDebug(taskUploadLogC) << getUid().toString() << "Fixed location header:" << redirect; } else { - qCDebug(taskUploadLogC) << "Location header:" << redirect; + qCDebug(taskUploadLogC) << getUid().toString() << "Location header:" << redirect; } m_url = QUrl(redirect.toString()); - qCDebug(taskUploadLogC) << "Following redirect to " << m_url.toString(); + qCDebug(taskUploadLogC) << getUid().toString() << "Following redirect to " << m_url.toString(); startAction(m_network); return true; } @@ -138,25 +138,25 @@ namespace Net { // handle HTTP redirection first // very unlikely for post requests, still can happen if (handleRedirect()) { - qCDebug(taskUploadLogC) << "Upload redirected:" << m_url.toString(); + qCDebug(taskUploadLogC) << getUid().toString() << "Upload redirected:" << m_url.toString(); return; } // if the download failed before this point ... if (m_state == State::Succeeded) { - qCDebug(taskUploadLogC) << "Upload failed but we are allowed to proceed:" << m_url.toString(); + qCDebug(taskUploadLogC) << getUid().toString() << "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) { - qCDebug(taskUploadLogC) << "Upload failed in previous step:" << m_url.toString(); + qCDebug(taskUploadLogC) << getUid().toString() << "Upload failed in previous step:" << m_url.toString(); m_sink->abort(); m_reply.reset(); emit failed(""); return; } else if (m_state == State::AbortedByUser) { - qCDebug(taskUploadLogC) << "Upload aborted in previous step:" << m_url.toString(); + qCDebug(taskUploadLogC) << getUid().toString() << "Upload aborted in previous step:" << m_url.toString(); m_sink->abort(); m_reply.reset(); emit aborted(); @@ -166,21 +166,21 @@ namespace Net { // make sure we got all the remaining data, if any auto data = m_reply->readAll(); if (data.size()) { - qCDebug(taskUploadLogC) << "Writing extra" << data.size() << "bytes"; + qCDebug(taskUploadLogC) << 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(taskUploadLogC) << "Upload failed to finalize:" << m_url.toString(); + qCDebug(taskUploadLogC) << getUid().toString() << "Upload failed to finalize:" << m_url.toString(); m_sink->abort(); m_reply.reset(); emit failed(""); return; } m_reply.reset(); - qCDebug(taskUploadLogC) << "Upload succeeded:" << m_url.toString(); + qCDebug(taskUploadLogC) << getUid().toString() << "Upload succeeded:" << m_url.toString(); emit succeeded(); } @@ -195,7 +195,7 @@ namespace Net { setStatus(tr("Uploading %1").arg(m_url.toString())); if (m_state == State::AbortedByUser) { - qCWarning(taskUploadLogC) << "Attempt to start an aborted Upload:" << m_url.toString(); + qCWarning(taskUploadLogC) << getUid().toString() << "Attempt to start an aborted Upload:" << m_url.toString(); emit aborted(); return; } @@ -204,10 +204,10 @@ namespace Net { switch (m_state) { case State::Succeeded: emitSucceeded(); - qCDebug(taskUploadLogC) << "Upload cache hit " << m_url.toString(); + qCDebug(taskUploadLogC) << getUid().toString() << "Upload cache hit " << m_url.toString(); return; case State::Running: - qCDebug(taskUploadLogC) << "Uploading " << m_url.toString(); + qCDebug(taskUploadLogC) << getUid().toString() << "Uploading " << m_url.toString(); break; case State::Inactive: case State::Failed: From 6b28af6cc5cc935bff47fcf8f4534827c958dbc3 Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Sun, 2 Apr 2023 19:40:32 -0700 Subject: [PATCH 151/185] fix: clean up license headers for Tasks Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- launcher/net/Download.h | 3 ++- launcher/net/FileSink.cpp | 2 +- launcher/net/FileSink.h | 2 +- launcher/net/HttpMetaCache.cpp | 2 +- launcher/net/HttpMetaCache.h | 2 +- launcher/net/MetaCacheSink.cpp | 2 +- launcher/net/MetaCacheSink.h | 2 +- launcher/net/NetAction.h | 3 ++- launcher/net/NetJob.cpp | 3 ++- launcher/net/NetJob.h | 3 ++- launcher/net/PasteUpload.cpp | 2 +- launcher/net/PasteUpload.h | 2 +- launcher/net/Upload.cpp | 1 + launcher/net/Upload.h | 3 ++- launcher/tasks/ConcurrentTask.cpp | 35 ++++++++++++++++++++++++++ launcher/tasks/ConcurrentTask.h | 35 ++++++++++++++++++++++++++ launcher/tasks/MultipleOptionsTask.cpp | 34 +++++++++++++++++++++++++ launcher/tasks/MultipleOptionsTask.h | 34 +++++++++++++++++++++++++ launcher/tasks/SequentialTask.cpp | 35 ++++++++++++++++++++++++++ launcher/tasks/SequentialTask.h | 35 ++++++++++++++++++++++++++ launcher/tasks/Task.cpp | 1 + 21 files changed, 228 insertions(+), 13 deletions(-) diff --git a/launcher/net/Download.h b/launcher/net/Download.h index ae2b034c..d8c18319 100644 --- a/launcher/net/Download.h +++ b/launcher/net/Download.h @@ -1,8 +1,9 @@ // SPDX-License-Identifier: GPL-3.0-only /* - * PolyMC - Minecraft Launcher + * Prism Launcher - Minecraft Launcher * Copyright (c) 2022 flowln * Copyright (C) 2022 Sefa Eyeoglu + * 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 diff --git a/launcher/net/FileSink.cpp b/launcher/net/FileSink.cpp index 3c2948d4..d5f09012 100644 --- a/launcher/net/FileSink.cpp +++ b/launcher/net/FileSink.cpp @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-3.0-only /* - * PolyMC - Minecraft Launcher + * Prism Launcher - Minecraft Launcher * Copyright (c) 2022 flowln * * This program is free software: you can redistribute it and/or modify diff --git a/launcher/net/FileSink.h b/launcher/net/FileSink.h index dffbdca6..40134b5f 100644 --- a/launcher/net/FileSink.h +++ b/launcher/net/FileSink.h @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-3.0-only /* - * PolyMC - Minecraft Launcher + * Prism Launcher - Minecraft Launcher * Copyright (c) 2022 flowln * * This program is free software: you can redistribute it and/or modify diff --git a/launcher/net/HttpMetaCache.cpp b/launcher/net/HttpMetaCache.cpp index 855211f7..c90f8d4d 100644 --- a/launcher/net/HttpMetaCache.cpp +++ b/launcher/net/HttpMetaCache.cpp @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-3.0-only /* - * PolyMC - Minecraft Launcher + * Prism Launcher - Minecraft Launcher * Copyright (c) 2022 flowln * * This program is free software: you can redistribute it and/or modify diff --git a/launcher/net/HttpMetaCache.h b/launcher/net/HttpMetaCache.h index 37f4b49a..0dcb5668 100644 --- a/launcher/net/HttpMetaCache.h +++ b/launcher/net/HttpMetaCache.h @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-3.0-only /* - * PolyMC - Minecraft Launcher + * Prism Launcher - Minecraft Launcher * Copyright (c) 2022 flowln * * This program is free software: you can redistribute it and/or modify diff --git a/launcher/net/MetaCacheSink.cpp b/launcher/net/MetaCacheSink.cpp index 46bfe37d..fc997553 100644 --- a/launcher/net/MetaCacheSink.cpp +++ b/launcher/net/MetaCacheSink.cpp @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-3.0-only /* - * PolyMC - Minecraft Launcher + * Prism Launcher - Minecraft Launcher * Copyright (c) 2022 flowln * * This program is free software: you can redistribute it and/or modify diff --git a/launcher/net/MetaCacheSink.h b/launcher/net/MetaCacheSink.h index f5948085..f9f7d233 100644 --- a/launcher/net/MetaCacheSink.h +++ b/launcher/net/MetaCacheSink.h @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-3.0-only /* - * PolyMC - Minecraft Launcher + * Prism Launcher - Minecraft Launcher * Copyright (c) 2022 flowln * * This program is free software: you can redistribute it and/or modify diff --git a/launcher/net/NetAction.h b/launcher/net/NetAction.h index f9456bd6..c1b0ef4a 100644 --- a/launcher/net/NetAction.h +++ b/launcher/net/NetAction.h @@ -1,7 +1,8 @@ // SPDX-License-Identifier: GPL-3.0-only /* - * PolyMC - Minecraft Launcher + * Prism Launcher - 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 diff --git a/launcher/net/NetJob.cpp b/launcher/net/NetJob.cpp index 4bcd40b5..3869316e 100644 --- a/launcher/net/NetJob.cpp +++ b/launcher/net/NetJob.cpp @@ -1,8 +1,9 @@ // SPDX-License-Identifier: GPL-3.0-only /* - * PolyMC - Minecraft Launcher + * Prism Launcher - Minecraft Launcher * Copyright (c) 2022 flowln * Copyright (C) 2022 Sefa Eyeoglu + * 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 diff --git a/launcher/net/NetJob.h b/launcher/net/NetJob.h index cd5d5e48..764cec18 100644 --- a/launcher/net/NetJob.h +++ b/launcher/net/NetJob.h @@ -1,7 +1,8 @@ // SPDX-License-Identifier: GPL-3.0-only /* - * PolyMC - Minecraft Launcher + * Prism Launcher - 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 diff --git a/launcher/net/PasteUpload.cpp b/launcher/net/PasteUpload.cpp index 24f456e3..9c2b20c6 100644 --- a/launcher/net/PasteUpload.cpp +++ b/launcher/net/PasteUpload.cpp @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-3.0-only /* - * PolyMC - Minecraft Launcher + * Prism Launcher - Minecraft Launcher * Copyright (C) 2022 Lenny McLennington * Copyright (C) 2022 Swirl * Copyright (C) 2022 Sefa Eyeoglu diff --git a/launcher/net/PasteUpload.h b/launcher/net/PasteUpload.h index eb315c2b..b72ab5b0 100644 --- a/launcher/net/PasteUpload.h +++ b/launcher/net/PasteUpload.h @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-3.0-only /* - * PolyMC - Minecraft Launcher + * Prism Launcher - Minecraft Launcher * Copyright (C) 2022 Lenny McLennington * * This program is free software: you can redistribute it and/or modify diff --git a/launcher/net/Upload.cpp b/launcher/net/Upload.cpp index 195e1679..85f364d3 100644 --- a/launcher/net/Upload.cpp +++ b/launcher/net/Upload.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 diff --git a/launcher/net/Upload.h b/launcher/net/Upload.h index 5a0b2e74..5182ab09 100644 --- a/launcher/net/Upload.h +++ b/launcher/net/Upload.h @@ -1,8 +1,9 @@ // SPDX-License-Identifier: GPL-3.0-only /* - * PolyMC - Minecraft Launcher + * Prism Launcher - Minecraft Launcher * Copyright (c) 2022 flowln * Copyright (C) 2022 Sefa Eyeoglu + * 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 diff --git a/launcher/tasks/ConcurrentTask.cpp b/launcher/tasks/ConcurrentTask.cpp index 8d4f94ed..3c62cf4d 100644 --- a/launcher/tasks/ConcurrentTask.cpp +++ b/launcher/tasks/ConcurrentTask.cpp @@ -1,3 +1,38 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - 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 + * 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 "ConcurrentTask.h" #include diff --git a/launcher/tasks/ConcurrentTask.h b/launcher/tasks/ConcurrentTask.h index 43e9f866..1cf1520e 100644 --- a/launcher/tasks/ConcurrentTask.h +++ b/launcher/tasks/ConcurrentTask.h @@ -1,3 +1,38 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - 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 + * 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 diff --git a/launcher/tasks/MultipleOptionsTask.cpp b/launcher/tasks/MultipleOptionsTask.cpp index 034499df..89187a26 100644 --- a/launcher/tasks/MultipleOptionsTask.cpp +++ b/launcher/tasks/MultipleOptionsTask.cpp @@ -1,3 +1,37 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (c) 2022 flowln + * + * 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 "MultipleOptionsTask.h" #include diff --git a/launcher/tasks/MultipleOptionsTask.h b/launcher/tasks/MultipleOptionsTask.h index db7d4d9a..a344343e 100644 --- a/launcher/tasks/MultipleOptionsTask.h +++ b/launcher/tasks/MultipleOptionsTask.h @@ -1,3 +1,37 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (c) 2022 flowln + * + * 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 "SequentialTask.h" diff --git a/launcher/tasks/SequentialTask.cpp b/launcher/tasks/SequentialTask.cpp index b2f86328..abf7536b 100644 --- a/launcher/tasks/SequentialTask.cpp +++ b/launcher/tasks/SequentialTask.cpp @@ -1,3 +1,38 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - 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 + * 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 "SequentialTask.h" #include diff --git a/launcher/tasks/SequentialTask.h b/launcher/tasks/SequentialTask.h index 5eace96e..cec3b2be 100644 --- a/launcher/tasks/SequentialTask.h +++ b/launcher/tasks/SequentialTask.h @@ -1,3 +1,38 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - 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 + * 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 "ConcurrentTask.h" diff --git a/launcher/tasks/Task.cpp b/launcher/tasks/Task.cpp index ffde4a10..e5f61919 100644 --- a/launcher/tasks/Task.cpp +++ b/launcher/tasks/Task.cpp @@ -2,6 +2,7 @@ /* * PolyMC - 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 From fe36471b8dfad6156b735f9393072b9111a12547 Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Sun, 2 Apr 2023 19:43:43 -0700 Subject: [PATCH 152/185] refactor: logging.h -> Logging.h Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- launcher/CMakeLists.txt | 22 ++++++++++++++++++++-- launcher/net/Download.cpp | 2 +- launcher/net/FileSink.cpp | 2 +- launcher/net/HttpMetaCache.cpp | 14 +++++++------- launcher/net/{logging.cpp => Logging.cpp} | 6 ++++-- launcher/net/{logging.h => Logging.h} | 4 +++- launcher/net/MetaCacheSink.cpp | 10 +++++----- launcher/net/PasteUpload.cpp | 2 +- launcher/net/Upload.cpp | 2 +- 9 files changed, 43 insertions(+), 21 deletions(-) rename launcher/net/{logging.cpp => Logging.cpp} (78%) rename launcher/net/{logging.h => Logging.h} (86%) diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index e49a4562..5253a517 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -123,8 +123,8 @@ set(NET_SOURCES net/HttpMetaCache.h net/MetaCacheSink.cpp net/MetaCacheSink.h - net/logging.h - net/logging.cpp + net/Logging.h + net/Logging.cpp net/NetAction.h net/NetJob.cpp net/NetJob.h @@ -596,6 +596,24 @@ ecm_qt_export_logging_category( EXPORT "${Launcher_Name}" ) +ecm_qt_export_logging_category( + IDENTIFIER taskMetaCacheLogC + CATEGORY_NAME "launcher.task.net.metacache" + DEFAULT_SEVERITY Debug + DESCRIPTION "task network meta-cache actions" + EXPORT "${Launcher_Name}" +) + +ecm_qt_export_logging_category( + IDENTIFIER taskHttpMetaCacheLogC + CATEGORY_NAME "launcher.task.net.metacache.http" + DEFAULT_SEVERITY Debug + DESCRIPTION "task network http meta-cache 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/net/Download.cpp b/launcher/net/Download.cpp index e7536dc9..bfd0be28 100644 --- a/launcher/net/Download.cpp +++ b/launcher/net/Download.cpp @@ -51,7 +51,7 @@ #include "BuildConfig.h" #include "Application.h" -#include "logging.h" +#include "net/Logging.h" #include "net/NetAction.h" namespace Net { diff --git a/launcher/net/FileSink.cpp b/launcher/net/FileSink.cpp index d5f09012..1ecb21fd 100644 --- a/launcher/net/FileSink.cpp +++ b/launcher/net/FileSink.cpp @@ -37,7 +37,7 @@ #include "FileSystem.h" -#include "logging.h" +#include "net/Logging.h" namespace Net { diff --git a/launcher/net/HttpMetaCache.cpp b/launcher/net/HttpMetaCache.cpp index c90f8d4d..e521c9e9 100644 --- a/launcher/net/HttpMetaCache.cpp +++ b/launcher/net/HttpMetaCache.cpp @@ -44,7 +44,7 @@ #include -#include "logging.h" +#include "net/Logging.h" auto MetaEntry::getFullPath() -> QString { @@ -139,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)) { - qCCritical(taskNetLogC) << "[HttpMetaCache]" << "Cannot add entry with unknown base: " << stale_entry->m_baseId.toLocal8Bit(); + qCCritical(taskHttpMetaCacheLogC) << "Cannot add entry with unknown base: " << stale_entry->m_baseId.toLocal8Bit(); return false; } if (stale_entry->m_stale) { - qCCritical(taskNetLogC) << "[HttpMetaCache]" << "Cannot add stale entry: " << stale_entry->getFullPath().toLocal8Bit(); + qCCritical(taskHttpMetaCacheLogC) << "Cannot add stale entry: " << stale_entry->getFullPath().toLocal8Bit(); return false; } @@ -168,10 +168,10 @@ void HttpMetaCache::evictAll() { for (QString& base : m_entries.keys()) { EntryMap& map = m_entries[base]; - qCDebug(taskNetLogC) << "[HttpMetaCache]" << "Evicting base" << base; + qCDebug(taskHttpMetaCacheLogC) << "Evicting base" << base; for (MetaEntryPtr entry : map.entry_list) { if (!evictEntry(entry)) - qCWarning(taskNetLogC) << "[HttpMetaCache]" << "Unexpected missing cache entry" << entry->m_basePath; + qCWarning(taskHttpMetaCacheLogC) << "Unexpected missing cache entry" << entry->m_basePath; } } } @@ -269,7 +269,7 @@ void HttpMetaCache::SaveNow() if (m_index_file.isNull()) return; - qCDebug(taskNetLogC) << "[HttpMetaCache]" << "Saving metacache with" << m_entries.size() << "entries"; + qCDebug(taskHttpMetaCacheLogC) << "Saving metacache with" << m_entries.size() << "entries"; QJsonObject toplevel; Json::writeString(toplevel, "version", "1"); @@ -304,6 +304,6 @@ void HttpMetaCache::SaveNow() try { Json::write(toplevel, m_index_file); } catch (const Exception& e) { - qCWarning(taskNetLogC) << "[HttpMetaCache]" << e.what(); + qCWarning(taskHttpMetaCacheLogC) << "Error writing cache:" << e.what(); } } diff --git a/launcher/net/logging.cpp b/launcher/net/Logging.cpp similarity index 78% rename from launcher/net/logging.cpp rename to launcher/net/Logging.cpp index e5b42bc4..a9b9db7c 100644 --- a/launcher/net/logging.cpp +++ b/launcher/net/Logging.cpp @@ -17,8 +17,10 @@ * */ -#include "logging.h" +#include "net/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 +Q_LOGGING_CATEGORY(taskUploadLogC, "launcher.task.net.upload") +Q_LOGGING_CATEGORY(taskMetaCacheLogC, "launcher.task.net.metacache") +Q_LOGGING_CATEGORY(taskHttpMetaCacheLogC, "launcher.task.net.metacache.http") diff --git a/launcher/net/logging.h b/launcher/net/Logging.h similarity index 86% rename from launcher/net/logging.h rename to launcher/net/Logging.h index e65e328c..b692e707 100644 --- a/launcher/net/logging.h +++ b/launcher/net/Logging.h @@ -23,4 +23,6 @@ Q_DECLARE_LOGGING_CATEGORY(taskNetLogC) Q_DECLARE_LOGGING_CATEGORY(taskDownloadLogC) -Q_DECLARE_LOGGING_CATEGORY(taskUploadLogC) \ No newline at end of file +Q_DECLARE_LOGGING_CATEGORY(taskUploadLogC) +Q_DECLARE_LOGGING_CATEGORY(taskMetaCacheLogC) +Q_DECLARE_LOGGING_CATEGORY(taskHttpMetaCacheLogC) diff --git a/launcher/net/MetaCacheSink.cpp b/launcher/net/MetaCacheSink.cpp index fc997553..e203bc06 100644 --- a/launcher/net/MetaCacheSink.cpp +++ b/launcher/net/MetaCacheSink.cpp @@ -39,7 +39,7 @@ #include #include "Application.h" -#include "logging.h" +#include "net/Logging.h" namespace Net { @@ -99,11 +99,11 @@ Task::State MetaCacheSink::finalizeCache(QNetworkReply & reply) { // Cache lifetime if (m_is_eternal) { - qCDebug(taskNetLogC) << "[MetaCache] Adding eternal cache entry:" << m_entry->getFullPath(); + qCDebug(taskMetaCacheLogC) << "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"); - // qCDebug(taskNetLogC) << "[MetaCache] Parsing 'Cache-Control' header with" << cache_control_header; + qCDebug(taskMetaCacheLogC) << "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(); @@ -111,7 +111,7 @@ Task::State MetaCacheSink::finalizeCache(QNetworkReply & reply) } else if (reply.hasRawHeader("Expires")) { auto expires_header = reply.rawHeader("Expires"); - // qCDebug(taskNetLogC) << "[MetaCache] Parsing 'Expires' header with" << expires_header; + qCDebug(taskMetaCacheLogC) << "Parsing 'Expires' header with" << expires_header; qint64 max_age = QDateTime::fromString(expires_header).toSecsSinceEpoch() - QDateTime::currentSecsSinceEpoch(); m_entry->setMaximumAge(max_age); @@ -121,7 +121,7 @@ Task::State MetaCacheSink::finalizeCache(QNetworkReply & reply) if (reply.hasRawHeader("Age")) { auto age_header = reply.rawHeader("Age"); - // qCDebug(taskNetLogC) << "[MetaCache] Parsing 'Age' header with" << age_header; + qCDebug(taskMetaCacheLogC) << "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 9c2b20c6..595279a3 100644 --- a/launcher/net/PasteUpload.cpp +++ b/launcher/net/PasteUpload.cpp @@ -47,7 +47,7 @@ #include #include -#include "logging.h" +#include "net/Logging.h" std::array PasteUpload::PasteTypes = { {{"0x0.st", "https://0x0.st", ""}, diff --git a/launcher/net/Upload.cpp b/launcher/net/Upload.cpp index 85f364d3..8045f850 100644 --- a/launcher/net/Upload.cpp +++ b/launcher/net/Upload.cpp @@ -43,7 +43,7 @@ #include "BuildConfig.h" #include "Application.h" -#include "logging.h" +#include "net/Logging.h" namespace Net { 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: [PATCH 153/185] refactor: propogate only only one StepProgress at a time Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- launcher/tasks/ConcurrentTask.cpp | 97 ++++++++++++++------------ launcher/tasks/ConcurrentTask.h | 2 +- launcher/tasks/Task.cpp | 2 +- launcher/tasks/Task.h | 9 ++- launcher/ui/dialogs/ProgressDialog.cpp | 42 ++++++----- launcher/ui/dialogs/ProgressDialog.h | 2 +- 6 files changed, 80 insertions(+), 74 deletions(-) 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; - } + 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()); + 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); - - if (tp->isDone()) { - task_bar->setVisible(false); - } - + 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); } + + 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 From 96decbac27b364e0ffdcb20c40b08a79b827be00 Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Mon, 3 Apr 2023 15:14:24 -0700 Subject: [PATCH 154/185] feat: default qtlogging.ini file Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- CMakeLists.txt | 4 +++ launcher/Application.cpp | 54 ++++++++++++++++++++++++--------- launcher/Application.h | 5 +++ launcher/CMakeLists.txt | 6 ++++ launcher/qtlogging.ini | 11 +++++++ program_info/win_install.nsi.in | 1 + 6 files changed, 66 insertions(+), 15 deletions(-) create mode 100644 launcher/qtlogging.ini diff --git a/CMakeLists.txt b/CMakeLists.txt index 05c69c89..2ff26aee 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -345,6 +345,8 @@ elseif(UNIX) install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/${Launcher_SVG} DESTINATION "${KDE_INSTALL_ICONDIR}/hicolor/scalable/apps") install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/${Launcher_mrpack_MIMEInfo} DESTINATION ${KDE_INSTALL_MIMEDIR}) + install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/launcher/qtlogging.ini" DESTINATION "${KDE_INSTALL_DATADIR}/${Launcher_Name}") + if(Launcher_ManPage) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${Launcher_ManPage} DESTINATION "${KDE_INSTALL_MANDIR}/man6") endif() @@ -372,6 +374,8 @@ else() message(FATAL_ERROR "Platform not supported") endif() + + ################################ Included Libs ################################ include(ExternalProject) diff --git a/launcher/Application.cpp b/launcher/Application.cpp index c8855cbc..f7595512 100644 --- a/launcher/Application.cpp +++ b/launcher/Application.cpp @@ -287,6 +287,7 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv) if (QFile::exists(FS::PathCombine(m_rootPath, "portable.txt"))) { dataPath = m_rootPath; adjustedBy = "Portable data path"; + m_portable = true; } #endif } @@ -411,25 +412,48 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv) " " "|" " " "%{if-category}[%{category}]: %{endif}" "%{message}"); + + bool foundLoggingRules = false; - 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); + auto logRulesFile = QStringLiteral("qtlogging.ini"); + auto logRulesPath = FS::PathCombine(dataPath, logRulesFile); + + qDebug() << "Testing" << logRulesPath << "..."; + foundLoggingRules = QFile::exists(logRulesPath); + + // search the dataPath() + + if(!foundLoggingRules && ! isPortable()) { + logRulesPath = QStandardPaths::locate(QStandardPaths::AppDataLocation, logRulesFile); + if(!logRulesPath.isEmpty()) { + qDebug() << "Found" << logRulesPath << "..."; + foundLoggingRules = true; } } + if(!QFile::exists(logRulesPath)) { + logRulesPath = FS::PathCombine(m_rootPath, logRulesFile); + qDebug() << "Testing" << logRulesPath << "..."; + foundLoggingRules = QFile::exists(logRulesPath); + } + + if(foundLoggingRules) { + // load and set logging rules + qDebug() << "Loading logging rules from:" << logRulesPath; + QSettings loggingRules(logRulesPath, QSettings::IniFormat); + loggingRules.beginGroup("Rules"); + QStringList rule_names = loggingRules.childKeys(); + QStringList rules; + qDebug() << "Setting log rules:"; + for (auto rule_name : rule_names) { + auto rule = QString("%1=%2").arg(rule_name).arg(loggingRules.value(rule_name).toString()); + rules.append(rule); + qDebug() << " " << rule; + } + auto rules_str = rules.join("\n"); + QLoggingCategory::setFilterRules(rules_str); + } + qDebug() << "<> Log initialized."; } diff --git a/launcher/Application.h b/launcher/Application.h index 0d24a4e9..3d833edc 100644 --- a/launcher/Application.h +++ b/launcher/Application.h @@ -187,6 +187,10 @@ public: return m_rootPath; } + const bool isPortable() { + return m_portable; + } + const Capabilities capabilities() { return m_capabilities; } @@ -275,6 +279,7 @@ private: QString m_rootPath; Status m_status = Application::StartingUp; Capabilities m_capabilities; + bool m_portable = false; #ifdef Q_OS_MACOS Qt::ApplicationState m_prevAppState = Qt::ApplicationInactive; diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index 5253a517..a3ef20e8 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -1161,6 +1161,12 @@ if(INSTALL_BUNDLE STREQUAL "full") CODE "file(WRITE \"\${CMAKE_INSTALL_PREFIX}/${RESOURCES_DEST_DIR}/qt.conf\" \" \")" COMPONENT Runtime ) + # add qtlogging.ini as a config file + install( + FILES "qtlogging.ini" + DESTINATION ${CMAKE_INSTALL_PREFIX}/${RESOURCES_DEST_DIR} + COMPONENT Runtime + ) # Bundle plugins # Image formats install( diff --git a/launcher/qtlogging.ini b/launcher/qtlogging.ini new file mode 100644 index 00000000..eba5390d --- /dev/null +++ b/launcher/qtlogging.ini @@ -0,0 +1,11 @@ +[Rules] +*.debug=true +# remove the debug lines, other log levels still get through +launcher.task.net.download.debug=false +# enable or disable whole catageries +launcher.task.net=true +launcher.task=false +launcher.task.net.upload=true +launcher.task.net.metacache=false +launcher.task.net.metacache.http=true + diff --git a/program_info/win_install.nsi.in b/program_info/win_install.nsi.in index 49e22500..a809c55d 100644 --- a/program_info/win_install.nsi.in +++ b/program_info/win_install.nsi.in @@ -282,6 +282,7 @@ Section "@Launcher_DisplayName@" File "@Launcher_APP_BINARY_NAME@.exe" File "qt.conf" + File "qtlogging.ini" File *.dll File /r "iconengines" File /r "imageformats" From d7032d975cb71bbf69d1dcd6a916db85ceb7619c Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Fri, 7 Apr 2023 13:01:45 -0700 Subject: [PATCH 155/185] fix: no need to loop all sub tasks pathc by flowin Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> Signed-off-by: flow --- launcher/tasks/ConcurrentTask.cpp | 54 ++++++++++++++++++++++--------- launcher/tasks/ConcurrentTask.h | 3 +- launcher/tasks/Task.h | 4 +++ 3 files changed, 44 insertions(+), 17 deletions(-) diff --git a/launcher/tasks/ConcurrentTask.cpp b/launcher/tasks/ConcurrentTask.cpp index 8434ecdc..439879e6 100644 --- a/launcher/tasks/ConcurrentTask.cpp +++ b/launcher/tasks/ConcurrentTask.cpp @@ -138,10 +138,12 @@ void ConcurrentTask::startNext() 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() }))); + auto task_progress = std::make_shared(TaskStepProgress({ next->getUid() })); + m_task_progress.insert(next->getUid(), task_progress); updateState(); - updateStepProgress(); + updateStepProgress(*task_progress.get(), Operation::ADDED); + QCoreApplication::processEvents(); @@ -166,7 +168,7 @@ void ConcurrentTask::subTaskSucceeded(Task::Ptr task) emit stepProgress(*task_progress.get()); updateState(); - updateStepProgress(); + updateStepProgress(*task_progress.get(), Operation::REMOVED); startNext(); } @@ -184,7 +186,7 @@ void ConcurrentTask::subTaskFailed(Task::Ptr task, const QString& msg) emit stepProgress(*task_progress.get()); updateState(); - updateStepProgress(); + updateStepProgress(*task_progress.get(), Operation::REMOVED); startNext(); } @@ -195,7 +197,6 @@ void ConcurrentTask::subTaskStatus(Task::Ptr task, const QString& msg) task_progress->state = TaskStepState::Running; emit stepProgress(*task_progress.get()); - updateStepProgress(); } void ConcurrentTask::subTaskDetails(Task::Ptr task, const QString& msg) @@ -205,48 +206,69 @@ void ConcurrentTask::subTaskDetails(Task::Ptr task, const QString& msg) task_progress->state = TaskStepState::Running; emit stepProgress(*task_progress.get()); - updateStepProgress(); } void ConcurrentTask::subTaskProgress(Task::Ptr task, qint64 current, qint64 total) { auto task_progress = m_task_progress.value(task->getUid()); + task_progress->old_current = task_progress->current; + task_progress->old_total = task_progress->old_total; + task_progress->current = current; task_progress->total = total; task_progress->state = TaskStepState::Running; emit stepProgress(*task_progress.get()); - updateStepProgress(); + updateStepProgress(*task_progress.get(), Operation::CHANGED); updateState(); } void ConcurrentTask::subTaskStepProgress(Task::Ptr task, TaskStepProgress 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; } else { auto tp = m_task_progress.value(task_progress.uid); + + tp->old_current = tp->current; + tp->old_total = tp->total; + tp->current = task_progress.current; tp->total = task_progress.total; tp->status = task_progress.status; tp->details = task_progress.details; + + op = Operation::CHANGED; } - + emit stepProgress(task_progress); - updateStepProgress(); + updateStepProgress(task_progress, op); } -void ConcurrentTask::updateStepProgress() +void ConcurrentTask::updateStepProgress(TaskStepProgress const& changed_progress, Operation op) { - qint64 current = 0, total = 0; - for (auto taskProgress : m_task_progress) { - current += taskProgress->current; - total += taskProgress->total; + + switch (op) { + case Operation::ADDED: + m_stepProgress += changed_progress.current; + m_stepTotalProgress += changed_progress.total; + break; + case Operation::REMOVED: + m_stepProgress -= changed_progress.current; + m_stepTotalProgress -= changed_progress.total; + break; + case Operation::CHANGED: + m_stepProgress -= changed_progress.old_current; + m_stepTotalProgress -= changed_progress.old_total; + m_stepProgress += changed_progress.current; + m_stepTotalProgress += changed_progress.total; + break; } - m_stepProgress = current; - m_stepTotalProgress = total; } void ConcurrentTask::updateState() diff --git a/launcher/tasks/ConcurrentTask.h b/launcher/tasks/ConcurrentTask.h index 974e8d4d..284fa345 100644 --- a/launcher/tasks/ConcurrentTask.h +++ b/launcher/tasks/ConcurrentTask.h @@ -83,7 +83,8 @@ protected: // NOTE: This is not thread-safe. [[nodiscard]] unsigned int totalSize() const { return m_queue.size() + m_doing.size() + m_done.size(); } - void updateStepProgress(); + enum class Operation { ADDED, REMOVED, CHANGED }; + void updateStepProgress(TaskStepProgress const& changed_progress, Operation); virtual void updateState(); diff --git a/launcher/tasks/Task.h b/launcher/tasks/Task.h index 9ae70270..d71eaf6b 100644 --- a/launcher/tasks/Task.h +++ b/launcher/tasks/Task.h @@ -57,6 +57,10 @@ struct TaskStepProgress { QUuid uid; qint64 current = 0; qint64 total = -1; + + qint64 old_current = 0; + qint64 old_total = -1; + QString status = ""; QString details = ""; TaskStepState state = TaskStepState::Waiting; From a80b4255515b1f3e61d12aeefcef6bf16ac4ee6b Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Fri, 7 Apr 2023 13:04:32 -0700 Subject: [PATCH 156/185] fix: no need for const bool Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- launcher/Application.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/Application.h b/launcher/Application.h index 3d833edc..ced0af17 100644 --- a/launcher/Application.h +++ b/launcher/Application.h @@ -187,7 +187,7 @@ public: return m_rootPath; } - const bool isPortable() { + bool isPortable() { return m_portable; } 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: [PATCH 157/185] 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(-) 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; From 9f9c829bc5db1c5a643e709d701b929af8194deb Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Sat, 8 Apr 2023 12:30:11 -0700 Subject: [PATCH 158/185] fix: prevent logspam, fix MacOS theme artifacts Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- launcher/qtlogging.ini | 3 +++ 1 file changed, 3 insertions(+) diff --git a/launcher/qtlogging.ini b/launcher/qtlogging.ini index eba5390d..4543378f 100644 --- a/launcher/qtlogging.ini +++ b/launcher/qtlogging.ini @@ -1,5 +1,8 @@ [Rules] *.debug=true +# prevent log spam and strange bugs +# qt.qpa.drawing in particular causes theme artifacts on MacOS +qt.*.debug=false # remove the debug lines, other log levels still get through launcher.task.net.download.debug=false # enable or disable whole catageries From 733619ca741336ba9999af43f4eddd9371462325 Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Sun, 9 Apr 2023 15:10:49 -0700 Subject: [PATCH 159/185] feat: estimate remining time on downloads Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- launcher/net/Download.cpp | 67 ++++++++++++++++++++++++++++--- launcher/net/NetAction.h | 9 ++--- launcher/tasks/ConcurrentTask.cpp | 12 ++++++ 3 files changed, 77 insertions(+), 11 deletions(-) diff --git a/launcher/net/Download.cpp b/launcher/net/Download.cpp index bfd0be28..bf0e5c26 100644 --- a/launcher/net/Download.cpp +++ b/launcher/net/Download.cpp @@ -93,6 +93,59 @@ QString truncateUrlHumanFriendly(QUrl &url, int max_len, bool hard_limit = false } +QString humanReadableDuration(double duration, int precision = 0) { + + using days = std::chrono::duration>; + + QString outStr; + QTextStream os(&outStr); + + auto std_duration = std::chrono::duration(duration); + auto d = std::chrono::duration_cast(std_duration); + std_duration -= d; + auto h = std::chrono::duration_cast(std_duration); + std_duration -= h; + auto m = std::chrono::duration_cast(std_duration); + std_duration -= m; + auto s = std::chrono::duration_cast(std_duration); + std_duration -= s; + auto ms = std::chrono::duration_cast(std_duration); + + auto dc = d.count(); + auto hc = h.count(); + auto mc = m.count(); + auto sc = s.count(); + auto msc = ms.count(); + + if (dc) { + os << dc << "days"; + } + if (hc) { + if (dc) + os << " "; + os << qSetFieldWidth(2) << hc << "h"; + } + if (mc) { + if (dc || hc) + os << " "; + os << qSetFieldWidth(2) << mc << "m"; + } + if (dc || hc || mc || sc) { + if (dc || hc || mc) + os << " "; + os << qSetFieldWidth(2) << sc << "s"; + } + if ((msc && (precision > 0)) || !(dc || hc || mc || sc)) { + if (dc || hc || mc || sc) + os << " "; + os << qSetFieldWidth(0) << qSetRealNumberPrecision(precision) << msc << "ms"; + } + + os.flush(); + + return outStr; +} + auto Download::makeCached(QUrl url, MetaEntryPtr entry, Options options) -> Download::Ptr { auto dl = makeShared(); @@ -193,21 +246,23 @@ void Download::downloadProgress(qint64 bytesReceived, qint64 bytesTotal) auto elapsed = now - m_last_progress_time; // use milliseconds for speed precision - auto elapsed_ms = std::chrono::duration_cast(elapsed).count(); + 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 remaing_time_s = (bytesTotal - bytesReceived) / dl_speed_bps; // current bytes out of total bytes QString dl_progress = tr("%1 / %2").arg(humanReadableFileSize(bytesReceived)).arg(humanReadableFileSize(bytesTotal)); - QString dl_speed; - if (elapsed_ms > 0) { + QString dl_speed_str; + if (elapsed_ms.count() > 0) { // bytes per second - dl_speed = tr("%1/s").arg(humanReadableFileSize(bytes_recived_since / elapsed_ms * 1000)); + dl_speed_str = tr("%1/s (%2)").arg(humanReadableFileSize(dl_speed_bps)).arg(humanReadableDuration(remaing_time_s)); } else { - dl_speed = tr("0 b/s"); + dl_speed_str = tr("0 b/s"); } - setDetails(dl_progress + "\n" + dl_speed); + setDetails(dl_progress + "\n" + dl_speed_str); setProgress(bytesReceived, bytesTotal); } diff --git a/launcher/net/NetAction.h b/launcher/net/NetAction.h index c1b0ef4a..df6ed995 100644 --- a/launcher/net/NetAction.h +++ b/launcher/net/NetAction.h @@ -47,20 +47,19 @@ 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) { +inline QString humanReadableFileSize(double 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; + bytes /= scale; u++; - } while (round(abs(size) * r) / r >= scale && u < units.length() - 1); + } while (round(abs(bytes) * r) / r >= scale && u < units.length() - 1); - return QString::number(size, 'f', 2) + " " + units[u]; + return QString::number(bytes, 'f', 2) + " " + units[u]; } class NetAction : public Task { diff --git a/launcher/tasks/ConcurrentTask.cpp b/launcher/tasks/ConcurrentTask.cpp index 37435739..dbb4d94d 100644 --- a/launcher/tasks/ConcurrentTask.cpp +++ b/launcher/tasks/ConcurrentTask.cpp @@ -197,6 +197,10 @@ void ConcurrentTask::subTaskStatus(Task::Ptr task, const QString& msg) task_progress->state = TaskStepState::Running; emit stepProgress(*task_progress.get()); + + if (totalSize() == 1) { + setStatus(msg); + } } void ConcurrentTask::subTaskDetails(Task::Ptr task, const QString& msg) @@ -206,6 +210,10 @@ void ConcurrentTask::subTaskDetails(Task::Ptr task, const QString& msg) task_progress->state = TaskStepState::Running; emit stepProgress(*task_progress.get()); + + if (totalSize() == 1) { + setDetails(msg); + } } void ConcurrentTask::subTaskProgress(Task::Ptr task, qint64 current, qint64 total) @@ -222,6 +230,10 @@ void ConcurrentTask::subTaskProgress(Task::Ptr task, qint64 current, qint64 tota emit stepProgress(*task_progress.get()); updateStepProgress(*task_progress.get(), Operation::CHANGED); updateState(); + + if (totalSize() == 1) { + setProgress(task_progress->current, task_progress->total); + } } void ConcurrentTask::subTaskStepProgress(Task::Ptr task, TaskStepProgress const& task_progress) 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: [PATCH 160/185] 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(-) 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; } 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: [PATCH 161/185] 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(-) 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; + }; - 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(); + + 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; } 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: [PATCH 162/185] 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(-) 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" From d0b6f0124b41f8e279df5b224c975d03b3631b1e Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Fri, 5 May 2023 14:13:34 -0700 Subject: [PATCH 163/185] change: don't search appdata locaiton for logging rules if using custom data dir Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- launcher/Application.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/launcher/Application.cpp b/launcher/Application.cpp index f7595512..28c2a9ce 100644 --- a/launcher/Application.cpp +++ b/launcher/Application.cpp @@ -8,6 +8,7 @@ * Copyright (C) 2022 Lenny McLennington * Copyright (C) 2022 Tayou * 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 @@ -423,7 +424,7 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv) // search the dataPath() - if(!foundLoggingRules && ! isPortable()) { + if(!foundLoggingRules && !isPortable() && dirParam.isEmpty()) { logRulesPath = QStandardPaths::locate(QStandardPaths::AppDataLocation, logRulesFile); if(!logRulesPath.isEmpty()) { qDebug() << "Found" << logRulesPath << "..."; From d38696f411ea0889d4152f999864912f1685120b Mon Sep 17 00:00:00 2001 From: DioEgizio <83089242+DioEgizio@users.noreply.github.com> Date: Sat, 6 May 2023 07:15:14 +0200 Subject: [PATCH 164/185] Update launcher/ui/pages/global/MinecraftPage.ui Co-authored-by: Sefa Eyeoglu Signed-off-by: DioEgizio <83089242+DioEgizio@users.noreply.github.com> --- launcher/ui/pages/global/MinecraftPage.ui | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/ui/pages/global/MinecraftPage.ui b/launcher/ui/pages/global/MinecraftPage.ui index cff071bf..103881b5 100644 --- a/launcher/ui/pages/global/MinecraftPage.ui +++ b/launcher/ui/pages/global/MinecraftPage.ui @@ -187,7 +187,7 @@ - System-related tweaks + Tweaks From 62a402d05aa2b4722201506794fd20e95b05845c Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Sat, 6 May 2023 19:10:58 -0700 Subject: [PATCH 165/185] refactor: move functions to utils + code-review fixes Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- launcher/Application.cpp | 8 +- launcher/MMCTime.cpp | 64 ++++++++++ launcher/MMCTime.h | 9 ++ launcher/StringUtils.cpp | 67 ++++++++++ launcher/StringUtils.h | 12 ++ .../atlauncher/ATLPackInstallTask.cpp | 2 +- launcher/net/Download.cpp | 119 +++--------------- launcher/net/NetAction.h | 36 ++---- launcher/qtlogging.ini | 2 + launcher/tasks/ConcurrentTask.h | 21 ++-- 10 files changed, 194 insertions(+), 146 deletions(-) diff --git a/launcher/Application.cpp b/launcher/Application.cpp index 28c2a9ce..1659eb44 100644 --- a/launcher/Application.cpp +++ b/launcher/Application.cpp @@ -423,16 +423,16 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv) foundLoggingRules = QFile::exists(logRulesPath); // search the dataPath() - + // seach app data standard path if(!foundLoggingRules && !isPortable() && dirParam.isEmpty()) { - logRulesPath = QStandardPaths::locate(QStandardPaths::AppDataLocation, logRulesFile); + logRulesPath = QStandardPaths::locate(QStandardPaths::AppDataLocation, FS::PathCombine("..", logRulesFile)); if(!logRulesPath.isEmpty()) { qDebug() << "Found" << logRulesPath << "..."; foundLoggingRules = true; } } - - if(!QFile::exists(logRulesPath)) { + // seach root path + if(!foundLoggingRules) { logRulesPath = FS::PathCombine(m_rootPath, logRulesFile); qDebug() << "Testing" << logRulesPath << "..."; foundLoggingRules = QFile::exists(logRulesPath); diff --git a/launcher/MMCTime.cpp b/launcher/MMCTime.cpp index 70bc4135..1702ec06 100644 --- a/launcher/MMCTime.cpp +++ b/launcher/MMCTime.cpp @@ -18,6 +18,8 @@ #include #include +#include +#include QString Time::prettifyDuration(int64_t duration) { int seconds = (int) (duration % 60); @@ -36,3 +38,65 @@ QString Time::prettifyDuration(int64_t duration) { } return QObject::tr("%1d %2h %3min").arg(days).arg(hours).arg(minutes); } + +QString Time::humanReadableDuration(double duration, int precision) { + + using days = std::chrono::duration>; + + QString outStr; + QTextStream os(&outStr); + + bool neg = false; + if (duration < 0) { + neg = true; // flag + duration *= -1; // invert + } + + auto std_duration = std::chrono::duration(duration); + auto d = std::chrono::duration_cast(std_duration); + std_duration -= d; + auto h = std::chrono::duration_cast(std_duration); + std_duration -= h; + auto m = std::chrono::duration_cast(std_duration); + std_duration -= m; + auto s = std::chrono::duration_cast(std_duration); + std_duration -= s; + auto ms = std::chrono::duration_cast(std_duration); + + auto dc = d.count(); + auto hc = h.count(); + auto mc = m.count(); + auto sc = s.count(); + auto msc = ms.count(); + + if (neg) { + os << '-'; + } + if (dc) { + os << dc << QObject::tr("days"); + } + if (hc) { + if (dc) + os << " "; + os << qSetFieldWidth(2) << hc << QObject::tr("h"); // hours + } + if (mc) { + if (dc || hc) + os << " "; + os << qSetFieldWidth(2) << mc << QObject::tr("m"); // minutes + } + if (dc || hc || mc || sc) { + if (dc || hc || mc) + os << " "; + os << qSetFieldWidth(2) << sc << QObject::tr("s"); // seconds + } + if ((msc && (precision > 0)) || !(dc || hc || mc || sc)) { + if (dc || hc || mc || sc) + os << " "; + os << qSetFieldWidth(0) << qSetRealNumberPrecision(precision) << msc << QObject::tr("ms"); // miliseconds + } + + os.flush(); + + return outStr; +} \ No newline at end of file diff --git a/launcher/MMCTime.h b/launcher/MMCTime.h index 10ff2ffe..576b837f 100644 --- a/launcher/MMCTime.h +++ b/launcher/MMCTime.h @@ -22,4 +22,13 @@ namespace Time { QString prettifyDuration(int64_t duration); +/** + * @brief Returns a string with short form time duration ie. `2days 1h3m4s56.0ms` + * miliseconds are only included if `percison` is greater than 0 + * + * @param duration a number of seconds as floating point + * @param precision number of decmial points to display on fractons of a second, defualts to 0. + * @return QString + */ +QString humanReadableDuration(double duration, int precision = 0); } diff --git a/launcher/StringUtils.cpp b/launcher/StringUtils.cpp index 0f3c3669..f677ebf6 100644 --- a/launcher/StringUtils.cpp +++ b/launcher/StringUtils.cpp @@ -1,5 +1,8 @@ #include "StringUtils.h" +#include +#include + /// If you're wondering where these came from exactly, then know you're not the only one =D /// TAKEN FROM Qt, because it doesn't expose it intelligently @@ -74,3 +77,67 @@ int StringUtils::naturalCompare(const QString& s1, const QString& s2, Qt::CaseSe // The two strings are the same (02 == 2) so fall back to the normal sort return QString::compare(s1, s2, cs); } + +/// Truncate a url while keeping its readability py placing the `...` in the middle of the path +QString StringUtils::truncateUrlHumanFriendly(QUrl &url, int max_len, bool hard_limit) +{ + 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; + + auto url_path_parts = url.path().split('/'); + QString last_path_segment = url_path_parts.takeLast(); + + if (url_path_parts.size() >= 1 && url_path_parts.first().isEmpty()) + url_path_parts.removeFirst(); // drop empty first segment (from leading / ) + + if (url_path_parts.size() >= 1) + url_path_parts.removeLast(); // drop the next to last path segment + + auto url_template = QStringLiteral("%1://%2/%3%4"); + + auto url_compact = url_path_parts.isEmpty() + ? url_template.arg(url.scheme(), url.host(), QStringList({ "...", last_path_segment }).join('/'), url.query()) + : url_template.arg(url.scheme(), url.host(), + QStringList({ url_path_parts.join('/'), "...", last_path_segment }).join('/'), url.query()); + + // remove url parts one by one if it's still too long + while (url_compact.length() > max_len && url_path_parts.size() >= 1) { + url_path_parts.removeLast(); // drop the next to last path segment + url_compact = url_path_parts.isEmpty() + ? url_template.arg(url.scheme(), url.host(), QStringList({ "...", last_path_segment }).join('/'), url.query()) + : url_template.arg(url.scheme(), url.host(), + QStringList({ url_path_parts.join('/'), "...", last_path_segment }).join('/'), url.query()); + } + + if ((url_compact.length() >= max_len) && hard_limit) { + // still too long, truncate normaly + url_compact = QString(str_url); + 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; + +} + +static const QStringList s_units_si {"KB", "MB", "GB", "TB"}; +static const QStringList s_units_kibi {"KiB", "MiB", "Gib", "TiB"}; + +QString StringUtils::humanReadableFileSize(double bytes, bool use_si, int decimal_points) { + const QStringList units = use_si ? s_units_si : s_units_kibi; + const int scale = use_si ? 1000 : 1024; + + int u = -1; + double r = pow(10, decimal_points); + + do { + bytes /= scale; + u++; + } while (round(abs(bytes) * r) / r >= scale && u < units.length() - 1); + + return QString::number(bytes, 'f', 2) + " " + units[u]; +} \ No newline at end of file diff --git a/launcher/StringUtils.h b/launcher/StringUtils.h index 1799605b..271e5099 100644 --- a/launcher/StringUtils.h +++ b/launcher/StringUtils.h @@ -1,6 +1,7 @@ #pragma once #include +#include namespace StringUtils { @@ -29,4 +30,15 @@ inline QString fromStdString(string s) #endif int naturalCompare(const QString& s1, const QString& s2, Qt::CaseSensitivity cs); + +/** + * @brief Truncate a url while keeping its readability py placing the `...` in the middle of the path + * @param url Url to truncate + * @param max_len max lenght of url in charaters + * @param hard_limit if truncating the path can't get the url short enough, truncate it normaly. + */ +QString truncateUrlHumanFriendly(QUrl &url, int max_len, bool hard_limit = false); + +QString humanReadableFileSize(double bytes, bool use_si = false, int decimal_points = 1); + } // namespace StringUtils diff --git a/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp b/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp index d130914f..96cea7b7 100644 --- a/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp +++ b/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp @@ -846,7 +846,7 @@ 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/net/Download.cpp b/launcher/net/Download.cpp index 082f963d..7617b5a0 100644 --- a/launcher/net/Download.cpp +++ b/launcher/net/Download.cpp @@ -37,7 +37,6 @@ */ #include "Download.h" -#include #include #include @@ -45,107 +44,19 @@ #include "ByteArraySink.h" #include "ChecksumValidator.h" -#include "FileSystem.h" #include "MetaCacheSink.h" -#include "BuildConfig.h" #include "Application.h" +#include "BuildConfig.h" #include "net/Logging.h" #include "net/NetAction.h" +#include "MMCTime.h" +#include "StringUtils.h" + 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; - - /* 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...\\5"); - if (url_compact.length() >= max_len) { - url_compact = QString(str_url); - url_compact.replace(re, "\\1\\2...\\5"); - } - - - 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; - -} - -QString humanReadableDuration(double duration, int precision = 0) { - - using days = std::chrono::duration>; - - QString outStr; - QTextStream os(&outStr); - - auto std_duration = std::chrono::duration(duration); - auto d = std::chrono::duration_cast(std_duration); - std_duration -= d; - auto h = std::chrono::duration_cast(std_duration); - std_duration -= h; - auto m = std::chrono::duration_cast(std_duration); - std_duration -= m; - auto s = std::chrono::duration_cast(std_duration); - std_duration -= s; - auto ms = std::chrono::duration_cast(std_duration); - - auto dc = d.count(); - auto hc = h.count(); - auto mc = m.count(); - auto sc = s.count(); - auto msc = ms.count(); - - if (dc) { - os << dc << "days"; - } - if (hc) { - if (dc) - os << " "; - os << qSetFieldWidth(2) << hc << "h"; - } - if (mc) { - if (dc || hc) - os << " "; - os << qSetFieldWidth(2) << mc << "m"; - } - if (dc || hc || mc || sc) { - if (dc || hc || mc) - os << " "; - os << qSetFieldWidth(2) << sc << "s"; - } - if ((msc && (precision > 0)) || !(dc || hc || mc || sc)) { - if (dc || hc || mc || sc) - os << " "; - os << qSetFieldWidth(0) << qSetRealNumberPrecision(precision) << msc << "ms"; - } - - os.flush(); - - return outStr; -} - auto Download::makeCached(QUrl url, MetaEntryPtr entry, Options options) -> Download::Ptr { auto dl = makeShared(); @@ -185,7 +96,7 @@ void Download::addValidator(Validator* v) void Download::executeTask() { - setStatus(tr("Downloading %1").arg(truncateUrlHumanFriendly(m_url, 100))); + setStatus(tr("Downloading %1").arg(StringUtils::truncateUrlHumanFriendly(m_url, 80))); if (getState() == Task::State::AbortedByUser) { qCWarning(taskDownloadLogC) << getUid().toString() << "Attempt to start an aborted Download:" << m_url.toString(); @@ -222,12 +133,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); @@ -252,16 +163,19 @@ void Download::downloadProgress(qint64 bytesReceived, qint64 bytesTotal) auto remaing_time_s = (bytesTotal - bytesReceived) / dl_speed_bps; //: 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_progress = + tr("%1 / %2").arg(StringUtils::humanReadableFileSize(bytesReceived)).arg(StringUtils::humanReadableFileSize(bytesTotal)); + QString dl_speed_str; if (elapsed_ms.count() > 0) { + auto str_eta = bytesTotal > 0 ? Time::humanReadableDuration(remaing_time_s) : tr("unknown"); //: 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)); + dl_speed_str = + tr("%1 /s (%2)").arg(StringUtils::humanReadableFileSize(dl_speed_bps)).arg(str_eta); } else { - //: Download speed at 0 bytes per second + //: Download speed at 0 bytes per second dl_speed_str = tr("0 B/s"); - } + } setDetails(dl_progress + "\n" + dl_speed_str); @@ -290,7 +204,8 @@ void Download::sslErrors(const QList& errors) { int i = 1; for (auto error : errors) { - qCCritical(taskDownloadLogC) << 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(taskDownloadLogC) << getUid().toString() << "Certificate in question:\n" << cert.toText(); i++; diff --git a/launcher/net/NetAction.h b/launcher/net/NetAction.h index a13d115f..32095041 100644 --- a/launcher/net/NetAction.h +++ b/launcher/net/NetAction.h @@ -36,38 +36,18 @@ #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(double 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; - - int u = -1; - double r = pow(10, decimal_points); - - do { - bytes /= scale; - u++; - } while (round(abs(bytes) * r) / r >= scale && u < units.length() - 1); - - return QString::number(bytes, 'f', 2) + " " + units[u]; -} - class NetAction : public Task { Q_OBJECT -protected: - explicit NetAction() : Task() {}; + protected: + explicit NetAction() : Task(){}; -public: + public: using Ptr = shared_qobject_ptr; virtual ~NetAction() = default; @@ -76,23 +56,23 @@ public: 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: - void executeTask() override {}; + protected: + void executeTask() override{}; -public: + public: shared_qobject_ptr m_network; /// the network reply diff --git a/launcher/qtlogging.ini b/launcher/qtlogging.ini index 4543378f..27f4e676 100644 --- a/launcher/qtlogging.ini +++ b/launcher/qtlogging.ini @@ -3,6 +3,8 @@ # prevent log spam and strange bugs # qt.qpa.drawing in particular causes theme artifacts on MacOS qt.*.debug=false +# dont log credentials buy defualt +launcher.auth.credentials.debug=false # remove the debug lines, other log levels still get through launcher.task.net.download.debug=false # enable or disable whole catageries diff --git a/launcher/tasks/ConcurrentTask.h b/launcher/tasks/ConcurrentTask.h index aec707bc..6325fc9e 100644 --- a/launcher/tasks/ConcurrentTask.h +++ b/launcher/tasks/ConcurrentTask.h @@ -35,17 +35,17 @@ */ #pragma once -#include #include #include #include +#include #include #include "tasks/Task.h" class ConcurrentTask : public Task { Q_OBJECT -public: + public: using Ptr = shared_qobject_ptr; explicit ConcurrentTask(QObject* parent = nullptr, QString task_name = "", int max_concurrent = 6); @@ -58,7 +58,7 @@ public: void addTask(Task::Ptr task); -public slots: + public slots: bool abort() override; /** Resets the internal state of the task. @@ -66,20 +66,19 @@ public slots: */ void clear(); -protected -slots: + protected slots: void executeTask() override; virtual void startNext(); 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 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, TaskStepProgress const& task_step_progress); -protected: + protected: // NOTE: This is not thread-safe. [[nodiscard]] unsigned int totalSize() const { return m_queue.size() + m_doing.size() + m_done.size(); } @@ -88,13 +87,13 @@ protected: virtual void updateState(); -protected: + protected: QString m_name; QString m_step_status; QQueue m_queue; - QHash m_doing; + QHash m_doing; QHash m_done; QHash m_failed; QHash m_succeeded; From 718abaae0ef465050c81c0dfba63ce9f0fff17fc Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Sat, 6 May 2023 19:18:39 -0700 Subject: [PATCH 166/185] doc fixes Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- launcher/MMCTime.h | 4 ++-- launcher/StringUtils.cpp | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/launcher/MMCTime.h b/launcher/MMCTime.h index 576b837f..6a5780b4 100644 --- a/launcher/MMCTime.h +++ b/launcher/MMCTime.h @@ -23,8 +23,8 @@ namespace Time { QString prettifyDuration(int64_t duration); /** - * @brief Returns a string with short form time duration ie. `2days 1h3m4s56.0ms` - * miliseconds are only included if `percison` is greater than 0 + * @brief Returns a string with short form time duration ie. `2days 1h3m4s56.0ms`. + * miliseconds are only included if `precision` is greater than 0. * * @param duration a number of seconds as floating point * @param precision number of decmial points to display on fractons of a second, defualts to 0. diff --git a/launcher/StringUtils.cpp b/launcher/StringUtils.cpp index f677ebf6..5d9e32b6 100644 --- a/launcher/StringUtils.cpp +++ b/launcher/StringUtils.cpp @@ -78,7 +78,6 @@ int StringUtils::naturalCompare(const QString& s1, const QString& s2, Qt::CaseSe return QString::compare(s1, s2, cs); } -/// Truncate a url while keeping its readability py placing the `...` in the middle of the path QString StringUtils::truncateUrlHumanFriendly(QUrl &url, int max_len, bool hard_limit) { auto display_options = QUrl::RemoveUserInfo | QUrl::RemoveFragment | QUrl::NormalizePathSegments; From 30cf73a22f1d185da15944857bed135d9e588267 Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Sun, 7 May 2023 13:23:59 -0700 Subject: [PATCH 167/185] typo fix Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- launcher/qtlogging.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/qtlogging.ini b/launcher/qtlogging.ini index 27f4e676..c12d1e10 100644 --- a/launcher/qtlogging.ini +++ b/launcher/qtlogging.ini @@ -3,7 +3,7 @@ # prevent log spam and strange bugs # qt.qpa.drawing in particular causes theme artifacts on MacOS qt.*.debug=false -# dont log credentials buy defualt +# don't log credentials by default launcher.auth.credentials.debug=false # remove the debug lines, other log levels still get through launcher.task.net.download.debug=false From f27716656c6f6006238203669a7a02f035733fc0 Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Thu, 11 May 2023 16:32:00 -0700 Subject: [PATCH 168/185] fix: remove qt < 5.6 support process error signal Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- launcher/LoggedProcess.cpp | 4 ---- launcher/java/JavaChecker.cpp | 4 ---- 2 files changed, 8 deletions(-) diff --git a/launcher/LoggedProcess.cpp b/launcher/LoggedProcess.cpp index 763a9b5c..d70f6d00 100644 --- a/launcher/LoggedProcess.cpp +++ b/launcher/LoggedProcess.cpp @@ -45,11 +45,7 @@ LoggedProcess::LoggedProcess(QObject *parent) : QProcess(parent) connect(this, &QProcess::readyReadStandardOutput, this, &LoggedProcess::on_stdOut); connect(this, &QProcess::readyReadStandardError, this, &LoggedProcess::on_stdErr); connect(this, QOverload::of(&QProcess::finished), this, &LoggedProcess::on_exit); -#if QT_VERSION >= QT_VERSION_CHECK(5, 6, 0) // &QProcess::errorOccurred added in 5.6 connect(this, &QProcess::errorOccurred, this, &LoggedProcess::on_error); -#else - connect(this, QOverload::of(&QProcess::error), this, &LoggedProcess::on_error); -#endif connect(this, &QProcess::stateChanged, this, &LoggedProcess::on_stateChange); } diff --git a/launcher/java/JavaChecker.cpp b/launcher/java/JavaChecker.cpp index 922580ce..b4c55b3d 100644 --- a/launcher/java/JavaChecker.cpp +++ b/launcher/java/JavaChecker.cpp @@ -88,11 +88,7 @@ void JavaChecker::performCheck() qDebug() << "Running java checker: " + m_path + args.join(" ");; connect(process.get(), QOverload::of(&QProcess::finished), this, &JavaChecker::finished); -#if QT_VERSION >= QT_VERSION_CHECK(5, 6, 0) // &QProcess::errorOccurred added in 5.6 connect(process.get(), &QProcess::errorOccurred, this, &JavaChecker::error); -#else - connect(process.get(), &QProcess::error, this, &JavaChecker::error); -#endif connect(process.get(), &QProcess::readyReadStandardOutput, this, &JavaChecker::stdoutReady); connect(process.get(), &QProcess::readyReadStandardError, this, &JavaChecker::stderrReady); connect(&killTimer, &QTimer::timeout, this, &JavaChecker::timeout); From b16829b0f9a24dba9d4c9582f82affb30a416f1b Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Fri, 12 May 2023 00:21:37 -0700 Subject: [PATCH 169/185] Gib -> GiB Co-authored-by: Sefa Eyeoglu Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- launcher/StringUtils.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/StringUtils.cpp b/launcher/StringUtils.cpp index 84820eb0..e08e6fdc 100644 --- a/launcher/StringUtils.cpp +++ b/launcher/StringUtils.cpp @@ -160,7 +160,7 @@ QString StringUtils::truncateUrlHumanFriendly(QUrl& url, int max_len, bool hard_ } 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_kibi{ "KiB", "MiB", "GiB", "TiB" }; QString StringUtils::humanReadableFileSize(double bytes, bool use_si, int decimal_points) { From 20781334939fbb15b1f0fec2a7f65b9ad25d02c1 Mon Sep 17 00:00:00 2001 From: Keri Date: Sun, 23 Apr 2023 11:53:21 +0300 Subject: [PATCH 170/185] fix: remove unnecessary keywords from desktop file this messes with apps that use tag search like rofi Signed-off-by: Keri Signed-off-by: Sefa Eyeoglu --- program_info/org.prismlauncher.PrismLauncher.desktop.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/program_info/org.prismlauncher.PrismLauncher.desktop.in b/program_info/org.prismlauncher.PrismLauncher.desktop.in index f08f2ba4..20fabe9d 100644 --- a/program_info/org.prismlauncher.PrismLauncher.desktop.in +++ b/program_info/org.prismlauncher.PrismLauncher.desktop.in @@ -8,6 +8,6 @@ Exec=@Launcher_APP_BINARY_NAME@ StartupNotify=true Icon=org.prismlauncher.PrismLauncher Categories=Game;ActionGame;AdventureGame;Simulation; -Keywords=game;minecraft;launcher;mc;multimc;polymc; +Keywords=game;minecraft;mc; StartupWMClass=PrismLauncher MimeType=application/zip;application/x-modrinth-modpack+zip From 90b330d4baf5c3519788c4cc773775733720d7ef Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Fri, 19 May 2023 18:34:54 +0200 Subject: [PATCH 171/185] chore: update social links Signed-off-by: Sefa Eyeoglu --- CMakeLists.txt | 6 +++--- README.md | 6 +++--- .../org.prismlauncher.PrismLauncher.metainfo.xml.in | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 2ff26aee..6bd12630 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -164,13 +164,13 @@ set(Launcher_BUG_TRACKER_URL "https://github.com/PrismLauncher/PrismLauncher/iss set(Launcher_TRANSLATIONS_URL "https://hosted.weblate.org/projects/prismlauncher/launcher/" CACHE STRING "URL for the translations platform.") # Matrix Space -set(Launcher_MATRIX_URL "https://matrix.to/#/#prismlauncher:matrix.org" CACHE STRING "URL to the Matrix Space") +set(Launcher_MATRIX_URL "https://prismlauncher.org/matrix" CACHE STRING "URL to the Matrix Space") # Discord URL -set(Launcher_DISCORD_URL "https://discord.gg/prismlauncher" CACHE STRING "URL for the Discord guild.") +set(Launcher_DISCORD_URL "https://prismlauncher.org/discord" CACHE STRING "URL for the Discord guild.") # Subreddit URL -set(Launcher_SUBREDDIT_URL "https://www.reddit.com/r/PrismLauncher/" CACHE STRING "URL for the subreddit.") +set(Launcher_SUBREDDIT_URL "https://prismlauncher.org/reddit" CACHE STRING "URL for the subreddit.") # Builds set(Launcher_FORCE_BUNDLED_LIBS OFF CACHE BOOL "Prevent using system libraries, if they are available as submodules") diff --git a/README.md b/README.md index aaa1fd4c..993f02f5 100644 --- a/README.md +++ b/README.md @@ -38,15 +38,15 @@ Feel free to create a GitHub issue if you find a bug or want to suggest a new fe - **Our Discord server:** -[![Prism Launcher Discord server](https://discordapp.com/api/guilds/1031648380885147709/widget.png?style=banner3)](https://discord.gg/prismlauncher) +[![Prism Launcher Discord server](https://discordapp.com/api/guilds/1031648380885147709/widget.png?style=banner3)](https://prismlauncher.org/discord) - **Our Matrix space:** -[![PrismLauncher Space](https://img.shields.io/matrix/prismlauncher:matrix.org?style=for-the-badge&label=Matrix%20Space&logo=matrix&color=purple)](https://matrix.to/#/#prismlauncher:matrix.org) +[![PrismLauncher Space](https://img.shields.io/matrix/prismlauncher:matrix.org?style=for-the-badge&label=Matrix%20Space&logo=matrix&color=purple)](https://prismlauncher.org/matrix) - **Our Subreddit:** -[![r/PrismLauncher](https://img.shields.io/reddit/subreddit-subscribers/prismlauncher?style=for-the-badge&logo=reddit)](https://www.reddit.com/r/PrismLauncher/) +[![r/PrismLauncher](https://img.shields.io/reddit/subreddit-subscribers/prismlauncher?style=for-the-badge&logo=reddit)](https://prismlauncher.org/reddit) ## Translations diff --git a/program_info/org.prismlauncher.PrismLauncher.metainfo.xml.in b/program_info/org.prismlauncher.PrismLauncher.metainfo.xml.in index 96708960..a482f0e3 100644 --- a/program_info/org.prismlauncher.PrismLauncher.metainfo.xml.in +++ b/program_info/org.prismlauncher.PrismLauncher.metainfo.xml.in @@ -10,7 +10,7 @@ https://prismlauncher.org/ https://prismlauncher.org/wiki/ https://github.com/PrismLauncher/PrismLauncher/issues - https://discord.gg/prismlauncher + https://prismlauncher.org/discord https://github.com/PrismLauncher/PrismLauncher https://github.com/PrismLauncher/PrismLauncher/blob/develop/CONTRIBUTING.md https://hosted.weblate.org/projects/prismlauncher/launcher From 1b3ff96ffd3a249d2b4b278a4afc2714038560d7 Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Sun, 21 May 2023 01:46:28 -0700 Subject: [PATCH 172/185] fix: memory leak with NetJob and responce not getting cleaned up Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- launcher/InstanceCreationTask.h | 2 +- launcher/InstanceImportTask.cpp | 26 ++-- .../modplatform/flame/FileResolvingTask.cpp | 111 +++++++++++++----- .../modplatform/flame/FileResolvingTask.h | 36 +++--- .../flame/FlameInstanceCreationTask.cpp | 1 + launcher/tasks/ConcurrentTask.cpp | 27 ++--- launcher/tasks/Task.cpp | 2 +- launcher/tasks/Task.h | 14 +++ 8 files changed, 140 insertions(+), 79 deletions(-) diff --git a/launcher/InstanceCreationTask.h b/launcher/InstanceCreationTask.h index 03ee1a7a..380fdf8a 100644 --- a/launcher/InstanceCreationTask.h +++ b/launcher/InstanceCreationTask.h @@ -34,7 +34,7 @@ class InstanceCreationTask : public InstanceTask { QString getError() const { return m_error_message; } protected: - void setError(QString message) { m_error_message = message; }; + void setError(const QString& message) { m_error_message = message; }; protected: bool m_abort = false; diff --git a/launcher/InstanceImportTask.cpp b/launcher/InstanceImportTask.cpp index 8a48873e..352848f0 100644 --- a/launcher/InstanceImportTask.cpp +++ b/launcher/InstanceImportTask.cpp @@ -41,6 +41,7 @@ #include "MMCZip.h" #include "NullInstance.h" +#include "QObjectPtr.h" #include "icons/IconList.h" #include "icons/IconUtils.h" @@ -260,7 +261,7 @@ void InstanceImportTask::extractFinished() void InstanceImportTask::processFlame() { - FlameCreationTask* inst_creation_task = nullptr; + shared_qobject_ptr inst_creation_task = nullptr; if (!m_extra_info.isEmpty()) { auto pack_id_it = m_extra_info.constFind("pack_id"); Q_ASSERT(pack_id_it != m_extra_info.constEnd()); @@ -275,10 +276,10 @@ void InstanceImportTask::processFlame() if (original_instance_id_it != m_extra_info.constEnd()) original_instance_id = original_instance_id_it.value(); - inst_creation_task = new FlameCreationTask(m_stagingPath, m_globalSettings, m_parent, pack_id, pack_version_id, original_instance_id); + inst_creation_task = makeShared(m_stagingPath, m_globalSettings, m_parent, pack_id, pack_version_id, original_instance_id); } else { // FIXME: Find a way to get IDs in directly imported ZIPs - inst_creation_task = new FlameCreationTask(m_stagingPath, m_globalSettings, m_parent, {}, {}); + inst_creation_task = makeShared(m_stagingPath, m_globalSettings, m_parent, QString(), QString()); } inst_creation_task->setName(*this); @@ -286,20 +287,19 @@ void InstanceImportTask::processFlame() inst_creation_task->setGroup(m_instGroup); inst_creation_task->setConfirmUpdate(shouldConfirmUpdate()); - connect(inst_creation_task, &Task::succeeded, this, [this, inst_creation_task] { + connect(inst_creation_task.get(), &Task::succeeded, this, [this, inst_creation_task] { setOverride(inst_creation_task->shouldOverride(), inst_creation_task->originalInstanceID()); emitSucceeded(); }); - 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::details, this, &InstanceImportTask::setDetails); - connect(inst_creation_task, &Task::finished, inst_creation_task, &InstanceCreationTask::deleteLater); + connect(inst_creation_task.get(), &Task::failed, this, &InstanceImportTask::emitFailed); + connect(inst_creation_task.get(), &Task::progress, this, &InstanceImportTask::setProgress); + connect(inst_creation_task.get(), &Task::stepProgress, this, &InstanceImportTask::propogateStepProgress); + connect(inst_creation_task.get(), &Task::status, this, &InstanceImportTask::setStatus); + connect(inst_creation_task.get(), &Task::details, this, &InstanceImportTask::setDetails); - connect(this, &Task::aborted, inst_creation_task, &InstanceCreationTask::abort); - connect(inst_creation_task, &Task::aborted, this, &Task::abort); - connect(inst_creation_task, &Task::abortStatusChanged, this, &Task::setAbortable); + connect(this, &Task::aborted, inst_creation_task.get(), &InstanceCreationTask::abort); + connect(inst_creation_task.get(), &Task::aborted, this, &Task::abort); + connect(inst_creation_task.get(), &Task::abortStatusChanged, this, &Task::setAbortable); inst_creation_task->start(); } diff --git a/launcher/modplatform/flame/FileResolvingTask.cpp b/launcher/modplatform/flame/FileResolvingTask.cpp index d3a737bb..83db642e 100644 --- a/launcher/modplatform/flame/FileResolvingTask.cpp +++ b/launcher/modplatform/flame/FileResolvingTask.cpp @@ -35,7 +35,29 @@ void Flame::FileResolvingTask::executeTask() QByteArray data = Json::toText(object); auto dl = Net::Upload::makeByteArray(QUrl("https://api.curseforge.com/v1/mods/files"), result.get(), data); m_dljob->addNetAction(dl); - connect(m_dljob.get(), &NetJob::finished, this, &Flame::FileResolvingTask::netJobFinished); + + auto step_progress = std::make_shared(); + connect(m_dljob.get(), &NetJob::finished, this, [this, step_progress]() { + step_progress->state = TaskStepState::Succeeded; + stepProgress(*step_progress); + netJobFinished(); + }); + connect(m_dljob.get(), &NetJob::failed, this, [this, step_progress](QString reason) { + step_progress->state = TaskStepState::Failed; + stepProgress(*step_progress); + emitFailed(reason); + }); + connect(m_dljob.get(), &NetJob::stepProgress, this, &FileResolvingTask::propogateStepProgress); + connect(m_dljob.get(), &NetJob::progress, this, [this, step_progress](qint64 current, qint64 total) { + qDebug() << "Resolve slug progress" << current << total; + step_progress->update(current, total); + stepProgress(*step_progress); + }); + connect(m_dljob.get(), &NetJob::status, this, [this, step_progress](QString status) { + step_progress->status = status; + stepProgress(*step_progress); + }); + m_dljob->start(); } @@ -44,7 +66,7 @@ void Flame::FileResolvingTask::netJobFinished() setProgress(1, 3); // job to check modrinth for blocked projects m_checkJob.reset(new NetJob("Modrinth check", m_network)); - blockedProjects = QMap(); + blockedProjects = QMap>(); QJsonDocument doc; QJsonArray array; @@ -71,8 +93,8 @@ void Flame::FileResolvingTask::netJobFinished() auto hash = out.hash; if(!hash.isEmpty()) { auto url = QString("https://api.modrinth.com/v2/version_file/%1?algorithm=sha1").arg(hash); - auto output = new QByteArray(); - auto dl = Net::Download::makeByteArray(QUrl(url), output); + auto output = std::make_shared(); + auto dl = Net::Download::makeByteArray(QUrl(url), output.get()); QObject::connect(dl.get(), &Net::Download::succeeded, [&out]() { out.resolved = true; }); @@ -82,7 +104,27 @@ void Flame::FileResolvingTask::netJobFinished() } } } - connect(m_checkJob.get(), &NetJob::finished, this, &Flame::FileResolvingTask::modrinthCheckFinished); + auto step_progress = std::make_shared(); + connect(m_checkJob.get(), &NetJob::finished, this, [this, step_progress]() { + step_progress->state = TaskStepState::Succeeded; + stepProgress(*step_progress); + modrinthCheckFinished(); + }); + connect(m_checkJob.get(), &NetJob::failed, this, [this, step_progress](QString reason) { + step_progress->state = TaskStepState::Failed; + stepProgress(*step_progress); + emitFailed(reason); + }); + connect(m_checkJob.get(), &NetJob::stepProgress, this, &FileResolvingTask::propogateStepProgress); + connect(m_checkJob.get(), &NetJob::progress, this, [this, step_progress](qint64 current, qint64 total) { + qDebug() << "Resolve slug progress" << current << total; + step_progress->update(current, total); + stepProgress(*step_progress); + }); + connect(m_checkJob.get(), &NetJob::status, this, [this, step_progress](QString status) { + step_progress->status = status; + stepProgress(*step_progress); + }); m_checkJob->start(); } @@ -95,7 +137,6 @@ void Flame::FileResolvingTask::modrinthCheckFinished() { auto &out = *it; auto bytes = blockedProjects[out]; if (!out->resolved) { - delete bytes; continue; } @@ -112,11 +153,9 @@ void Flame::FileResolvingTask::modrinthCheckFinished() { } else { out->resolved = false; } - - delete bytes; } //copy to an output list and filter out projects found on modrinth - auto block = new QList(); + auto block = std::make_shared>(); auto it = blockedProjects.keys(); std::copy_if(it.begin(), it.end(), std::back_inserter(*block), [](File *f) { return !f->resolved; @@ -124,32 +163,48 @@ void Flame::FileResolvingTask::modrinthCheckFinished() { //Display not found mods early if (!block->empty()) { //blocked mods found, we need the slug for displaying.... we need another job :D ! - auto slugJob = new NetJob("Slug Job", m_network); - auto slugs = QVector(block->size()); - auto index = 0; - for (auto fileInfo: *block) { - auto projectId = fileInfo->projectId; - slugs[index] = QByteArray(); + m_slugJob.reset(new NetJob("Slug Job", m_network)); + int index = 0; + for (auto mod : *block) { + auto projectId = mod->projectId; + auto output = std::make_shared(); auto url = QString("https://api.curseforge.com/v1/mods/%1").arg(projectId); - auto dl = Net::Download::makeByteArray(url, &slugs[index]); - slugJob->addNetAction(dl); - index++; - } - connect(slugJob, &NetJob::succeeded, this, [slugs, this, slugJob, block]() { - slugJob->deleteLater(); - auto index = 0; - for (const auto &slugResult: slugs) { - auto json = QJsonDocument::fromJson(slugResult); + auto dl = Net::Download::makeByteArray(url, output.get()); + qDebug() << "Fetching url slug for file:" << mod->fileName; + QObject::connect(dl.get(), &Net::Download::succeeded, [block, index, output]() { + auto mod = block->at(index); // use the shared_ptr so it is captured and only freed when we are done + auto json = QJsonDocument::fromJson(*output); auto base = Json::requireString(Json::requireObject(Json::requireObject(Json::requireObject(json),"data"),"links"), "websiteUrl"); - auto mod = block->at(index); auto link = QString("%1/download/%2").arg(base, QString::number(mod->fileId)); mod->websiteUrl = link; - index++; - } + }); + m_slugJob->addNetAction(dl); + index++; + } + auto step_progress = std::make_shared(); + connect(m_slugJob.get(), &NetJob::succeeded, this, [this, step_progress]() { + step_progress->state = TaskStepState::Succeeded; + stepProgress(*step_progress); emitSucceeded(); }); - slugJob->start(); + connect(m_slugJob.get(), &NetJob::failed, this, [this, step_progress](QString reason) { + step_progress->state = TaskStepState::Failed; + stepProgress(*step_progress); + emitFailed(reason); + }); + connect(m_slugJob.get(), &NetJob::stepProgress, this, &FileResolvingTask::propogateStepProgress); + connect(m_slugJob.get(), &NetJob::progress, this, [this, step_progress](qint64 current, qint64 total) { + qDebug() << "Resolve slug progress" << current << total; + step_progress->update(current, total); + stepProgress(*step_progress); + }); + connect(m_slugJob.get(), &NetJob::status, this, [this, step_progress](QString status) { + step_progress->status = status; + stepProgress(*step_progress); + }); + + m_slugJob->start(); } else { emitSucceeded(); } diff --git a/launcher/modplatform/flame/FileResolvingTask.h b/launcher/modplatform/flame/FileResolvingTask.h index 8fc17ea9..c280827a 100644 --- a/launcher/modplatform/flame/FileResolvingTask.h +++ b/launcher/modplatform/flame/FileResolvingTask.h @@ -1,41 +1,37 @@ #pragma once -#include "tasks/Task.h" -#include "net/NetJob.h" #include "PackManifest.h" +#include "net/NetJob.h" +#include "tasks/Task.h" -namespace Flame -{ -class FileResolvingTask : public Task -{ +namespace Flame { +class FileResolvingTask : public Task { Q_OBJECT -public: - explicit FileResolvingTask(const shared_qobject_ptr& network, Flame::Manifest &toProcess); - virtual ~FileResolvingTask() {}; + public: + explicit FileResolvingTask(const shared_qobject_ptr& network, Flame::Manifest& toProcess); + virtual ~FileResolvingTask(){}; bool canAbort() const override { return true; } bool abort() override; - const Flame::Manifest &getResults() const - { - return m_toProcess; - } + const Flame::Manifest& getResults() const { return m_toProcess; } -protected: + protected: virtual void executeTask() override; -protected slots: + protected slots: void netJobFinished(); -private: /* data */ + private: /* data */ shared_qobject_ptr m_network; Flame::Manifest m_toProcess; - std::shared_ptr result; + std::shared_ptr result; NetJob::Ptr m_dljob; - NetJob::Ptr m_checkJob; + NetJob::Ptr m_checkJob; + NetJob::Ptr m_slugJob; void modrinthCheckFinished(); - QMap blockedProjects; + QMap> blockedProjects; }; -} +} // namespace Flame diff --git a/launcher/modplatform/flame/FlameInstanceCreationTask.cpp b/launcher/modplatform/flame/FlameInstanceCreationTask.cpp index 86fd2ab4..dae93d1c 100644 --- a/launcher/modplatform/flame/FlameInstanceCreationTask.cpp +++ b/launcher/modplatform/flame/FlameInstanceCreationTask.cpp @@ -384,6 +384,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); + connect(m_mod_id_resolver.get(), &Flame::FileResolvingTask::details, this, &FlameCreationTask::setDetails); m_mod_id_resolver->start(); loop.exec(); diff --git a/launcher/tasks/ConcurrentTask.cpp b/launcher/tasks/ConcurrentTask.cpp index fae2f3dc..5ee14505 100644 --- a/launcher/tasks/ConcurrentTask.cpp +++ b/launcher/tasks/ConcurrentTask.cpp @@ -138,7 +138,7 @@ void ConcurrentTask::startNext() connect(next.get(), &Task::progress, this, [this, next](qint64 current, qint64 total) { subTaskProgress(next, current, total); }); m_doing.insert(next.get(), next); - auto task_progress = std::make_shared(TaskStepProgress({ next->getUid() })); + auto task_progress = std::make_shared(next->getUid()); m_task_progress.insert(next->getUid(), task_progress); updateState(); @@ -166,9 +166,9 @@ void ConcurrentTask::subTaskSucceeded(Task::Ptr task) disconnect(task.get(), 0, this, 0); - emit stepProgress(*task_progress.get()); + emit stepProgress(*task_progress); updateState(); - updateStepProgress(*task_progress.get(), Operation::REMOVED); + updateStepProgress(*task_progress, Operation::REMOVED); startNext(); } @@ -184,9 +184,9 @@ void ConcurrentTask::subTaskFailed(Task::Ptr task, const QString& msg) disconnect(task.get(), 0, this, 0); - emit stepProgress(*task_progress.get()); + emit stepProgress(*task_progress); updateState(); - updateStepProgress(*task_progress.get(), Operation::REMOVED); + updateStepProgress(*task_progress, Operation::REMOVED); startNext(); } @@ -196,7 +196,7 @@ void ConcurrentTask::subTaskStatus(Task::Ptr task, const QString& msg) task_progress->status = msg; task_progress->state = TaskStepState::Running; - emit stepProgress(*task_progress.get()); + emit stepProgress(*task_progress); if (totalSize() == 1) { setStatus(msg); @@ -209,7 +209,7 @@ void ConcurrentTask::subTaskDetails(Task::Ptr task, const QString& msg) task_progress->details = msg; task_progress->state = TaskStepState::Running; - emit stepProgress(*task_progress.get()); + emit stepProgress(*task_progress); if (totalSize() == 1) { setDetails(msg); @@ -220,15 +220,10 @@ void ConcurrentTask::subTaskProgress(Task::Ptr task, qint64 current, qint64 tota { auto task_progress = m_task_progress.value(task->getUid()); - task_progress->old_current = task_progress->current; - task_progress->old_total = task_progress->old_total; - - task_progress->current = current; - task_progress->total = total; - task_progress->state = TaskStepState::Running; - - emit stepProgress(*task_progress.get()); - updateStepProgress(*task_progress.get(), Operation::CHANGED); + task_progress->update(current, total); + + emit stepProgress(*task_progress); + updateStepProgress(*task_progress, Operation::CHANGED); updateState(); if (totalSize() == 1) { diff --git a/launcher/tasks/Task.cpp b/launcher/tasks/Task.cpp index b0addd46..29c55cd4 100644 --- a/launcher/tasks/Task.cpp +++ b/launcher/tasks/Task.cpp @@ -109,7 +109,7 @@ void Task::start() return; } } - // NOTE: only fall thorugh to here in end states + // NOTE: only fall through to here in end states m_state = State::Running; emit started(); executeTask(); diff --git a/launcher/tasks/Task.h b/launcher/tasks/Task.h index 799ed945..6d8bbbb4 100644 --- a/launcher/tasks/Task.h +++ b/launcher/tasks/Task.h @@ -64,7 +64,21 @@ struct TaskStepProgress { QString status = ""; QString details = ""; TaskStepState state = TaskStepState::Waiting; + TaskStepProgress() { + this->uid = QUuid::createUuid(); + } + TaskStepProgress(QUuid uid) { + this->uid = uid; + } bool isDone() const { return (state == TaskStepState::Failed) || (state == TaskStepState::Succeeded); } + void update(qint64 current, qint64 total) { + this->old_current = this->current; + this->old_total = this->total; + + this->current = current; + this->total = total; + this->state = TaskStepState::Running; + } }; Q_DECLARE_METATYPE(TaskStepProgress) From 21cb4598999808849eb18503f7aae54039c73cea Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Sun, 21 May 2023 01:47:54 -0700 Subject: [PATCH 173/185] fix: memory leak NetJob wans't getting cleaned up. ensure lambda capture of job doens;t increase refcount or it will be cyclic Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- launcher/modplatform/flame/FlameAPI.cpp | 41 +++++++++---------------- 1 file changed, 15 insertions(+), 26 deletions(-) diff --git a/launcher/modplatform/flame/FlameAPI.cpp b/launcher/modplatform/flame/FlameAPI.cpp index 5ef9a409..92590a08 100644 --- a/launcher/modplatform/flame/FlameAPI.cpp +++ b/launcher/modplatform/flame/FlameAPI.cpp @@ -38,14 +38,14 @@ auto FlameAPI::getModFileChangelog(int modId, int fileId) -> QString QEventLoop lock; QString changelog; - auto* netJob = new NetJob(QString("Flame::FileChangelog"), APPLICATION->network()); - auto* response = new QByteArray(); + auto netJob = makeShared(QString("Flame::FileChangelog"), APPLICATION->network()); + auto response = std::make_shared(); netJob->addNetAction(Net::Download::makeByteArray( QString("https://api.curseforge.com/v1/mods/%1/files/%2/changelog") .arg(QString::fromStdString(std::to_string(modId)), QString::fromStdString(std::to_string(fileId))), - response)); + response.get())); - QObject::connect(netJob, &NetJob::succeeded, [netJob, response, &changelog] { + QObject::connect(netJob.get(), &NetJob::succeeded, [&netJob, response, &changelog] { QJsonParseError parse_error{}; QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error); if (parse_error.error != QJsonParseError::NoError) { @@ -60,10 +60,7 @@ auto FlameAPI::getModFileChangelog(int modId, int fileId) -> QString changelog = Json::ensureString(doc.object(), "data"); }); - QObject::connect(netJob, &NetJob::finished, [response, &lock] { - delete response; - lock.quit(); - }); + QObject::connect(netJob.get(), &NetJob::finished, [&lock] { lock.quit(); }); netJob->start(); lock.exec(); @@ -76,13 +73,12 @@ auto FlameAPI::getModDescription(int modId) -> QString QEventLoop lock; QString description; - auto* netJob = new NetJob(QString("Flame::ModDescription"), APPLICATION->network()); - auto* response = new QByteArray(); + auto netJob = makeShared(QString("Flame::ModDescription"), APPLICATION->network()); + auto response = std::make_shared(); netJob->addNetAction(Net::Download::makeByteArray( - QString("https://api.curseforge.com/v1/mods/%1/description") - .arg(QString::number(modId)), response)); + QString("https://api.curseforge.com/v1/mods/%1/description").arg(QString::number(modId)), response.get())); - QObject::connect(netJob, &NetJob::succeeded, [netJob, response, &description] { + QObject::connect(netJob.get(), &NetJob::succeeded, [&netJob, response, &description] { QJsonParseError parse_error{}; QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error); if (parse_error.error != QJsonParseError::NoError) { @@ -97,10 +93,7 @@ auto FlameAPI::getModDescription(int modId) -> QString description = Json::ensureString(doc.object(), "data"); }); - QObject::connect(netJob, &NetJob::finished, [response, &lock] { - delete response; - lock.quit(); - }); + QObject::connect(netJob.get(), &NetJob::finished, [&lock] { lock.quit(); }); netJob->start(); lock.exec(); @@ -118,13 +111,13 @@ auto FlameAPI::getLatestVersion(VersionSearchArgs&& args) -> ModPlatform::Indexe QEventLoop loop; - auto netJob = new NetJob(QString("Flame::GetLatestVersion(%1)").arg(args.pack.name), APPLICATION->network()); - auto response = new QByteArray(); + auto netJob = makeShared(QString("Flame::GetLatestVersion(%1)").arg(args.pack.name), APPLICATION->network()); + auto response = std::make_shared(); ModPlatform::IndexedVersion ver; - netJob->addNetAction(Net::Download::makeByteArray(versions_url, response)); + netJob->addNetAction(Net::Download::makeByteArray(versions_url, response.get())); - QObject::connect(netJob, &NetJob::succeeded, [response, args, &ver] { + QObject::connect(netJob.get(), &NetJob::succeeded, [response, args, &ver] { QJsonParseError parse_error{}; QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error); if (parse_error.error != QJsonParseError::NoError) { @@ -158,11 +151,7 @@ auto FlameAPI::getLatestVersion(VersionSearchArgs&& args) -> ModPlatform::Indexe } }); - QObject::connect(netJob, &NetJob::finished, [response, netJob, &loop] { - netJob->deleteLater(); - delete response; - loop.quit(); - }); + QObject::connect(netJob.get(), &NetJob::finished, [&loop] { loop.quit(); }); netJob->start(); From 79839771561641d6fa34549861a5ed163e382312 Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Sun, 21 May 2023 01:48:34 -0700 Subject: [PATCH 174/185] feat: Qt 5.15 adds transfer timeouts. at least use it for downloads Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- launcher/net/Download.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/launcher/net/Download.cpp b/launcher/net/Download.cpp index cd3fcc85..7f8d3a06 100644 --- a/launcher/net/Download.cpp +++ b/launcher/net/Download.cpp @@ -134,11 +134,14 @@ void Download::executeTask() request.setRawHeader("Authorization", token.toUtf8()); } +#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) + request.setTransferTimeout(); +#endif + 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); From 6b8fe283f0bda66806175de10ba5874a4afdae45 Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Sun, 21 May 2023 01:49:13 -0700 Subject: [PATCH 175/185] fix: memory leak, set parent so it's in tree to get cleaned up. Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- launcher/ui/pages/instance/VersionPage.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/ui/pages/instance/VersionPage.cpp b/launcher/ui/pages/instance/VersionPage.cpp index fffb96f2..74b7ec7c 100644 --- a/launcher/ui/pages/instance/VersionPage.cpp +++ b/launcher/ui/pages/instance/VersionPage.cpp @@ -165,7 +165,7 @@ VersionPage::VersionPage(MinecraftInstance *inst, QWidget *parent) auto proxy = new IconProxy(ui->packageView); proxy->setSourceModel(m_profile.get()); - m_filterModel = new QSortFilterProxyModel(); + m_filterModel = new QSortFilterProxyModel(this); m_filterModel->setDynamicSortFilter(true); m_filterModel->setFilterCaseSensitivity(Qt::CaseInsensitive); m_filterModel->setSortCaseSensitivity(Qt::CaseInsensitive); From 42f9eccb174736eb0812f1d111709ffa93cfefdd Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 24 May 2023 14:21:39 +0000 Subject: [PATCH 176/185] chore(deps): update cachix/install-nix-action action to v21 --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 02cc8b1f..02b705f0 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -587,7 +587,7 @@ jobs: submodules: 'true' - name: Install nix if: inputs.build_type == 'Debug' - uses: cachix/install-nix-action@v20 + uses: cachix/install-nix-action@v21 with: install_url: https://nixos.org/nix/install extra_nix_config: | From 70983c72696afbf444a768b37c87e3f6aa0353e2 Mon Sep 17 00:00:00 2001 From: DioEgizio <83089242+DioEgizio@users.noreply.github.com> Date: Thu, 25 May 2023 16:38:24 +0200 Subject: [PATCH 177/185] chore: update to Qt 6.5.1 Signed-off-by: DioEgizio <83089242+DioEgizio@users.noreply.github.com> --- .github/workflows/build.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 02cc8b1f..bee1ea14 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -68,7 +68,7 @@ jobs: qt_ver: 6 qt_host: windows qt_arch: '' - qt_version: '6.5.0' + qt_version: '6.5.1' qt_modules: 'qt5compat qtimageformats' qt_tools: '' @@ -80,7 +80,7 @@ jobs: qt_ver: 6 qt_host: windows qt_arch: 'win64_msvc2019_arm64' - qt_version: '6.5.0' + qt_version: '6.5.1' qt_modules: 'qt5compat qtimageformats' qt_tools: '' @@ -90,7 +90,7 @@ jobs: qt_ver: 6 qt_host: mac qt_arch: '' - qt_version: '6.5.0' + qt_version: '6.5.1' qt_modules: 'qt5compat qtimageformats' qt_tools: '' From e61d8e4dc870aaeb2949557a87cf2749df573667 Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Thu, 25 May 2023 16:16:58 -0700 Subject: [PATCH 178/185] fix: katabasis and QStyle leaks Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- launcher/ui/pages/instance/ManagedPackPage.cpp | 7 +++++-- libraries/katabasis/src/Reply.cpp | 2 ++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/launcher/ui/pages/instance/ManagedPackPage.cpp b/launcher/ui/pages/instance/ManagedPackPage.cpp index dc983d9a..593590f7 100644 --- a/launcher/ui/pages/instance/ManagedPackPage.cpp +++ b/launcher/ui/pages/instance/ManagedPackPage.cpp @@ -62,8 +62,11 @@ ManagedPackPage::ManagedPackPage(BaseInstance* inst, InstanceWindow* instance_wi // NOTE: GTK2 themes crash with the proxy style. // This seems like an upstream bug, so there's not much else that can be done. - if (!QStyleFactory::keys().contains("gtk2")) - ui->versionsComboBox->setStyle(new NoBigComboBoxStyle(ui->versionsComboBox->style())); + if (!QStyleFactory::keys().contains("gtk2")){ + auto comboStyle = new NoBigComboBoxStyle(ui->versionsComboBox->style()); + comboStyle->setParent(APPLICATION); // make sure this gets cleaned up (setting to simply `this` causes it to be freed too soon) + ui->versionsComboBox->setStyle(comboStyle); + } ui->reloadButton->setVisible(false); connect(ui->reloadButton, &QPushButton::clicked, this, [this](bool){ diff --git a/libraries/katabasis/src/Reply.cpp b/libraries/katabasis/src/Reply.cpp index 3e27a7e6..c2607900 100644 --- a/libraries/katabasis/src/Reply.cpp +++ b/libraries/katabasis/src/Reply.cpp @@ -40,6 +40,8 @@ void ReplyList::remove(QNetworkReply *reply) { if (o2Reply) { o2Reply->stop(); (void)replies_.removeOne(o2Reply); + // we took ownership, we must free + delete o2Reply; } } From aae892dfd1a28411fc14c267c073c71c20696f39 Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Fri, 26 May 2023 19:21:07 -0700 Subject: [PATCH 179/185] fix(memory leak): IndexedPack too large to live inside a qlist without pointers () Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- launcher/modplatform/ModIndex.h | 3 ++ launcher/ui/pages/modplatform/ModModel.cpp | 4 +-- .../ui/pages/modplatform/ResourceModel.cpp | 29 ++++++++++--------- launcher/ui/pages/modplatform/ResourceModel.h | 2 +- .../pages/modplatform/ResourcePackModel.cpp | 4 +-- .../ui/pages/modplatform/ShaderPackModel.cpp | 4 +-- tests/ResourceModel_test.cpp | 6 ++-- 7 files changed, 28 insertions(+), 24 deletions(-) diff --git a/launcher/modplatform/ModIndex.h b/launcher/modplatform/ModIndex.h index 40f1efc4..8d0223f9 100644 --- a/launcher/modplatform/ModIndex.h +++ b/launcher/modplatform/ModIndex.h @@ -23,6 +23,7 @@ #include #include #include +#include class QIODevice; @@ -83,6 +84,8 @@ struct ExtraPackData { }; struct IndexedPack { + using Ptr = std::shared_ptr; + QVariant addonId; ResourceProvider provider; QString name; diff --git a/launcher/ui/pages/modplatform/ModModel.cpp b/launcher/ui/pages/modplatform/ModModel.cpp index 3ffe6cb0..afd8b292 100644 --- a/launcher/ui/pages/modplatform/ModModel.cpp +++ b/launcher/ui/pages/modplatform/ModModel.cpp @@ -36,7 +36,7 @@ ResourceAPI::SearchArgs ModModel::createSearchArguments() ResourceAPI::VersionSearchArgs ModModel::createVersionsArguments(QModelIndex& entry) { - auto& pack = m_packs[entry.row()]; + auto& pack = *m_packs[entry.row()]; auto profile = static_cast(m_base_instance).getPackProfile(); Q_ASSERT(profile); @@ -51,7 +51,7 @@ ResourceAPI::VersionSearchArgs ModModel::createVersionsArguments(QModelIndex& en ResourceAPI::ProjectInfoArgs ModModel::createInfoArguments(QModelIndex& entry) { - auto& pack = m_packs[entry.row()]; + auto& pack = *m_packs[entry.row()]; return { pack }; } diff --git a/launcher/ui/pages/modplatform/ResourceModel.cpp b/launcher/ui/pages/modplatform/ResourceModel.cpp index db7d26f8..631ae68c 100644 --- a/launcher/ui/pages/modplatform/ResourceModel.cpp +++ b/launcher/ui/pages/modplatform/ResourceModel.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include "Application.h" #include "BuildConfig.h" @@ -45,16 +46,16 @@ auto ResourceModel::data(const QModelIndex& index, int role) const -> QVariant auto pack = m_packs.at(pos); switch (role) { case Qt::ToolTipRole: { - if (pack.description.length() > 100) { + if (pack->description.length() > 100) { // some magic to prevent to long tooltips and replace html linebreaks - QString edit = pack.description.left(97); + QString edit = pack->description.left(97); edit = edit.left(edit.lastIndexOf("
")).left(edit.lastIndexOf(" ")).append("..."); return edit; } - return pack.description; + return pack->description; } case Qt::DecorationRole: { - if (auto icon_or_none = const_cast(this)->getIcon(const_cast(index), pack.logoUrl); + if (auto icon_or_none = const_cast(this)->getIcon(const_cast(index), pack->logoUrl); icon_or_none.has_value()) return icon_or_none.value(); @@ -64,16 +65,16 @@ auto ResourceModel::data(const QModelIndex& index, int role) const -> QVariant return QSize(0, 58); case Qt::UserRole: { QVariant v; - v.setValue(pack); + v.setValue(*pack); return v; } // Custom data case UserDataTypes::TITLE: - return pack.name; + return pack->name; case UserDataTypes::DESCRIPTION: - return pack.description; + return pack->description; case UserDataTypes::SELECTED: - return pack.isAnyVersionSelected(); + return pack->isAnyVersionSelected(); default: break; } @@ -102,7 +103,7 @@ bool ResourceModel::setData(const QModelIndex& index, const QVariant& value, int if (pos >= m_packs.size() || pos < 0 || !index.isValid()) return false; - m_packs[pos] = value.value(); + m_packs[pos] = std::make_shared(value.value()); emit dataChanged(index, index); return true; @@ -161,7 +162,7 @@ void ResourceModel::loadEntry(QModelIndex& entry) if (!hasActiveInfoJob()) m_current_info_job.clear(); - if (!pack.versionsLoaded) { + if (!pack->versionsLoaded) { auto args{ createVersionsArguments(entry) }; auto callbacks{ createVersionsCallbacks(entry) }; @@ -177,7 +178,7 @@ void ResourceModel::loadEntry(QModelIndex& entry) runInfoJob(job); } - if (!pack.extraDataLoaded) { + if (!pack->extraDataLoaded) { auto args{ createInfoArguments(entry) }; auto callbacks{ createInfoCallbacks(entry) }; @@ -326,15 +327,15 @@ void ResourceModel::loadIndexedPackVersions(ModPlatform::IndexedPack&, QJsonArra void ResourceModel::searchRequestSucceeded(QJsonDocument& doc) { - QList newList; + QList newList; auto packs = documentToArray(doc); for (auto packRaw : packs) { auto packObj = packRaw.toObject(); - ModPlatform::IndexedPack pack; + ModPlatform::IndexedPack::Ptr pack = std::make_shared(); try { - loadIndexedPack(pack, packObj); + loadIndexedPack(*pack, packObj); newList.append(pack); } catch (const JSONValidationError& e) { qWarning() << "Error while loading resource from " << debugName() << ": " << e.cause(); diff --git a/launcher/ui/pages/modplatform/ResourceModel.h b/launcher/ui/pages/modplatform/ResourceModel.h index 46a02d6e..1ec42cda 100644 --- a/launcher/ui/pages/modplatform/ResourceModel.h +++ b/launcher/ui/pages/modplatform/ResourceModel.h @@ -123,7 +123,7 @@ class ResourceModel : public QAbstractListModel { QSet m_currently_running_icon_actions; QSet m_failed_icon_actions; - QList m_packs; + 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? diff --git a/launcher/ui/pages/modplatform/ResourcePackModel.cpp b/launcher/ui/pages/modplatform/ResourcePackModel.cpp index 3df9a787..18c14bf8 100644 --- a/launcher/ui/pages/modplatform/ResourcePackModel.cpp +++ b/launcher/ui/pages/modplatform/ResourcePackModel.cpp @@ -22,13 +22,13 @@ ResourceAPI::SearchArgs ResourcePackResourceModel::createSearchArguments() ResourceAPI::VersionSearchArgs ResourcePackResourceModel::createVersionsArguments(QModelIndex& entry) { auto& pack = m_packs[entry.row()]; - return { pack }; + return { *pack }; } ResourceAPI::ProjectInfoArgs ResourcePackResourceModel::createInfoArguments(QModelIndex& entry) { auto& pack = m_packs[entry.row()]; - return { pack }; + return { *pack }; } void ResourcePackResourceModel::searchWithTerm(const QString& term, unsigned int sort) diff --git a/launcher/ui/pages/modplatform/ShaderPackModel.cpp b/launcher/ui/pages/modplatform/ShaderPackModel.cpp index 2101b394..aabd3be6 100644 --- a/launcher/ui/pages/modplatform/ShaderPackModel.cpp +++ b/launcher/ui/pages/modplatform/ShaderPackModel.cpp @@ -22,13 +22,13 @@ ResourceAPI::SearchArgs ShaderPackResourceModel::createSearchArguments() ResourceAPI::VersionSearchArgs ShaderPackResourceModel::createVersionsArguments(QModelIndex& entry) { auto& pack = m_packs[entry.row()]; - return { pack }; + return { *pack }; } ResourceAPI::ProjectInfoArgs ShaderPackResourceModel::createInfoArguments(QModelIndex& entry) { auto& pack = m_packs[entry.row()]; - return { pack }; + return { *pack }; } void ShaderPackResourceModel::searchWithTerm(const QString& term, unsigned int sort) diff --git a/tests/ResourceModel_test.cpp b/tests/ResourceModel_test.cpp index 716bf853..c0d9cd95 100644 --- a/tests/ResourceModel_test.cpp +++ b/tests/ResourceModel_test.cpp @@ -75,9 +75,9 @@ class ResourceModelTest : public QObject { auto search_json = DummyResourceAPI::searchRequestResult(); auto processed_response = model->documentToArray(search_json).first().toObject(); - QVERIFY(processed_pack.addonId.toString() == Json::requireString(processed_response, "project_id")); - QVERIFY(processed_pack.description == Json::requireString(processed_response, "description")); - QVERIFY(processed_pack.authors.first().name == Json::requireString(processed_response, "author")); + QVERIFY(processed_pack->addonId.toString() == Json::requireString(processed_response, "project_id")); + QVERIFY(processed_pack->description == Json::requireString(processed_response, "description")); + QVERIFY(processed_pack->authors.first().name == Json::requireString(processed_response, "author")); } }; From ff03dd22fe842fc3a24b517f8e9f7a8a54565337 Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Fri, 26 May 2023 21:10:49 -0700 Subject: [PATCH 180/185] fix(memory leak): don't override default deconstructor + reset shared_ptr + ensure modal get's cleaned up Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- launcher/ui/pages/modplatform/ModPage.h | 2 -- launcher/ui/pages/modplatform/ResourceModel.cpp | 2 +- launcher/ui/pages/modplatform/ResourcePackPage.h | 2 -- launcher/ui/pages/modplatform/ResourcePage.cpp | 2 ++ launcher/ui/pages/modplatform/ShaderPackPage.h | 2 -- 5 files changed, 3 insertions(+), 7 deletions(-) diff --git a/launcher/ui/pages/modplatform/ModPage.h b/launcher/ui/pages/modplatform/ModPage.h index c3b58cd6..4ea55efa 100644 --- a/launcher/ui/pages/modplatform/ModPage.h +++ b/launcher/ui/pages/modplatform/ModPage.h @@ -41,8 +41,6 @@ class ModPage : public ResourcePage { return page; } - ~ModPage() override = default; - //: The plural version of 'mod' [[nodiscard]] inline QString resourcesString() const override { return tr("mods"); } //: The singular version of 'mods' diff --git a/launcher/ui/pages/modplatform/ResourceModel.cpp b/launcher/ui/pages/modplatform/ResourceModel.cpp index 631ae68c..472aa851 100644 --- a/launcher/ui/pages/modplatform/ResourceModel.cpp +++ b/launcher/ui/pages/modplatform/ResourceModel.cpp @@ -230,7 +230,7 @@ void ResourceModel::clearData() void ResourceModel::runSearchJob(Task::Ptr ptr) { - m_current_search_job = ptr; + m_current_search_job.reset(ptr); // clean up first m_current_search_job->start(); } void ResourceModel::runInfoJob(Task::Ptr ptr) diff --git a/launcher/ui/pages/modplatform/ResourcePackPage.h b/launcher/ui/pages/modplatform/ResourcePackPage.h index c01c89c4..8c5cf08b 100644 --- a/launcher/ui/pages/modplatform/ResourcePackPage.h +++ b/launcher/ui/pages/modplatform/ResourcePackPage.h @@ -31,8 +31,6 @@ class ResourcePackResourcePage : public ResourcePage { 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' diff --git a/launcher/ui/pages/modplatform/ResourcePage.cpp b/launcher/ui/pages/modplatform/ResourcePage.cpp index bbd465bc..f75bb886 100644 --- a/launcher/ui/pages/modplatform/ResourcePage.cpp +++ b/launcher/ui/pages/modplatform/ResourcePage.cpp @@ -83,6 +83,8 @@ ResourcePage::ResourcePage(ResourceDownloadDialog* parent, BaseInstance& base_in ResourcePage::~ResourcePage() { delete m_ui; + if (m_model) + delete m_model; } void ResourcePage::retranslate() diff --git a/launcher/ui/pages/modplatform/ShaderPackPage.h b/launcher/ui/pages/modplatform/ShaderPackPage.h index 972419a8..9039c4d9 100644 --- a/launcher/ui/pages/modplatform/ShaderPackPage.h +++ b/launcher/ui/pages/modplatform/ShaderPackPage.h @@ -31,8 +31,6 @@ class ShaderPackResourcePage : public ResourcePage { 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' From c81cb59b4b76bc4558a857c7e13c50629c6b27db Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Fri, 26 May 2023 21:21:10 -0700 Subject: [PATCH 181/185] fix(memory leak): don't capture job and create cyclic refrence Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- launcher/modplatform/helpers/NetworkResourceAPI.cpp | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/launcher/modplatform/helpers/NetworkResourceAPI.cpp b/launcher/modplatform/helpers/NetworkResourceAPI.cpp index 010ac15e..a3c592fd 100644 --- a/launcher/modplatform/helpers/NetworkResourceAPI.cpp +++ b/launcher/modplatform/helpers/NetworkResourceAPI.cpp @@ -24,7 +24,7 @@ Task::Ptr NetworkResourceAPI::searchProjects(SearchArgs&& args, SearchCallbacks& netJob->addNetAction(Net::Download::makeByteArray(QUrl(search_url), response)); - QObject::connect(netJob.get(), &NetJob::succeeded, [=]{ + QObject::connect(netJob.get(), &NetJob::succeeded, [this, response, callbacks]{ QJsonParseError parse_error{}; QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error); if (parse_error.error != QJsonParseError::NoError) { @@ -40,16 +40,20 @@ Task::Ptr NetworkResourceAPI::searchProjects(SearchArgs&& args, SearchCallbacks& callbacks.on_succeed(doc); }); - QObject::connect(netJob.get(), &NetJob::failed, [=](QString reason){ + QObject::connect(netJob.get(), &NetJob::failed, [&netJob, callbacks](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.get(), &NetJob::aborted, [=]{ + QObject::connect(netJob.get(), &NetJob::aborted, [callbacks]{ callbacks.on_abort(); }); + QObject::connect(netJob.get(), &NetJob::finished, [response] { + delete response; + }); + return netJob; } @@ -88,7 +92,7 @@ Task::Ptr NetworkResourceAPI::getProjectVersions(VersionSearchArgs&& args, Versi netJob->addNetAction(Net::Download::makeByteArray(versions_url, response)); - QObject::connect(netJob.get(), &NetJob::succeeded, [=] { + QObject::connect(netJob.get(), &NetJob::succeeded, [response, callbacks, args] { QJsonParseError parse_error{}; QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error); if (parse_error.error != QJsonParseError::NoError) { From 37420405c7b5dddb003533e1487ba45a2da5b808 Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Sat, 27 May 2023 23:22:40 -0700 Subject: [PATCH 182/185] fix(memory leak): refactor NoBigComboStyle -> singleton Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- .../ui/pages/instance/ManagedPackPage.cpp | 33 +++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/launcher/ui/pages/instance/ManagedPackPage.cpp b/launcher/ui/pages/instance/ManagedPackPage.cpp index 593590f7..ac34a5f4 100644 --- a/launcher/ui/pages/instance/ManagedPackPage.cpp +++ b/launcher/ui/pages/instance/ManagedPackPage.cpp @@ -41,8 +41,38 @@ class NoBigComboBoxStyle : public QProxyStyle { return QProxyStyle::styleHint(hint, option, widget, returnData); } // clang-format on + + static NoBigComboBoxStyle* GetInstance(QStyle* style); + + private: + static QMap s_singleton_instances_; + static std::mutex s_singleton_instances_mutex_; }; +QMap NoBigComboBoxStyle::s_singleton_instances_ = {}; +std::mutex NoBigComboBoxStyle::s_singleton_instances_mutex_; + +/** + * QProxyStyle and QStyle objects object to being freed even if all the widgets using them are gone + * so make singlestons tied to the lifetime of the application to clean them up and ensure they arn't + * being remade over and over agian leaking memory. + * */ +NoBigComboBoxStyle* NoBigComboBoxStyle::GetInstance(QStyle* style) +{ + std::lock_guard lock(s_singleton_instances_mutex_); + auto inst_iter = s_singleton_instances_.constFind(style); + NoBigComboBoxStyle* inst = nullptr; + if(inst_iter == s_singleton_instances_.constEnd() || *inst_iter == nullptr) { + inst = new NoBigComboBoxStyle(style); + inst->setParent(APPLICATION); + s_singleton_instances_.insert(style, inst); + qDebug() << "QProxyStyle NoBigComboBox created for" << style->objectName() << style; + } else { + inst = *inst_iter; + } + return inst; +} + ManagedPackPage* ManagedPackPage::createPage(BaseInstance* inst, QString type, QWidget* parent) { if (type == "modrinth") @@ -63,8 +93,7 @@ ManagedPackPage::ManagedPackPage(BaseInstance* inst, InstanceWindow* instance_wi // NOTE: GTK2 themes crash with the proxy style. // This seems like an upstream bug, so there's not much else that can be done. if (!QStyleFactory::keys().contains("gtk2")){ - auto comboStyle = new NoBigComboBoxStyle(ui->versionsComboBox->style()); - comboStyle->setParent(APPLICATION); // make sure this gets cleaned up (setting to simply `this` causes it to be freed too soon) + auto comboStyle = NoBigComboBoxStyle::GetInstance(ui->versionsComboBox->style()); ui->versionsComboBox->setStyle(comboStyle); } 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: [PATCH 183/185] 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 +- .../minecraft/mod/ResourceFolderModel.cpp | 2 +- launcher/minecraft/mod/ResourceFolderModel.h | 4 +-- .../minecraft/mod/ResourcePackFolderModel.cpp | 2 +- .../minecraft/mod/ResourcePackFolderModel.h | 2 +- .../minecraft/mod/ShaderPackFolderModel.h | 2 +- .../minecraft/mod/TexturePackFolderModel.cpp | 2 +- .../minecraft/mod/TexturePackFolderModel.h | 2 +- launcher/ui/dialogs/NewInstanceDialog.cpp | 2 +- .../ui/dialogs/ResourceDownloadDialog.cpp | 2 +- launcher/ui/widgets/PageContainer.cpp | 4 ++- tests/ResourceFolderModel_test.cpp | 17 ++++------- 17 files changed, 46 insertions(+), 51 deletions(-) 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); From 0357921284f68c7948104fe95d23209757afde09 Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Sun, 28 May 2023 04:37:09 -0700 Subject: [PATCH 184/185] cleanup: move qstyle getInstance decl inline Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- .../ui/pages/instance/ManagedPackPage.cpp | 54 ++++++++++--------- 1 file changed, 28 insertions(+), 26 deletions(-) diff --git a/launcher/ui/pages/instance/ManagedPackPage.cpp b/launcher/ui/pages/instance/ManagedPackPage.cpp index ac34a5f4..a708377c 100644 --- a/launcher/ui/pages/instance/ManagedPackPage.cpp +++ b/launcher/ui/pages/instance/ManagedPackPage.cpp @@ -30,8 +30,6 @@ class NoBigComboBoxStyle : public QProxyStyle { Q_OBJECT public: - NoBigComboBoxStyle(QStyle* style) : QProxyStyle(style) {} - // clang-format off int styleHint(QStyle::StyleHint hint, const QStyleOption* option = nullptr, const QWidget* widget = nullptr, QStyleHintReturn* returnData = nullptr) const override { @@ -42,36 +40,40 @@ class NoBigComboBoxStyle : public QProxyStyle { } // clang-format on - static NoBigComboBoxStyle* GetInstance(QStyle* style); + /** + * Something about QProxyStyle and QStyle objects means they can't be free'd just + * because all the widgets using them are gone. + * They seems to be tied to the QApplicaiton lifecycle. + * So make singletons tied to the lifetime of the application to clean them up and ensure they aren't + * being remade over and over again, thus leaking memory. + */ + public: + static NoBigComboBoxStyle* getInstance(QStyle* style) + { + std::lock_guard lock(s_singleton_instances_mutex_); + auto inst_iter = s_singleton_instances_.constFind(style); + NoBigComboBoxStyle* inst = nullptr; + if (inst_iter == s_singleton_instances_.constEnd() || *inst_iter == nullptr) { + inst = new NoBigComboBoxStyle(style); + inst->setParent(APPLICATION); + s_singleton_instances_.insert(style, inst); + qDebug() << "QProxyStyle NoBigComboBox created for" << style->objectName() << style; + } else { + inst = *inst_iter; + } + return inst; + } private: - static QMap s_singleton_instances_; + NoBigComboBoxStyle(QStyle* style) : QProxyStyle(style) {} + + static QHash s_singleton_instances_; static std::mutex s_singleton_instances_mutex_; }; -QMap NoBigComboBoxStyle::s_singleton_instances_ = {}; +QHash NoBigComboBoxStyle::s_singleton_instances_ = {}; std::mutex NoBigComboBoxStyle::s_singleton_instances_mutex_; -/** - * QProxyStyle and QStyle objects object to being freed even if all the widgets using them are gone - * so make singlestons tied to the lifetime of the application to clean them up and ensure they arn't - * being remade over and over agian leaking memory. - * */ -NoBigComboBoxStyle* NoBigComboBoxStyle::GetInstance(QStyle* style) -{ - std::lock_guard lock(s_singleton_instances_mutex_); - auto inst_iter = s_singleton_instances_.constFind(style); - NoBigComboBoxStyle* inst = nullptr; - if(inst_iter == s_singleton_instances_.constEnd() || *inst_iter == nullptr) { - inst = new NoBigComboBoxStyle(style); - inst->setParent(APPLICATION); - s_singleton_instances_.insert(style, inst); - qDebug() << "QProxyStyle NoBigComboBox created for" << style->objectName() << style; - } else { - inst = *inst_iter; - } - return inst; -} ManagedPackPage* ManagedPackPage::createPage(BaseInstance* inst, QString type, QWidget* parent) { @@ -93,7 +95,7 @@ ManagedPackPage::ManagedPackPage(BaseInstance* inst, InstanceWindow* instance_wi // NOTE: GTK2 themes crash with the proxy style. // This seems like an upstream bug, so there's not much else that can be done. if (!QStyleFactory::keys().contains("gtk2")){ - auto comboStyle = NoBigComboBoxStyle::GetInstance(ui->versionsComboBox->style()); + auto comboStyle = NoBigComboBoxStyle::getInstance(ui->versionsComboBox->style()); ui->versionsComboBox->setStyle(comboStyle); } From 7af116fb006e2eb62429740bd0abbe14f50ff244 Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Sun, 28 May 2023 05:06:28 -0700 Subject: [PATCH 185/185] refactor: function scope statics Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- launcher/ui/pages/instance/ManagedPackPage.cpp | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/launcher/ui/pages/instance/ManagedPackPage.cpp b/launcher/ui/pages/instance/ManagedPackPage.cpp index a708377c..d0701a7a 100644 --- a/launcher/ui/pages/instance/ManagedPackPage.cpp +++ b/launcher/ui/pages/instance/ManagedPackPage.cpp @@ -50,6 +50,9 @@ class NoBigComboBoxStyle : public QProxyStyle { public: static NoBigComboBoxStyle* getInstance(QStyle* style) { + static QHash s_singleton_instances_ = {}; + static std::mutex s_singleton_instances_mutex_; + std::lock_guard lock(s_singleton_instances_mutex_); auto inst_iter = s_singleton_instances_.constFind(style); NoBigComboBoxStyle* inst = nullptr; @@ -67,14 +70,8 @@ class NoBigComboBoxStyle : public QProxyStyle { private: NoBigComboBoxStyle(QStyle* style) : QProxyStyle(style) {} - static QHash s_singleton_instances_; - static std::mutex s_singleton_instances_mutex_; }; -QHash NoBigComboBoxStyle::s_singleton_instances_ = {}; -std::mutex NoBigComboBoxStyle::s_singleton_instances_mutex_; - - ManagedPackPage* ManagedPackPage::createPage(BaseInstance* inst, QString type, QWidget* parent) { if (type == "modrinth")