From a80b820e9491624429c75a049140ebe738b6d604 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Wed, 1 Mar 2023 19:38:27 +0000 Subject: [PATCH 001/122] 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/122] 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/122] 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/122] 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/122] 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/122] 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/122] 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/122] 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/122] 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/122] 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/122] 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/122] 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/122] 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/122] 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/122] =?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/122] 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/122] 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/122] 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/122] 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 ddca838e46d2d147cbc5965be31895dd73676c79 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Sat, 18 Mar 2023 12:21:13 +0000 Subject: [PATCH 020/122] 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 021/122] 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 022/122] 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 023/122] 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 710156b9f1b9555cd7e5562c8673074f80457d92 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Sun, 19 Mar 2023 21:25:12 +0000 Subject: [PATCH 024/122] 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 025/122] 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 e42050cc8a2f74178d6cd0afe8c92ae9b802cf73 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Tue, 28 Mar 2023 14:22:28 +0100 Subject: [PATCH 026/122] 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 871d647c93944b7dabd070ed49a6eeaa8745d55a Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Thu, 6 Apr 2023 19:18:36 +0100 Subject: [PATCH 027/122] 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 028/122] 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 029/122] 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 030/122] 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 031/122] 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 032/122] 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 3a7961834abbd28014de89799514510e74f365ab Mon Sep 17 00:00:00 2001 From: Kode Date: Sun, 9 Apr 2023 21:28:40 +0100 Subject: [PATCH 033/122] 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 b8e0c8ebc62ce22d5dcaf4295d80c6070eb45f49 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Mon, 17 Apr 2023 10:16:03 +0100 Subject: [PATCH 034/122] 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 035/122] 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 036/122] 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 037/122] 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 038/122] 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 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 039/122] 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 040/122] 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 041/122] 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 042/122] 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 043/122] 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 044/122] 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 045/122] 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 046/122] 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 047/122] 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 048/122] 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 049/122] 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 050/122] 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 051/122] 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 052/122] 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 053/122] 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 054/122] 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 055/122] 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 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 056/122] 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 057/122] 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 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 058/122] 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 059/122] 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 060/122] 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 061/122] 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 062/122] 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 063/122] 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 064/122] 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 065/122] 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 066/122] =?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 067/122] 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 068/122] 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 069/122] 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 070/122] 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 071/122] 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 072/122] 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 073/122] 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 074/122] 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 075/122] 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 076/122] 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 077/122] 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 078/122] 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 079/122] 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 080/122] 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 081/122] 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 082/122] 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 083/122] 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 084/122] 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 085/122] 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 086/122] 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 087/122] 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 088/122] 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 089/122] 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 090/122] 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 091/122] 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 092/122] 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 093/122] 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 094/122] 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 095/122] 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 096/122] 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 097/122] 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 098/122] 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 099/122] 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 100/122] 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 101/122] 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 102/122] 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 103/122] 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 104/122] 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 105/122] 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 106/122] 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 107/122] 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 108/122] 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 109/122] 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 110/122] 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 111/122] 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 112/122] 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 113/122] 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 114/122] 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 93436b09401b7f425cffaac49e03756526d04cb3 Mon Sep 17 00:00:00 2001 From: guihkx <626206+guihkx@users.noreply.github.com> Date: Fri, 9 Jun 2023 00:48:05 -0300 Subject: [PATCH 115/122] ci: exclude .git directory from the source code tarball Reduces the its final size from 17.1 MiB down to 7.9 MiB. Signed-off-by: guihkx <626206+guihkx@users.noreply.github.com> --- .github/workflows/trigger_release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/trigger_release.yml b/.github/workflows/trigger_release.yml index 3c56a38ea..f19b83986 100644 --- a/.github/workflows/trigger_release.yml +++ b/.github/workflows/trigger_release.yml @@ -47,7 +47,7 @@ jobs: mv PrismLauncher-macOS-Legacy*/PrismLauncher.tar.gz PrismLauncher-macOS-Legacy-${{ env.VERSION }}.tar.gz mv PrismLauncher-macOS*/PrismLauncher.tar.gz PrismLauncher-macOS-${{ env.VERSION }}.tar.gz - tar -czf PrismLauncher-${{ env.VERSION }}.tar.gz PrismLauncher-${{ env.VERSION }} + tar --exclude='.git' -czf PrismLauncher-${{ env.VERSION }}.tar.gz PrismLauncher-${{ env.VERSION }} for d in PrismLauncher-Windows-MSVC*; do cd "${d}" || continue From f2932c6d0387c41bd876d4af4148a7ffb5c83154 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Fri, 9 Jun 2023 21:23:41 +0300 Subject: [PATCH 116/122] Fixed some crashes Signed-off-by: Trial97 --- launcher/net/ByteArraySink.h | 15 ++++++++++++--- launcher/tasks/ConcurrentTask.cpp | 3 +-- launcher/ui/pages/modplatform/ResourcePage.cpp | 9 ++++++--- 3 files changed, 19 insertions(+), 8 deletions(-) diff --git a/launcher/net/ByteArraySink.h b/launcher/net/ByteArraySink.h index 501318a11..0d77727ee 100644 --- a/launcher/net/ByteArraySink.h +++ b/launcher/net/ByteArraySink.h @@ -53,7 +53,10 @@ class ByteArraySink : public Sink { public: auto init(QNetworkRequest& request) -> Task::State override { - m_output->clear(); + if (m_output) + m_output->clear(); + else + qWarning() << "ByteArraySink was not cleared because it's not adresable"; if (initAllValidators(request)) return Task::State::Running; return Task::State::Failed; @@ -61,7 +64,10 @@ class ByteArraySink : public Sink { auto write(QByteArray& data) -> Task::State override { - m_output->append(data); + if (m_output) + m_output->append(data); + else + qWarning() << "ByteArraySink no write because it's not adresable"; if (writeAllValidators(data)) return Task::State::Running; return Task::State::Failed; @@ -69,7 +75,10 @@ class ByteArraySink : public Sink { auto abort() -> Task::State override { - m_output->clear(); + if (m_output) + m_output->clear(); + else + qWarning() << "ByteArraySink no clear because it's not adresable"; failAllValidators(); return Task::State::Failed; } diff --git a/launcher/tasks/ConcurrentTask.cpp b/launcher/tasks/ConcurrentTask.cpp index 5ee145055..9aada5e69 100644 --- a/launcher/tasks/ConcurrentTask.cpp +++ b/launcher/tasks/ConcurrentTask.cpp @@ -138,19 +138,18 @@ 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); + qsizetype num_starts = qMin(m_queue.size(), m_total_max_size - m_doing.size()); auto task_progress = std::make_shared(next->getUid()); m_task_progress.insert(next->getUid(), task_progress); updateState(); updateStepProgress(*task_progress.get(), Operation::ADDED); - QCoreApplication::processEvents(); QMetaObject::invokeMethod(next.get(), &Task::start, Qt::QueuedConnection); // Allow going up the number of concurrent tasks in case of tasks being added in the middle of a running task. - int num_starts = qMin(m_queue.size(), m_total_max_size - m_doing.size()); for (int i = 0; i < num_starts; i++) QMetaObject::invokeMethod(this, &ConcurrentTask::startNext, Qt::QueuedConnection); } diff --git a/launcher/ui/pages/modplatform/ResourcePage.cpp b/launcher/ui/pages/modplatform/ResourcePage.cpp index 736034add..2bb867775 100644 --- a/launcher/ui/pages/modplatform/ResourcePage.cpp +++ b/launcher/ui/pages/modplatform/ResourcePage.cpp @@ -240,10 +240,13 @@ void ResourcePage::updateSelectionButton() } m_ui->resourceSelectionButton->setEnabled(true); - if (!getCurrentPack()->isVersionSelected(m_selected_version_index)) { - m_ui->resourceSelectionButton->setText(tr("Select %1 for download").arg(resourceString())); + if (getCurrentPack()) { + 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())); } else { - m_ui->resourceSelectionButton->setText(tr("Deselect %1 for download").arg(resourceString())); + qWarning() << "Try to update selection but there is not a pack selected"; } } From b3d743635c86aac583fb802c1e3c7aa25e12dc88 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Fri, 9 Jun 2023 21:29:12 +0300 Subject: [PATCH 117/122] Updated the messages Signed-off-by: Trial97 --- launcher/net/ByteArraySink.h | 6 +++--- launcher/ui/pages/modplatform/ResourcePage.cpp | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/launcher/net/ByteArraySink.h b/launcher/net/ByteArraySink.h index 0d77727ee..728193b30 100644 --- a/launcher/net/ByteArraySink.h +++ b/launcher/net/ByteArraySink.h @@ -56,7 +56,7 @@ class ByteArraySink : public Sink { if (m_output) m_output->clear(); else - qWarning() << "ByteArraySink was not cleared because it's not adresable"; + qWarning() << "ByteArraySink did not initialize the buffer because it's not addressable"; if (initAllValidators(request)) return Task::State::Running; return Task::State::Failed; @@ -67,7 +67,7 @@ class ByteArraySink : public Sink { if (m_output) m_output->append(data); else - qWarning() << "ByteArraySink no write because it's not adresable"; + qWarning() << "ByteArraySink did not write the buffer because it's not addressable"; if (writeAllValidators(data)) return Task::State::Running; return Task::State::Failed; @@ -78,7 +78,7 @@ class ByteArraySink : public Sink { if (m_output) m_output->clear(); else - qWarning() << "ByteArraySink no clear because it's not adresable"; + qWarning() << "ByteArraySink did not clear the buffer because it's not addressable"; failAllValidators(); return Task::State::Failed; } diff --git a/launcher/ui/pages/modplatform/ResourcePage.cpp b/launcher/ui/pages/modplatform/ResourcePage.cpp index 2bb867775..1d2509d80 100644 --- a/launcher/ui/pages/modplatform/ResourcePage.cpp +++ b/launcher/ui/pages/modplatform/ResourcePage.cpp @@ -246,7 +246,7 @@ void ResourcePage::updateSelectionButton() else m_ui->resourceSelectionButton->setText(tr("Deselect %1 for download").arg(resourceString())); } else { - qWarning() << "Try to update selection but there is not a pack selected"; + qWarning() << "Tried to update the selected button but there is not a pack selected"; } } From 5aa1c340dc6c353e34d0fbcac84fb89798a06481 Mon Sep 17 00:00:00 2001 From: Tayou Date: Sun, 11 Jun 2023 01:58:37 +0200 Subject: [PATCH 118/122] 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 119/122] 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) From 94ddc8bbf7a65f08079c4cf42deb4eea86b1e53b Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Mon, 12 Jun 2023 14:14:50 +0100 Subject: [PATCH 120/122] Could this work? Signed-off-by: TheKodeToad --- launcher/modplatform/modrinth/ModrinthPackExportTask.cpp | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp b/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp index 29df90ddf..eff47b37c 100644 --- a/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp +++ b/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp @@ -27,7 +27,7 @@ #include "minecraft/PackProfile.h" #include "minecraft/mod/ModFolderModel.h" -const QStringList ModrinthPackExportTask::PREFIXES({ "mods", "coremods", "resourcepacks", "texturepacks", "shaderpacks" }); +const QStringList ModrinthPackExportTask::PREFIXES({ "mods/", "coremods/", "resourcepacks/", "texturepacks/", "shaderpacks/" }); const QStringList ModrinthPackExportTask::FILE_EXTENSIONS({ "jar", "litemod", "zip" }); ModrinthPackExportTask::ModrinthPackExportTask(const QString& name, @@ -99,14 +99,12 @@ void ModrinthPackExportTask::collectHashes() const QString relative = gameRoot.relativeFilePath(file.absoluteFilePath()); // require sensible file types - if (!std::any_of(PREFIXES.begin(), PREFIXES.end(), - [&relative](const QString& prefix) { return relative.startsWith(prefix + QDir::separator()); })) + if (!std::any_of(PREFIXES.begin(), PREFIXES.end(), [&relative](const QString& prefix) { return relative.startsWith(prefix); })) 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 sha512(QCryptographicHash::Algorithm::Sha512); From f4a814b5e639641bad40bcea6abaf34f9c253046 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Mon, 12 Jun 2023 15:46:15 +0100 Subject: [PATCH 121/122] Remove unnecessary code Signed-off-by: TheKodeToad --- launcher/modplatform/modrinth/ModrinthPackExportTask.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp b/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp index eff47b37c..bff9bf42e 100644 --- a/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp +++ b/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp @@ -301,9 +301,7 @@ QByteArray ModrinthPackExportTask::generateIndex() const ResolvedFile& value = iterator.value(); QJsonObject file; - QString path = iterator.key(); - path.replace(QDir::separator(), "/"); - file["path"] = path; + file["path"] = iterator.key(); file["downloads"] = QJsonArray({ iterator.value().url }); QJsonObject hashes; From b77fb059083ba04a3ccbc1b6e9ce4f5353b200ad Mon Sep 17 00:00:00 2001 From: Trial97 Date: Tue, 13 Jun 2023 21:07:05 +0300 Subject: [PATCH 122/122] Added back the INIFile read function Signed-off-by: Trial97 --- launcher/settings/INIFile.cpp | 85 +++++++++++++++++++++++++++++++---- tests/INIFile_test.cpp | 56 +++++++++++++++-------- 2 files changed, 115 insertions(+), 26 deletions(-) diff --git a/launcher/settings/INIFile.cpp b/launcher/settings/INIFile.cpp index f0347cabf..cb909ae75 100644 --- a/launcher/settings/INIFile.cpp +++ b/launcher/settings/INIFile.cpp @@ -45,12 +45,12 @@ #include -INIFile::INIFile() -{ -} +INIFile::INIFile() {} bool INIFile::saveFile(QString fileName) { + if (!contains("ConfigVersion")) + insert("ConfigVersion", "1.1"); QSettings _settings_obj{ fileName, QSettings::Format::IniFormat }; _settings_obj.setFallbacksEnabled(false); @@ -71,6 +71,67 @@ bool INIFile::saveFile(QString fileName) return true; } +QString 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; +} +bool parseOldFileFormat(QIODevice& device, QSettings::SettingsMap& map) +{ + QTextStream in(device.readAll()); +#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); + map.insert(key, value); + } + + return true; +} bool INIFile::loadFile(QString fileName) { @@ -84,10 +145,19 @@ bool INIFile::loadFile(QString fileName) qCritical() << "A format error occurred (e.g. loading a malformed INI file)."; return false; } - - for (auto&& key : _settings_obj.allKeys()) - insert(key, _settings_obj.value(key)); - + if (!_settings_obj.value("ConfigVersion").isValid()) { + QFile file(fileName); + if (!file.open(QIODevice::ReadOnly)) + return false; + QSettings::SettingsMap map; + parseOldFileFormat(file, map); + file.close(); + for (auto&& key : map.keys()) + insert(key, map.value(key)); + insert("ConfigVersion", "1.1"); + } else + for (auto&& key : _settings_obj.allKeys()) + insert(key, _settings_obj.value(key)); return true; } @@ -103,4 +173,3 @@ void INIFile::set(QString key, QVariant val) { this->operator[](key) = val; } - diff --git a/tests/INIFile_test.cpp b/tests/INIFile_test.cpp index 4be8133c0..2f49e573d 100644 --- a/tests/INIFile_test.cpp +++ b/tests/INIFile_test.cpp @@ -1,24 +1,16 @@ #include +#include #include #include -#include #include -class IniFileTest : public QObject -{ +class IniFileTest : public QObject { Q_OBJECT -private -slots: - void initTestCase() - { - - } - void cleanupTestCase() - { - - } + private slots: + void initTestCase() {} + void cleanupTestCase() {} void test_Escape_data() { @@ -47,17 +39,17 @@ slots: // load INIFile f2; f2.loadFile(filename); - QCOMPARE(f2.get("a","NOT SET").toString(), a); - QCOMPARE(f2.get("b","NOT SET").toString(), b); + 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"}; + QStringList list_strings = { "a", "b", "c" }; QString slist_numbers = "(1,2,3,10)"; - QList list_numbers = {1, 2, 3, 10}; + QList list_numbers = { 1, 2, 3, 10 }; QString filename = "test_SaveLoadLists.ini"; @@ -72,13 +64,41 @@ slots: QStringList out_list_strings = f2.get("list_strings", QStringList()).toStringList(); qDebug() << "OutStringList" << out_list_strings; - + QList out_list_numbers = QVariantUtils::toList(f2.get("list_numbers", QVariantUtils::fromList(QList()))); qDebug() << "OutNumbersList" << out_list_numbers; QCOMPARE(out_list_strings, list_strings); QCOMPARE(out_list_numbers, list_numbers); } + + void test_SaveAleardyExistingFile() + { + QString fileName = "test_SaveAleardyExistingFile.ini"; + QString fileContent = R"(InstanceType=OneSix +iconKey=vanillia_icon +name=Minecraft Vanillia +OverrideCommands=true +PreLaunchCommand="$INST_JAVA" -jar packwiz-installer-bootstrap.jar link +)"; + QFile file(fileName); + + if (file.open(QFile::WriteOnly | QFile::Text)) { + QTextStream stream(&file); + stream << fileContent; + file.close(); + } + + // load + INIFile f1; + f1.loadFile(fileName); + QCOMPARE(f1.get("PreLaunchCommand", "NOT SET").toString(), "\"$INST_JAVA\" -jar packwiz-installer-bootstrap.jar link"); + f1.saveFile(fileName); + INIFile f2; + f2.loadFile(fileName); + QCOMPARE(f2.get("PreLaunchCommand", "NOT SET").toString(), "\"$INST_JAVA\" -jar packwiz-installer-bootstrap.jar link"); + QCOMPARE(f2.get("ConfigVersion", "NOT SET").toString(), "1.1"); + } }; QTEST_GUILESS_MAIN(IniFileTest)