From a80b820e9491624429c75a049140ebe738b6d604 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Wed, 1 Mar 2023 19:38:27 +0000 Subject: UI for mrpack export (broken) Signed-off-by: TheKodeToad --- launcher/ui/dialogs/ExportMrPackDialog.cpp | 31 ++++++++++++ launcher/ui/dialogs/ExportMrPackDialog.h | 38 +++++++++++++++ launcher/ui/dialogs/ExportMrPackDialog.ui | 77 ++++++++++++++++++++++++++++++ 3 files changed, 146 insertions(+) create mode 100644 launcher/ui/dialogs/ExportMrPackDialog.cpp create mode 100644 launcher/ui/dialogs/ExportMrPackDialog.h create mode 100644 launcher/ui/dialogs/ExportMrPackDialog.ui (limited to 'launcher/ui/dialogs') diff --git a/launcher/ui/dialogs/ExportMrPackDialog.cpp b/launcher/ui/dialogs/ExportMrPackDialog.cpp new file mode 100644 index 00000000..eb53a61f --- /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 00000000..78322a8f --- /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 00000000..2b553987 --- /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 + + + + + + + + -- cgit From 0fadb5a2be5f8136a4a5f52f7e5602698b53f09e Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Wed, 1 Mar 2023 21:11:04 +0000 Subject: 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(-) (limited to 'launcher/ui/dialogs') diff --git a/launcher/ui/dialogs/ExportMrPackDialog.cpp b/launcher/ui/dialogs/ExportMrPackDialog.cpp index eb53a61f..735a6245 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 78322a8f..31ab86ff 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 2b553987..37c87158 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 + + + + -- cgit From 46cc325f7cc9810abe6e152484a51fed92b1bb52 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Thu, 2 Mar 2023 11:25:36 +0000 Subject: 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 (limited to 'launcher/ui/dialogs') diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index 0b27ff85..2664ba66 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 00000000..bd0a82a4 --- /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 00000000..aec42b41 --- /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 f13e36e8..2c706b8a 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 dea02d1b..b1b8f911 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 735a6245..1a839061 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 31ab86ff..454c25eb 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 37c87158..8e6d61ff 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 + + -- cgit From a5dd6b6cd7050b4861a50a417d939c6a49d5cc33 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Thu, 2 Mar 2023 16:40:49 +0000 Subject: Export without file urls Signed-off-by: TheKodeToad --- launcher/ui/dialogs/ExportInstanceDialog.cpp | 2 +- launcher/ui/dialogs/ExportMrPackDialog.cpp | 93 +++++++++++++++++++++++++--- launcher/ui/dialogs/ExportMrPackDialog.h | 3 + 3 files changed, 90 insertions(+), 8 deletions(-) (limited to 'launcher/ui/dialogs') diff --git a/launcher/ui/dialogs/ExportInstanceDialog.cpp b/launcher/ui/dialogs/ExportInstanceDialog.cpp index 2c706b8a..f310a689 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 1a839061..9015407d 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); } +ExportMrPackDialog::~ExportMrPackDialog() +{ + delete ui; +} + void ExportMrPackDialog::done(int result) { - if (result != Accepted) { - QDialog::done(result); - return; - } + if (result == Accepted) + runExport(); + QDialog::done(result); } -ExportMrPackDialog::~ExportMrPackDialog() +void ExportMrPackDialog::runExport() { - delete ui; + 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 454c25eb..0cf4eb7f 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(); }; -- cgit From 9ec32b2561eac4ae50db88467d9afae57dd78614 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Thu, 2 Mar 2023 17:36:28 +0000 Subject: Fix QuaZipFile usage Signed-off-by: TheKodeToad --- launcher/ui/dialogs/ExportMrPackDialog.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) (limited to 'launcher/ui/dialogs') diff --git a/launcher/ui/dialogs/ExportMrPackDialog.cpp b/launcher/ui/dialogs/ExportMrPackDialog.cpp index 9015407d..96c7a201 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()); -- cgit From 88ef02474f9c696318eb1c9e0213a7900f60f67e Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Thu, 2 Mar 2023 17:36:39 +0000 Subject: Minify index JSON Signed-off-by: TheKodeToad --- launcher/ui/dialogs/ExportMrPackDialog.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'launcher/ui/dialogs') diff --git a/launcher/ui/dialogs/ExportMrPackDialog.cpp b/launcher/ui/dialogs/ExportMrPackDialog.cpp index 96c7a201..14518783 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 -- cgit From 6505b0c065b32e13f392061ccb184d288329a058 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Thu, 2 Mar 2023 19:48:41 +0000 Subject: Move logic to task Signed-off-by: TheKodeToad --- launcher/CMakeLists.txt | 2 + .../modrinth/ModrinthPackExportTask.cpp | 110 +++++++++++++++++++++ .../modplatform/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 (limited to 'launcher/ui/dialogs') diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index 2664ba66..f3af2ebf 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 00000000..029b47a5 --- /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 00000000..c38be204 --- /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 14518783..4c2e5593 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; - } - - 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; - } + ModrinthPackExportTask task(ui->name->text(), ui->version->text(), ui->summary->text(), instance, output, + [this](const QString& path) { return proxy->blockedPaths().covers(path); }); - 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 0cf4eb7f..89263fc6 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(); }; -- cgit From adcdf28d64abbe16304c2d377488af1898f9b2af Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Fri, 3 Mar 2023 11:14:57 +0000 Subject: Move task to another thread I don't know whether this is the prefered method. Signed-off-by: TheKodeToad --- .../modrinth/ModrinthPackExportTask.cpp | 79 +++++++++++----------- launcher/ui/dialogs/ExportMrPackDialog.cpp | 32 ++++----- launcher/ui/dialogs/ExportMrPackDialog.h | 2 - 3 files changed, 55 insertions(+), 58 deletions(-) (limited to 'launcher/ui/dialogs') diff --git a/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp b/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp index 029b47a5..331fbf94 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..."); + QtConcurrent::run(QThreadPool::globalInstance(), [this] { + QFileInfoList files; + if (!MMCZip::collectFileListRecursively(instance->gameRoot(), nullptr, &files, filter)) { + emitFailed(tr("Could not collect list of files")); + return; + } - QuaZip zip(output); - if (!zip.open(QuaZip::mdCreate)) { - QFile::remove(output); - emitFailed(tr("Could not create file")); - return; - } + setStatus("Adding files..."); - { - QuaZipFile indexFile(&zip); - if (!indexFile.open(QIODevice::WriteOnly, QuaZipNewInfo("modrinth.index.json"))) { + QuaZip zip(output); + if (!zip.open(QuaZip::mdCreate)) { QFile::remove(output); - - emitFailed(tr("Could not create index")); + emitFailed(tr("Could not create file")); return; } - indexFile.write(generateIndex()); - } - // should exist - QDir dotMinecraft(instance->gameRoot()); + { + QuaZipFile indexFile(&zip); + if (!indexFile.open(QIODevice::WriteOnly, QuaZipNewInfo("modrinth.index.json"))) { + QFile::remove(output); - { - 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())); + emitFailed(tr("Could not create index")); return; } - i++; + indexFile.write(generateIndex()); } - } - zip.close(); + // 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++; + } + } - if (zip.getZipError() != 0) { - QFile::remove(output); - emitFailed(tr("A zip error occured")); - return; - } + zip.close(); + + if (zip.getZipError() != 0) { + QFile::remove(output); + emitFailed(tr("A zip error occured")); + return; + } - emitSucceeded(); + emitSucceeded(); + }); } QByteArray ModrinthPackExportTask::generateIndex() diff --git a/launcher/ui/dialogs/ExportMrPackDialog.cpp b/launcher/ui/dialogs/ExportMrPackDialog.cpp index 4c2e5593..81663c9a 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); - QDialog::done(result); -} + if (output.isEmpty()) + return; -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); + ModrinthPackExportTask task(ui->name->text(), ui->version->text(), ui->summary->text(), instance, output, + [this](const QString& path) { return proxy->blockedPaths().covers(path); }); - if (output.isEmpty()) - return; + ProgressDialog progress(this); + progress.setSkipButton(true, tr("Abort")); + progress.execWithTask(&task); + } - 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); } \ No newline at end of file diff --git a/launcher/ui/dialogs/ExportMrPackDialog.h b/launcher/ui/dialogs/ExportMrPackDialog.h index 89263fc6..3ded4887 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(); }; -- cgit From 55f928f845b56d09a124f1ba85e196c2b91d856d Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Fri, 3 Mar 2023 15:06:29 +0000 Subject: More consistent naming Signed-off-by: TheKodeToad --- launcher/ui/dialogs/ExportMrPackDialog.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'launcher/ui/dialogs') diff --git a/launcher/ui/dialogs/ExportMrPackDialog.cpp b/launcher/ui/dialogs/ExportMrPackDialog.cpp index 81663c9a..266479b3 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; -- cgit From 87384182a19ea852522af1b0d69420a510c0a94b Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Sat, 4 Mar 2023 11:07:07 +0000 Subject: 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(-) (limited to 'launcher/ui/dialogs') diff --git a/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp b/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp index c151edf5..5ddd3408 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 266479b3..13262a7e 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); -- cgit From 970ec8187c2a6b45b9b1031260c07f4e26fe8827 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Sat, 4 Mar 2023 19:55:38 +0000 Subject: More refactoring Signed-off-by: TheKodeToad --- launcher/CMakeLists.txt | 4 +- launcher/FileIgnoreProxy.cpp | 266 +++++++++++++++++++++ launcher/FileIgnoreProxy.h | 71 ++++++ launcher/PackIgnoreProxy.cpp | 266 --------------------- launcher/PackIgnoreProxy.h | 71 ------ .../modrinth/ModrinthPackExportTask.cpp | 29 ++- .../modplatform/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 +- 11 files changed, 373 insertions(+), 370 deletions(-) create mode 100644 launcher/FileIgnoreProxy.cpp create mode 100644 launcher/FileIgnoreProxy.h delete mode 100644 launcher/PackIgnoreProxy.cpp delete mode 100644 launcher/PackIgnoreProxy.h (limited to 'launcher/ui/dialogs') diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index 380e8336..66099c4e 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/FileIgnoreProxy.cpp b/launcher/FileIgnoreProxy.cpp new file mode 100644 index 00000000..7dda0290 --- /dev/null +++ b/launcher/FileIgnoreProxy.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 "FileIgnoreProxy.h" + +#include +#include +#include +#include +#include "FileSystem.h" +#include "SeparatorPrefixTree.h" +#include "StringUtils.h" + +FileIgnoreProxy::FileIgnoreProxy(QString root, QObject* parent) : QSortFilterProxyModel(parent), root(root) {} +// NOTE: Sadly, we have to do sorting ourselves. +bool FileIgnoreProxy::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 FileIgnoreProxy::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 FileIgnoreProxy::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 FileIgnoreProxy::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 FileIgnoreProxy::relPath(const QString& path) const +{ + QString prefix = QDir().absoluteFilePath(root); + prefix += '/'; + if (!path.startsWith(prefix)) { + return QString(); + } + return path.mid(prefix.size()); +} + +bool FileIgnoreProxy::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 FileIgnoreProxy::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 FileIgnoreProxy::setBlockedPaths(QStringList paths) +{ + beginResetModel(); + blocked.clear(); + blocked.insert(paths); + endResetModel(); +} + +const SeparatorPrefixTree<'/'>& FileIgnoreProxy::blockedPaths() const +{ + return blocked; +} + +bool FileIgnoreProxy::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/FileIgnoreProxy.h b/launcher/FileIgnoreProxy.h new file mode 100644 index 00000000..a0f6c51a --- /dev/null +++ b/launcher/FileIgnoreProxy.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 FileIgnoreProxy : public QSortFilterProxyModel { + Q_OBJECT + + public: + FileIgnoreProxy(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/PackIgnoreProxy.cpp b/launcher/PackIgnoreProxy.cpp deleted file mode 100644 index bd0a82a4..00000000 --- a/launcher/PackIgnoreProxy.cpp +++ /dev/null @@ -1,266 +0,0 @@ -// 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 deleted file mode 100644 index aec42b41..00000000 --- a/launcher/PackIgnoreProxy.h +++ /dev/null @@ -1,71 +0,0 @@ -// 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/modplatform/modrinth/ModrinthPackExportTask.cpp b/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp index 5ddd3408..3c69413d 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 ec87c1cd..d7a42e7b 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 f310a689..ea01c5e2 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 b1b8f911..d96f4537 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 13262a7e..1a69cc53 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 3ded4887..63e3f016 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; }; -- cgit From 2cc9b0df068ace61c60217973d4d66412cb53968 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Wed, 8 Mar 2023 18:10:52 +0000 Subject: 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(-) (limited to 'launcher/ui/dialogs') diff --git a/launcher/FileIgnoreProxy.cpp b/launcher/FileIgnoreProxy.cpp index 7dda0290..fd05624a 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 a0f6c51a..baf05c7a 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 1a69cc53..03238ec4 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); -- cgit From ddca838e46d2d147cbc5965be31895dd73676c79 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Sat, 18 Mar 2023 12:21:13 +0000 Subject: Info and error dialogs TODO: is there a better approach? Signed-off-by: TheKodeToad --- .../modplatform/modrinth/ModrinthPackExportTask.cpp | 21 ++++++++++++--------- launcher/ui/dialogs/ExportMrPackDialog.cpp | 16 ++++++++++++++++ 2 files changed, 28 insertions(+), 9 deletions(-) (limited to 'launcher/ui/dialogs') diff --git a/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp b/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp index e12ee923..e8bc3673 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 03238ec4..8a49f314 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) -- cgit From 5346dfc782d1736811b60b8548ce4c4335caabff Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Sat, 18 Mar 2023 12:57:44 +0000 Subject: Use first line of notes for summary Signed-off-by: TheKodeToad --- launcher/ui/dialogs/ExportMrPackDialog.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'launcher/ui/dialogs') diff --git a/launcher/ui/dialogs/ExportMrPackDialog.cpp b/launcher/ui/dialogs/ExportMrPackDialog.cpp index 8a49f314..28c719c7 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 -- cgit From ec8cb056bf717edb97bf3e65fbad0ffc1b78ec34 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Sun, 19 Mar 2023 11:49:09 +0000 Subject: QRegExp -> QRegularExpression :P Signed-off-by: TheKodeToad --- launcher/ui/dialogs/ExportMrPackDialog.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'launcher/ui/dialogs') diff --git a/launcher/ui/dialogs/ExportMrPackDialog.cpp b/launcher/ui/dialogs/ExportMrPackDialog.cpp index 28c719c7..02b6721d 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 -- cgit From e42050cc8a2f74178d6cd0afe8c92ae9b802cf73 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Tue, 28 Mar 2023 14:22:28 +0100 Subject: 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(-) (limited to 'launcher/ui/dialogs') diff --git a/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp b/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp index 46bfab6d..d630f5d0 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 02b6721d..a622eb30 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(); -- cgit From b8e0c8ebc62ce22d5dcaf4295d80c6070eb45f49 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Mon, 17 Apr 2023 10:16:03 +0100 Subject: 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(-) (limited to 'launcher/ui/dialogs') diff --git a/buildconfig/BuildConfig.h b/buildconfig/BuildConfig.h index 38fa3a65..8543d724 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 fd05624a..4fd1ef91 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 baf05c7a..a5a1153d 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 021d8a56..8b17f644 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 35b4792d..a0f912df 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 a622eb30..bc983efe 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 +} + -- cgit From f997529cd4fb077b06d05da9c6ff0c23b85b4ebb Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Thu, 30 Mar 2023 11:22:55 -0700 Subject: feat: better task tracking Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- launcher/CMakeLists.txt | 3 + launcher/net/Download.cpp | 58 +++++++++------ launcher/net/Download.h | 10 +++ launcher/net/NetAction.h | 33 +++++++-- launcher/tasks/ConcurrentTask.cpp | 99 +++++++++++++++++++------- launcher/tasks/ConcurrentTask.h | 20 +++--- launcher/tasks/Task.cpp | 26 ++++--- launcher/tasks/Task.h | 35 +++++++-- launcher/ui/dialogs/ProgressDialog.cpp | 109 ++++++++++++++++++++++++----- launcher/ui/dialogs/ProgressDialog.h | 54 +++++++++++--- launcher/ui/dialogs/ProgressDialog.ui | 61 ++++++++-------- launcher/ui/widgets/SubTaskProgressBar.cpp | 58 +++++++++++++++ launcher/ui/widgets/SubTaskProgressBar.h | 50 +++++++++++++ launcher/ui/widgets/SubTaskProgressBar.ui | 70 ++++++++++++++++++ tests/Task_test.cpp | 4 +- 15 files changed, 552 insertions(+), 138 deletions(-) create mode 100644 launcher/ui/widgets/SubTaskProgressBar.cpp create mode 100644 launcher/ui/widgets/SubTaskProgressBar.h create mode 100644 launcher/ui/widgets/SubTaskProgressBar.ui (limited to 'launcher/ui/dialogs') diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index ee36175f..24330adf 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -909,6 +909,8 @@ SET(LAUNCHER_SOURCES ui/widgets/VariableSizedImageObject.cpp ui/widgets/ProjectItem.h ui/widgets/ProjectItem.cpp + ui/widgets/SubTaskProgressBar.h + ui/widgets/SubTaskProgressBar.cpp ui/widgets/VersionListView.cpp ui/widgets/VersionListView.h ui/widgets/VersionSelectWidget.cpp @@ -969,6 +971,7 @@ qt_wrap_ui(LAUNCHER_UI ui/widgets/CustomCommands.ui ui/widgets/InfoFrame.ui ui/widgets/ModFilterWidget.ui + ui/widgets/SubTaskProgressBar.ui ui/widgets/ThemeCustomizationWidget.ui ui/dialogs/CopyInstanceDialog.ui ui/dialogs/ProfileSetupDialog.ui diff --git a/launcher/net/Download.cpp b/launcher/net/Download.cpp index e8a1d0b0..26488a43 100644 --- a/launcher/net/Download.cpp +++ b/launcher/net/Download.cpp @@ -48,12 +48,15 @@ #include "BuildConfig.h" #include "Application.h" +Q_LOGGING_CATEGORY(DownloadLogC, "Task.Net.Download") + namespace Net { auto Download::makeCached(QUrl url, MetaEntryPtr entry, Options options) -> Download::Ptr { auto dl = makeShared(); dl->m_url = url; + dl->setObjectName(QString("CACHE:") + url.toString()); dl->m_options = options; auto md5Node = new ChecksumValidator(QCryptographicHash::Md5); auto cachedNode = new MetaCacheSink(entry, md5Node, options.testFlag(Option::MakeEternal)); @@ -65,6 +68,7 @@ auto Download::makeByteArray(QUrl url, QByteArray* output, Options options) -> D { auto dl = makeShared(); dl->m_url = url; + dl->setObjectName(QString("BYTES:") + url.toString()); dl->m_options = options; dl->m_sink.reset(new ByteArraySink(output)); return dl; @@ -74,6 +78,7 @@ auto Download::makeFile(QUrl url, QString path, Options options) -> Download::Pt { auto dl = makeShared(); dl->m_url = url; + dl->setObjectName(QString("FILE:") + url.toString()); dl->m_options = options; dl->m_sink.reset(new FileSink(path)); return dl; @@ -89,7 +94,7 @@ void Download::executeTask() setStatus(tr("Downloading %1").arg(m_url.toString())); if (getState() == Task::State::AbortedByUser) { - qWarning() << "Attempt to start an aborted Download:" << m_url.toString(); + qCWarning(DownloadLogC) << getUid().toString() << "Attempt to start an aborted Download:" << m_url.toString(); emitAborted(); return; } @@ -99,10 +104,10 @@ void Download::executeTask() switch (m_state) { case State::Succeeded: emit succeeded(); - qDebug() << "Download cache hit " << m_url.toString(); + qCDebug(DownloadLogC) << getUid().toString() << "Download cache hit " << m_url.toString(); return; case State::Running: - qDebug() << "Downloading " << m_url.toString(); + qCDebug(DownloadLogC) << getUid().toString() << "Downloading " << m_url.toString(); break; case State::Inactive: case State::Failed: @@ -123,9 +128,12 @@ void Download::executeTask() if (!token.isNull()) request.setRawHeader("Authorization", token.toUtf8()); } + + m_last_progress_time = m_clock.now(); + m_last_progress_bytes = 0; QNetworkReply* rep = m_network->get(request); - + m_reply.reset(rep); connect(rep, &QNetworkReply::downloadProgress, this, &Download::downloadProgress); connect(rep, &QNetworkReply::finished, this, &Download::downloadFinished); @@ -140,13 +148,21 @@ void Download::executeTask() void Download::downloadProgress(qint64 bytesReceived, qint64 bytesTotal) { + auto now = m_clock.now(); + auto elapsed = now - m_last_progress_time; + auto elapsed_ms = std::chrono::duration_cast(elapsed).count(); + auto bytes_recived_since = bytesReceived - m_last_progress_bytes; + + auto speed = humanReadableFileSize(bytes_recived_since / elapsed_ms * 1000) + "/s"; + m_details = speed; + setProgress(bytesReceived, bytesTotal); } void Download::downloadError(QNetworkReply::NetworkError error) { if (error == QNetworkReply::OperationCanceledError) { - qCritical() << "Aborted " << m_url.toString(); + qCCritical(DownloadLogC) << getUid().toString() << "Aborted " << m_url.toString(); m_state = State::AbortedByUser; } else { if (m_options & Option::AcceptLocalFiles) { @@ -156,7 +172,7 @@ void Download::downloadError(QNetworkReply::NetworkError error) } } // error happened during download. - qCritical() << "Failed " << m_url.toString() << " with reason " << error; + qCCritical(DownloadLogC) << getUid().toString() << "Failed " << m_url.toString() << " with reason " << error; m_state = State::Failed; } } @@ -165,9 +181,9 @@ void Download::sslErrors(const QList& errors) { int i = 1; for (auto error : errors) { - qCritical() << "Download" << m_url.toString() << "SSL Error #" << i << " : " << error.errorString(); + qCCritical(DownloadLogC) << getUid().toString() << "Download" << m_url.toString() << "SSL Error #" << i << " : " << error.errorString(); auto cert = error.certificate(); - qCritical() << "Certificate in question:\n" << cert.toText(); + qCCritical(DownloadLogC) << getUid().toString() << "Certificate in question:\n" << cert.toText(); i++; } } @@ -210,17 +226,17 @@ auto Download::handleRedirect() -> bool */ redirect = QUrl(redirectStr, QUrl::TolerantMode); if (!redirect.isValid()) { - qWarning() << "Failed to parse redirect URL:" << redirectStr; + qCWarning(DownloadLogC) << getUid().toString() << "Failed to parse redirect URL:" << redirectStr; downloadError(QNetworkReply::ProtocolFailure); return false; } - qDebug() << "Fixed location header:" << redirect; + qCDebug(DownloadLogC) << getUid().toString() << "Fixed location header:" << redirect; } else { - qDebug() << "Location header:" << redirect; + qCDebug(DownloadLogC) << getUid().toString() << "Location header:" << redirect; } m_url = QUrl(redirect.toString()); - qDebug() << "Following redirect to " << m_url.toString(); + qCDebug(DownloadLogC) << getUid().toString() << "Following redirect to " << m_url.toString(); startAction(m_network); return true; @@ -230,26 +246,26 @@ void Download::downloadFinished() { // handle HTTP redirection first if (handleRedirect()) { - qDebug() << "Download redirected:" << m_url.toString(); + qCDebug(DownloadLogC) << getUid().toString() << "Download redirected:" << m_url.toString(); return; } // if the download failed before this point ... if (m_state == State::Succeeded) // pretend to succeed so we continue processing :) { - qDebug() << "Download failed but we are allowed to proceed:" << m_url.toString(); + qCDebug(DownloadLogC) << getUid().toString() << "Download failed but we are allowed to proceed:" << m_url.toString(); m_sink->abort(); m_reply.reset(); emit succeeded(); return; } else if (m_state == State::Failed) { - qDebug() << "Download failed in previous step:" << m_url.toString(); + qCDebug(DownloadLogC) << getUid().toString() << "Download failed in previous step:" << m_url.toString(); m_sink->abort(); m_reply.reset(); emit failed(""); return; } else if (m_state == State::AbortedByUser) { - qDebug() << "Download aborted in previous step:" << m_url.toString(); + qCDebug(DownloadLogC) << getUid().toString() << "Download aborted in previous step:" << m_url.toString(); m_sink->abort(); m_reply.reset(); emit aborted(); @@ -259,14 +275,14 @@ void Download::downloadFinished() // make sure we got all the remaining data, if any auto data = m_reply->readAll(); if (data.size()) { - qDebug() << "Writing extra" << data.size() << "bytes"; + qCDebug(DownloadLogC) << getUid().toString() << "Writing extra" << data.size() << "bytes"; m_state = m_sink->write(data); } // otherwise, finalize the whole graph m_state = m_sink->finalize(*m_reply.get()); if (m_state != State::Succeeded) { - qDebug() << "Download failed to finalize:" << m_url.toString(); + qCDebug(DownloadLogC) << getUid().toString() << "Download failed to finalize:" << m_url.toString(); m_sink->abort(); m_reply.reset(); emit failed(""); @@ -274,7 +290,7 @@ void Download::downloadFinished() } m_reply.reset(); - qDebug() << "Download succeeded:" << m_url.toString(); + qCDebug(DownloadLogC) << getUid().toString() << "Download succeeded:" << m_url.toString(); emit succeeded(); } @@ -284,11 +300,11 @@ void Download::downloadReadyRead() auto data = m_reply->readAll(); m_state = m_sink->write(data); if (m_state == State::Failed) { - qCritical() << "Failed to process response chunk"; + qCCritical(DownloadLogC) << getUid().toString() << "Failed to process response chunk"; } // qDebug() << "Download" << m_url.toString() << "gained" << data.size() << "bytes"; } else { - qCritical() << "Cannot write download data! illegal status " << m_status; + qCCritical(DownloadLogC) << getUid().toString() << "Cannot write download data! illegal status " << m_status; } } diff --git a/launcher/net/Download.h b/launcher/net/Download.h index 7e1df322..cbee0d03 100644 --- a/launcher/net/Download.h +++ b/launcher/net/Download.h @@ -22,6 +22,7 @@ * Copyright 2013-2021 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * @@ -36,6 +37,8 @@ #pragma once +#include + #include "HttpMetaCache.h" #include "NetAction.h" #include "Sink.h" @@ -63,6 +66,7 @@ class Download : public NetAction { void addValidator(Validator* v); auto abort() -> bool override; auto canAbort() const -> bool override { return true; }; + auto getDetails() const -> QString override {return m_details; }; private: auto handleRedirect() -> bool; @@ -80,6 +84,12 @@ class Download : public NetAction { private: std::unique_ptr m_sink; Options m_options; + + std::chrono::steady_clock m_clock; + std::chrono::time_point m_last_progress_time; + qint64 m_last_progress_bytes; + + QString m_details; }; } // namespace Net diff --git a/launcher/net/NetAction.h b/launcher/net/NetAction.h index 38fe058b..f9456bd6 100644 --- a/launcher/net/NetAction.h +++ b/launcher/net/NetAction.h @@ -35,18 +35,39 @@ #pragma once +#include + #include #include #include "QObjectPtr.h" #include "tasks/Task.h" +static const QStringList s_units_si {"kb", "MB", "GB", "TB"}; +static const QStringList s_units_kibi {"kiB", "MiB", "Gib", "TiB"}; + +inline QString humanReadableFileSize(qint64 bytes, bool use_si = false, int decimal_points = 1) { + const QStringList units = use_si ? s_units_si : s_units_kibi; + const int scale = use_si ? 1000 : 1024; + double size = bytes; + + int u = -1; + double r = pow(10, decimal_points); + + do { + size /= scale; + u++; + } while (round(abs(size) * r) / r >= scale && u < units.length() - 1); + + return QString::number(size, 'f', 2) + " " + units[u]; +} + class NetAction : public Task { Q_OBJECT - protected: +protected: explicit NetAction() : Task() {}; - public: +public: using Ptr = shared_qobject_ptr; virtual ~NetAction() = default; @@ -55,23 +76,23 @@ class NetAction : public Task { void setNetwork(shared_qobject_ptr network) { m_network = network; } - protected slots: +protected slots: virtual void downloadProgress(qint64 bytesReceived, qint64 bytesTotal) = 0; virtual void downloadError(QNetworkReply::NetworkError error) = 0; virtual void downloadFinished() = 0; virtual void downloadReadyRead() = 0; - public slots: +public slots: void startAction(shared_qobject_ptr network) { m_network = network; executeTask(); } - protected: +protected: void executeTask() override {}; - public: +public: shared_qobject_ptr m_network; /// the network reply diff --git a/launcher/tasks/ConcurrentTask.cpp b/launcher/tasks/ConcurrentTask.cpp index 3cc37b2a..48e1bc18 100644 --- a/launcher/tasks/ConcurrentTask.cpp +++ b/launcher/tasks/ConcurrentTask.cpp @@ -15,14 +15,13 @@ ConcurrentTask::~ConcurrentTask() } } -auto ConcurrentTask::getStepProgress() const -> qint64 +auto ConcurrentTask::getStepProgress() const -> QList { - return m_stepProgress; -} - -auto ConcurrentTask::getStepTotalProgress() const -> qint64 -{ - return m_stepTotalProgress; + QList task_progress; + for (auto progress : task_progress) { + task_progress.append(task_progress); + } + return task_progress; } void ConcurrentTask::addTask(Task::Ptr task) @@ -33,10 +32,13 @@ void ConcurrentTask::addTask(Task::Ptr task) void ConcurrentTask::executeTask() { // Start the least amount of tasks needed, but at least one - int num_starts = qMax(1, qMin(m_total_max_size, m_queue.size())); - for (int i = 0; i < num_starts; i++) { - QMetaObject::invokeMethod(this, &ConcurrentTask::startNext, Qt::QueuedConnection); - } + // int num_starts = qMax(1, qMin(m_total_max_size, m_queue.size())); + // for (int i = 0; i < num_starts; i++) { + // QMetaObject::invokeMethod(this, &ConcurrentTask::startNext, Qt::QueuedConnection); + // } + // Start One task, startNext hadles starting the up to the m_total_max_size + // while tracking the number currently being done + QMetaObject::invokeMethod(this, &ConcurrentTask::startNext, Qt::QueuedConnection); } bool ConcurrentTask::abort() @@ -97,17 +99,18 @@ void ConcurrentTask::startNext() Task::Ptr next = m_queue.dequeue(); - connect(next.get(), &Task::succeeded, this, [this, next] { subTaskSucceeded(next); }); + connect(next.get(), &Task::succeeded, this, [this, next](){ subTaskSucceeded(next); }); connect(next.get(), &Task::failed, this, [this, next](QString msg) { subTaskFailed(next, msg); }); - connect(next.get(), &Task::status, this, &ConcurrentTask::subTaskStatus); - connect(next.get(), &Task::stepStatus, this, &ConcurrentTask::subTaskStatus); + connect(next.get(), &Task::status, this, [this, next](QString msg){ subTaskStatus(next, msg); }); + connect(next.get(), &Task::stepProgress, this, [this, next](QList tp){ subTaskStepProgress(next, tp); }); - connect(next.get(), &Task::progress, this, &ConcurrentTask::subTaskProgress); + connect(next.get(), &Task::progress, this, [this, next](qint64 current, qint64 total){ subTaskProgress(next, current, total); }); m_doing.insert(next.get(), next); + m_task_progress.insert(next->getUid(), std::make_shared(TaskStepProgress({next->getUid()}))); + - setStepStatus(next->isMultiStep() ? next->getStepStatus() : next->getStatus()); updateState(); QCoreApplication::processEvents(); @@ -123,7 +126,10 @@ void ConcurrentTask::startNext() void ConcurrentTask::subTaskSucceeded(Task::Ptr task) { m_done.insert(task.get(), task); + m_succeeded.insert(task.get(), task); + m_doing.remove(task.get()); + m_task_progress.value(task->getUid())->state = TaskState::Succeeded; disconnect(task.get(), 0, this, 0); @@ -138,6 +144,7 @@ void ConcurrentTask::subTaskFailed(Task::Ptr task, const QString& msg) m_failed.insert(task.get(), task); m_doing.remove(task.get()); + m_task_progress.value(task->getUid())->state = TaskState::Failed; disconnect(task.get(), 0, this, 0); @@ -146,20 +153,64 @@ void ConcurrentTask::subTaskFailed(Task::Ptr task, const QString& msg) startNext(); } -void ConcurrentTask::subTaskStatus(const QString& msg) +void ConcurrentTask::subTaskStatus(Task::Ptr task, const QString& msg) +{ + auto taskProgress = m_task_progress.value(task->getUid()); + taskProgress->status = msg; + updateState(); +} + +void ConcurrentTask::subTaskProgress(Task::Ptr task, qint64 current, qint64 total) { - setStepStatus(msg); + auto taskProgress = m_task_progress.value(task->getUid()); + + taskProgress->current = current; + taskProgress->total = total; + + taskProgress->details = task->getDetails(); + + updateStepProgress(); + updateState(); } -void ConcurrentTask::subTaskProgress(qint64 current, qint64 total) +void ConcurrentTask::subTaskStepProgress(Task::Ptr task, QList task_step_progress) { - m_stepProgress = current; - m_stepTotalProgress = total; + for (auto progress : task_step_progress) { + if (!m_task_progress.contains(progress.uid)) + m_task_progress.insert(progress.uid, std::make_shared(progress)); + + + } + +} + +void ConcurrentTask::updateStepProgress() +{ + qint64 current = 0, total = 0; + for ( auto taskProgress : m_task_progress ) { + current += taskProgress->current; + total += taskProgress->total; + } + + m_stepProgress = current; + m_stepTotalProgress = total; } void ConcurrentTask::updateState() { - setProgress(m_done.count(), totalSize()); - setStatus(tr("Executing %1 task(s) (%2 out of %3 are done)") - .arg(QString::number(m_doing.count()), QString::number(m_done.count()), QString::number(totalSize()))); + if (totalSize() > 1) { + setProgress(m_done.count(), totalSize()); + setStatus(tr("Executing %1 task(s) (%2 out of %3 are done)").arg(QString::number(m_doing.count()), QString::number(m_done.count()), QString::number(totalSize()))); + } else { + setProgress(m_stepProgress, m_stepTotalProgress); + QString status = tr("Please wait ..."); + if (m_queue.size() > 0) { + status = tr("Waiting for 1 task to start ..."); + } else if (m_doing.size() > 0) { + status = tr("Executing 1 task:"); + } else if (m_done.size() > 0) { + status = tr("Task finished."); + } + setStatus(status); + } } diff --git a/launcher/tasks/ConcurrentTask.h b/launcher/tasks/ConcurrentTask.h index d074d2e2..93469766 100644 --- a/launcher/tasks/ConcurrentTask.h +++ b/launcher/tasks/ConcurrentTask.h @@ -1,7 +1,10 @@ #pragma once +#include +#include #include #include +#include #include "tasks/Task.h" @@ -16,10 +19,7 @@ public: bool canAbort() const override { return true; } inline auto isMultiStep() const -> bool override { return m_queue.size() > 1; }; - auto getStepProgress() const -> qint64 override; - auto getStepTotalProgress() const -> qint64 override; - - inline auto getStepStatus() const -> QString override { return m_step_status; } + auto getStepProgress() const -> QList override; void addTask(Task::Ptr task); @@ -39,14 +39,15 @@ slots: void subTaskSucceeded(Task::Ptr); void subTaskFailed(Task::Ptr, const QString &msg); - void subTaskStatus(const QString &msg); - void subTaskProgress(qint64 current, qint64 total); + void subTaskStatus(Task::Ptr task, const QString &msg); + void subTaskProgress(Task::Ptr task, qint64 current, qint64 total); + void subTaskStepProgress(Task::Ptr task, QList task_step_progress); protected: // NOTE: This is not thread-safe. [[nodiscard]] unsigned int totalSize() const { return m_queue.size() + m_doing.size() + m_done.size(); } - void setStepStatus(QString status) { m_step_status = status; emit stepStatus(status); }; + void updateStepProgress(); virtual void updateState(); @@ -56,9 +57,12 @@ protected: QQueue m_queue; - QHash m_doing; + QHash m_doing; QHash m_done; QHash m_failed; + QHash m_succeeded; + + QHash> m_task_progress; int m_total_max_size; diff --git a/launcher/tasks/Task.cpp b/launcher/tasks/Task.cpp index 9ea1bb26..452dc2e3 100644 --- a/launcher/tasks/Task.cpp +++ b/launcher/tasks/Task.cpp @@ -37,8 +37,11 @@ #include +Q_LOGGING_CATEGORY(TaskLogC, "Task") + Task::Task(QObject *parent, bool show_debug) : QObject(parent), m_show_debug(show_debug) { + m_uid = QUuid::createUuid(); setAutoDelete(false); } @@ -65,31 +68,31 @@ void Task::start() case State::Inactive: { if (m_show_debug) - qDebug() << "Task" << describe() << "starting for the first time"; + qCDebug(TaskLogC) << "Task" << describe() << "starting for the first time"; break; } case State::AbortedByUser: { if (m_show_debug) - qDebug() << "Task" << describe() << "restarting for after being aborted by user"; + qCDebug(TaskLogC) << "Task" << describe() << "restarting for after being aborted by user"; break; } case State::Failed: { if (m_show_debug) - qDebug() << "Task" << describe() << "restarting for after failing at first"; + qCDebug(TaskLogC) << "Task" << describe() << "restarting for after failing at first"; break; } case State::Succeeded: { if (m_show_debug) - qDebug() << "Task" << describe() << "restarting for after succeeding at first"; + qCDebug(TaskLogC) << "Task" << describe() << "restarting for after succeeding at first"; break; } case State::Running: { if (m_show_debug) - qWarning() << "The launcher tried to start task" << describe() << "while it was already running!"; + qCWarning(TaskLogC) << "The launcher tried to start task" << describe() << "while it was already running!"; return; } } @@ -104,12 +107,12 @@ void Task::emitFailed(QString reason) // Don't fail twice. if (!isRunning()) { - qCritical() << "Task" << describe() << "failed while not running!!!!: " << reason; + qCCritical(TaskLogC) << "Task" << describe() << "failed while not running!!!!: " << reason; return; } m_state = State::Failed; m_failReason = reason; - qCritical() << "Task" << describe() << "failed: " << reason; + qCCritical(TaskLogC) << "Task" << describe() << "failed: " << reason; emit failed(reason); emit finished(); } @@ -119,13 +122,13 @@ void Task::emitAborted() // Don't abort twice. if (!isRunning()) { - qCritical() << "Task" << describe() << "aborted while not running!!!!"; + qCCritical(TaskLogC) << "Task" << describe() << "aborted while not running!!!!"; return; } m_state = State::AbortedByUser; m_failReason = "Aborted."; if (m_show_debug) - qDebug() << "Task" << describe() << "aborted."; + qCDebug(TaskLogC) << "Task" << describe() << "aborted."; emit aborted(); emit finished(); } @@ -135,12 +138,12 @@ void Task::emitSucceeded() // Don't succeed twice. if (!isRunning()) { - qCritical() << "Task" << describe() << "succeeded while not running!!!!"; + qCCritical(TaskLogC) << "Task" << describe() << "succeeded while not running!!!!"; return; } m_state = State::Succeeded; if (m_show_debug) - qDebug() << "Task" << describe() << "succeeded"; + qCDebug(TaskLogC) << "Task" << describe() << "succeeded"; emit succeeded(); emit finished(); } @@ -159,6 +162,7 @@ QString Task::describe() { out << name; } + out << " ID: " << m_uid.toString(QUuid::WithoutBraces); out << QChar(')'); out.flush(); return outStr; diff --git a/launcher/tasks/Task.h b/launcher/tasks/Task.h index 3d607dca..a6ab15b8 100644 --- a/launcher/tasks/Task.h +++ b/launcher/tasks/Task.h @@ -1,7 +1,8 @@ // SPDX-License-Identifier: GPL-3.0-only /* - * PolyMC - Minecraft Launcher + * PrismLauncher - Minecraft Launcher * Copyright (c) 2022 flowln + * Copyright (c) 2023 Rachel Powers <508861+Ryex@users.noreply.github.com> * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -36,9 +37,29 @@ #pragma once #include +#include +#include #include "QObjectPtr.h" +enum class TaskState { + Waiting, + Running, + Failed, + Succeeded, + Finished +}; + +struct TaskStepProgress { + QUuid uid; + qint64 current; + qint64 total; + QString status; + QString details; + TaskState state = TaskState::Waiting; + bool isDone() { return (state == TaskState::Failed) || (state == TaskState::Succeeded) || (state == TaskState::Finished); } +}; + class Task : public QObject, public QRunnable { Q_OBJECT public: @@ -73,12 +94,14 @@ class Task : public QObject, public QRunnable { auto getState() const -> State { return m_state; } QString getStatus() { return m_status; } - virtual auto getStepStatus() const -> QString { return m_status; } qint64 getProgress() { return m_progress; } qint64 getTotalProgress() { return m_progressTotal; } - virtual auto getStepProgress() const -> qint64 { return 0; } - virtual auto getStepTotalProgress() const -> qint64 { return 100; } + virtual auto getStepProgress() const -> QList { return {}; } + + virtual auto getDetails() const -> QString { return ""; } + + QUuid getUid() { return m_uid; } protected: void logWarning(const QString& line); @@ -94,7 +117,7 @@ class Task : public QObject, public QRunnable { void aborted(); void failed(QString reason); void status(QString status); - void stepStatus(QString status); + void stepProgress(QList task_progress); // /** Emitted when the canAbort() status has changed. */ @@ -135,4 +158,6 @@ class Task : public QObject, public QRunnable { private: // Change using setAbortStatus bool m_can_abort = false; + QUuid m_uid; + }; diff --git a/launcher/ui/dialogs/ProgressDialog.cpp b/launcher/ui/dialogs/ProgressDialog.cpp index da73a449..da627af3 100644 --- a/launcher/ui/dialogs/ProgressDialog.cpp +++ b/launcher/ui/dialogs/ProgressDialog.cpp @@ -1,26 +1,66 @@ -/* Copyright 2013-2021 MultiMC Contributors +/// SPDX-License-Identifier: GPL-3.0-only +/* + * PrismLaucher - Minecraft Launcher + * Copyright (C) 2023 Rachel Powers <508861+Ryex@users.noreply.github.com> * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. * - * http://www.apache.org/licenses/LICENSE-2.0 + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * This file incorporates work covered by the following copyright and + * permission notice: + * + * Copyright 2013-2021 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ #include "ProgressDialog.h" #include "ui_ProgressDialog.h" +#include #include #include #include "tasks/Task.h" +#include "ui/widgets/SubTaskProgressBar.h" + + +template +int map_int_range(T value) +{ + auto type_min = std::numeric_limits::min(); + auto type_max = std::numeric_limits::max(); + + auto int_min = std::numeric_limits::min(); + auto int_max = std::numeric_limits::max(); + + auto type_range = type_max - type_min; + auto int_range = int_max - int_min; + + return static_cast((value - type_min) * int_range / type_range + int_min); +} + + ProgressDialog::ProgressDialog(QWidget* parent) : QDialog(parent), ui(new Ui::ProgressDialog) { ui->setupUi(this); @@ -79,7 +119,7 @@ int ProgressDialog::execWithTask(Task* task) connect(task, &Task::failed, this, &ProgressDialog::onTaskFailed); connect(task, &Task::succeeded, this, &ProgressDialog::onTaskSucceeded); connect(task, &Task::status, this, &ProgressDialog::changeStatus); - connect(task, &Task::stepStatus, this, &ProgressDialog::changeStatus); + connect(task, &Task::stepProgress, this, &ProgressDialog::changeStepProgress); connect(task, &Task::progress, this, &ProgressDialog::changeProgress); connect(task, &Task::aborted, this, &ProgressDialog::hide); @@ -149,23 +189,54 @@ void ProgressDialog::onTaskSucceeded() void ProgressDialog::changeStatus(const QString& status) { ui->globalStatusLabel->setText(task->getStatus()); - ui->statusLabel->setText(task->getStepStatus()); + // ui->statusLabel->setText(task->getStepStatus()); updateSize(); } +void ProgressDialog::addTaskProgress(TaskStepProgress progress) +{ + SubTaskProgressBar* task_bar = new SubTaskProgressBar(this); + taskProgress.insert(progress.uid, task_bar); + ui->taskProgressLayout->addWidget(task_bar); +} + +void ProgressDialog::changeStepProgress(QList task_progress) +{ + for (auto tp : task_progress) { + if (!taskProgress.contains(tp.uid)) + addTaskProgress(tp); + auto task_bar = taskProgress.value(tp.uid); + + if (tp.total < 0) { + task_bar->setRange(0, 0); + } else { + task_bar->setRange(0, map_int_range(tp.total)); + } + + task_bar->setValue(map_int_range(tp.current)); + task_bar->setStatus(tp.status); + task_bar->setDetails(tp.details); + + if (tp.isDone()) { + task_bar->setVisible(false); + } + + } +} + void ProgressDialog::changeProgress(qint64 current, qint64 total) { ui->globalProgressBar->setMaximum(total); ui->globalProgressBar->setValue(current); - if (!m_is_multi_step) { - ui->taskProgressBar->setMaximum(total); - ui->taskProgressBar->setValue(current); - } else { - ui->taskProgressBar->setMaximum(task->getStepProgress()); - ui->taskProgressBar->setValue(task->getStepTotalProgress()); - } + // if (!m_is_multi_step) { + // ui->taskProgressBar->setMaximum(total); + // ui->taskProgressBar->setValue(current); + // } else { + // ui->taskProgressBar->setMaximum(task->getStepProgress()); + // ui->taskProgressBar->setValue(task->getStepTotalProgress()); + // } } void ProgressDialog::keyPressEvent(QKeyEvent* e) diff --git a/launcher/ui/dialogs/ProgressDialog.h b/launcher/ui/dialogs/ProgressDialog.h index 0b4b78a4..a7e203fb 100644 --- a/launcher/ui/dialogs/ProgressDialog.h +++ b/launcher/ui/dialogs/ProgressDialog.h @@ -1,22 +1,50 @@ -/* Copyright 2013-2021 MultiMC Contributors +/// SPDX-License-Identifier: GPL-3.0-only +/* + * PrismLaucher - Minecraft Launcher + * Copyright (C) 2023 Rachel Powers <508861+Ryex@users.noreply.github.com> * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. * - * http://www.apache.org/licenses/LICENSE-2.0 + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * This file incorporates work covered by the following copyright and + * permission notice: + * + * Copyright 2013-2021 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ + #pragma once #include #include +#include +#include + +#include "QObjectPtr.h" +#include "tasks/Task.h" + +#include "ui/widgets/SubTaskProgressBar.h" class Task; class SequentialTask; @@ -52,6 +80,7 @@ slots: void changeStatus(const QString &status); void changeProgress(qint64 current, qint64 total); + void changeStepProgress(QList task_progress); private @@ -64,6 +93,7 @@ protected: private: bool handleImmediateResult(QDialog::DialogCode &result); + void addTaskProgress(TaskStepProgress progress); private: Ui::ProgressDialog *ui; @@ -71,4 +101,8 @@ private: Task *task; bool m_is_multi_step = false; + QHash taskProgress; + + }; + diff --git a/launcher/ui/dialogs/ProgressDialog.ui b/launcher/ui/dialogs/ProgressDialog.ui index 34ab71e3..0a998987 100644 --- a/launcher/ui/dialogs/ProgressDialog.ui +++ b/launcher/ui/dialogs/ProgressDialog.ui @@ -2,6 +2,20 @@ ProgressDialog + + + 0 + 0 + 400 + 109 + + + + + 0 + 0 + + 400 @@ -18,6 +32,16 @@ Please wait... + + + + true + + + 24 + + + @@ -31,15 +55,11 @@ - - - - Global Task Status... - - + + - - + + 0 @@ -47,30 +67,7 @@ - Task Status... - - - true - - - - - - - 24 - - - false - - - - - - - true - - - 24 + Global Task Status... diff --git a/launcher/ui/widgets/SubTaskProgressBar.cpp b/launcher/ui/widgets/SubTaskProgressBar.cpp new file mode 100644 index 00000000..84ea5f20 --- /dev/null +++ b/launcher/ui/widgets/SubTaskProgressBar.cpp @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * PrismLaucher - Minecraft Launcher + * Copyright (C) 2022 Rachel Powers <508861+Ryex@users.noreply.github.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#include "SubTaskProgressBar.h" +#include "ui_SubTaskProgressBar.h" + +unique_qobject_ptr SubTaskProgressBar::create(QWidget* parent) +{ + auto progress_bar = new SubTaskProgressBar(parent); + return unique_qobject_ptr(progress_bar); +} + +SubTaskProgressBar::SubTaskProgressBar(QWidget* parent) + : ui(new Ui::SubTaskProgressBar) +{ + ui->setupUi(this); +} +SubTaskProgressBar::~SubTaskProgressBar() +{ + delete ui; +} + +void SubTaskProgressBar::setRange(int min, int max) +{ + ui->progressBar->setRange(min, max); +} + +void SubTaskProgressBar::setValue(int value) +{ + ui->progressBar->setValue(value); +} + +void SubTaskProgressBar::setStatus(QString status) +{ + ui->statusLabel->setText(status); +} + +void SubTaskProgressBar::setDetails(QString details) +{ + ui->statusDetailsLabel->setText(details); +} + diff --git a/launcher/ui/widgets/SubTaskProgressBar.h b/launcher/ui/widgets/SubTaskProgressBar.h new file mode 100644 index 00000000..3375a0bc --- /dev/null +++ b/launcher/ui/widgets/SubTaskProgressBar.h @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * PrismLaucher - Minecraft Launcher + * Copyright (C) 2022 Rachel Powers <508861+Ryex@users.noreply.github.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ +#pragma once + +#include +#include +#include +#include "QObjectPtr.h" + +namespace Ui { +class SubTaskProgressBar; +} + +class SubTaskProgressBar : public QWidget +{ + Q_OBJECT + +public: + static unique_qobject_ptr create(QWidget* parent = nullptr); + + SubTaskProgressBar(QWidget* parent = nullptr); + ~SubTaskProgressBar(); + + void setRange(int min, int max); + void setValue(int value); + void setStatus(QString status); + void setDetails(QString details); + + + +private: + Ui::SubTaskProgressBar* ui; + +}; diff --git a/launcher/ui/widgets/SubTaskProgressBar.ui b/launcher/ui/widgets/SubTaskProgressBar.ui new file mode 100644 index 00000000..966fdb88 --- /dev/null +++ b/launcher/ui/widgets/SubTaskProgressBar.ui @@ -0,0 +1,70 @@ + + + SubTaskProgressBar + + + + 0 + 0 + 265 + 65 + + + + + 0 + 0 + + + + Form + + + + + + + + + 0 + 0 + + + + Sub Task Status... + + + + + + + + 0 + 0 + + + + Status Details + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + 24 + + + true + + + + + + + + diff --git a/tests/Task_test.cpp b/tests/Task_test.cpp index 95eb4a30..678382ba 100644 --- a/tests/Task_test.cpp +++ b/tests/Task_test.cpp @@ -99,7 +99,7 @@ class TaskTest : public QObject { t.setStatus(status); QCOMPARE(t.getStatus(), status); - QCOMPARE(t.getStepStatus(), status); + QCOMPARE(t.getStepProgress().isEmpty(), QList{}.isEmpty()); } void test_SetStatus_MultiStep(){ @@ -111,7 +111,7 @@ class TaskTest : public QObject { QCOMPARE(t.getStatus(), status); // Even though it is multi step, it does not override the getStepStatus method, // so it should remain the same. - QCOMPARE(t.getStepStatus(), status); + QCOMPARE(t.getStepProgress().isEmpty(), QList{}.isEmpty()); } void test_SetProgress(){ -- cgit From 9d2f0e4dc8fc3995052770c6a7948cb0372fdcbb Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Thu, 30 Mar 2023 23:50:29 -0700 Subject: feat: Propogated subtask progress Oh boy this is big. > TaskStepProgress struct is now QMetaObject compatabile and can be sent through signals > Task now has a method to propogates sub task progress it must be signal bound by each task containing a task wishing to report progress of it's children. > Downloads report speed > Tasks now have UUIDS to track them - use when reporting - use when logging - use when storeing them or objects related to them Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- launcher/InstanceImportTask.cpp | 3 + launcher/InstanceList.cpp | 1 + launcher/ResourceDownloadTask.cpp | 1 + launcher/launch/steps/Update.cpp | 5 +- launcher/minecraft/MinecraftLoadAndCheck.cpp | 1 + launcher/minecraft/MinecraftUpdate.cpp | 2 + launcher/minecraft/update/AssetUpdateTask.cpp | 2 + launcher/minecraft/update/FMLLibrariesTask.cpp | 1 + launcher/minecraft/update/LibrariesTask.cpp | 2 + .../modplatform/atlauncher/ATLPackInstallTask.cpp | 2 + .../flame/FlameInstanceCreationTask.cpp | 4 +- .../modplatform/legacy_ftb/PackInstallTask.cpp | 1 + .../modrinth/ModrinthInstanceCreationTask.cpp | 2 + .../technic/SingleZipPackInstallTask.cpp | 1 + .../modplatform/technic/SolderPackInstallTask.cpp | 1 + launcher/net/Download.cpp | 39 +++++++- launcher/tasks/ConcurrentTask.cpp | 44 +++++---- launcher/tasks/ConcurrentTask.h | 6 +- launcher/tasks/Task.cpp | 5 + launcher/tasks/Task.h | 26 +++-- launcher/ui/dialogs/ProgressDialog.cpp | 65 +++++++------ launcher/ui/dialogs/ProgressDialog.h | 4 +- launcher/ui/dialogs/ProgressDialog.ui | 108 +++++++++++++++++---- launcher/ui/widgets/SubTaskProgressBar.ui | 31 +++++- tests/Task_test.cpp | 9 +- 25 files changed, 269 insertions(+), 97 deletions(-) (limited to 'launcher/ui/dialogs') diff --git a/launcher/InstanceImportTask.cpp b/launcher/InstanceImportTask.cpp index 080828a8..c196396d 100644 --- a/launcher/InstanceImportTask.cpp +++ b/launcher/InstanceImportTask.cpp @@ -98,6 +98,7 @@ void InstanceImportTask::executeTask() connect(m_filesNetJob.get(), &NetJob::succeeded, this, &InstanceImportTask::downloadSucceeded); connect(m_filesNetJob.get(), &NetJob::progress, this, &InstanceImportTask::downloadProgressChanged); + connect(m_filesNetJob.get(), &NetJob::stepProgress, this, &InstanceImportTask::propogateStepProgress); connect(m_filesNetJob.get(), &NetJob::failed, this, &InstanceImportTask::downloadFailed); connect(m_filesNetJob.get(), &NetJob::aborted, this, &InstanceImportTask::downloadAborted); @@ -291,6 +292,7 @@ void InstanceImportTask::processFlame() }); connect(inst_creation_task, &Task::failed, this, &InstanceImportTask::emitFailed); connect(inst_creation_task, &Task::progress, this, &InstanceImportTask::setProgress); + connect(inst_creation_task, &Task::stepProgress, this, &InstanceImportTask::propogateStepProgress); connect(inst_creation_task, &Task::status, this, &InstanceImportTask::setStatus); connect(inst_creation_task, &Task::finished, inst_creation_task, &InstanceCreationTask::deleteLater); @@ -382,6 +384,7 @@ void InstanceImportTask::processModrinth() }); connect(inst_creation_task, &Task::failed, this, &InstanceImportTask::emitFailed); connect(inst_creation_task, &Task::progress, this, &InstanceImportTask::setProgress); + connect(inst_creation_task, &Task::stepProgress, this, &InstanceImportTask::propogateStepProgress); connect(inst_creation_task, &Task::status, this, &InstanceImportTask::setStatus); connect(inst_creation_task, &Task::finished, inst_creation_task, &InstanceCreationTask::deleteLater); diff --git a/launcher/InstanceList.cpp b/launcher/InstanceList.cpp index 68e3e92c..dbc891ff 100644 --- a/launcher/InstanceList.cpp +++ b/launcher/InstanceList.cpp @@ -788,6 +788,7 @@ class InstanceStaging : public Task { connect(child, &Task::abortStatusChanged, this, &InstanceStaging::setAbortable); connect(child, &Task::status, this, &InstanceStaging::setStatus); connect(child, &Task::progress, this, &InstanceStaging::setProgress); + connect(child, &Task::stepProgress, this, &InstanceStaging::propogateStepProgress); connect(&m_backoffTimer, &QTimer::timeout, this, &InstanceStaging::childSucceded); } diff --git a/launcher/ResourceDownloadTask.cpp b/launcher/ResourceDownloadTask.cpp index 98bcf259..61b918aa 100644 --- a/launcher/ResourceDownloadTask.cpp +++ b/launcher/ResourceDownloadTask.cpp @@ -53,6 +53,7 @@ ResourceDownloadTask::ResourceDownloadTask(ModPlatform::IndexedPack pack, m_filesNetJob->addNetAction(Net::Download::makeFile(m_pack_version.downloadUrl, dir.absoluteFilePath(getFilename()))); connect(m_filesNetJob.get(), &NetJob::succeeded, this, &ResourceDownloadTask::downloadSucceeded); connect(m_filesNetJob.get(), &NetJob::progress, this, &ResourceDownloadTask::downloadProgressChanged); + connect(m_filesNetJob.get(), &NetJob::stepProgress, this, &ResourceDownloadTask::propogateStepProgress); connect(m_filesNetJob.get(), &NetJob::failed, this, &ResourceDownloadTask::downloadFailed); addTask(m_filesNetJob); diff --git a/launcher/launch/steps/Update.cpp b/launcher/launch/steps/Update.cpp index 28bd153d..1640d115 100644 --- a/launcher/launch/steps/Update.cpp +++ b/launcher/launch/steps/Update.cpp @@ -27,8 +27,9 @@ void Update::executeTask() if(m_updateTask) { connect(m_updateTask.get(), SIGNAL(finished()), this, SLOT(updateFinished())); - connect(m_updateTask.get(), &Task::progress, this, &Task::setProgress); - connect(m_updateTask.get(), &Task::status, this, &Task::setStatus); + connect(m_updateTask.get(), &Task::progress, this, &Update::setProgress); + connect(m_updateTask.get(), &Task::stepProgress, this, &Update::propogateStepProgress); + connect(m_updateTask.get(), &Task::status, this, &Update::setStatus); emit progressReportingRequest(); return; } diff --git a/launcher/minecraft/MinecraftLoadAndCheck.cpp b/launcher/minecraft/MinecraftLoadAndCheck.cpp index d72bc7be..1c3f6fb7 100644 --- a/launcher/minecraft/MinecraftLoadAndCheck.cpp +++ b/launcher/minecraft/MinecraftLoadAndCheck.cpp @@ -22,6 +22,7 @@ void MinecraftLoadAndCheck::executeTask() connect(m_task.get(), &Task::failed, this, &MinecraftLoadAndCheck::subtaskFailed); connect(m_task.get(), &Task::aborted, this, [this]{ subtaskFailed(tr("Aborted")); }); connect(m_task.get(), &Task::progress, this, &MinecraftLoadAndCheck::progress); + connect(m_task.get(), &Task::stepProgress, this, &MinecraftLoadAndCheck::propogateStepProgress); connect(m_task.get(), &Task::status, this, &MinecraftLoadAndCheck::setStatus); } diff --git a/launcher/minecraft/MinecraftUpdate.cpp b/launcher/minecraft/MinecraftUpdate.cpp index 07ad4882..3ce808f8 100644 --- a/launcher/minecraft/MinecraftUpdate.cpp +++ b/launcher/minecraft/MinecraftUpdate.cpp @@ -100,6 +100,7 @@ void MinecraftUpdate::next() disconnect(task.get(), &Task::failed, this, &MinecraftUpdate::subtaskFailed); disconnect(task.get(), &Task::aborted, this, &Task::abort); disconnect(task.get(), &Task::progress, this, &MinecraftUpdate::progress); + disconnect(task.get(), &Task::stepProgress, this, &MinecraftUpdate::propogateStepProgress); disconnect(task.get(), &Task::status, this, &MinecraftUpdate::setStatus); } if(m_currentTask == m_tasks.size()) @@ -118,6 +119,7 @@ void MinecraftUpdate::next() connect(task.get(), &Task::failed, this, &MinecraftUpdate::subtaskFailed); connect(task.get(), &Task::aborted, this, &Task::abort); connect(task.get(), &Task::progress, this, &MinecraftUpdate::progress); + connect(task.get(), &Task::stepProgress, this, &MinecraftUpdate::propogateStepProgress); connect(task.get(), &Task::status, this, &MinecraftUpdate::setStatus); // if the task is already running, do not start it again if(!task->isRunning()) diff --git a/launcher/minecraft/update/AssetUpdateTask.cpp b/launcher/minecraft/update/AssetUpdateTask.cpp index 8ccb0e1d..31fd5eb1 100644 --- a/launcher/minecraft/update/AssetUpdateTask.cpp +++ b/launcher/minecraft/update/AssetUpdateTask.cpp @@ -45,6 +45,7 @@ void AssetUpdateTask::executeTask() connect(downloadJob.get(), &NetJob::failed, this, &AssetUpdateTask::assetIndexFailed); connect(downloadJob.get(), &NetJob::aborted, this, [this]{ emitFailed(tr("Aborted")); }); connect(downloadJob.get(), &NetJob::progress, this, &AssetUpdateTask::progress); + connect(downloadJob.get(), &NetJob::stepProgress, this, &AssetUpdateTask::propogateStepProgress); qDebug() << m_inst->name() << ": Starting asset index download"; downloadJob->start(); @@ -83,6 +84,7 @@ void AssetUpdateTask::assetIndexFinished() connect(downloadJob.get(), &NetJob::failed, this, &AssetUpdateTask::assetsFailed); connect(downloadJob.get(), &NetJob::aborted, this, [this]{ emitFailed(tr("Aborted")); }); connect(downloadJob.get(), &NetJob::progress, this, &AssetUpdateTask::progress); + connect(downloadJob.get(), &NetJob::stepProgress, this, &AssetUpdateTask::propogateStepProgress); downloadJob->start(); return; } diff --git a/launcher/minecraft/update/FMLLibrariesTask.cpp b/launcher/minecraft/update/FMLLibrariesTask.cpp index 96fd3ba3..75e5c572 100644 --- a/launcher/minecraft/update/FMLLibrariesTask.cpp +++ b/launcher/minecraft/update/FMLLibrariesTask.cpp @@ -75,6 +75,7 @@ void FMLLibrariesTask::executeTask() connect(dljob.get(), &NetJob::failed, this, &FMLLibrariesTask::fmllibsFailed); connect(dljob.get(), &NetJob::aborted, this, [this]{ emitFailed(tr("Aborted")); }); connect(dljob.get(), &NetJob::progress, this, &FMLLibrariesTask::progress); + connect(dljob.get(), &NetJob::stepProgress, this, &FMLLibrariesTask::propogateStepProgress); downloadJob.reset(dljob); downloadJob->start(); } diff --git a/launcher/minecraft/update/LibrariesTask.cpp b/launcher/minecraft/update/LibrariesTask.cpp index b9410111..415b9a66 100644 --- a/launcher/minecraft/update/LibrariesTask.cpp +++ b/launcher/minecraft/update/LibrariesTask.cpp @@ -70,6 +70,8 @@ void LibrariesTask::executeTask() connect(downloadJob.get(), &NetJob::failed, this, &LibrariesTask::jarlibFailed); connect(downloadJob.get(), &NetJob::aborted, this, [this]{ emitFailed(tr("Aborted")); }); connect(downloadJob.get(), &NetJob::progress, this, &LibrariesTask::progress); + connect(downloadJob.get(), &NetJob::stepProgress, this, &LibrariesTask::propogateStepProgress); + downloadJob->start(); } diff --git a/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp b/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp index 4bd8b7f2..28026732 100644 --- a/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp +++ b/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp @@ -683,6 +683,7 @@ void PackInstallTask::installConfigs() abortable = true; setProgress(current, total); }); + connect(jobPtr.get(), &NetJob::stepProgress, this, &PackInstallTask::propogateStepProgress); connect(jobPtr.get(), &NetJob::aborted, [&]{ abortable = false; jobPtr.reset(); @@ -849,6 +850,7 @@ void PackInstallTask::downloadMods() abortable = true; setProgress(current, total); }); + connect(jobPtr.get(), &NetJob::stepProgress, this, &PackInstallTask::propogateStepProgress); connect(jobPtr.get(), &NetJob::aborted, [&] { abortable = false; diff --git a/launcher/modplatform/flame/FlameInstanceCreationTask.cpp b/launcher/modplatform/flame/FlameInstanceCreationTask.cpp index 964b559c..3cb6b61a 100644 --- a/launcher/modplatform/flame/FlameInstanceCreationTask.cpp +++ b/launcher/modplatform/flame/FlameInstanceCreationTask.cpp @@ -35,6 +35,7 @@ #include "FlameInstanceCreationTask.h" +#include "modplatform/flame/FileResolvingTask.h" #include "modplatform/flame/FlameAPI.h" #include "modplatform/flame/PackManifest.h" @@ -382,7 +383,7 @@ bool FlameCreationTask::createInstance() }); connect(m_mod_id_resolver.get(), &Flame::FileResolvingTask::progress, this, &FlameCreationTask::setProgress); connect(m_mod_id_resolver.get(), &Flame::FileResolvingTask::status, this, &FlameCreationTask::setStatus); - + connect(m_mod_id_resolver.get(), &Flame::FileResolvingTask::stepProgress, this, &FlameCreationTask::propogateStepProgress); m_mod_id_resolver->start(); loop.exec(); @@ -497,6 +498,7 @@ void FlameCreationTask::setupDownloadJob(QEventLoop& loop) setError(reason); }); connect(m_files_job.get(), &NetJob::progress, this, &FlameCreationTask::setProgress); + connect(m_files_job.get(), &NetJob::stepProgress, this, &FlameCreationTask::propogateStepProgress); connect(m_files_job.get(), &NetJob::finished, &loop, &QEventLoop::quit); setStatus(tr("Downloading mods...")); diff --git a/launcher/modplatform/legacy_ftb/PackInstallTask.cpp b/launcher/modplatform/legacy_ftb/PackInstallTask.cpp index 8d45fc5c..36c142ac 100644 --- a/launcher/modplatform/legacy_ftb/PackInstallTask.cpp +++ b/launcher/modplatform/legacy_ftb/PackInstallTask.cpp @@ -81,6 +81,7 @@ void PackInstallTask::downloadPack() connect(netJobContainer.get(), &NetJob::succeeded, this, &PackInstallTask::onDownloadSucceeded); connect(netJobContainer.get(), &NetJob::failed, this, &PackInstallTask::onDownloadFailed); connect(netJobContainer.get(), &NetJob::progress, this, &PackInstallTask::onDownloadProgress); + connect(netJobContainer.get(), &NetJob::stepProgress, this, &PackInstallTask::propogateStepProgress); connect(netJobContainer.get(), &NetJob::aborted, this, &PackInstallTask::onDownloadAborted); netJobContainer->start(); diff --git a/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp b/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp index 6814e645..2fb656ea 100644 --- a/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp +++ b/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp @@ -11,6 +11,7 @@ #include "net/ChecksumValidator.h" +#include "net/NetJob.h" #include "settings/INISettingsObject.h" #include "ui/dialogs/CustomMessageBox.h" @@ -263,6 +264,7 @@ bool ModrinthCreationTask::createInstance() }); connect(m_files_job.get(), &NetJob::finished, &loop, &QEventLoop::quit); connect(m_files_job.get(), &NetJob::progress, [&](qint64 current, qint64 total) { setProgress(current, total); }); + connect(m_files_job.get(), &NetJob::stepProgress, this, &ModrinthCreationTask::propogateStepProgress); setStatus(tr("Downloading mods...")); m_files_job->start(); diff --git a/launcher/modplatform/technic/SingleZipPackInstallTask.cpp b/launcher/modplatform/technic/SingleZipPackInstallTask.cpp index 8fd43d21..f07ca24a 100644 --- a/launcher/modplatform/technic/SingleZipPackInstallTask.cpp +++ b/launcher/modplatform/technic/SingleZipPackInstallTask.cpp @@ -50,6 +50,7 @@ void Technic::SingleZipPackInstallTask::executeTask() auto job = m_filesNetJob.get(); connect(job, &NetJob::succeeded, this, &Technic::SingleZipPackInstallTask::downloadSucceeded); connect(job, &NetJob::progress, this, &Technic::SingleZipPackInstallTask::downloadProgressChanged); + connect(job, &NetJob::stepProgress, this, &Technic::SingleZipPackInstallTask::propogateStepProgress); connect(job, &NetJob::failed, this, &Technic::SingleZipPackInstallTask::downloadFailed); m_filesNetJob->start(); } diff --git a/launcher/modplatform/technic/SolderPackInstallTask.cpp b/launcher/modplatform/technic/SolderPackInstallTask.cpp index 77c503f0..c26d6a5a 100644 --- a/launcher/modplatform/technic/SolderPackInstallTask.cpp +++ b/launcher/modplatform/technic/SolderPackInstallTask.cpp @@ -127,6 +127,7 @@ void Technic::SolderPackInstallTask::fileListSucceeded() connect(m_filesNetJob.get(), &NetJob::succeeded, this, &Technic::SolderPackInstallTask::downloadSucceeded); connect(m_filesNetJob.get(), &NetJob::progress, this, &Technic::SolderPackInstallTask::downloadProgressChanged); + connect(m_filesNetJob.get(), &NetJob::stepProgress, this, &Technic::SolderPackInstallTask::propogateStepProgress); connect(m_filesNetJob.get(), &NetJob::failed, this, &Technic::SolderPackInstallTask::downloadFailed); connect(m_filesNetJob.get(), &NetJob::aborted, this, &Technic::SolderPackInstallTask::downloadAborted); m_filesNetJob->start(); diff --git a/launcher/net/Download.cpp b/launcher/net/Download.cpp index 26488a43..a4c3ebfc 100644 --- a/launcher/net/Download.cpp +++ b/launcher/net/Download.cpp @@ -36,6 +36,8 @@ */ #include "Download.h" +#include +#include #include #include @@ -52,6 +54,33 @@ Q_LOGGING_CATEGORY(DownloadLogC, "Task.Net.Download") namespace Net { +QString truncateUrlHumanFriendly(QUrl &url, int max_len, bool hard_limit = false) +{ + auto display_options = QUrl::RemoveUserInfo | QUrl::RemoveFragment | QUrl::NormalizePathSegments; + auto str_url = url.toDisplayString(display_options); + if (str_url.length() <= max_len) + return str_url; + + QRegularExpression re(R"(^([\w]+:\/\/)([\w._-]+\/)([\w._-]+\/).*(\/[^]+[^]+)$)"); + + auto url_compact = QString(str_url); + url_compact.replace(re, "\\1\\2\\3...\\4"); + if (url_compact.length() >= max_len) { + auto url_compact = QString(str_url); + url_compact.replace(re, "\\1\\2...\\4"); + } + + + if ((url_compact.length() >= max_len) && hard_limit) { + auto to_remove = url_compact.length() - max_len + 3; + url_compact.remove(url_compact.length() - to_remove - 1, to_remove); + url_compact.append("..."); + } + + return url_compact; + +} + auto Download::makeCached(QUrl url, MetaEntryPtr entry, Options options) -> Download::Ptr { auto dl = makeShared(); @@ -91,7 +120,7 @@ void Download::addValidator(Validator* v) void Download::executeTask() { - setStatus(tr("Downloading %1").arg(m_url.toString())); + setStatus(tr("Downloading %1").arg(truncateUrlHumanFriendly(m_url, 60))); if (getState() == Task::State::AbortedByUser) { qCWarning(DownloadLogC) << getUid().toString() << "Attempt to start an aborted Download:" << m_url.toString(); @@ -152,9 +181,11 @@ void Download::downloadProgress(qint64 bytesReceived, qint64 bytesTotal) auto elapsed = now - m_last_progress_time; auto elapsed_ms = std::chrono::duration_cast(elapsed).count(); auto bytes_recived_since = bytesReceived - m_last_progress_bytes; - - auto speed = humanReadableFileSize(bytes_recived_since / elapsed_ms * 1000) + "/s"; - m_details = speed; + if (elapsed_ms > 0) { + m_details = humanReadableFileSize(bytes_recived_since / elapsed_ms * 1000) + "/s"; + } else { + m_details = "0 b/s"; + } setProgress(bytesReceived, bytesTotal); } diff --git a/launcher/tasks/ConcurrentTask.cpp b/launcher/tasks/ConcurrentTask.cpp index 48e1bc18..fde7d0ad 100644 --- a/launcher/tasks/ConcurrentTask.cpp +++ b/launcher/tasks/ConcurrentTask.cpp @@ -2,6 +2,7 @@ #include #include +#include "tasks/Task.h" ConcurrentTask::ConcurrentTask(QObject* parent, QString task_name, int max_concurrent) : Task(parent), m_name(task_name), m_total_max_size(max_concurrent) @@ -15,13 +16,9 @@ ConcurrentTask::~ConcurrentTask() } } -auto ConcurrentTask::getStepProgress() const -> QList +auto ConcurrentTask::getStepProgress() const -> TaskStepProgressList { - QList task_progress; - for (auto progress : task_progress) { - task_progress.append(task_progress); - } - return task_progress; + return m_task_progress.values(); } void ConcurrentTask::addTask(Task::Ptr task) @@ -103,7 +100,7 @@ void ConcurrentTask::startNext() connect(next.get(), &Task::failed, this, [this, next](QString msg) { subTaskFailed(next, msg); }); connect(next.get(), &Task::status, this, [this, next](QString msg){ subTaskStatus(next, msg); }); - connect(next.get(), &Task::stepProgress, this, [this, next](QList tp){ subTaskStepProgress(next, tp); }); + connect(next.get(), &Task::stepProgress, this, [this, next](TaskStepProgressList tp){ subTaskStepProgress(next, tp); }); connect(next.get(), &Task::progress, this, [this, next](qint64 current, qint64 total){ subTaskProgress(next, current, total); }); @@ -112,6 +109,7 @@ void ConcurrentTask::startNext() updateState(); + updateStepProgress(); QCoreApplication::processEvents(); @@ -129,12 +127,12 @@ void ConcurrentTask::subTaskSucceeded(Task::Ptr task) m_succeeded.insert(task.get(), task); m_doing.remove(task.get()); - m_task_progress.value(task->getUid())->state = TaskState::Succeeded; + m_task_progress.value(task->getUid())->state = TaskStepState::Succeeded; disconnect(task.get(), 0, this, 0); updateState(); - + updateStepProgress(); startNext(); } @@ -144,20 +142,22 @@ void ConcurrentTask::subTaskFailed(Task::Ptr task, const QString& msg) m_failed.insert(task.get(), task); m_doing.remove(task.get()); - m_task_progress.value(task->getUid())->state = TaskState::Failed; + m_task_progress.value(task->getUid())->state = TaskStepState::Failed; disconnect(task.get(), 0, this, 0); updateState(); - + updateStepProgress(); startNext(); } void ConcurrentTask::subTaskStatus(Task::Ptr task, const QString& msg) { auto taskProgress = m_task_progress.value(task->getUid()); - taskProgress->status = msg; + taskProgress->status = msg; + taskProgress->state = TaskStepState::Running; updateState(); + updateStepProgress(); } void ConcurrentTask::subTaskProgress(Task::Ptr task, qint64 current, qint64 total) @@ -166,21 +166,28 @@ void ConcurrentTask::subTaskProgress(Task::Ptr task, qint64 current, qint64 tota taskProgress->current = current; taskProgress->total = total; - + taskProgress->state = TaskStepState::Running; taskProgress->details = task->getDetails(); updateStepProgress(); updateState(); } -void ConcurrentTask::subTaskStepProgress(Task::Ptr task, QList task_step_progress) +void ConcurrentTask::subTaskStepProgress(Task::Ptr task, TaskStepProgressList task_step_progress) { for (auto progress : task_step_progress) { - if (!m_task_progress.contains(progress.uid)) - m_task_progress.insert(progress.uid, std::make_shared(progress)); - - + if (!m_task_progress.contains(progress->uid)) { + m_task_progress.insert(progress->uid, progress); + } else { + auto tp = m_task_progress.value(progress->uid); + tp->current = progress->current; + tp->total = progress->total; + tp->status = progress->status; + tp->details = progress->details; + } } + + updateStepProgress(); } @@ -194,6 +201,7 @@ void ConcurrentTask::updateStepProgress() m_stepProgress = current; m_stepTotalProgress = total; + emit stepProgress(m_task_progress.values()); } void ConcurrentTask::updateState() diff --git a/launcher/tasks/ConcurrentTask.h b/launcher/tasks/ConcurrentTask.h index 93469766..9d4413c6 100644 --- a/launcher/tasks/ConcurrentTask.h +++ b/launcher/tasks/ConcurrentTask.h @@ -18,8 +18,8 @@ public: bool canAbort() const override { return true; } - inline auto isMultiStep() const -> bool override { return m_queue.size() > 1; }; - auto getStepProgress() const -> QList override; + inline auto isMultiStep() const -> bool override { return totalSize() > 1; }; + auto getStepProgress() const -> TaskStepProgressList override; void addTask(Task::Ptr task); @@ -41,7 +41,7 @@ slots: void subTaskFailed(Task::Ptr, const QString &msg); void subTaskStatus(Task::Ptr task, const QString &msg); void subTaskProgress(Task::Ptr task, qint64 current, qint64 total); - void subTaskStepProgress(Task::Ptr task, QList task_step_progress); + void subTaskStepProgress(Task::Ptr task, TaskStepProgressList task_step_progress); protected: // NOTE: This is not thread-safe. diff --git a/launcher/tasks/Task.cpp b/launcher/tasks/Task.cpp index 452dc2e3..5aada876 100644 --- a/launcher/tasks/Task.cpp +++ b/launcher/tasks/Task.cpp @@ -148,6 +148,11 @@ void Task::emitSucceeded() emit finished(); } +void Task::propogateStepProgress(TaskStepProgressList task_progress) +{ + emit stepProgress(task_progress); +} + QString Task::describe() { QString outStr; diff --git a/launcher/tasks/Task.h b/launcher/tasks/Task.h index a6ab15b8..863f8a4c 100644 --- a/launcher/tasks/Task.h +++ b/launcher/tasks/Task.h @@ -42,7 +42,7 @@ #include "QObjectPtr.h" -enum class TaskState { +enum class TaskStepState { Waiting, Running, Failed, @@ -50,16 +50,22 @@ enum class TaskState { Finished }; +Q_DECLARE_METATYPE(TaskStepState) + struct TaskStepProgress { QUuid uid; - qint64 current; - qint64 total; - QString status; - QString details; - TaskState state = TaskState::Waiting; - bool isDone() { return (state == TaskState::Failed) || (state == TaskState::Succeeded) || (state == TaskState::Finished); } + qint64 current = 0; + qint64 total = -1; + QString status = ""; + QString details = ""; + TaskStepState state = TaskStepState::Waiting; + bool isDone() { return (state == TaskStepState::Failed) || (state == TaskStepState::Succeeded) || (state == TaskStepState::Finished); } }; +Q_DECLARE_METATYPE(TaskStepProgress) + +typedef QList> TaskStepProgressList; + class Task : public QObject, public QRunnable { Q_OBJECT public: @@ -97,7 +103,7 @@ class Task : public QObject, public QRunnable { qint64 getProgress() { return m_progress; } qint64 getTotalProgress() { return m_progressTotal; } - virtual auto getStepProgress() const -> QList { return {}; } + virtual auto getStepProgress() const -> TaskStepProgressList { return {}; } virtual auto getDetails() const -> QString { return ""; } @@ -117,7 +123,7 @@ class Task : public QObject, public QRunnable { void aborted(); void failed(QString reason); void status(QString status); - void stepProgress(QList task_progress); // + void stepProgress(TaskStepProgressList task_progress); // /** Emitted when the canAbort() status has changed. */ @@ -140,6 +146,8 @@ class Task : public QObject, public QRunnable { virtual void emitAborted(); virtual void emitFailed(QString reason = ""); + virtual void propogateStepProgress(TaskStepProgressList task_progress); + public slots: void setStatus(const QString& status); void setProgress(qint64 current, qint64 total); diff --git a/launcher/ui/dialogs/ProgressDialog.cpp b/launcher/ui/dialogs/ProgressDialog.cpp index da627af3..f7a3a862 100644 --- a/launcher/ui/dialogs/ProgressDialog.cpp +++ b/launcher/ui/dialogs/ProgressDialog.cpp @@ -48,10 +48,12 @@ template int map_int_range(T value) { - auto type_min = std::numeric_limits::min(); + // auto type_min = std::numeric_limits::min(); + auto type_min = 0; auto type_max = std::numeric_limits::max(); - auto int_min = std::numeric_limits::min(); + // auto int_min = std::numeric_limits::min(); + auto int_min = 0; auto int_max = std::numeric_limits::max(); auto type_range = type_max - type_min; @@ -64,6 +66,7 @@ int map_int_range(T value) ProgressDialog::ProgressDialog(QWidget* parent) : QDialog(parent), ui(new Ui::ProgressDialog) { ui->setupUi(this); + ui->taskProgressScrollArea->setHidden(true); this->setWindowFlags(this->windowFlags() & ~Qt::WindowContextHelpButtonHint); setAttribute(Qt::WidgetAttribute::WA_QuitOnClose, true); setSkipButton(false); @@ -94,10 +97,17 @@ ProgressDialog::~ProgressDialog() } void ProgressDialog::updateSize() -{ +{ + QSize lastSize = this->size(); QSize qSize = QSize(480, minimumSizeHint().height()); resize(qSize); setFixedSize(qSize); + // keep the dialog in the center after a resize + if (lastSize != qSize) + this->move( + this->parentWidget()->x() + (this->parentWidget()->width() - this->width()) / 2, + this->parentWidget()->y() + (this->parentWidget()->height() - this->height()) / 2 + ); } int ProgressDialog::execWithTask(Task* task) @@ -126,10 +136,8 @@ int ProgressDialog::execWithTask(Task* task) connect(task, &Task::abortStatusChanged, ui->skipButton, &QPushButton::setEnabled); m_is_multi_step = task->isMultiStep(); - if (!m_is_multi_step) { - ui->globalStatusLabel->setHidden(true); - ui->globalProgressBar->setHidden(true); - } + ui->taskProgressScrollArea->setHidden(!m_is_multi_step); + updateSize(); // It's a good idea to start the task after we entered the dialog's event loop :^) if (!task->isRunning()) { @@ -139,6 +147,9 @@ int ProgressDialog::execWithTask(Task* task) changeProgress(task->getProgress(), task->getTotalProgress()); } + // auto size_hint = ui->verticalLayout->sizeHint(); + // resize(size_hint.width(), size_hint.height()); + return QDialog::exec(); } @@ -189,40 +200,45 @@ void ProgressDialog::onTaskSucceeded() void ProgressDialog::changeStatus(const QString& status) { ui->globalStatusLabel->setText(task->getStatus()); - // ui->statusLabel->setText(task->getStepStatus()); + ui->globalStatusDetailsLabel->setText(task->getDetails()); updateSize(); } -void ProgressDialog::addTaskProgress(TaskStepProgress progress) +void ProgressDialog::addTaskProgress(TaskStepProgress* progress) { SubTaskProgressBar* task_bar = new SubTaskProgressBar(this); - taskProgress.insert(progress.uid, task_bar); - ui->taskProgressLayout->addWidget(task_bar); + taskProgress.insert(progress->uid, task_bar); + ui->taskProgressLayout->insertWidget(0, task_bar); } -void ProgressDialog::changeStepProgress(QList task_progress) +void ProgressDialog::changeStepProgress(TaskStepProgressList task_progress) { + m_is_multi_step = true; + ui->taskProgressScrollArea->setHidden(false); + for (auto tp : task_progress) { - if (!taskProgress.contains(tp.uid)) - addTaskProgress(tp); - auto task_bar = taskProgress.value(tp.uid); + if (!taskProgress.contains(tp->uid)) + addTaskProgress(tp.get()); + auto task_bar = taskProgress.value(tp->uid); - if (tp.total < 0) { + if (tp->total < 0) { task_bar->setRange(0, 0); } else { - task_bar->setRange(0, map_int_range(tp.total)); + task_bar->setRange(0, map_int_range(tp->total)); } - task_bar->setValue(map_int_range(tp.current)); - task_bar->setStatus(tp.status); - task_bar->setDetails(tp.details); + task_bar->setValue(map_int_range(tp->current)); + task_bar->setStatus(tp->status); + task_bar->setDetails(tp->details); - if (tp.isDone()) { + if (tp->isDone()) { task_bar->setVisible(false); } } + + updateSize(); } void ProgressDialog::changeProgress(qint64 current, qint64 total) @@ -230,13 +246,6 @@ void ProgressDialog::changeProgress(qint64 current, qint64 total) ui->globalProgressBar->setMaximum(total); ui->globalProgressBar->setValue(current); - // if (!m_is_multi_step) { - // ui->taskProgressBar->setMaximum(total); - // ui->taskProgressBar->setValue(current); - // } else { - // ui->taskProgressBar->setMaximum(task->getStepProgress()); - // ui->taskProgressBar->setValue(task->getStepTotalProgress()); - // } } void ProgressDialog::keyPressEvent(QKeyEvent* e) diff --git a/launcher/ui/dialogs/ProgressDialog.h b/launcher/ui/dialogs/ProgressDialog.h index a7e203fb..95a4db16 100644 --- a/launcher/ui/dialogs/ProgressDialog.h +++ b/launcher/ui/dialogs/ProgressDialog.h @@ -80,7 +80,7 @@ slots: void changeStatus(const QString &status); void changeProgress(qint64 current, qint64 total); - void changeStepProgress(QList task_progress); + void changeStepProgress(TaskStepProgressList task_progress); private @@ -93,7 +93,7 @@ protected: private: bool handleImmediateResult(QDialog::DialogCode &result); - void addTaskProgress(TaskStepProgress progress); + void addTaskProgress(TaskStepProgress* progress); private: Ui::ProgressDialog *ui; diff --git a/launcher/ui/dialogs/ProgressDialog.ui b/launcher/ui/dialogs/ProgressDialog.ui index 0a998987..47597689 100644 --- a/launcher/ui/dialogs/ProgressDialog.ui +++ b/launcher/ui/dialogs/ProgressDialog.ui @@ -6,20 +6,20 @@ 0 0 - 400 - 109 + 600 + 260 - - 0 - 0 + + 1 + 1 - 400 - 0 + 600 + 260 @@ -31,43 +31,109 @@ Please wait... - - + + + + + + + + 0 + 0 + + + + + 0 + 15 + + + + Global Task Status... + + + + + + + Global Status Details... + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + true + + + 0 + 24 + + 24 - - + + - + 0 0 - - Skip + + + 0 + 100 + + + + QFrame::StyledPanel + + Qt::ScrollBarAsNeeded + + + QAbstractScrollArea::AdjustToContents + + + true + + + + + 0 + 0 + 584 + 146 + + + + + 2 + + + - - - - - + + - + 0 0 - Global Task Status... + Skip diff --git a/launcher/ui/widgets/SubTaskProgressBar.ui b/launcher/ui/widgets/SubTaskProgressBar.ui index 966fdb88..ceae5e26 100644 --- a/launcher/ui/widgets/SubTaskProgressBar.ui +++ b/launcher/ui/widgets/SubTaskProgressBar.ui @@ -6,12 +6,12 @@ 0 0 - 265 - 65 + 597 + 61 - + 0 0 @@ -20,29 +20,45 @@ Form + + 0 + - + 0 0 + + + 8 + + Sub Task Status... + + true + - + 0 0 + + + 8 + + Status Details @@ -55,6 +71,11 @@ + + + 8 + + 24 diff --git a/tests/Task_test.cpp b/tests/Task_test.cpp index 678382ba..dabe5da2 100644 --- a/tests/Task_test.cpp +++ b/tests/Task_test.cpp @@ -69,8 +69,9 @@ class BigConcurrentTaskThread : public QThread { auto sub_tasks = new BasicTask::Ptr[s_num_tasks]; for (unsigned i = 0; i < s_num_tasks; i++) { - sub_tasks[i] = makeShared(false); - big_task.addTask(sub_tasks[i]); + auto sub_task = makeShared(false); + sub_tasks[i] = sub_task; + big_task.addTask(sub_task); } big_task.run(); @@ -99,7 +100,7 @@ class TaskTest : public QObject { t.setStatus(status); QCOMPARE(t.getStatus(), status); - QCOMPARE(t.getStepProgress().isEmpty(), QList{}.isEmpty()); + QCOMPARE(t.getStepProgress().isEmpty(), TaskStepProgressList{}.isEmpty()); } void test_SetStatus_MultiStep(){ @@ -111,7 +112,7 @@ class TaskTest : public QObject { QCOMPARE(t.getStatus(), status); // Even though it is multi step, it does not override the getStepStatus method, // so it should remain the same. - QCOMPARE(t.getStepProgress().isEmpty(), QList{}.isEmpty()); + QCOMPARE(t.getStepProgress().isEmpty(), TaskStepProgressList{}.isEmpty()); } void test_SetProgress(){ -- cgit From f1028fa66d556b024765ba4e21ac76d4510a3e3e Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Fri, 31 Mar 2023 12:29:59 -0700 Subject: fix: properly map progress range - doument PCRE used for URL compacting Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- launcher/net/Download.cpp | 20 +++++++++--- launcher/tasks/ConcurrentTask.cpp | 7 +---- launcher/ui/dialogs/ProgressDialog.cpp | 56 ++++++++++++++++++---------------- launcher/ui/dialogs/ProgressDialog.ui | 21 ++++++------- 4 files changed, 55 insertions(+), 49 deletions(-) (limited to 'launcher/ui/dialogs') diff --git a/launcher/net/Download.cpp b/launcher/net/Download.cpp index a4c3ebfc..3eef9117 100644 --- a/launcher/net/Download.cpp +++ b/launcher/net/Download.cpp @@ -61,13 +61,23 @@ QString truncateUrlHumanFriendly(QUrl &url, int max_len, bool hard_limit = false if (str_url.length() <= max_len) return str_url; - QRegularExpression re(R"(^([\w]+:\/\/)([\w._-]+\/)([\w._-]+\/).*(\/[^]+[^]+)$)"); + /* this is a PCRE regular expression that splits a URL (given by the display rules above) into 5 capture groups + * the scheme (ie https://) is group 1 + * the host (with trailing /) is group 2 + * the first part of the path (with trailing /) is group 3 + * the last part of the path (with leading /) is group 5 + * the remainder of the URL is in the .* and in group 4 + * + * See: https://regex101.com/r/inHkek/1 + * for an interactive breakdown + */ + QRegularExpression re(R"(^([\w]+:\/\/)([\w._-]+\/)([\w._-]+\/)(.*)(\/[^]+[^]+)$)"); auto url_compact = QString(str_url); - url_compact.replace(re, "\\1\\2\\3...\\4"); + url_compact.replace(re, "\\1\\2\\3...\\5"); if (url_compact.length() >= max_len) { - auto url_compact = QString(str_url); - url_compact.replace(re, "\\1\\2...\\4"); + url_compact = QString(str_url); + url_compact.replace(re, "\\1\\2...\\5"); } @@ -120,7 +130,7 @@ void Download::addValidator(Validator* v) void Download::executeTask() { - setStatus(tr("Downloading %1").arg(truncateUrlHumanFriendly(m_url, 60))); + setStatus(tr("Downloading %1").arg(truncateUrlHumanFriendly(m_url, 100))); if (getState() == Task::State::AbortedByUser) { qCWarning(DownloadLogC) << getUid().toString() << "Attempt to start an aborted Download:" << m_url.toString(); diff --git a/launcher/tasks/ConcurrentTask.cpp b/launcher/tasks/ConcurrentTask.cpp index fde7d0ad..41c405db 100644 --- a/launcher/tasks/ConcurrentTask.cpp +++ b/launcher/tasks/ConcurrentTask.cpp @@ -28,12 +28,7 @@ void ConcurrentTask::addTask(Task::Ptr task) void ConcurrentTask::executeTask() { - // Start the least amount of tasks needed, but at least one - // int num_starts = qMax(1, qMin(m_total_max_size, m_queue.size())); - // for (int i = 0; i < num_starts; i++) { - // QMetaObject::invokeMethod(this, &ConcurrentTask::startNext, Qt::QueuedConnection); - // } - // Start One task, startNext hadles starting the up to the m_total_max_size + // Start One task, startNext hadels starting the up to the m_total_max_size // while tracking the number currently being done QMetaObject::invokeMethod(this, &ConcurrentTask::startNext, Qt::QueuedConnection); } diff --git a/launcher/ui/dialogs/ProgressDialog.cpp b/launcher/ui/dialogs/ProgressDialog.cpp index f7a3a862..7ab766e4 100644 --- a/launcher/ui/dialogs/ProgressDialog.cpp +++ b/launcher/ui/dialogs/ProgressDialog.cpp @@ -45,21 +45,18 @@ #include "ui/widgets/SubTaskProgressBar.h" -template -int map_int_range(T value) +// map a value in a numaric range of an arbatray type to between 0 and INT_MAX +// for getting the best percision out of the qt progress bar +template::value, T>::type> +std::tuple map_int_zero_max(T current, T range_max, T range_min) { - // auto type_min = std::numeric_limits::min(); - auto type_min = 0; - auto type_max = std::numeric_limits::max(); + int int_max = std::numeric_limits::max(); - // auto int_min = std::numeric_limits::min(); - auto int_min = 0; - auto int_max = std::numeric_limits::max(); + auto type_range = range_max - range_min; + double percentage = static_cast(current - range_min) / static_cast(type_range); + int mapped_current = percentage * int_max; - auto type_range = type_max - type_min; - auto int_range = int_max - int_min; - - return static_cast((value - type_min) * int_range / type_range + int_min); + return {mapped_current, int_max}; } @@ -100,14 +97,21 @@ void ProgressDialog::updateSize() { QSize lastSize = this->size(); QSize qSize = QSize(480, minimumSizeHint().height()); - resize(qSize); - setFixedSize(qSize); - // keep the dialog in the center after a resize - if (lastSize != qSize) + + // if the current window is too small + if ((lastSize != qSize) && (lastSize.height() < qSize.height())) + { + resize(qSize); + + // keep the dialog in the center after a resize this->move( this->parentWidget()->x() + (this->parentWidget()->width() - this->width()) / 2, this->parentWidget()->y() + (this->parentWidget()->height() - this->height()) / 2 ); + } + + setMinimumSize(qSize); + } int ProgressDialog::execWithTask(Task* task) @@ -147,9 +151,6 @@ int ProgressDialog::execWithTask(Task* task) changeProgress(task->getProgress(), task->getTotalProgress()); } - // auto size_hint = ui->verticalLayout->sizeHint(); - // resize(size_hint.width(), size_hint.height()); - return QDialog::exec(); } @@ -209,26 +210,31 @@ void ProgressDialog::addTaskProgress(TaskStepProgress* progress) { SubTaskProgressBar* task_bar = new SubTaskProgressBar(this); taskProgress.insert(progress->uid, task_bar); - ui->taskProgressLayout->insertWidget(0, task_bar); + ui->taskProgressLayout->addWidget(task_bar); } void ProgressDialog::changeStepProgress(TaskStepProgressList task_progress) { m_is_multi_step = true; - ui->taskProgressScrollArea->setHidden(false); + if(ui->taskProgressScrollArea->isHidden()) { + ui->taskProgressScrollArea->setHidden(false); + updateSize(); + } for (auto tp : task_progress) { if (!taskProgress.contains(tp->uid)) addTaskProgress(tp.get()); auto task_bar = taskProgress.value(tp->uid); - if (tp->total < 0) { + + auto const [mapped_current, mapped_total] = map_int_zero_max(tp->current, tp->total, 0); + if (tp->total <= 0) { task_bar->setRange(0, 0); } else { - task_bar->setRange(0, map_int_range(tp->total)); + task_bar->setRange(0, mapped_total); } - task_bar->setValue(map_int_range(tp->current)); + task_bar->setValue(mapped_current); task_bar->setStatus(tp->status); task_bar->setDetails(tp->details); @@ -237,8 +243,6 @@ void ProgressDialog::changeStepProgress(TaskStepProgressList task_progress) } } - - updateSize(); } void ProgressDialog::changeProgress(qint64 current, qint64 total) diff --git a/launcher/ui/dialogs/ProgressDialog.ui b/launcher/ui/dialogs/ProgressDialog.ui index 47597689..a4d08124 100644 --- a/launcher/ui/dialogs/ProgressDialog.ui +++ b/launcher/ui/dialogs/ProgressDialog.ui @@ -6,8 +6,8 @@ 0 0 - 600 - 260 + 480 + 210 @@ -18,19 +18,16 @@ - 600 - 260 - - - - - 600 - 16777215 + 480 + 210 Please wait... + + true + @@ -112,8 +109,8 @@ 0 0 - 584 - 146 + 464 + 96 -- cgit From b6452215c16f6b1ee45fea746f9498767e48d049 Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Fri, 31 Mar 2023 19:25:01 -0700 Subject: feat: add `details` signal to `Task` feat: add details to mod pack downloading feat: add logging rule sloading form `ligging.ini at data path root feat: add `launcher.task` `launcher.task.net` and `launcher.task.net.[down|up]load` logging categories fix: add new subtask progress to the end of the lay out not the beginning (cuts down on flickering) Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- launcher/Application.cpp | 19 +++++++++ launcher/CMakeLists.txt | 33 +++++++++++++++ launcher/InstanceImportTask.cpp | 2 + launcher/InstanceList.cpp | 1 + launcher/java/JavaInstallList.cpp | 1 + launcher/launch/steps/Update.cpp | 1 + launcher/minecraft/MinecraftUpdate.cpp | 2 + .../modplatform/atlauncher/ATLPackInstallTask.cpp | 3 +- .../flame/FlameInstanceCreationTask.cpp | 7 +++- .../modrinth/ModrinthInstanceCreationTask.cpp | 7 +++- launcher/net/Download.cpp | 47 +++++++++++----------- launcher/net/Download.h | 3 -- launcher/net/FileSink.cpp | 10 +++-- launcher/net/HttpMetaCache.cpp | 16 ++++---- launcher/net/MetaCacheSink.cpp | 10 +++-- launcher/net/PasteUpload.cpp | 22 +++++----- launcher/net/Upload.cpp | 38 ++++++++--------- launcher/net/logging.cpp | 24 +++++++++++ launcher/net/logging.h | 26 ++++++++++++ launcher/tasks/ConcurrentTask.cpp | 11 ++++- launcher/tasks/ConcurrentTask.h | 1 + launcher/tasks/Task.cpp | 42 ++++++++++++------- launcher/tasks/Task.h | 8 +++- launcher/ui/dialogs/ProgressDialog.cpp | 2 +- launcher/ui/widgets/ProgressWidget.cpp | 1 + launcher/ui/widgets/SubTaskProgressBar.ui | 7 +++- 26 files changed, 249 insertions(+), 95 deletions(-) create mode 100644 launcher/net/logging.cpp create mode 100644 launcher/net/logging.h (limited to 'launcher/ui/dialogs') diff --git a/launcher/Application.cpp b/launcher/Application.cpp index a7c97aa7..c8855cbc 100644 --- a/launcher/Application.cpp +++ b/launcher/Application.cpp @@ -46,6 +46,7 @@ #include "net/PasteUpload.h" #include "pathmatcher/MultiMatcher.h" #include "pathmatcher/SimplePrefixMatcher.h" +#include "settings/INIFile.h" #include "ui/MainWindow.h" #include "ui/InstanceWindow.h" @@ -410,6 +411,24 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv) " " "|" " " "%{if-category}[%{category}]: %{endif}" "%{message}"); + + if(QFile::exists("logging.ini")) { + // load and set logging rules + qDebug() << "Loading logging rules from:" << QString("%1/logging.ini").arg(dataPath); + INIFile loggingRules; + bool rulesLoaded = loggingRules.loadFile(QString("logging.ini")); + if (rulesLoaded) { + QStringList rules; + qDebug() << "Setting log rules:"; + for (auto it = loggingRules.begin(); it != loggingRules.end(); ++it) { + auto rule = it.key() + "=" + it.value().toString(); + rules.append(rule); + qDebug() << " " << rule; + } + auto rules_str = rules.join("\n"); + QLoggingCategory::setFilterRules(rules_str); + } + } qDebug() << "<> Log initialized."; } diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index 24330adf..e49a4562 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -123,6 +123,8 @@ set(NET_SOURCES net/HttpMetaCache.h net/MetaCacheSink.cpp net/MetaCacheSink.h + net/logging.h + net/logging.cpp net/NetAction.h net/NetJob.cpp net/NetJob.h @@ -563,6 +565,37 @@ ecm_qt_declare_logging_category(CORE_SOURCES EXPORT "${Launcher_Name}" ) +ecm_qt_export_logging_category( + IDENTIFIER taskLogC + CATEGORY_NAME "launcher.task" + DEFAULT_SEVERITY Debug + DESCRIPTION "Task actions" + EXPORT "${Launcher_Name}" +) + +ecm_qt_export_logging_category( + IDENTIFIER taskNetLogC + CATEGORY_NAME "launcher.task.net" + DEFAULT_SEVERITY Debug + DESCRIPTION "task network action" + EXPORT "${Launcher_Name}" +) + +ecm_qt_export_logging_category( + IDENTIFIER taskDownloadLogC + CATEGORY_NAME "launcher.task.net.download" + DEFAULT_SEVERITY Debug + DESCRIPTION "task network download actions" + EXPORT "${Launcher_Name}" +) +ecm_qt_export_logging_category( + IDENTIFIER taskUploadLogC + CATEGORY_NAME "launcher.task.net.upload" + DEFAULT_SEVERITY Debug + DESCRIPTION "task network upload actions" + EXPORT "${Launcher_Name}" +) + if(KDE_INSTALL_LOGGINGCATEGORIESDIR) # only install if there is a standard path for this ecm_qt_install_logging_categories( EXPORT "${Launcher_Name}" diff --git a/launcher/InstanceImportTask.cpp b/launcher/InstanceImportTask.cpp index c196396d..8a48873e 100644 --- a/launcher/InstanceImportTask.cpp +++ b/launcher/InstanceImportTask.cpp @@ -294,6 +294,7 @@ void InstanceImportTask::processFlame() connect(inst_creation_task, &Task::progress, this, &InstanceImportTask::setProgress); connect(inst_creation_task, &Task::stepProgress, this, &InstanceImportTask::propogateStepProgress); connect(inst_creation_task, &Task::status, this, &InstanceImportTask::setStatus); + connect(inst_creation_task, &Task::details, this, &InstanceImportTask::setDetails); connect(inst_creation_task, &Task::finished, inst_creation_task, &InstanceCreationTask::deleteLater); connect(this, &Task::aborted, inst_creation_task, &InstanceCreationTask::abort); @@ -386,6 +387,7 @@ void InstanceImportTask::processModrinth() connect(inst_creation_task, &Task::progress, this, &InstanceImportTask::setProgress); connect(inst_creation_task, &Task::stepProgress, this, &InstanceImportTask::propogateStepProgress); connect(inst_creation_task, &Task::status, this, &InstanceImportTask::setStatus); + connect(inst_creation_task, &Task::details, this, &InstanceImportTask::setDetails); connect(inst_creation_task, &Task::finished, inst_creation_task, &InstanceCreationTask::deleteLater); connect(this, &Task::aborted, inst_creation_task, &InstanceCreationTask::abort); diff --git a/launcher/InstanceList.cpp b/launcher/InstanceList.cpp index dbc891ff..5f98a184 100644 --- a/launcher/InstanceList.cpp +++ b/launcher/InstanceList.cpp @@ -787,6 +787,7 @@ class InstanceStaging : public Task { connect(child, &Task::aborted, this, &InstanceStaging::childAborted); connect(child, &Task::abortStatusChanged, this, &InstanceStaging::setAbortable); connect(child, &Task::status, this, &InstanceStaging::setStatus); + connect(child, &Task::details, this, &InstanceStaging::setDetails); connect(child, &Task::progress, this, &InstanceStaging::setProgress); connect(child, &Task::stepProgress, this, &InstanceStaging::propogateStepProgress); connect(&m_backoffTimer, &QTimer::timeout, this, &InstanceStaging::childSucceded); diff --git a/launcher/java/JavaInstallList.cpp b/launcher/java/JavaInstallList.cpp index b29af857..5f133622 100644 --- a/launcher/java/JavaInstallList.cpp +++ b/launcher/java/JavaInstallList.cpp @@ -170,6 +170,7 @@ void JavaListLoadTask::executeTask() m_job.reset(new JavaCheckerJob("Java detection")); connect(m_job.get(), &Task::finished, this, &JavaListLoadTask::javaCheckerFinished); connect(m_job.get(), &Task::progress, this, &Task::setProgress); + // stepProgress? qDebug() << "Probing the following Java paths: "; int id = 0; diff --git a/launcher/launch/steps/Update.cpp b/launcher/launch/steps/Update.cpp index 1640d115..c8e576a5 100644 --- a/launcher/launch/steps/Update.cpp +++ b/launcher/launch/steps/Update.cpp @@ -30,6 +30,7 @@ void Update::executeTask() connect(m_updateTask.get(), &Task::progress, this, &Update::setProgress); connect(m_updateTask.get(), &Task::stepProgress, this, &Update::propogateStepProgress); connect(m_updateTask.get(), &Task::status, this, &Update::setStatus); + connect(m_updateTask.get(), &Task::details, this, &Update::setDetails); emit progressReportingRequest(); return; } diff --git a/launcher/minecraft/MinecraftUpdate.cpp b/launcher/minecraft/MinecraftUpdate.cpp index 3ce808f8..35430bb0 100644 --- a/launcher/minecraft/MinecraftUpdate.cpp +++ b/launcher/minecraft/MinecraftUpdate.cpp @@ -102,6 +102,7 @@ void MinecraftUpdate::next() disconnect(task.get(), &Task::progress, this, &MinecraftUpdate::progress); disconnect(task.get(), &Task::stepProgress, this, &MinecraftUpdate::propogateStepProgress); disconnect(task.get(), &Task::status, this, &MinecraftUpdate::setStatus); + disconnect(task.get(), &Task::details, this, &MinecraftUpdate::setDetails); } if(m_currentTask == m_tasks.size()) { @@ -121,6 +122,7 @@ void MinecraftUpdate::next() connect(task.get(), &Task::progress, this, &MinecraftUpdate::progress); connect(task.get(), &Task::stepProgress, this, &MinecraftUpdate::propogateStepProgress); connect(task.get(), &Task::status, this, &MinecraftUpdate::setStatus); + connect(task.get(), &Task::details, this, &MinecraftUpdate::setDetails); // if the task is already running, do not start it again if(!task->isRunning()) { diff --git a/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp b/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp index 28026732..d130914f 100644 --- a/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp +++ b/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp @@ -846,7 +846,8 @@ void PackInstallTask::downloadMods() emitFailed(reason); }); connect(jobPtr.get(), &NetJob::progress, [&](qint64 current, qint64 total) - { + { + setDetails(tr("%1 out of %2 complete").arg(current).arg(total)); abortable = true; setProgress(current, total); }); diff --git a/launcher/modplatform/flame/FlameInstanceCreationTask.cpp b/launcher/modplatform/flame/FlameInstanceCreationTask.cpp index 3cb6b61a..86fd2ab4 100644 --- a/launcher/modplatform/flame/FlameInstanceCreationTask.cpp +++ b/launcher/modplatform/flame/FlameInstanceCreationTask.cpp @@ -453,7 +453,7 @@ void FlameCreationTask::idResolverSucceeded(QEventLoop& loop) void FlameCreationTask::setupDownloadJob(QEventLoop& loop) { - m_files_job.reset(new NetJob(tr("Mod download"), APPLICATION->network())); + m_files_job.reset(new NetJob(tr("Mod Download Flame"), APPLICATION->network())); for (const auto& result : m_mod_id_resolver->getResults().files) { QString filename = result.fileName; if (!result.required) { @@ -497,7 +497,10 @@ void FlameCreationTask::setupDownloadJob(QEventLoop& loop) m_files_job.reset(); setError(reason); }); - connect(m_files_job.get(), &NetJob::progress, this, &FlameCreationTask::setProgress); + connect(m_files_job.get(), &NetJob::progress, this, [this](qint64 current, qint64 total){ + setDetails(tr("%1 out of %2 complete").arg(current).arg(total)); + setProgress(current, total); + }); connect(m_files_job.get(), &NetJob::stepProgress, this, &FlameCreationTask::propogateStepProgress); connect(m_files_job.get(), &NetJob::finished, &loop, &QEventLoop::quit); diff --git a/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp b/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp index 2fb656ea..bb8227aa 100644 --- a/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp +++ b/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp @@ -224,7 +224,7 @@ bool ModrinthCreationTask::createInstance() instance.setName(name()); instance.saveNow(); - m_files_job.reset(new NetJob(tr("Mod download"), APPLICATION->network())); + m_files_job.reset(new NetJob(tr("Mod Download Modrinth"), APPLICATION->network())); auto root_modpack_path = FS::PathCombine(m_stagingPath, ".minecraft"); auto root_modpack_url = QUrl::fromLocalFile(root_modpack_path); @@ -263,7 +263,10 @@ bool ModrinthCreationTask::createInstance() setError(reason); }); connect(m_files_job.get(), &NetJob::finished, &loop, &QEventLoop::quit); - connect(m_files_job.get(), &NetJob::progress, [&](qint64 current, qint64 total) { setProgress(current, total); }); + connect(m_files_job.get(), &NetJob::progress, [&](qint64 current, qint64 total) { + setDetails(tr("%1 out of %2 complete").arg(current).arg(total)); + setProgress(current, total); + }); connect(m_files_job.get(), &NetJob::stepProgress, this, &ModrinthCreationTask::propogateStepProgress); setStatus(tr("Downloading mods...")); diff --git a/launcher/net/Download.cpp b/launcher/net/Download.cpp index 3eef9117..86e4bd97 100644 --- a/launcher/net/Download.cpp +++ b/launcher/net/Download.cpp @@ -4,6 +4,7 @@ * Copyright (c) 2022 flowln * Copyright (C) 2022 Sefa Eyeoglu * Copyright (C) 2023 TheKodeToad + * Copyright (C) 2023 Rachel Powers <508861+Ryex@users.noreply.github.com> * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -50,7 +51,7 @@ #include "BuildConfig.h" #include "Application.h" -Q_LOGGING_CATEGORY(DownloadLogC, "Task.Net.Download") +#include "logging.h" namespace Net { @@ -133,7 +134,7 @@ void Download::executeTask() setStatus(tr("Downloading %1").arg(truncateUrlHumanFriendly(m_url, 100))); if (getState() == Task::State::AbortedByUser) { - qCWarning(DownloadLogC) << getUid().toString() << "Attempt to start an aborted Download:" << m_url.toString(); + qCWarning(taskDownloadLogC) << getUid().toString() << "Attempt to start an aborted Download:" << m_url.toString(); emitAborted(); return; } @@ -143,10 +144,10 @@ void Download::executeTask() switch (m_state) { case State::Succeeded: emit succeeded(); - qCDebug(DownloadLogC) << getUid().toString() << "Download cache hit " << m_url.toString(); + qCDebug(taskDownloadLogC) << getUid().toString() << "Download cache hit " << m_url.toString(); return; case State::Running: - qCDebug(DownloadLogC) << getUid().toString() << "Downloading " << m_url.toString(); + qCDebug(taskDownloadLogC) << getUid().toString() << "Downloading " << m_url.toString(); break; case State::Inactive: case State::Failed: @@ -192,9 +193,9 @@ void Download::downloadProgress(qint64 bytesReceived, qint64 bytesTotal) auto elapsed_ms = std::chrono::duration_cast(elapsed).count(); auto bytes_recived_since = bytesReceived - m_last_progress_bytes; if (elapsed_ms > 0) { - m_details = humanReadableFileSize(bytes_recived_since / elapsed_ms * 1000) + "/s"; + setDetails(humanReadableFileSize(bytes_recived_since / elapsed_ms * 1000) + "/s"); } else { - m_details = "0 b/s"; + setDetails("0 b/s"); } setProgress(bytesReceived, bytesTotal); @@ -203,7 +204,7 @@ void Download::downloadProgress(qint64 bytesReceived, qint64 bytesTotal) void Download::downloadError(QNetworkReply::NetworkError error) { if (error == QNetworkReply::OperationCanceledError) { - qCCritical(DownloadLogC) << getUid().toString() << "Aborted " << m_url.toString(); + qCCritical(taskDownloadLogC) << getUid().toString() << "Aborted " << m_url.toString(); m_state = State::AbortedByUser; } else { if (m_options & Option::AcceptLocalFiles) { @@ -213,7 +214,7 @@ void Download::downloadError(QNetworkReply::NetworkError error) } } // error happened during download. - qCCritical(DownloadLogC) << getUid().toString() << "Failed " << m_url.toString() << " with reason " << error; + qCCritical(taskDownloadLogC) << getUid().toString() << "Failed " << m_url.toString() << " with reason " << error; m_state = State::Failed; } } @@ -222,9 +223,9 @@ void Download::sslErrors(const QList& errors) { int i = 1; for (auto error : errors) { - qCCritical(DownloadLogC) << getUid().toString() << "Download" << m_url.toString() << "SSL Error #" << i << " : " << error.errorString(); + qCCritical(taskDownloadLogC) << getUid().toString() << "Download" << m_url.toString() << "SSL Error #" << i << " : " << error.errorString(); auto cert = error.certificate(); - qCCritical(DownloadLogC) << getUid().toString() << "Certificate in question:\n" << cert.toText(); + qCCritical(taskDownloadLogC) << getUid().toString() << "Certificate in question:\n" << cert.toText(); i++; } } @@ -267,17 +268,17 @@ auto Download::handleRedirect() -> bool */ redirect = QUrl(redirectStr, QUrl::TolerantMode); if (!redirect.isValid()) { - qCWarning(DownloadLogC) << getUid().toString() << "Failed to parse redirect URL:" << redirectStr; + qCWarning(taskDownloadLogC) << getUid().toString() << "Failed to parse redirect URL:" << redirectStr; downloadError(QNetworkReply::ProtocolFailure); return false; } - qCDebug(DownloadLogC) << getUid().toString() << "Fixed location header:" << redirect; + qCDebug(taskDownloadLogC) << getUid().toString() << "Fixed location header:" << redirect; } else { - qCDebug(DownloadLogC) << getUid().toString() << "Location header:" << redirect; + qCDebug(taskDownloadLogC) << getUid().toString() << "Location header:" << redirect; } m_url = QUrl(redirect.toString()); - qCDebug(DownloadLogC) << getUid().toString() << "Following redirect to " << m_url.toString(); + qCDebug(taskDownloadLogC) << getUid().toString() << "Following redirect to " << m_url.toString(); startAction(m_network); return true; @@ -287,26 +288,26 @@ void Download::downloadFinished() { // handle HTTP redirection first if (handleRedirect()) { - qCDebug(DownloadLogC) << getUid().toString() << "Download redirected:" << m_url.toString(); + qCDebug(taskDownloadLogC) << getUid().toString() << "Download redirected:" << m_url.toString(); return; } // if the download failed before this point ... if (m_state == State::Succeeded) // pretend to succeed so we continue processing :) { - qCDebug(DownloadLogC) << getUid().toString() << "Download failed but we are allowed to proceed:" << m_url.toString(); + qCDebug(taskDownloadLogC) << getUid().toString() << "Download failed but we are allowed to proceed:" << m_url.toString(); m_sink->abort(); m_reply.reset(); emit succeeded(); return; } else if (m_state == State::Failed) { - qCDebug(DownloadLogC) << getUid().toString() << "Download failed in previous step:" << m_url.toString(); + qCDebug(taskDownloadLogC) << getUid().toString() << "Download failed in previous step:" << m_url.toString(); m_sink->abort(); m_reply.reset(); emit failed(""); return; } else if (m_state == State::AbortedByUser) { - qCDebug(DownloadLogC) << getUid().toString() << "Download aborted in previous step:" << m_url.toString(); + qCDebug(taskDownloadLogC) << getUid().toString() << "Download aborted in previous step:" << m_url.toString(); m_sink->abort(); m_reply.reset(); emit aborted(); @@ -316,14 +317,14 @@ void Download::downloadFinished() // make sure we got all the remaining data, if any auto data = m_reply->readAll(); if (data.size()) { - qCDebug(DownloadLogC) << getUid().toString() << "Writing extra" << data.size() << "bytes"; + qCDebug(taskDownloadLogC) << getUid().toString() << "Writing extra" << data.size() << "bytes"; m_state = m_sink->write(data); } // otherwise, finalize the whole graph m_state = m_sink->finalize(*m_reply.get()); if (m_state != State::Succeeded) { - qCDebug(DownloadLogC) << getUid().toString() << "Download failed to finalize:" << m_url.toString(); + qCDebug(taskDownloadLogC) << getUid().toString() << "Download failed to finalize:" << m_url.toString(); m_sink->abort(); m_reply.reset(); emit failed(""); @@ -331,7 +332,7 @@ void Download::downloadFinished() } m_reply.reset(); - qCDebug(DownloadLogC) << getUid().toString() << "Download succeeded:" << m_url.toString(); + qCDebug(taskDownloadLogC) << getUid().toString() << "Download succeeded:" << m_url.toString(); emit succeeded(); } @@ -341,11 +342,11 @@ void Download::downloadReadyRead() auto data = m_reply->readAll(); m_state = m_sink->write(data); if (m_state == State::Failed) { - qCCritical(DownloadLogC) << getUid().toString() << "Failed to process response chunk"; + qCCritical(taskDownloadLogC) << getUid().toString() << "Failed to process response chunk"; } // qDebug() << "Download" << m_url.toString() << "gained" << data.size() << "bytes"; } else { - qCCritical(DownloadLogC) << getUid().toString() << "Cannot write download data! illegal status " << m_status; + qCCritical(taskDownloadLogC) << getUid().toString() << "Cannot write download data! illegal status " << m_status; } } diff --git a/launcher/net/Download.h b/launcher/net/Download.h index cbee0d03..ae2b034c 100644 --- a/launcher/net/Download.h +++ b/launcher/net/Download.h @@ -66,7 +66,6 @@ class Download : public NetAction { void addValidator(Validator* v); auto abort() -> bool override; auto canAbort() const -> bool override { return true; }; - auto getDetails() const -> QString override {return m_details; }; private: auto handleRedirect() -> bool; @@ -88,8 +87,6 @@ class Download : public NetAction { std::chrono::steady_clock m_clock; std::chrono::time_point m_last_progress_time; qint64 m_last_progress_bytes; - - QString m_details; }; } // namespace Net diff --git a/launcher/net/FileSink.cpp b/launcher/net/FileSink.cpp index ba0caf6c..3c2948d4 100644 --- a/launcher/net/FileSink.cpp +++ b/launcher/net/FileSink.cpp @@ -37,6 +37,8 @@ #include "FileSystem.h" +#include "logging.h" + namespace Net { Task::State FileSink::init(QNetworkRequest& request) @@ -48,14 +50,14 @@ Task::State FileSink::init(QNetworkRequest& request) // create a new save file and open it for writing if (!FS::ensureFilePathExists(m_filename)) { - qCritical() << "Could not create folder for " + m_filename; + qCCritical(taskNetLogC) << "Could not create folder for " + m_filename; return Task::State::Failed; } wroteAnyData = false; m_output_file.reset(new QSaveFile(m_filename)); if (!m_output_file->open(QIODevice::WriteOnly)) { - qCritical() << "Could not open " + m_filename + " for writing"; + qCCritical(taskNetLogC) << "Could not open " + m_filename + " for writing"; return Task::State::Failed; } @@ -67,7 +69,7 @@ Task::State FileSink::init(QNetworkRequest& request) Task::State FileSink::write(QByteArray& data) { if (!writeAllValidators(data) || m_output_file->write(data) != data.size()) { - qCritical() << "Failed writing into " + m_filename; + qCCritical(taskNetLogC) << "Failed writing into " + m_filename; m_output_file->cancelWriting(); m_output_file.reset(); wroteAnyData = false; @@ -106,7 +108,7 @@ Task::State FileSink::finalize(QNetworkReply& reply) // nothing went wrong... if (!m_output_file->commit()) { - qCritical() << "Failed to commit changes to " << m_filename; + qCCritical(taskNetLogC) << "Failed to commit changes to " << m_filename; m_output_file->cancelWriting(); return Task::State::Failed; } diff --git a/launcher/net/HttpMetaCache.cpp b/launcher/net/HttpMetaCache.cpp index 0d7ca769..855211f7 100644 --- a/launcher/net/HttpMetaCache.cpp +++ b/launcher/net/HttpMetaCache.cpp @@ -44,6 +44,8 @@ #include +#include "logging.h" + auto MetaEntry::getFullPath() -> QString { // FIXME: make local? @@ -124,7 +126,7 @@ auto HttpMetaCache::resolveEntry(QString base, QString resource_path, QString ex // Get rid of old entries, to prevent cache problems auto current_time = QDateTime::currentSecsSinceEpoch(); if (entry->isExpired(current_time - ( file_last_changed / 1000 ))) { - qWarning() << "Removing cache entry because of old age!"; + qCWarning(taskNetLogC) << "[HttpMetaCache]" << "Removing cache entry because of old age!"; selected_base.entry_list.remove(resource_path); return staleEntry(base, resource_path); } @@ -137,12 +139,12 @@ auto HttpMetaCache::resolveEntry(QString base, QString resource_path, QString ex auto HttpMetaCache::updateEntry(MetaEntryPtr stale_entry) -> bool { if (!m_entries.contains(stale_entry->m_baseId)) { - qCritical() << "Cannot add entry with unknown base: " << stale_entry->m_baseId.toLocal8Bit(); + qCCritical(taskNetLogC) << "[HttpMetaCache]" << "Cannot add entry with unknown base: " << stale_entry->m_baseId.toLocal8Bit(); return false; } if (stale_entry->m_stale) { - qCritical() << "Cannot add stale entry: " << stale_entry->getFullPath().toLocal8Bit(); + qCCritical(taskNetLogC) << "[HttpMetaCache]" << "Cannot add stale entry: " << stale_entry->getFullPath().toLocal8Bit(); return false; } @@ -166,10 +168,10 @@ void HttpMetaCache::evictAll() { for (QString& base : m_entries.keys()) { EntryMap& map = m_entries[base]; - qDebug() << "Evicting base" << base; + qCDebug(taskNetLogC) << "[HttpMetaCache]" << "Evicting base" << base; for (MetaEntryPtr entry : map.entry_list) { if (!evictEntry(entry)) - qWarning() << "Unexpected missing cache entry" << entry->m_basePath; + qCWarning(taskNetLogC) << "[HttpMetaCache]" << "Unexpected missing cache entry" << entry->m_basePath; } } } @@ -267,7 +269,7 @@ void HttpMetaCache::SaveNow() if (m_index_file.isNull()) return; - qDebug() << "[HttpMetaCache]" << "Saving metacache with" << m_entries.size() << "entries"; + qCDebug(taskNetLogC) << "[HttpMetaCache]" << "Saving metacache with" << m_entries.size() << "entries"; QJsonObject toplevel; Json::writeString(toplevel, "version", "1"); @@ -302,6 +304,6 @@ void HttpMetaCache::SaveNow() try { Json::write(toplevel, m_index_file); } catch (const Exception& e) { - qWarning() << e.what(); + qCWarning(taskNetLogC) << "[HttpMetaCache]" << e.what(); } } diff --git a/launcher/net/MetaCacheSink.cpp b/launcher/net/MetaCacheSink.cpp index c730fdbf..46bfe37d 100644 --- a/launcher/net/MetaCacheSink.cpp +++ b/launcher/net/MetaCacheSink.cpp @@ -39,6 +39,8 @@ #include #include "Application.h" +#include "logging.h" + namespace Net { /** Maximum time to hold a cache entry @@ -97,11 +99,11 @@ Task::State MetaCacheSink::finalizeCache(QNetworkReply & reply) { // Cache lifetime if (m_is_eternal) { - qDebug() << "[MetaCache] Adding eternal cache entry:" << m_entry->getFullPath(); + qCDebug(taskNetLogC) << "[MetaCache] Adding eternal cache entry:" << m_entry->getFullPath(); m_entry->makeEternal(true); } else if (reply.hasRawHeader("Cache-Control")) { auto cache_control_header = reply.rawHeader("Cache-Control"); - // qDebug() << "[MetaCache] Parsing 'Cache-Control' header with" << cache_control_header; + // qCDebug(taskNetLogC) << "[MetaCache] Parsing 'Cache-Control' header with" << cache_control_header; QRegularExpression max_age_expr("max-age=([0-9]+)"); qint64 max_age = max_age_expr.match(cache_control_header).captured(1).toLongLong(); @@ -109,7 +111,7 @@ Task::State MetaCacheSink::finalizeCache(QNetworkReply & reply) } else if (reply.hasRawHeader("Expires")) { auto expires_header = reply.rawHeader("Expires"); - // qDebug() << "[MetaCache] Parsing 'Expires' header with" << expires_header; + // qCDebug(taskNetLogC) << "[MetaCache] Parsing 'Expires' header with" << expires_header; qint64 max_age = QDateTime::fromString(expires_header).toSecsSinceEpoch() - QDateTime::currentSecsSinceEpoch(); m_entry->setMaximumAge(max_age); @@ -119,7 +121,7 @@ Task::State MetaCacheSink::finalizeCache(QNetworkReply & reply) if (reply.hasRawHeader("Age")) { auto age_header = reply.rawHeader("Age"); - // qDebug() << "[MetaCache] Parsing 'Age' header with" << age_header; + // qCDebug(taskNetLogC) << "[MetaCache] Parsing 'Age' header with" << age_header; qint64 current_age = age_header.toLongLong(); m_entry->setCurrentAge(current_age); diff --git a/launcher/net/PasteUpload.cpp b/launcher/net/PasteUpload.cpp index d9e785c5..d5df3799 100644 --- a/launcher/net/PasteUpload.cpp +++ b/launcher/net/PasteUpload.cpp @@ -47,6 +47,8 @@ #include #include +#include "logging.h" + std::array PasteUpload::PasteTypes = { {{"0x0.st", "https://0x0.st", ""}, {"hastebin", "https://hst.sh", "/documents"}, @@ -147,7 +149,7 @@ void PasteUpload::executeTask() void PasteUpload::downloadError(QNetworkReply::NetworkError error) { // error happened during download. - qCritical() << "Network error: " << error; + qCCritical(taskUploadLogC) << "Network error: " << error; emitFailed(m_reply->errorString()); } @@ -166,7 +168,7 @@ void PasteUpload::downloadFinished() { QString reasonPhrase = m_reply->attribute(QNetworkRequest::HttpReasonPhraseAttribute).toString(); emitFailed(tr("Error: %1 returned unexpected status code %2 %3").arg(m_uploadUrl).arg(statusCode).arg(reasonPhrase)); - qCritical() << m_uploadUrl << " returned unexpected status code " << statusCode << " with body: " << data; + qCCritical(taskUploadLogC) << m_uploadUrl << " returned unexpected status code " << statusCode << " with body: " << data; m_reply.reset(); return; } @@ -187,7 +189,7 @@ void PasteUpload::downloadFinished() else { emitFailed(tr("Error: %1 returned a malformed response body").arg(m_uploadUrl)); - qCritical() << m_uploadUrl << " returned malformed response body: " << data; + qCCritical(taskUploadLogC) << m_uploadUrl << " returned malformed response body: " << data; return; } break; @@ -206,15 +208,15 @@ void PasteUpload::downloadFinished() { QString error = jsonObj["error"].toString(); emitFailed(tr("Error: %1 returned an error: %2").arg(m_uploadUrl, error)); - qCritical() << m_uploadUrl << " returned error: " << error; - qCritical() << "Response body: " << data; + qCCritical(taskUploadLogC) << m_uploadUrl << " returned error: " << error; + qCCritical(taskUploadLogC) << "Response body: " << data; return; } } else { emitFailed(tr("Error: %1 returned a malformed response body").arg(m_uploadUrl)); - qCritical() << m_uploadUrl << " returned malformed response body: " << data; + qCCritical(taskUploadLogC) << m_uploadUrl << " returned malformed response body: " << data; return; } break; @@ -234,16 +236,16 @@ void PasteUpload::downloadFinished() QString error = jsonObj["error"].toString(); QString message = (jsonObj.contains("message") && jsonObj["message"].isString()) ? jsonObj["message"].toString() : "none"; emitFailed(tr("Error: %1 returned an error code: %2\nError message: %3").arg(m_uploadUrl, error, message)); - qCritical() << m_uploadUrl << " returned error: " << error; - qCritical() << "Error message: " << message; - qCritical() << "Response body: " << data; + qCCritical(taskUploadLogC) << m_uploadUrl << " returned error: " << error; + qCCritical(taskUploadLogC) << "Error message: " << message; + qCCritical(taskUploadLogC) << "Response body: " << data; return; } } else { emitFailed(tr("Error: %1 returned a malformed response body").arg(m_uploadUrl)); - qCritical() << m_uploadUrl << " returned malformed response body: " << data; + qCCritical(taskUploadLogC) << m_uploadUrl << " returned malformed response body: " << data; return; } break; diff --git a/launcher/net/Upload.cpp b/launcher/net/Upload.cpp index ccf43c2d..518e302c 100644 --- a/launcher/net/Upload.cpp +++ b/launcher/net/Upload.cpp @@ -42,6 +42,8 @@ #include "BuildConfig.h" #include "Application.h" +#include "logging.h" + namespace Net { bool Upload::abort() @@ -60,11 +62,11 @@ namespace Net { void Upload::downloadError(QNetworkReply::NetworkError error) { if (error == QNetworkReply::OperationCanceledError) { - qCritical() << "Aborted " << m_url.toString(); + qCCritical(taskUploadLogC) << "Aborted " << m_url.toString(); m_state = State::AbortedByUser; } else { // error happened during download. - qCritical() << "Failed " << m_url.toString() << " with reason " << error; + qCCritical(taskUploadLogC) << "Failed " << m_url.toString() << " with reason " << error; m_state = State::Failed; } } @@ -72,9 +74,9 @@ namespace Net { void Upload::sslErrors(const QList &errors) { int i = 1; for (const auto& error : errors) { - qCritical() << "Upload" << m_url.toString() << "SSL Error #" << i << " : " << error.errorString(); + qCCritical(taskUploadLogC) << "Upload" << m_url.toString() << "SSL Error #" << i << " : " << error.errorString(); auto cert = error.certificate(); - qCritical() << "Certificate in question:\n" << cert.toText(); + qCCritical(taskUploadLogC) << "Certificate in question:\n" << cert.toText(); i++; } } @@ -117,17 +119,17 @@ namespace Net { */ redirect = QUrl(redirectStr, QUrl::TolerantMode); if (!redirect.isValid()) { - qWarning() << "Failed to parse redirect URL:" << redirectStr; + qCWarning(taskUploadLogC) << "Failed to parse redirect URL:" << redirectStr; downloadError(QNetworkReply::ProtocolFailure); return false; } - qDebug() << "Fixed location header:" << redirect; + qCDebug(taskUploadLogC) << "Fixed location header:" << redirect; } else { - qDebug() << "Location header:" << redirect; + qCDebug(taskUploadLogC) << "Location header:" << redirect; } m_url = QUrl(redirect.toString()); - qDebug() << "Following redirect to " << m_url.toString(); + qCDebug(taskUploadLogC) << "Following redirect to " << m_url.toString(); startAction(m_network); return true; } @@ -136,25 +138,25 @@ namespace Net { // handle HTTP redirection first // very unlikely for post requests, still can happen if (handleRedirect()) { - qDebug() << "Upload redirected:" << m_url.toString(); + qCDebug(taskUploadLogC) << "Upload redirected:" << m_url.toString(); return; } // if the download failed before this point ... if (m_state == State::Succeeded) { - qDebug() << "Upload failed but we are allowed to proceed:" << m_url.toString(); + qCDebug(taskUploadLogC) << "Upload failed but we are allowed to proceed:" << m_url.toString(); m_sink->abort(); m_reply.reset(); emit succeeded(); return; } else if (m_state == State::Failed) { - qDebug() << "Upload failed in previous step:" << m_url.toString(); + qCDebug(taskUploadLogC) << "Upload failed in previous step:" << m_url.toString(); m_sink->abort(); m_reply.reset(); emit failed(""); return; } else if (m_state == State::AbortedByUser) { - qDebug() << "Upload aborted in previous step:" << m_url.toString(); + qCDebug(taskUploadLogC) << "Upload aborted in previous step:" << m_url.toString(); m_sink->abort(); m_reply.reset(); emit aborted(); @@ -164,21 +166,21 @@ namespace Net { // make sure we got all the remaining data, if any auto data = m_reply->readAll(); if (data.size()) { - qDebug() << "Writing extra" << data.size() << "bytes"; + qCDebug(taskUploadLogC) << "Writing extra" << data.size() << "bytes"; m_state = m_sink->write(data); } // otherwise, finalize the whole graph m_state = m_sink->finalize(*m_reply.get()); if (m_state != State::Succeeded) { - qDebug() << "Upload failed to finalize:" << m_url.toString(); + qCDebug(taskUploadLogC) << "Upload failed to finalize:" << m_url.toString(); m_sink->abort(); m_reply.reset(); emit failed(""); return; } m_reply.reset(); - qDebug() << "Upload succeeded:" << m_url.toString(); + qCDebug(taskUploadLogC) << "Upload succeeded:" << m_url.toString(); emit succeeded(); } @@ -193,7 +195,7 @@ namespace Net { setStatus(tr("Uploading %1").arg(m_url.toString())); if (m_state == State::AbortedByUser) { - qWarning() << "Attempt to start an aborted Upload:" << m_url.toString(); + qCWarning(taskUploadLogC) << "Attempt to start an aborted Upload:" << m_url.toString(); emit aborted(); return; } @@ -202,10 +204,10 @@ namespace Net { switch (m_state) { case State::Succeeded: emitSucceeded(); - qDebug() << "Upload cache hit " << m_url.toString(); + qCDebug(taskUploadLogC) << "Upload cache hit " << m_url.toString(); return; case State::Running: - qDebug() << "Uploading " << m_url.toString(); + qCDebug(taskUploadLogC) << "Uploading " << m_url.toString(); break; case State::Inactive: case State::Failed: diff --git a/launcher/net/logging.cpp b/launcher/net/logging.cpp new file mode 100644 index 00000000..e5b42bc4 --- /dev/null +++ b/launcher/net/logging.cpp @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (C) 2023 Rachel Powers <508861+Ryex@users.noreply.github.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#include "logging.h" + +Q_LOGGING_CATEGORY(taskNetLogC, "launcher.task.net") +Q_LOGGING_CATEGORY(taskDownloadLogC, "launcher.task.net.download") +Q_LOGGING_CATEGORY(taskUploadLogC, "launcher.task.net.upload") \ No newline at end of file diff --git a/launcher/net/logging.h b/launcher/net/logging.h new file mode 100644 index 00000000..e65e328c --- /dev/null +++ b/launcher/net/logging.h @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (C) 2023 Rachel Powers <508861+Ryex@users.noreply.github.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#pragma once + +#include + +Q_DECLARE_LOGGING_CATEGORY(taskNetLogC) +Q_DECLARE_LOGGING_CATEGORY(taskDownloadLogC) +Q_DECLARE_LOGGING_CATEGORY(taskUploadLogC) \ No newline at end of file diff --git a/launcher/tasks/ConcurrentTask.cpp b/launcher/tasks/ConcurrentTask.cpp index 41c405db..8d4f94ed 100644 --- a/launcher/tasks/ConcurrentTask.cpp +++ b/launcher/tasks/ConcurrentTask.cpp @@ -95,6 +95,7 @@ void ConcurrentTask::startNext() connect(next.get(), &Task::failed, this, [this, next](QString msg) { subTaskFailed(next, msg); }); connect(next.get(), &Task::status, this, [this, next](QString msg){ subTaskStatus(next, msg); }); + connect(next.get(), &Task::details, this, [this, next](QString msg){ subTaskDetails(next, msg); }); connect(next.get(), &Task::stepProgress, this, [this, next](TaskStepProgressList tp){ subTaskStepProgress(next, tp); }); connect(next.get(), &Task::progress, this, [this, next](qint64 current, qint64 total){ subTaskProgress(next, current, total); }); @@ -151,7 +152,14 @@ void ConcurrentTask::subTaskStatus(Task::Ptr task, const QString& msg) auto taskProgress = m_task_progress.value(task->getUid()); taskProgress->status = msg; taskProgress->state = TaskStepState::Running; - updateState(); + updateStepProgress(); +} + +void ConcurrentTask::subTaskDetails(Task::Ptr task, const QString& msg) +{ + auto taskProgress = m_task_progress.value(task->getUid()); + taskProgress->details = msg; + taskProgress->state = TaskStepState::Running; updateStepProgress(); } @@ -162,7 +170,6 @@ void ConcurrentTask::subTaskProgress(Task::Ptr task, qint64 current, qint64 tota taskProgress->current = current; taskProgress->total = total; taskProgress->state = TaskStepState::Running; - taskProgress->details = task->getDetails(); updateStepProgress(); updateState(); diff --git a/launcher/tasks/ConcurrentTask.h b/launcher/tasks/ConcurrentTask.h index 9d4413c6..43e9f866 100644 --- a/launcher/tasks/ConcurrentTask.h +++ b/launcher/tasks/ConcurrentTask.h @@ -40,6 +40,7 @@ slots: void subTaskSucceeded(Task::Ptr); void subTaskFailed(Task::Ptr, const QString &msg); void subTaskStatus(Task::Ptr task, const QString &msg); + void subTaskDetails(Task::Ptr task, const QString &msg); void subTaskProgress(Task::Ptr task, qint64 current, qint64 total); void subTaskStepProgress(Task::Ptr task, TaskStepProgressList task_step_progress); diff --git a/launcher/tasks/Task.cpp b/launcher/tasks/Task.cpp index 5aada876..ffde4a10 100644 --- a/launcher/tasks/Task.cpp +++ b/launcher/tasks/Task.cpp @@ -37,7 +37,7 @@ #include -Q_LOGGING_CATEGORY(TaskLogC, "Task") +Q_LOGGING_CATEGORY(taskLogC, "launcher.task") Task::Task(QObject *parent, bool show_debug) : QObject(parent), m_show_debug(show_debug) { @@ -54,11 +54,23 @@ void Task::setStatus(const QString &new_status) } } +void Task::setDetails(const QString& new_details) +{ + if (m_details != new_details) + { + m_details = new_details; + emit details(m_details); + } +} + void Task::setProgress(qint64 current, qint64 total) { - m_progress = current; - m_progressTotal = total; - emit progress(m_progress, m_progressTotal); + if ((m_progress != current) || (m_progressTotal != total)) { + m_progress = current; + m_progressTotal = total; + + emit progress(m_progress, m_progressTotal); + } } void Task::start() @@ -68,31 +80,31 @@ void Task::start() case State::Inactive: { if (m_show_debug) - qCDebug(TaskLogC) << "Task" << describe() << "starting for the first time"; + qCDebug(taskLogC) << "Task" << describe() << "starting for the first time"; break; } case State::AbortedByUser: { if (m_show_debug) - qCDebug(TaskLogC) << "Task" << describe() << "restarting for after being aborted by user"; + qCDebug(taskLogC) << "Task" << describe() << "restarting for after being aborted by user"; break; } case State::Failed: { if (m_show_debug) - qCDebug(TaskLogC) << "Task" << describe() << "restarting for after failing at first"; + qCDebug(taskLogC) << "Task" << describe() << "restarting for after failing at first"; break; } case State::Succeeded: { if (m_show_debug) - qCDebug(TaskLogC) << "Task" << describe() << "restarting for after succeeding at first"; + qCDebug(taskLogC) << "Task" << describe() << "restarting for after succeeding at first"; break; } case State::Running: { if (m_show_debug) - qCWarning(TaskLogC) << "The launcher tried to start task" << describe() << "while it was already running!"; + qCWarning(taskLogC) << "The launcher tried to start task" << describe() << "while it was already running!"; return; } } @@ -107,12 +119,12 @@ void Task::emitFailed(QString reason) // Don't fail twice. if (!isRunning()) { - qCCritical(TaskLogC) << "Task" << describe() << "failed while not running!!!!: " << reason; + qCCritical(taskLogC) << "Task" << describe() << "failed while not running!!!!: " << reason; return; } m_state = State::Failed; m_failReason = reason; - qCCritical(TaskLogC) << "Task" << describe() << "failed: " << reason; + qCCritical(taskLogC) << "Task" << describe() << "failed: " << reason; emit failed(reason); emit finished(); } @@ -122,13 +134,13 @@ void Task::emitAborted() // Don't abort twice. if (!isRunning()) { - qCCritical(TaskLogC) << "Task" << describe() << "aborted while not running!!!!"; + qCCritical(taskLogC) << "Task" << describe() << "aborted while not running!!!!"; return; } m_state = State::AbortedByUser; m_failReason = "Aborted."; if (m_show_debug) - qCDebug(TaskLogC) << "Task" << describe() << "aborted."; + qCDebug(taskLogC) << "Task" << describe() << "aborted."; emit aborted(); emit finished(); } @@ -138,12 +150,12 @@ void Task::emitSucceeded() // Don't succeed twice. if (!isRunning()) { - qCCritical(TaskLogC) << "Task" << describe() << "succeeded while not running!!!!"; + qCCritical(taskLogC) << "Task" << describe() << "succeeded while not running!!!!"; return; } m_state = State::Succeeded; if (m_show_debug) - qCDebug(TaskLogC) << "Task" << describe() << "succeeded"; + qCDebug(taskLogC) << "Task" << describe() << "succeeded"; emit succeeded(); emit finished(); } diff --git a/launcher/tasks/Task.h b/launcher/tasks/Task.h index 863f8a4c..96b3b855 100644 --- a/launcher/tasks/Task.h +++ b/launcher/tasks/Task.h @@ -42,6 +42,8 @@ #include "QObjectPtr.h" +Q_DECLARE_LOGGING_CATEGORY(taskLogC) + enum class TaskStepState { Waiting, Running, @@ -100,12 +102,13 @@ class Task : public QObject, public QRunnable { auto getState() const -> State { return m_state; } QString getStatus() { return m_status; } + QString getDetails() { return m_details; } qint64 getProgress() { return m_progress; } qint64 getTotalProgress() { return m_progressTotal; } virtual auto getStepProgress() const -> TaskStepProgressList { return {}; } - virtual auto getDetails() const -> QString { return ""; } + QUuid getUid() { return m_uid; } @@ -123,6 +126,7 @@ class Task : public QObject, public QRunnable { void aborted(); void failed(QString reason); void status(QString status); + void details(QString details); void stepProgress(TaskStepProgressList task_progress); // /** Emitted when the canAbort() status has changed. @@ -150,6 +154,7 @@ class Task : public QObject, public QRunnable { public slots: void setStatus(const QString& status); + void setDetails(const QString& details); void setProgress(qint64 current, qint64 total); protected: @@ -157,6 +162,7 @@ class Task : public QObject, public QRunnable { QStringList m_Warnings; QString m_failReason = ""; QString m_status; + QString m_details; int m_progress = 0; int m_progressTotal = 100; diff --git a/launcher/ui/dialogs/ProgressDialog.cpp b/launcher/ui/dialogs/ProgressDialog.cpp index 7ab766e4..1937c553 100644 --- a/launcher/ui/dialogs/ProgressDialog.cpp +++ b/launcher/ui/dialogs/ProgressDialog.cpp @@ -133,9 +133,9 @@ int ProgressDialog::execWithTask(Task* task) connect(task, &Task::failed, this, &ProgressDialog::onTaskFailed); connect(task, &Task::succeeded, this, &ProgressDialog::onTaskSucceeded); connect(task, &Task::status, this, &ProgressDialog::changeStatus); + connect(task, &Task::details, this, &ProgressDialog::changeStatus); connect(task, &Task::stepProgress, this, &ProgressDialog::changeStepProgress); connect(task, &Task::progress, this, &ProgressDialog::changeProgress); - connect(task, &Task::aborted, this, &ProgressDialog::hide); connect(task, &Task::abortStatusChanged, ui->skipButton, &QPushButton::setEnabled); diff --git a/launcher/ui/widgets/ProgressWidget.cpp b/launcher/ui/widgets/ProgressWidget.cpp index f736af08..9181de7f 100644 --- a/launcher/ui/widgets/ProgressWidget.cpp +++ b/launcher/ui/widgets/ProgressWidget.cpp @@ -51,6 +51,7 @@ void ProgressWidget::watch(const Task* task) connect(m_task, &Task::finished, this, &ProgressWidget::handleTaskFinish); connect(m_task, &Task::status, this, &ProgressWidget::handleTaskStatus); + // TODO: should we connect &Task::details connect(m_task, &Task::progress, this, &ProgressWidget::handleTaskProgress); connect(m_task, &Task::destroyed, this, &ProgressWidget::taskDestroyed); diff --git a/launcher/ui/widgets/SubTaskProgressBar.ui b/launcher/ui/widgets/SubTaskProgressBar.ui index ceae5e26..5431eab6 100644 --- a/launcher/ui/widgets/SubTaskProgressBar.ui +++ b/launcher/ui/widgets/SubTaskProgressBar.ui @@ -6,8 +6,8 @@ 0 0 - 597 - 61 + 312 + 86 @@ -25,6 +25,9 @@ + + 8 + -- cgit From 0fb6a2836be2fe51e27a47595950923dca329006 Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Sun, 2 Apr 2023 21:51:07 -0700 Subject: refactor: propogate only only one StepProgress at a time Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- launcher/tasks/ConcurrentTask.cpp | 99 ++++++++++++++++++---------------- launcher/tasks/ConcurrentTask.h | 2 +- launcher/tasks/Task.cpp | 2 +- launcher/tasks/Task.h | 9 ++-- launcher/ui/dialogs/ProgressDialog.cpp | 38 +++++++------ launcher/ui/dialogs/ProgressDialog.h | 2 +- 6 files changed, 79 insertions(+), 73 deletions(-) (limited to 'launcher/ui/dialogs') diff --git a/launcher/tasks/ConcurrentTask.cpp b/launcher/tasks/ConcurrentTask.cpp index 3c62cf4d..8434ecdc 100644 --- a/launcher/tasks/ConcurrentTask.cpp +++ b/launcher/tasks/ConcurrentTask.cpp @@ -35,13 +35,15 @@ */ #include "ConcurrentTask.h" -#include #include +#include #include "tasks/Task.h" ConcurrentTask::ConcurrentTask(QObject* parent, QString task_name, int max_concurrent) : Task(parent), m_name(task_name), m_total_max_size(max_concurrent) -{ setObjectName(task_name); } +{ + setObjectName(task_name); +} ConcurrentTask::~ConcurrentTask() { @@ -126,18 +128,17 @@ void ConcurrentTask::startNext() Task::Ptr next = m_queue.dequeue(); - connect(next.get(), &Task::succeeded, this, [this, next](){ subTaskSucceeded(next); }); + connect(next.get(), &Task::succeeded, this, [this, next]() { subTaskSucceeded(next); }); connect(next.get(), &Task::failed, this, [this, next](QString msg) { subTaskFailed(next, msg); }); - connect(next.get(), &Task::status, this, [this, next](QString msg){ subTaskStatus(next, msg); }); - connect(next.get(), &Task::details, this, [this, next](QString msg){ subTaskDetails(next, msg); }); - connect(next.get(), &Task::stepProgress, this, [this, next](TaskStepProgressList tp){ subTaskStepProgress(next, tp); }); + connect(next.get(), &Task::status, this, [this, next](QString msg) { subTaskStatus(next, msg); }); + connect(next.get(), &Task::details, this, [this, next](QString msg) { subTaskDetails(next, msg); }); + connect(next.get(), &Task::stepProgress, this, [this, next](TaskStepProgress tp) { subTaskStepProgress(next, tp); }); - connect(next.get(), &Task::progress, this, [this, next](qint64 current, qint64 total){ subTaskProgress(next, current, total); }); + connect(next.get(), &Task::progress, this, [this, next](qint64 current, qint64 total) { subTaskProgress(next, current, total); }); m_doing.insert(next.get(), next); - m_task_progress.insert(next->getUid(), std::make_shared(TaskStepProgress({next->getUid()}))); - + m_task_progress.insert(next->getUid(), std::make_shared(TaskStepProgress({ next->getUid() }))); updateState(); updateStepProgress(); @@ -158,10 +159,12 @@ void ConcurrentTask::subTaskSucceeded(Task::Ptr task) m_succeeded.insert(task.get(), task); m_doing.remove(task.get()); - m_task_progress.value(task->getUid())->state = TaskStepState::Succeeded; + auto task_progress = m_task_progress.value(task->getUid()); + task_progress->state = TaskStepState::Succeeded; disconnect(task.get(), 0, this, 0); + emit stepProgress(*task_progress.get()); updateState(); updateStepProgress(); startNext(); @@ -173,10 +176,13 @@ void ConcurrentTask::subTaskFailed(Task::Ptr task, const QString& msg) m_failed.insert(task.get(), task); m_doing.remove(task.get()); - m_task_progress.value(task->getUid())->state = TaskStepState::Failed; + + auto task_progress = m_task_progress.value(task->getUid()); + task_progress->state = TaskStepState::Failed; disconnect(task.get(), 0, this, 0); + emit stepProgress(*task_progress.get()); updateState(); updateStepProgress(); startNext(); @@ -184,68 +190,71 @@ void ConcurrentTask::subTaskFailed(Task::Ptr task, const QString& msg) void ConcurrentTask::subTaskStatus(Task::Ptr task, const QString& msg) { - auto taskProgress = m_task_progress.value(task->getUid()); - taskProgress->status = msg; - taskProgress->state = TaskStepState::Running; + auto task_progress = m_task_progress.value(task->getUid()); + task_progress->status = msg; + task_progress->state = TaskStepState::Running; + + emit stepProgress(*task_progress.get()); updateStepProgress(); } void ConcurrentTask::subTaskDetails(Task::Ptr task, const QString& msg) { - auto taskProgress = m_task_progress.value(task->getUid()); - taskProgress->details = msg; - taskProgress->state = TaskStepState::Running; + auto task_progress = m_task_progress.value(task->getUid()); + task_progress->details = msg; + task_progress->state = TaskStepState::Running; + + emit stepProgress(*task_progress.get()); updateStepProgress(); } void ConcurrentTask::subTaskProgress(Task::Ptr task, qint64 current, qint64 total) { - auto taskProgress = m_task_progress.value(task->getUid()); - - taskProgress->current = current; - taskProgress->total = total; - taskProgress->state = TaskStepState::Running; + auto task_progress = m_task_progress.value(task->getUid()); + task_progress->current = current; + task_progress->total = total; + task_progress->state = TaskStepState::Running; + + emit stepProgress(*task_progress.get()); updateStepProgress(); updateState(); } -void ConcurrentTask::subTaskStepProgress(Task::Ptr task, TaskStepProgressList task_step_progress) +void ConcurrentTask::subTaskStepProgress(Task::Ptr task, TaskStepProgress task_progress) { - for (auto progress : task_step_progress) { - if (!m_task_progress.contains(progress->uid)) { - m_task_progress.insert(progress->uid, progress); - } else { - auto tp = m_task_progress.value(progress->uid); - tp->current = progress->current; - tp->total = progress->total; - tp->status = progress->status; - tp->details = progress->details; - } + if (!m_task_progress.contains(task_progress.uid)) { + m_task_progress.insert(task_progress.uid, std::make_shared(task_progress)); + } else { + auto tp = m_task_progress.value(task_progress.uid); + tp->current = task_progress.current; + tp->total = task_progress.total; + tp->status = task_progress.status; + tp->details = task_progress.details; } - - updateStepProgress(); + emit stepProgress(task_progress); + updateStepProgress(); } void ConcurrentTask::updateStepProgress() { - qint64 current = 0, total = 0; - for ( auto taskProgress : m_task_progress ) { - current += taskProgress->current; - total += taskProgress->total; - } - - m_stepProgress = current; - m_stepTotalProgress = total; - emit stepProgress(m_task_progress.values()); + qint64 current = 0, total = 0; + for (auto taskProgress : m_task_progress) { + current += taskProgress->current; + total += taskProgress->total; + } + + m_stepProgress = current; + m_stepTotalProgress = total; } void ConcurrentTask::updateState() { if (totalSize() > 1) { setProgress(m_done.count(), totalSize()); - setStatus(tr("Executing %1 task(s) (%2 out of %3 are done)").arg(QString::number(m_doing.count()), QString::number(m_done.count()), QString::number(totalSize()))); + setStatus(tr("Executing %1 task(s) (%2 out of %3 are done)") + .arg(QString::number(m_doing.count()), QString::number(m_done.count()), QString::number(totalSize()))); } else { setProgress(m_stepProgress, m_stepTotalProgress); QString status = tr("Please wait ..."); diff --git a/launcher/tasks/ConcurrentTask.h b/launcher/tasks/ConcurrentTask.h index 1cf1520e..974e8d4d 100644 --- a/launcher/tasks/ConcurrentTask.h +++ b/launcher/tasks/ConcurrentTask.h @@ -77,7 +77,7 @@ slots: void subTaskStatus(Task::Ptr task, const QString &msg); void subTaskDetails(Task::Ptr task, const QString &msg); void subTaskProgress(Task::Ptr task, qint64 current, qint64 total); - void subTaskStepProgress(Task::Ptr task, TaskStepProgressList task_step_progress); + void subTaskStepProgress(Task::Ptr task, TaskStepProgress task_step_progress); protected: // NOTE: This is not thread-safe. diff --git a/launcher/tasks/Task.cpp b/launcher/tasks/Task.cpp index e5f61919..fd1b8898 100644 --- a/launcher/tasks/Task.cpp +++ b/launcher/tasks/Task.cpp @@ -161,7 +161,7 @@ void Task::emitSucceeded() emit finished(); } -void Task::propogateStepProgress(TaskStepProgressList task_progress) +void Task::propogateStepProgress(TaskStepProgress task_progress) { emit stepProgress(task_progress); } diff --git a/launcher/tasks/Task.h b/launcher/tasks/Task.h index 96b3b855..9ae70270 100644 --- a/launcher/tasks/Task.h +++ b/launcher/tasks/Task.h @@ -48,8 +48,7 @@ enum class TaskStepState { Waiting, Running, Failed, - Succeeded, - Finished + Succeeded }; Q_DECLARE_METATYPE(TaskStepState) @@ -61,7 +60,7 @@ struct TaskStepProgress { QString status = ""; QString details = ""; TaskStepState state = TaskStepState::Waiting; - bool isDone() { return (state == TaskStepState::Failed) || (state == TaskStepState::Succeeded) || (state == TaskStepState::Finished); } + bool isDone() { return (state == TaskStepState::Failed) || (state == TaskStepState::Succeeded); } }; Q_DECLARE_METATYPE(TaskStepProgress) @@ -127,7 +126,7 @@ class Task : public QObject, public QRunnable { void failed(QString reason); void status(QString status); void details(QString details); - void stepProgress(TaskStepProgressList task_progress); // + void stepProgress(TaskStepProgress task_progress); // /** Emitted when the canAbort() status has changed. */ @@ -150,7 +149,7 @@ class Task : public QObject, public QRunnable { virtual void emitAborted(); virtual void emitFailed(QString reason = ""); - virtual void propogateStepProgress(TaskStepProgressList task_progress); + virtual void propogateStepProgress(TaskStepProgress task_progress); public slots: void setStatus(const QString& status); diff --git a/launcher/ui/dialogs/ProgressDialog.cpp b/launcher/ui/dialogs/ProgressDialog.cpp index 1937c553..7594484e 100644 --- a/launcher/ui/dialogs/ProgressDialog.cpp +++ b/launcher/ui/dialogs/ProgressDialog.cpp @@ -46,8 +46,8 @@ // map a value in a numaric range of an arbatray type to between 0 and INT_MAX -// for getting the best percision out of the qt progress bar -template::value, T>::type> +// for getting the best precision out of the qt progress bar +template, bool> = true> std::tuple map_int_zero_max(T current, T range_max, T range_min) { int int_max = std::numeric_limits::max(); @@ -213,7 +213,7 @@ void ProgressDialog::addTaskProgress(TaskStepProgress* progress) ui->taskProgressLayout->addWidget(task_bar); } -void ProgressDialog::changeStepProgress(TaskStepProgressList task_progress) +void ProgressDialog::changeStepProgress(TaskStepProgress task_progress) { m_is_multi_step = true; if(ui->taskProgressScrollArea->isHidden()) { @@ -221,28 +221,26 @@ void ProgressDialog::changeStepProgress(TaskStepProgressList task_progress) updateSize(); } - for (auto tp : task_progress) { - if (!taskProgress.contains(tp->uid)) - addTaskProgress(tp.get()); - auto task_bar = taskProgress.value(tp->uid); + if (!taskProgress.contains(task_progress.uid)) + addTaskProgress(&task_progress); + auto task_bar = taskProgress.value(task_progress.uid); - auto const [mapped_current, mapped_total] = map_int_zero_max(tp->current, tp->total, 0); - if (tp->total <= 0) { - task_bar->setRange(0, 0); - } else { - task_bar->setRange(0, mapped_total); - } - - task_bar->setValue(mapped_current); - task_bar->setStatus(tp->status); - task_bar->setDetails(tp->details); + auto const [mapped_current, mapped_total] = map_int_zero_max(task_progress.current, task_progress.total, 0); + if (task_progress.total <= 0) { + task_bar->setRange(0, 0); + } else { + task_bar->setRange(0, mapped_total); + } - if (tp->isDone()) { - task_bar->setVisible(false); - } + task_bar->setValue(mapped_current); + task_bar->setStatus(task_progress.status); + task_bar->setDetails(task_progress.details); + if (task_progress.isDone()) { + task_bar->setVisible(false); } + } void ProgressDialog::changeProgress(qint64 current, qint64 total) diff --git a/launcher/ui/dialogs/ProgressDialog.h b/launcher/ui/dialogs/ProgressDialog.h index 95a4db16..6779b949 100644 --- a/launcher/ui/dialogs/ProgressDialog.h +++ b/launcher/ui/dialogs/ProgressDialog.h @@ -80,7 +80,7 @@ slots: void changeStatus(const QString &status); void changeProgress(qint64 current, qint64 total); - void changeStepProgress(TaskStepProgressList task_progress); + void changeStepProgress(TaskStepProgress task_progress); private -- cgit From 236764adf6cb985dfc6d00b9cbcba8eb176510ed Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Fri, 7 Apr 2023 19:44:57 -0700 Subject: refactor: Qt can handle const& in signals and slots While most Qt types cna use implicit data sharing pasing our own structs means copies. const& ensure it's only copied as needed by Qt. Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- launcher/tasks/ConcurrentTask.cpp | 10 ++++++---- launcher/tasks/ConcurrentTask.h | 2 +- launcher/tasks/Task.cpp | 2 +- launcher/tasks/Task.h | 6 +++--- launcher/ui/dialogs/ProgressDialog.cpp | 8 ++++---- launcher/ui/dialogs/ProgressDialog.h | 4 ++-- 6 files changed, 17 insertions(+), 15 deletions(-) (limited to 'launcher/ui/dialogs') diff --git a/launcher/tasks/ConcurrentTask.cpp b/launcher/tasks/ConcurrentTask.cpp index 439879e6..37435739 100644 --- a/launcher/tasks/ConcurrentTask.cpp +++ b/launcher/tasks/ConcurrentTask.cpp @@ -133,7 +133,7 @@ void ConcurrentTask::startNext() connect(next.get(), &Task::status, this, [this, next](QString msg) { subTaskStatus(next, msg); }); connect(next.get(), &Task::details, this, [this, next](QString msg) { subTaskDetails(next, msg); }); - connect(next.get(), &Task::stepProgress, this, [this, next](TaskStepProgress tp) { subTaskStepProgress(next, tp); }); + connect(next.get(), &Task::stepProgress, this, [this, next](TaskStepProgress const& tp) { subTaskStepProgress(next, tp); }); connect(next.get(), &Task::progress, this, [this, next](qint64 current, qint64 total) { subTaskProgress(next, current, total); }); @@ -224,13 +224,15 @@ void ConcurrentTask::subTaskProgress(Task::Ptr task, qint64 current, qint64 tota updateState(); } -void ConcurrentTask::subTaskStepProgress(Task::Ptr task, TaskStepProgress task_progress) +void ConcurrentTask::subTaskStepProgress(Task::Ptr task, TaskStepProgress const& task_progress) { Operation op = Operation::ADDED; if (!m_task_progress.contains(task_progress.uid)) { m_task_progress.insert(task_progress.uid, std::make_shared(task_progress)); op = Operation::ADDED; + emit stepProgress(task_progress); + updateStepProgress(task_progress, op); } else { auto tp = m_task_progress.value(task_progress.uid); @@ -243,10 +245,10 @@ void ConcurrentTask::subTaskStepProgress(Task::Ptr task, TaskStepProgress task_p tp->details = task_progress.details; op = Operation::CHANGED; + emit stepProgress(*tp.get()); + updateStepProgress(*tp.get(), op); } - emit stepProgress(task_progress); - updateStepProgress(task_progress, op); } void ConcurrentTask::updateStepProgress(TaskStepProgress const& changed_progress, Operation op) diff --git a/launcher/tasks/ConcurrentTask.h b/launcher/tasks/ConcurrentTask.h index 284fa345..aec707bc 100644 --- a/launcher/tasks/ConcurrentTask.h +++ b/launcher/tasks/ConcurrentTask.h @@ -77,7 +77,7 @@ slots: void subTaskStatus(Task::Ptr task, const QString &msg); void subTaskDetails(Task::Ptr task, const QString &msg); void subTaskProgress(Task::Ptr task, qint64 current, qint64 total); - void subTaskStepProgress(Task::Ptr task, TaskStepProgress task_step_progress); + void subTaskStepProgress(Task::Ptr task, TaskStepProgress const& task_step_progress); protected: // NOTE: This is not thread-safe. diff --git a/launcher/tasks/Task.cpp b/launcher/tasks/Task.cpp index fd1b8898..b0addd46 100644 --- a/launcher/tasks/Task.cpp +++ b/launcher/tasks/Task.cpp @@ -161,7 +161,7 @@ void Task::emitSucceeded() emit finished(); } -void Task::propogateStepProgress(TaskStepProgress task_progress) +void Task::propogateStepProgress(TaskStepProgress const& task_progress) { emit stepProgress(task_progress); } diff --git a/launcher/tasks/Task.h b/launcher/tasks/Task.h index d71eaf6b..04a09187 100644 --- a/launcher/tasks/Task.h +++ b/launcher/tasks/Task.h @@ -64,7 +64,7 @@ struct TaskStepProgress { QString status = ""; QString details = ""; TaskStepState state = TaskStepState::Waiting; - bool isDone() { return (state == TaskStepState::Failed) || (state == TaskStepState::Succeeded); } + bool isDone() const { return (state == TaskStepState::Failed) || (state == TaskStepState::Succeeded); } }; Q_DECLARE_METATYPE(TaskStepProgress) @@ -130,7 +130,7 @@ class Task : public QObject, public QRunnable { void failed(QString reason); void status(QString status); void details(QString details); - void stepProgress(TaskStepProgress task_progress); // + void stepProgress(TaskStepProgress const& task_progress); // /** Emitted when the canAbort() status has changed. */ @@ -153,7 +153,7 @@ class Task : public QObject, public QRunnable { virtual void emitAborted(); virtual void emitFailed(QString reason = ""); - virtual void propogateStepProgress(TaskStepProgress task_progress); + virtual void propogateStepProgress(TaskStepProgress const& task_progress); public slots: void setStatus(const QString& status); diff --git a/launcher/ui/dialogs/ProgressDialog.cpp b/launcher/ui/dialogs/ProgressDialog.cpp index 7594484e..94feee44 100644 --- a/launcher/ui/dialogs/ProgressDialog.cpp +++ b/launcher/ui/dialogs/ProgressDialog.cpp @@ -206,14 +206,14 @@ void ProgressDialog::changeStatus(const QString& status) updateSize(); } -void ProgressDialog::addTaskProgress(TaskStepProgress* progress) +void ProgressDialog::addTaskProgress(TaskStepProgress const& progress) { SubTaskProgressBar* task_bar = new SubTaskProgressBar(this); - taskProgress.insert(progress->uid, task_bar); + taskProgress.insert(progress.uid, task_bar); ui->taskProgressLayout->addWidget(task_bar); } -void ProgressDialog::changeStepProgress(TaskStepProgress task_progress) +void ProgressDialog::changeStepProgress(TaskStepProgress const& task_progress) { m_is_multi_step = true; if(ui->taskProgressScrollArea->isHidden()) { @@ -222,7 +222,7 @@ void ProgressDialog::changeStepProgress(TaskStepProgress task_progress) } if (!taskProgress.contains(task_progress.uid)) - addTaskProgress(&task_progress); + addTaskProgress(task_progress); auto task_bar = taskProgress.value(task_progress.uid); diff --git a/launcher/ui/dialogs/ProgressDialog.h b/launcher/ui/dialogs/ProgressDialog.h index 6779b949..fc9a0fbc 100644 --- a/launcher/ui/dialogs/ProgressDialog.h +++ b/launcher/ui/dialogs/ProgressDialog.h @@ -80,7 +80,7 @@ slots: void changeStatus(const QString &status); void changeProgress(qint64 current, qint64 total); - void changeStepProgress(TaskStepProgress task_progress); + void changeStepProgress(TaskStepProgress const& task_progress); private @@ -93,7 +93,7 @@ protected: private: bool handleImmediateResult(QDialog::DialogCode &result); - void addTaskProgress(TaskStepProgress* progress); + void addTaskProgress(TaskStepProgress const& progress); private: Ui::ProgressDialog *ui; -- cgit From b266068644d2caab4f103b0adf7a491b95f52369 Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Fri, 5 May 2023 14:07:10 -0700 Subject: Apply suggestions from code review Co-authored-by: flow Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- launcher/java/JavaInstallList.cpp | 1 - launcher/net/Download.cpp | 13 +++++++------ launcher/net/NetAction.h | 4 ++-- launcher/tasks/ConcurrentTask.cpp | 6 +++--- launcher/tasks/Task.h | 2 +- launcher/ui/dialogs/ProgressDialog.cpp | 2 +- launcher/ui/widgets/SubTaskProgressBar.h | 2 -- 7 files changed, 14 insertions(+), 16 deletions(-) (limited to 'launcher/ui/dialogs') diff --git a/launcher/java/JavaInstallList.cpp b/launcher/java/JavaInstallList.cpp index 5f133622..b29af857 100644 --- a/launcher/java/JavaInstallList.cpp +++ b/launcher/java/JavaInstallList.cpp @@ -170,7 +170,6 @@ void JavaListLoadTask::executeTask() m_job.reset(new JavaCheckerJob("Java detection")); connect(m_job.get(), &Task::finished, this, &JavaListLoadTask::javaCheckerFinished); connect(m_job.get(), &Task::progress, this, &Task::setProgress); - // stepProgress? qDebug() << "Probing the following Java paths: "; int id = 0; diff --git a/launcher/net/Download.cpp b/launcher/net/Download.cpp index bf0e5c26..082f963d 100644 --- a/launcher/net/Download.cpp +++ b/launcher/net/Download.cpp @@ -247,19 +247,20 @@ void Download::downloadProgress(qint64 bytesReceived, qint64 bytesTotal) // use milliseconds for speed precision auto elapsed_ms = std::chrono::duration_cast(elapsed); - auto bytes_recived_since = bytesReceived - m_last_progress_bytes; - auto dl_speed_bps = (double)bytes_recived_since / elapsed_ms.count() * 1000; + auto bytes_received_since = bytesReceived - m_last_progress_bytes; + auto dl_speed_bps = (double)bytes_received_since / elapsed_ms.count() * 1000; auto remaing_time_s = (bytesTotal - bytesReceived) / dl_speed_bps; - // current bytes out of total bytes + //: Current amount of bytes downloaded, out of the total amount of bytes in the download QString dl_progress = tr("%1 / %2").arg(humanReadableFileSize(bytesReceived)).arg(humanReadableFileSize(bytesTotal)); QString dl_speed_str; if (elapsed_ms.count() > 0) { - // bytes per second - dl_speed_str = tr("%1/s (%2)").arg(humanReadableFileSize(dl_speed_bps)).arg(humanReadableDuration(remaing_time_s)); + //: Download speed, in bytes per second (remaining download time in parenthesis) + dl_speed_str = tr("%1 /s (%2)").arg(humanReadableFileSize(dl_speed_bps)).arg(humanReadableDuration(remaing_time_s)); } else { - dl_speed_str = tr("0 b/s"); + //: Download speed at 0 bytes per second + dl_speed_str = tr("0 B/s"); } setDetails(dl_progress + "\n" + dl_speed_str); diff --git a/launcher/net/NetAction.h b/launcher/net/NetAction.h index df6ed995..a13d115f 100644 --- a/launcher/net/NetAction.h +++ b/launcher/net/NetAction.h @@ -44,8 +44,8 @@ #include "QObjectPtr.h" #include "tasks/Task.h" -static const QStringList s_units_si {"kb", "MB", "GB", "TB"}; -static const QStringList s_units_kibi {"kiB", "MiB", "Gib", "TiB"}; +static const QStringList s_units_si {"kB", "MB", "GB", "TB"}; +static const QStringList s_units_kibi {"KiB", "MiB", "Gib", "TiB"}; inline QString humanReadableFileSize(double bytes, bool use_si = false, int decimal_points = 1) { const QStringList units = use_si ? s_units_si : s_units_kibi; diff --git a/launcher/tasks/ConcurrentTask.cpp b/launcher/tasks/ConcurrentTask.cpp index dbb4d94d..fae2f3dc 100644 --- a/launcher/tasks/ConcurrentTask.cpp +++ b/launcher/tasks/ConcurrentTask.cpp @@ -65,7 +65,7 @@ void ConcurrentTask::addTask(Task::Ptr task) void ConcurrentTask::executeTask() { - // Start One task, startNext hadels starting the up to the m_total_max_size + // Start one task, startNext handles starting the up to the m_total_max_size // while tracking the number currently being done QMetaObject::invokeMethod(this, &ConcurrentTask::startNext, Qt::QueuedConnection); } @@ -293,9 +293,9 @@ void ConcurrentTask::updateState() .arg(QString::number(m_doing.count()), QString::number(m_done.count()), QString::number(totalSize()))); } else { setProgress(m_stepProgress, m_stepTotalProgress); - QString status = tr("Please wait ..."); + QString status = tr("Please wait..."); if (m_queue.size() > 0) { - status = tr("Waiting for 1 task to start ..."); + status = tr("Waiting for a task to start..."); } else if (m_doing.size() > 0) { status = tr("Executing 1 task:"); } else if (m_done.size() > 0) { diff --git a/launcher/tasks/Task.h b/launcher/tasks/Task.h index 04a09187..799ed945 100644 --- a/launcher/tasks/Task.h +++ b/launcher/tasks/Task.h @@ -130,7 +130,7 @@ class Task : public QObject, public QRunnable { void failed(QString reason); void status(QString status); void details(QString details); - void stepProgress(TaskStepProgress const& task_progress); // + void stepProgress(TaskStepProgress const& task_progress); /** Emitted when the canAbort() status has changed. */ diff --git a/launcher/ui/dialogs/ProgressDialog.cpp b/launcher/ui/dialogs/ProgressDialog.cpp index 94feee44..246a0fd4 100644 --- a/launcher/ui/dialogs/ProgressDialog.cpp +++ b/launcher/ui/dialogs/ProgressDialog.cpp @@ -45,7 +45,7 @@ #include "ui/widgets/SubTaskProgressBar.h" -// map a value in a numaric range of an arbatray type to between 0 and INT_MAX +// map a value in a numeric range of an arbitrary type to between 0 and INT_MAX // for getting the best precision out of the qt progress bar template, bool> = true> std::tuple map_int_zero_max(T current, T range_max, T range_min) diff --git a/launcher/ui/widgets/SubTaskProgressBar.h b/launcher/ui/widgets/SubTaskProgressBar.h index 3375a0bc..8f8aeea2 100644 --- a/launcher/ui/widgets/SubTaskProgressBar.h +++ b/launcher/ui/widgets/SubTaskProgressBar.h @@ -18,8 +18,6 @@ */ #pragma once -#include -#include #include #include "QObjectPtr.h" -- cgit From e1b6020b76401eb5a2f164775f3f738f357e4e2d Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Sat, 13 May 2023 18:24:01 +0100 Subject: Make some changes Signed-off-by: TheKodeToad --- launcher/modplatform/modrinth/ModrinthPackExportTask.cpp | 13 +++++++++++-- launcher/ui/dialogs/ExportInstanceDialog.cpp | 3 --- 2 files changed, 11 insertions(+), 5 deletions(-) (limited to 'launcher/ui/dialogs') diff --git a/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp b/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp index 9a45e46b..c550fdce 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 ea01c5e2..57fe8119 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" -- cgit From 22aaf56855d9f1c4c8e2c2fdfba30a2d40a0ebdc Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Mon, 15 May 2023 18:48:30 +0100 Subject: De-hardcode .index Signed-off-by: TheKodeToad --- launcher/ui/dialogs/ExportMrPackDialog.cpp | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) (limited to 'launcher/ui/dialogs') diff --git a/launcher/ui/dialogs/ExportMrPackDialog.cpp b/launcher/ui/dialogs/ExportMrPackDialog.cpp index bc983efe..2b9f9174 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); } - -- cgit From 3be18b58bbb10bd7f93132d5c2bba6286dd85edc Mon Sep 17 00:00:00 2001 From: Kode Date: Mon, 15 May 2023 19:15:56 +0100 Subject: Better variable name :p Signed-off-by: Kode --- launcher/ui/dialogs/ExportMrPackDialog.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'launcher/ui/dialogs') diff --git a/launcher/ui/dialogs/ExportMrPackDialog.cpp b/launcher/ui/dialogs/ExportMrPackDialog.cpp index 2b9f9174..1551cc60 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); -- cgit From 863027cbe826bbb38f7bebdef436a9682d094cb2 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Mon, 22 May 2023 11:56:37 +0100 Subject: Enable size grip Signed-off-by: TheKodeToad --- launcher/ui/dialogs/ExportMrPackDialog.ui | 3 +++ 1 file changed, 3 insertions(+) (limited to 'launcher/ui/dialogs') diff --git a/launcher/ui/dialogs/ExportMrPackDialog.ui b/launcher/ui/dialogs/ExportMrPackDialog.ui index 8e6d61ff..f154d210 100644 --- a/launcher/ui/dialogs/ExportMrPackDialog.ui +++ b/launcher/ui/dialogs/ExportMrPackDialog.ui @@ -13,6 +13,9 @@ Export Modrinth Pack + + true + -- cgit From a04a6f1e0d0d551506a86964c51e5ce6af5587b4 Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Sun, 28 May 2023 02:15:39 -0700 Subject: fix(memory leak): don't give shared pointers out to foldermodels (causes cyclic refrence) Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- launcher/minecraft/MinecraftInstance.cpp | 30 +++++++++++----------- launcher/minecraft/MinecraftInstance.h | 16 ++++++------ launcher/minecraft/WorldList.cpp | 2 +- launcher/minecraft/WorldList.h | 4 +-- launcher/minecraft/mod/ModFolderModel.cpp | 2 +- launcher/minecraft/mod/ModFolderModel.h | 2 +- launcher/minecraft/mod/ResourceFolderModel.cpp | 2 +- launcher/minecraft/mod/ResourceFolderModel.h | 4 +-- launcher/minecraft/mod/ResourcePackFolderModel.cpp | 2 +- launcher/minecraft/mod/ResourcePackFolderModel.h | 2 +- launcher/minecraft/mod/ShaderPackFolderModel.h | 2 +- launcher/minecraft/mod/TexturePackFolderModel.cpp | 2 +- launcher/minecraft/mod/TexturePackFolderModel.h | 2 +- launcher/ui/dialogs/NewInstanceDialog.cpp | 2 +- launcher/ui/dialogs/ResourceDownloadDialog.cpp | 2 +- launcher/ui/widgets/PageContainer.cpp | 4 ++- tests/ResourceFolderModel_test.cpp | 17 ++++-------- 17 files changed, 46 insertions(+), 51 deletions(-) (limited to 'launcher/ui/dialogs') diff --git a/launcher/minecraft/MinecraftInstance.cpp b/launcher/minecraft/MinecraftInstance.cpp index 35bef05e..2c624a36 100644 --- a/launcher/minecraft/MinecraftInstance.cpp +++ b/launcher/minecraft/MinecraftInstance.cpp @@ -1109,79 +1109,79 @@ JavaVersion MinecraftInstance::getJavaVersion() return JavaVersion(settings()->get("JavaVersion").toString()); } -std::shared_ptr MinecraftInstance::loaderModList() const +std::shared_ptr MinecraftInstance::loaderModList() { if (!m_loader_mod_list) { bool is_indexed = !APPLICATION->settings()->get("ModMetadataDisabled").toBool(); - m_loader_mod_list.reset(new ModFolderModel(modsRoot(), shared_from_this(), is_indexed)); + m_loader_mod_list.reset(new ModFolderModel(modsRoot(), this, is_indexed)); m_loader_mod_list->disableInteraction(isRunning()); connect(this, &BaseInstance::runningStatusChanged, m_loader_mod_list.get(), &ModFolderModel::disableInteraction); } return m_loader_mod_list; } -std::shared_ptr MinecraftInstance::coreModList() const +std::shared_ptr MinecraftInstance::coreModList() { if (!m_core_mod_list) { bool is_indexed = !APPLICATION->settings()->get("ModMetadataDisabled").toBool(); - m_core_mod_list.reset(new ModFolderModel(coreModsDir(), shared_from_this(), is_indexed)); + m_core_mod_list.reset(new ModFolderModel(coreModsDir(), this, is_indexed)); m_core_mod_list->disableInteraction(isRunning()); connect(this, &BaseInstance::runningStatusChanged, m_core_mod_list.get(), &ModFolderModel::disableInteraction); } return m_core_mod_list; } -std::shared_ptr MinecraftInstance::nilModList() const +std::shared_ptr MinecraftInstance::nilModList() { if (!m_nil_mod_list) { bool is_indexed = !APPLICATION->settings()->get("ModMetadataDisabled").toBool(); - m_nil_mod_list.reset(new ModFolderModel(nilModsDir(), shared_from_this(), is_indexed, false)); + m_nil_mod_list.reset(new ModFolderModel(nilModsDir(), this, is_indexed, false)); m_nil_mod_list->disableInteraction(isRunning()); connect(this, &BaseInstance::runningStatusChanged, m_nil_mod_list.get(), &ModFolderModel::disableInteraction); } return m_nil_mod_list; } -std::shared_ptr MinecraftInstance::resourcePackList() const +std::shared_ptr MinecraftInstance::resourcePackList() { if (!m_resource_pack_list) { - m_resource_pack_list.reset(new ResourcePackFolderModel(resourcePacksDir(), shared_from_this())); + m_resource_pack_list.reset(new ResourcePackFolderModel(resourcePacksDir(), this)); } return m_resource_pack_list; } -std::shared_ptr MinecraftInstance::texturePackList() const +std::shared_ptr MinecraftInstance::texturePackList() { if (!m_texture_pack_list) { - m_texture_pack_list.reset(new TexturePackFolderModel(texturePacksDir(), shared_from_this())); + m_texture_pack_list.reset(new TexturePackFolderModel(texturePacksDir(), this)); } return m_texture_pack_list; } -std::shared_ptr MinecraftInstance::shaderPackList() const +std::shared_ptr MinecraftInstance::shaderPackList() { if (!m_shader_pack_list) { - m_shader_pack_list.reset(new ShaderPackFolderModel(shaderPacksDir(), shared_from_this())); + m_shader_pack_list.reset(new ShaderPackFolderModel(shaderPacksDir(), this)); } return m_shader_pack_list; } -std::shared_ptr MinecraftInstance::worldList() const +std::shared_ptr MinecraftInstance::worldList() { if (!m_world_list) { - m_world_list.reset(new WorldList(worldDir(), shared_from_this())); + m_world_list.reset(new WorldList(worldDir(), this)); } return m_world_list; } -std::shared_ptr MinecraftInstance::gameOptionsModel() const +std::shared_ptr MinecraftInstance::gameOptionsModel() { if (!m_game_options) { diff --git a/launcher/minecraft/MinecraftInstance.h b/launcher/minecraft/MinecraftInstance.h index a75fa481..068b3008 100644 --- a/launcher/minecraft/MinecraftInstance.h +++ b/launcher/minecraft/MinecraftInstance.h @@ -115,14 +115,14 @@ public: std::shared_ptr getPackProfile() const; ////// Mod Lists ////// - std::shared_ptr loaderModList() const; - std::shared_ptr coreModList() const; - std::shared_ptr nilModList() const; - std::shared_ptr resourcePackList() const; - std::shared_ptr texturePackList() const; - std::shared_ptr shaderPackList() const; - std::shared_ptr worldList() const; - std::shared_ptr gameOptionsModel() const; + std::shared_ptr loaderModList(); + std::shared_ptr coreModList(); + std::shared_ptr nilModList(); + std::shared_ptr resourcePackList(); + std::shared_ptr texturePackList(); + std::shared_ptr shaderPackList(); + std::shared_ptr worldList(); + std::shared_ptr gameOptionsModel(); ////// Launch stuff ////// Task::Ptr createUpdateTask(Net::Mode mode) override; diff --git a/launcher/minecraft/WorldList.cpp b/launcher/minecraft/WorldList.cpp index df6b4ecc..0feee299 100644 --- a/launcher/minecraft/WorldList.cpp +++ b/launcher/minecraft/WorldList.cpp @@ -45,7 +45,7 @@ #include #include -WorldList::WorldList(const QString &dir, std::shared_ptr instance) +WorldList::WorldList(const QString &dir, BaseInstance* instance) : QAbstractListModel(), m_instance(instance), m_dir(dir) { FS::ensureFolderPathExists(m_dir.absolutePath()); diff --git a/launcher/minecraft/WorldList.h b/launcher/minecraft/WorldList.h index 10fb4e3c..96b64193 100644 --- a/launcher/minecraft/WorldList.h +++ b/launcher/minecraft/WorldList.h @@ -50,7 +50,7 @@ public: IconFileRole }; - WorldList(const QString &dir, std::shared_ptr instance); + WorldList(const QString &dir, BaseInstance* instance); virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; @@ -128,7 +128,7 @@ signals: void changed(); protected: - std::shared_ptr m_instance; + BaseInstance* m_instance; QFileSystemWatcher *m_watcher; bool is_watching; QDir m_dir; diff --git a/launcher/minecraft/mod/ModFolderModel.cpp b/launcher/minecraft/mod/ModFolderModel.cpp index 6ae25d33..5e3b31e0 100644 --- a/launcher/minecraft/mod/ModFolderModel.cpp +++ b/launcher/minecraft/mod/ModFolderModel.cpp @@ -54,7 +54,7 @@ #include "minecraft/mod/tasks/ModFolderLoadTask.h" #include "modplatform/ModIndex.h" -ModFolderModel::ModFolderModel(const QString& dir, std::shared_ptr instance, bool is_indexed, bool create_dir) +ModFolderModel::ModFolderModel(const QString& dir, BaseInstance* instance, bool is_indexed, bool create_dir) : ResourceFolderModel(QDir(dir), instance, nullptr, create_dir), m_is_indexed(is_indexed) { m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::VERSION, SortType::DATE, SortType::PROVIDER }; diff --git a/launcher/minecraft/mod/ModFolderModel.h b/launcher/minecraft/mod/ModFolderModel.h index 46f5087f..d337fe29 100644 --- a/launcher/minecraft/mod/ModFolderModel.h +++ b/launcher/minecraft/mod/ModFolderModel.h @@ -75,7 +75,7 @@ public: Enable, Toggle }; - ModFolderModel(const QString &dir, std::shared_ptr instance, bool is_indexed = false, bool create_dir = true); + ModFolderModel(const QString &dir, BaseInstance* instance, bool is_indexed = false, bool create_dir = true); QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; diff --git a/launcher/minecraft/mod/ResourceFolderModel.cpp b/launcher/minecraft/mod/ResourceFolderModel.cpp index e1973468..d2d875e4 100644 --- a/launcher/minecraft/mod/ResourceFolderModel.cpp +++ b/launcher/minecraft/mod/ResourceFolderModel.cpp @@ -16,7 +16,7 @@ #include "tasks/Task.h" -ResourceFolderModel::ResourceFolderModel(QDir dir, std::shared_ptr instance, QObject* parent, bool create_dir) +ResourceFolderModel::ResourceFolderModel(QDir dir, BaseInstance* instance, QObject* parent, bool create_dir) : QAbstractListModel(parent), m_dir(dir), m_instance(instance), m_watcher(this) { if (create_dir) { diff --git a/launcher/minecraft/mod/ResourceFolderModel.h b/launcher/minecraft/mod/ResourceFolderModel.h index fdf5f331..0a35e1bc 100644 --- a/launcher/minecraft/mod/ResourceFolderModel.h +++ b/launcher/minecraft/mod/ResourceFolderModel.h @@ -26,7 +26,7 @@ class QSortFilterProxyModel; class ResourceFolderModel : public QAbstractListModel { Q_OBJECT public: - ResourceFolderModel(QDir, std::shared_ptr, QObject* parent = nullptr, bool create_dir = true); + ResourceFolderModel(QDir, BaseInstance* instance, QObject* parent = nullptr, bool create_dir = true); ~ResourceFolderModel() override; /** Starts watching the paths for changes. @@ -191,7 +191,7 @@ class ResourceFolderModel : public QAbstractListModel { bool m_can_interact = true; QDir m_dir; - std::shared_ptr m_instance; + BaseInstance* m_instance; QFileSystemWatcher m_watcher; bool m_is_watching = false; diff --git a/launcher/minecraft/mod/ResourcePackFolderModel.cpp b/launcher/minecraft/mod/ResourcePackFolderModel.cpp index 6eba4e2e..c12d1f23 100644 --- a/launcher/minecraft/mod/ResourcePackFolderModel.cpp +++ b/launcher/minecraft/mod/ResourcePackFolderModel.cpp @@ -45,7 +45,7 @@ #include "minecraft/mod/tasks/BasicFolderLoadTask.h" #include "minecraft/mod/tasks/LocalResourcePackParseTask.h" -ResourcePackFolderModel::ResourcePackFolderModel(const QString& dir, std::shared_ptr instance) +ResourcePackFolderModel::ResourcePackFolderModel(const QString& dir, BaseInstance* instance) : ResourceFolderModel(QDir(dir), instance) { m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::PACK_FORMAT, SortType::DATE }; diff --git a/launcher/minecraft/mod/ResourcePackFolderModel.h b/launcher/minecraft/mod/ResourcePackFolderModel.h index 66d5a074..db4b14fb 100644 --- a/launcher/minecraft/mod/ResourcePackFolderModel.h +++ b/launcher/minecraft/mod/ResourcePackFolderModel.h @@ -17,7 +17,7 @@ public: NUM_COLUMNS }; - explicit ResourcePackFolderModel(const QString &dir, std::shared_ptr instance); + explicit ResourcePackFolderModel(const QString &dir, BaseInstance* instance); [[nodiscard]] QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; diff --git a/launcher/minecraft/mod/ShaderPackFolderModel.h b/launcher/minecraft/mod/ShaderPackFolderModel.h index 6f3f2811..dc5acf80 100644 --- a/launcher/minecraft/mod/ShaderPackFolderModel.h +++ b/launcher/minecraft/mod/ShaderPackFolderModel.h @@ -6,7 +6,7 @@ class ShaderPackFolderModel : public ResourceFolderModel { Q_OBJECT public: - explicit ShaderPackFolderModel(const QString& dir, std::shared_ptr instance) + explicit ShaderPackFolderModel(const QString& dir, BaseInstance* instance) : ResourceFolderModel(QDir(dir), instance) {} }; diff --git a/launcher/minecraft/mod/TexturePackFolderModel.cpp b/launcher/minecraft/mod/TexturePackFolderModel.cpp index 1e218537..c6609ed1 100644 --- a/launcher/minecraft/mod/TexturePackFolderModel.cpp +++ b/launcher/minecraft/mod/TexturePackFolderModel.cpp @@ -39,7 +39,7 @@ #include "minecraft/mod/tasks/BasicFolderLoadTask.h" #include "minecraft/mod/tasks/LocalTexturePackParseTask.h" -TexturePackFolderModel::TexturePackFolderModel(const QString& dir, std::shared_ptr instance) +TexturePackFolderModel::TexturePackFolderModel(const QString& dir, BaseInstance* instance) : ResourceFolderModel(QDir(dir), instance) {} diff --git a/launcher/minecraft/mod/TexturePackFolderModel.h b/launcher/minecraft/mod/TexturePackFolderModel.h index 246997bd..425a71e4 100644 --- a/launcher/minecraft/mod/TexturePackFolderModel.h +++ b/launcher/minecraft/mod/TexturePackFolderModel.h @@ -43,7 +43,7 @@ class TexturePackFolderModel : public ResourceFolderModel Q_OBJECT public: - explicit TexturePackFolderModel(const QString &dir, std::shared_ptr instance); + explicit TexturePackFolderModel(const QString &dir, BaseInstance* instance); [[nodiscard]] Task* createUpdateTask() override; [[nodiscard]] Task* createParseTask(Resource&) override; }; diff --git a/launcher/ui/dialogs/NewInstanceDialog.cpp b/launcher/ui/dialogs/NewInstanceDialog.cpp index 64ed7673..aafaf220 100644 --- a/launcher/ui/dialogs/NewInstanceDialog.cpp +++ b/launcher/ui/dialogs/NewInstanceDialog.cpp @@ -99,7 +99,7 @@ NewInstanceDialog::NewInstanceDialog(const QString & initialGroup, const QString // NOTE: m_buttons must be initialized before PageContainer, because it indirectly accesses m_buttons through setSuggestedPack! Do not move this below. m_buttons = new QDialogButtonBox(QDialogButtonBox::Help | QDialogButtonBox::Ok | QDialogButtonBox::Cancel); - m_container = new PageContainer(this); + m_container = new PageContainer(this, {}, this); m_container->setSizePolicy(QSizePolicy::Policy::Preferred, QSizePolicy::Policy::Expanding); m_container->layout()->setContentsMargins(0, 0, 0, 0); ui->verticalLayout->insertWidget(2, m_container); diff --git a/launcher/ui/dialogs/ResourceDownloadDialog.cpp b/launcher/ui/dialogs/ResourceDownloadDialog.cpp index edb7d063..d2a8d33e 100644 --- a/launcher/ui/dialogs/ResourceDownloadDialog.cpp +++ b/launcher/ui/dialogs/ResourceDownloadDialog.cpp @@ -89,7 +89,7 @@ void ResourceDownloadDialog::reject() // won't work with subclasses if we put it in this ctor. void ResourceDownloadDialog::initializeContainer() { - m_container = new PageContainer(this); + m_container = new PageContainer(this, {}, this); m_container->setSizePolicy(QSizePolicy::Policy::Preferred, QSizePolicy::Policy::Expanding); m_container->layout()->setContentsMargins(0, 0, 0, 0); m_vertical_layout.addWidget(m_container); diff --git a/launcher/ui/widgets/PageContainer.cpp b/launcher/ui/widgets/PageContainer.cpp index 0a06a351..b9b17b42 100644 --- a/launcher/ui/widgets/PageContainer.cpp +++ b/launcher/ui/widgets/PageContainer.cpp @@ -87,7 +87,9 @@ PageContainer::PageContainer(BasePageProvider *pageProvider, QString defaultId, auto pages = pageProvider->getPages(); for (auto page : pages) { - page->stackIndex = m_pageStack->addWidget(dynamic_cast(page)); + auto widget = dynamic_cast(page); + widget->setParent(this); + page->stackIndex = m_pageStack->addWidget(widget); page->listIndex = counter; page->setParentContainer(this); counter++; diff --git a/tests/ResourceFolderModel_test.cpp b/tests/ResourceFolderModel_test.cpp index 054d81c4..962d89f1 100644 --- a/tests/ResourceFolderModel_test.cpp +++ b/tests/ResourceFolderModel_test.cpp @@ -90,9 +90,7 @@ slots: QEventLoop loop; - InstancePtr instance; - - ModFolderModel m(tempDir.path(), instance, true); + ModFolderModel m(tempDir.path(), nullptr, true); connect(&m, &ModFolderModel::updateFinished, &loop, &QEventLoop::quit); @@ -116,8 +114,7 @@ slots: QString folder = source + '/'; QTemporaryDir tempDir; QEventLoop loop; - InstancePtr instance; - ModFolderModel m(tempDir.path(), instance, true); + ModFolderModel m(tempDir.path(), nullptr, true); connect(&m, &ModFolderModel::updateFinished, &loop, &QEventLoop::quit); @@ -140,8 +137,7 @@ slots: void test_addFromWatch() { QString source = QFINDTESTDATA("testdata/ResourceFolderModel"); - InstancePtr instance; - ModFolderModel model(source, instance); + ModFolderModel model(source, nullptr); QCOMPARE(model.size(), 0); @@ -161,9 +157,7 @@ slots: QString file_mod = QFINDTESTDATA("testdata/ResourceFolderModel/supercoolmod.jar"); QTemporaryDir tmp; - InstancePtr instance; - - ResourceFolderModel model(QDir(tmp.path()), instance); + ResourceFolderModel model(QDir(tmp.path()), nullptr); QCOMPARE(model.size(), 0); @@ -214,8 +208,7 @@ slots: QString file_mod = QFINDTESTDATA("testdata/ResourceFolderModel/supercoolmod.jar"); QTemporaryDir tmp; - InstancePtr instance; - ResourceFolderModel model(tmp.path(), instance); + ResourceFolderModel model(tmp.path(), nullptr); QCOMPARE(model.size(), 0); -- cgit From 1840505a0f887ebfc2c719113873ea3345b133fb Mon Sep 17 00:00:00 2001 From: Alexandru Ionut Tripon Date: Sat, 3 Jun 2023 00:04:06 +0300 Subject: Fix crash when selecting same mod from different providers (#1029) --- launcher/ResourceDownloadTask.cpp | 49 +++++++------ launcher/ResourceDownloadTask.h | 58 ++++++++------- launcher/modplatform/ModIndex.h | 35 +++++---- launcher/modplatform/flame/FlameCheckUpdate.cpp | 22 +++--- .../modplatform/modrinth/ModrinthCheckUpdate.cpp | 28 +++---- launcher/ui/dialogs/ResourceDownloadDialog.cpp | 82 ++++++++++----------- launcher/ui/dialogs/ResourceDownloadDialog.h | 17 ++--- launcher/ui/pages/modplatform/ModPage.cpp | 27 ++++--- launcher/ui/pages/modplatform/ModPage.h | 15 ++-- launcher/ui/pages/modplatform/ResourceModel.cpp | 65 ++++++++++++++--- launcher/ui/pages/modplatform/ResourceModel.h | 12 +++ launcher/ui/pages/modplatform/ResourcePage.cpp | 85 +++++++++++++--------- launcher/ui/pages/modplatform/ResourcePage.h | 18 +++-- launcher/ui/pages/modplatform/ShaderPackPage.cpp | 16 ++-- launcher/ui/pages/modplatform/ShaderPackPage.h | 4 +- launcher/ui/widgets/PageContainer.cpp | 5 ++ launcher/ui/widgets/PageContainer.h | 1 + 17 files changed, 314 insertions(+), 225 deletions(-) (limited to 'launcher/ui/dialogs') diff --git a/launcher/ResourceDownloadTask.cpp b/launcher/ResourceDownloadTask.cpp index 61b918aa..06c03c77 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 73ad2d07..09147c8c 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 8d0223f9..82da2ab2 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 06a89502..e09aeb3d 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 d1be7209..4fe91ce7 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 d2a8d33e..61c48e75 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; + for (auto page : m_container->getPages()) { + static_cast(page)->removeResourceFromPage(pack_name); + } + setButtonStatus(); } -void ResourceDownloadDialog::removeResource(ModPlatform::IndexedPack& pack, ModPlatform::IndexedVersion& ver) +void ResourceDownloadDialog::setButtonStatus() { - 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; + auto selected = false; + for (auto page : m_container->getPages()) { + auto res = static_cast(page); + selected = selected || res->hasSelectedPacks(); } - - // 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()); + 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 5678dc8b..5b5b48c6 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 04be43ad..95064d16 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 4ea55efa..5510c191 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 472aa851..a5ea1ca9 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 1ec42cda..69e23473 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 f75bb886..736034ad 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 1896d53e..b4a87f57 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 251c07e7..fbf94e84 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 9039c4d9..fcf6d4a7 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 b9b17b42..38a22897 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 97e294dc..ad74d43a 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) -- cgit From e26827b84922d7d84c3ee83dfed58759b1c0ca15 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Sat, 3 Jun 2023 13:39:42 +0100 Subject: 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 (limited to 'launcher/ui/dialogs') diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index 82ed0930..ce2771a4 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 00000000..f2b6f442 --- /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 00000000..20853404 --- /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 603a723c..8ecd91a9 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 d96f4537..5e801875 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 1551cc60..06e4693e 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 63e3f016..98f1d5fc 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; }; -- cgit From 37b4f606c8e0853c831f792e7238587c66222176 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Mon, 5 Jun 2023 17:52:48 +0100 Subject: 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(-) (limited to 'launcher/ui/dialogs') diff --git a/launcher/ui/dialogs/ExportMrPackDialog.cpp b/launcher/ui/dialogs/ExportMrPackDialog.cpp index 06e4693e..239873f6 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 98f1d5fc..1c70c4ae 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 f154d210..9a789737 100644 --- a/launcher/ui/dialogs/ExportMrPackDialog.ui +++ b/launcher/ui/dialogs/ExportMrPackDialog.ui @@ -51,7 +51,11 @@
- + + + 1.0.0 + +
-- cgit From a807b231a75fdcb95408aa35a3143e2e5f5ca60f Mon Sep 17 00:00:00 2001 From: leo78913 Date: Tue, 6 Jun 2023 15:14:50 -0300 Subject: fix: fix crash when selecting resource/texture/shader packs Signed-off-by: leo78913 --- launcher/ui/dialogs/ResourceDownloadDialog.cpp | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'launcher/ui/dialogs') diff --git a/launcher/ui/dialogs/ResourceDownloadDialog.cpp b/launcher/ui/dialogs/ResourceDownloadDialog.cpp index 61c48e75..6d90480f 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; } -- cgit