From a80b820e9491624429c75a049140ebe738b6d604 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Wed, 1 Mar 2023 19:38:27 +0000 Subject: [PATCH 001/207] UI for mrpack export (broken) Signed-off-by: TheKodeToad --- launcher/CMakeLists.txt | 3 + launcher/ui/MainWindow.cpp | 16 ++++- launcher/ui/MainWindow.h | 5 +- launcher/ui/MainWindow.ui | 23 ++++++- launcher/ui/dialogs/ExportMrPackDialog.cpp | 31 +++++++++ launcher/ui/dialogs/ExportMrPackDialog.h | 38 +++++++++++ launcher/ui/dialogs/ExportMrPackDialog.ui | 77 ++++++++++++++++++++++ 7 files changed, 187 insertions(+), 6 deletions(-) create mode 100644 launcher/ui/dialogs/ExportMrPackDialog.cpp create mode 100644 launcher/ui/dialogs/ExportMrPackDialog.h create mode 100644 launcher/ui/dialogs/ExportMrPackDialog.ui diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index 202e633ca..0b27ff855 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -851,6 +851,8 @@ SET(LAUNCHER_SOURCES ui/dialogs/EditAccountDialog.h ui/dialogs/ExportInstanceDialog.cpp ui/dialogs/ExportInstanceDialog.h + ui/dialogs/ExportMrPackDialog.cpp + ui/dialogs/ExportMrPackDialog.h ui/dialogs/IconPickerDialog.cpp ui/dialogs/IconPickerDialog.h ui/dialogs/ImportResourceDialog.cpp @@ -995,6 +997,7 @@ qt_wrap_ui(LAUNCHER_UI ui/dialogs/ProfileSelectDialog.ui ui/dialogs/SkinUploadDialog.ui ui/dialogs/ExportInstanceDialog.ui + ui/dialogs/ExportMrPackDialog.ui ui/dialogs/IconPickerDialog.ui ui/dialogs/ImportResourceDialog.ui ui/dialogs/MSALoginDialog.ui diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp index 8490b2924..ff7a12c29 100644 --- a/launcher/ui/MainWindow.cpp +++ b/launcher/ui/MainWindow.cpp @@ -2,7 +2,7 @@ /* * Prism Launcher - Minecraft Launcher * Copyright (C) 2022 Sefa Eyeoglu - * Copyright (C) 2022 TheKodeToad + * 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 @@ -107,6 +107,7 @@ #include "ui/dialogs/CopyInstanceDialog.h" #include "ui/dialogs/EditAccountDialog.h" #include "ui/dialogs/ExportInstanceDialog.h" +#include "ui/dialogs/ExportMrPackDialog.h" #include "ui/dialogs/ImportResourceDialog.h" #include "ui/themes/ITheme.h" #include "ui/themes/ThemeManager.h" @@ -397,6 +398,8 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWi // removing this looks stupid view->setFocus(); + ui->actionExportInstance->setMenu(ui->exportInstanceMenu); + retranslateUi(); } @@ -1345,7 +1348,7 @@ void MainWindow::on_actionDeleteInstance_triggered() APPLICATION->instances()->deleteInstance(id); } -void MainWindow::on_actionExportInstance_triggered() +void MainWindow::on_actionExportInstanceZip_triggered() { if (m_selectedInstance) { @@ -1354,6 +1357,15 @@ void MainWindow::on_actionExportInstance_triggered() } } +void MainWindow::on_actionExportInstanceMrPack_triggered() +{ + if (m_selectedInstance) + { + ExportMrPackDialog dlg(m_selectedInstance, this); + dlg.exec(); + } +} + void MainWindow::on_actionRenameInstance_triggered() { if (m_selectedInstance) diff --git a/launcher/ui/MainWindow.h b/launcher/ui/MainWindow.h index 3a42c34e1..35b4792d6 100644 --- a/launcher/ui/MainWindow.h +++ b/launcher/ui/MainWindow.h @@ -2,6 +2,7 @@ /* * PolyMC - Minecraft Launcher * 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 @@ -151,7 +152,9 @@ private slots: void deleteGroup(); void undoTrashInstance(); - void on_actionExportInstance_triggered(); + inline void on_actionExportInstance_triggered() { on_actionExportInstanceZip_triggered(); } + void on_actionExportInstanceZip_triggered(); + void on_actionExportInstanceMrPack_triggered(); void on_actionRenameInstance_triggered(); diff --git a/launcher/ui/MainWindow.ui b/launcher/ui/MainWindow.ui index 2b6a10b10..2ede882d8 100644 --- a/launcher/ui/MainWindow.ui +++ b/launcher/ui/MainWindow.ui @@ -459,10 +459,27 @@ E&xport... - Export the selected instance as a zip file. + Export the selected instance to supported formats. - - Ctrl+E + + + + + + + + Prism Launcher (zip) + + + Export the instance as a ZIP. + + + + + Modrinth (mrpack) + + + Export to a Modrinth modpack. diff --git a/launcher/ui/dialogs/ExportMrPackDialog.cpp b/launcher/ui/dialogs/ExportMrPackDialog.cpp new file mode 100644 index 000000000..eb53a61f8 --- /dev/null +++ b/launcher/ui/dialogs/ExportMrPackDialog.cpp @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * 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 + * 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 "ExportMrPackDialog.h" +#include "ui_ExportMrPackDialog.h" + +ExportMrPackDialog::ExportMrPackDialog(InstancePtr instance, QWidget* parent) + : QDialog(parent), instance(instance), ui(new Ui::ExportMrPackDialog) +{ + ui->setupUi(this); +} + +ExportMrPackDialog::~ExportMrPackDialog() +{ + delete ui; +} diff --git a/launcher/ui/dialogs/ExportMrPackDialog.h b/launcher/ui/dialogs/ExportMrPackDialog.h new file mode 100644 index 000000000..78322a8f7 --- /dev/null +++ b/launcher/ui/dialogs/ExportMrPackDialog.h @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * 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 + * 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 "BaseInstance.h" + +namespace Ui { +class ExportMrPackDialog; +} + +class ExportMrPackDialog : public QDialog { + Q_OBJECT + + public: + explicit ExportMrPackDialog(InstancePtr instance, QWidget* parent = nullptr); + ~ExportMrPackDialog(); + + private: + InstancePtr instance; + Ui::ExportMrPackDialog* ui; +}; diff --git a/launcher/ui/dialogs/ExportMrPackDialog.ui b/launcher/ui/dialogs/ExportMrPackDialog.ui new file mode 100644 index 000000000..2b5539873 --- /dev/null +++ b/launcher/ui/dialogs/ExportMrPackDialog.ui @@ -0,0 +1,77 @@ + + + ExportMrPackDialog + + + + 0 + 0 + 650 + 413 + + + + Export Modrinth Pack + + + + + + Information + + + + + + Summary + + + + + + + + + + Name + + + + + + + Version + + + + + + + + + + + + + + + + Files + + + + + + + + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + From 8b897ac714f5317c6544c0fa6058495544301391 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Wed, 1 Mar 2023 20:45:07 +0000 Subject: [PATCH 002/207] Fix menu being set as central widget Signed-off-by: TheKodeToad --- launcher/ui/MainWindow.ui | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/launcher/ui/MainWindow.ui b/launcher/ui/MainWindow.ui index 2ede882d8..4a89bc100 100644 --- a/launcher/ui/MainWindow.ui +++ b/launcher/ui/MainWindow.ui @@ -150,6 +150,10 @@ + + + + @@ -462,25 +466,15 @@ Export the selected instance to supported formats. - - - - Prism Launcher (zip) - - Export the instance as a ZIP. - Modrinth (mrpack) - - Export to a Modrinth modpack. - From 0fadb5a2be5f8136a4a5f52f7e5602698b53f09e Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Wed, 1 Mar 2023 21:11:04 +0000 Subject: [PATCH 003/207] Add *basic* interaction Signed-off-by: TheKodeToad --- launcher/ui/dialogs/ExportMrPackDialog.cpp | 10 +++++ launcher/ui/dialogs/ExportMrPackDialog.h | 2 + launcher/ui/dialogs/ExportMrPackDialog.ui | 47 ++++++++++++++++++++-- 3 files changed, 55 insertions(+), 4 deletions(-) diff --git a/launcher/ui/dialogs/ExportMrPackDialog.cpp b/launcher/ui/dialogs/ExportMrPackDialog.cpp index eb53a61f8..735a6245d 100644 --- a/launcher/ui/dialogs/ExportMrPackDialog.cpp +++ b/launcher/ui/dialogs/ExportMrPackDialog.cpp @@ -23,6 +23,16 @@ ExportMrPackDialog::ExportMrPackDialog(InstancePtr instance, QWidget* parent) : QDialog(parent), instance(instance), ui(new Ui::ExportMrPackDialog) { ui->setupUi(this); + ui->name->setText(instance->name()); +} + +void ExportMrPackDialog::done(int result) { + if (result != Accepted) { + QDialog::done(result); + return; + } + QDialog::done(result); + } ExportMrPackDialog::~ExportMrPackDialog() diff --git a/launcher/ui/dialogs/ExportMrPackDialog.h b/launcher/ui/dialogs/ExportMrPackDialog.h index 78322a8f7..31ab86ff1 100644 --- a/launcher/ui/dialogs/ExportMrPackDialog.h +++ b/launcher/ui/dialogs/ExportMrPackDialog.h @@ -32,6 +32,8 @@ class ExportMrPackDialog : public QDialog { explicit ExportMrPackDialog(InstancePtr instance, QWidget* parent = nullptr); ~ExportMrPackDialog(); + void done(int result) override; + private: InstancePtr instance; Ui::ExportMrPackDialog* ui; diff --git a/launcher/ui/dialogs/ExportMrPackDialog.ui b/launcher/ui/dialogs/ExportMrPackDialog.ui index 2b5539873..37c87158a 100644 --- a/launcher/ui/dialogs/ExportMrPackDialog.ui +++ b/launcher/ui/dialogs/ExportMrPackDialog.ui @@ -28,7 +28,7 @@ - + @@ -45,10 +45,10 @@ - + - + @@ -72,6 +72,45 @@ + + name + version + summary + treeView + - + + + buttonBox + accepted() + ExportMrPackDialog + accept() + + + 324 + 390 + + + 324 + 206 + + + + + buttonBox + rejected() + ExportMrPackDialog + reject() + + + 324 + 390 + + + 324 + 206 + + + + From 46cc325f7cc9810abe6e152484a51fed92b1bb52 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Thu, 2 Mar 2023 11:25:36 +0000 Subject: [PATCH 004/207] Add file tree Signed-off-by: TheKodeToad --- launcher/CMakeLists.txt | 1 + launcher/PackIgnoreProxy.cpp | 266 +++++++++++++++++ launcher/PackIgnoreProxy.h | 71 +++++ launcher/ui/dialogs/ExportInstanceDialog.cpp | 294 +------------------ launcher/ui/dialogs/ExportInstanceDialog.h | 2 +- launcher/ui/dialogs/ExportMrPackDialog.cpp | 19 +- launcher/ui/dialogs/ExportMrPackDialog.h | 2 + launcher/ui/dialogs/ExportMrPackDialog.ui | 25 +- 8 files changed, 379 insertions(+), 301 deletions(-) create mode 100644 launcher/PackIgnoreProxy.cpp create mode 100644 launcher/PackIgnoreProxy.h diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index 0b27ff855..2664ba669 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -664,6 +664,7 @@ SET(LAUNCHER_SOURCES # FIXME: maybe find a better home for this. SkinUtils.cpp SkinUtils.h + PackIgnoreProxy.cpp # GUI - setup wizard ui/setupwizard/SetupWizard.h diff --git a/launcher/PackIgnoreProxy.cpp b/launcher/PackIgnoreProxy.cpp new file mode 100644 index 000000000..bd0a82a49 --- /dev/null +++ b/launcher/PackIgnoreProxy.cpp @@ -0,0 +1,266 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * 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 + * 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 "PackIgnoreProxy.h" + +#include +#include +#include +#include +#include "FileSystem.h" +#include "SeparatorPrefixTree.h" +#include "StringUtils.h" + +PackIgnoreProxy::PackIgnoreProxy(QString root, QObject* parent) : QSortFilterProxyModel(parent), root(root) {} +// NOTE: Sadly, we have to do sorting ourselves. +bool PackIgnoreProxy::lessThan(const QModelIndex& left, const QModelIndex& right) const +{ + QFileSystemModel* fsm = qobject_cast(sourceModel()); + if (!fsm) { + return QSortFilterProxyModel::lessThan(left, right); + } + bool asc = sortOrder() == Qt::AscendingOrder ? true : false; + + QFileInfo leftFileInfo = fsm->fileInfo(left); + QFileInfo rightFileInfo = fsm->fileInfo(right); + + if (!leftFileInfo.isDir() && rightFileInfo.isDir()) { + return !asc; + } + if (leftFileInfo.isDir() && !rightFileInfo.isDir()) { + return asc; + } + + // sort and proxy model breaks the original model... + if (sortColumn() == 0) { + return StringUtils::naturalCompare(leftFileInfo.fileName(), rightFileInfo.fileName(), Qt::CaseInsensitive) < 0; + } + if (sortColumn() == 1) { + auto leftSize = leftFileInfo.size(); + auto rightSize = rightFileInfo.size(); + if ((leftSize == rightSize) || (leftFileInfo.isDir() && rightFileInfo.isDir())) { + return StringUtils::naturalCompare(leftFileInfo.fileName(), rightFileInfo.fileName(), Qt::CaseInsensitive) < 0 ? asc : !asc; + } + return leftSize < rightSize; + } + return QSortFilterProxyModel::lessThan(left, right); +} + +Qt::ItemFlags PackIgnoreProxy::flags(const QModelIndex& index) const +{ + if (!index.isValid()) + return Qt::NoItemFlags; + + auto sourceIndex = mapToSource(index); + Qt::ItemFlags flags = sourceIndex.flags(); + if (index.column() == 0) { + flags |= Qt::ItemIsUserCheckable; + if (sourceIndex.model()->hasChildren(sourceIndex)) { + flags |= Qt::ItemIsAutoTristate; + } + } + + return flags; +} + +QVariant PackIgnoreProxy::data(const QModelIndex& index, int role) const +{ + QModelIndex sourceIndex = mapToSource(index); + + if (index.column() == 0 && role == Qt::CheckStateRole) { + QFileSystemModel* fsm = qobject_cast(sourceModel()); + auto blockedPath = relPath(fsm->filePath(sourceIndex)); + auto cover = blocked.cover(blockedPath); + if (!cover.isNull()) { + return QVariant(Qt::Unchecked); + } else if (blocked.exists(blockedPath)) { + return QVariant(Qt::PartiallyChecked); + } else { + return QVariant(Qt::Checked); + } + } + + return sourceIndex.data(role); +} + +bool PackIgnoreProxy::setData(const QModelIndex& index, const QVariant& value, int role) +{ + if (index.column() == 0 && role == Qt::CheckStateRole) { + Qt::CheckState state = static_cast(value.toInt()); + return setFilterState(index, state); + } + + QModelIndex sourceIndex = mapToSource(index); + return QSortFilterProxyModel::sourceModel()->setData(sourceIndex, value, role); +} + +QString PackIgnoreProxy::relPath(const QString& path) const +{ + QString prefix = QDir().absoluteFilePath(root); + prefix += '/'; + if (!path.startsWith(prefix)) { + return QString(); + } + return path.mid(prefix.size()); +} + +bool PackIgnoreProxy::setFilterState(QModelIndex index, Qt::CheckState state) +{ + QFileSystemModel* fsm = qobject_cast(sourceModel()); + + if (!fsm) { + return false; + } + + QModelIndex sourceIndex = mapToSource(index); + auto blockedPath = relPath(fsm->filePath(sourceIndex)); + bool changed = false; + if (state == Qt::Unchecked) { + // blocking a path + auto& node = blocked.insert(blockedPath); + // get rid of all blocked nodes below + node.clear(); + changed = true; + } else if (state == Qt::Checked || state == Qt::PartiallyChecked) { + if (!blocked.remove(blockedPath)) { + auto cover = blocked.cover(blockedPath); + qDebug() << "Blocked by cover" << cover; + // uncover + blocked.remove(cover); + // block all contents, except for any cover + QModelIndex rootIndex = fsm->index(FS::PathCombine(root, cover)); + QModelIndex doing = rootIndex; + int row = 0; + QStack todo; + while (1) { + auto node = fsm->index(row, 0, doing); + if (!node.isValid()) { + if (!todo.size()) { + break; + } else { + doing = todo.pop(); + row = 0; + continue; + } + } + auto relpath = relPath(fsm->filePath(node)); + if (blockedPath.startsWith(relpath)) // cover found? + { + // continue processing cover later + todo.push(node); + } else { + // or just block this one. + blocked.insert(relpath); + } + row++; + } + } + changed = true; + } + if (changed) { + // update the thing + emit dataChanged(index, index, { Qt::CheckStateRole }); + // update everything above index + QModelIndex up = index.parent(); + while (1) { + if (!up.isValid()) + break; + emit dataChanged(up, up, { Qt::CheckStateRole }); + up = up.parent(); + } + // and everything below the index + QModelIndex doing = index; + int row = 0; + QStack todo; + while (1) { + auto node = this->index(row, 0, doing); + if (!node.isValid()) { + if (!todo.size()) { + break; + } else { + doing = todo.pop(); + row = 0; + continue; + } + } + emit dataChanged(node, node, { Qt::CheckStateRole }); + todo.push(node); + row++; + } + // siblings and unrelated nodes are ignored + } + return true; +} + +bool PackIgnoreProxy::shouldExpand(QModelIndex index) +{ + QModelIndex sourceIndex = mapToSource(index); + QFileSystemModel* fsm = qobject_cast(sourceModel()); + if (!fsm) { + return false; + } + auto blockedPath = relPath(fsm->filePath(sourceIndex)); + auto found = blocked.find(blockedPath); + if (found) { + return !found->leaf(); + } + return false; +} + +void PackIgnoreProxy::setBlockedPaths(QStringList paths) +{ + beginResetModel(); + blocked.clear(); + blocked.insert(paths); + endResetModel(); +} + +const SeparatorPrefixTree<'/'>& PackIgnoreProxy::blockedPaths() const +{ + return blocked; +} + +bool PackIgnoreProxy::filterAcceptsColumn(int source_column, const QModelIndex& source_parent) const +{ + Q_UNUSED(source_parent) + + // adjust the columns you want to filter out here + // return false for those that will be hidden + if (source_column == 2 || source_column == 3) + return false; + + return true; +} \ No newline at end of file diff --git a/launcher/PackIgnoreProxy.h b/launcher/PackIgnoreProxy.h new file mode 100644 index 000000000..aec42b41b --- /dev/null +++ b/launcher/PackIgnoreProxy.h @@ -0,0 +1,71 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * 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 + * 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 "SeparatorPrefixTree.h" + +class PackIgnoreProxy : public QSortFilterProxyModel { + Q_OBJECT + + public: + PackIgnoreProxy(QString root, QObject* parent); + // NOTE: Sadly, we have to do sorting ourselves. + bool lessThan(const QModelIndex& left, const QModelIndex& right) const; + + virtual Qt::ItemFlags flags(const QModelIndex& index) const; + + virtual QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const; + virtual bool setData(const QModelIndex& index, const QVariant& value, int role = Qt::EditRole); + + QString relPath(const QString& path) const; + + bool setFilterState(QModelIndex index, Qt::CheckState state); + + bool shouldExpand(QModelIndex index); + + void setBlockedPaths(QStringList paths); + + const SeparatorPrefixTree<'/'>& blockedPaths() const; + + protected: + bool filterAcceptsColumn(int source_column, const QModelIndex& source_parent) const; + + private: + const QString root; + SeparatorPrefixTree<'/'> blocked; +}; \ No newline at end of file diff --git a/launcher/ui/dialogs/ExportInstanceDialog.cpp b/launcher/ui/dialogs/ExportInstanceDialog.cpp index f13e36e86..2c706b8ac 100644 --- a/launcher/ui/dialogs/ExportInstanceDialog.cpp +++ b/launcher/ui/dialogs/ExportInstanceDialog.cpp @@ -51,294 +51,15 @@ #include #include -class PackIgnoreProxy : public QSortFilterProxyModel -{ - Q_OBJECT - -public: - PackIgnoreProxy(InstancePtr instance, QObject *parent) : QSortFilterProxyModel(parent) - { - m_instance = instance; - } - // NOTE: Sadly, we have to do sorting ourselves. - bool lessThan(const QModelIndex &left, const QModelIndex &right) const - { - QFileSystemModel *fsm = qobject_cast(sourceModel()); - if (!fsm) - { - return QSortFilterProxyModel::lessThan(left, right); - } - bool asc = sortOrder() == Qt::AscendingOrder ? true : false; - - QFileInfo leftFileInfo = fsm->fileInfo(left); - QFileInfo rightFileInfo = fsm->fileInfo(right); - - if (!leftFileInfo.isDir() && rightFileInfo.isDir()) - { - return !asc; - } - if (leftFileInfo.isDir() && !rightFileInfo.isDir()) - { - return asc; - } - - // sort and proxy model breaks the original model... - if (sortColumn() == 0) - { - return StringUtils::naturalCompare(leftFileInfo.fileName(), rightFileInfo.fileName(), - Qt::CaseInsensitive) < 0; - } - if (sortColumn() == 1) - { - auto leftSize = leftFileInfo.size(); - auto rightSize = rightFileInfo.size(); - if ((leftSize == rightSize) || (leftFileInfo.isDir() && rightFileInfo.isDir())) - { - return StringUtils::naturalCompare(leftFileInfo.fileName(), - rightFileInfo.fileName(), - Qt::CaseInsensitive) < 0 - ? asc - : !asc; - } - return leftSize < rightSize; - } - return QSortFilterProxyModel::lessThan(left, right); - } - - virtual Qt::ItemFlags flags(const QModelIndex &index) const - { - if (!index.isValid()) - return Qt::NoItemFlags; - - auto sourceIndex = mapToSource(index); - Qt::ItemFlags flags = sourceIndex.flags(); - if (index.column() == 0) - { - flags |= Qt::ItemIsUserCheckable; - if (sourceIndex.model()->hasChildren(sourceIndex)) - { - flags |= Qt::ItemIsAutoTristate; - } - } - - return flags; - } - - virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const - { - QModelIndex sourceIndex = mapToSource(index); - - if (index.column() == 0 && role == Qt::CheckStateRole) - { - QFileSystemModel *fsm = qobject_cast(sourceModel()); - auto blockedPath = relPath(fsm->filePath(sourceIndex)); - auto cover = blocked.cover(blockedPath); - if (!cover.isNull()) - { - return QVariant(Qt::Unchecked); - } - else if (blocked.exists(blockedPath)) - { - return QVariant(Qt::PartiallyChecked); - } - else - { - return QVariant(Qt::Checked); - } - } - - return sourceIndex.data(role); - } - - virtual bool setData(const QModelIndex &index, const QVariant &value, - int role = Qt::EditRole) - { - if (index.column() == 0 && role == Qt::CheckStateRole) - { - Qt::CheckState state = static_cast(value.toInt()); - return setFilterState(index, state); - } - - QModelIndex sourceIndex = mapToSource(index); - return QSortFilterProxyModel::sourceModel()->setData(sourceIndex, value, role); - } - - QString relPath(const QString &path) const - { - QString prefix = QDir().absoluteFilePath(m_instance->instanceRoot()); - prefix += '/'; - if (!path.startsWith(prefix)) - { - return QString(); - } - return path.mid(prefix.size()); - } - - bool setFilterState(QModelIndex index, Qt::CheckState state) - { - QFileSystemModel *fsm = qobject_cast(sourceModel()); - - if (!fsm) - { - return false; - } - - QModelIndex sourceIndex = mapToSource(index); - auto blockedPath = relPath(fsm->filePath(sourceIndex)); - bool changed = false; - if (state == Qt::Unchecked) - { - // blocking a path - auto &node = blocked.insert(blockedPath); - // get rid of all blocked nodes below - node.clear(); - changed = true; - } - else if (state == Qt::Checked || state == Qt::PartiallyChecked) - { - if (!blocked.remove(blockedPath)) - { - auto cover = blocked.cover(blockedPath); - qDebug() << "Blocked by cover" << cover; - // uncover - blocked.remove(cover); - // block all contents, except for any cover - QModelIndex rootIndex = - fsm->index(FS::PathCombine(m_instance->instanceRoot(), cover)); - QModelIndex doing = rootIndex; - int row = 0; - QStack todo; - while (1) - { - auto node = fsm->index(row, 0, doing); - if (!node.isValid()) - { - if (!todo.size()) - { - break; - } - else - { - doing = todo.pop(); - row = 0; - continue; - } - } - auto relpath = relPath(fsm->filePath(node)); - if (blockedPath.startsWith(relpath)) // cover found? - { - // continue processing cover later - todo.push(node); - } - else - { - // or just block this one. - blocked.insert(relpath); - } - row++; - } - } - changed = true; - } - if (changed) - { - // update the thing - emit dataChanged(index, index, {Qt::CheckStateRole}); - // update everything above index - QModelIndex up = index.parent(); - while (1) - { - if (!up.isValid()) - break; - emit dataChanged(up, up, {Qt::CheckStateRole}); - up = up.parent(); - } - // and everything below the index - QModelIndex doing = index; - int row = 0; - QStack todo; - while (1) - { - auto node = this->index(row, 0, doing); - if (!node.isValid()) - { - if (!todo.size()) - { - break; - } - else - { - doing = todo.pop(); - row = 0; - continue; - } - } - emit dataChanged(node, node, {Qt::CheckStateRole}); - todo.push(node); - row++; - } - // siblings and unrelated nodes are ignored - } - return true; - } - - bool shouldExpand(QModelIndex index) - { - QModelIndex sourceIndex = mapToSource(index); - QFileSystemModel *fsm = qobject_cast(sourceModel()); - if (!fsm) - { - return false; - } - auto blockedPath = relPath(fsm->filePath(sourceIndex)); - auto found = blocked.find(blockedPath); - if(found) - { - return !found->leaf(); - } - return false; - } - - void setBlockedPaths(QStringList paths) - { - beginResetModel(); - blocked.clear(); - blocked.insert(paths); - endResetModel(); - } - - const SeparatorPrefixTree<'/'> & blockedPaths() const - { - return blocked; - } - -protected: - bool filterAcceptsColumn(int source_column, const QModelIndex &source_parent) const - { - Q_UNUSED(source_parent) - - // adjust the columns you want to filter out here - // return false for those that will be hidden - if (source_column == 2 || source_column == 3) - return false; - - return true; - } - -private: - InstancePtr m_instance; - SeparatorPrefixTree<'/'> blocked; -}; - ExportInstanceDialog::ExportInstanceDialog(InstancePtr instance, QWidget *parent) : QDialog(parent), ui(new Ui::ExportInstanceDialog), m_instance(instance) { ui->setupUi(this); auto model = new QFileSystemModel(this); - proxyModel = new PackIgnoreProxy(m_instance, this); + auto root = instance->instanceRoot(); + proxyModel = new PackIgnoreProxy(root, this); loadPackIgnore(); proxyModel->setSourceModel(model); - auto root = instance->instanceRoot(); ui->treeView->setModel(proxyModel); ui->treeView->setRootIndex(proxyModel->mapFromSource(model->index(root))); ui->treeView->sortByColumn(0, Qt::AscendingOrder); @@ -407,17 +128,6 @@ bool ExportInstanceDialog::doExport() { return false; } - if (QFile::exists(output)) - { - int ret = - QMessageBox::question(this, tr("Overwrite?"), - tr("This file already exists. Do you want to overwrite it?"), - QMessageBox::No, QMessageBox::Yes); - if (ret == QMessageBox::No) - { - return false; - } - } SaveIcon(m_instance); diff --git a/launcher/ui/dialogs/ExportInstanceDialog.h b/launcher/ui/dialogs/ExportInstanceDialog.h index dea02d1be..b1b8f9119 100644 --- a/launcher/ui/dialogs/ExportInstanceDialog.h +++ b/launcher/ui/dialogs/ExportInstanceDialog.h @@ -18,9 +18,9 @@ #include #include #include +#include "PackIgnoreProxy.h" class BaseInstance; -class PackIgnoreProxy; typedef std::shared_ptr InstancePtr; namespace Ui diff --git a/launcher/ui/dialogs/ExportMrPackDialog.cpp b/launcher/ui/dialogs/ExportMrPackDialog.cpp index 735a6245d..1a8390619 100644 --- a/launcher/ui/dialogs/ExportMrPackDialog.cpp +++ b/launcher/ui/dialogs/ExportMrPackDialog.cpp @@ -17,6 +17,7 @@ */ #include "ExportMrPackDialog.h" +#include #include "ui_ExportMrPackDialog.h" ExportMrPackDialog::ExportMrPackDialog(InstancePtr instance, QWidget* parent) @@ -24,15 +25,29 @@ ExportMrPackDialog::ExportMrPackDialog(InstancePtr instance, QWidget* parent) { ui->setupUi(this); ui->name->setText(instance->name()); + + auto model = new QFileSystemModel(this); + // use the game root - everything outside cannot be exported + QString root = instance->gameRoot(); + proxy = new PackIgnoreProxy(root, this); + proxy->setSourceModel(model); + ui->treeView->setModel(proxy); + ui->treeView->setRootIndex(proxy->mapFromSource(model->index(root))); + ui->treeView->sortByColumn(0, Qt::AscendingOrder); + model->setFilter(QDir::AllEntries | QDir::NoDotAndDotDot | QDir::AllDirs | QDir::Hidden); + model->setRootPath(root); + auto headerView = ui->treeView->header(); + headerView->setSectionResizeMode(QHeaderView::ResizeToContents); + headerView->setSectionResizeMode(0, QHeaderView::Stretch); } -void ExportMrPackDialog::done(int result) { +void ExportMrPackDialog::done(int result) +{ if (result != Accepted) { QDialog::done(result); return; } QDialog::done(result); - } ExportMrPackDialog::~ExportMrPackDialog() diff --git a/launcher/ui/dialogs/ExportMrPackDialog.h b/launcher/ui/dialogs/ExportMrPackDialog.h index 31ab86ff1..454c25eb1 100644 --- a/launcher/ui/dialogs/ExportMrPackDialog.h +++ b/launcher/ui/dialogs/ExportMrPackDialog.h @@ -20,6 +20,7 @@ #include #include "BaseInstance.h" +#include "PackIgnoreProxy.h" namespace Ui { class ExportMrPackDialog; @@ -37,4 +38,5 @@ class ExportMrPackDialog : public QDialog { private: InstancePtr instance; Ui::ExportMrPackDialog* ui; + PackIgnoreProxy* proxy; }; diff --git a/launcher/ui/dialogs/ExportMrPackDialog.ui b/launcher/ui/dialogs/ExportMrPackDialog.ui index 37c87158a..8e6d61ffd 100644 --- a/launcher/ui/dialogs/ExportMrPackDialog.ui +++ b/launcher/ui/dialogs/ExportMrPackDialog.ui @@ -15,13 +15,13 @@ - + Information - + Summary @@ -31,14 +31,14 @@ - + Name - + Version @@ -54,14 +54,27 @@ - + Files - + + + true + + + QAbstractItemView::ExtendedSelection + + + true + + + false + + From a5dd6b6cd7050b4861a50a417d939c6a49d5cc33 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Thu, 2 Mar 2023 16:40:49 +0000 Subject: [PATCH 005/207] Export without file urls Signed-off-by: TheKodeToad --- launcher/ui/dialogs/ExportInstanceDialog.cpp | 2 +- launcher/ui/dialogs/ExportMrPackDialog.cpp | 99 ++++++++++++++++++-- launcher/ui/dialogs/ExportMrPackDialog.h | 3 + 3 files changed, 93 insertions(+), 11 deletions(-) diff --git a/launcher/ui/dialogs/ExportInstanceDialog.cpp b/launcher/ui/dialogs/ExportInstanceDialog.cpp index 2c706b8ac..f310a6897 100644 --- a/launcher/ui/dialogs/ExportInstanceDialog.cpp +++ b/launcher/ui/dialogs/ExportInstanceDialog.cpp @@ -123,7 +123,7 @@ bool ExportInstanceDialog::doExport() const QString output = QFileDialog::getSaveFileName( this, tr("Export %1").arg(m_instance->name()), - FS::PathCombine(QDir::homePath(), name + ".zip"), "Zip (*.zip)", nullptr, QFileDialog::DontConfirmOverwrite); + FS::PathCombine(QDir::homePath(), name + ".zip"), "Zip (*.zip)", nullptr); if (output.isEmpty()) { return false; diff --git a/launcher/ui/dialogs/ExportMrPackDialog.cpp b/launcher/ui/dialogs/ExportMrPackDialog.cpp index 1a8390619..9015407de 100644 --- a/launcher/ui/dialogs/ExportMrPackDialog.cpp +++ b/launcher/ui/dialogs/ExportMrPackDialog.cpp @@ -17,9 +17,17 @@ */ #include "ExportMrPackDialog.h" -#include +#include +#include "FileSystem.h" +#include "minecraft/MinecraftInstance.h" +#include "minecraft/PackProfile.h" #include "ui_ExportMrPackDialog.h" +#include +#include +#include +#include "MMCZip.h" + ExportMrPackDialog::ExportMrPackDialog(InstancePtr instance, QWidget* parent) : QDialog(parent), instance(instance), ui(new Ui::ExportMrPackDialog) { @@ -41,16 +49,87 @@ ExportMrPackDialog::ExportMrPackDialog(InstancePtr instance, QWidget* parent) headerView->setSectionResizeMode(0, QHeaderView::Stretch); } -void ExportMrPackDialog::done(int result) -{ - if (result != Accepted) { - QDialog::done(result); - return; - } - QDialog::done(result); -} - ExportMrPackDialog::~ExportMrPackDialog() { delete ui; } + +void ExportMrPackDialog::done(int result) +{ + if (result == Accepted) + runExport(); + + QDialog::done(result); +} + +void ExportMrPackDialog::runExport() +{ + const QString filename = FS::RemoveInvalidFilenameChars(ui->name->text()); + const QString output = + QFileDialog::getSaveFileName(this, tr("Export %1").arg(ui->name->text()), FS::PathCombine(QDir::homePath(), filename + ".mrpack"), + "Modrinth modpack (*.mrpack *.zip)", nullptr); + + if (output.isEmpty()) + return; + + QFileInfoList files; + if (!MMCZip::collectFileListRecursively(instance->gameRoot(), nullptr, &files, + [this](const QString& path) { return proxy->blockedPaths().covers(path); })) { + QMessageBox::warning(this, tr("Unable to export modpack"), tr("Could not collect list of files")); + return; + } + + QuaZip zip(output); + if (!zip.open(QuaZip::mdCreate)) { + QFile::remove(output); + QMessageBox::warning(this, tr("Unable to export modpack"), tr("Could not create file")); + return; + } + + QuaZipFile indexFile(&zip); + indexFile.setFileName("modrinth.index.json"); + if (!indexFile.open(QuaZipFile::NewOnly)) { + QFile::remove(output); + QMessageBox::warning(this, tr("Unable to export modpack"), tr("Could not create index")); + return; + } + indexFile.write(generateIndex()); + indexFile.close(); + + // should exist + QDir dotMinecraft(instance->gameRoot()); + + for (const QFileInfo& file : files) + if (!JlCompress::compressFile(&zip, file.absoluteFilePath(), "overrides/" + dotMinecraft.relativeFilePath(file.absoluteFilePath()))) + QMessageBox::warning(this, tr("Unable to export modpack"), tr("Could not compress %1").arg(file.absoluteFilePath())); + + zip.close(); + + if (zip.getZipError() != 0) { + QFile::remove(output); + QMessageBox::warning(this, tr("Unable to export modpack"), tr("A zip error occured")); + return; + } +} + +QByteArray ExportMrPackDialog::generateIndex() +{ + QJsonObject obj; + obj["formatVersion"] = 1; + obj["game"] = "minecraft"; + obj["name"] = ui->name->text(); + obj["versionId"] = ui->version->text(); + obj["summary"] = ui->summary->text(); + + MinecraftInstance* mc = dynamic_cast(instance.get()); + if (mc) { + auto profile = mc->getPackProfile(); + auto minecraft = profile->getComponent("net.minecraft"); + + QJsonObject dependencies; + dependencies["minecraft"] = minecraft->m_version; + obj["dependencies"] = dependencies; + } + + return QJsonDocument(obj).toJson(); +} \ No newline at end of file diff --git a/launcher/ui/dialogs/ExportMrPackDialog.h b/launcher/ui/dialogs/ExportMrPackDialog.h index 454c25eb1..0cf4eb7fd 100644 --- a/launcher/ui/dialogs/ExportMrPackDialog.h +++ b/launcher/ui/dialogs/ExportMrPackDialog.h @@ -39,4 +39,7 @@ class ExportMrPackDialog : public QDialog { InstancePtr instance; Ui::ExportMrPackDialog* ui; PackIgnoreProxy* proxy; + + void runExport(); + QByteArray generateIndex(); }; From 9ec32b2561eac4ae50db88467d9afae57dd78614 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Thu, 2 Mar 2023 17:36:28 +0000 Subject: [PATCH 006/207] Fix QuaZipFile usage Signed-off-by: TheKodeToad --- launcher/ui/dialogs/ExportMrPackDialog.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/launcher/ui/dialogs/ExportMrPackDialog.cpp b/launcher/ui/dialogs/ExportMrPackDialog.cpp index 9015407de..96c7a2015 100644 --- a/launcher/ui/dialogs/ExportMrPackDialog.cpp +++ b/launcher/ui/dialogs/ExportMrPackDialog.cpp @@ -86,15 +86,15 @@ void ExportMrPackDialog::runExport() return; } - QuaZipFile indexFile(&zip); - indexFile.setFileName("modrinth.index.json"); - if (!indexFile.open(QuaZipFile::NewOnly)) { - QFile::remove(output); - QMessageBox::warning(this, tr("Unable to export modpack"), tr("Could not create index")); - return; + { + QuaZipFile indexFile(&zip); + if (!indexFile.open(QIODevice::WriteOnly, QuaZipNewInfo("modrinth.index.json"))) { + QFile::remove(output); + QMessageBox::warning(this, tr("Unable to export modpack"), tr("Could not create index")); + return; + } + indexFile.write(generateIndex()); } - indexFile.write(generateIndex()); - indexFile.close(); // should exist QDir dotMinecraft(instance->gameRoot()); From 88ef02474f9c696318eb1c9e0213a7900f60f67e Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Thu, 2 Mar 2023 17:36:39 +0000 Subject: [PATCH 007/207] Minify index JSON Signed-off-by: TheKodeToad --- launcher/ui/dialogs/ExportMrPackDialog.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/ui/dialogs/ExportMrPackDialog.cpp b/launcher/ui/dialogs/ExportMrPackDialog.cpp index 96c7a2015..14518783f 100644 --- a/launcher/ui/dialogs/ExportMrPackDialog.cpp +++ b/launcher/ui/dialogs/ExportMrPackDialog.cpp @@ -131,5 +131,5 @@ QByteArray ExportMrPackDialog::generateIndex() obj["dependencies"] = dependencies; } - return QJsonDocument(obj).toJson(); + return QJsonDocument(obj).toJson(QJsonDocument::Compact); } \ No newline at end of file From 6505b0c065b32e13f392061ccb184d288329a058 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Thu, 2 Mar 2023 19:48:41 +0000 Subject: [PATCH 008/207] Move logic to task Signed-off-by: TheKodeToad --- launcher/CMakeLists.txt | 2 + .../modrinth/ModrinthPackExportTask.cpp | 110 ++++++++++++++++++ .../modrinth/ModrinthPackExportTask.h | 44 +++++++ launcher/ui/dialogs/ExportMrPackDialog.cpp | 72 ++---------- launcher/ui/dialogs/ExportMrPackDialog.h | 3 +- 5 files changed, 166 insertions(+), 65 deletions(-) create mode 100644 launcher/modplatform/modrinth/ModrinthPackExportTask.cpp create mode 100644 launcher/modplatform/modrinth/ModrinthPackExportTask.h diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index 2664ba669..f3af2ebf1 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -522,6 +522,8 @@ set(MODRINTH_SOURCES modplatform/modrinth/ModrinthCheckUpdate.h modplatform/modrinth/ModrinthInstanceCreationTask.cpp modplatform/modrinth/ModrinthInstanceCreationTask.h + modplatform/modrinth/ModrinthPackExportTask.cpp + modplatform/modrinth/ModrinthPackExportTask.h ) set(MODPACKSCH_SOURCES diff --git a/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp b/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp new file mode 100644 index 000000000..029b47a52 --- /dev/null +++ b/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp @@ -0,0 +1,110 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * 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 + * 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 "ModrinthPackExportTask.h" +#include +#include +#include "MMCZip.h" +#include "minecraft/MinecraftInstance.h" +#include "minecraft/PackProfile.h" + +ModrinthPackExportTask::ModrinthPackExportTask(const QString& name, + const QString& version, + const QString& summary, + InstancePtr instance, + const QString& output, + MMCZip::FilterFunction filter) + : name(name), version(version), summary(summary), instance(instance), output(output), filter(filter) +{} + +void ModrinthPackExportTask::executeTask() +{ + QFileInfoList files; + if (!MMCZip::collectFileListRecursively(instance->gameRoot(), nullptr, &files, filter)) { + emitFailed(tr("Could not collect list of files")); + return; + } + + setStatus("Adding files..."); + + QuaZip zip(output); + if (!zip.open(QuaZip::mdCreate)) { + QFile::remove(output); + emitFailed(tr("Could not create file")); + return; + } + + { + QuaZipFile indexFile(&zip); + if (!indexFile.open(QIODevice::WriteOnly, QuaZipNewInfo("modrinth.index.json"))) { + QFile::remove(output); + + emitFailed(tr("Could not create index")); + return; + } + indexFile.write(generateIndex()); + } + + // should exist + QDir dotMinecraft(instance->gameRoot()); + + { + size_t i = 0; + for (const QFileInfo& file : files) { + setProgress(i, files.length()); + if (!JlCompress::compressFile(&zip, file.absoluteFilePath(), + "overrides/" + dotMinecraft.relativeFilePath(file.absoluteFilePath()))) { + emitFailed(tr("Could not compress %1").arg(file.absoluteFilePath())); + return; + } + i++; + } + } + + zip.close(); + + if (zip.getZipError() != 0) { + QFile::remove(output); + emitFailed(tr("A zip error occured")); + return; + } + + emitSucceeded(); +} + +QByteArray ModrinthPackExportTask::generateIndex() +{ + QJsonObject obj; + obj["formatVersion"] = 1; + obj["game"] = "minecraft"; + obj["name"] = name; + obj["versionId"] = version; + obj["summary"] = summary; + + MinecraftInstance* mc = dynamic_cast(instance.get()); + if (mc) { + auto profile = mc->getPackProfile(); + auto minecraft = profile->getComponent("net.minecraft"); + + QJsonObject dependencies; + dependencies["minecraft"] = minecraft->m_version; + obj["dependencies"] = dependencies; + } + + return QJsonDocument(obj).toJson(QJsonDocument::Compact); +} \ No newline at end of file diff --git a/launcher/modplatform/modrinth/ModrinthPackExportTask.h b/launcher/modplatform/modrinth/ModrinthPackExportTask.h new file mode 100644 index 000000000..c38be204b --- /dev/null +++ b/launcher/modplatform/modrinth/ModrinthPackExportTask.h @@ -0,0 +1,44 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * 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 + * 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 "BaseInstance.h" +#include "MMCZip.h" +#include "tasks/Task.h" + +class ModrinthPackExportTask : public Task { + public: + ModrinthPackExportTask(const QString& name, + const QString& version, + const QString& summary, + InstancePtr instance, + const QString& output, + MMCZip::FilterFunction filter); + + protected: + void executeTask() override; + + private: + const QString name, version, summary; + const InstancePtr instance; + const QString output; + const MMCZip::FilterFunction filter; + + QByteArray generateIndex(); +}; \ No newline at end of file diff --git a/launcher/ui/dialogs/ExportMrPackDialog.cpp b/launcher/ui/dialogs/ExportMrPackDialog.cpp index 14518783f..4c2e5593c 100644 --- a/launcher/ui/dialogs/ExportMrPackDialog.cpp +++ b/launcher/ui/dialogs/ExportMrPackDialog.cpp @@ -17,16 +17,16 @@ */ #include "ExportMrPackDialog.h" -#include -#include "FileSystem.h" -#include "minecraft/MinecraftInstance.h" -#include "minecraft/PackProfile.h" +#include "ui/dialogs/ProgressDialog.h" #include "ui_ExportMrPackDialog.h" #include #include +#include #include +#include "FileSystem.h" #include "MMCZip.h" +#include "modplatform/modrinth/ModrinthPackExportTask.h" ExportMrPackDialog::ExportMrPackDialog(InstancePtr instance, QWidget* parent) : QDialog(parent), instance(instance), ui(new Ui::ExportMrPackDialog) @@ -72,64 +72,10 @@ void ExportMrPackDialog::runExport() if (output.isEmpty()) return; - QFileInfoList files; - if (!MMCZip::collectFileListRecursively(instance->gameRoot(), nullptr, &files, - [this](const QString& path) { return proxy->blockedPaths().covers(path); })) { - QMessageBox::warning(this, tr("Unable to export modpack"), tr("Could not collect list of files")); - return; - } + ModrinthPackExportTask task(ui->name->text(), ui->version->text(), ui->summary->text(), instance, output, + [this](const QString& path) { return proxy->blockedPaths().covers(path); }); - QuaZip zip(output); - if (!zip.open(QuaZip::mdCreate)) { - QFile::remove(output); - QMessageBox::warning(this, tr("Unable to export modpack"), tr("Could not create file")); - return; - } - - { - QuaZipFile indexFile(&zip); - if (!indexFile.open(QIODevice::WriteOnly, QuaZipNewInfo("modrinth.index.json"))) { - QFile::remove(output); - QMessageBox::warning(this, tr("Unable to export modpack"), tr("Could not create index")); - return; - } - indexFile.write(generateIndex()); - } - - // should exist - QDir dotMinecraft(instance->gameRoot()); - - for (const QFileInfo& file : files) - if (!JlCompress::compressFile(&zip, file.absoluteFilePath(), "overrides/" + dotMinecraft.relativeFilePath(file.absoluteFilePath()))) - QMessageBox::warning(this, tr("Unable to export modpack"), tr("Could not compress %1").arg(file.absoluteFilePath())); - - zip.close(); - - if (zip.getZipError() != 0) { - QFile::remove(output); - QMessageBox::warning(this, tr("Unable to export modpack"), tr("A zip error occured")); - return; - } -} - -QByteArray ExportMrPackDialog::generateIndex() -{ - QJsonObject obj; - obj["formatVersion"] = 1; - obj["game"] = "minecraft"; - obj["name"] = ui->name->text(); - obj["versionId"] = ui->version->text(); - obj["summary"] = ui->summary->text(); - - MinecraftInstance* mc = dynamic_cast(instance.get()); - if (mc) { - auto profile = mc->getPackProfile(); - auto minecraft = profile->getComponent("net.minecraft"); - - QJsonObject dependencies; - dependencies["minecraft"] = minecraft->m_version; - obj["dependencies"] = dependencies; - } - - return QJsonDocument(obj).toJson(QJsonDocument::Compact); + ProgressDialog progress(this); + progress.setSkipButton(true, tr("Abort")); + progress.execWithTask(&task); } \ No newline at end of file diff --git a/launcher/ui/dialogs/ExportMrPackDialog.h b/launcher/ui/dialogs/ExportMrPackDialog.h index 0cf4eb7fd..89263fc69 100644 --- a/launcher/ui/dialogs/ExportMrPackDialog.h +++ b/launcher/ui/dialogs/ExportMrPackDialog.h @@ -36,10 +36,9 @@ class ExportMrPackDialog : public QDialog { void done(int result) override; private: - InstancePtr instance; + const InstancePtr instance; Ui::ExportMrPackDialog* ui; PackIgnoreProxy* proxy; void runExport(); - QByteArray generateIndex(); }; From f28a7b9d083a9181c04c7100f50152c116e72cb6 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Thu, 2 Mar 2023 19:50:42 +0000 Subject: [PATCH 009/207] Add PackIgnoreProxy.h to cmake Signed-off-by: TheKodeToad --- launcher/CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index f3af2ebf1..380e83366 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -667,6 +667,7 @@ SET(LAUNCHER_SOURCES SkinUtils.cpp SkinUtils.h PackIgnoreProxy.cpp + PackIgnoreProxy.h # GUI - setup wizard ui/setupwizard/SetupWizard.h From adcdf28d64abbe16304c2d377488af1898f9b2af Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Fri, 3 Mar 2023 11:14:57 +0000 Subject: [PATCH 010/207] Move task to another thread I don't know whether this is the prefered method. Signed-off-by: TheKodeToad --- .../modrinth/ModrinthPackExportTask.cpp | 85 ++++++++++--------- launcher/ui/dialogs/ExportMrPackDialog.cpp | 36 ++++---- launcher/ui/dialogs/ExportMrPackDialog.h | 2 - 3 files changed, 60 insertions(+), 63 deletions(-) diff --git a/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp b/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp index 029b47a52..331fbf94d 100644 --- a/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp +++ b/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp @@ -17,6 +17,7 @@ */ #include "ModrinthPackExportTask.h" +#include #include #include #include "MMCZip.h" @@ -34,57 +35,59 @@ ModrinthPackExportTask::ModrinthPackExportTask(const QString& name, void ModrinthPackExportTask::executeTask() { - QFileInfoList files; - if (!MMCZip::collectFileListRecursively(instance->gameRoot(), nullptr, &files, filter)) { - emitFailed(tr("Could not collect list of files")); - return; - } - - setStatus("Adding files..."); - - QuaZip zip(output); - if (!zip.open(QuaZip::mdCreate)) { - QFile::remove(output); - emitFailed(tr("Could not create file")); - return; - } - - { - QuaZipFile indexFile(&zip); - if (!indexFile.open(QIODevice::WriteOnly, QuaZipNewInfo("modrinth.index.json"))) { - QFile::remove(output); - - emitFailed(tr("Could not create index")); + QtConcurrent::run(QThreadPool::globalInstance(), [this] { + QFileInfoList files; + if (!MMCZip::collectFileListRecursively(instance->gameRoot(), nullptr, &files, filter)) { + emitFailed(tr("Could not collect list of files")); return; } - indexFile.write(generateIndex()); - } - // should exist - QDir dotMinecraft(instance->gameRoot()); + setStatus("Adding files..."); - { - size_t i = 0; - for (const QFileInfo& file : files) { - setProgress(i, files.length()); - if (!JlCompress::compressFile(&zip, file.absoluteFilePath(), - "overrides/" + dotMinecraft.relativeFilePath(file.absoluteFilePath()))) { - emitFailed(tr("Could not compress %1").arg(file.absoluteFilePath())); + QuaZip zip(output); + if (!zip.open(QuaZip::mdCreate)) { + QFile::remove(output); + emitFailed(tr("Could not create file")); + return; + } + + { + QuaZipFile indexFile(&zip); + if (!indexFile.open(QIODevice::WriteOnly, QuaZipNewInfo("modrinth.index.json"))) { + QFile::remove(output); + + emitFailed(tr("Could not create index")); return; } - i++; + indexFile.write(generateIndex()); } - } - zip.close(); + // should exist + QDir dotMinecraft(instance->gameRoot()); - if (zip.getZipError() != 0) { - QFile::remove(output); - emitFailed(tr("A zip error occured")); - return; - } + { + size_t i = 0; + for (const QFileInfo& file : files) { + setProgress(i, files.length()); + if (!JlCompress::compressFile(&zip, file.absoluteFilePath(), + "overrides/" + dotMinecraft.relativeFilePath(file.absoluteFilePath()))) { + emitFailed(tr("Could not compress %1").arg(file.absoluteFilePath())); + return; + } + i++; + } + } - emitSucceeded(); + zip.close(); + + if (zip.getZipError() != 0) { + QFile::remove(output); + emitFailed(tr("A zip error occured")); + return; + } + + emitSucceeded(); + }); } QByteArray ModrinthPackExportTask::generateIndex() diff --git a/launcher/ui/dialogs/ExportMrPackDialog.cpp b/launcher/ui/dialogs/ExportMrPackDialog.cpp index 4c2e5593c..81663c9aa 100644 --- a/launcher/ui/dialogs/ExportMrPackDialog.cpp +++ b/launcher/ui/dialogs/ExportMrPackDialog.cpp @@ -56,26 +56,22 @@ ExportMrPackDialog::~ExportMrPackDialog() void ExportMrPackDialog::done(int result) { - if (result == Accepted) - runExport(); + if (result == Accepted) { + const QString filename = FS::RemoveInvalidFilenameChars(ui->name->text()); + const QString output = + QFileDialog::getSaveFileName(this, tr("Export %1").arg(ui->name->text()), FS::PathCombine(QDir::homePath(), filename + ".mrpack"), + "Modrinth modpack (*.mrpack *.zip)", nullptr); + + if (output.isEmpty()) + return; + + ModrinthPackExportTask task(ui->name->text(), ui->version->text(), ui->summary->text(), instance, output, + [this](const QString& path) { return proxy->blockedPaths().covers(path); }); + + ProgressDialog progress(this); + progress.setSkipButton(true, tr("Abort")); + progress.execWithTask(&task); + } QDialog::done(result); -} - -void ExportMrPackDialog::runExport() -{ - const QString filename = FS::RemoveInvalidFilenameChars(ui->name->text()); - const QString output = - QFileDialog::getSaveFileName(this, tr("Export %1").arg(ui->name->text()), FS::PathCombine(QDir::homePath(), filename + ".mrpack"), - "Modrinth modpack (*.mrpack *.zip)", nullptr); - - if (output.isEmpty()) - return; - - ModrinthPackExportTask task(ui->name->text(), ui->version->text(), ui->summary->text(), instance, output, - [this](const QString& path) { return proxy->blockedPaths().covers(path); }); - - ProgressDialog progress(this); - progress.setSkipButton(true, tr("Abort")); - progress.execWithTask(&task); } \ No newline at end of file diff --git a/launcher/ui/dialogs/ExportMrPackDialog.h b/launcher/ui/dialogs/ExportMrPackDialog.h index 89263fc69..3ded48872 100644 --- a/launcher/ui/dialogs/ExportMrPackDialog.h +++ b/launcher/ui/dialogs/ExportMrPackDialog.h @@ -39,6 +39,4 @@ class ExportMrPackDialog : public QDialog { const InstancePtr instance; Ui::ExportMrPackDialog* ui; PackIgnoreProxy* proxy; - - void runExport(); }; From dcaa907fede11c8f0aeddde8a78e8d9397eaee2f Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Fri, 3 Mar 2023 11:24:10 +0000 Subject: [PATCH 011/207] Mod loader support Signed-off-by: TheKodeToad --- .../modrinth/ModrinthPackExportTask.cpp | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp b/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp index 331fbf94d..1bb78caed 100644 --- a/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp +++ b/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp @@ -102,10 +102,22 @@ QByteArray ModrinthPackExportTask::generateIndex() MinecraftInstance* mc = dynamic_cast(instance.get()); if (mc) { auto profile = mc->getPackProfile(); + // collect all supported components auto minecraft = profile->getComponent("net.minecraft"); + auto quilt = profile->getComponent("org.quiltmc.quilt-loader"); + auto fabric = profile->getComponent("net.fabricmc.fabric-loader"); + auto forge = profile->getComponent("net.minecraftforge"); + // convert all available components to mrpack dependencies QJsonObject dependencies; - dependencies["minecraft"] = minecraft->m_version; + if (minecraft != nullptr) + dependencies["minecraft"] = minecraft->m_version; + if (quilt != nullptr) + dependencies["quilt-loader"] = quilt->m_version; + if (fabric != nullptr) + dependencies["fabric-loader"] = fabric->m_version; + if (forge != nullptr) + dependencies["forge"] = forge->m_version; obj["dependencies"] = dependencies; } From 2343aad088e302bf9f4f75eb6c4d6d1da3c7fbe3 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Fri, 3 Mar 2023 15:00:07 +0000 Subject: [PATCH 012/207] Make it work! (TODO make it not crash) Signed-off-by: TheKodeToad --- .../modrinth/ModrinthPackExportTask.cpp | 119 +++++++++++++++--- .../modrinth/ModrinthPackExportTask.h | 7 +- 2 files changed, 108 insertions(+), 18 deletions(-) diff --git a/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp b/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp index 1bb78caed..a319eec47 100644 --- a/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp +++ b/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp @@ -17,12 +17,17 @@ */ #include "ModrinthPackExportTask.h" -#include + +#include +#include #include #include +#include +#include "Json.h" #include "MMCZip.h" #include "minecraft/MinecraftInstance.h" #include "minecraft/PackProfile.h" +#include "modplatform/modrinth/ModrinthAPI.h" ModrinthPackExportTask::ModrinthPackExportTask(const QString& name, const QString& version, @@ -35,13 +40,76 @@ ModrinthPackExportTask::ModrinthPackExportTask(const QString& name, void ModrinthPackExportTask::executeTask() { - QtConcurrent::run(QThreadPool::globalInstance(), [this] { - QFileInfoList files; - if (!MMCZip::collectFileListRecursively(instance->gameRoot(), nullptr, &files, filter)) { - emitFailed(tr("Could not collect list of files")); - return; + setStatus(tr("Searching for files...")); + + QFileInfoList files; + if (!MMCZip::collectFileListRecursively(instance->gameRoot(), nullptr, &files, filter)) { + emitFailed(tr("Could not collect list of files")); + return; + } + + QDir mc(instance->gameRoot()); + + ModrinthAPI api; + + static const QStringList prefixes({ "mods", "coremods", "resourcepacks", "texturepacks", "shaderpacks" }); + // hash -> file + QMap hashes; + + for (QFileInfo file : files) { + QString relative = mc.relativeFilePath(file.absoluteFilePath()); + // require sensible file types + if (!(relative.endsWith(".zip") || relative.endsWith(".jar") || relative.endsWith(".litemod"))) + continue; + + if (!std::any_of(prefixes.begin(), prefixes.end(), + [&relative](const QString& prefix) { return relative.startsWith(prefix + QDir::separator()); })) + continue; + + QCryptographicHash hash(QCryptographicHash::Algorithm::Sha512); + + QFile openFile(file.absoluteFilePath()); + if (!openFile.open(QFile::ReadOnly)) { + qWarning() << "Could not open" << file << "for hashing"; + continue; } + if (!hash.addData(&openFile)) { + qWarning() << "Could not add hash data for" << file; + continue; + } + + hashes[hash.result().toHex()] = relative; + } + + QByteArray* response = new QByteArray; + Task::Ptr versionsTask = api.currentVersions(hashes.keys(), "sha512", response); + connect(versionsTask.get(), &NetJob::succeeded, this, [this, mc, files, hashes, response, versionsTask]() { + // file -> url + QMap resolved; + + try { + QJsonDocument doc = Json::requireDocument(*response); + for (auto iter = hashes.keyBegin(); iter != hashes.keyEnd(); iter++) { + QJsonObject obj = doc[*iter].toObject(); + if (obj.isEmpty()) + continue; + + QJsonArray files = obj["files"].toArray(); + if (auto fileIter = std::find_if(files.begin(), files.end(), + [&iter](const QJsonValue& file) { return file["hashes"]["sha512"] == *iter; }); + fileIter != files.end()) { + // map the file to the url + resolved[hashes[*iter]] = ResolvedFile{ fileIter->toObject()["hashes"].toObject()["sha1"].toString(), *iter, + fileIter->toObject()["url"].toString(), fileIter->toObject()["size"].toInt() }; + } + } + } catch (Json::JsonException& e) { + qWarning() << "Failed to parse versions response" << e.what(); + } + + delete response; + setStatus("Adding files..."); QuaZip zip(output); @@ -55,25 +123,19 @@ void ModrinthPackExportTask::executeTask() QuaZipFile indexFile(&zip); if (!indexFile.open(QIODevice::WriteOnly, QuaZipNewInfo("modrinth.index.json"))) { QFile::remove(output); - emitFailed(tr("Could not create index")); return; } - indexFile.write(generateIndex()); + indexFile.write(generateIndex(resolved)); } - // should exist - QDir dotMinecraft(instance->gameRoot()); - { size_t i = 0; for (const QFileInfo& file : files) { setProgress(i, files.length()); - if (!JlCompress::compressFile(&zip, file.absoluteFilePath(), - "overrides/" + dotMinecraft.relativeFilePath(file.absoluteFilePath()))) { - emitFailed(tr("Could not compress %1").arg(file.absoluteFilePath())); - return; - } + QString relative = mc.relativeFilePath(file.absoluteFilePath()); + if (!resolved.contains(relative) && !JlCompress::compressFile(&zip, file.absoluteFilePath(), "overrides/" + relative)) + qWarning() << "Could not compress" << file; i++; } } @@ -88,9 +150,11 @@ void ModrinthPackExportTask::executeTask() emitSucceeded(); }); + connect(versionsTask.get(), &NetJob::failed, this, [this](const QString& reason) { emitFailed(reason); }); + versionsTask->start(); } -QByteArray ModrinthPackExportTask::generateIndex() +QByteArray ModrinthPackExportTask::generateIndex(const QMap& urls) { QJsonObject obj; obj["formatVersion"] = 1; @@ -121,5 +185,26 @@ QByteArray ModrinthPackExportTask::generateIndex() obj["dependencies"] = dependencies; } + QJsonArray files; + QMapIterator iterator(urls); + while (iterator.hasNext()) { + iterator.next(); + + const ResolvedFile& value = iterator.value(); + + QJsonObject file; + file["path"] = iterator.key(); + file["downloads"] = QJsonArray({ iterator.value().url }); + + QJsonObject hashes; + hashes["sha1"] = value.sha1; + hashes["sha512"] = value.sha512; + file["hashes"] = hashes; + file["fileSize"] = value.size; + + files << file; + } + obj["files"] = files; + return QJsonDocument(obj).toJson(QJsonDocument::Compact); } \ No newline at end of file diff --git a/launcher/modplatform/modrinth/ModrinthPackExportTask.h b/launcher/modplatform/modrinth/ModrinthPackExportTask.h index c38be204b..974c9f0ed 100644 --- a/launcher/modplatform/modrinth/ModrinthPackExportTask.h +++ b/launcher/modplatform/modrinth/ModrinthPackExportTask.h @@ -40,5 +40,10 @@ class ModrinthPackExportTask : public Task { const QString output; const MMCZip::FilterFunction filter; - QByteArray generateIndex(); + struct ResolvedFile { + QString sha1, sha512, url; + int size; + }; + + QByteArray generateIndex(const QMap& urls); }; \ No newline at end of file From 55f928f845b56d09a124f1ba85e196c2b91d856d Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Fri, 3 Mar 2023 15:06:29 +0000 Subject: [PATCH 013/207] More consistent naming Signed-off-by: TheKodeToad --- launcher/ui/dialogs/ExportMrPackDialog.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/ui/dialogs/ExportMrPackDialog.cpp b/launcher/ui/dialogs/ExportMrPackDialog.cpp index 81663c9aa..266479b32 100644 --- a/launcher/ui/dialogs/ExportMrPackDialog.cpp +++ b/launcher/ui/dialogs/ExportMrPackDialog.cpp @@ -60,7 +60,7 @@ void ExportMrPackDialog::done(int result) const QString filename = FS::RemoveInvalidFilenameChars(ui->name->text()); const QString output = QFileDialog::getSaveFileName(this, tr("Export %1").arg(ui->name->text()), FS::PathCombine(QDir::homePath(), filename + ".mrpack"), - "Modrinth modpack (*.mrpack *.zip)", nullptr); + "Modrinth pack (*.mrpack *.zip)", nullptr); if (output.isEmpty()) return; From a2716f5cf6d72168ab5aa4415c046505e42404f1 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Sat, 4 Mar 2023 10:24:25 +0000 Subject: [PATCH 014/207] Improve code Even more broken now (it is stuck loading forever)! Signed-off-by: TheKodeToad --- .../modrinth/ModrinthPackExportTask.cpp | 173 +++++++++--------- .../modrinth/ModrinthPackExportTask.h | 14 +- 2 files changed, 103 insertions(+), 84 deletions(-) diff --git a/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp b/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp index a319eec47..d7a43c7a4 100644 --- a/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp +++ b/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp @@ -29,6 +29,8 @@ #include "minecraft/PackProfile.h" #include "modplatform/modrinth/ModrinthAPI.h" +const QStringList ModrinthPackExportTask::PREFIXES = QStringList({ "mods", "coremods", "resourcepacks", "texturepacks", "shaderpacks" }); + ModrinthPackExportTask::ModrinthPackExportTask(const QString& name, const QString& version, const QString& summary, @@ -41,28 +43,34 @@ ModrinthPackExportTask::ModrinthPackExportTask(const QString& name, void ModrinthPackExportTask::executeTask() { setStatus(tr("Searching for files...")); + setProgress(0, 0); + collectFiles(); - QFileInfoList files; + QByteArray* response = new QByteArray; + Task::Ptr versionsTask = api.currentVersions(fileHashes.values(), "sha512", response); + connect(versionsTask.get(), &NetJob::succeeded, [this, response]() { parseApiResponse(response); }); + connect(versionsTask.get(), &NetJob::failed, this, &ModrinthPackExportTask::emitFailed); + versionsTask->start(); +} + +void ModrinthPackExportTask::collectFiles() +{ + files.clear(); if (!MMCZip::collectFileListRecursively(instance->gameRoot(), nullptr, &files, filter)) { emitFailed(tr("Could not collect list of files")); return; } + fileHashes.clear(); + QDir mc(instance->gameRoot()); - - ModrinthAPI api; - - static const QStringList prefixes({ "mods", "coremods", "resourcepacks", "texturepacks", "shaderpacks" }); - // hash -> file - QMap hashes; - for (QFileInfo file : files) { QString relative = mc.relativeFilePath(file.absoluteFilePath()); // require sensible file types if (!(relative.endsWith(".zip") || relative.endsWith(".jar") || relative.endsWith(".litemod"))) continue; - if (!std::any_of(prefixes.begin(), prefixes.end(), + if (!std::any_of(PREFIXES.begin(), PREFIXES.end(), [&relative](const QString& prefix) { return relative.startsWith(prefix + QDir::separator()); })) continue; @@ -79,82 +87,81 @@ void ModrinthPackExportTask::executeTask() continue; } - hashes[hash.result().toHex()] = relative; + fileHashes[relative] = hash.result().toHex(); } - - QByteArray* response = new QByteArray; - Task::Ptr versionsTask = api.currentVersions(hashes.keys(), "sha512", response); - connect(versionsTask.get(), &NetJob::succeeded, this, [this, mc, files, hashes, response, versionsTask]() { - // file -> url - QMap resolved; - - try { - QJsonDocument doc = Json::requireDocument(*response); - for (auto iter = hashes.keyBegin(); iter != hashes.keyEnd(); iter++) { - QJsonObject obj = doc[*iter].toObject(); - if (obj.isEmpty()) - continue; - - QJsonArray files = obj["files"].toArray(); - if (auto fileIter = std::find_if(files.begin(), files.end(), - [&iter](const QJsonValue& file) { return file["hashes"]["sha512"] == *iter; }); - fileIter != files.end()) { - // map the file to the url - resolved[hashes[*iter]] = ResolvedFile{ fileIter->toObject()["hashes"].toObject()["sha1"].toString(), *iter, - fileIter->toObject()["url"].toString(), fileIter->toObject()["size"].toInt() }; - } - } - } catch (Json::JsonException& e) { - qWarning() << "Failed to parse versions response" << e.what(); - } - - delete response; - - setStatus("Adding files..."); - - QuaZip zip(output); - if (!zip.open(QuaZip::mdCreate)) { - QFile::remove(output); - emitFailed(tr("Could not create file")); - return; - } - - { - QuaZipFile indexFile(&zip); - if (!indexFile.open(QIODevice::WriteOnly, QuaZipNewInfo("modrinth.index.json"))) { - QFile::remove(output); - emitFailed(tr("Could not create index")); - return; - } - indexFile.write(generateIndex(resolved)); - } - - { - size_t i = 0; - for (const QFileInfo& file : files) { - setProgress(i, files.length()); - QString relative = mc.relativeFilePath(file.absoluteFilePath()); - if (!resolved.contains(relative) && !JlCompress::compressFile(&zip, file.absoluteFilePath(), "overrides/" + relative)) - qWarning() << "Could not compress" << file; - i++; - } - } - - zip.close(); - - if (zip.getZipError() != 0) { - QFile::remove(output); - emitFailed(tr("A zip error occured")); - return; - } - - emitSucceeded(); - }); - connect(versionsTask.get(), &NetJob::failed, this, [this](const QString& reason) { emitFailed(reason); }); - versionsTask->start(); } -QByteArray ModrinthPackExportTask::generateIndex(const QMap& urls) +void ModrinthPackExportTask::parseApiResponse(QByteArray* response) +{ + QMap resolved; + + try { + QJsonDocument doc = Json::requireDocument(*response); + + QMapIterator iterator(fileHashes); + while (iterator.hasNext()) { + iterator.next(); + + QJsonObject obj = doc[iterator.value()].toObject(); + if (obj.isEmpty()) + continue; + + QJsonArray files = obj["files"].toArray(); + if (auto fileIter = std::find_if(files.begin(), files.end(), + [&iterator](const QJsonValue& file) { return file["hashes"]["sha512"] == iterator.value(); }); + fileIter != files.end()) { + // map the file to the url + resolved[iterator.key()] = ResolvedFile{ fileIter->toObject()["hashes"].toObject()["sha1"].toString(), iterator.value(), + fileIter->toObject()["url"].toString(), fileIter->toObject()["size"].toInt() }; + } + } + } catch (Json::JsonException& e) { + qWarning() << "Failed to parse versions response" << e.what(); + } + + buildZip(resolved); +} + +void ModrinthPackExportTask::buildZip(const QMap& resolvedFiles) +{ + setStatus("Adding files..."); + QuaZip zip(output); + if (!zip.open(QuaZip::mdCreate)) { + QFile::remove(output); + emitFailed(tr("Could not create file")); + return; + } + + QuaZipFile indexFile(&zip); + if (!indexFile.open(QIODevice::WriteOnly, QuaZipNewInfo("modrinth.index.json"))) { + QFile::remove(output); + emitFailed(tr("Could not create index")); + return; + } + indexFile.write(generateIndex(resolvedFiles)); + + QDir mc(instance->gameRoot()); + size_t i = 0; + for (const QFileInfo& file : files) { + setProgress(i, files.length()); + QString relative = mc.relativeFilePath(file.absoluteFilePath()); + if (!resolvedFiles.contains(relative) && !JlCompress::compressFile(&zip, file.absoluteFilePath(), "overrides/" + relative)) + qWarning() << "Could not compress" << file; + i++; + } + + zip.close(); + + if (zip.getZipError() != 0) { + QFile::remove(output); + emitFailed(tr("A zip error occured")); + return; + } + + emitSucceeded(); +} + +QByteArray ModrinthPackExportTask::generateIndex(const QMap& resolvedFiles) { QJsonObject obj; obj["formatVersion"] = 1; @@ -186,7 +193,7 @@ QByteArray ModrinthPackExportTask::generateIndex(const QMap iterator(urls); + QMapIterator iterator(resolvedFiles); while (iterator.hasNext()) { iterator.next(); diff --git a/launcher/modplatform/modrinth/ModrinthPackExportTask.h b/launcher/modplatform/modrinth/ModrinthPackExportTask.h index 974c9f0ed..4ac005223 100644 --- a/launcher/modplatform/modrinth/ModrinthPackExportTask.h +++ b/launcher/modplatform/modrinth/ModrinthPackExportTask.h @@ -20,6 +20,7 @@ #include "BaseInstance.h" #include "MMCZip.h" +#include "modplatform/modrinth/ModrinthAPI.h" #include "tasks/Task.h" class ModrinthPackExportTask : public Task { @@ -35,15 +36,26 @@ class ModrinthPackExportTask : public Task { void executeTask() override; private: + static const QStringList PREFIXES; + + // inputs const QString name, version, summary; const InstancePtr instance; const QString output; const MMCZip::FilterFunction filter; + ModrinthAPI api; + QFileInfoList files; + QMap fileHashes; + struct ResolvedFile { QString sha1, sha512, url; int size; }; - QByteArray generateIndex(const QMap& urls); + void collectFiles(); + void parseApiResponse(QByteArray* response); + void buildZip(const QMap& resolvedFiles); + + QByteArray generateIndex(const QMap& resolvedFiles); }; \ No newline at end of file From f583e617ec86a7538523a99ae008143a787e593b Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Sat, 4 Mar 2023 10:37:52 +0000 Subject: [PATCH 015/207] =?UTF-8?q?Implement=20abort=20(possible=20broken?= =?UTF-8?q?=3F)=20and=20therefore=20make=20it=20work=20without=20crashing!?= =?UTF-8?q?=20The=20shared=20pointer=20was=20going=20out=20of=20scope=20?= =?UTF-8?q?=F0=9F=A4=A6.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: TheKodeToad --- .../modrinth/ModrinthPackExportTask.cpp | 14 ++++++++++---- .../modplatform/modrinth/ModrinthPackExportTask.h | 2 ++ 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp b/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp index d7a43c7a4..c151edf57 100644 --- a/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp +++ b/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp @@ -47,10 +47,16 @@ void ModrinthPackExportTask::executeTask() collectFiles(); QByteArray* response = new QByteArray; - Task::Ptr versionsTask = api.currentVersions(fileHashes.values(), "sha512", response); - connect(versionsTask.get(), &NetJob::succeeded, [this, response]() { parseApiResponse(response); }); - connect(versionsTask.get(), &NetJob::failed, this, &ModrinthPackExportTask::emitFailed); - versionsTask->start(); + task = api.currentVersions(fileHashes.values(), "sha512", response); + connect(task.get(), &NetJob::succeeded, [this, response]() { parseApiResponse(response); }); + connect(task.get(), &NetJob::failed, this, &ModrinthPackExportTask::emitFailed); + task->start(); +} + +bool ModrinthPackExportTask::abort() { + if (!task.isNull()) + return task->abort(); + return false; } void ModrinthPackExportTask::collectFiles() diff --git a/launcher/modplatform/modrinth/ModrinthPackExportTask.h b/launcher/modplatform/modrinth/ModrinthPackExportTask.h index 4ac005223..ec87c1cd2 100644 --- a/launcher/modplatform/modrinth/ModrinthPackExportTask.h +++ b/launcher/modplatform/modrinth/ModrinthPackExportTask.h @@ -34,6 +34,7 @@ class ModrinthPackExportTask : public Task { protected: void executeTask() override; + bool abort() override; private: static const QStringList PREFIXES; @@ -47,6 +48,7 @@ class ModrinthPackExportTask : public Task { ModrinthAPI api; QFileInfoList files; QMap fileHashes; + Task::Ptr task; struct ResolvedFile { QString sha1, sha512, url; From 87384182a19ea852522af1b0d69420a510c0a94b Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Sat, 4 Mar 2023 11:07:07 +0000 Subject: [PATCH 016/207] Fix abort? Signed-off-by: TheKodeToad --- launcher/modplatform/modrinth/ModrinthPackExportTask.cpp | 8 ++++++-- launcher/ui/dialogs/ExportMrPackDialog.cpp | 3 ++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp b/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp index c151edf57..5ddd3408a 100644 --- a/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp +++ b/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp @@ -54,8 +54,12 @@ void ModrinthPackExportTask::executeTask() } bool ModrinthPackExportTask::abort() { - if (!task.isNull()) - return task->abort(); + if (!task.isNull() && task->abort()) { + task = nullptr; + emitFailed(tr("Aborted")); + return true; + } + return false; } diff --git a/launcher/ui/dialogs/ExportMrPackDialog.cpp b/launcher/ui/dialogs/ExportMrPackDialog.cpp index 266479b32..13262a7e7 100644 --- a/launcher/ui/dialogs/ExportMrPackDialog.cpp +++ b/launcher/ui/dialogs/ExportMrPackDialog.cpp @@ -70,7 +70,8 @@ void ExportMrPackDialog::done(int result) ProgressDialog progress(this); progress.setSkipButton(true, tr("Abort")); - progress.execWithTask(&task); + if (progress.execWithTask(&task) != QDialog::Accepted) + return; } QDialog::done(result); From 970ec8187c2a6b45b9b1031260c07f4e26fe8827 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Sat, 4 Mar 2023 19:55:38 +0000 Subject: [PATCH 017/207] More refactoring Signed-off-by: TheKodeToad --- launcher/CMakeLists.txt | 4 +-- ...ackIgnoreProxy.cpp => FileIgnoreProxy.cpp} | 24 +++++++-------- .../{PackIgnoreProxy.h => FileIgnoreProxy.h} | 4 +-- .../modrinth/ModrinthPackExportTask.cpp | 29 ++++++++++--------- .../modrinth/ModrinthPackExportTask.h | 17 ++++++----- launcher/ui/dialogs/ExportInstanceDialog.cpp | 2 +- launcher/ui/dialogs/ExportInstanceDialog.h | 4 +-- launcher/ui/dialogs/ExportMrPackDialog.cpp | 9 +++--- launcher/ui/dialogs/ExportMrPackDialog.h | 4 +-- 9 files changed, 50 insertions(+), 47 deletions(-) rename launcher/{PackIgnoreProxy.cpp => FileIgnoreProxy.cpp} (91%) rename launcher/{PackIgnoreProxy.h => FileIgnoreProxy.h} (95%) diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index 380e83366..66099c4e8 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -666,8 +666,8 @@ SET(LAUNCHER_SOURCES # FIXME: maybe find a better home for this. SkinUtils.cpp SkinUtils.h - PackIgnoreProxy.cpp - PackIgnoreProxy.h + FileIgnoreProxy.cpp + FileIgnoreProxy.h # GUI - setup wizard ui/setupwizard/SetupWizard.h diff --git a/launcher/PackIgnoreProxy.cpp b/launcher/FileIgnoreProxy.cpp similarity index 91% rename from launcher/PackIgnoreProxy.cpp rename to launcher/FileIgnoreProxy.cpp index bd0a82a49..7dda02908 100644 --- a/launcher/PackIgnoreProxy.cpp +++ b/launcher/FileIgnoreProxy.cpp @@ -34,7 +34,7 @@ * limitations under the License. */ -#include "PackIgnoreProxy.h" +#include "FileIgnoreProxy.h" #include #include @@ -44,9 +44,9 @@ #include "SeparatorPrefixTree.h" #include "StringUtils.h" -PackIgnoreProxy::PackIgnoreProxy(QString root, QObject* parent) : QSortFilterProxyModel(parent), root(root) {} +FileIgnoreProxy::FileIgnoreProxy(QString root, QObject* parent) : QSortFilterProxyModel(parent), root(root) {} // NOTE: Sadly, we have to do sorting ourselves. -bool PackIgnoreProxy::lessThan(const QModelIndex& left, const QModelIndex& right) const +bool FileIgnoreProxy::lessThan(const QModelIndex& left, const QModelIndex& right) const { QFileSystemModel* fsm = qobject_cast(sourceModel()); if (!fsm) { @@ -79,7 +79,7 @@ bool PackIgnoreProxy::lessThan(const QModelIndex& left, const QModelIndex& right return QSortFilterProxyModel::lessThan(left, right); } -Qt::ItemFlags PackIgnoreProxy::flags(const QModelIndex& index) const +Qt::ItemFlags FileIgnoreProxy::flags(const QModelIndex& index) const { if (!index.isValid()) return Qt::NoItemFlags; @@ -96,7 +96,7 @@ Qt::ItemFlags PackIgnoreProxy::flags(const QModelIndex& index) const return flags; } -QVariant PackIgnoreProxy::data(const QModelIndex& index, int role) const +QVariant FileIgnoreProxy::data(const QModelIndex& index, int role) const { QModelIndex sourceIndex = mapToSource(index); @@ -116,7 +116,7 @@ QVariant PackIgnoreProxy::data(const QModelIndex& index, int role) const return sourceIndex.data(role); } -bool PackIgnoreProxy::setData(const QModelIndex& index, const QVariant& value, int role) +bool FileIgnoreProxy::setData(const QModelIndex& index, const QVariant& value, int role) { if (index.column() == 0 && role == Qt::CheckStateRole) { Qt::CheckState state = static_cast(value.toInt()); @@ -127,7 +127,7 @@ bool PackIgnoreProxy::setData(const QModelIndex& index, const QVariant& value, i return QSortFilterProxyModel::sourceModel()->setData(sourceIndex, value, role); } -QString PackIgnoreProxy::relPath(const QString& path) const +QString FileIgnoreProxy::relPath(const QString& path) const { QString prefix = QDir().absoluteFilePath(root); prefix += '/'; @@ -137,7 +137,7 @@ QString PackIgnoreProxy::relPath(const QString& path) const return path.mid(prefix.size()); } -bool PackIgnoreProxy::setFilterState(QModelIndex index, Qt::CheckState state) +bool FileIgnoreProxy::setFilterState(QModelIndex index, Qt::CheckState state) { QFileSystemModel* fsm = qobject_cast(sourceModel()); @@ -225,7 +225,7 @@ bool PackIgnoreProxy::setFilterState(QModelIndex index, Qt::CheckState state) return true; } -bool PackIgnoreProxy::shouldExpand(QModelIndex index) +bool FileIgnoreProxy::shouldExpand(QModelIndex index) { QModelIndex sourceIndex = mapToSource(index); QFileSystemModel* fsm = qobject_cast(sourceModel()); @@ -240,7 +240,7 @@ bool PackIgnoreProxy::shouldExpand(QModelIndex index) return false; } -void PackIgnoreProxy::setBlockedPaths(QStringList paths) +void FileIgnoreProxy::setBlockedPaths(QStringList paths) { beginResetModel(); blocked.clear(); @@ -248,12 +248,12 @@ void PackIgnoreProxy::setBlockedPaths(QStringList paths) endResetModel(); } -const SeparatorPrefixTree<'/'>& PackIgnoreProxy::blockedPaths() const +const SeparatorPrefixTree<'/'>& FileIgnoreProxy::blockedPaths() const { return blocked; } -bool PackIgnoreProxy::filterAcceptsColumn(int source_column, const QModelIndex& source_parent) const +bool FileIgnoreProxy::filterAcceptsColumn(int source_column, const QModelIndex& source_parent) const { Q_UNUSED(source_parent) diff --git a/launcher/PackIgnoreProxy.h b/launcher/FileIgnoreProxy.h similarity index 95% rename from launcher/PackIgnoreProxy.h rename to launcher/FileIgnoreProxy.h index aec42b41b..a0f6c51ae 100644 --- a/launcher/PackIgnoreProxy.h +++ b/launcher/FileIgnoreProxy.h @@ -39,11 +39,11 @@ #include #include "SeparatorPrefixTree.h" -class PackIgnoreProxy : public QSortFilterProxyModel { +class FileIgnoreProxy : public QSortFilterProxyModel { Q_OBJECT public: - PackIgnoreProxy(QString root, QObject* parent); + FileIgnoreProxy(QString root, QObject* parent); // NOTE: Sadly, we have to do sorting ourselves. bool lessThan(const QModelIndex& left, const QModelIndex& right) const; diff --git a/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp b/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp index 5ddd3408a..3c69413da 100644 --- a/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp +++ b/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp @@ -27,6 +27,7 @@ #include "MMCZip.h" #include "minecraft/MinecraftInstance.h" #include "minecraft/PackProfile.h" +#include "minecraft/mod/Mod.h" #include "modplatform/modrinth/ModrinthAPI.h" const QStringList ModrinthPackExportTask::PREFIXES = QStringList({ "mods", "coremods", "resourcepacks", "texturepacks", "shaderpacks" }); @@ -47,13 +48,14 @@ void ModrinthPackExportTask::executeTask() collectFiles(); QByteArray* response = new QByteArray; - task = api.currentVersions(fileHashes.values(), "sha512", response); + task = api.currentVersions(pendingHashes.values(), "sha512", response); connect(task.get(), &NetJob::succeeded, [this, response]() { parseApiResponse(response); }); connect(task.get(), &NetJob::failed, this, &ModrinthPackExportTask::emitFailed); task->start(); } -bool ModrinthPackExportTask::abort() { +bool ModrinthPackExportTask::abort() +{ if (!task.isNull() && task->abort()) { task = nullptr; emitFailed(tr("Aborted")); @@ -71,7 +73,8 @@ void ModrinthPackExportTask::collectFiles() return; } - fileHashes.clear(); + pendingHashes.clear(); + resolvedFiles.clear(); QDir mc(instance->gameRoot()); for (QFileInfo file : files) { @@ -97,18 +100,16 @@ void ModrinthPackExportTask::collectFiles() continue; } - fileHashes[relative] = hash.result().toHex(); + pendingHashes[relative] = hash.result().toHex(); } } void ModrinthPackExportTask::parseApiResponse(QByteArray* response) { - QMap resolved; - try { QJsonDocument doc = Json::requireDocument(*response); - QMapIterator iterator(fileHashes); + QMapIterator iterator(pendingHashes); while (iterator.hasNext()) { iterator.next(); @@ -121,18 +122,20 @@ void ModrinthPackExportTask::parseApiResponse(QByteArray* response) [&iterator](const QJsonValue& file) { return file["hashes"]["sha512"] == iterator.value(); }); fileIter != files.end()) { // map the file to the url - resolved[iterator.key()] = ResolvedFile{ fileIter->toObject()["hashes"].toObject()["sha1"].toString(), iterator.value(), - fileIter->toObject()["url"].toString(), fileIter->toObject()["size"].toInt() }; + resolvedFiles[iterator.key()] = + ResolvedFile{ fileIter->toObject()["hashes"].toObject()["sha1"].toString(), iterator.value(), + fileIter->toObject()["url"].toString(), fileIter->toObject()["size"].toInt() }; } } } catch (Json::JsonException& e) { qWarning() << "Failed to parse versions response" << e.what(); } + pendingHashes.clear(); - buildZip(resolved); + buildZip(); } -void ModrinthPackExportTask::buildZip(const QMap& resolvedFiles) +void ModrinthPackExportTask::buildZip() { setStatus("Adding files..."); QuaZip zip(output); @@ -148,7 +151,7 @@ void ModrinthPackExportTask::buildZip(const QMap& resolve emitFailed(tr("Could not create index")); return; } - indexFile.write(generateIndex(resolvedFiles)); + indexFile.write(generateIndex()); QDir mc(instance->gameRoot()); size_t i = 0; @@ -171,7 +174,7 @@ void ModrinthPackExportTask::buildZip(const QMap& resolve emitSucceeded(); } -QByteArray ModrinthPackExportTask::generateIndex(const QMap& resolvedFiles) +QByteArray ModrinthPackExportTask::generateIndex() { QJsonObject obj; obj["formatVersion"] = 1; diff --git a/launcher/modplatform/modrinth/ModrinthPackExportTask.h b/launcher/modplatform/modrinth/ModrinthPackExportTask.h index ec87c1cd2..d7a42e7bf 100644 --- a/launcher/modplatform/modrinth/ModrinthPackExportTask.h +++ b/launcher/modplatform/modrinth/ModrinthPackExportTask.h @@ -37,6 +37,11 @@ class ModrinthPackExportTask : public Task { bool abort() override; private: + struct ResolvedFile { + QString sha1, sha512, url; + int size; + }; + static const QStringList PREFIXES; // inputs @@ -47,17 +52,13 @@ class ModrinthPackExportTask : public Task { ModrinthAPI api; QFileInfoList files; - QMap fileHashes; + QMap pendingHashes; + QMap resolvedFiles; Task::Ptr task; - struct ResolvedFile { - QString sha1, sha512, url; - int size; - }; - void collectFiles(); void parseApiResponse(QByteArray* response); - void buildZip(const QMap& resolvedFiles); + void buildZip(); - QByteArray generateIndex(const QMap& resolvedFiles); + QByteArray generateIndex(); }; \ No newline at end of file diff --git a/launcher/ui/dialogs/ExportInstanceDialog.cpp b/launcher/ui/dialogs/ExportInstanceDialog.cpp index f310a6897..ea01c5e21 100644 --- a/launcher/ui/dialogs/ExportInstanceDialog.cpp +++ b/launcher/ui/dialogs/ExportInstanceDialog.cpp @@ -57,7 +57,7 @@ ExportInstanceDialog::ExportInstanceDialog(InstancePtr instance, QWidget *parent ui->setupUi(this); auto model = new QFileSystemModel(this); auto root = instance->instanceRoot(); - proxyModel = new PackIgnoreProxy(root, this); + proxyModel = new FileIgnoreProxy(root, this); loadPackIgnore(); proxyModel->setSourceModel(model); ui->treeView->setModel(proxyModel); diff --git a/launcher/ui/dialogs/ExportInstanceDialog.h b/launcher/ui/dialogs/ExportInstanceDialog.h index b1b8f9119..d96f45376 100644 --- a/launcher/ui/dialogs/ExportInstanceDialog.h +++ b/launcher/ui/dialogs/ExportInstanceDialog.h @@ -18,7 +18,7 @@ #include #include #include -#include "PackIgnoreProxy.h" +#include "FileIgnoreProxy.h" class BaseInstance; typedef std::shared_ptr InstancePtr; @@ -47,7 +47,7 @@ private: private: Ui::ExportInstanceDialog *ui; InstancePtr m_instance; - PackIgnoreProxy * proxyModel; + FileIgnoreProxy * proxyModel; private slots: void rowsInserted(QModelIndex parent, int top, int bottom); diff --git a/launcher/ui/dialogs/ExportMrPackDialog.cpp b/launcher/ui/dialogs/ExportMrPackDialog.cpp index 13262a7e7..1a69cc533 100644 --- a/launcher/ui/dialogs/ExportMrPackDialog.cpp +++ b/launcher/ui/dialogs/ExportMrPackDialog.cpp @@ -37,7 +37,7 @@ ExportMrPackDialog::ExportMrPackDialog(InstancePtr instance, QWidget* parent) auto model = new QFileSystemModel(this); // use the game root - everything outside cannot be exported QString root = instance->gameRoot(); - proxy = new PackIgnoreProxy(root, this); + proxy = new FileIgnoreProxy(root, this); proxy->setSourceModel(model); ui->treeView->setModel(proxy); ui->treeView->setRootIndex(proxy->mapFromSource(model->index(root))); @@ -58,16 +58,15 @@ void ExportMrPackDialog::done(int result) { if (result == Accepted) { const QString filename = FS::RemoveInvalidFilenameChars(ui->name->text()); - const QString output = - QFileDialog::getSaveFileName(this, tr("Export %1").arg(ui->name->text()), FS::PathCombine(QDir::homePath(), filename + ".mrpack"), - "Modrinth pack (*.mrpack *.zip)", nullptr); + const QString output = QFileDialog::getSaveFileName(this, tr("Export %1").arg(ui->name->text()), + FS::PathCombine(QDir::homePath(), filename + ".mrpack"), + "Modrinth pack (*.mrpack *.zip)", nullptr); if (output.isEmpty()) return; ModrinthPackExportTask task(ui->name->text(), ui->version->text(), ui->summary->text(), instance, output, [this](const QString& path) { return proxy->blockedPaths().covers(path); }); - ProgressDialog progress(this); progress.setSkipButton(true, tr("Abort")); if (progress.execWithTask(&task) != QDialog::Accepted) diff --git a/launcher/ui/dialogs/ExportMrPackDialog.h b/launcher/ui/dialogs/ExportMrPackDialog.h index 3ded48872..63e3f0169 100644 --- a/launcher/ui/dialogs/ExportMrPackDialog.h +++ b/launcher/ui/dialogs/ExportMrPackDialog.h @@ -20,7 +20,7 @@ #include #include "BaseInstance.h" -#include "PackIgnoreProxy.h" +#include "FileIgnoreProxy.h" namespace Ui { class ExportMrPackDialog; @@ -38,5 +38,5 @@ class ExportMrPackDialog : public QDialog { private: const InstancePtr instance; Ui::ExportMrPackDialog* ui; - PackIgnoreProxy* proxy; + FileIgnoreProxy* proxy; }; From 5d5fcae5010ce0860bef23d161f0b0d698ade1ab Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Mon, 6 Mar 2023 17:22:20 +0000 Subject: [PATCH 018/207] Further reduce buggy behaviour Signed-off-by: TheKodeToad --- .../modrinth/ModrinthPackExportTask.cpp | 84 ++++++++++++------- .../modrinth/ModrinthPackExportTask.h | 1 + 2 files changed, 53 insertions(+), 32 deletions(-) diff --git a/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp b/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp index 3c69413da..e12ee9236 100644 --- a/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp +++ b/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp @@ -19,6 +19,7 @@ #include "ModrinthPackExportTask.h" #include +#include #include #include #include @@ -56,13 +57,17 @@ void ModrinthPackExportTask::executeTask() bool ModrinthPackExportTask::abort() { - if (!task.isNull() && task->abort()) { + if (task != nullptr) { + if (!task->abort()) + return false; + task = nullptr; emitFailed(tr("Aborted")); return true; } - return false; + pendingAbort = true; + return true; } void ModrinthPackExportTask::collectFiles() @@ -106,6 +111,8 @@ void ModrinthPackExportTask::collectFiles() void ModrinthPackExportTask::parseApiResponse(QByteArray* response) { + task = nullptr; + try { QJsonDocument doc = Json::requireDocument(*response); @@ -137,41 +144,54 @@ void ModrinthPackExportTask::parseApiResponse(QByteArray* response) void ModrinthPackExportTask::buildZip() { - setStatus("Adding files..."); - QuaZip zip(output); - if (!zip.open(QuaZip::mdCreate)) { - QFile::remove(output); - emitFailed(tr("Could not create file")); - return; - } + QtConcurrent::run(QThreadPool::globalInstance(), [this]() { + setStatus("Adding files..."); + QuaZip zip(output); + if (!zip.open(QuaZip::mdCreate)) { + QFile::remove(output); + emitFailed(tr("Could not create file")); + return; + } - QuaZipFile indexFile(&zip); - if (!indexFile.open(QIODevice::WriteOnly, QuaZipNewInfo("modrinth.index.json"))) { - QFile::remove(output); - emitFailed(tr("Could not create index")); - return; - } - indexFile.write(generateIndex()); + if (pendingAbort) { + emitFailed(tr("Aborted")); + return; + } - QDir mc(instance->gameRoot()); - size_t i = 0; - for (const QFileInfo& file : files) { - setProgress(i, files.length()); - QString relative = mc.relativeFilePath(file.absoluteFilePath()); - if (!resolvedFiles.contains(relative) && !JlCompress::compressFile(&zip, file.absoluteFilePath(), "overrides/" + relative)) - qWarning() << "Could not compress" << file; - i++; - } + QuaZipFile indexFile(&zip); + if (!indexFile.open(QIODevice::WriteOnly, QuaZipNewInfo("modrinth.index.json"))) { + QFile::remove(output); + emitFailed(tr("Could not create index")); + return; + } + indexFile.write(generateIndex()); - zip.close(); + QDir mc(instance->gameRoot()); + size_t i = 0; + for (const QFileInfo& file : files) { + if (pendingAbort) { + QFile::remove(output); + emitFailed(tr("Aborted")); + return; + } - if (zip.getZipError() != 0) { - QFile::remove(output); - emitFailed(tr("A zip error occured")); - return; - } + setProgress(i, files.length()); + QString relative = mc.relativeFilePath(file.absoluteFilePath()); + if (!resolvedFiles.contains(relative) && !JlCompress::compressFile(&zip, file.absoluteFilePath(), "overrides/" + relative)) + qWarning() << "Could not compress" << file; + i++; + } - emitSucceeded(); + zip.close(); + + if (zip.getZipError() != 0) { + QFile::remove(output); + emitFailed(tr("A zip error occured")); + return; + } + + emitSucceeded(); + }); } QByteArray ModrinthPackExportTask::generateIndex() diff --git a/launcher/modplatform/modrinth/ModrinthPackExportTask.h b/launcher/modplatform/modrinth/ModrinthPackExportTask.h index d7a42e7bf..04578d2cb 100644 --- a/launcher/modplatform/modrinth/ModrinthPackExportTask.h +++ b/launcher/modplatform/modrinth/ModrinthPackExportTask.h @@ -55,6 +55,7 @@ class ModrinthPackExportTask : public Task { QMap pendingHashes; QMap resolvedFiles; Task::Ptr task; + bool pendingAbort = false; void collectFiles(); void parseApiResponse(QByteArray* response); From 2cc9b0df068ace61c60217973d4d66412cb53968 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Wed, 8 Mar 2023 18:10:52 +0000 Subject: [PATCH 019/207] Only select some paths by default - again! Signed-off-by: TheKodeToad --- launcher/FileIgnoreProxy.cpp | 5 ----- launcher/FileIgnoreProxy.h | 3 ++- launcher/ui/dialogs/ExportMrPackDialog.cpp | 17 ++++++++++++++++- 3 files changed, 18 insertions(+), 7 deletions(-) diff --git a/launcher/FileIgnoreProxy.cpp b/launcher/FileIgnoreProxy.cpp index 7dda02908..fd05624af 100644 --- a/launcher/FileIgnoreProxy.cpp +++ b/launcher/FileIgnoreProxy.cpp @@ -248,11 +248,6 @@ void FileIgnoreProxy::setBlockedPaths(QStringList paths) endResetModel(); } -const SeparatorPrefixTree<'/'>& FileIgnoreProxy::blockedPaths() const -{ - return blocked; -} - bool FileIgnoreProxy::filterAcceptsColumn(int source_column, const QModelIndex& source_parent) const { Q_UNUSED(source_parent) diff --git a/launcher/FileIgnoreProxy.h b/launcher/FileIgnoreProxy.h index a0f6c51ae..baf05c7a6 100644 --- a/launcher/FileIgnoreProxy.h +++ b/launcher/FileIgnoreProxy.h @@ -60,7 +60,8 @@ class FileIgnoreProxy : public QSortFilterProxyModel { void setBlockedPaths(QStringList paths); - const SeparatorPrefixTree<'/'>& blockedPaths() const; + inline const SeparatorPrefixTree<'/'>& blockedPaths() const { return blocked; } + inline SeparatorPrefixTree<'/'>& blockedPaths() { return blocked; } protected: bool filterAcceptsColumn(int source_column, const QModelIndex& source_parent) const; diff --git a/launcher/ui/dialogs/ExportMrPackDialog.cpp b/launcher/ui/dialogs/ExportMrPackDialog.cpp index 1a69cc533..03238ec4e 100644 --- a/launcher/ui/dialogs/ExportMrPackDialog.cpp +++ b/launcher/ui/dialogs/ExportMrPackDialog.cpp @@ -39,11 +39,26 @@ ExportMrPackDialog::ExportMrPackDialog(InstancePtr instance, QWidget* parent) QString root = instance->gameRoot(); proxy = new FileIgnoreProxy(root, this); proxy->setSourceModel(model); + + QDir::Filters filter(QDir::AllEntries | QDir::NoDotAndDotDot | QDir::AllDirs | QDir::Hidden); + + for (QString file : QDir(root).entryList(filter)) { + if (!(file == "mods" || file == "coremods" || file == "datapacks" || file == "config" || file == "options.txt" || + file == "servers.dat")) + proxy->blockedPaths().insert(file); + } + + QDir modsIndex(instance->gameRoot() + "/mods/.index"); + if (modsIndex.exists()) + proxy->blockedPaths().insert("mods/.index"); + ui->treeView->setModel(proxy); ui->treeView->setRootIndex(proxy->mapFromSource(model->index(root))); ui->treeView->sortByColumn(0, Qt::AscendingOrder); - model->setFilter(QDir::AllEntries | QDir::NoDotAndDotDot | QDir::AllDirs | QDir::Hidden); + + model->setFilter(filter); model->setRootPath(root); + auto headerView = ui->treeView->header(); headerView->setSectionResizeMode(QHeaderView::ResizeToContents); headerView->setSectionResizeMode(0, QHeaderView::Stretch); From c17f6e37e5e32138f778cedf66c80e20f1eacd2e Mon Sep 17 00:00:00 2001 From: Janrupf Date: Mon, 13 Mar 2023 16:37:45 +0100 Subject: [PATCH 020/207] 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 321f944b3..879af5356 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 eb4279533..ff885f10d 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 a9f44c0b1..324eb461c 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 c60156c2c..33f66f1be 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 f084d9709..1fe556781 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 021/207] 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 5615a758f..f173511cf 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 99bb22314..6d4f3f245 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 8b6cb99ce..7162a6cf1 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 022/207] 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 051e1664c..a0f503cd6 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 f173511cf..f476fdf92 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 023/207] 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 000000000..3550a30f2 --- /dev/null +++ b/.envrc @@ -0,0 +1 @@ +use flake diff --git a/.gitignore b/.gitignore index 010bc3235..778024e3f 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 f476fdf92..3294c799c 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 024/207] 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 778024e3f..635d1c534 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 a0f503cd6..5820e098d 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 3294c799c..6173670af 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 025/207] 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 2443ac567..a139039df 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 ccc7c263b..a6989d15a 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 026/207] 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 6173670af..0f7363083 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 027/207] 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 0f7363083..f656703ce 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 028/207] 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 5820e098d..ad9196a9b 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 029/207] 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 010bc3235..c72f7a1d8 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 030/207] 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 1fe556781..923b7f95c 100644 --- a/launcher/ui/pages/global/LauncherPage.ui +++ b/launcher/ui/pages/global/LauncherPage.ui @@ -130,7 +130,10 @@ - Downloads: + &Downloads: + + + downloadsDirTextBox From ddca838e46d2d147cbc5965be31895dd73676c79 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Sat, 18 Mar 2023 12:21:13 +0000 Subject: [PATCH 031/207] Info and error dialogs TODO: is there a better approach? Signed-off-by: TheKodeToad --- .../modrinth/ModrinthPackExportTask.cpp | 21 +++++++++++-------- launcher/ui/dialogs/ExportMrPackDialog.cpp | 16 ++++++++++++++ 2 files changed, 28 insertions(+), 9 deletions(-) diff --git a/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp b/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp index e12ee9236..e8bc36736 100644 --- a/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp +++ b/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp @@ -18,8 +18,7 @@ #include "ModrinthPackExportTask.h" -#include -#include +#include #include #include #include @@ -28,7 +27,6 @@ #include "MMCZip.h" #include "minecraft/MinecraftInstance.h" #include "minecraft/PackProfile.h" -#include "minecraft/mod/Mod.h" #include "modplatform/modrinth/ModrinthAPI.h" const QStringList ModrinthPackExportTask::PREFIXES = QStringList({ "mods", "coremods", "resourcepacks", "texturepacks", "shaderpacks" }); @@ -62,7 +60,7 @@ bool ModrinthPackExportTask::abort() return false; task = nullptr; - emitFailed(tr("Aborted")); + emitAborted(); return true; } @@ -154,14 +152,16 @@ void ModrinthPackExportTask::buildZip() } if (pendingAbort) { - emitFailed(tr("Aborted")); + QMetaObject::invokeMethod( + this, [this]() { emitAborted(); }, Qt::QueuedConnection); return; } QuaZipFile indexFile(&zip); if (!indexFile.open(QIODevice::WriteOnly, QuaZipNewInfo("modrinth.index.json"))) { QFile::remove(output); - emitFailed(tr("Could not create index")); + QMetaObject::invokeMethod( + this, [this]() { emitFailed(tr("Could not create index")); }, Qt::QueuedConnection); return; } indexFile.write(generateIndex()); @@ -171,7 +171,8 @@ void ModrinthPackExportTask::buildZip() for (const QFileInfo& file : files) { if (pendingAbort) { QFile::remove(output); - emitFailed(tr("Aborted")); + QMetaObject::invokeMethod( + this, [this]() { emitAborted(); }, Qt::QueuedConnection); return; } @@ -186,11 +187,13 @@ void ModrinthPackExportTask::buildZip() if (zip.getZipError() != 0) { QFile::remove(output); - emitFailed(tr("A zip error occured")); + QMetaObject::invokeMethod( + this, [this]() { emitFailed(tr("A zip error occurred")); }, Qt::QueuedConnection); return; } - emitSucceeded(); + QMetaObject::invokeMethod( + this, [this]() { emitSucceeded(); }, Qt::QueuedConnection); }); } diff --git a/launcher/ui/dialogs/ExportMrPackDialog.cpp b/launcher/ui/dialogs/ExportMrPackDialog.cpp index 03238ec4e..8a49f3147 100644 --- a/launcher/ui/dialogs/ExportMrPackDialog.cpp +++ b/launcher/ui/dialogs/ExportMrPackDialog.cpp @@ -17,6 +17,8 @@ */ #include "ExportMrPackDialog.h" +#include "Application.h" +#include "ui/dialogs/CustomMessageBox.h" #include "ui/dialogs/ProgressDialog.h" #include "ui_ExportMrPackDialog.h" @@ -82,6 +84,20 @@ void ExportMrPackDialog::done(int result) ModrinthPackExportTask task(ui->name->text(), ui->version->text(), ui->summary->text(), instance, output, [this](const QString& path) { return proxy->blockedPaths().covers(path); }); + + connect(&task, &Task::failed, + [this](const QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); }); + + connect(&task, &Task::succeeded, [this, &task]() { + QStringList warnings = task.warnings(); + if (warnings.count() > 0) + CustomMessageBox::selectable(this, tr("Warnings"), warnings.join('\n'), QMessageBox::Warning)->show(); + }); + connect(&task, &Task::aborted, [this] { + CustomMessageBox::selectable(this, tr("Task aborted"), tr("The task has been aborted by the user."), QMessageBox::Information) + ->show(); + }); + ProgressDialog progress(this); progress.setSkipButton(true, tr("Abort")); if (progress.execWithTask(&task) != QDialog::Accepted) From 5346dfc782d1736811b60b8548ce4c4335caabff Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Sat, 18 Mar 2023 12:57:44 +0000 Subject: [PATCH 032/207] Use first line of notes for summary Signed-off-by: TheKodeToad --- launcher/ui/dialogs/ExportMrPackDialog.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/ui/dialogs/ExportMrPackDialog.cpp b/launcher/ui/dialogs/ExportMrPackDialog.cpp index 8a49f3147..28c719c70 100644 --- a/launcher/ui/dialogs/ExportMrPackDialog.cpp +++ b/launcher/ui/dialogs/ExportMrPackDialog.cpp @@ -17,7 +17,6 @@ */ #include "ExportMrPackDialog.h" -#include "Application.h" #include "ui/dialogs/CustomMessageBox.h" #include "ui/dialogs/ProgressDialog.h" #include "ui_ExportMrPackDialog.h" @@ -35,6 +34,7 @@ ExportMrPackDialog::ExportMrPackDialog(InstancePtr instance, QWidget* parent) { ui->setupUi(this); ui->name->setText(instance->name()); + ui->summary->setText(instance->notes().split(QRegExp("\\r?\\n"))[0]); auto model = new QFileSystemModel(this); // use the game root - everything outside cannot be exported From 8837f06e4e97ed966662b52db206facd7f91a489 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Sat, 18 Mar 2023 14:01:41 +0000 Subject: [PATCH 033/207] Only add summary if not empty Signed-off-by: TheKodeToad --- launcher/modplatform/modrinth/ModrinthPackExportTask.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp b/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp index e8bc36736..cfd751d5e 100644 --- a/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp +++ b/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp @@ -204,7 +204,8 @@ QByteArray ModrinthPackExportTask::generateIndex() obj["game"] = "minecraft"; obj["name"] = name; obj["versionId"] = version; - obj["summary"] = summary; + if (!summary.isEmpty()) + obj["summary"] = summary; MinecraftInstance* mc = dynamic_cast(instance.get()); if (mc) { From ec8cb056bf717edb97bf3e65fbad0ffc1b78ec34 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Sun, 19 Mar 2023 11:49:09 +0000 Subject: [PATCH 034/207] QRegExp -> QRegularExpression :P Signed-off-by: TheKodeToad --- launcher/ui/dialogs/ExportMrPackDialog.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/ui/dialogs/ExportMrPackDialog.cpp b/launcher/ui/dialogs/ExportMrPackDialog.cpp index 28c719c70..02b6721db 100644 --- a/launcher/ui/dialogs/ExportMrPackDialog.cpp +++ b/launcher/ui/dialogs/ExportMrPackDialog.cpp @@ -34,7 +34,7 @@ ExportMrPackDialog::ExportMrPackDialog(InstancePtr instance, QWidget* parent) { ui->setupUi(this); ui->name->setText(instance->name()); - ui->summary->setText(instance->notes().split(QRegExp("\\r?\\n"))[0]); + ui->summary->setText(instance->notes().split(QRegularExpression("\\r?\\n"))[0]); auto model = new QFileSystemModel(this); // use the game root - everything outside cannot be exported 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 035/207] 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 9337f3c7a..022a04f8e 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 710156b9f1b9555cd7e5562c8673074f80457d92 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Sun, 19 Mar 2023 21:25:12 +0000 Subject: [PATCH 036/207] Replace native file separator - this was accidentally brought to my attention on Modrinth's guild Signed-off-by: TheKodeToad --- launcher/modplatform/modrinth/ModrinthPackExportTask.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp b/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp index cfd751d5e..f86f1ad13 100644 --- a/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp +++ b/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp @@ -237,7 +237,9 @@ QByteArray ModrinthPackExportTask::generateIndex() const ResolvedFile& value = iterator.value(); QJsonObject file; - file["path"] = iterator.key(); + QString path = iterator.key(); + path.replace(QDir::separator(), "/"); + file["path"] = path; file["downloads"] = QJsonArray({ iterator.value().url }); QJsonObject hashes; From 46f448dfba2a3c4bffe60f373ee27b1d19873c9e Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Sun, 19 Mar 2023 21:26:25 +0000 Subject: [PATCH 037/207] Improve invokeMethod syntax Signed-off-by: TheKodeToad --- .../modplatform/modrinth/ModrinthPackExportTask.cpp | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp b/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp index f86f1ad13..46bfab6d1 100644 --- a/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp +++ b/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp @@ -152,8 +152,7 @@ void ModrinthPackExportTask::buildZip() } if (pendingAbort) { - QMetaObject::invokeMethod( - this, [this]() { emitAborted(); }, Qt::QueuedConnection); + QMetaObject::invokeMethod(this, &ModrinthPackExportTask::emitAborted, Qt::QueuedConnection); return; } @@ -171,8 +170,7 @@ void ModrinthPackExportTask::buildZip() for (const QFileInfo& file : files) { if (pendingAbort) { QFile::remove(output); - QMetaObject::invokeMethod( - this, [this]() { emitAborted(); }, Qt::QueuedConnection); + QMetaObject::invokeMethod(this, &ModrinthPackExportTask::emitAborted, Qt::QueuedConnection); return; } @@ -192,8 +190,7 @@ void ModrinthPackExportTask::buildZip() return; } - QMetaObject::invokeMethod( - this, [this]() { emitSucceeded(); }, Qt::QueuedConnection); + QMetaObject::invokeMethod(this, &ModrinthPackExportTask::emitSucceeded, Qt::QueuedConnection); }); } @@ -226,6 +223,7 @@ QByteArray ModrinthPackExportTask::generateIndex() dependencies["fabric-loader"] = fabric->m_version; if (forge != nullptr) dependencies["forge"] = forge->m_version; + obj["dependencies"] = dependencies; } 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 038/207] 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 3340670b7..b3e2ee7d0 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 074570e31..2216aa1bd 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 aee5245df..ec4af98ce 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 f083f3c72..98f55f96f 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 7b93a5164..18a6d7047 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 6988b2df3..25c0f3fc6 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 000000000..9b5589ab5 --- /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 000000000..253d13940 --- /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 000000000..a4e162642 --- /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 000000000..7f06795e7 --- /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 3f5122f66..981352ae9 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 884501d14..a80faab9b 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 b7828fe31..e41ad5261 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 3a5c38d04..ce83aa498 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 039/207] 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 ce83aa498..846718894 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 040/207] 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 846718894..395ca5c09 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 041/207] 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 2216aa1bd..18d4ce0b0 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 042/207] 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 18d4ce0b0..dd62893c7 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 302eaf967..69770e99b 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 ec4af98ce..9e51f9326 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 98f55f96f..b15d16850 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 0f3c3669f..93a44d4c6 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 1799605b3..1ba195550 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 9b5589ab5..78486507a 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 253d13940..5d0ba1236 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 395ca5c09..be0a4be07 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 043/207] 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 9e51f9326..c48a3bba9 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 b15d16850..2e7392980 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 78486507a..a731ecdbe 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 5d0ba1236..d146b8d91 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 be0a4be07..4ccc4003d 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 044/207] 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 c48a3bba9..c94770ee3 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 2e7392980..d79096e67 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 18a6d7047..e363d4c69 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 25c0f3fc6..61719a062 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 188d163b6..31c6bdca4 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 1f29b8545..d9651b07b 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 981352ae9..e477b4b35 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 a80faab9b..57775925d 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 e41ad5261..d8eb96eb6 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 045/207] 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 e363d4c69..59825ced1 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 61719a062..9fc9dcaba 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 31c6bdca4..81502d891 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 d9651b07b..3dce1662e 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 e477b4b35..55962c5a5 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 57775925d..2fc6f38ad 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 d8eb96eb6..8df0d3db3 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 046/207] 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 d79096e67..782a2f401 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 1eda43fe2..b4b663c1b 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 81f9cb908..2a78f830f 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 f13e36e86..07ec3c709 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 047/207] 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 c94770ee3..7b7fc80ba 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 782a2f401..531036dd2 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 048/207] 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 7b7fc80ba..fd09842f2 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 531036dd2..aa28de93f 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 59825ced1..f2aa5e694 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 9fc9dcaba..583fdd4c2 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 81502d891..b3ea54b08 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 3dce1662e..aea9d99a1 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 55962c5a5..c51bc0675 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 2fc6f38ad..859c643c4 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 8df0d3db3..ce8657f6a 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 049/207] 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 69770e99b..2984a1b4f 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 fd09842f2..c363f6ea0 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 aa28de93f..83ff99a4e 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 f2aa5e694..c03d0aae4 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 583fdd4c2..14ae95518 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 b3ea54b08..ba0052fad 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 c51bc0675..c6cbefcf4 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 859c643c4..2dea37950 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 ce8657f6a..7bf75c2d8 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 050/207] 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 c363f6ea0..4ee3899b0 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 83ff99a4e..7485206a9 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 c03d0aae4..0650002b8 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 14ae95518..c7bde0682 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 ba0052fad..40babd0fe 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 b4b663c1b..1a336375b 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 c6cbefcf4..9fe129f13 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 4ccc4003d..4418dd627 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 051/207] 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 4ee3899b0..57b796ade 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 052/207] 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 4418dd627..ab78c1509 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 053/207] 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 57b796ade..c3a0c273e 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 ab78c1509..169f06697 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 054/207] 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 c3a0c273e..4037d3469 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 7485206a9..3f6b78e54 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 68e3e92cb..1ca16ae90 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 055/207] 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 4037d3469..69e2d36a2 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 056/207] 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 69e2d36a2..948ec55e4 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 3f6b78e54..4c0113096 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 057/207] 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 948ec55e4..a9461bb0a 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 4c0113096..cafbd2a83 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 058/207] 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 dd62893c7..b47f5746d 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 9fe129f13..495e98e98 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 7bf75c2d8..58442f732 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 059/207] 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 a9461bb0a..af2ba299a 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 cafbd2a83..361993ebf 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 d310f8b95..54fb94346 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 8327253a3..10328cce8 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 ae29a972f..1262fa1d6 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 082947556..bd32dd4e3 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 3f31b93c6..e2053f92b 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 0d35d7551..a0b8a4bbc 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 0c37f3a39..a5e9ae91e 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 f2a77c122..d1748c1cc 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 3bd788705..f840b2de3 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 da4bd091f..56584a34a 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 d4a395d90..b6ad159e1 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 060/207] 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 1262fa1d6..43733110d 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 e2053f92b..943f30adc 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 d1748c1cc..95b7651fe 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 56584a34a..1fcfa9091 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 58442f732..009f5b884 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 061/207] 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 af2ba299a..d6e6b92fb 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 361993ebf..84526c113 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 062/207] 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 d6e6b92fb..3d66d8447 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 063/207] 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 3d66d8447..6ef1eea74 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 064/207] 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 49e225007..f8918c580 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 065/207] 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 6ef1eea74..45c73d247 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 066/207] 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 8680361c6..6428be43b 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 a2a4f8246..83a8064fa 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 40babd0fe..e0a4de0b6 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 1ca16ae90..179bfb9aa 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 edacba3cd..48bede07d 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 733cd4440..e48e6f47d 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 4313e8293..86bf08989 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 8a0bc0455..4c51d6e96 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 6200bc3ae..ff4301725 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 8490b2924..a6aa8320f 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 b64b031b4..d13937c00 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 067/207] 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 495e98e98..62c0bb399 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 2dea37950..c447bee98 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 068/207] 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 45c73d247..2bd0cf527 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 84526c113..71175bb49 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 e0a4de0b6..5ef7a7fd5 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 009f5b884..3101acec4 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 169f06697..19565a993 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 069/207] 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 2bd0cf527..714af4a08 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 179bfb9aa..5b33d6786 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 93a44d4c6..2fa565016 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 1ba195550..c4a6ab318 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 070/207] 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 6428be43b..ad45aa2d2 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 5ef7a7fd5..6bd56de37 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 a4e162642..239aa9783 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 7f06795e7..4a22ff182 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 43733110d..3681bcdad 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 943f30adc..91d161757 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 95b7651fe..29a0c7364 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 1fcfa9091..0480d8baf 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 62c0bb399..ced57ae06 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 071/207] 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 022a04f8e..df1a38fc1 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 072/207] 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 714af4a08..c913b43e7 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 073/207] 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 3101acec4..5060debcf 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 074/207] 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 c913b43e7..640cf9be2 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 71175bb49..673c35639 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 6bd56de37..4ac3b51ad 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 a731ecdbe..ded460611 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 d146b8d91..4c47d9bbb 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 4a22ff182..83566a3c6 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 ced57ae06..347bd39ff 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 c447bee98..698c6e939 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 075/207] 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 6447f5c6f..43a9c4f68 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 076/207] 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 3340670b7..5c589a5ff 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 077/207] 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 ff885f10d..05f50c061 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 014f488a9..01077aa51 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 078/207] 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 05f50c061..ac06625e2 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 079/207] 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 43a9c4f68..c8d5c34e0 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 2360d1ea1..af3ed79f4 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 080/207] 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 cc597fe0b..eca3e8657 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 640f436de..cff071bf5 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 081/207] 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 879af5356..1fc315495 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 ac06625e2..36df5f467 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 324eb461c..20e02230f 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 33f66f1be..c5ebbe39d 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 923b7f95c..6279d879b 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 082/207] 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 36df5f467..7b3a395aa 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 e42050cc8a2f74178d6cd0afe8c92ae9b802cf73 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Tue, 28 Mar 2023 14:22:28 +0100 Subject: [PATCH 083/207] Skip lookup if no files and fail if zipping fails Signed-off-by: TheKodeToad --- .../modrinth/ModrinthPackExportTask.cpp | 22 +++++++++++++------ launcher/ui/dialogs/ExportMrPackDialog.cpp | 6 ----- 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp b/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp index 46bfab6d1..d630f5d02 100644 --- a/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp +++ b/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp @@ -46,11 +46,15 @@ void ModrinthPackExportTask::executeTask() setProgress(0, 0); collectFiles(); - QByteArray* response = new QByteArray; - task = api.currentVersions(pendingHashes.values(), "sha512", response); - connect(task.get(), &NetJob::succeeded, [this, response]() { parseApiResponse(response); }); - connect(task.get(), &NetJob::failed, this, &ModrinthPackExportTask::emitFailed); - task->start(); + if (pendingHashes.isEmpty()) + buildZip(); + else { + QByteArray* response = new QByteArray; + task = api.currentVersions(pendingHashes.values(), "sha512", response); + connect(task.get(), &NetJob::succeeded, [this, response]() { parseApiResponse(response); }); + connect(task.get(), &NetJob::failed, this, &ModrinthPackExportTask::emitFailed); + task->start(); + } } bool ModrinthPackExportTask::abort() @@ -176,8 +180,12 @@ void ModrinthPackExportTask::buildZip() setProgress(i, files.length()); QString relative = mc.relativeFilePath(file.absoluteFilePath()); - if (!resolvedFiles.contains(relative) && !JlCompress::compressFile(&zip, file.absoluteFilePath(), "overrides/" + relative)) - qWarning() << "Could not compress" << file; + if (!resolvedFiles.contains(relative) && !JlCompress::compressFile(&zip, file.absoluteFilePath(), "overrides/" + relative)) { + QFile::remove(output); + QMetaObject::invokeMethod( + this, [this, relative]() { emitFailed(tr("Could not compress %1").arg(relative)); }, Qt::QueuedConnection); + return; + } i++; } diff --git a/launcher/ui/dialogs/ExportMrPackDialog.cpp b/launcher/ui/dialogs/ExportMrPackDialog.cpp index 02b6721db..a622eb304 100644 --- a/launcher/ui/dialogs/ExportMrPackDialog.cpp +++ b/launcher/ui/dialogs/ExportMrPackDialog.cpp @@ -87,12 +87,6 @@ void ExportMrPackDialog::done(int result) connect(&task, &Task::failed, [this](const QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); }); - - connect(&task, &Task::succeeded, [this, &task]() { - QStringList warnings = task.warnings(); - if (warnings.count() > 0) - CustomMessageBox::selectable(this, tr("Warnings"), warnings.join('\n'), QMessageBox::Warning)->show(); - }); connect(&task, &Task::aborted, [this] { CustomMessageBox::selectable(this, tr("Task aborted"), tr("The task has been aborted by the user."), QMessageBox::Information) ->show(); From c7dc115365006a8bff8ca93a01a6df4134eb92e9 Mon Sep 17 00:00:00 2001 From: seth Date: Fri, 31 Mar 2023 11:13:18 -0400 Subject: [PATCH 084/207] 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 d635518e3..076d3064b 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 085/207] 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 076d3064b..890aca572 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 086/207] 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 a6aa8320f..e20a7613a 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 087/207] 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 640cf9be2..c046ee86d 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 673c35639..47044d939 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 19565a993..ec1f0bcff 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 088/207] 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 6d4f3f245..cd782f902 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 089/207] 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 890aca572..90e48e298 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 090/207] 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 022a04f8e..ae683795a 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 091/207] 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 cd782f902..f219d5d2f 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 092/207] 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 f219d5d2f..e0616b6ea 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 093/207] 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 6279d879b..906efd1a0 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 094/207] 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 7b3a395aa..90078a98a 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 20e02230f..516523207 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 c5ebbe39d..33f66f1be 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 095/207] 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 ae683795a..43c71a765 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 096/207] 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 90078a98a..e7404f2d9 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 097/207] 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 b47f5746d..03d3fcbfa 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 c046ee86d..869fbe36c 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 47044d939..cb581d0c5 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 098/207] 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 869fbe36c..bd985c5b4 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 ded460611..c9599b820 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 099/207] 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 03d3fcbfa..71e54c85d 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 100/207] 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 71e54c85d..4dbb9e6ed 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 101/207] 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 4dbb9e6ed..4de0f73bb 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 871d647c93944b7dabd070ed49a6eeaa8745d55a Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Thu, 6 Apr 2023 19:18:36 +0100 Subject: [PATCH 102/207] Use local metadata Signed-off-by: TheKodeToad --- buildconfig/BuildConfig.h | 2 + .../modrinth/ModrinthPackExportTask.cpp | 87 ++++++++++++++----- .../modrinth/ModrinthPackExportTask.h | 5 +- 3 files changed, 73 insertions(+), 21 deletions(-) diff --git a/buildconfig/BuildConfig.h b/buildconfig/BuildConfig.h index a05d7a9eb..38fa3a659 100644 --- a/buildconfig/BuildConfig.h +++ b/buildconfig/BuildConfig.h @@ -36,6 +36,7 @@ #pragma once #include +#include /** * \brief The Config class holds all the build-time information passed from the build system. @@ -160,6 +161,7 @@ class Config { QString MODRINTH_STAGING_URL = "https://staging-api.modrinth.com/v2"; QString MODRINTH_PROD_URL = "https://api.modrinth.com/v2"; + QStringList MODRINTH_MRPACK_HOSTS{"cdn.modrinth.com", "github.com", "raw.githubusercontent.com", "gitlab.com"}; QString FLAME_BASE_URL = "https://api.curseforge.com/v1"; diff --git a/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp b/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp index d630f5d02..2ec519910 100644 --- a/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp +++ b/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp @@ -22,14 +22,15 @@ #include #include #include -#include #include "Json.h" #include "MMCZip.h" #include "minecraft/MinecraftInstance.h" #include "minecraft/PackProfile.h" +#include "minecraft/mod/ModFolderModel.h" #include "modplatform/modrinth/ModrinthAPI.h" -const QStringList ModrinthPackExportTask::PREFIXES = QStringList({ "mods", "coremods", "resourcepacks", "texturepacks", "shaderpacks" }); +const QStringList ModrinthPackExportTask::PREFIXES({ "mods", "coremods", "resourcepacks", "texturepacks", "shaderpacks" }); +const QStringList ModrinthPackExportTask::ALLOWED_HOSTS({ "mods", "coremods", "resourcepacks", "texturepacks", "shaderpacks" }); ModrinthPackExportTask::ModrinthPackExportTask(const QString& name, const QString& version, @@ -45,16 +46,6 @@ void ModrinthPackExportTask::executeTask() setStatus(tr("Searching for files...")); setProgress(0, 0); collectFiles(); - - if (pendingHashes.isEmpty()) - buildZip(); - else { - QByteArray* response = new QByteArray; - task = api.currentVersions(pendingHashes.values(), "sha512", response); - connect(task.get(), &NetJob::succeeded, [this, response]() { parseApiResponse(response); }); - connect(task.get(), &NetJob::failed, this, &ModrinthPackExportTask::emitFailed); - task->start(); - } } bool ModrinthPackExportTask::abort() @@ -83,6 +74,17 @@ void ModrinthPackExportTask::collectFiles() pendingHashes.clear(); resolvedFiles.clear(); + const MinecraftInstance* mcInstance = dynamic_cast(instance.get()); + auto mods = mcInstance->loaderModList(); + mods->update(); + connect(mods.get(), &ModFolderModel::updateFinished, this, &ModrinthPackExportTask::collectHashes); +} + +void ModrinthPackExportTask::collectHashes() +{ + const MinecraftInstance* mcInstance = dynamic_cast(instance.get()); + auto mods = mcInstance->loaderModList(); + QDir mc(instance->gameRoot()); for (QFileInfo file : files) { QString relative = mc.relativeFilePath(file.absoluteFilePath()); @@ -102,13 +104,58 @@ void ModrinthPackExportTask::collectFiles() continue; } - if (!hash.addData(&openFile)) { - qWarning() << "Could not add hash data for" << file; + QByteArray data = openFile.readAll(); + if (openFile.error() != QFileDevice::NoError) { + qWarning() << "Could not read" << file; continue; } + hash.addData(data); + auto allMods = mods->allMods(); + if (auto modIter = std::find_if(allMods.begin(), allMods.end(), [&file](Mod* mod) { return mod->fileinfo() == file; }); + modIter != allMods.end()) { + Mod* mod = *modIter; + if (mod->metadata() != nullptr) { + QUrl& url = mod->metadata()->url; + // most likely some of these may be from curseforge + if (!url.isEmpty() && BuildConfig.MODRINTH_MRPACK_HOSTS.contains(url.host())) { + qDebug() << "Resolving" << relative << "from index"; + + // we've already read it + // let's go back! + openFile.seek(openFile.size()); + + QCryptographicHash hash2(QCryptographicHash::Algorithm::Sha1); + hash2.addData(data); + + ResolvedFile file{ hash2.result().toHex(), hash.result().toHex(), url.toString(), openFile.size() }; + resolvedFiles[relative] = file; + + // nice! we've managed to resolve based on local metadata! + // no need to enqueue it + continue; + } + } + } + + qDebug() << "Enqueueing" << relative << "for Modrinth query"; pendingHashes[relative] = hash.result().toHex(); } + + makeApiRequest(); +} + +void ModrinthPackExportTask::makeApiRequest() +{ + if (pendingHashes.isEmpty()) + buildZip(); + else { + QByteArray* response = new QByteArray; + task = api.currentVersions(pendingHashes.values(), "sha512", response); + connect(task.get(), &NetJob::succeeded, [this, response]() { parseApiResponse(response); }); + connect(task.get(), &NetJob::failed, this, &ModrinthPackExportTask::emitFailed); + task->start(); + } } void ModrinthPackExportTask::parseApiResponse(QByteArray* response) @@ -146,7 +193,7 @@ void ModrinthPackExportTask::parseApiResponse(QByteArray* response) void ModrinthPackExportTask::buildZip() { - QtConcurrent::run(QThreadPool::globalInstance(), [this]() { + QThreadPool::globalInstance()->start([this]() { setStatus("Adding files..."); QuaZip zip(output); if (!zip.open(QuaZip::mdCreate)) { @@ -183,7 +230,7 @@ void ModrinthPackExportTask::buildZip() if (!resolvedFiles.contains(relative) && !JlCompress::compressFile(&zip, file.absoluteFilePath(), "overrides/" + relative)) { QFile::remove(output); QMetaObject::invokeMethod( - this, [this, relative]() { emitFailed(tr("Could not compress %1").arg(relative)); }, Qt::QueuedConnection); + this, [this, relative]() { emitFailed(tr("Could not read and compress %1").arg(relative)); }, Qt::QueuedConnection); return; } i++; @@ -216,10 +263,10 @@ QByteArray ModrinthPackExportTask::generateIndex() if (mc) { auto profile = mc->getPackProfile(); // collect all supported components - auto minecraft = profile->getComponent("net.minecraft"); - auto quilt = profile->getComponent("org.quiltmc.quilt-loader"); - auto fabric = profile->getComponent("net.fabricmc.fabric-loader"); - auto forge = profile->getComponent("net.minecraftforge"); + ComponentPtr minecraft = profile->getComponent("net.minecraft"); + ComponentPtr quilt = profile->getComponent("org.quiltmc.quilt-loader"); + ComponentPtr fabric = profile->getComponent("net.fabricmc.fabric-loader"); + ComponentPtr forge = profile->getComponent("net.minecraftforge"); // convert all available components to mrpack dependencies QJsonObject dependencies; diff --git a/launcher/modplatform/modrinth/ModrinthPackExportTask.h b/launcher/modplatform/modrinth/ModrinthPackExportTask.h index 04578d2cb..217956db5 100644 --- a/launcher/modplatform/modrinth/ModrinthPackExportTask.h +++ b/launcher/modplatform/modrinth/ModrinthPackExportTask.h @@ -39,10 +39,11 @@ class ModrinthPackExportTask : public Task { private: struct ResolvedFile { QString sha1, sha512, url; - int size; + qint64 size; }; static const QStringList PREFIXES; + static const QStringList ALLOWED_HOSTS; // inputs const QString name, version, summary; @@ -58,6 +59,8 @@ class ModrinthPackExportTask : public Task { bool pendingAbort = false; void collectFiles(); + void collectHashes(); + void makeApiRequest(); void parseApiResponse(QByteArray* response); void buildZip(); From 073aaf9b3bbe4edb763dfeb1a5b3d22473e59e41 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Thu, 6 Apr 2023 19:19:35 +0100 Subject: [PATCH 103/207] Remove "prototype" field Signed-off-by: TheKodeToad --- launcher/modplatform/modrinth/ModrinthPackExportTask.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp b/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp index 2ec519910..cfb59fc3a 100644 --- a/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp +++ b/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp @@ -30,7 +30,6 @@ #include "modplatform/modrinth/ModrinthAPI.h" const QStringList ModrinthPackExportTask::PREFIXES({ "mods", "coremods", "resourcepacks", "texturepacks", "shaderpacks" }); -const QStringList ModrinthPackExportTask::ALLOWED_HOSTS({ "mods", "coremods", "resourcepacks", "texturepacks", "shaderpacks" }); ModrinthPackExportTask::ModrinthPackExportTask(const QString& name, const QString& version, From d7a137ad13854d008de438786d6a97b4092bac99 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Thu, 6 Apr 2023 19:24:19 +0100 Subject: [PATCH 104/207] Remove more prototype not good code Signed-off-by: TheKodeToad --- .../modrinth/ModrinthPackExportTask.cpp | 22 +++++++++++-------- .../modrinth/ModrinthPackExportTask.h | 2 ++ 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp b/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp index cfb59fc3a..772928e90 100644 --- a/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp +++ b/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp @@ -37,7 +37,13 @@ ModrinthPackExportTask::ModrinthPackExportTask(const QString& name, InstancePtr instance, const QString& output, MMCZip::FilterFunction filter) - : name(name), version(version), summary(summary), instance(instance), output(output), filter(filter) + : name(name) + , version(version) + , summary(summary) + , instance(instance) + , mcInstance(dynamic_cast(instance.get())) + , output(output) + , filter(filter) {} void ModrinthPackExportTask::executeTask() @@ -73,17 +79,15 @@ void ModrinthPackExportTask::collectFiles() pendingHashes.clear(); resolvedFiles.clear(); - const MinecraftInstance* mcInstance = dynamic_cast(instance.get()); - auto mods = mcInstance->loaderModList(); - mods->update(); - connect(mods.get(), &ModFolderModel::updateFinished, this, &ModrinthPackExportTask::collectHashes); + if (mcInstance) { + mcInstance->loaderModList()->update(); + connect(mcInstance->loaderModList().get(), &ModFolderModel::updateFinished, this, &ModrinthPackExportTask::collectHashes); + } else + collectHashes(); } void ModrinthPackExportTask::collectHashes() { - const MinecraftInstance* mcInstance = dynamic_cast(instance.get()); - auto mods = mcInstance->loaderModList(); - QDir mc(instance->gameRoot()); for (QFileInfo file : files) { QString relative = mc.relativeFilePath(file.absoluteFilePath()); @@ -110,7 +114,7 @@ void ModrinthPackExportTask::collectHashes() } hash.addData(data); - auto allMods = mods->allMods(); + auto allMods = mcInstance->loaderModList()->allMods(); if (auto modIter = std::find_if(allMods.begin(), allMods.end(), [&file](Mod* mod) { return mod->fileinfo() == file; }); modIter != allMods.end()) { Mod* mod = *modIter; diff --git a/launcher/modplatform/modrinth/ModrinthPackExportTask.h b/launcher/modplatform/modrinth/ModrinthPackExportTask.h index 217956db5..021d8a568 100644 --- a/launcher/modplatform/modrinth/ModrinthPackExportTask.h +++ b/launcher/modplatform/modrinth/ModrinthPackExportTask.h @@ -20,6 +20,7 @@ #include "BaseInstance.h" #include "MMCZip.h" +#include "minecraft/MinecraftInstance.h" #include "modplatform/modrinth/ModrinthAPI.h" #include "tasks/Task.h" @@ -48,6 +49,7 @@ class ModrinthPackExportTask : public Task { // inputs const QString name, version, summary; const InstancePtr instance; + const MinecraftInstance* mcInstance; const QString output; const MMCZip::FilterFunction filter; From 012d8bb4680b54bedd9b58fb6102c768002cf0ed Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Thu, 6 Apr 2023 19:58:09 +0100 Subject: [PATCH 105/207] Revert concurrent syntax Signed-off-by: TheKodeToad --- .../modplatform/modrinth/ModrinthPackExportTask.cpp | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp b/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp index 772928e90..f4b2bcd38 100644 --- a/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp +++ b/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp @@ -20,14 +20,12 @@ #include #include -#include #include +#include #include "Json.h" #include "MMCZip.h" -#include "minecraft/MinecraftInstance.h" #include "minecraft/PackProfile.h" #include "minecraft/mod/ModFolderModel.h" -#include "modplatform/modrinth/ModrinthAPI.h" const QStringList ModrinthPackExportTask::PREFIXES({ "mods", "coremods", "resourcepacks", "texturepacks", "shaderpacks" }); @@ -190,13 +188,11 @@ void ModrinthPackExportTask::parseApiResponse(QByteArray* response) qWarning() << "Failed to parse versions response" << e.what(); } pendingHashes.clear(); - - buildZip(); } void ModrinthPackExportTask::buildZip() { - QThreadPool::globalInstance()->start([this]() { + static_cast(QtConcurrent::run(QThreadPool::globalInstance(), [this]() { setStatus("Adding files..."); QuaZip zip(output); if (!zip.open(QuaZip::mdCreate)) { @@ -249,7 +245,7 @@ void ModrinthPackExportTask::buildZip() } QMetaObject::invokeMethod(this, &ModrinthPackExportTask::emitSucceeded, Qt::QueuedConnection); - }); + })); } QByteArray ModrinthPackExportTask::generateIndex() @@ -301,6 +297,7 @@ QByteArray ModrinthPackExportTask::generateIndex() QJsonObject hashes; hashes["sha1"] = value.sha1; hashes["sha512"] = value.sha512; + file["hashes"] = hashes; file["fileSize"] = value.size; From b65f4c9536691096d44ae427ff71bfe971592747 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Thu, 6 Apr 2023 19:59:15 +0100 Subject: [PATCH 106/207] Better collectFileListRecursively error Signed-off-by: TheKodeToad --- launcher/modplatform/modrinth/ModrinthPackExportTask.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp b/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp index f4b2bcd38..2618074e8 100644 --- a/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp +++ b/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp @@ -70,7 +70,7 @@ void ModrinthPackExportTask::collectFiles() { files.clear(); if (!MMCZip::collectFileListRecursively(instance->gameRoot(), nullptr, &files, filter)) { - emitFailed(tr("Could not collect list of files")); + emitFailed(tr("Could not search for files")); return; } From 813ccc1381f8cb128d0bdcaac1e62d76e8b74289 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Fri, 7 Apr 2023 11:03:11 +0100 Subject: [PATCH 107/207] How did i- Signed-off-by: TheKodeToad --- launcher/modplatform/modrinth/ModrinthPackExportTask.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp b/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp index 2618074e8..fc16f9127 100644 --- a/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp +++ b/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp @@ -164,7 +164,7 @@ void ModrinthPackExportTask::parseApiResponse(QByteArray* response) task = nullptr; try { - QJsonDocument doc = Json::requireDocument(*response); + const QJsonDocument doc = Json::requireDocument(*response); QMapIterator iterator(pendingHashes); while (iterator.hasNext()) { @@ -188,6 +188,7 @@ void ModrinthPackExportTask::parseApiResponse(QByteArray* response) qWarning() << "Failed to parse versions response" << e.what(); } pendingHashes.clear(); + buildZip(); } void ModrinthPackExportTask::buildZip() 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 108/207] 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 e7404f2d9..f2ef734b6 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 109/207] 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 516523207..816dde723 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 906efd1a0..55bd3eea7 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 110/207] 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 879af5356..c33694e97 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 91c5fc63b..0d24a4e90 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 22043e441..8f0757e1a 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 2e5b7f25d..a0a638bd0 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 24875e331..a95bc8752 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 b5c03defb..05f31233e 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 134064853..94ac8a245 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 9af44b5a0..87f36d9c1 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 111/207] 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 43c71a765..4369d2496 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 3a7961834abbd28014de89799514510e74f365ab Mon Sep 17 00:00:00 2001 From: Kode Date: Sun, 9 Apr 2023 21:28:40 +0100 Subject: [PATCH 112/207] Remove `seek` Don't need it if the data is already in a byte array. Signed-off-by: Kode --- launcher/modplatform/modrinth/ModrinthPackExportTask.cpp | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp b/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp index fc16f9127..ed50fd20e 100644 --- a/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp +++ b/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp @@ -122,10 +122,6 @@ void ModrinthPackExportTask::collectHashes() if (!url.isEmpty() && BuildConfig.MODRINTH_MRPACK_HOSTS.contains(url.host())) { qDebug() << "Resolving" << relative << "from index"; - // we've already read it - // let's go back! - openFile.seek(openFile.size()); - QCryptographicHash hash2(QCryptographicHash::Algorithm::Sha1); hash2.addData(data); @@ -307,4 +303,4 @@ QByteArray ModrinthPackExportTask::generateIndex() obj["files"] = files; return QJsonDocument(obj).toJson(QJsonDocument::Compact); -} \ No newline at end of file +} 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 113/207] 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 f2ef734b6..ba453df6a 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 01077aa51..e3b7c9756 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 114/207] 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 4369d2496..663bfc5f8 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 b8e0c8ebc62ce22d5dcaf4295d80c6070eb45f49 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Mon, 17 Apr 2023 10:16:03 +0100 Subject: [PATCH 115/207] Boring changes Signed-off-by: TheKodeToad --- buildconfig/BuildConfig.h | 3 ++- launcher/FileIgnoreProxy.cpp | 2 +- launcher/FileIgnoreProxy.h | 2 +- launcher/modplatform/modrinth/ModrinthPackExportTask.h | 2 +- launcher/ui/MainWindow.h | 2 +- launcher/ui/dialogs/ExportMrPackDialog.cpp | 3 ++- 6 files changed, 8 insertions(+), 6 deletions(-) diff --git a/buildconfig/BuildConfig.h b/buildconfig/BuildConfig.h index 38fa3a659..8543d7241 100644 --- a/buildconfig/BuildConfig.h +++ b/buildconfig/BuildConfig.h @@ -1,8 +1,9 @@ // SPDX-License-Identifier: GPL-3.0-only /* - * PolyMC - Minecraft Launcher + * Prism Launcher - Minecraft Launcher * Copyright (c) 2022 Jamie Mansfield * 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 diff --git a/launcher/FileIgnoreProxy.cpp b/launcher/FileIgnoreProxy.cpp index fd05624af..4fd1ef912 100644 --- a/launcher/FileIgnoreProxy.cpp +++ b/launcher/FileIgnoreProxy.cpp @@ -258,4 +258,4 @@ bool FileIgnoreProxy::filterAcceptsColumn(int source_column, const QModelIndex& return false; return true; -} \ No newline at end of file +} diff --git a/launcher/FileIgnoreProxy.h b/launcher/FileIgnoreProxy.h index baf05c7a6..a5a1153d8 100644 --- a/launcher/FileIgnoreProxy.h +++ b/launcher/FileIgnoreProxy.h @@ -69,4 +69,4 @@ class FileIgnoreProxy : public QSortFilterProxyModel { private: const QString root; SeparatorPrefixTree<'/'> blocked; -}; \ No newline at end of file +}; diff --git a/launcher/modplatform/modrinth/ModrinthPackExportTask.h b/launcher/modplatform/modrinth/ModrinthPackExportTask.h index 021d8a568..8b17f6445 100644 --- a/launcher/modplatform/modrinth/ModrinthPackExportTask.h +++ b/launcher/modplatform/modrinth/ModrinthPackExportTask.h @@ -67,4 +67,4 @@ class ModrinthPackExportTask : public Task { void buildZip(); QByteArray generateIndex(); -}; \ No newline at end of file +}; diff --git a/launcher/ui/MainWindow.h b/launcher/ui/MainWindow.h index 35b4792d6..a0f912df4 100644 --- a/launcher/ui/MainWindow.h +++ b/launcher/ui/MainWindow.h @@ -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) 2023 TheKodeToad * diff --git a/launcher/ui/dialogs/ExportMrPackDialog.cpp b/launcher/ui/dialogs/ExportMrPackDialog.cpp index a622eb304..bc983efe4 100644 --- a/launcher/ui/dialogs/ExportMrPackDialog.cpp +++ b/launcher/ui/dialogs/ExportMrPackDialog.cpp @@ -99,4 +99,5 @@ void ExportMrPackDialog::done(int result) } QDialog::done(result); -} \ No newline at end of file +} + From ba17efa3816f13a6c986f2b9223f71fa1d560aac Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Mon, 17 Apr 2023 13:18:25 +0100 Subject: [PATCH 116/207] Smol fixes Signed-off-by: TheKodeToad --- .../modrinth/ModrinthPackExportTask.cpp | 59 ++++++++++--------- .../modrinth/ModrinthPackExportTask.h | 2 + 2 files changed, 32 insertions(+), 29 deletions(-) diff --git a/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp b/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp index ed50fd20e..6da9d3c19 100644 --- a/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp +++ b/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp @@ -28,6 +28,7 @@ #include "minecraft/mod/ModFolderModel.h" const QStringList ModrinthPackExportTask::PREFIXES({ "mods", "coremods", "resourcepacks", "texturepacks", "shaderpacks" }); +const QStringList ModrinthPackExportTask::FILE_EXTENSIONS({ "jar", "litemod", "zip" }); ModrinthPackExportTask::ModrinthPackExportTask(const QString& name, const QString& version, @@ -40,6 +41,7 @@ ModrinthPackExportTask::ModrinthPackExportTask(const QString& name, , summary(summary) , instance(instance) , mcInstance(dynamic_cast(instance.get())) + , gameRoot(instance->gameRoot()) , output(output) , filter(filter) {} @@ -86,18 +88,19 @@ void ModrinthPackExportTask::collectFiles() void ModrinthPackExportTask::collectHashes() { - QDir mc(instance->gameRoot()); - for (QFileInfo file : files) { - QString relative = mc.relativeFilePath(file.absoluteFilePath()); + for (const QFileInfo& file : files) { + const QString relative = gameRoot.relativeFilePath(file.absoluteFilePath()); // require sensible file types - if (!(relative.endsWith(".zip") || relative.endsWith(".jar") || relative.endsWith(".litemod"))) - continue; - if (!std::any_of(PREFIXES.begin(), PREFIXES.end(), [&relative](const QString& prefix) { return relative.startsWith(prefix + QDir::separator()); })) continue; + if (!std::any_of(FILE_EXTENSIONS.begin(), FILE_EXTENSIONS.end(), [&relative](const QString& extension) { + return relative.endsWith('.' + extension) || relative.endsWith('.' + extension + ".disabled"); + })) { + continue; + } - QCryptographicHash hash(QCryptographicHash::Algorithm::Sha512); + QCryptographicHash sha512(QCryptographicHash::Algorithm::Sha512); QFile openFile(file.absoluteFilePath()); if (!openFile.open(QFile::ReadOnly)) { @@ -105,27 +108,27 @@ void ModrinthPackExportTask::collectHashes() continue; } - QByteArray data = openFile.readAll(); + const QByteArray data = openFile.readAll(); if (openFile.error() != QFileDevice::NoError) { qWarning() << "Could not read" << file; continue; } - hash.addData(data); + sha512.addData(data); auto allMods = mcInstance->loaderModList()->allMods(); if (auto modIter = std::find_if(allMods.begin(), allMods.end(), [&file](Mod* mod) { return mod->fileinfo() == file; }); modIter != allMods.end()) { - Mod* mod = *modIter; + const Mod* mod = *modIter; if (mod->metadata() != nullptr) { QUrl& url = mod->metadata()->url; // most likely some of these may be from curseforge if (!url.isEmpty() && BuildConfig.MODRINTH_MRPACK_HOSTS.contains(url.host())) { qDebug() << "Resolving" << relative << "from index"; - QCryptographicHash hash2(QCryptographicHash::Algorithm::Sha1); - hash2.addData(data); + QCryptographicHash sha1(QCryptographicHash::Algorithm::Sha1); + sha1.addData(data); - ResolvedFile file{ hash2.result().toHex(), hash.result().toHex(), url.toString(), openFile.size() }; + ResolvedFile file{ sha1.result().toHex(), sha512.result().toHex(), url.toString(), openFile.size() }; resolvedFiles[relative] = file; // nice! we've managed to resolve based on local metadata! @@ -136,7 +139,7 @@ void ModrinthPackExportTask::collectHashes() } qDebug() << "Enqueueing" << relative << "for Modrinth query"; - pendingHashes[relative] = hash.result().toHex(); + pendingHashes[relative] = sha512.result().toHex(); } makeApiRequest(); @@ -166,11 +169,11 @@ void ModrinthPackExportTask::parseApiResponse(QByteArray* response) while (iterator.hasNext()) { iterator.next(); - QJsonObject obj = doc[iterator.value()].toObject(); + const QJsonObject obj = doc[iterator.value()].toObject(); if (obj.isEmpty()) continue; - QJsonArray files = obj["files"].toArray(); + const QJsonArray files = obj["files"].toArray(); if (auto fileIter = std::find_if(files.begin(), files.end(), [&iterator](const QJsonValue& file) { return file["hashes"]["sha512"] == iterator.value(); }); fileIter != files.end()) { @@ -180,7 +183,7 @@ void ModrinthPackExportTask::parseApiResponse(QByteArray* response) fileIter->toObject()["url"].toString(), fileIter->toObject()["size"].toInt() }; } } - } catch (Json::JsonException& e) { + } catch (const Json::JsonException& e) { qWarning() << "Failed to parse versions response" << e.what(); } pendingHashes.clear(); @@ -212,8 +215,7 @@ void ModrinthPackExportTask::buildZip() } indexFile.write(generateIndex()); - QDir mc(instance->gameRoot()); - size_t i = 0; + size_t progress = 0; for (const QFileInfo& file : files) { if (pendingAbort) { QFile::remove(output); @@ -221,15 +223,15 @@ void ModrinthPackExportTask::buildZip() return; } - setProgress(i, files.length()); - QString relative = mc.relativeFilePath(file.absoluteFilePath()); + setProgress(progress, files.length()); + const QString relative = gameRoot.relativeFilePath(file.absoluteFilePath()); if (!resolvedFiles.contains(relative) && !JlCompress::compressFile(&zip, file.absoluteFilePath(), "overrides/" + relative)) { QFile::remove(output); QMetaObject::invokeMethod( this, [this, relative]() { emitFailed(tr("Could not read and compress %1").arg(relative)); }, Qt::QueuedConnection); return; } - i++; + progress++; } zip.close(); @@ -255,14 +257,13 @@ QByteArray ModrinthPackExportTask::generateIndex() if (!summary.isEmpty()) obj["summary"] = summary; - MinecraftInstance* mc = dynamic_cast(instance.get()); - if (mc) { - auto profile = mc->getPackProfile(); + if (mcInstance) { + auto profile = mcInstance->getPackProfile(); // collect all supported components - ComponentPtr minecraft = profile->getComponent("net.minecraft"); - ComponentPtr quilt = profile->getComponent("org.quiltmc.quilt-loader"); - ComponentPtr fabric = profile->getComponent("net.fabricmc.fabric-loader"); - ComponentPtr forge = profile->getComponent("net.minecraftforge"); + const ComponentPtr minecraft = profile->getComponent("net.minecraft"); + const ComponentPtr quilt = profile->getComponent("org.quiltmc.quilt-loader"); + const ComponentPtr fabric = profile->getComponent("net.fabricmc.fabric-loader"); + const ComponentPtr forge = profile->getComponent("net.minecraftforge"); // convert all available components to mrpack dependencies QJsonObject dependencies; diff --git a/launcher/modplatform/modrinth/ModrinthPackExportTask.h b/launcher/modplatform/modrinth/ModrinthPackExportTask.h index 8b17f6445..c00751f2f 100644 --- a/launcher/modplatform/modrinth/ModrinthPackExportTask.h +++ b/launcher/modplatform/modrinth/ModrinthPackExportTask.h @@ -44,12 +44,14 @@ class ModrinthPackExportTask : public Task { }; static const QStringList PREFIXES; + static const QStringList FILE_EXTENSIONS; static const QStringList ALLOWED_HOSTS; // inputs const QString name, version, summary; const InstancePtr instance; const MinecraftInstance* mcInstance; + const QDir gameRoot; const QString output; const MMCZip::FilterFunction filter; From 2e9403a324bf7a62fd61b4df78d6c9cc13f65d01 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Mon, 17 Apr 2023 13:19:59 +0100 Subject: [PATCH 117/207] This was moved Signed-off-by: TheKodeToad --- launcher/modplatform/modrinth/ModrinthPackExportTask.h | 1 - 1 file changed, 1 deletion(-) diff --git a/launcher/modplatform/modrinth/ModrinthPackExportTask.h b/launcher/modplatform/modrinth/ModrinthPackExportTask.h index c00751f2f..a0b006b9a 100644 --- a/launcher/modplatform/modrinth/ModrinthPackExportTask.h +++ b/launcher/modplatform/modrinth/ModrinthPackExportTask.h @@ -45,7 +45,6 @@ class ModrinthPackExportTask : public Task { static const QStringList PREFIXES; static const QStringList FILE_EXTENSIONS; - static const QStringList ALLOWED_HOSTS; // inputs const QString name, version, summary; 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 118/207] 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 52cc868a7..e29e22709 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 c8d5c34e0..763a9b5cc 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 1dfc64324..13174f6e8 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 041583d1d..922580ce3 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 67d70066f..48274974d 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 28bd153d4..b67316b02 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 ae29a972f..de21c4741 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 bb82e1e26..a21634b7a 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 48cf5d428..3b050ac0f 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 c73a11b6c..1d5ea36da 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 185d69b6b..38069f90a 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 921bd0942..fbaaeacb6 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 83a84685b..b9a1c9d3f 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 c7987875a..711f87392 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 2c1f0a2ec..ac8c5b361 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 e8a1d0b0e..30c1953f8 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 7e1df322f..01ec46dba 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 0d7ca7691..0ec822512 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 38fe058b5..1ff8f601a 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 ccf43c2dc..f3cdb786d 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 5a0b2e747..f58b746ad 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 a72c32d3b..ab425f1a0 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 f8ac9bc24..a50f9afae 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 8a0bc0455..634acd341 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 1dc0d109f..15c0cab6d 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 b1acc3c0a..28ffb9cdc 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 7fff3b93c..fffb96f20 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 709736d3f9a77206b5b6f809e5e45495f1db1315 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Mon, 17 Apr 2023 13:52:51 +0100 Subject: [PATCH 119/207] Make response const I don't think the segfault fix was ideal :P Signed-off-by: TheKodeToad --- launcher/modplatform/modrinth/ModrinthPackExportTask.cpp | 6 ++---- launcher/modplatform/modrinth/ModrinthPackExportTask.h | 2 +- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp b/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp index 6da9d3c19..549321319 100644 --- a/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp +++ b/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp @@ -56,9 +56,7 @@ void ModrinthPackExportTask::executeTask() bool ModrinthPackExportTask::abort() { if (task != nullptr) { - if (!task->abort()) - return false; - + task->abort(); task = nullptr; emitAborted(); return true; @@ -158,7 +156,7 @@ void ModrinthPackExportTask::makeApiRequest() } } -void ModrinthPackExportTask::parseApiResponse(QByteArray* response) +void ModrinthPackExportTask::parseApiResponse(const QByteArray* response) { task = nullptr; diff --git a/launcher/modplatform/modrinth/ModrinthPackExportTask.h b/launcher/modplatform/modrinth/ModrinthPackExportTask.h index a0b006b9a..25045cf2a 100644 --- a/launcher/modplatform/modrinth/ModrinthPackExportTask.h +++ b/launcher/modplatform/modrinth/ModrinthPackExportTask.h @@ -64,7 +64,7 @@ class ModrinthPackExportTask : public Task { void collectFiles(); void collectHashes(); void makeApiRequest(); - void parseApiResponse(QByteArray* response); + void parseApiResponse(const QByteArray* response); void buildZip(); QByteArray generateIndex(); From f41426f3945ff60a26b34935bb1b0b43d8de800b Mon Sep 17 00:00:00 2001 From: Japa Date: Fri, 21 Apr 2023 00:30:38 -0300 Subject: [PATCH 120/207] 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 af4da5d05..f9f4a56b8 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 121/207] 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 f9f4a56b8..3ab8b3af9 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 122/207] 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 3ab8b3af9..3183b41d4 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 123/207] 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 3183b41d4..b2171a85d 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 124/207] 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 074570e31..ee36175fb 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 68d4751cd..000000000 --- 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 97b1eb0b1..000000000 --- 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 421527aef..000000000 --- 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 a8b6f35ec..000000000 --- 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 df182f096..64ed76739 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 e2b548f2d..000000000 --- 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 1be28e995..000000000 --- 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 e80654158..000000000 --- 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 d7a120f06..000000000 --- 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 7d59a6ae7..000000000 --- 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 631ae7f56..000000000 --- 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 8de0f4e65..000000000 --- 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 125/207] 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 ad45aa2d2..a8fce879c 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 4de0f73bb..def73b855 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 bd985c5b4..d98526dfd 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 000000000..7e422c3e8 --- /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 2fa565016..d109d63db 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 c4a6ab318..36c8cf8f4 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 da27a505f..5342d693b 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 e48e6f47d..f0347cabf 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 86bf08989..0d5c05ebb 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 4c51d6e96..8a0bc0455 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 ff4301725..4d735511c 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 d13937c00..4be8133c0 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 126/207] 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 af4da5d05..6b7ca84f7 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 3681bcdad..62e55cd4c 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 bd32dd4e3..10fb4e3c7 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 91d161757..6ae25d338 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 84e70db90..46f5087f0 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 29a0c7364..e19734689 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 f840b2de3..fdf5f3315 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 0480d8baf..6eba4e2ec 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 cb620ce2b..66d5a074b 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 a3aa958fa..6f3f2811b 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 5a32cfaf7..1e218537e 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 261f83b49..246997bdb 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 e20a7613a..72b7db641 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 347bd39ff..d75bb5fe3 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 e38b8e931..054d81c41 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 127/207] 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 530695973..842eb60ff 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 128/207] 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 ee36175fb..24330adf0 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 e8a1d0b0e..26488a43d 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 7e1df322f..cbee0d036 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 38fe058b5..f9456bd6c 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 3cc37b2a2..48e1bc18f 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 d074d2e2e..934697667 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 9ea1bb26d..452dc2e38 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 3d607dcac..a6ab15b8d 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 da73a4492..da627af38 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 0b4b78a4e..a7e203fbb 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 34ab71e32..0a9989870 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 000000000..84ea5f207 --- /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 000000000..3375a0bcf --- /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 000000000..966fdb88d --- /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 95eb4a30b..678382ba8 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 129/207] 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 080828a8e..c196396d4 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 68e3e92cb..dbc891ffe 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 98bcf2592..61b918aaf 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 28bd153d4..1640d1152 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 d72bc7bed..1c3f6fb71 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 07ad48823..3ce808f88 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 8ccb0e1d3..31fd5eb11 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 96fd3ba35..75e5c5720 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 b94101111..415b9a660 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 4bd8b7f22..28026732c 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 964b559c7..3cb6b61a1 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 8d45fc5c3..36c142acb 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 6814e6457..2fb656ea9 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 8fd43d219..f07ca24af 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 77c503f09..c26d6a5a6 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 26488a43d..a4c3ebfc3 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 48e1bc18f..fde7d0ad8 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 934697667..9d4413c6a 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 452dc2e38..5aada8766 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 a6ab15b8d..863f8a4c4 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 da627af38..f7a3a862f 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 a7e203fbb..95a4db166 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 0a9989870..475976894 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 966fdb88d..ceae5e268 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 678382ba8..dabe5da26 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 130/207] 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 a4c3ebfc3..3eef91172 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 fde7d0ad8..41c405db9 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 f7a3a862f..7ab766e43 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 475976894..a4d08124c 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 131/207] 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 a7c97aa77..c8855cbc8 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 24330adf0..e49a4562a 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 c196396d4..8a48873ef 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 dbc891ffe..5f98a1845 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 b29af8576..5f1336225 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 1640d1152..c8e576a50 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 3ce808f88..35430bb0f 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 28026732c..d130914fd 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 3cb6b61a1..86fd2ab49 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 2fb656ea9..bb8227aa2 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 3eef91172..86e4bd97f 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 cbee0d036..ae2b034c5 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 ba0caf6c0..3c2948d4a 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 0d7ca7691..855211f70 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 c730fdbf2..46bfe37dc 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 d9e785c52..d5df3799a 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 ccf43c2dc..518e302c5 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 000000000..e5b42bc4c --- /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 000000000..e65e328ca --- /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 41c405db9..8d4f94ed6 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 9d4413c6a..43e9f8662 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 5aada8766..ffde4a104 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 863f8a4c4..96b3b855d 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 7ab766e43..1937c553f 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 f736af087..9181de7f8 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 ceae5e268..5431eab60 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 132/207] 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 86e4bd97f..e7536dc9b 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 133/207] 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 d5df3799a..24f456e39 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 518e302c5..195e16797 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 134/207] 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 ae2b034c5..d8c18319f 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 3c2948d4a..d5f090123 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 dffbdca67..40134b5f4 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 855211f70..c90f8d4d2 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 37f4b49a2..0dcb5668d 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 46bfe37dc..fc997553b 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 f59480857..f9f7d2337 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 f9456bd6c..c1b0ef4a1 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 4bcd40b5d..3869316e3 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 cd5d5e48a..764cec18b 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 24f456e39..9c2b20c6d 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 eb315c2b8..b72ab5b00 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 195e16797..85f364d34 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 5a0b2e747..5182ab099 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 8d4f94ed6..3c62cf4de 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 43e9f8662..1cf1520e7 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 034499dfa..89187a26d 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 db7d4d9a4..a344343ef 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 b2f86328a..abf7536b9 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 5eace96ee..cec3b2be8 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 ffde4a104..e5f619198 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 135/207] 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 e49a4562a..5253a5176 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 e7536dc9b..bfd0be28a 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 d5f090123..1ecb21fdf 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 c90f8d4d2..e521c9e9a 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 e5b42bc4c..a9b9db7cf 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 e65e328ca..b692e7075 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 fc997553b..e203bc06d 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 9c2b20c6d..595279a3a 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 85f364d34..8045f8503 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 136/207] 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 3c62cf4de..8434ecdcd 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 1cf1520e7..974e8d4d6 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 e5f619198..fd1b88985 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 96b3b855d..9ae70270e 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 1937c553f..7594484e8 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 95a4db166..6779b9494 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 137/207] 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 05c69c893..2ff26aeee 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 c8855cbc8..f75955123 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 0d24a4e90..3d833edc3 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 5253a5176..a3ef20e84 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 000000000..eba5390db --- /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 49e225007..a809c55dd 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 138/207] 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 8434ecdcd..439879e6c 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 974e8d4d6..284fa3454 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 9ae70270e..d71eaf6ba 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 139/207] 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 3d833edc3..ced0af17d 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 140/207] 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 439879e6c..37435739b 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 284fa3454..aec707bc2 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 fd1b88985..b0addd46a 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 d71eaf6ba..04a091873 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 7594484e8..94feee44c 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 6779b9494..fc9a0fbc3 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 141/207] 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 eba5390db..4543378f9 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 142/207] 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 bfd0be28a..bf0e5c269 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 c1b0ef4a1..df6ed9957 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 37435739b..dbb4d94dd 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 e0380960fda706a70bef6f63610bcfa68775e21d Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Tue, 2 May 2023 14:23:27 +0100 Subject: [PATCH 143/207] Change to use future Signed-off-by: TheKodeToad --- .../modrinth/ModrinthPackExportTask.cpp | 57 +++++++++++-------- .../modrinth/ModrinthPackExportTask.h | 8 ++- 2 files changed, 40 insertions(+), 25 deletions(-) diff --git a/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp b/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp index 549321319..d2ef06535 100644 --- a/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp +++ b/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp @@ -62,8 +62,11 @@ bool ModrinthPackExportTask::abort() return true; } - pendingAbort = true; - return true; + if (buildZipFuture.isRunning()) { + buildZipFuture.cancel(); + return true; + } + return false; } void ModrinthPackExportTask::collectFiles() @@ -190,44 +193,37 @@ void ModrinthPackExportTask::parseApiResponse(const QByteArray* response) void ModrinthPackExportTask::buildZip() { - static_cast(QtConcurrent::run(QThreadPool::globalInstance(), [this]() { - setStatus("Adding files..."); + setStatus("Adding files..."); + + buildZipFuture = QtConcurrent::run(QThreadPool::globalInstance(), [this]() { QuaZip zip(output); if (!zip.open(QuaZip::mdCreate)) { QFile::remove(output); - emitFailed(tr("Could not create file")); - return; + return BuildZipResult(tr("Could not create file")); } - if (pendingAbort) { - QMetaObject::invokeMethod(this, &ModrinthPackExportTask::emitAborted, Qt::QueuedConnection); - return; - } + if (buildZipFuture.isCanceled()) + return BuildZipResult(); QuaZipFile indexFile(&zip); if (!indexFile.open(QIODevice::WriteOnly, QuaZipNewInfo("modrinth.index.json"))) { QFile::remove(output); - QMetaObject::invokeMethod( - this, [this]() { emitFailed(tr("Could not create index")); }, Qt::QueuedConnection); - return; + return BuildZipResult(tr("Could not create index")); } indexFile.write(generateIndex()); size_t progress = 0; for (const QFileInfo& file : files) { - if (pendingAbort) { + if (buildZipFuture.isCanceled()) { QFile::remove(output); - QMetaObject::invokeMethod(this, &ModrinthPackExportTask::emitAborted, Qt::QueuedConnection); - return; + return BuildZipResult(); } setProgress(progress, files.length()); const QString relative = gameRoot.relativeFilePath(file.absoluteFilePath()); if (!resolvedFiles.contains(relative) && !JlCompress::compressFile(&zip, file.absoluteFilePath(), "overrides/" + relative)) { QFile::remove(output); - QMetaObject::invokeMethod( - this, [this, relative]() { emitFailed(tr("Could not read and compress %1").arg(relative)); }, Qt::QueuedConnection); - return; + return BuildZipResult(tr("Could not read and compress %1").arg(relative)); } progress++; } @@ -236,13 +232,26 @@ void ModrinthPackExportTask::buildZip() if (zip.getZipError() != 0) { QFile::remove(output); - QMetaObject::invokeMethod( - this, [this]() { emitFailed(tr("A zip error occurred")); }, Qt::QueuedConnection); - return; + return BuildZipResult(tr("A zip error occurred")); } - QMetaObject::invokeMethod(this, &ModrinthPackExportTask::emitSucceeded, Qt::QueuedConnection); - })); + return BuildZipResult(); + }); + connect(&buildZipWatcher, &QFutureWatcher::finished, this, &ModrinthPackExportTask::finish); + buildZipWatcher.setFuture(buildZipFuture); +} + +void ModrinthPackExportTask::finish() +{ + if (buildZipFuture.isCanceled()) + emitAborted(); + else { + const BuildZipResult result = buildZipFuture.result(); + if (result.has_value()) + emitFailed(result.value()); + else + emitSucceeded(); + } } QByteArray ModrinthPackExportTask::generateIndex() diff --git a/launcher/modplatform/modrinth/ModrinthPackExportTask.h b/launcher/modplatform/modrinth/ModrinthPackExportTask.h index 25045cf2a..5426d6da7 100644 --- a/launcher/modplatform/modrinth/ModrinthPackExportTask.h +++ b/launcher/modplatform/modrinth/ModrinthPackExportTask.h @@ -18,6 +18,8 @@ #pragma once +#include +#include #include "BaseInstance.h" #include "MMCZip.h" #include "minecraft/MinecraftInstance.h" @@ -54,18 +56,22 @@ class ModrinthPackExportTask : public Task { const QString output; const MMCZip::FilterFunction filter; + typedef std::optional BuildZipResult; + ModrinthAPI api; QFileInfoList files; QMap pendingHashes; QMap resolvedFiles; Task::Ptr task; - bool pendingAbort = false; + QFuture buildZipFuture; + QFutureWatcher buildZipWatcher; void collectFiles(); void collectHashes(); void makeApiRequest(); void parseApiResponse(const QByteArray* response); void buildZip(); + void finish(); QByteArray generateIndex(); }; 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 144/207] 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 ba453df6a..3e1137c27 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 145/207] 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 3e1137c27..fdfae5973 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 146/207] 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 5f1336225..b29af8576 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 bf0e5c269..082f963d0 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 df6ed9957..a13d115f7 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 dbb4d94dd..fae2f3dc2 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 04a091873..799ed9452 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 94feee44c..246a0fd49 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 3375a0bcf..8f8aeea2a 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 147/207] 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 f75955123..28c2a9ced 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 148/207] 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 cff071bf5..103881b57 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 149/207] 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 28c2a9ced..1659eb445 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 70bc4135f..1702ec066 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 10ff2ffe6..576b837f5 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 0f3c3669f..f677ebf63 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 1799605b3..271e50992 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 d130914fd..96cea7b7d 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 082f963d0..7617b5a0c 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 a13d115f7..32095041a 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 4543378f9..27f4e6763 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 aec707bc2..6325fc9e7 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 150/207] 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 576b837f5..6a5780b44 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 f677ebf63..5d9e32b6b 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 151/207] 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 27f4e6763..c12d1e109 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 152/207] 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 763a9b5cc..d70f6d005 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 922580ce3..b4c55b3d7 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 153/207] 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 84820eb0e..e08e6fdce 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 5b8d0254408a6fd3e123eb06b7cbcdd20bd54518 Mon Sep 17 00:00:00 2001 From: Kode Date: Fri, 12 May 2023 14:43:55 +0100 Subject: [PATCH 154/207] ty! Co-authored-by: flow Signed-off-by: Kode --- launcher/modplatform/modrinth/ModrinthPackExportTask.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp b/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp index d2ef06535..9a45e46b1 100644 --- a/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp +++ b/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp @@ -64,6 +64,7 @@ bool ModrinthPackExportTask::abort() if (buildZipFuture.isRunning()) { buildZipFuture.cancel(); + // NOTE: Here we don't do `emitAborted()` because it will be done when `buildZipFuture` actually cancels, which may not occur immediately. return true; } return false; From e1b6020b76401eb5a2f164775f3f738f357e4e2d Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Sat, 13 May 2023 18:24:01 +0100 Subject: [PATCH 155/207] Make some changes Signed-off-by: TheKodeToad --- .../modplatform/modrinth/ModrinthPackExportTask.cpp | 13 +++++++++++-- launcher/ui/dialogs/ExportInstanceDialog.cpp | 3 --- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp b/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp index 9a45e46b1..c550fdce6 100644 --- a/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp +++ b/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp @@ -67,11 +67,15 @@ bool ModrinthPackExportTask::abort() // NOTE: Here we don't do `emitAborted()` because it will be done when `buildZipFuture` actually cancels, which may not occur immediately. return true; } + return false; } void ModrinthPackExportTask::collectFiles() { + setAbortable(false); + QCoreApplication::processEvents(); + files.clear(); if (!MMCZip::collectFileListRecursively(instance->gameRoot(), nullptr, &files, filter)) { emitFailed(tr("Could not search for files")); @@ -91,6 +95,8 @@ void ModrinthPackExportTask::collectFiles() void ModrinthPackExportTask::collectHashes() { for (const QFileInfo& file : files) { + QCoreApplication::processEvents(); + const QString relative = gameRoot.relativeFilePath(file.absoluteFilePath()); // require sensible file types if (!std::any_of(PREFIXES.begin(), PREFIXES.end(), @@ -149,6 +155,8 @@ void ModrinthPackExportTask::collectHashes() void ModrinthPackExportTask::makeApiRequest() { + setAbortable(true); + if (pendingHashes.isEmpty()) buildZip(); else { @@ -186,7 +194,8 @@ void ModrinthPackExportTask::parseApiResponse(const QByteArray* response) } } } catch (const Json::JsonException& e) { - qWarning() << "Failed to parse versions response" << e.what(); + emitFailed(tr("Failed to parse versions response: %1").arg(e.what())); + return; } pendingHashes.clear(); buildZip(); @@ -194,7 +203,7 @@ void ModrinthPackExportTask::parseApiResponse(const QByteArray* response) void ModrinthPackExportTask::buildZip() { - setStatus("Adding files..."); + setStatus(tr("Adding files...")); buildZipFuture = QtConcurrent::run(QThreadPool::globalInstance(), [this]() { QuaZip zip(output); diff --git a/launcher/ui/dialogs/ExportInstanceDialog.cpp b/launcher/ui/dialogs/ExportInstanceDialog.cpp index ea01c5e21..57fe8119e 100644 --- a/launcher/ui/dialogs/ExportInstanceDialog.cpp +++ b/launcher/ui/dialogs/ExportInstanceDialog.cpp @@ -45,7 +45,6 @@ #include #include #include -#include "StringUtils.h" #include "SeparatorPrefixTree.h" #include "Application.h" #include @@ -218,5 +217,3 @@ void ExportInstanceDialog::savePackIgnore() qWarning() << e.cause(); } } - -#include "ExportInstanceDialog.moc" From 129e959a3b0a7d21965b15d6a65b0a9d22994838 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Sat, 13 May 2023 18:28:45 +0100 Subject: [PATCH 156/207] Move setAbortable(true) Signed-off-by: TheKodeToad --- launcher/modplatform/modrinth/ModrinthPackExportTask.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp b/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp index c550fdce6..8db89bbd3 100644 --- a/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp +++ b/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp @@ -150,13 +150,12 @@ void ModrinthPackExportTask::collectHashes() pendingHashes[relative] = sha512.result().toHex(); } + setAbortable(true); makeApiRequest(); } void ModrinthPackExportTask::makeApiRequest() { - setAbortable(true); - if (pendingHashes.isEmpty()) buildZip(); else { From 18cfe219fef2aabc1d3260764c02cd3476615176 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Sat, 13 May 2023 18:58:05 +0100 Subject: [PATCH 157/207] =?UTF-8?q?Hopefully=20This=20Works=E2=84=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: TheKodeToad --- launcher/FileIgnoreProxy.cpp | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/launcher/FileIgnoreProxy.cpp b/launcher/FileIgnoreProxy.cpp index 4fd1ef912..a3b7d5054 100644 --- a/launcher/FileIgnoreProxy.cpp +++ b/launcher/FileIgnoreProxy.cpp @@ -129,12 +129,7 @@ bool FileIgnoreProxy::setData(const QModelIndex& index, const QVariant& value, i QString FileIgnoreProxy::relPath(const QString& path) const { - QString prefix = QDir().absoluteFilePath(root); - prefix += '/'; - if (!path.startsWith(prefix)) { - return QString(); - } - return path.mid(prefix.size()); + return QDir(root).relativeFilePath(path); } bool FileIgnoreProxy::setFilterState(QModelIndex index, Qt::CheckState state) From 20781334939fbb15b1f0fec2a7f65b9ad25d02c1 Mon Sep 17 00:00:00 2001 From: Keri Date: Sun, 23 Apr 2023 11:53:21 +0300 Subject: [PATCH 158/207] 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 f08f2ba43..20fabe9d4 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 7537ea1ef532f5d8d50ad7aa7e49c4961ddf7b1c Mon Sep 17 00:00:00 2001 From: leo78913 Date: Sun, 14 May 2023 14:57:51 -0300 Subject: [PATCH 159/207] make instance settings account selector a comboBox Signed-off-by: leo78913 --- .../pages/instance/InstanceSettingsPage.cpp | 54 +++++-------------- .../ui/pages/instance/InstanceSettingsPage.h | 3 +- .../ui/pages/instance/InstanceSettingsPage.ui | 9 +--- 3 files changed, 15 insertions(+), 51 deletions(-) diff --git a/launcher/ui/pages/instance/InstanceSettingsPage.cpp b/launcher/ui/pages/instance/InstanceSettingsPage.cpp index 4b4c73dc6..a583ab1d0 100644 --- a/launcher/ui/pages/instance/InstanceSettingsPage.cpp +++ b/launcher/ui/pages/instance/InstanceSettingsPage.cpp @@ -60,15 +60,13 @@ InstanceSettingsPage::InstanceSettingsPage(BaseInstance *inst, QWidget *parent) m_settings = inst->settings(); ui->setupUi(this); - accountMenu = new QMenu(this); - // Use undocumented property... https://stackoverflow.com/questions/7121718/create-a-scrollbar-in-a-submenu-qt - accountMenu->setStyleSheet("QMenu { menu-scrollable: 1; }"); - ui->instanceAccountSelector->setMenu(accountMenu); - connect(ui->openGlobalJavaSettingsButton, &QCommandLinkButton::clicked, this, &InstanceSettingsPage::globalSettingsButtonClicked); connect(APPLICATION, &Application::globalSettingsAboutToOpen, this, &InstanceSettingsPage::applySettings); connect(APPLICATION, &Application::globalSettingsClosed, this, &InstanceSettingsPage::loadSettings); + connect(ui->instanceAccountSelector, QOverload::of(&QComboBox::currentIndexChanged), this, &InstanceSettingsPage::changeInstanceAccount); loadSettings(); + + updateThresholds(); } @@ -454,36 +452,17 @@ void InstanceSettingsPage::on_javaTestBtn_clicked() void InstanceSettingsPage::updateAccountsMenu() { - accountMenu->clear(); - + ui->instanceAccountSelector->clear(); auto accounts = APPLICATION->accounts(); int accountIndex = accounts->findAccountByProfileId(m_settings->get("InstanceAccountId").toString()); - MinecraftAccountPtr defaultAccount = accounts->defaultAccount(); - - if (accountIndex != -1 && accounts->at(accountIndex)) { - defaultAccount = accounts->at(accountIndex); - } - - if (defaultAccount) { - ui->instanceAccountSelector->setText(defaultAccount->profileName()); - ui->instanceAccountSelector->setIcon(getFaceForAccount(defaultAccount)); - } else { - ui->instanceAccountSelector->setText(tr("No default account")); - ui->instanceAccountSelector->setIcon(APPLICATION->getThemedIcon("noaccount")); - } for (int i = 0; i < accounts->count(); i++) { MinecraftAccountPtr account = accounts->at(i); - QAction* action = new QAction(account->profileName(), this); - action->setData(i); - action->setCheckable(true); - if (accountIndex == i) { - action->setChecked(true); - } - action->setIcon(getFaceForAccount(account)); - accountMenu->addAction(action); - connect(action, SIGNAL(triggered(bool)), this, SLOT(changeInstanceAccount())); + ui->instanceAccountSelector->addItem(getFaceForAccount(account), account->profileName(), i); + if (i == accountIndex) + ui->instanceAccountSelector->setCurrentIndex(i); } + } QIcon InstanceSettingsPage::getFaceForAccount(MinecraftAccountPtr account) @@ -495,20 +474,13 @@ QIcon InstanceSettingsPage::getFaceForAccount(MinecraftAccountPtr account) return APPLICATION->getThemedIcon("noaccount"); } -void InstanceSettingsPage::changeInstanceAccount() +void InstanceSettingsPage::changeInstanceAccount(int index) { - QAction* sAction = (QAction*)sender(); - - Q_ASSERT(sAction->data().type() == QVariant::Type::Int); - - QVariant data = sAction->data(); - int index = data.toInt(); auto accounts = APPLICATION->accounts(); - auto account = accounts->at(index); - m_settings->set("InstanceAccountId", account->profileId()); - - ui->instanceAccountSelector->setText(account->profileName()); - ui->instanceAccountSelector->setIcon(getFaceForAccount(account)); + if (index != -1 && accounts->at(index) && ui->instanceAccountGroupBox->isChecked()) { + auto account = accounts->at(index); + m_settings->set("InstanceAccountId", account->profileId()); + } } void InstanceSettingsPage::on_maxMemSpinBox_valueChanged(int i) diff --git a/launcher/ui/pages/instance/InstanceSettingsPage.h b/launcher/ui/pages/instance/InstanceSettingsPage.h index cb6fbae0c..043c3e25b 100644 --- a/launcher/ui/pages/instance/InstanceSettingsPage.h +++ b/launcher/ui/pages/instance/InstanceSettingsPage.h @@ -95,12 +95,11 @@ private slots: void updateAccountsMenu(); QIcon getFaceForAccount(MinecraftAccountPtr account); - void changeInstanceAccount(); + void changeInstanceAccount(int index); private: Ui::InstanceSettingsPage *ui; BaseInstance *m_instance; SettingsObjectPtr m_settings; unique_qobject_ptr checker; - QMenu *accountMenu = nullptr; }; diff --git a/launcher/ui/pages/instance/InstanceSettingsPage.ui b/launcher/ui/pages/instance/InstanceSettingsPage.ui index 1b9861842..19d6dc02d 100644 --- a/launcher/ui/pages/instance/InstanceSettingsPage.ui +++ b/launcher/ui/pages/instance/InstanceSettingsPage.ui @@ -636,14 +636,7 @@ - - - QToolButton::InstantPopup - - - Qt::ToolButtonTextBesideIcon - - + From 22aaf56855d9f1c4c8e2c2fdfba30a2d40a0ebdc Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Mon, 15 May 2023 18:48:30 +0100 Subject: [PATCH 160/207] De-hardcode .index Signed-off-by: TheKodeToad --- launcher/ui/dialogs/ExportMrPackDialog.cpp | 27 ++++++++++++---------- 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/launcher/ui/dialogs/ExportMrPackDialog.cpp b/launcher/ui/dialogs/ExportMrPackDialog.cpp index bc983efe4..2b9f9174d 100644 --- a/launcher/ui/dialogs/ExportMrPackDialog.cpp +++ b/launcher/ui/dialogs/ExportMrPackDialog.cpp @@ -17,6 +17,7 @@ */ #include "ExportMrPackDialog.h" +#include "minecraft/mod/ModFolderModel.h" #include "ui/dialogs/CustomMessageBox.h" #include "ui/dialogs/ProgressDialog.h" #include "ui_ExportMrPackDialog.h" @@ -36,32 +37,35 @@ ExportMrPackDialog::ExportMrPackDialog(InstancePtr instance, QWidget* parent) ui->name->setText(instance->name()); ui->summary->setText(instance->notes().split(QRegularExpression("\\r?\\n"))[0]); - auto model = new QFileSystemModel(this); + QFileSystemModel* model = new QFileSystemModel(this); // use the game root - everything outside cannot be exported - QString root = instance->gameRoot(); - proxy = new FileIgnoreProxy(root, this); + const QDir root(instance->gameRoot()); + proxy = new FileIgnoreProxy(instance->gameRoot(), this); proxy->setSourceModel(model); - QDir::Filters filter(QDir::AllEntries | QDir::NoDotAndDotDot | QDir::AllDirs | QDir::Hidden); + const QDir::Filters filter(QDir::AllEntries | QDir::NoDotAndDotDot | QDir::AllDirs | QDir::Hidden); - for (QString file : QDir(root).entryList(filter)) { + for (const QString& file : root.entryList(filter)) { if (!(file == "mods" || file == "coremods" || file == "datapacks" || file == "config" || file == "options.txt" || file == "servers.dat")) proxy->blockedPaths().insert(file); } - QDir modsIndex(instance->gameRoot() + "/mods/.index"); - if (modsIndex.exists()) - proxy->blockedPaths().insert("mods/.index"); + MinecraftInstance* mcInstance = dynamic_cast(instance.get()); + if (mcInstance) { + const QDir dir = mcInstance->loaderModList()->indexDir(); + if (dir.exists()) + proxy->blockedPaths().insert(root.relativeFilePath(dir.absolutePath())); + } ui->treeView->setModel(proxy); - ui->treeView->setRootIndex(proxy->mapFromSource(model->index(root))); + ui->treeView->setRootIndex(proxy->mapFromSource(model->index(instance->gameRoot()))); ui->treeView->sortByColumn(0, Qt::AscendingOrder); model->setFilter(filter); - model->setRootPath(root); + model->setRootPath(instance->gameRoot()); - auto headerView = ui->treeView->header(); + QHeaderView* headerView = ui->treeView->header(); headerView->setSectionResizeMode(QHeaderView::ResizeToContents); headerView->setSectionResizeMode(0, QHeaderView::Stretch); } @@ -100,4 +104,3 @@ void ExportMrPackDialog::done(int result) QDialog::done(result); } - From 3be18b58bbb10bd7f93132d5c2bba6286dd85edc Mon Sep 17 00:00:00 2001 From: Kode Date: Mon, 15 May 2023 19:15:56 +0100 Subject: [PATCH 161/207] Better variable name :p Signed-off-by: Kode --- launcher/ui/dialogs/ExportMrPackDialog.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/launcher/ui/dialogs/ExportMrPackDialog.cpp b/launcher/ui/dialogs/ExportMrPackDialog.cpp index 2b9f9174d..1551cc607 100644 --- a/launcher/ui/dialogs/ExportMrPackDialog.cpp +++ b/launcher/ui/dialogs/ExportMrPackDialog.cpp @@ -53,9 +53,9 @@ ExportMrPackDialog::ExportMrPackDialog(InstancePtr instance, QWidget* parent) MinecraftInstance* mcInstance = dynamic_cast(instance.get()); if (mcInstance) { - const QDir dir = mcInstance->loaderModList()->indexDir(); - if (dir.exists()) - proxy->blockedPaths().insert(root.relativeFilePath(dir.absolutePath())); + const QDir index = mcInstance->loaderModList()->indexDir(); + if (index.exists()) + proxy->blockedPaths().insert(root.relativeFilePath(index.absolutePath())); } ui->treeView->setModel(proxy); From 90b330d4baf5c3519788c4cc773775733720d7ef Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Fri, 19 May 2023 18:34:54 +0200 Subject: [PATCH 162/207] 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 2ff26aeee..6bd12630d 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 aaa1fd4c6..993f02f5d 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 967089603..a482f0e38 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 163/207] 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 03ee1a7aa..380fdf8a4 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 8a48873ef..352848f02 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 d3a737bb1..83db642e7 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 8fc17ea91..c280827af 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 86fd2ab49..dae93d1c6 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 fae2f3dc2..5ee145055 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 b0addd46a..29c55cd48 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 799ed9452..6d8bbbb46 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 164/207] 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 5ef9a4090..92590a084 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 165/207] 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 cd3fcc855..7f8d3a067 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 166/207] 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 fffb96f20..74b7ec7c2 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 863027cbe826bbb38f7bebdef436a9682d094cb2 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Mon, 22 May 2023 11:56:37 +0100 Subject: [PATCH 167/207] Enable size grip Signed-off-by: TheKodeToad --- launcher/ui/dialogs/ExportMrPackDialog.ui | 3 +++ 1 file changed, 3 insertions(+) diff --git a/launcher/ui/dialogs/ExportMrPackDialog.ui b/launcher/ui/dialogs/ExportMrPackDialog.ui index 8e6d61ffd..f154d210b 100644 --- a/launcher/ui/dialogs/ExportMrPackDialog.ui +++ b/launcher/ui/dialogs/ExportMrPackDialog.ui @@ -13,6 +13,9 @@ Export Modrinth Pack + + true + From 3c937532f2c76257f47da04b1d71e48bfc839dc6 Mon Sep 17 00:00:00 2001 From: Tayou Date: Wed, 24 May 2023 15:18:08 +0200 Subject: [PATCH 168/207] fix System theme colors on windows Signed-off-by: Tayou --- launcher/ui/themes/SystemTheme.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/ui/themes/SystemTheme.cpp b/launcher/ui/themes/SystemTheme.cpp index a95bc8752..3a746d027 100644 --- a/launcher/ui/themes/SystemTheme.cpp +++ b/launcher/ui/themes/SystemTheme.cpp @@ -43,7 +43,7 @@ SystemTheme::SystemTheme() { themeDebugLog() << "Determining System Theme..."; const auto& style = QApplication::style(); - systemPalette = style->standardPalette(); + systemPalette = QApplication::palette(); QString lowerThemeName = style->objectName(); themeDebugLog() << "System theme seems to be:" << lowerThemeName; QStringList styles = QStyleFactory::keys(); 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 169/207] 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 02cc8b1f1..02b705f0f 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 170/207] 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 02cc8b1f1..bee1ea14c 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 171/207] 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 dc983d9a9..593590f70 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 3e27a7e6b..c26079005 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 172/207] 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 40f1efc4e..8d0223f91 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 3ffe6cb06..afd8b2921 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 db7d26f86..631ae68ce 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 46a02d6ef..1ec42cda8 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 3df9a7876..18c14bf81 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 2101b3946..aabd3be6e 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 716bf853a..c0d9cd95d 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 173/207] 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 c3b58cd63..4ea55efa3 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 631ae68ce..472aa8515 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 c01c89c4a..8c5cf08b7 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 bbd465bc1..f75bb886d 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 972419a81..9039c4d91 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 174/207] 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 010ac15e9..a3c592fdc 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 d582bf7f1f803d0ea8422732b46e25ee05f2fcb0 Mon Sep 17 00:00:00 2001 From: seth Date: Sat, 27 May 2023 13:45:28 -0400 Subject: [PATCH 175/207] feat(nix): flake-utils -> flake-parts Signed-off-by: seth --- default.nix | 15 +++++- flake.lock | 68 ++++++++++++++++-------- flake.nix | 78 +++------------------------- nix/default.nix | 120 ++++++++++--------------------------------- nix/dev.nix | 44 ++++++++++++++++ nix/distribution.nix | 27 ++++++++++ nix/flake-compat.nix | 9 ---- nix/package.nix | 100 ++++++++++++++++++++++++++++++++++++ 8 files changed, 264 insertions(+), 197 deletions(-) create mode 100644 nix/dev.nix create mode 100644 nix/distribution.nix delete mode 100644 nix/flake-compat.nix create mode 100644 nix/package.nix diff --git a/default.nix b/default.nix index 146942d59..c7d0c267d 100644 --- a/default.nix +++ b/default.nix @@ -1 +1,14 @@ -(import nix/flake-compat.nix).defaultNix +( + import + ( + let + lock = builtins.fromJSON (builtins.readFile ./flake.lock); + in + fetchTarball { + url = "https://github.com/edolstra/flake-compat/archive/${lock.nodes.flake-compat.locked.rev}.tar.gz"; + sha256 = lock.nodes.flake-compat.locked.narHash; + } + ) + {src = ./.;} +) +.defaultNix diff --git a/flake.lock b/flake.lock index ad9196a9b..f4122f77d 100644 --- a/flake.lock +++ b/flake.lock @@ -16,29 +16,34 @@ "type": "github" } }, - "flake-compat_2": { - "flake": false, + "flake-parts": { + "inputs": { + "nixpkgs-lib": "nixpkgs-lib" + }, "locked": { - "lastModified": 1673956053, - "narHash": "sha256-4gtG9iQuiKITOjNQQeQIpoIB6b16fm+504Ch3sNKLd8=", - "owner": "edolstra", - "repo": "flake-compat", - "rev": "35bb57c0c8d8b62bbfd284272c928ceb64ddbde9", + "lastModified": 1683560683, + "narHash": "sha256-XAygPMN5Xnk/W2c1aW0jyEa6lfMDZWlQgiNtmHXytPc=", + "owner": "hercules-ci", + "repo": "flake-parts", + "rev": "006c75898cf814ef9497252b022e91c946ba8e17", "type": "github" }, "original": { - "owner": "edolstra", - "repo": "flake-compat", + "owner": "hercules-ci", + "repo": "flake-parts", "type": "github" } }, "flake-utils": { + "inputs": { + "systems": "systems" + }, "locked": { - "lastModified": 1676283394, - "narHash": "sha256-XX2f9c3iySLCw54rJ/CZs+ZK6IQy7GXNY4nSOyu2QG4=", + "lastModified": 1681202837, + "narHash": "sha256-H+Rh19JDwRtpVPAWp64F+rlEtxUWBAQW28eAi3SRSzg=", "owner": "numtide", "repo": "flake-utils", - "rev": "3db36a8b464d0c4532ba1c7dda728f4576d6d073", + "rev": "cfacdce06f30d2b68473a46042957675eebb3401", "type": "github" }, "original": { @@ -100,33 +105,37 @@ "type": "github" } }, - "nixpkgs-stable": { + "nixpkgs-lib": { "locked": { - "lastModified": 1673800717, - "narHash": "sha256-SFHraUqLSu5cC6IxTprex/nTsI81ZQAtDvlBvGDWfnA=", + "dir": "lib", + "lastModified": 1682879489, + "narHash": "sha256-sASwo8gBt7JDnOOstnps90K1wxmVfyhsTPPNTGBPjjg=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "2f9fd351ec37f5d479556cd48be4ca340da59b8f", + "rev": "da45bf6ec7bbcc5d1e14d3795c025199f28e0de0", "type": "github" }, "original": { + "dir": "lib", "owner": "NixOS", - "ref": "nixos-22.11", + "ref": "nixos-unstable", "repo": "nixpkgs", "type": "github" } }, "pre-commit-hooks": { "inputs": { - "flake-compat": "flake-compat_2", - "flake-utils": [ - "flake-utils" + "flake-compat": [ + "flake-compat" ], + "flake-utils": "flake-utils", "gitignore": "gitignore", "nixpkgs": [ "nixpkgs" ], - "nixpkgs-stable": "nixpkgs-stable" + "nixpkgs-stable": [ + "nixpkgs" + ] }, "locked": { "lastModified": 1678376203, @@ -145,11 +154,26 @@ "root": { "inputs": { "flake-compat": "flake-compat", - "flake-utils": "flake-utils", + "flake-parts": "flake-parts", "libnbtplusplus": "libnbtplusplus", "nixpkgs": "nixpkgs", "pre-commit-hooks": "pre-commit-hooks" } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } } }, "root": "root", diff --git a/flake.nix b/flake.nix index f656703ce..c3148fe03 100644 --- a/flake.nix +++ b/flake.nix @@ -3,11 +3,12 @@ inputs = { nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable"; - flake-utils.url = "github:numtide/flake-utils"; + flake-parts.url = "github:hercules-ci/flake-parts"; pre-commit-hooks = { url = "github:cachix/pre-commit-hooks.nix"; inputs.nixpkgs.follows = "nixpkgs"; - inputs.flake-utils.follows = "flake-utils"; + inputs.nixpkgs-stable.follows = "nixpkgs"; + inputs.flake-compat.follows = "flake-compat"; }; flake-compat = { url = "github:edolstra/flake-compat"; @@ -19,73 +20,8 @@ }; }; - outputs = { - self, - nixpkgs, - flake-utils, - pre-commit-hooks, - libnbtplusplus, - ... - }: let - # User-friendly version number. - version = builtins.substring 0 8 self.lastModifiedDate; - - # Supported systems (qtbase is currently broken for "aarch64-darwin") - supportedSystems = with flake-utils.lib.system; [ - x86_64-linux - x86_64-darwin - aarch64-linux - ]; - - packagesFn = pkgs: { - prismlauncher-qt5 = pkgs.libsForQt5.callPackage ./nix { - inherit version self libnbtplusplus; - }; - prismlauncher = pkgs.qt6Packages.callPackage ./nix { - inherit version self libnbtplusplus; - }; - }; - in - 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; - deadnix.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 - deadnix - clang-tools - ]; - - inputsFrom = [self.packages.${system}.default]; - buildInputs = with pkgs; [ccache ninja]; - }; - }) - // { - overlays.default = final: _: (packagesFn final); - }; + outputs = inputs: + inputs.flake-parts.lib.mkFlake + {inherit inputs;} + {imports = [./nix];}; } diff --git a/nix/default.nix b/nix/default.nix index e0616b6ea..7bad1440c 100644 --- a/nix/default.nix +++ b/nix/default.nix @@ -1,100 +1,32 @@ { - lib, - stdenv, - cmake, - ninja, - 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], - gamemodeSupport ? true, - gamemode, - # flake + inputs, self, - version, - libnbtplusplus, -}: -stdenv.mkDerivation rec { - pname = "prismlauncher"; - inherit version; - - src = lib.cleanSource self; - - nativeBuildInputs = [extra-cmake-modules cmake file jdk17 ninja wrapQtAppsHook]; - buildInputs = - [ - qtbase - qtsvg - zlib - quazip - ghc_filesystem - tomlplusplus - cmark - ] - ++ lib.optional (lib.versionAtLeast qtbase.version "6") qtwayland - ++ lib.optional gamemodeSupport gamemode.dev; - - cmakeFlags = - lib.optionals (msaClientID != "") ["-DLauncher_MSA_CLIENT_ID=${msaClientID}"] - ++ lib.optionals (lib.versionOlder qtbase.version "6") ["-DLauncher_QT_VERSION_MAJOR=5"]; - - postUnpack = '' - rm -rf source/libraries/libnbtplusplus - mkdir source/libraries/libnbtplusplus - ln -s ${libnbtplusplus}/* source/libraries/libnbtplusplus - chmod -R +r+w source/libraries/libnbtplusplus - chown -R $USER: source/libraries/libnbtplusplus - ''; - - qtWrapperArgs = let - libpath = with xorg; - 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}" - # xorg.xrandr needed for LWJGL [2.9.2, 3) https://github.com/LWJGL/lwjgl/issues/128 - "--prefix PATH : ${lib.makeBinPath [xorg.xrandr]}" + ... +}: { + imports = [ + ./dev.nix + ./distribution.nix ]; - meta = with lib; { - homepage = "https://prismlauncher.org/"; - description = "A free, open source launcher for Minecraft"; - longDescription = '' - Allows you to have multiple, separate instances of Minecraft (each with - their own mods, texture packs, saves, etc) and helps you manage them and - their associated options with a simple interface. - ''; - platforms = platforms.linux; - changelog = "https://github.com/PrismLauncher/PrismLauncher/releases/tag/${version}"; - license = licenses.gpl3Only; - maintainers = with maintainers; [minion3665 Scrumplex]; + _module.args = { + # User-friendly version number. + version = builtins.substring 0 8 self.lastModifiedDate; }; + + perSystem = {system, ...}: { + # Nixpkgs instantiated for supported systems with our overlay. + _module.args.pkgs = import inputs.nixpkgs { + inherit system; + overlays = [self.overlays.default]; + }; + }; + + # Supported systems. + systems = [ + "x86_64-linux" + "x86_64-darwin" + "aarch64-linux" + # Disabled due to qtbase being currently broken for "aarch64-darwin." + # "aarch64-darwin" + ]; } diff --git a/nix/dev.nix b/nix/dev.nix new file mode 100644 index 000000000..0fe68c4ec --- /dev/null +++ b/nix/dev.nix @@ -0,0 +1,44 @@ +{ + inputs, + self, + ... +}: { + perSystem = { + system, + pkgs, + ... + }: { + checks = { + pre-commit-check = inputs.pre-commit-hooks.lib.${system}.run { + src = self; + hooks = { + markdownlint.enable = true; + + alejandra.enable = true; + deadnix.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++"]; + }; + }; + }; + }; + + devShells.default = pkgs.mkShell { + inherit (self.checks.${system}.pre-commit-check) shellHook; + packages = with pkgs; [ + nodePackages.markdownlint-cli + alejandra + deadnix + clang-tools + ]; + + inputsFrom = [self.packages.${system}.default]; + buildInputs = with pkgs; [ccache ninja]; + }; + + formatter = pkgs.alejandra; + }; +} diff --git a/nix/distribution.nix b/nix/distribution.nix new file mode 100644 index 000000000..0b223f175 --- /dev/null +++ b/nix/distribution.nix @@ -0,0 +1,27 @@ +{ + inputs, + self, + version, + ... +}: { + perSystem = {pkgs, ...}: { + packages = { + inherit (pkgs) prismlauncher prismlauncher-qt5; + default = pkgs.prismlauncher; + }; + }; + + flake = { + overlays.default = _: prev: let + # Helper function to build prism against different versions of Qt. + mkPrism = qt: + qt.callPackage ./package.nix { + inherit (inputs) libnbtplusplus; + inherit self version; + }; + in { + prismlauncher = mkPrism prev.qt6Packages; + prismlauncher-qt5 = mkPrism prev.libsForQt5; + }; + }; +} diff --git a/nix/flake-compat.nix b/nix/flake-compat.nix deleted file mode 100644 index 7162a6cf1..000000000 --- a/nix/flake-compat.nix +++ /dev/null @@ -1,9 +0,0 @@ -let - lock = builtins.fromJSON (builtins.readFile ../flake.lock); - inherit (lock.nodes.flake-compat.locked) rev narHash; - flake-compat = fetchTarball { - url = "https://github.com/edolstra/flake-compat/archive/${rev}.tar.gz"; - sha256 = narHash; - }; -in - import flake-compat {src = ../.;} diff --git a/nix/package.nix b/nix/package.nix new file mode 100644 index 000000000..e0616b6ea --- /dev/null +++ b/nix/package.nix @@ -0,0 +1,100 @@ +{ + lib, + stdenv, + cmake, + ninja, + 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], + gamemodeSupport ? true, + gamemode, + # flake + self, + version, + libnbtplusplus, +}: +stdenv.mkDerivation rec { + pname = "prismlauncher"; + inherit version; + + src = lib.cleanSource self; + + nativeBuildInputs = [extra-cmake-modules cmake file jdk17 ninja wrapQtAppsHook]; + buildInputs = + [ + qtbase + qtsvg + zlib + quazip + ghc_filesystem + tomlplusplus + cmark + ] + ++ lib.optional (lib.versionAtLeast qtbase.version "6") qtwayland + ++ lib.optional gamemodeSupport gamemode.dev; + + cmakeFlags = + lib.optionals (msaClientID != "") ["-DLauncher_MSA_CLIENT_ID=${msaClientID}"] + ++ lib.optionals (lib.versionOlder qtbase.version "6") ["-DLauncher_QT_VERSION_MAJOR=5"]; + + postUnpack = '' + rm -rf source/libraries/libnbtplusplus + mkdir source/libraries/libnbtplusplus + ln -s ${libnbtplusplus}/* source/libraries/libnbtplusplus + chmod -R +r+w source/libraries/libnbtplusplus + chown -R $USER: source/libraries/libnbtplusplus + ''; + + qtWrapperArgs = let + libpath = with xorg; + 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}" + # xorg.xrandr needed for LWJGL [2.9.2, 3) https://github.com/LWJGL/lwjgl/issues/128 + "--prefix PATH : ${lib.makeBinPath [xorg.xrandr]}" + ]; + + meta = with lib; { + homepage = "https://prismlauncher.org/"; + description = "A free, open source launcher for Minecraft"; + longDescription = '' + Allows you to have multiple, separate instances of Minecraft (each with + their own mods, texture packs, saves, etc) and helps you manage them and + their associated options with a simple interface. + ''; + platforms = platforms.linux; + changelog = "https://github.com/PrismLauncher/PrismLauncher/releases/tag/${version}"; + license = licenses.gpl3Only; + maintainers = with maintainers; [minion3665 Scrumplex]; + }; +} From 5d14724e66a1911b04dd5091e520751fd7f5ee90 Mon Sep 17 00:00:00 2001 From: seth Date: Sat, 27 May 2023 19:16:36 -0400 Subject: [PATCH 176/207] chore(deps): enable nix lockfile maintenance for renovate Signed-off-by: seth --- renovate.json | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/renovate.json b/renovate.json index 39a2b6e9a..d97a8dc6c 100644 --- a/renovate.json +++ b/renovate.json @@ -2,5 +2,11 @@ "$schema": "https://docs.renovatebot.com/renovate-schema.json", "extends": [ "config:base" - ] + ], + "nix": { + "enabled": true + }, + "lockFileMaintenance": { + "enabled": true + } } From a52574b02670229e4731507b11230a47535c223e Mon Sep 17 00:00:00 2001 From: seth Date: Sat, 27 May 2023 19:25:49 -0400 Subject: [PATCH 177/207] chore(nix): add nil Signed-off-by: seth --- nix/dev.nix | 2 ++ 1 file changed, 2 insertions(+) diff --git a/nix/dev.nix b/nix/dev.nix index 0fe68c4ec..a4ff2cc49 100644 --- a/nix/dev.nix +++ b/nix/dev.nix @@ -16,6 +16,7 @@ alejandra.enable = true; deadnix.enable = true; + nil.enable = true; clang-format = { enable = @@ -33,6 +34,7 @@ alejandra deadnix clang-tools + nil ]; inputsFrom = [self.packages.${system}.default]; From acf1946dacb50913305c3edc05705e8e735eafb1 Mon Sep 17 00:00:00 2001 From: seth Date: Sat, 27 May 2023 19:25:58 -0400 Subject: [PATCH 178/207] chore(nix): update sources Signed-off-by: seth --- flake.lock | 36 +++++++++--------------------------- 1 file changed, 9 insertions(+), 27 deletions(-) diff --git a/flake.lock b/flake.lock index f4122f77d..875866438 100644 --- a/flake.lock +++ b/flake.lock @@ -35,15 +35,12 @@ } }, "flake-utils": { - "inputs": { - "systems": "systems" - }, "locked": { - "lastModified": 1681202837, - "narHash": "sha256-H+Rh19JDwRtpVPAWp64F+rlEtxUWBAQW28eAi3SRSzg=", + "lastModified": 1667395993, + "narHash": "sha256-nuEHfE/LcWyuSWnS8t12N1wc105Qtau+/OdUAjtQ0rA=", "owner": "numtide", "repo": "flake-utils", - "rev": "cfacdce06f30d2b68473a46042957675eebb3401", + "rev": "5aed5285a952e0b949eb3ba02c12fa4fcfef535f", "type": "github" }, "original": { @@ -91,11 +88,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1678693419, - "narHash": "sha256-bbSv5yqZAW6dz+3f3f3pOUZbxpPN+3OgCljgn7P+nnQ=", + "lastModified": 1685012353, + "narHash": "sha256-U3oOge4cHnav8OLGdRVhL45xoRj4Ppd+It6nPC9nNIU=", "owner": "nixos", "repo": "nixpkgs", - "rev": "8e3fad82be64c06fbfb9fd43993aec9ef4623936", + "rev": "aeb75dba965e790de427b73315d5addf91a54955", "type": "github" }, "original": { @@ -138,11 +135,11 @@ ] }, "locked": { - "lastModified": 1678376203, - "narHash": "sha256-3tyYGyC8h7fBwncLZy5nCUjTJPrHbmNwp47LlNLOHSM=", + "lastModified": 1684842236, + "narHash": "sha256-rYWsIXHvNhVQ15RQlBUv67W3YnM+Pd+DuXGMvCBq2IE=", "owner": "cachix", "repo": "pre-commit-hooks.nix", - "rev": "1a20b9708962096ec2481eeb2ddca29ed747770a", + "rev": "61e567d6497bc9556f391faebe5e410e6623217f", "type": "github" }, "original": { @@ -159,21 +156,6 @@ "nixpkgs": "nixpkgs", "pre-commit-hooks": "pre-commit-hooks" } - }, - "systems": { - "locked": { - "lastModified": 1681028828, - "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", - "owner": "nix-systems", - "repo": "default", - "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", - "type": "github" - }, - "original": { - "owner": "nix-systems", - "repo": "default", - "type": "github" - } } }, "root": "root", 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 179/207] 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 593590f70..ac34a5f44 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 180/207] 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 35bef05ed..2c624a365 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 a75fa4813..068b30082 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 df6b4ecc8..0feee2999 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 10fb4e3c7..96b64193f 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 6ae25d338..5e3b31e08 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 46f5087f0..d337fe296 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 e19734689..d2d875e48 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 fdf5f3315..0a35e1bca 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 6eba4e2ec..c12d1f237 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 66d5a074b..db4b14fb0 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 6f3f2811b..dc5acf80f 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 1e218537e..c6609ed1e 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 246997bdb..425a71e46 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 64ed76739..aafaf2202 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 edb7d063c..d2a8d33eb 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 0a06a3518..b9b17b423 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 054d81c41..962d89f11 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 86974b046ee0b232b07e3a28cbc1e954d88dd40f Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Sun, 28 May 2023 11:48:09 +0100 Subject: [PATCH 181/207] Clarify comment Signed-off-by: TheKodeToad --- launcher/modplatform/modrinth/ModrinthPackExportTask.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp b/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp index 8db89bbd3..98fbc218d 100644 --- a/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp +++ b/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp @@ -129,7 +129,7 @@ void ModrinthPackExportTask::collectHashes() const Mod* mod = *modIter; if (mod->metadata() != nullptr) { QUrl& url = mod->metadata()->url; - // most likely some of these may be from curseforge + // ensure the url is permitted on modrinth.com if (!url.isEmpty() && BuildConfig.MODRINTH_MRPACK_HOSTS.contains(url.host())) { qDebug() << "Resolving" << relative << "from index"; 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 182/207] 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 ac34a5f44..a708377c9 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 183/207] 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 a708377c9..d0701a7ad 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") From 03b66ba7a5a1e35f47a59a9e1cd5d73ad6685c8e Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Wed, 31 May 2023 13:25:14 -0700 Subject: [PATCH 184/207] packaging: make windows nsis installer run the uninstaller for previous install before installing Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- program_info/win_install.nsi.in | 76 +++++++++++++++++++++++++++++++-- 1 file changed, 72 insertions(+), 4 deletions(-) diff --git a/program_info/win_install.nsi.in b/program_info/win_install.nsi.in index 1d902d5d9..27c400395 100644 --- a/program_info/win_install.nsi.in +++ b/program_info/win_install.nsi.in @@ -12,6 +12,8 @@ OutFile "../@Launcher_CommonName@-Setup.exe" !define MUI_ICON "../@Launcher_Branding_ICO@" +!define UNINST_KEY "Software\Microsoft\Windows\CurrentVersion\Uninstall\@Launcher_CommonName@" + ;-------------------------------- ; Pages @@ -269,7 +271,74 @@ VIAddVersionKey /LANG=${LANG_ENGLISH} "ProductVersion" "@Launcher_VERSION_NAME4@ !macroend -;-------------------------------- +;------------------------------------------ +; Uninstall Previous install + +!macro RunUninstall exitcode uninstcommand + Push `${uninstcommand}` + Call RunUninstall + Pop ${exitcode} +!macroend + +; Checks that the uninstaller in the provided command exists and runs it. +Function RunUninstall + Exch $1 ; input uninstcommand + Push $2 ; Uninstaller + Push $3 ; Len + Push $4 ; uninstcommand + StrCpy $4 $1 ; make a copy of the command for later + StrCpy $3 "" + StrCpy $2 $1 1 ; take first char of string + StrCmp $2 '"' quoteloop stringloop + stringloop: ; get string length + StrCpy $2 $1 1 $3 ; get next char + IntOp $3 $3 + 1 ; index += 1 + StrCmp $2 "" +2 stringloop; if empty exit loop + IntOp $3 $3 - 1 ; index -= 1 + Goto run + quoteloop: ; get string length with quotes removed + StrCmp $3 "" 0 +2 ; if index is set skip quote removal + StrCpy $1 $1 "" 1 ; Remove initial quote + IntOp $3 $3 + 1 ; index += 1 + StrCpy $2 $1 1 $3 ; get next char + StrCmp $2 "" +2 ; if empty exit loop + StrCmp $2 '"' 0 quoteloop ; if ending quote exit loop, else loop + run: + StrCpy $2 $1 $3 ; Path to uninstaller ; (copy string up to ending quote - if it exists) + StrCpy $1 161 ; ERROR_BAD_PATHNAME ; set exit code (it get's overwritten with uninstaller exit code if ExecWait call doesn't error) + GetFullPathName $3 "$2\.." ; $InstDir + IfFileExists "$2" 0 +4 + ExecWait $4 $1 ; The file exists, call the saved command + IntCmp $1 0 "" +2 +2 ; Don't delete the installer if it was aborted ; + Delete "$2" ; Delete the uninstaller + RMDir "$3" ; Try to delete $InstDir + Pop $4 + Pop $3 + Pop $2 + Exch $1 ; exitcode +FunctionEnd + +; The "" makes the section hidden. +Section "" UninstallPrevious + + ReadRegStr $0 HKCU "${UNINST_KEY}" "QuietUninstallString" + ${If} $0 == "" + ReadRegStr $0 HKCU "${UNINST_KEY}" "UninstallString" + ${EndIf} + + ${If} $0 != "" + ${AndIf} ${Cmd} `MessageBox MB_YESNO|MB_ICONQUESTION "Uninstall previous version?" /SD IDYES IDYES` + !insertmacro RunUninstall $0 $0 + ${If} $0 <> 0 + MessageBox MB_YESNO|MB_ICONSTOP "Failed to uninstall, continue anyway?" /SD IDYES IDYES +2 + Abort + ${EndIf} + ${EndIf} + +SectionEnd + + +;------------------------------------ ; The stuff to install Section "@Launcher_DisplayName@" @@ -299,11 +368,10 @@ Section "@Launcher_DisplayName@" ${GetParameters} $R0 ${GetOptions} $R0 "/NoUninstaller" $R1 ${If} ${Errors} - !define UNINST_KEY "Software\Microsoft\Windows\CurrentVersion\Uninstall\@Launcher_CommonName@" WriteRegStr HKCU "${UNINST_KEY}" "DisplayName" "@Launcher_DisplayName@" WriteRegStr HKCU "${UNINST_KEY}" "DisplayIcon" "$INSTDIR\@Launcher_APP_BINARY_NAME@.exe" - WriteRegStr HKCU "${UNINST_KEY}" "UninstallString" '"$INSTDIR\uninstall.exe"' - WriteRegStr HKCU "${UNINST_KEY}" "QuietUninstallString" '"$INSTDIR\uninstall.exe" /S' + WriteRegStr HKCU "${UNINST_KEY}" "UninstallString" '"$INSTDIR\uninstall.exe" _?=$INSTDIR' + WriteRegStr HKCU "${UNINST_KEY}" "QuietUninstallString" '"$INSTDIR\uninstall.exe" /S _?=$INSTDIR' WriteRegStr HKCU "${UNINST_KEY}" "InstallLocation" "$INSTDIR" WriteRegStr HKCU "${UNINST_KEY}" "Publisher" "@Launcher_DisplayName@ Contributors" WriteRegStr HKCU "${UNINST_KEY}" "Version" "@Launcher_VERSION_NAME4@" From 4593538fc864fe67d4c31376506f4d2b81201895 Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Wed, 31 May 2023 13:54:13 -0700 Subject: [PATCH 185/207] fix: typo - space before comment Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- program_info/win_install.nsi.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/program_info/win_install.nsi.in b/program_info/win_install.nsi.in index 27c400395..8389e2468 100644 --- a/program_info/win_install.nsi.in +++ b/program_info/win_install.nsi.in @@ -293,7 +293,7 @@ Function RunUninstall stringloop: ; get string length StrCpy $2 $1 1 $3 ; get next char IntOp $3 $3 + 1 ; index += 1 - StrCmp $2 "" +2 stringloop; if empty exit loop + StrCmp $2 "" +2 stringloop ; if empty exit loop IntOp $3 $3 - 1 ; index -= 1 Goto run quoteloop: ; get string length with quotes removed From 6a4fb6a27149893f65d09cb69f1ee7f0ad6bcfad Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Thu, 1 Jun 2023 12:40:08 -0700 Subject: [PATCH 186/207] packaging: remove redundant "do you want to uninstall previous version" Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- program_info/win_install.nsi.in | 1 - 1 file changed, 1 deletion(-) diff --git a/program_info/win_install.nsi.in b/program_info/win_install.nsi.in index 8389e2468..d3b5c256f 100644 --- a/program_info/win_install.nsi.in +++ b/program_info/win_install.nsi.in @@ -327,7 +327,6 @@ Section "" UninstallPrevious ${EndIf} ${If} $0 != "" - ${AndIf} ${Cmd} `MessageBox MB_YESNO|MB_ICONQUESTION "Uninstall previous version?" /SD IDYES IDYES` !insertmacro RunUninstall $0 $0 ${If} $0 <> 0 MessageBox MB_YESNO|MB_ICONSTOP "Failed to uninstall, continue anyway?" /SD IDYES IDYES +2 From 0f0cbd4c1faf7584f8f6deff7421ce8d7e79befb Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Fri, 2 Jun 2023 12:41:18 +0200 Subject: [PATCH 187/207] refactor(nix): introduce unwrapped packages Signed-off-by: Sefa Eyeoglu --- nix/distribution.nix | 10 ++++---- nix/package.nix | 57 +++++++++----------------------------------- 2 files changed, 17 insertions(+), 50 deletions(-) diff --git a/nix/distribution.nix b/nix/distribution.nix index 0b223f175..7c5ef93ad 100644 --- a/nix/distribution.nix +++ b/nix/distribution.nix @@ -6,13 +6,13 @@ }: { perSystem = {pkgs, ...}: { packages = { - inherit (pkgs) prismlauncher prismlauncher-qt5; + inherit (pkgs) prismlauncher-qt5-unwrapped prismlauncher-qt5 prismlauncher-unwrapped prismlauncher; default = pkgs.prismlauncher; }; }; flake = { - overlays.default = _: prev: let + overlays.default = final: prev: let # Helper function to build prism against different versions of Qt. mkPrism = qt: qt.callPackage ./package.nix { @@ -20,8 +20,10 @@ inherit self version; }; in { - prismlauncher = mkPrism prev.qt6Packages; - prismlauncher-qt5 = mkPrism prev.libsForQt5; + prismlauncher-qt5-unwrapped = mkPrism final.libsForQt5; + prismlauncher-qt5 = prev.prismlauncher-qt5.override {inherit (final) prismlauncher-unwrapped;}; + prismlauncher-unwrapped = mkPrism final.qt6Packages; + prismlauncher = prev.prismlauncher.override {inherit (final) prismlauncher-unwrapped;}; }; }; } diff --git a/nix/package.nix b/nix/package.nix index e0616b6ea..edc266dc4 100644 --- a/nix/package.nix +++ b/nix/package.nix @@ -3,86 +3,51 @@ stdenv, cmake, ninja, - 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], - gamemodeSupport ? true, + ghc_filesystem, gamemode, - # flake + msaClientID ? null, + gamemodeSupport ? true, self, version, libnbtplusplus, }: stdenv.mkDerivation rec { - pname = "prismlauncher"; + pname = "prismlauncher-unwrapped"; inherit version; src = lib.cleanSource self; - nativeBuildInputs = [extra-cmake-modules cmake file jdk17 ninja wrapQtAppsHook]; + nativeBuildInputs = [extra-cmake-modules cmake jdk17 ninja]; buildInputs = [ qtbase - qtsvg zlib quazip ghc_filesystem tomlplusplus cmark ] - ++ lib.optional (lib.versionAtLeast qtbase.version "6") qtwayland - ++ lib.optional gamemodeSupport gamemode.dev; + ++ lib.optional gamemodeSupport gamemode; + + hardeningEnable = ["pie"]; cmakeFlags = - lib.optionals (msaClientID != "") ["-DLauncher_MSA_CLIENT_ID=${msaClientID}"] + lib.optionals (msaClientID != null) ["-DLauncher_MSA_CLIENT_ID=${msaClientID}"] ++ lib.optionals (lib.versionOlder qtbase.version "6") ["-DLauncher_QT_VERSION_MAJOR=5"]; postUnpack = '' rm -rf source/libraries/libnbtplusplus - mkdir source/libraries/libnbtplusplus - ln -s ${libnbtplusplus}/* source/libraries/libnbtplusplus - chmod -R +r+w source/libraries/libnbtplusplus - chown -R $USER: source/libraries/libnbtplusplus + ln -s ${libnbtplusplus} source/libraries/libnbtplusplus ''; - qtWrapperArgs = let - libpath = with xorg; - 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}" - # xorg.xrandr needed for LWJGL [2.9.2, 3) https://github.com/LWJGL/lwjgl/issues/128 - "--prefix PATH : ${lib.makeBinPath [xorg.xrandr]}" - ]; + dontWrapQtApps = true; meta = with lib; { homepage = "https://prismlauncher.org/"; From 29e532c096e8c89ba3f0e071fbdecf646f9814ea Mon Sep 17 00:00:00 2001 From: seth Date: Fri, 2 Jun 2023 11:53:09 -0400 Subject: [PATCH 188/207] fix(nix): fix prismlauncher-qt5 Signed-off-by: seth --- nix/distribution.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nix/distribution.nix b/nix/distribution.nix index 7c5ef93ad..0f2e26f3e 100644 --- a/nix/distribution.nix +++ b/nix/distribution.nix @@ -21,7 +21,7 @@ }; in { prismlauncher-qt5-unwrapped = mkPrism final.libsForQt5; - prismlauncher-qt5 = prev.prismlauncher-qt5.override {inherit (final) prismlauncher-unwrapped;}; + prismlauncher-qt5 = prev.prismlauncher-qt5.override {prismlauncher-unwrapped = final.prismlauncher-qt5-unwrapped;}; prismlauncher-unwrapped = mkPrism final.qt6Packages; prismlauncher = prev.prismlauncher.override {inherit (final) prismlauncher-unwrapped;}; }; From 1840505a0f887ebfc2c719113873ea3345b133fb Mon Sep 17 00:00:00 2001 From: Alexandru Ionut Tripon Date: Sat, 3 Jun 2023 00:04:06 +0300 Subject: [PATCH 189/207] Fix crash when selecting same mod from different providers (#1029) --- launcher/ResourceDownloadTask.cpp | 49 +++++------ launcher/ResourceDownloadTask.h | 58 +++++++------ launcher/modplatform/ModIndex.h | 35 ++++---- .../modplatform/flame/FlameCheckUpdate.cpp | 22 ++--- .../modrinth/ModrinthCheckUpdate.cpp | 28 +++--- .../ui/dialogs/ResourceDownloadDialog.cpp | 86 +++++++++---------- launcher/ui/dialogs/ResourceDownloadDialog.h | 17 ++-- launcher/ui/pages/modplatform/ModPage.cpp | 27 +++--- launcher/ui/pages/modplatform/ModPage.h | 15 ++-- .../ui/pages/modplatform/ResourceModel.cpp | 65 +++++++++++--- launcher/ui/pages/modplatform/ResourceModel.h | 12 +++ .../ui/pages/modplatform/ResourcePage.cpp | 85 ++++++++++-------- launcher/ui/pages/modplatform/ResourcePage.h | 18 ++-- .../ui/pages/modplatform/ShaderPackPage.cpp | 16 ++-- .../ui/pages/modplatform/ShaderPackPage.h | 4 +- launcher/ui/widgets/PageContainer.cpp | 5 ++ launcher/ui/widgets/PageContainer.h | 1 + 17 files changed, 316 insertions(+), 227 deletions(-) diff --git a/launcher/ResourceDownloadTask.cpp b/launcher/ResourceDownloadTask.cpp index 61b918aaf..06c03c779 100644 --- a/launcher/ResourceDownloadTask.cpp +++ b/launcher/ResourceDownloadTask.cpp @@ -1,21 +1,21 @@ // SPDX-License-Identifier: GPL-3.0-only -/* -* Prism Launcher - Minecraft Launcher -* Copyright (c) 2022-2023 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 . -*/ +/* + * Prism Launcher - Minecraft Launcher + * Copyright (c) 2022-2023 flowln + * Copyright (C) 2022 Sefa Eyeoglu + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ #include "ResourceDownloadTask.h" @@ -24,14 +24,15 @@ #include "minecraft/mod/ModFolderModel.h" #include "minecraft/mod/ResourceFolderModel.h" -ResourceDownloadTask::ResourceDownloadTask(ModPlatform::IndexedPack pack, +ResourceDownloadTask::ResourceDownloadTask(ModPlatform::IndexedPack::Ptr pack, ModPlatform::IndexedVersion version, const std::shared_ptr packs, - bool is_indexed) - : m_pack(std::move(pack)), m_pack_version(std::move(version)), m_pack_model(packs) + bool is_indexed, + QString custom_target_folder) + : m_pack(std::move(pack)), m_pack_version(std::move(version)), m_pack_model(packs), m_custom_target_folder(custom_target_folder) { if (auto model = dynamic_cast(m_pack_model.get()); model && is_indexed) { - m_update_task.reset(new LocalModUpdateTask(model->indexDir(), m_pack, m_pack_version)); + m_update_task.reset(new LocalModUpdateTask(model->indexDir(), *m_pack, m_pack_version)); connect(m_update_task.get(), &LocalModUpdateTask::hasOldMod, this, &ResourceDownloadTask::hasOldResource); addTask(m_update_task); @@ -40,13 +41,13 @@ ResourceDownloadTask::ResourceDownloadTask(ModPlatform::IndexedPack pack, m_filesNetJob.reset(new NetJob(tr("Resource download"), APPLICATION->network())); m_filesNetJob->setStatus(tr("Downloading resource:\n%1").arg(m_pack_version.downloadUrl)); - QDir dir { m_pack_model->dir() }; + QDir dir{ m_pack_model->dir() }; { // FIXME: Make this more generic. May require adding additional info to IndexedVersion, // or adquiring a reference to the base instance. - if (!m_pack_version.custom_target_folder.isEmpty()) { + if (!m_custom_target_folder.isEmpty()) { dir.cdUp(); - dir.cd(m_pack_version.custom_target_folder); + dir.cd(m_custom_target_folder); } } diff --git a/launcher/ResourceDownloadTask.h b/launcher/ResourceDownloadTask.h index 73ad2d070..09147c8cb 100644 --- a/launcher/ResourceDownloadTask.h +++ b/launcher/ResourceDownloadTask.h @@ -1,44 +1,51 @@ // SPDX-License-Identifier: GPL-3.0-only /* -* Prism Launcher - Minecraft Launcher -* Copyright (c) 2022-2023 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 . -*/ + * Prism Launcher - Minecraft Launcher + * Copyright (c) 2022-2023 flowln + * Copyright (C) 2022 Sefa Eyeoglu + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ #pragma once #include "net/NetJob.h" #include "tasks/SequentialTask.h" -#include "modplatform/ModIndex.h" #include "minecraft/mod/tasks/LocalModUpdateTask.h" +#include "modplatform/ModIndex.h" class ResourceFolderModel; class ResourceDownloadTask : public SequentialTask { Q_OBJECT -public: - explicit ResourceDownloadTask(ModPlatform::IndexedPack pack, ModPlatform::IndexedVersion version, const std::shared_ptr packs, bool is_indexed = true); + public: + explicit ResourceDownloadTask(ModPlatform::IndexedPack::Ptr pack, + ModPlatform::IndexedVersion version, + const std::shared_ptr packs, + bool is_indexed = true, + QString custom_target_folder = {}); const QString& getFilename() const { return m_pack_version.fileName; } - const QString& getCustomPath() const { return m_pack_version.custom_target_folder; } + const QString& getCustomPath() const { return m_custom_target_folder; } const QVariant& getVersionID() const { return m_pack_version.fileId; } + const QString& getName() const { return m_pack->name; } + ModPlatform::IndexedPack::Ptr getPack() { return m_pack; } -private: - ModPlatform::IndexedPack m_pack; + private: + ModPlatform::IndexedPack::Ptr m_pack; ModPlatform::IndexedVersion m_pack_version; const std::shared_ptr m_pack_model; + QString m_custom_target_folder; NetJob::Ptr m_filesNetJob; LocalModUpdateTask::Ptr m_update_task; @@ -47,11 +54,8 @@ private: void downloadFailed(QString reason); void downloadSucceeded(); - std::tuple to_delete {"", ""}; + std::tuple to_delete{ "", "" }; -private slots: + private slots: void hasOldResource(QString name, QString filename); }; - - - diff --git a/launcher/modplatform/ModIndex.h b/launcher/modplatform/ModIndex.h index 8d0223f91..82da2ab2f 100644 --- a/launcher/modplatform/ModIndex.h +++ b/launcher/modplatform/ModIndex.h @@ -1,20 +1,20 @@ // SPDX-License-Identifier: GPL-3.0-only /* -* PolyMC - 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 . -*/ + * PolyMC - 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 . + */ #pragma once @@ -69,7 +69,6 @@ struct IndexedVersion { // For internal use, not provided by APIs bool is_currently_selected = false; - QString custom_target_folder; }; struct ExtraPackData { @@ -116,12 +115,12 @@ struct IndexedPack { if (!versionsLoaded) return false; - return std::any_of(versions.constBegin(), versions.constEnd(), - [](auto const& v) { return v.is_currently_selected; }); + return std::any_of(versions.constBegin(), versions.constEnd(), [](auto const& v) { return v.is_currently_selected; }); } }; } // namespace ModPlatform Q_DECLARE_METATYPE(ModPlatform::IndexedPack) +Q_DECLARE_METATYPE(ModPlatform::IndexedPack::Ptr) Q_DECLARE_METATYPE(ModPlatform::ResourceProvider) diff --git a/launcher/modplatform/flame/FlameCheckUpdate.cpp b/launcher/modplatform/flame/FlameCheckUpdate.cpp index 06a895027..e09aeb3d9 100644 --- a/launcher/modplatform/flame/FlameCheckUpdate.cpp +++ b/launcher/modplatform/flame/FlameCheckUpdate.cpp @@ -3,6 +3,7 @@ #include "FlameModIndex.h" #include +#include #include "FileSystem.h" #include "Json.h" @@ -129,8 +130,7 @@ void FlameCheckUpdate::executeTask() setStatus(tr("Getting API response from CurseForge for '%1'...").arg(mod->name())); setProgress(i++, m_mods.size()); - ModPlatform::IndexedPack pack{ mod->metadata()->project_id.toString() }; - auto latest_ver = api.getLatestVersion({ pack, m_game_versions, m_loaders }); + auto latest_ver = api.getLatestVersion({ { mod->metadata()->project_id.toString() }, m_game_versions, m_loaders }); // Check if we were aborted while getting the latest version if (m_was_aborted) { @@ -156,15 +156,15 @@ void FlameCheckUpdate::executeTask() if (!latest_ver.hash.isEmpty() && (mod->metadata()->hash != latest_ver.hash || mod->status() == ModStatus::NotInstalled)) { // Fake pack with the necessary info to pass to the download task :) - ModPlatform::IndexedPack pack; - pack.name = mod->name(); - pack.slug = mod->metadata()->slug; - pack.addonId = mod->metadata()->project_id; - pack.websiteUrl = mod->homeurl(); + auto pack = std::make_shared(); + pack->name = mod->name(); + pack->slug = mod->metadata()->slug; + pack->addonId = mod->metadata()->project_id; + pack->websiteUrl = mod->homeurl(); for (auto& author : mod->authors()) - pack.authors.append({ author }); - pack.description = mod->description(); - pack.provider = ModPlatform::ResourceProvider::FLAME; + pack->authors.append({ author }); + pack->description = mod->description(); + pack->provider = ModPlatform::ResourceProvider::FLAME; auto old_version = mod->version(); if (old_version.isEmpty() && mod->status() != ModStatus::NotInstalled) { @@ -173,7 +173,7 @@ void FlameCheckUpdate::executeTask() } auto download_task = makeShared(pack, latest_ver, m_mods_folder); - m_updatable.emplace_back(pack.name, mod->metadata()->hash, old_version, latest_ver.version, + m_updatable.emplace_back(pack->name, mod->metadata()->hash, old_version, latest_ver.version, api.getModFileChangelog(latest_ver.addonId.toInt(), latest_ver.fileId.toInt()), ModPlatform::ResourceProvider::FLAME, download_task); } diff --git a/launcher/modplatform/modrinth/ModrinthCheckUpdate.cpp b/launcher/modplatform/modrinth/ModrinthCheckUpdate.cpp index d1be72099..4fe91ce78 100644 --- a/launcher/modplatform/modrinth/ModrinthCheckUpdate.cpp +++ b/launcher/modplatform/modrinth/ModrinthCheckUpdate.cpp @@ -54,7 +54,7 @@ void ModrinthCheckUpdate::executeTask() if (mod->metadata()->hash_format != best_hash_type) { auto hash_task = Hashing::createModrinthHasher(mod->fileinfo().absoluteFilePath()); connect(hash_task.get(), &Task::succeeded, [&] { - QString hash (hash_task->getResult()); + QString hash(hash_task->getResult()); hashes.append(hash); mappings.insert(hash, mod); }); @@ -67,7 +67,7 @@ void ModrinthCheckUpdate::executeTask() } QEventLoop loop; - connect(&hashing_task, &Task::finished, [&loop]{ loop.quit(); }); + connect(&hashing_task, &Task::finished, [&loop] { loop.quit(); }); hashing_task.start(); loop.exec(); @@ -112,7 +112,8 @@ void ModrinthCheckUpdate::executeTask() // so we may want to filter it QString loader_filter; if (m_loaders.has_value()) { - static auto flags = { ResourceAPI::ModLoaderType::Forge, ResourceAPI::ModLoaderType::Fabric, ResourceAPI::ModLoaderType::Quilt }; + static auto flags = { ResourceAPI::ModLoaderType::Forge, ResourceAPI::ModLoaderType::Fabric, + ResourceAPI::ModLoaderType::Quilt }; for (auto flag : flags) { if (m_loaders.value().testFlag(flag)) { loader_filter = api.getModLoaderString(flag); @@ -122,7 +123,8 @@ void ModrinthCheckUpdate::executeTask() } // Currently, we rely on a couple heuristics to determine whether an update is actually available or not: - // - The file needs to be preferred: It is either the primary file, or the one found via (explicit) usage of the loader_filter + // - The file needs to be preferred: It is either the primary file, or the one found via (explicit) usage of the + // loader_filter // - The version reported by the JAR is different from the version reported by the indexed version (it's usually the case) // Such is the pain of having arbitrary files for a given version .-. @@ -149,19 +151,19 @@ void ModrinthCheckUpdate::executeTask() continue; // Fake pack with the necessary info to pass to the download task :) - ModPlatform::IndexedPack pack; - pack.name = mod->name(); - pack.slug = mod->metadata()->slug; - pack.addonId = mod->metadata()->project_id; - pack.websiteUrl = mod->homeurl(); + auto pack = std::make_shared(); + pack->name = mod->name(); + pack->slug = mod->metadata()->slug; + pack->addonId = mod->metadata()->project_id; + pack->websiteUrl = mod->homeurl(); for (auto& author : mod->authors()) - pack.authors.append({ author }); - pack.description = mod->description(); - pack.provider = ModPlatform::ResourceProvider::MODRINTH; + pack->authors.append({ author }); + pack->description = mod->description(); + pack->provider = ModPlatform::ResourceProvider::MODRINTH; auto download_task = makeShared(pack, project_ver, m_mods_folder); - m_updatable.emplace_back(pack.name, hash, mod->version(), project_ver.version_number, project_ver.changelog, + m_updatable.emplace_back(pack->name, hash, mod->version(), project_ver.version_number, project_ver.changelog, ModPlatform::ResourceProvider::MODRINTH, download_task); } } diff --git a/launcher/ui/dialogs/ResourceDownloadDialog.cpp b/launcher/ui/dialogs/ResourceDownloadDialog.cpp index d2a8d33eb..61c48e759 100644 --- a/launcher/ui/dialogs/ResourceDownloadDialog.cpp +++ b/launcher/ui/dialogs/ResourceDownloadDialog.cpp @@ -20,14 +20,15 @@ #include "ResourceDownloadDialog.h" #include +#include #include "Application.h" #include "ResourceDownloadTask.h" #include "minecraft/mod/ModFolderModel.h" #include "minecraft/mod/ResourcePackFolderModel.h" -#include "minecraft/mod/TexturePackFolderModel.h" #include "minecraft/mod/ShaderPackFolderModel.h" +#include "minecraft/mod/TexturePackFolderModel.h" #include "ui/dialogs/ReviewMessageBox.h" @@ -41,7 +42,10 @@ namespace ResourceDownload { ResourceDownloadDialog::ResourceDownloadDialog(QWidget* parent, const std::shared_ptr base_model) - : QDialog(parent), m_base_model(base_model), m_buttons(QDialogButtonBox::Help | QDialogButtonBox::Ok | QDialogButtonBox::Cancel), m_vertical_layout(this) + : QDialog(parent) + , m_base_model(base_model) + , m_buttons(QDialogButtonBox::Help | QDialogButtonBox::Ok | QDialogButtonBox::Cancel) + , m_vertical_layout(this) { setObjectName(QStringLiteral("ResourceDownloadDialog")); @@ -102,7 +106,8 @@ void ResourceDownloadDialog::initializeContainer() void ResourceDownloadDialog::connectButtons() { auto OkButton = m_buttons.button(QDialogButtonBox::Ok); - OkButton->setToolTip(tr("Opens a new popup to review your selected %1 and confirm your selection. Shortcut: Ctrl+Return").arg(resourcesString())); + OkButton->setToolTip( + tr("Opens a new popup to review your selected %1 and confirm your selection. Shortcut: Ctrl+Return").arg(resourcesString())); connect(OkButton, &QPushButton::clicked, this, &ResourceDownloadDialog::confirm); auto CancelButton = m_buttons.button(QDialogButtonBox::Cancel); @@ -114,21 +119,24 @@ void ResourceDownloadDialog::connectButtons() void ResourceDownloadDialog::confirm() { - auto keys = m_selected.keys(); - keys.sort(Qt::CaseInsensitive); + auto selected = getTasks(); + std::sort(selected.begin(), selected.end(), [](const DownloadTaskPtr& a, const DownloadTaskPtr& b) { + return QString::compare(a->getName(), b->getName(), Qt::CaseInsensitive) < 0; + }); auto confirm_dialog = ReviewMessageBox::create(this, tr("Confirm %1 to download").arg(resourcesString())); confirm_dialog->retranslateUi(resourcesString()); - for (auto& task : keys) { - auto selected = m_selected.constFind(task).value(); - confirm_dialog->appendResource({ task, selected->getFilename(), selected->getCustomPath() }); + for (auto& task : selected) { + confirm_dialog->appendResource({ task->getName(), task->getFilename(), task->getCustomPath() }); } if (confirm_dialog->exec()) { auto deselected = confirm_dialog->deselectedResources(); - for (auto name : deselected) { - m_selected.remove(name); + for (auto page : m_container->getPages()) { + auto res = static_cast(page); + for (auto name : deselected) + res->removeResourceFromPage(name); } this->accept(); @@ -145,46 +153,39 @@ ResourcePage* ResourceDownloadDialog::getSelectedPage() return m_selectedPage; } -void ResourceDownloadDialog::addResource(ModPlatform::IndexedPack& pack, ModPlatform::IndexedVersion& ver, bool is_indexed) +void ResourceDownloadDialog::addResource(ModPlatform::IndexedPack::Ptr pack, ModPlatform::IndexedVersion& ver) { - removeResource(pack, ver); - - ver.is_currently_selected = true; - m_selected.insert(pack.name, makeShared(pack, ver, getBaseModel(), is_indexed)); - - m_buttons.button(QDialogButtonBox::Ok)->setEnabled(!m_selected.isEmpty()); + removeResource(pack->name); + m_selectedPage->addResourceToPage(pack, ver, getBaseModel()); + setButtonStatus(); } -static ModPlatform::IndexedVersion& getVersionWithID(ModPlatform::IndexedPack& pack, QVariant id) +void ResourceDownloadDialog::removeResource(const QString& pack_name) { - Q_ASSERT(pack.versionsLoaded); - auto it = std::find_if(pack.versions.begin(), pack.versions.end(), [id](auto const& v) { return v.fileId == id; }); - Q_ASSERT(it != pack.versions.end()); - return *it; -} - -void ResourceDownloadDialog::removeResource(ModPlatform::IndexedPack& pack, ModPlatform::IndexedVersion& ver) -{ - if (auto selected_task_it = m_selected.find(pack.name); selected_task_it != m_selected.end()) { - auto selected_task = *selected_task_it; - auto old_version_id = selected_task->getVersionID(); - - // If the new and old version IDs don't match, search for the old one and deselect it. - if (ver.fileId != old_version_id) - getVersionWithID(pack, old_version_id).is_currently_selected = false; + for (auto page : m_container->getPages()) { + static_cast(page)->removeResourceFromPage(pack_name); } + setButtonStatus(); +} - // Deselect the new version too, since all versions of that pack got removed. - ver.is_currently_selected = false; - - m_selected.remove(pack.name); - - m_buttons.button(QDialogButtonBox::Ok)->setEnabled(!m_selected.isEmpty()); +void ResourceDownloadDialog::setButtonStatus() +{ + auto selected = false; + for (auto page : m_container->getPages()) { + auto res = static_cast(page); + selected = selected || res->hasSelectedPacks(); + } + m_buttons.button(QDialogButtonBox::Ok)->setEnabled(selected); } const QList ResourceDownloadDialog::getTasks() { - return m_selected.values(); + QList selected; + for (auto page : m_container->getPages()) { + auto res = static_cast(page); + selected.append(res->selectedPacks()); + } + return selected; } void ResourceDownloadDialog::selectedPageChanged(BasePage* previous, BasePage* selected) @@ -205,8 +206,6 @@ void ResourceDownloadDialog::selectedPageChanged(BasePage* previous, BasePage* s m_selectedPage->setSearchTerm(prev_page->getSearchTerm()); } - - ModDownloadDialog::ModDownloadDialog(QWidget* parent, const std::shared_ptr& mods, BaseInstance* instance) : ResourceDownloadDialog(parent, mods), m_instance(instance) { @@ -232,7 +231,6 @@ QList ModDownloadDialog::getPages() return pages; } - ResourcePackDownloadDialog::ResourcePackDownloadDialog(QWidget* parent, const std::shared_ptr& resource_packs, BaseInstance* instance) @@ -258,7 +256,6 @@ QList ResourcePackDownloadDialog::getPages() return pages; } - TexturePackDownloadDialog::TexturePackDownloadDialog(QWidget* parent, const std::shared_ptr& resource_packs, BaseInstance* instance) @@ -284,7 +281,6 @@ QList TexturePackDownloadDialog::getPages() 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 5678dc8bb..5b5b48c63 100644 --- a/launcher/ui/dialogs/ResourceDownloadDialog.h +++ b/launcher/ui/dialogs/ResourceDownloadDialog.h @@ -62,8 +62,8 @@ class ResourceDownloadDialog : public QDialog, public BasePageProvider { bool selectPage(QString pageId); ResourcePage* getSelectedPage(); - void addResource(ModPlatform::IndexedPack&, ModPlatform::IndexedVersion&, bool is_indexed = false); - void removeResource(ModPlatform::IndexedPack&, ModPlatform::IndexedVersion&); + void addResource(ModPlatform::IndexedPack::Ptr, ModPlatform::IndexedVersion&); + void removeResource(const QString&); const QList getTasks(); [[nodiscard]] const std::shared_ptr getBaseModel() const { return m_base_model; } @@ -79,6 +79,7 @@ class ResourceDownloadDialog : public QDialog, public BasePageProvider { protected: [[nodiscard]] virtual QString geometrySaveKey() const { return ""; } + void setButtonStatus(); protected: const std::shared_ptr m_base_model; @@ -88,12 +89,8 @@ class ResourceDownloadDialog : public QDialog, public BasePageProvider { QDialogButtonBox m_buttons; QVBoxLayout m_vertical_layout; - - QHash m_selected; }; - - class ModDownloadDialog final : public ResourceDownloadDialog { Q_OBJECT @@ -135,8 +132,8 @@ class TexturePackDownloadDialog final : public ResourceDownloadDialog { public: explicit TexturePackDownloadDialog(QWidget* parent, - const std::shared_ptr& resource_packs, - BaseInstance* instance); + const std::shared_ptr& resource_packs, + BaseInstance* instance); ~TexturePackDownloadDialog() override = default; //: String that gets appended to the texture pack download dialog title ("Download " + resourcesString()) @@ -153,9 +150,7 @@ class ShaderPackDownloadDialog final : public ResourceDownloadDialog { Q_OBJECT public: - explicit ShaderPackDownloadDialog(QWidget* parent, - const std::shared_ptr& shader_packs, - BaseInstance* instance); + 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()) diff --git a/launcher/ui/pages/modplatform/ModPage.cpp b/launcher/ui/pages/modplatform/ModPage.cpp index 04be43ada..95064d16a 100644 --- a/launcher/ui/pages/modplatform/ModPage.cpp +++ b/launcher/ui/pages/modplatform/ModPage.cpp @@ -55,8 +55,7 @@ namespace ResourceDownload { -ModPage::ModPage(ModDownloadDialog* dialog, BaseInstance& instance) - : ResourcePage(dialog, instance) +ModPage::ModPage(ModDownloadDialog* dialog, BaseInstance& instance) : ResourcePage(dialog, instance) { connect(m_ui->searchButton, &QPushButton::clicked, this, &ModPage::triggerSearch); connect(m_ui->resourceFilterButton, &QPushButton::clicked, this, &ModPage::filterMods); @@ -75,12 +74,10 @@ void ModPage::setFilterWidget(unique_qobject_ptr& widget) m_filter_widget->setInstance(&static_cast(m_base_instance)); m_filter = m_filter_widget->getFilter(); - connect(m_filter_widget.get(), &ModFilterWidget::filterChanged, this, [&]{ - m_ui->searchButton->setStyleSheet("text-decoration: underline"); - }); - connect(m_filter_widget.get(), &ModFilterWidget::filterUnchanged, this, [&]{ - m_ui->searchButton->setStyleSheet("text-decoration: none"); - }); + connect(m_filter_widget.get(), &ModFilterWidget::filterChanged, this, + [&] { m_ui->searchButton->setStyleSheet("text-decoration: underline"); }); + connect(m_filter_widget.get(), &ModFilterWidget::filterUnchanged, this, + [&] { m_ui->searchButton->setStyleSheet("text-decoration: none"); }); } /******** Callbacks to events in the UI (set up in the derived classes) ********/ @@ -125,11 +122,11 @@ void ModPage::updateVersionList() QString mcVersion = packProfile->getComponentVersion("net.minecraft"); auto current_pack = getCurrentPack(); - for (int i = 0; i < current_pack.versions.size(); i++) { - auto version = current_pack.versions[i]; + for (int i = 0; i < current_pack->versions.size(); i++) { + auto version = current_pack->versions[i]; bool valid = false; - for(auto& mcVer : m_filter->versions){ - //NOTE: Flame doesn't care about loader, so passing it changes nothing. + for (auto& mcVer : m_filter->versions) { + // NOTE: Flame doesn't care about loader, so passing it changes nothing. if (validateVersion(version, mcVer.toString(), packProfile->getModLoaders())) { valid = true; break; @@ -148,10 +145,12 @@ void ModPage::updateVersionList() updateSelectionButton(); } -void ModPage::addResourceToDialog(ModPlatform::IndexedPack& pack, ModPlatform::IndexedVersion& version) +void ModPage::addResourceToPage(ModPlatform::IndexedPack::Ptr pack, + ModPlatform::IndexedVersion& version, + const std::shared_ptr base_model) { bool is_indexed = !APPLICATION->settings()->get("ModMetadataDisabled").toBool(); - m_parent_dialog->addResource(pack, version, is_indexed); + m_model->addPack(pack, version, base_model, is_indexed); } } // namespace ResourceDownload diff --git a/launcher/ui/pages/modplatform/ModPage.h b/launcher/ui/pages/modplatform/ModPage.h index 4ea55efa3..5510c1911 100644 --- a/launcher/ui/pages/modplatform/ModPage.h +++ b/launcher/ui/pages/modplatform/ModPage.h @@ -8,8 +8,8 @@ #include "modplatform/ModIndex.h" -#include "ui/pages/modplatform/ResourcePage.h" #include "ui/pages/modplatform/ModModel.h" +#include "ui/pages/modplatform/ResourcePage.h" #include "ui/widgets/ModFilterWidget.h" namespace Ui { @@ -25,13 +25,14 @@ class ModPage : public ResourcePage { Q_OBJECT public: - template + template static T* create(ModDownloadDialog* dialog, BaseInstance& instance) { auto page = new T(dialog, instance); auto model = static_cast(page->getModel()); - auto filter_widget = ModFilterWidget::create(static_cast(instance).getPackProfile()->getComponentVersion("net.minecraft"), page); + auto filter_widget = + ModFilterWidget::create(static_cast(instance).getPackProfile()->getComponentVersion("net.minecraft"), page); page->setFilterWidget(filter_widget); model->setFilter(page->getFilter()); @@ -48,9 +49,13 @@ class ModPage : public ResourcePage { [[nodiscard]] QMap urlHandlers() const override; - void addResourceToDialog(ModPlatform::IndexedPack&, ModPlatform::IndexedVersion&) override; + void addResourceToPage(ModPlatform::IndexedPack::Ptr, + ModPlatform::IndexedVersion&, + const std::shared_ptr) override; - virtual auto validateVersion(ModPlatform::IndexedVersion& ver, QString mineVer, std::optional loaders = {}) const -> bool = 0; + virtual auto validateVersion(ModPlatform::IndexedVersion& ver, + QString mineVer, + std::optional loaders = {}) const -> bool = 0; [[nodiscard]] bool supportsFiltering() const override { return true; }; auto getFilter() const -> const std::shared_ptr { return m_filter; } diff --git a/launcher/ui/pages/modplatform/ResourceModel.cpp b/launcher/ui/pages/modplatform/ResourceModel.cpp index 472aa8515..a5ea1ca9f 100644 --- a/launcher/ui/pages/modplatform/ResourceModel.cpp +++ b/launcher/ui/pages/modplatform/ResourceModel.cpp @@ -6,9 +6,11 @@ #include #include +#include #include #include #include +#include #include #include "Application.h" @@ -65,7 +67,7 @@ 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 @@ -103,7 +105,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] = std::make_shared(value.value()); + m_packs[pos] = value.value(); emit dataChanged(index, index); return true; @@ -230,7 +232,7 @@ void ResourceModel::clearData() void ResourceModel::runSearchJob(Task::Ptr ptr) { - m_current_search_job.reset(ptr); // clean up first + m_current_search_job.reset(ptr); // clean up first m_current_search_job->start(); } void ResourceModel::runInfoJob(Task::Ptr ptr) @@ -336,7 +338,15 @@ void ResourceModel::searchRequestSucceeded(QJsonDocument& doc) ModPlatform::IndexedPack::Ptr pack = std::make_shared(); try { loadIndexedPack(*pack, packObj); - newList.append(pack); + if (auto sel = std::find_if(m_selected.begin(), m_selected.end(), + [&pack](const DownloadTaskPtr i) { + const auto ipack = i->getPack(); + return ipack->provider == pack->provider && ipack->addonId == pack->addonId; + }); + sel != m_selected.end()) { + newList.append(sel->get()->getPack()); + } else + newList.append(pack); } catch (const JSONValidationError& e) { qWarning() << "Error while loading resource from " << debugName() << ": " << e.cause(); continue; @@ -390,15 +400,15 @@ void ResourceModel::searchRequestAborted() void ResourceModel::versionRequestSucceeded(QJsonDocument& doc, ModPlatform::IndexedPack& pack, const QModelIndex& index) { - auto current_pack = data(index, Qt::UserRole).value(); + auto current_pack = data(index, Qt::UserRole).value(); // Check if the index is still valid for this resource or not - if (pack.addonId != current_pack.addonId) + if (pack.addonId != current_pack->addonId) return; try { auto arr = doc.isObject() ? Json::ensureArray(doc.object(), "data") : doc.array(); - loadIndexedPackVersions(current_pack, arr); + loadIndexedPackVersions(*current_pack, arr); } catch (const JSONValidationError& e) { qDebug() << doc; qWarning() << "Error while reading " << debugName() << " resource version: " << e.cause(); @@ -417,15 +427,15 @@ void ResourceModel::versionRequestSucceeded(QJsonDocument& doc, ModPlatform::Ind void ResourceModel::infoRequestSucceeded(QJsonDocument& doc, ModPlatform::IndexedPack& pack, const QModelIndex& index) { - auto current_pack = data(index, Qt::UserRole).value(); + auto current_pack = data(index, Qt::UserRole).value(); // Check if the index is still valid for this resource or not - if (pack.addonId != current_pack.addonId) + if (pack.addonId != current_pack->addonId) return; try { auto obj = Json::requireObject(doc); - loadExtraPackInfo(current_pack, obj); + loadExtraPackInfo(*current_pack, obj); } catch (const JSONValidationError& e) { qDebug() << doc; qWarning() << "Error while reading " << debugName() << " resource info: " << e.cause(); @@ -442,4 +452,39 @@ void ResourceModel::infoRequestSucceeded(QJsonDocument& doc, ModPlatform::Indexe emit projectInfoUpdated(); } +void ResourceModel::addPack(ModPlatform::IndexedPack::Ptr pack, + ModPlatform::IndexedVersion& version, + const std::shared_ptr packs, + bool is_indexed, + QString custom_target_folder) +{ + version.is_currently_selected = true; + m_selected.append(makeShared(pack, version, packs, is_indexed, custom_target_folder)); +} + +void ResourceModel::removePack(const QString& rem) +{ + auto pred = [&rem](const DownloadTaskPtr i) { return rem == i->getName(); }; +#if QT_VERSION >= QT_VERSION_CHECK(6, 1, 0) + m_selected.removeIf(pred); +#else + { + for (auto it = m_selected.begin(); it != m_selected.end();) + if (pred(*it)) + it = m_selected.erase(it); + else + ++it; + } +#endif + auto pack = std::find_if(m_packs.begin(), m_packs.end(), [&rem](const ModPlatform::IndexedPack::Ptr i) { return rem == i->name; }); + if (pack == m_packs.end()) { // ignore it if is not in the current search + return; + } + if (!pack->get()->versionsLoaded) { + return; + } + for (auto& ver : pack->get()->versions) + ver.is_currently_selected = false; +} + } // namespace ResourceDownload diff --git a/launcher/ui/pages/modplatform/ResourceModel.h b/launcher/ui/pages/modplatform/ResourceModel.h index 1ec42cda8..69e234730 100644 --- a/launcher/ui/pages/modplatform/ResourceModel.h +++ b/launcher/ui/pages/modplatform/ResourceModel.h @@ -10,6 +10,7 @@ #include "QObjectPtr.h" +#include "ResourceDownloadTask.h" #include "modplatform/ResourceAPI.h" #include "tasks/ConcurrentTask.h" @@ -29,6 +30,8 @@ class ResourceModel : public QAbstractListModel { Q_PROPERTY(QString search_term MEMBER m_search_term WRITE setSearchTerm) public: + using DownloadTaskPtr = shared_qobject_ptr; + ResourceModel(ResourceAPI* api); ~ResourceModel() override; @@ -80,6 +83,14 @@ class ResourceModel : public QAbstractListModel { /** Gets the icon at the URL for the given index. If it's not fetched yet, fetch it and update when fisinhed. */ std::optional getIcon(QModelIndex&, const QUrl&); + void addPack(ModPlatform::IndexedPack::Ptr pack, + ModPlatform::IndexedVersion& version, + const std::shared_ptr packs, + bool is_indexed = false, + QString custom_target_folder = {}); + void removePack(const QString& rem); + QList selectedPacks() { return m_selected; } + protected: /** Resets the model's data. */ void clearData(); @@ -124,6 +135,7 @@ class ResourceModel : public QAbstractListModel { QSet m_failed_icon_actions; QList m_packs; + QList m_selected; // 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/ResourcePage.cpp b/launcher/ui/pages/modplatform/ResourcePage.cpp index f75bb886d..736034add 100644 --- a/launcher/ui/pages/modplatform/ResourcePage.cpp +++ b/launcher/ui/pages/modplatform/ResourcePage.cpp @@ -37,6 +37,7 @@ */ #include "ResourcePage.h" +#include "modplatform/ModIndex.h" #include "ui_ResourcePage.h" #include @@ -158,16 +159,16 @@ void ResourcePage::addSortings() m_ui->sortByBox->addItem(sorting.readable_name, QVariant(sorting.index)); } -bool ResourcePage::setCurrentPack(ModPlatform::IndexedPack pack) +bool ResourcePage::setCurrentPack(ModPlatform::IndexedPack::Ptr pack) { QVariant v; v.setValue(pack); return m_model->setData(m_ui->packView->currentIndex(), v, Qt::UserRole); } -ModPlatform::IndexedPack ResourcePage::getCurrentPack() const +ModPlatform::IndexedPack::Ptr ResourcePage::getCurrentPack() const { - return m_model->data(m_ui->packView->currentIndex(), Qt::UserRole).value(); + return m_model->data(m_ui->packView->currentIndex(), Qt::UserRole).value(); } void ResourcePage::updateUi() @@ -175,14 +176,14 @@ void ResourcePage::updateUi() auto current_pack = getCurrentPack(); QString text = ""; - QString name = current_pack.name; + QString name = current_pack->name; - if (current_pack.websiteUrl.isEmpty()) + if (current_pack->websiteUrl.isEmpty()) text = name; else - text = "" + name + ""; + text = "websiteUrl + "\">" + name + ""; - if (!current_pack.authors.empty()) { + if (!current_pack->authors.empty()) { auto authorToStr = [](ModPlatform::ModpackAuthor& author) -> QString { if (author.url.isEmpty()) { return author.name; @@ -190,44 +191,44 @@ void ResourcePage::updateUi() return QString("%2").arg(author.url, author.name); }; QStringList authorStrs; - for (auto& author : current_pack.authors) { + for (auto& author : current_pack->authors) { authorStrs.push_back(authorToStr(author)); } text += "
" + tr(" by ") + authorStrs.join(", "); } - if (current_pack.extraDataLoaded) { - if (!current_pack.extraData.donate.isEmpty()) { + if (current_pack->extraDataLoaded) { + if (!current_pack->extraData.donate.isEmpty()) { text += "

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

" + tr("External links:") + "
"; } - if (!current_pack.extraData.issuesUrl.isEmpty()) - text += "- " + tr("Issues: %1").arg(current_pack.extraData.issuesUrl) + "
"; - if (!current_pack.extraData.wikiUrl.isEmpty()) - text += "- " + tr("Wiki: %1").arg(current_pack.extraData.wikiUrl) + "
"; - if (!current_pack.extraData.sourceUrl.isEmpty()) - text += "- " + tr("Source code: %1").arg(current_pack.extraData.sourceUrl) + "
"; - if (!current_pack.extraData.discordUrl.isEmpty()) - text += "- " + tr("Discord: %1").arg(current_pack.extraData.discordUrl) + "
"; + if (!current_pack->extraData.issuesUrl.isEmpty()) + text += "- " + tr("Issues: %1").arg(current_pack->extraData.issuesUrl) + "
"; + if (!current_pack->extraData.wikiUrl.isEmpty()) + text += "- " + tr("Wiki: %1").arg(current_pack->extraData.wikiUrl) + "
"; + if (!current_pack->extraData.sourceUrl.isEmpty()) + text += "- " + tr("Source code: %1").arg(current_pack->extraData.sourceUrl) + "
"; + if (!current_pack->extraData.discordUrl.isEmpty()) + text += "- " + tr("Discord: %1").arg(current_pack->extraData.discordUrl) + "
"; } text += "
"; m_ui->packDescription->setHtml( - text + (current_pack.extraData.body.isEmpty() ? current_pack.description : markdownToHTML(current_pack.extraData.body))); + text + (current_pack->extraData.body.isEmpty() ? current_pack->description : markdownToHTML(current_pack->extraData.body))); m_ui->packDescription->flush(); } @@ -239,7 +240,7 @@ void ResourcePage::updateSelectionButton() } m_ui->resourceSelectionButton->setEnabled(true); - if (!getCurrentPack().isVersionSelected(m_selected_version_index)) { + if (!getCurrentPack()->isVersionSelected(m_selected_version_index)) { m_ui->resourceSelectionButton->setText(tr("Select %1 for download").arg(resourceString())); } else { m_ui->resourceSelectionButton->setText(tr("Deselect %1 for download").arg(resourceString())); @@ -254,12 +255,12 @@ void ResourcePage::updateVersionList() m_ui->versionSelectionBox->clear(); m_ui->versionSelectionBox->blockSignals(false); - for (int i = 0; i < current_pack.versions.size(); i++) { - auto& version = current_pack.versions[i]; + for (int i = 0; i < current_pack->versions.size(); i++) { + auto& version = current_pack->versions[i]; if (optedOut(version)) continue; - m_ui->versionSelectionBox->addItem(current_pack.versions[i].version, QVariant(i)); + m_ui->versionSelectionBox->addItem(current_pack->versions[i].version, QVariant(i)); } if (m_ui->versionSelectionBox->count() == 0) { @@ -279,7 +280,7 @@ void ResourcePage::onSelectionChanged(QModelIndex curr, QModelIndex prev) auto current_pack = getCurrentPack(); bool request_load = false; - if (!current_pack.versionsLoaded) { + if (!current_pack->versionsLoaded) { m_ui->resourceSelectionButton->setText(tr("Loading versions...")); m_ui->resourceSelectionButton->setEnabled(false); @@ -288,7 +289,7 @@ void ResourcePage::onSelectionChanged(QModelIndex curr, QModelIndex prev) updateVersionList(); } - if (!current_pack.extraDataLoaded) + if (!current_pack->extraDataLoaded) request_load = true; if (request_load) @@ -308,14 +309,26 @@ void ResourcePage::onVersionSelectionChanged(QString data) updateSelectionButton(); } -void ResourcePage::addResourceToDialog(ModPlatform::IndexedPack& pack, ModPlatform::IndexedVersion& version) +void ResourcePage::addResourceToDialog(ModPlatform::IndexedPack::Ptr pack, ModPlatform::IndexedVersion& version) { m_parent_dialog->addResource(pack, version); } -void ResourcePage::removeResourceFromDialog(ModPlatform::IndexedPack& pack, ModPlatform::IndexedVersion& version) +void ResourcePage::removeResourceFromDialog(const QString& pack_name) { - m_parent_dialog->removeResource(pack, version); + m_parent_dialog->removeResource(pack_name); +} + +void ResourcePage::addResourceToPage(ModPlatform::IndexedPack::Ptr pack, + ModPlatform::IndexedVersion& ver, + const std::shared_ptr base_model) +{ + m_model->addPack(pack, ver, base_model); +} + +void ResourcePage::removeResourceFromPage(const QString& name) +{ + m_model->removePack(name); } void ResourcePage::onResourceSelected() @@ -324,12 +337,12 @@ void ResourcePage::onResourceSelected() return; auto current_pack = getCurrentPack(); - if (!current_pack.versionsLoaded) + if (!current_pack->versionsLoaded) return; - auto& version = current_pack.versions[m_selected_version_index]; + auto& version = current_pack->versions[m_selected_version_index]; if (version.is_currently_selected) - removeResourceFromDialog(current_pack, version); + removeResourceFromDialog(current_pack->name); else addResourceToDialog(current_pack, version); @@ -340,7 +353,7 @@ void ResourcePage::onResourceSelected() updateSelectionButton(); /* Force redraw on the resource list when the selection changes */ - m_ui->packView->adjustSize(); + m_ui->packView->repaint(); } void ResourcePage::openUrl(const QUrl& url) @@ -370,7 +383,7 @@ void ResourcePage::openUrl(const QUrl& url) const QString slug = match.captured(1); // ensure the user isn't opening the same mod - if (slug != getCurrentPack().slug) { + if (slug != getCurrentPack()->slug) { m_parent_dialog->selectPage(page); auto newPage = m_parent_dialog->getSelectedPage(); diff --git a/launcher/ui/pages/modplatform/ResourcePage.h b/launcher/ui/pages/modplatform/ResourcePage.h index 1896d53ea..b4a87f573 100644 --- a/launcher/ui/pages/modplatform/ResourcePage.h +++ b/launcher/ui/pages/modplatform/ResourcePage.h @@ -7,10 +7,12 @@ #include #include +#include "ResourceDownloadTask.h" #include "modplatform/ModIndex.h" #include "modplatform/ResourceAPI.h" #include "ui/pages/BasePage.h" +#include "ui/pages/modplatform/ResourceModel.h" #include "ui/widgets/ProgressWidget.h" namespace Ui { @@ -27,6 +29,7 @@ class ResourceModel; class ResourcePage : public QWidget, public BasePage { Q_OBJECT public: + using DownloadTaskPtr = shared_qobject_ptr; ~ResourcePage() override; /* Affects what the user sees */ @@ -57,8 +60,8 @@ class ResourcePage : public QWidget, public BasePage { /** Programatically set the term in the search bar. */ void setSearchTerm(QString); - [[nodiscard]] bool setCurrentPack(ModPlatform::IndexedPack); - [[nodiscard]] auto getCurrentPack() const -> ModPlatform::IndexedPack; + [[nodiscard]] bool setCurrentPack(ModPlatform::IndexedPack::Ptr); + [[nodiscard]] auto getCurrentPack() const -> ModPlatform::IndexedPack::Ptr; [[nodiscard]] auto getDialog() const -> const ResourceDownloadDialog* { return m_parent_dialog; } [[nodiscard]] auto getModel() const -> ResourceModel* { return m_model; } @@ -72,12 +75,17 @@ class ResourcePage : public QWidget, public BasePage { virtual void updateSelectionButton(); virtual void updateVersionList(); - virtual void addResourceToDialog(ModPlatform::IndexedPack&, ModPlatform::IndexedVersion&); - virtual void removeResourceFromDialog(ModPlatform::IndexedPack&, ModPlatform::IndexedVersion&); + void addResourceToDialog(ModPlatform::IndexedPack::Ptr, ModPlatform::IndexedVersion&); + void removeResourceFromDialog(const QString& pack_name); + virtual void removeResourceFromPage(const QString& name); + virtual void addResourceToPage(ModPlatform::IndexedPack::Ptr, ModPlatform::IndexedVersion&, const std::shared_ptr); + + QList selectedPacks() { return m_model->selectedPacks(); } + bool hasSelectedPacks() { return !(m_model->selectedPacks().isEmpty()); } protected slots: virtual void triggerSearch() {} - + void onSelectionChanged(QModelIndex first, QModelIndex second); void onVersionSelectionChanged(QString data); void onResourceSelected(); diff --git a/launcher/ui/pages/modplatform/ShaderPackPage.cpp b/launcher/ui/pages/modplatform/ShaderPackPage.cpp index 251c07e71..fbf94e844 100644 --- a/launcher/ui/pages/modplatform/ShaderPackPage.cpp +++ b/launcher/ui/pages/modplatform/ShaderPackPage.cpp @@ -13,8 +13,7 @@ namespace ResourceDownload { -ShaderPackResourcePage::ShaderPackResourcePage(ShaderPackDownloadDialog* dialog, BaseInstance& instance) - : ResourcePage(dialog, instance) +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); @@ -38,17 +37,20 @@ 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("(?: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) +void ShaderPackResourcePage::addResourceToPage(ModPlatform::IndexedPack::Ptr pack, + ModPlatform::IndexedVersion& version, + const std::shared_ptr base_model) { + QString custom_target_folder; if (version.loaders.contains(QStringLiteral("canvas"))) - version.custom_target_folder = QStringLiteral("resourcepacks"); - - m_parent_dialog->addResource(pack, version); + custom_target_folder = QStringLiteral("resourcepacks"); + m_model->addPack(pack, version, base_model, false, custom_target_folder); } } // namespace ResourceDownload diff --git a/launcher/ui/pages/modplatform/ShaderPackPage.h b/launcher/ui/pages/modplatform/ShaderPackPage.h index 9039c4d91..fcf6d4a7c 100644 --- a/launcher/ui/pages/modplatform/ShaderPackPage.h +++ b/launcher/ui/pages/modplatform/ShaderPackPage.h @@ -38,7 +38,9 @@ class ShaderPackResourcePage : public ResourcePage { [[nodiscard]] bool supportsFiltering() const override { return false; }; - void addResourceToDialog(ModPlatform::IndexedPack&, ModPlatform::IndexedVersion&) override; + void addResourceToPage(ModPlatform::IndexedPack::Ptr, + ModPlatform::IndexedVersion&, + const std::shared_ptr) override; [[nodiscard]] QMap urlHandlers() const override; diff --git a/launcher/ui/widgets/PageContainer.cpp b/launcher/ui/widgets/PageContainer.cpp index b9b17b423..38a228973 100644 --- a/launcher/ui/widgets/PageContainer.cpp +++ b/launcher/ui/widgets/PageContainer.cpp @@ -137,6 +137,11 @@ BasePage* PageContainer::getPage(QString pageId) return m_model->findPageEntryById(pageId); } +const QList PageContainer::getPages() const +{ + return m_model->pages(); +} + void PageContainer::refreshContainer() { m_proxyModel->invalidate(); diff --git a/launcher/ui/widgets/PageContainer.h b/launcher/ui/widgets/PageContainer.h index 97e294dcf..ad74d43a2 100644 --- a/launcher/ui/widgets/PageContainer.h +++ b/launcher/ui/widgets/PageContainer.h @@ -80,6 +80,7 @@ public: virtual bool selectPage(QString pageId) override; BasePage* getPage(QString pageId) override; + const QList getPages() const; void refreshContainer() override; virtual void setParentContainer(BasePageContainer * container) From f6f32914de6dbad07cffe786d0f15df03525a1c2 Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Fri, 2 Jun 2023 15:48:02 -0700 Subject: [PATCH 190/207] fix: add origonal instance path to allowed_symlinks.txt when copying via symlinks Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- launcher/InstanceCopyTask.cpp | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/launcher/InstanceCopyTask.cpp b/launcher/InstanceCopyTask.cpp index 4ac3b51ad..abe97b170 100644 --- a/launcher/InstanceCopyTask.cpp +++ b/launcher/InstanceCopyTask.cpp @@ -39,7 +39,16 @@ 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")); + QFileInfo mcDir(FS::PathCombine(m_stagingPath, "minecraft")); + QFileInfo dotMCDir(FS::PathCombine(m_stagingPath, ".minecraft")); + + QString staging_mc_dir; + if (mcDir.exists() && !dotMCDir.exists()) + staging_mc_dir = mcDir.filePath(); + else + staging_mc_dir = dotMCDir.filePath(); + + FS::copy savesCopy(FS::PathCombine(m_origInstance->gameRoot(), "saves"), FS::PathCombine(staging_mc_dir, "saves")); savesCopy.followSymlinks(true); return savesCopy(); @@ -123,6 +132,7 @@ void InstanceCopyTask::copyFinished() emitFailed(tr("Instance folder copy failed.")); return; } + // FIXME: shouldn't this be able to report errors? auto instanceSettings = std::make_shared(FS::PathCombine(m_stagingPath, "instance.cfg")); @@ -134,6 +144,24 @@ void InstanceCopyTask::copyFinished() } if (m_useLinks) inst->addLinkedInstanceId(m_origInstance->id()); + if (m_useLinks) { + auto allowed_symlinks_file = QFileInfo(FS::PathCombine(inst->gameRoot(), "allowed_symlinks.txt")); + + QByteArray allowed_symlinks; + if (allowed_symlinks_file.exists()) { + allowed_symlinks.append(FS::read(allowed_symlinks_file.path())); + if (allowed_symlinks.right(1) != "\n") + allowed_symlinks.append("\n"); // we want to be on a new line + } + allowed_symlinks.append(m_origInstance->gameRoot().toUtf8()); + allowed_symlinks.append("\n"); + if (allowed_symlinks_file.isSymbolicLink()) + FS::deletePath(allowed_symlinks_file + .path()); // we dont want to modify the origonal. also make sure the resulting file is not itself a link. + + FS::write(allowed_symlinks_file.path(), allowed_symlinks); + } + emitSucceeded(); } From 8eb10e991f79ef38e1c689d28e34b5f4fda8dc83 Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Fri, 2 Jun 2023 16:14:38 -0700 Subject: [PATCH 191/207] fix: use isSymLink (i've made this mistake before but I've made it again) Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- launcher/InstanceCopyTask.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/InstanceCopyTask.cpp b/launcher/InstanceCopyTask.cpp index abe97b170..a0b5635c5 100644 --- a/launcher/InstanceCopyTask.cpp +++ b/launcher/InstanceCopyTask.cpp @@ -155,7 +155,7 @@ void InstanceCopyTask::copyFinished() } allowed_symlinks.append(m_origInstance->gameRoot().toUtf8()); allowed_symlinks.append("\n"); - if (allowed_symlinks_file.isSymbolicLink()) + if (allowed_symlinks_file.isSymLink()) FS::deletePath(allowed_symlinks_file .path()); // we dont want to modify the origonal. also make sure the resulting file is not itself a link. From e26827b84922d7d84c3ee83dfed58759b1c0ca15 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Sat, 3 Jun 2023 13:39:42 +0100 Subject: [PATCH 192/207] Optimised icons Signed-off-by: TheKodeToad --- launcher/CMakeLists.txt | 2 + launcher/FastFileIconProvider.cpp | 47 ++++++++++++++++++++ launcher/FastFileIconProvider.h | 26 +++++++++++ launcher/ui/dialogs/ExportInstanceDialog.cpp | 5 ++- launcher/ui/dialogs/ExportInstanceDialog.h | 42 ++++++++++++----- launcher/ui/dialogs/ExportMrPackDialog.cpp | 3 ++ launcher/ui/dialogs/ExportMrPackDialog.h | 2 + 7 files changed, 115 insertions(+), 12 deletions(-) create mode 100644 launcher/FastFileIconProvider.cpp create mode 100644 launcher/FastFileIconProvider.h diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index 82ed09302..ce2771a49 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -724,6 +724,8 @@ SET(LAUNCHER_SOURCES SkinUtils.h FileIgnoreProxy.cpp FileIgnoreProxy.h + FastFileIconProvider.cpp + FastFileIconProvider.h # GUI - setup wizard ui/setupwizard/SetupWizard.h diff --git a/launcher/FastFileIconProvider.cpp b/launcher/FastFileIconProvider.cpp new file mode 100644 index 000000000..f2b6f4425 --- /dev/null +++ b/launcher/FastFileIconProvider.cpp @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * 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 + * 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 "FastFileIconProvider.h" + +#include +#include + +QIcon FastFileIconProvider::icon(const QFileInfo& info) const +{ +#if QT_VERSION >= QT_VERSION_CHECK(6, 4, 0) + bool link = info.isSymbolicLink() || info.isAlias() || info.isShortcut(); +#else + // in versions prior to 6.4 we don't have access to isAlias + bool link = info.isSymLink(); +#endif + QStyle::StandardPixmap icon; + + if (info.isDir()) { + if (link) + icon = QStyle::SP_DirLinkIcon; + else + icon = QStyle::SP_DirIcon; + } else { + if (link) + icon = QStyle::SP_FileLinkIcon; + else + icon = QStyle::SP_FileIcon; + } + + return QApplication::style()->standardIcon(icon); +} \ No newline at end of file diff --git a/launcher/FastFileIconProvider.h b/launcher/FastFileIconProvider.h new file mode 100644 index 000000000..208534044 --- /dev/null +++ b/launcher/FastFileIconProvider.h @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * 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 + * 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 + +class FastFileIconProvider : public QFileIconProvider { + public: + QIcon icon(const QFileInfo& info) const override; +}; \ No newline at end of file diff --git a/launcher/ui/dialogs/ExportInstanceDialog.cpp b/launcher/ui/dialogs/ExportInstanceDialog.cpp index 603a723cd..8ecd91a90 100644 --- a/launcher/ui/dialogs/ExportInstanceDialog.cpp +++ b/launcher/ui/dialogs/ExportInstanceDialog.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 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 @@ -46,7 +47,6 @@ #include #include #include -#include "StringUtils.h" #include "SeparatorPrefixTree.h" #include "Application.h" #include @@ -57,6 +57,7 @@ ExportInstanceDialog::ExportInstanceDialog(InstancePtr instance, QWidget *parent { ui->setupUi(this); auto model = new QFileSystemModel(this); + model->setIconProvider(&icons); auto root = instance->instanceRoot(); proxyModel = new FileIgnoreProxy(root, this); loadPackIgnore(); diff --git a/launcher/ui/dialogs/ExportInstanceDialog.h b/launcher/ui/dialogs/ExportInstanceDialog.h index d96f45376..5e8018751 100644 --- a/launcher/ui/dialogs/ExportInstanceDialog.h +++ b/launcher/ui/dialogs/ExportInstanceDialog.h @@ -1,16 +1,36 @@ -/* Copyright 2013-2021 MultiMC Contributors +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (C) 2023 TheKodeToad * - * 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 @@ -19,6 +39,7 @@ #include #include #include "FileIgnoreProxy.h" +#include "FastFileIconProvider.h" class BaseInstance; typedef std::shared_ptr InstancePtr; @@ -48,6 +69,7 @@ private: Ui::ExportInstanceDialog *ui; InstancePtr m_instance; FileIgnoreProxy * proxyModel; + FastFileIconProvider icons; private slots: void rowsInserted(QModelIndex parent, int top, int bottom); diff --git a/launcher/ui/dialogs/ExportMrPackDialog.cpp b/launcher/ui/dialogs/ExportMrPackDialog.cpp index 1551cc607..06e4693ea 100644 --- a/launcher/ui/dialogs/ExportMrPackDialog.cpp +++ b/launcher/ui/dialogs/ExportMrPackDialog.cpp @@ -26,6 +26,7 @@ #include #include #include +#include "FastFileIconProvider.h" #include "FileSystem.h" #include "MMCZip.h" #include "modplatform/modrinth/ModrinthPackExportTask.h" @@ -38,6 +39,8 @@ ExportMrPackDialog::ExportMrPackDialog(InstancePtr instance, QWidget* parent) ui->summary->setText(instance->notes().split(QRegularExpression("\\r?\\n"))[0]); QFileSystemModel* model = new QFileSystemModel(this); + model->setIconProvider(&icons); + // use the game root - everything outside cannot be exported const QDir root(instance->gameRoot()); proxy = new FileIgnoreProxy(instance->gameRoot(), this); diff --git a/launcher/ui/dialogs/ExportMrPackDialog.h b/launcher/ui/dialogs/ExportMrPackDialog.h index 63e3f0169..98f1d5fc5 100644 --- a/launcher/ui/dialogs/ExportMrPackDialog.h +++ b/launcher/ui/dialogs/ExportMrPackDialog.h @@ -20,6 +20,7 @@ #include #include "BaseInstance.h" +#include "FastFileIconProvider.h" #include "FileIgnoreProxy.h" namespace Ui { @@ -39,4 +40,5 @@ class ExportMrPackDialog : public QDialog { const InstancePtr instance; Ui::ExportMrPackDialog* ui; FileIgnoreProxy* proxy; + FastFileIconProvider icons; }; From 3c87e5d31eb8fb33d6e63a38377c9729a130af45 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Sat, 3 Jun 2023 13:44:09 +0100 Subject: [PATCH 193/207] Make mcInstance mutable Signed-off-by: TheKodeToad --- launcher/modplatform/modrinth/ModrinthPackExportTask.cpp | 2 +- launcher/modplatform/modrinth/ModrinthPackExportTask.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp b/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp index 98fbc218d..29df90ddf 100644 --- a/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp +++ b/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp @@ -40,7 +40,7 @@ ModrinthPackExportTask::ModrinthPackExportTask(const QString& name, , version(version) , summary(summary) , instance(instance) - , mcInstance(dynamic_cast(instance.get())) + , mcInstance(dynamic_cast(instance.get())) , gameRoot(instance->gameRoot()) , output(output) , filter(filter) diff --git a/launcher/modplatform/modrinth/ModrinthPackExportTask.h b/launcher/modplatform/modrinth/ModrinthPackExportTask.h index 5426d6da7..af00ffaab 100644 --- a/launcher/modplatform/modrinth/ModrinthPackExportTask.h +++ b/launcher/modplatform/modrinth/ModrinthPackExportTask.h @@ -51,7 +51,7 @@ class ModrinthPackExportTask : public Task { // inputs const QString name, version, summary; const InstancePtr instance; - const MinecraftInstance* mcInstance; + MinecraftInstance* mcInstance; const QDir gameRoot; const QString output; const MMCZip::FilterFunction filter; From f613b03efd58e04451e70d4e673adff5837492a9 Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Sat, 3 Jun 2023 08:28:49 -0700 Subject: [PATCH 194/207] Typo fix Co-authored-by: Sefa Eyeoglu Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- launcher/InstanceCopyTask.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/InstanceCopyTask.cpp b/launcher/InstanceCopyTask.cpp index a0b5635c5..60dcd5a1c 100644 --- a/launcher/InstanceCopyTask.cpp +++ b/launcher/InstanceCopyTask.cpp @@ -157,7 +157,7 @@ void InstanceCopyTask::copyFinished() allowed_symlinks.append("\n"); if (allowed_symlinks_file.isSymLink()) FS::deletePath(allowed_symlinks_file - .path()); // we dont want to modify the origonal. also make sure the resulting file is not itself a link. + .path()); // we dont want to modify the original. also make sure the resulting file is not itself a link. FS::write(allowed_symlinks_file.path(), allowed_symlinks); } From 5824047ffa4e31f018ddc068c2677ce9b8e5b43d Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Mon, 5 Jun 2023 01:12:16 -0700 Subject: [PATCH 195/207] fix(memory leak): cyclic refrence in translations model dl task Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- launcher/translations/TranslationsModel.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/launcher/translations/TranslationsModel.cpp b/launcher/translations/TranslationsModel.cpp index 46db48049..23e55c51d 100644 --- a/launcher/translations/TranslationsModel.cpp +++ b/launcher/translations/TranslationsModel.cpp @@ -190,7 +190,7 @@ struct TranslationsModel::Private std::unique_ptr m_qt_translator; std::unique_ptr m_app_translator; - Net::Download::Ptr m_index_task; + Net::Download* m_index_task; QString m_downloadingTranslation; NetJob::Ptr m_dl_job; NetJob::Ptr m_index_job; @@ -673,8 +673,9 @@ void TranslationsModel::downloadIndex() d->m_index_job.reset(new NetJob("Translations Index", APPLICATION->network())); MetaEntryPtr entry = APPLICATION->metacache()->resolveEntry("translations", "index_v2.json"); entry->setStale(true); - d->m_index_task = Net::Download::makeCached(QUrl(BuildConfig.TRANSLATIONS_BASE_URL + "index_v2.json"), entry); - d->m_index_job->addNetAction(d->m_index_task); + auto task = Net::Download::makeCached(QUrl(BuildConfig.TRANSLATIONS_BASE_URL + "index_v2.json"), entry); + d->m_index_task = task.get(); + d->m_index_job->addNetAction(task); connect(d->m_index_job.get(), &NetJob::failed, this, &TranslationsModel::indexFailed); connect(d->m_index_job.get(), &NetJob::succeeded, this, &TranslationsModel::indexReceived); d->m_index_job->start(); From 37b4f606c8e0853c831f792e7238587c66222176 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Mon, 5 Jun 2023 17:52:48 +0100 Subject: [PATCH 196/207] Validate input lengths on mrpack export Signed-off-by: TheKodeToad --- launcher/ui/dialogs/ExportMrPackDialog.cpp | 14 ++++++++++++++ launcher/ui/dialogs/ExportMrPackDialog.h | 1 + launcher/ui/dialogs/ExportMrPackDialog.ui | 6 +++++- 3 files changed, 20 insertions(+), 1 deletion(-) diff --git a/launcher/ui/dialogs/ExportMrPackDialog.cpp b/launcher/ui/dialogs/ExportMrPackDialog.cpp index 06e4693ea..239873f6d 100644 --- a/launcher/ui/dialogs/ExportMrPackDialog.cpp +++ b/launcher/ui/dialogs/ExportMrPackDialog.cpp @@ -26,6 +26,7 @@ #include #include #include +#include #include "FastFileIconProvider.h" #include "FileSystem.h" #include "MMCZip.h" @@ -38,6 +39,13 @@ ExportMrPackDialog::ExportMrPackDialog(InstancePtr instance, QWidget* parent) ui->name->setText(instance->name()); ui->summary->setText(instance->notes().split(QRegularExpression("\\r?\\n"))[0]); + // ensure a valid pack is generated + // the name and version fields mustn't be empty + connect(ui->name, &QLineEdit::textEdited, this, &ExportMrPackDialog::validate); + connect(ui->version, &QLineEdit::textEdited, this, &ExportMrPackDialog::validate); + // the instance name can technically be empty + validate(); + QFileSystemModel* model = new QFileSystemModel(this); model->setIconProvider(&icons); @@ -107,3 +115,9 @@ void ExportMrPackDialog::done(int result) QDialog::done(result); } + +void ExportMrPackDialog::validate() +{ + const bool invalid = ui->name->text().isEmpty() || ui->version->text().isEmpty(); + ui->buttonBox->button(QDialogButtonBox::Ok)->setDisabled(invalid); +} diff --git a/launcher/ui/dialogs/ExportMrPackDialog.h b/launcher/ui/dialogs/ExportMrPackDialog.h index 98f1d5fc5..1c70c4ae1 100644 --- a/launcher/ui/dialogs/ExportMrPackDialog.h +++ b/launcher/ui/dialogs/ExportMrPackDialog.h @@ -35,6 +35,7 @@ class ExportMrPackDialog : public QDialog { ~ExportMrPackDialog(); void done(int result) override; + void validate(); private: const InstancePtr instance; diff --git a/launcher/ui/dialogs/ExportMrPackDialog.ui b/launcher/ui/dialogs/ExportMrPackDialog.ui index f154d210b..9a7897378 100644 --- a/launcher/ui/dialogs/ExportMrPackDialog.ui +++ b/launcher/ui/dialogs/ExportMrPackDialog.ui @@ -51,7 +51,11 @@
- + + + 1.0.0 + +
From 6505a6280111e29b33d829703d1c1a87f90dba7a Mon Sep 17 00:00:00 2001 From: Trial97 Date: Tue, 6 Jun 2023 10:34:05 +0300 Subject: [PATCH 197/207] Renamed requires fields Signed-off-by: Trial97 --- launcher/meta/JsonFormat.cpp | 7 +++---- launcher/meta/Version.cpp | 4 ++-- launcher/meta/Version.h | 4 ++-- launcher/meta/VersionList.cpp | 4 ++-- launcher/minecraft/Component.cpp | 4 ++-- launcher/minecraft/OneSixVersionFormat.cpp | 10 +++++----- launcher/minecraft/VersionFile.h | 2 +- launcher/modplatform/atlauncher/ATLPackInstallTask.cpp | 2 +- .../atlauncher/AtlUserInteractionSupportImpl.cpp | 2 +- 9 files changed, 19 insertions(+), 20 deletions(-) diff --git a/launcher/meta/JsonFormat.cpp b/launcher/meta/JsonFormat.cpp index 473f37d66..cb2d06ea0 100644 --- a/launcher/meta/JsonFormat.cpp +++ b/launcher/meta/JsonFormat.cpp @@ -56,10 +56,10 @@ static Version::Ptr parseCommonVersion(const QString &uid, const QJsonObject &ob version->setType(ensureString(obj, "type", QString())); version->setRecommended(ensureBoolean(obj, QString("recommended"), false)); version->setVolatile(ensureBoolean(obj, QString("volatile"), false)); - RequireSet requires, conflicts; - parseRequires(obj, &requires, "requires"); + RequireSet reqs, conflicts; + parseRequires(obj, &reqs, "requires"); parseRequires(obj, &conflicts, "conflicts"); - version->setRequires(requires, conflicts); + version->setRequires(reqs, conflicts); return version; } @@ -176,7 +176,6 @@ void parseRequires(const QJsonObject& obj, RequireSet* ptr, const char * keyName { if(obj.contains(keyName)) { - QSet requires; auto reqArray = requireArray(obj, keyName); auto iter = reqArray.begin(); while(iter != reqArray.end()) diff --git a/launcher/meta/Version.cpp b/launcher/meta/Version.cpp index e617abf82..0718a4204 100644 --- a/launcher/meta/Version.cpp +++ b/launcher/meta/Version.cpp @@ -116,9 +116,9 @@ void Meta::Version::setTime(const qint64 time) emit timeChanged(); } -void Meta::Version::setRequires(const Meta::RequireSet &requires, const Meta::RequireSet &conflicts) +void Meta::Version::setRequires(const Meta::RequireSet &reqs, const Meta::RequireSet &conflicts) { - m_requires = requires; + m_requires = reqs; m_conflicts = conflicts; emit requiresChanged(); } diff --git a/launcher/meta/Version.h b/launcher/meta/Version.h index 781561931..59a96a68b 100644 --- a/launcher/meta/Version.h +++ b/launcher/meta/Version.h @@ -63,7 +63,7 @@ public: { return m_time; } - const Meta::RequireSet &requires() const + const Meta::RequireSet &requiredSet() const { return m_requires; } @@ -91,7 +91,7 @@ public: public: // for usage by format parsers only void setType(const QString &type); void setTime(const qint64 time); - void setRequires(const Meta::RequireSet &requires, const Meta::RequireSet &conflicts); + void setRequires(const Meta::RequireSet &reqs, const Meta::RequireSet &conflicts); void setVolatile(bool volatile_); void setRecommended(bool recommended); void setProvidesRecommendations(); diff --git a/launcher/meta/VersionList.cpp b/launcher/meta/VersionList.cpp index 7f001dfc2..9f4482784 100644 --- a/launcher/meta/VersionList.cpp +++ b/launcher/meta/VersionList.cpp @@ -77,7 +77,7 @@ QVariant VersionList::data(const QModelIndex &index, int role) const case ParentVersionRole: { // FIXME: HACK: this should be generic and be replaced by something else. Anything that is a hard 'equals' dep is a 'parent uid'. - auto & reqs = version->requires(); + auto & reqs = version->requiredSet(); auto iter = std::find_if(reqs.begin(), reqs.end(), [](const Require & req) { return req.uid == "net.minecraft"; @@ -92,7 +92,7 @@ QVariant VersionList::data(const QModelIndex &index, int role) const case UidRole: return version->uid(); case TimeRole: return version->time(); - case RequiresRole: return QVariant::fromValue(version->requires()); + case RequiresRole: return QVariant::fromValue(version->requiredSet()); case SortRole: return version->rawTime(); case VersionPtrRole: return QVariant::fromValue(version); case RecommendedRole: return version->isRecommended(); diff --git a/launcher/minecraft/Component.cpp b/launcher/minecraft/Component.cpp index 7e5b60589..ff81fcbb8 100644 --- a/launcher/minecraft/Component.cpp +++ b/launcher/minecraft/Component.cpp @@ -451,9 +451,9 @@ void Component::updateCachedData() m_cachedVolatile = file->m_volatile; changed = true; } - if(!deepCompare(m_cachedRequires, file->requires)) + if(!deepCompare(m_cachedRequires, file->m_requires)) { - m_cachedRequires = file->requires; + m_cachedRequires = file->m_requires; changed = true; } if(!deepCompare(m_cachedConflicts, file->conflicts)) diff --git a/launcher/minecraft/OneSixVersionFormat.cpp b/launcher/minecraft/OneSixVersionFormat.cpp index 888b68609..b586198bf 100644 --- a/launcher/minecraft/OneSixVersionFormat.cpp +++ b/launcher/minecraft/OneSixVersionFormat.cpp @@ -276,7 +276,7 @@ VersionFilePtr OneSixVersionFormat::versionFileFromJson(const QJsonDocument &doc if (root.contains("requires")) { - Meta::parseRequires(root, &out->requires); + Meta::parseRequires(root, &out->m_requires); } QString dependsOnMinecraftVersion = root.value("mcVersion").toString(); if(!dependsOnMinecraftVersion.isEmpty()) @@ -284,9 +284,9 @@ VersionFilePtr OneSixVersionFormat::versionFileFromJson(const QJsonDocument &doc Meta::Require mcReq; mcReq.uid = "net.minecraft"; mcReq.equalsVersion = dependsOnMinecraftVersion; - if (out->requires.count(mcReq) == 0) + if (out->m_requires.count(mcReq) == 0) { - out->requires.insert(mcReq); + out->m_requires.insert(mcReq); } } if (root.contains("conflicts")) @@ -392,9 +392,9 @@ QJsonDocument OneSixVersionFormat::versionFileToJson(const VersionFilePtr &patch } root.insert("mods", array); } - if(!patch->requires.empty()) + if(!patch->m_requires.empty()) { - Meta::serializeRequires(root, &patch->requires, "requires"); + Meta::serializeRequires(root, &patch->m_requires, "requires"); } if(!patch->conflicts.empty()) { diff --git a/launcher/minecraft/VersionFile.h b/launcher/minecraft/VersionFile.h index 11c5a3af3..8e9dd1670 100644 --- a/launcher/minecraft/VersionFile.h +++ b/launcher/minecraft/VersionFile.h @@ -138,7 +138,7 @@ public: /* data */ * Prism Launcher: set of packages this depends on * NOTE: this is shared with the meta format!!! */ - Meta::RequireSet requires; + Meta::RequireSet m_requires; /** * Prism Launcher: set of packages this conflicts with diff --git a/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp b/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp index 96cea7b7d..07e0bf23b 100644 --- a/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp +++ b/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp @@ -352,7 +352,7 @@ QString PackInstallTask::getVersionForLoader(QString uid) if(m_version.loader.recommended || m_version.loader.latest) { for (int i = 0; i < vlist->versions().size(); i++) { auto version = vlist->versions().at(i); - auto reqs = version->requires(); + auto reqs = version->requiredSet(); // filter by minecraft version, if the loader depends on a certain version. // not all mod loaders depend on a given Minecraft version, so we won't do this diff --git a/launcher/ui/pages/modplatform/atlauncher/AtlUserInteractionSupportImpl.cpp b/launcher/ui/pages/modplatform/atlauncher/AtlUserInteractionSupportImpl.cpp index f5f50caee..3d2d568a1 100644 --- a/launcher/ui/pages/modplatform/atlauncher/AtlUserInteractionSupportImpl.cpp +++ b/launcher/ui/pages/modplatform/atlauncher/AtlUserInteractionSupportImpl.cpp @@ -68,7 +68,7 @@ QString AtlUserInteractionSupportImpl::chooseVersion(Meta::VersionList::Ptr vlis // select recommended build for (int i = 0; i < vlist->versions().size(); i++) { auto version = vlist->versions().at(i); - auto reqs = version->requires(); + auto reqs = version->requiredSet(); // filter by minecraft version, if the loader depends on a certain version. if (minecraftVersion != nullptr) { From 3a068970f99c1437717651da951cee0762921308 Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Tue, 6 Jun 2023 07:03:13 -0700 Subject: [PATCH 198/207] Packaging: file manifest in portable install (#1101) --- .github/workflows/build.yml | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 691e257bf..a0a0943ad 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -375,6 +375,8 @@ jobs: shell: msys2 {0} run: | cmake --install ${{ env.BUILD_DIR }} + touch ${{ env.INSTALL_DIR }}/manifest.txt + for l in $(find ${{ env.INSTALL_DIR }} -type f); do l=$(cygpath -u $l); l=${l#$(pwd)/}; l=${l#${{ env.INSTALL_DIR }}/}; l=${l#./}; echo $l; done >> ${{ env.INSTALL_DIR }}/manifest.txt - name: Package (Windows MSVC) if: runner.os == 'Windows' && matrix.msystem == '' @@ -387,6 +389,10 @@ jobs: Copy-Item D:/a/PrismLauncher/Qt/Tools/OpenSSL/Win_x86/bin/libcrypto-1_1.dll -Destination libcrypto-1_1.dll Copy-Item D:/a/PrismLauncher/Qt/Tools/OpenSSL/Win_x86/bin/libssl-1_1.dll -Destination libssl-1_1.dll } + cd ${{ github.workspace }} + + Get-ChildItem ${{ env.INSTALL_DIR }} -Recurse | ForEach FullName | Resolve-Path -Relative | %{ $_.TrimStart('.\') } | %{ $_.TrimStart('${{ env.INSTALL_DIR }}') } | %{ $_.TrimStart('\') } | Out-File -FilePath ${{ env.INSTALL_DIR }}/manifest.txt + - name: Fetch codesign certificate (Windows) if: runner.os == 'Windows' @@ -411,12 +417,15 @@ jobs: run: | cp -r ${{ env.INSTALL_DIR }} ${{ env.INSTALL_PORTABLE_DIR }} # cmake install on Windows is slow, let's just copy instead cmake --install ${{ env.BUILD_DIR }} --prefix ${{ env.INSTALL_PORTABLE_DIR }} --component portable + for l in $(find ${{ env.INSTALL_PORTABLE_DIR }} -type f); do l=$(cygpath -u $l); l=${l#$(pwd)/}; l=${l#${{ env.INSTALL_PORTABLE_DIR }}/}; l=${l#./}; echo $l; done >> ${{ env.INSTALL_PORTABLE_DIR }}/manifest.txt - name: Package (Windows MSVC, portable) if: runner.os == 'Windows' && matrix.msystem == '' run: | cp -r ${{ env.INSTALL_DIR }} ${{ env.INSTALL_PORTABLE_DIR }} # cmake install on Windows is slow, let's just copy instead cmake --install ${{ env.BUILD_DIR }} --prefix ${{ env.INSTALL_PORTABLE_DIR }} --component portable + + Get-ChildItem ${{ env.INSTALL_PORTABLE_DIR }} -Recurse | ForEach FullName | Resolve-Path -Relative | %{ $_.TrimStart('.\') } | %{ $_.TrimStart('${{ env.INSTALL_PORTABLE_DIR }}') } | %{ $_.TrimStart('\') } | Out-File -FilePath ${{ env.INSTALL_DIR }}/manifest.txt - name: Package (Windows, installer) if: runner.os == 'Windows' @@ -437,6 +446,7 @@ jobs: if: runner.os == 'Linux' run: | cmake --install ${{ env.BUILD_DIR }} --prefix ${{ env.INSTALL_DIR }} + for l in $(find ${{ env.INSTALL_DIR }} -type f); do l=${l#$(pwd)/}; l=${l#${{ env.INSTALL_DIR }}/}; l=${l#./}; echo $l; done > ${{ env.INSTALL_DIR }}/manifest.txt cd ${{ env.INSTALL_DIR }} tar --owner root --group root -czf ../PrismLauncher.tar.gz * @@ -446,6 +456,8 @@ jobs: run: | cmake --install ${{ env.BUILD_DIR }} --prefix ${{ env.INSTALL_PORTABLE_DIR }} cmake --install ${{ env.BUILD_DIR }} --prefix ${{ env.INSTALL_PORTABLE_DIR }} --component portable + for l in $(find ${{ env.INSTALL_PORTABLE_DIR }} -type f); do l=${l#$(pwd)/}; l=${l#${{ env.INSTALL_PORTABLE_DIR }}/}; l=${l#./}; echo $l; done > ${{ env.INSTALL_PORTABLE_DIR }}/manifest.txt + cd ${{ env.INSTALL_PORTABLE_DIR }} tar -czf ../PrismLauncher-portable.tar.gz * From e8843417952ae0dc881a1ef8f352e4959f2c572b Mon Sep 17 00:00:00 2001 From: Tayou Date: Tue, 6 Jun 2023 18:15:26 +0200 Subject: [PATCH 199/207] save meta custom url as string, not QUrl Signed-off-by: Tayou --- launcher/ui/pages/global/APIPage.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/ui/pages/global/APIPage.cpp b/launcher/ui/pages/global/APIPage.cpp index f662ee1c3..dca1b3a63 100644 --- a/launcher/ui/pages/global/APIPage.cpp +++ b/launcher/ui/pages/global/APIPage.cpp @@ -177,7 +177,7 @@ void APIPage::applySettings() metaURL.setScheme("https"); } - s->set("MetaURLOverride", metaURL); + s->set("MetaURLOverride", metaURL.toString()); QString flameKey = ui->flameKey->text(); s->set("FlameKeyOverride", flameKey); QString modrinthToken = ui->modrinthToken->text(); From d59a06344a73e028a6f026d9f32027c2b9f73b39 Mon Sep 17 00:00:00 2001 From: leo78913 Date: Sun, 14 May 2023 15:03:32 -0300 Subject: [PATCH 200/207] fix main toolbar accounts toolbutton name previously it was not using the selected account name when opening the launcher and i also added an action group to the menu items so it uses radio buttons instead of checkboxes :p Signed-off-by: leo78913 --- launcher/ui/MainWindow.cpp | 31 +++++++++++++++++++++---------- 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp index 72b7db641..fab1185dd 100644 --- a/launcher/ui/MainWindow.cpp +++ b/launcher/ui/MainWindow.cpp @@ -199,7 +199,6 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWi helpMenuButton->setPopupMode(QToolButton::InstantPopup); auto accountMenuButton = dynamic_cast(ui->mainToolBar->widgetForAction(ui->actionAccountsButton)); - ui->actionAccountsButton->setMenu(ui->accountsMenu); accountMenuButton->setPopupMode(QToolButton::InstantPopup); } @@ -414,15 +413,6 @@ void MainWindow::keyReleaseEvent(QKeyEvent *event) void MainWindow::retranslateUi() { - auto accounts = APPLICATION->accounts(); - MinecraftAccountPtr defaultAccount = accounts->defaultAccount(); - if(defaultAccount) { - auto profileLabel = profileInUseFilter(defaultAccount->profileName(), defaultAccount->isInUse()); - ui->actionAccountsButton->setText(profileLabel); - } - else { - ui->actionAccountsButton->setText(tr("Accounts")); - } if (m_selectedInstance) { m_statusLeft->setText(m_selectedInstance->getStatusbarDescription()); @@ -432,6 +422,12 @@ void MainWindow::retranslateUi() ui->retranslateUi(this); + MinecraftAccountPtr defaultAccount = APPLICATION->accounts()->defaultAccount(); + if(defaultAccount) { + auto profileLabel = profileInUseFilter(defaultAccount->profileName(), defaultAccount->isInUse()); + ui->actionAccountsButton->setText(profileLabel); + } + changeIconButton->setToolTip(ui->actionChangeInstIcon->toolTip()); renameButton->setToolTip(ui->actionRenameInstance->toolTip()); @@ -673,6 +669,15 @@ void MainWindow::repopulateAccountsMenu() { ui->accountsMenu->clear(); + // NOTE: this is done so the accounts button text is not set to the accounts menu title + QMenu *accountsButtonMenu = ui->actionAccountsButton->menu(); + if (accountsButtonMenu) { + accountsButtonMenu->clear(); + } else { + accountsButtonMenu = new QMenu(this); + ui->actionAccountsButton->setMenu(accountsButtonMenu); + } + auto accounts = APPLICATION->accounts(); MinecraftAccountPtr defaultAccount = accounts->defaultAccount(); @@ -687,6 +692,8 @@ void MainWindow::repopulateAccountsMenu() } } + QActionGroup* accountsGroup = new QActionGroup(this); + if (accounts->count() <= 0) { ui->actionNoAccountsAdded->setEnabled(false); @@ -702,6 +709,7 @@ void MainWindow::repopulateAccountsMenu() QAction *action = new QAction(profileLabel, this); action->setData(i); action->setCheckable(true); + action->setActionGroup(accountsGroup); if (defaultAccount == account) { action->setChecked(true); @@ -730,6 +738,7 @@ void MainWindow::repopulateAccountsMenu() ui->actionNoDefaultAccount->setData(-1); ui->actionNoDefaultAccount->setChecked(!defaultAccount); + ui->actionNoDefaultAccount->setActionGroup(accountsGroup); ui->accountsMenu->addAction(ui->actionNoDefaultAccount); @@ -737,6 +746,8 @@ void MainWindow::repopulateAccountsMenu() ui->accountsMenu->addSeparator(); ui->accountsMenu->addAction(ui->actionManageAccounts); + + accountsButtonMenu->addActions(ui->accountsMenu->actions()); } void MainWindow::updatesAllowedChanged(bool allowed) From a807b231a75fdcb95408aa35a3143e2e5f5ca60f Mon Sep 17 00:00:00 2001 From: leo78913 Date: Tue, 6 Jun 2023 15:14:50 -0300 Subject: [PATCH 201/207] fix: fix crash when selecting resource/texture/shader packs Signed-off-by: leo78913 --- launcher/ui/dialogs/ResourceDownloadDialog.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/launcher/ui/dialogs/ResourceDownloadDialog.cpp b/launcher/ui/dialogs/ResourceDownloadDialog.cpp index 61c48e759..6d90480ff 100644 --- a/launcher/ui/dialogs/ResourceDownloadDialog.cpp +++ b/launcher/ui/dialogs/ResourceDownloadDialog.cpp @@ -253,6 +253,8 @@ QList ResourcePackDownloadDialog::getPages() if (APPLICATION->capabilities() & Application::SupportsFlame) pages.append(FlameResourcePackPage::create(this, *m_instance)); + m_selectedPage = dynamic_cast(pages[0]); + return pages; } @@ -278,6 +280,8 @@ QList TexturePackDownloadDialog::getPages() if (APPLICATION->capabilities() & Application::SupportsFlame) pages.append(FlameTexturePackPage::create(this, *m_instance)); + m_selectedPage = dynamic_cast(pages[0]); + return pages; } @@ -301,6 +305,8 @@ QList ShaderPackDownloadDialog::getPages() pages.append(ModrinthShaderPackPage::create(this, *m_instance)); + m_selectedPage = dynamic_cast(pages[0]); + return pages; } From a9302468e78c3e05ff6dd1e0bdcecea99e1a1e99 Mon Sep 17 00:00:00 2001 From: leo78913 Date: Tue, 6 Jun 2023 16:10:01 -0300 Subject: [PATCH 202/207] update resource and data pack pack_format_versions Signed-off-by: leo78913 --- launcher/minecraft/mod/DataPack.cpp | 4 +++- launcher/minecraft/mod/ResourcePack.cpp | 3 ++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/launcher/minecraft/mod/DataPack.cpp b/launcher/minecraft/mod/DataPack.cpp index 5c58f6b27..ca75cd2aa 100644 --- a/launcher/minecraft/mod/DataPack.cpp +++ b/launcher/minecraft/mod/DataPack.cpp @@ -33,7 +33,9 @@ static const QMap> s_pack_format_versions = { { 4, { Version("1.13"), Version("1.14.4") } }, { 5, { Version("1.15"), Version("1.16.1") } }, { 6, { Version("1.16.2"), Version("1.16.5") } }, { 7, { Version("1.17"), Version("1.17.1") } }, { 8, { Version("1.18"), Version("1.18.1") } }, { 9, { Version("1.18.2"), Version("1.18.2") } }, - { 10, { Version("1.19"), Version("1.19.3") } }, + { 10, { Version("1.19"), Version("1.19.3") } }, { 11, { Version("23w03a"), Version("23w05a") } }, + { 12, { Version("1.19.4"), Version("1.19.4") } }, { 13, { Version("23w12a"), Version("23w14a") } }, + { 14, { Version("23w16a"), Version("23w17a") } }, { 15, { Version("1.20"), Version("1.20") } }, }; void DataPack::setPackFormat(int new_format_id) diff --git a/launcher/minecraft/mod/ResourcePack.cpp b/launcher/minecraft/mod/ResourcePack.cpp index 876d5c3ee..759d2b566 100644 --- a/launcher/minecraft/mod/ResourcePack.cpp +++ b/launcher/minecraft/mod/ResourcePack.cpp @@ -18,7 +18,8 @@ static const QMap> s_pack_format_versions = { { 5, { Version("1.15"), Version("1.16.1") } }, { 6, { Version("1.16.2"), Version("1.16.5") } }, { 7, { Version("1.17"), Version("1.17.1") } }, { 8, { Version("1.18"), Version("1.18.2") } }, { 9, { Version("1.19"), Version("1.19.2") } }, { 11, { Version("22w42a"), Version("22w44a") } }, - { 12, { Version("1.19.3"), Version("1.19.3") } }, + { 12, { Version("1.19.3"), Version("1.19.3") } }, { 13, { Version("1.19.4"), Version("1.19.4") } }, + { 14, { Version("1.20"), Version("1.20") } } }; void ResourcePack::setPackFormat(int new_format_id) From d12110b47bb1bb5109e41f2cf0f20908ceb8bc10 Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Wed, 7 Jun 2023 06:21:01 -0700 Subject: [PATCH 203/207] fix #1118 : use `filePath` not `path` on `QFileInfo` Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- launcher/InstanceCopyTask.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/launcher/InstanceCopyTask.cpp b/launcher/InstanceCopyTask.cpp index 60dcd5a1c..57a3143a1 100644 --- a/launcher/InstanceCopyTask.cpp +++ b/launcher/InstanceCopyTask.cpp @@ -149,7 +149,7 @@ void InstanceCopyTask::copyFinished() QByteArray allowed_symlinks; if (allowed_symlinks_file.exists()) { - allowed_symlinks.append(FS::read(allowed_symlinks_file.path())); + allowed_symlinks.append(FS::read(allowed_symlinks_file.filePath())); if (allowed_symlinks.right(1) != "\n") allowed_symlinks.append("\n"); // we want to be on a new line } @@ -157,9 +157,9 @@ void InstanceCopyTask::copyFinished() allowed_symlinks.append("\n"); if (allowed_symlinks_file.isSymLink()) FS::deletePath(allowed_symlinks_file - .path()); // we dont want to modify the original. also make sure the resulting file is not itself a link. + .filePath()); // we dont want to modify the original. also make sure the resulting file is not itself a link. - FS::write(allowed_symlinks_file.path(), allowed_symlinks); + FS::write(allowed_symlinks_file.filePath(), allowed_symlinks); } emitSucceeded(); From 5b3431b26884aaee695dba97ca5faaddd46a5081 Mon Sep 17 00:00:00 2001 From: DioEgizio <83089242+DioEgizio@users.noreply.github.com> Date: Thu, 8 Jun 2023 14:55:09 +0200 Subject: [PATCH 204/207] chore: revert macOS Qt version back to 6.5.0 Qt 6.5.1 seems to cause issues with Rectangle Signed-off-by: DioEgizio <83089242+DioEgizio@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 a0a0943ad..3ce9fb403 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.5.1' + qt_version: '6.5.0' qt_modules: 'qt5compat qtimageformats' qt_tools: '' From 75b1eaed0c71147398788d127df8240936ec72dc Mon Sep 17 00:00:00 2001 From: DioEgizio <83089242+DioEgizio@users.noreply.github.com> Date: Thu, 8 Jun 2023 17:12:49 +0200 Subject: [PATCH 205/207] chore: bump macOS requirement to 11.0 Noticed only now that Qt 6.5 bumps the macOS requirement to macOS 11. This was basically already effective in prism since with the Qt 6.5 bump pr macOS 10.15 user's wouldn't be able to run this, but updating the requirement here makes it more clear for the end user trying to run prism on macOS 10.15 Signed-off-by: DioEgizio <83089242+DioEgizio@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 3ce9fb403..a6a6eceaa 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -86,7 +86,7 @@ jobs: - os: macos-12 name: macOS - macosx_deployment_target: 10.15 + macosx_deployment_target: 11.0 qt_ver: 6 qt_host: mac qt_arch: '' From 5aa1c340dc6c353e34d0fbcac84fb89798a06481 Mon Sep 17 00:00:00 2001 From: Tayou Date: Sun, 11 Jun 2023 01:58:37 +0200 Subject: [PATCH 206/207] rainbow konami & toggle Signed-off-by: Tayou --- launcher/ui/MainWindow.cpp | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp index 834f57dd9..dfee3b471 100644 --- a/launcher/ui/MainWindow.cpp +++ b/launcher/ui/MainWindow.cpp @@ -470,7 +470,23 @@ void MainWindow::lockToolbars(bool state) void MainWindow::konamiTriggered() { - qDebug() << "Super Secret Mode ACTIVATED!"; + QString gradient = " stop:0 rgba(125, 0, 0, 255), stop:0.166 rgba(125, 125, 0, 255), stop:0.333 rgba(0, 125, 0, 255), stop:0.5 rgba(0, 125, 125, 255), stop:0.666 rgba(0, 0, 125, 255), stop:0.833 rgba(125, 0, 125, 255), stop:1 rgba(125, 0, 0, 255));"; + QString stylesheet = "background-color: qlineargradient(spread:pad, x1:0, y1:0, x2:1, y2:0," + gradient; + if (ui->mainToolBar->styleSheet() == stylesheet) { + ui->mainToolBar->setStyleSheet(""); + ui->instanceToolBar->setStyleSheet(""); + ui->centralWidget->setStyleSheet(""); + ui->newsToolBar->setStyleSheet(""); + ui->statusBar->setStyleSheet(""); + qDebug() << "Super Secret Mode DEACTIVATED!"; + } else { + ui->mainToolBar->setStyleSheet(stylesheet); + ui->instanceToolBar->setStyleSheet("background-color: qlineargradient(spread:pad, x1:0, y1:0, x2:0, y2:1," + gradient); + ui->centralWidget->setStyleSheet("background-color: qlineargradient(spread:pad, x1:0, y1:0, x2:1, y2:1," + gradient); + ui->newsToolBar->setStyleSheet(stylesheet); + ui->statusBar->setStyleSheet(stylesheet); + qDebug() << "Super Secret Mode ACTIVATED!"; + } } void MainWindow::showInstanceContextMenu(const QPoint &pos) From d6c7b4e813a254a2fb78547a7947aa913d80dbbb Mon Sep 17 00:00:00 2001 From: leo78913 Date: Sun, 11 Jun 2023 21:49:33 -0300 Subject: [PATCH 207/207] add icons to export menu Signed-off-by: leo78913 --- launcher/ui/MainWindow.cpp | 9 ++++++--- launcher/ui/MainWindow.ui | 10 ++++++---- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp index 834f57dd9..bb7844d38 100644 --- a/launcher/ui/MainWindow.cpp +++ b/launcher/ui/MainWindow.cpp @@ -187,7 +187,7 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWi } - // set the menu for the folders help, and accounts tool buttons + // set the menu for the folders help, accounts, and export tool buttons { auto foldersMenuButton = dynamic_cast(ui->mainToolBar->widgetForAction(ui->actionFoldersButton)); ui->actionFoldersButton->setMenu(ui->foldersMenu); @@ -201,6 +201,11 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWi auto accountMenuButton = dynamic_cast(ui->mainToolBar->widgetForAction(ui->actionAccountsButton)); accountMenuButton->setPopupMode(QToolButton::InstantPopup); + + auto exportInstanceMenu = new QMenu(this); + exportInstanceMenu->addAction(ui->actionExportInstanceZip); + exportInstanceMenu->addAction(ui->actionExportInstanceMrPack); + ui->actionExportInstance->setMenu(exportInstanceMenu); } // hide, disable and show stuff @@ -397,8 +402,6 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWi // removing this looks stupid view->setFocus(); - ui->actionExportInstance->setMenu(ui->exportInstanceMenu); - retranslateUi(); } diff --git a/launcher/ui/MainWindow.ui b/launcher/ui/MainWindow.ui index 4a89bc100..9e639ab05 100644 --- a/launcher/ui/MainWindow.ui +++ b/launcher/ui/MainWindow.ui @@ -150,10 +150,6 @@ - - - - @@ -467,11 +463,17 @@ + + + Prism Launcher (zip) + + + Modrinth (mrpack)