From e8ee4497f77a571b305a48b70f84c8729b800859 Mon Sep 17 00:00:00 2001 From: chmodsayshello Date: Sun, 25 Dec 2022 23:39:38 +0100 Subject: store logs in sperate directory Signed-off-by: chmodsayshello --- launcher/Application.cpp | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/launcher/Application.cpp b/launcher/Application.cpp index ff34a168..f68e8792 100644 --- a/launcher/Application.cpp +++ b/launcher/Application.cpp @@ -394,7 +394,11 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv) // init the logger { - static const QString logBase = BuildConfig.LAUNCHER_NAME + "-%0.log"; + static const QString logBase = "logs/"+BuildConfig.LAUNCHER_NAME + "-%0.log"; + QDir logDir = QDir(dataPath); + if(!logDir.exists("logs")) { + logDir.mkpath("logs"); //this can fail, but there is no need to throw an error *yet*, since it also triggers the error message below! + } auto moveFile = [](const QString &oldName, const QString &newName) { QFile::remove(newName); @@ -415,11 +419,11 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv) QString( "The launcher couldn't create a log file - the data folder is not writable.\n" "\n" - "Make sure you have write permissions to the data folder.\n" + "Make sure you have write permissions to the logs folder.\n" "(%1)\n" "\n" "The launcher cannot continue until you fix this problem." - ).arg(dataPath) + ).arg(dataPath+"/logs") ); return; } @@ -1666,6 +1670,7 @@ bool Application::handleDataMigration(const QString& currentData, matcher->add(std::make_shared(configFile)); matcher->add(std::make_shared( BuildConfig.LAUNCHER_CONFIGFILE)); // it's possible that we already used that directory before + matcher->add(std::make_shared("logs/")); matcher->add(std::make_shared("accounts.json")); matcher->add(std::make_shared("accounts/")); matcher->add(std::make_shared("assets/")); -- cgit 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/CMakeLists.txt | 3 ++ launcher/ui/MainWindow.cpp | 16 ++++++- launcher/ui/MainWindow.h | 5 +- launcher/ui/MainWindow.ui | 23 +++++++-- launcher/ui/dialogs/ExportMrPackDialog.cpp | 31 ++++++++++++ launcher/ui/dialogs/ExportMrPackDialog.h | 38 +++++++++++++++ launcher/ui/dialogs/ExportMrPackDialog.ui | 77 ++++++++++++++++++++++++++++++ 7 files changed, 187 insertions(+), 6 deletions(-) create mode 100644 launcher/ui/dialogs/ExportMrPackDialog.cpp create mode 100644 launcher/ui/dialogs/ExportMrPackDialog.h create mode 100644 launcher/ui/dialogs/ExportMrPackDialog.ui diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index 202e633c..0b27ff85 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -851,6 +851,8 @@ SET(LAUNCHER_SOURCES ui/dialogs/EditAccountDialog.h ui/dialogs/ExportInstanceDialog.cpp ui/dialogs/ExportInstanceDialog.h + ui/dialogs/ExportMrPackDialog.cpp + ui/dialogs/ExportMrPackDialog.h ui/dialogs/IconPickerDialog.cpp ui/dialogs/IconPickerDialog.h ui/dialogs/ImportResourceDialog.cpp @@ -995,6 +997,7 @@ qt_wrap_ui(LAUNCHER_UI ui/dialogs/ProfileSelectDialog.ui ui/dialogs/SkinUploadDialog.ui ui/dialogs/ExportInstanceDialog.ui + ui/dialogs/ExportMrPackDialog.ui ui/dialogs/IconPickerDialog.ui ui/dialogs/ImportResourceDialog.ui ui/dialogs/MSALoginDialog.ui diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp index 8490b292..ff7a12c2 100644 --- a/launcher/ui/MainWindow.cpp +++ b/launcher/ui/MainWindow.cpp @@ -2,7 +2,7 @@ /* * Prism Launcher - Minecraft Launcher * Copyright (C) 2022 Sefa Eyeoglu - * Copyright (C) 2022 TheKodeToad + * Copyright (C) 2023 TheKodeToad * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -107,6 +107,7 @@ #include "ui/dialogs/CopyInstanceDialog.h" #include "ui/dialogs/EditAccountDialog.h" #include "ui/dialogs/ExportInstanceDialog.h" +#include "ui/dialogs/ExportMrPackDialog.h" #include "ui/dialogs/ImportResourceDialog.h" #include "ui/themes/ITheme.h" #include "ui/themes/ThemeManager.h" @@ -397,6 +398,8 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWi // removing this looks stupid view->setFocus(); + ui->actionExportInstance->setMenu(ui->exportInstanceMenu); + retranslateUi(); } @@ -1345,7 +1348,7 @@ void MainWindow::on_actionDeleteInstance_triggered() APPLICATION->instances()->deleteInstance(id); } -void MainWindow::on_actionExportInstance_triggered() +void MainWindow::on_actionExportInstanceZip_triggered() { if (m_selectedInstance) { @@ -1354,6 +1357,15 @@ void MainWindow::on_actionExportInstance_triggered() } } +void MainWindow::on_actionExportInstanceMrPack_triggered() +{ + if (m_selectedInstance) + { + ExportMrPackDialog dlg(m_selectedInstance, this); + dlg.exec(); + } +} + void MainWindow::on_actionRenameInstance_triggered() { if (m_selectedInstance) diff --git a/launcher/ui/MainWindow.h b/launcher/ui/MainWindow.h index 3a42c34e..35b4792d 100644 --- a/launcher/ui/MainWindow.h +++ b/launcher/ui/MainWindow.h @@ -2,6 +2,7 @@ /* * PolyMC - Minecraft Launcher * Copyright (C) 2022 Sefa Eyeoglu + * Copyright (C) 2023 TheKodeToad * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -151,7 +152,9 @@ private slots: void deleteGroup(); void undoTrashInstance(); - void on_actionExportInstance_triggered(); + inline void on_actionExportInstance_triggered() { on_actionExportInstanceZip_triggered(); } + void on_actionExportInstanceZip_triggered(); + void on_actionExportInstanceMrPack_triggered(); void on_actionRenameInstance_triggered(); diff --git a/launcher/ui/MainWindow.ui b/launcher/ui/MainWindow.ui index 2b6a10b1..2ede882d 100644 --- a/launcher/ui/MainWindow.ui +++ b/launcher/ui/MainWindow.ui @@ -459,10 +459,27 @@ E&xport... - Export the selected instance as a zip file. + Export the selected instance to supported formats. - - Ctrl+E + + + + + + + + Prism Launcher (zip) + + + Export the instance as a ZIP. + + + + + Modrinth (mrpack) + + + Export to a Modrinth modpack. diff --git a/launcher/ui/dialogs/ExportMrPackDialog.cpp b/launcher/ui/dialogs/ExportMrPackDialog.cpp new file mode 100644 index 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 8b897ac714f5317c6544c0fa6058495544301391 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Wed, 1 Mar 2023 20:45:07 +0000 Subject: Fix menu being set as central widget Signed-off-by: TheKodeToad --- launcher/ui/MainWindow.ui | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/launcher/ui/MainWindow.ui b/launcher/ui/MainWindow.ui index 2ede882d..4a89bc10 100644 --- a/launcher/ui/MainWindow.ui +++ b/launcher/ui/MainWindow.ui @@ -150,6 +150,10 @@ + + + + @@ -462,25 +466,15 @@ Export the selected instance to supported formats. - - - - Prism Launcher (zip) - - Export the instance as a ZIP. - Modrinth (mrpack) - - Export to a Modrinth modpack. - -- 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(-) 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 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(-) 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(-) 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(-) 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 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 f28a7b9d083a9181c04c7100f50152c116e72cb6 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Thu, 2 Mar 2023 19:50:42 +0000 Subject: Add PackIgnoreProxy.h to cmake Signed-off-by: TheKodeToad --- launcher/CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index f3af2ebf..380e8336 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -667,6 +667,7 @@ SET(LAUNCHER_SOURCES SkinUtils.cpp SkinUtils.h PackIgnoreProxy.cpp + PackIgnoreProxy.h # GUI - setup wizard ui/setupwizard/SetupWizard.h -- 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(-) 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 dcaa907fede11c8f0aeddde8a78e8d9397eaee2f Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Fri, 3 Mar 2023 11:24:10 +0000 Subject: Mod loader support Signed-off-by: TheKodeToad --- launcher/modplatform/modrinth/ModrinthPackExportTask.cpp | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp b/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp index 331fbf94..1bb78cae 100644 --- a/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp +++ b/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp @@ -102,10 +102,22 @@ QByteArray ModrinthPackExportTask::generateIndex() MinecraftInstance* mc = dynamic_cast(instance.get()); if (mc) { auto profile = mc->getPackProfile(); + // collect all supported components auto minecraft = profile->getComponent("net.minecraft"); + auto quilt = profile->getComponent("org.quiltmc.quilt-loader"); + auto fabric = profile->getComponent("net.fabricmc.fabric-loader"); + auto forge = profile->getComponent("net.minecraftforge"); + // convert all available components to mrpack dependencies QJsonObject dependencies; - dependencies["minecraft"] = minecraft->m_version; + if (minecraft != nullptr) + dependencies["minecraft"] = minecraft->m_version; + if (quilt != nullptr) + dependencies["quilt-loader"] = quilt->m_version; + if (fabric != nullptr) + dependencies["fabric-loader"] = fabric->m_version; + if (forge != nullptr) + dependencies["forge"] = forge->m_version; obj["dependencies"] = dependencies; } -- cgit From 2343aad088e302bf9f4f75eb6c4d6d1da3c7fbe3 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Fri, 3 Mar 2023 15:00:07 +0000 Subject: Make it work! (TODO make it not crash) Signed-off-by: TheKodeToad --- .../modrinth/ModrinthPackExportTask.cpp | 119 ++++++++++++++++++--- .../modplatform/modrinth/ModrinthPackExportTask.h | 7 +- 2 files changed, 108 insertions(+), 18 deletions(-) diff --git a/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp b/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp index 1bb78cae..a319eec4 100644 --- a/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp +++ b/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp @@ -17,12 +17,17 @@ */ #include "ModrinthPackExportTask.h" -#include + +#include +#include #include #include +#include +#include "Json.h" #include "MMCZip.h" #include "minecraft/MinecraftInstance.h" #include "minecraft/PackProfile.h" +#include "modplatform/modrinth/ModrinthAPI.h" ModrinthPackExportTask::ModrinthPackExportTask(const QString& name, const QString& version, @@ -35,13 +40,76 @@ ModrinthPackExportTask::ModrinthPackExportTask(const QString& name, void ModrinthPackExportTask::executeTask() { - QtConcurrent::run(QThreadPool::globalInstance(), [this] { - QFileInfoList files; - if (!MMCZip::collectFileListRecursively(instance->gameRoot(), nullptr, &files, filter)) { - emitFailed(tr("Could not collect list of files")); - return; + setStatus(tr("Searching for files...")); + + QFileInfoList files; + if (!MMCZip::collectFileListRecursively(instance->gameRoot(), nullptr, &files, filter)) { + emitFailed(tr("Could not collect list of files")); + return; + } + + QDir mc(instance->gameRoot()); + + ModrinthAPI api; + + static const QStringList prefixes({ "mods", "coremods", "resourcepacks", "texturepacks", "shaderpacks" }); + // hash -> file + QMap hashes; + + for (QFileInfo file : files) { + QString relative = mc.relativeFilePath(file.absoluteFilePath()); + // require sensible file types + if (!(relative.endsWith(".zip") || relative.endsWith(".jar") || relative.endsWith(".litemod"))) + continue; + + if (!std::any_of(prefixes.begin(), prefixes.end(), + [&relative](const QString& prefix) { return relative.startsWith(prefix + QDir::separator()); })) + continue; + + QCryptographicHash hash(QCryptographicHash::Algorithm::Sha512); + + QFile openFile(file.absoluteFilePath()); + if (!openFile.open(QFile::ReadOnly)) { + qWarning() << "Could not open" << file << "for hashing"; + continue; } + if (!hash.addData(&openFile)) { + qWarning() << "Could not add hash data for" << file; + continue; + } + + hashes[hash.result().toHex()] = relative; + } + + QByteArray* response = new QByteArray; + Task::Ptr versionsTask = api.currentVersions(hashes.keys(), "sha512", response); + connect(versionsTask.get(), &NetJob::succeeded, this, [this, mc, files, hashes, response, versionsTask]() { + // file -> url + QMap resolved; + + try { + QJsonDocument doc = Json::requireDocument(*response); + for (auto iter = hashes.keyBegin(); iter != hashes.keyEnd(); iter++) { + QJsonObject obj = doc[*iter].toObject(); + if (obj.isEmpty()) + continue; + + QJsonArray files = obj["files"].toArray(); + if (auto fileIter = std::find_if(files.begin(), files.end(), + [&iter](const QJsonValue& file) { return file["hashes"]["sha512"] == *iter; }); + fileIter != files.end()) { + // map the file to the url + resolved[hashes[*iter]] = ResolvedFile{ fileIter->toObject()["hashes"].toObject()["sha1"].toString(), *iter, + fileIter->toObject()["url"].toString(), fileIter->toObject()["size"].toInt() }; + } + } + } catch (Json::JsonException& e) { + qWarning() << "Failed to parse versions response" << e.what(); + } + + delete response; + setStatus("Adding files..."); QuaZip zip(output); @@ -55,25 +123,19 @@ void ModrinthPackExportTask::executeTask() QuaZipFile indexFile(&zip); if (!indexFile.open(QIODevice::WriteOnly, QuaZipNewInfo("modrinth.index.json"))) { QFile::remove(output); - emitFailed(tr("Could not create index")); return; } - indexFile.write(generateIndex()); + indexFile.write(generateIndex(resolved)); } - // should exist - QDir dotMinecraft(instance->gameRoot()); - { size_t i = 0; for (const QFileInfo& file : files) { setProgress(i, files.length()); - if (!JlCompress::compressFile(&zip, file.absoluteFilePath(), - "overrides/" + dotMinecraft.relativeFilePath(file.absoluteFilePath()))) { - emitFailed(tr("Could not compress %1").arg(file.absoluteFilePath())); - return; - } + QString relative = mc.relativeFilePath(file.absoluteFilePath()); + if (!resolved.contains(relative) && !JlCompress::compressFile(&zip, file.absoluteFilePath(), "overrides/" + relative)) + qWarning() << "Could not compress" << file; i++; } } @@ -88,9 +150,11 @@ void ModrinthPackExportTask::executeTask() emitSucceeded(); }); + connect(versionsTask.get(), &NetJob::failed, this, [this](const QString& reason) { emitFailed(reason); }); + versionsTask->start(); } -QByteArray ModrinthPackExportTask::generateIndex() +QByteArray ModrinthPackExportTask::generateIndex(const QMap& urls) { QJsonObject obj; obj["formatVersion"] = 1; @@ -121,5 +185,26 @@ QByteArray ModrinthPackExportTask::generateIndex() obj["dependencies"] = dependencies; } + QJsonArray files; + QMapIterator iterator(urls); + while (iterator.hasNext()) { + iterator.next(); + + const ResolvedFile& value = iterator.value(); + + QJsonObject file; + file["path"] = iterator.key(); + file["downloads"] = QJsonArray({ iterator.value().url }); + + QJsonObject hashes; + hashes["sha1"] = value.sha1; + hashes["sha512"] = value.sha512; + file["hashes"] = hashes; + file["fileSize"] = value.size; + + files << file; + } + obj["files"] = files; + return QJsonDocument(obj).toJson(QJsonDocument::Compact); } \ No newline at end of file diff --git a/launcher/modplatform/modrinth/ModrinthPackExportTask.h b/launcher/modplatform/modrinth/ModrinthPackExportTask.h index c38be204..974c9f0e 100644 --- a/launcher/modplatform/modrinth/ModrinthPackExportTask.h +++ b/launcher/modplatform/modrinth/ModrinthPackExportTask.h @@ -40,5 +40,10 @@ class ModrinthPackExportTask : public Task { const QString output; const MMCZip::FilterFunction filter; - QByteArray generateIndex(); + struct ResolvedFile { + QString sha1, sha512, url; + int size; + }; + + QByteArray generateIndex(const QMap& urls); }; \ No newline at end of file -- 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(-) 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 a2716f5cf6d72168ab5aa4415c046505e42404f1 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Sat, 4 Mar 2023 10:24:25 +0000 Subject: Improve code Even more broken now (it is stuck loading forever)! Signed-off-by: TheKodeToad --- .../modrinth/ModrinthPackExportTask.cpp | 153 +++++++++++---------- .../modplatform/modrinth/ModrinthPackExportTask.h | 14 +- 2 files changed, 93 insertions(+), 74 deletions(-) diff --git a/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp b/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp index a319eec4..d7a43c7a 100644 --- a/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp +++ b/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp @@ -29,6 +29,8 @@ #include "minecraft/PackProfile.h" #include "modplatform/modrinth/ModrinthAPI.h" +const QStringList ModrinthPackExportTask::PREFIXES = QStringList({ "mods", "coremods", "resourcepacks", "texturepacks", "shaderpacks" }); + ModrinthPackExportTask::ModrinthPackExportTask(const QString& name, const QString& version, const QString& summary, @@ -41,28 +43,34 @@ ModrinthPackExportTask::ModrinthPackExportTask(const QString& name, void ModrinthPackExportTask::executeTask() { setStatus(tr("Searching for files...")); + setProgress(0, 0); + collectFiles(); + + QByteArray* response = new QByteArray; + Task::Ptr versionsTask = api.currentVersions(fileHashes.values(), "sha512", response); + connect(versionsTask.get(), &NetJob::succeeded, [this, response]() { parseApiResponse(response); }); + connect(versionsTask.get(), &NetJob::failed, this, &ModrinthPackExportTask::emitFailed); + versionsTask->start(); +} - QFileInfoList files; +void ModrinthPackExportTask::collectFiles() +{ + files.clear(); if (!MMCZip::collectFileListRecursively(instance->gameRoot(), nullptr, &files, filter)) { emitFailed(tr("Could not collect list of files")); return; } - QDir mc(instance->gameRoot()); - - ModrinthAPI api; - - static const QStringList prefixes({ "mods", "coremods", "resourcepacks", "texturepacks", "shaderpacks" }); - // hash -> file - QMap hashes; + fileHashes.clear(); + QDir mc(instance->gameRoot()); for (QFileInfo file : files) { QString relative = mc.relativeFilePath(file.absoluteFilePath()); // require sensible file types if (!(relative.endsWith(".zip") || relative.endsWith(".jar") || relative.endsWith(".litemod"))) continue; - if (!std::any_of(prefixes.begin(), prefixes.end(), + if (!std::any_of(PREFIXES.begin(), PREFIXES.end(), [&relative](const QString& prefix) { return relative.startsWith(prefix + QDir::separator()); })) continue; @@ -79,82 +87,81 @@ void ModrinthPackExportTask::executeTask() continue; } - hashes[hash.result().toHex()] = relative; + fileHashes[relative] = hash.result().toHex(); } +} - QByteArray* response = new QByteArray; - Task::Ptr versionsTask = api.currentVersions(hashes.keys(), "sha512", response); - connect(versionsTask.get(), &NetJob::succeeded, this, [this, mc, files, hashes, response, versionsTask]() { - // file -> url - QMap resolved; - - try { - QJsonDocument doc = Json::requireDocument(*response); - for (auto iter = hashes.keyBegin(); iter != hashes.keyEnd(); iter++) { - QJsonObject obj = doc[*iter].toObject(); - if (obj.isEmpty()) - continue; - - QJsonArray files = obj["files"].toArray(); - if (auto fileIter = std::find_if(files.begin(), files.end(), - [&iter](const QJsonValue& file) { return file["hashes"]["sha512"] == *iter; }); - fileIter != files.end()) { - // map the file to the url - resolved[hashes[*iter]] = ResolvedFile{ fileIter->toObject()["hashes"].toObject()["sha1"].toString(), *iter, - fileIter->toObject()["url"].toString(), fileIter->toObject()["size"].toInt() }; - } +void ModrinthPackExportTask::parseApiResponse(QByteArray* response) +{ + QMap resolved; + + try { + QJsonDocument doc = Json::requireDocument(*response); + + QMapIterator iterator(fileHashes); + while (iterator.hasNext()) { + iterator.next(); + + QJsonObject obj = doc[iterator.value()].toObject(); + if (obj.isEmpty()) + continue; + + QJsonArray files = obj["files"].toArray(); + if (auto fileIter = std::find_if(files.begin(), files.end(), + [&iterator](const QJsonValue& file) { return file["hashes"]["sha512"] == iterator.value(); }); + fileIter != files.end()) { + // map the file to the url + resolved[iterator.key()] = ResolvedFile{ fileIter->toObject()["hashes"].toObject()["sha1"].toString(), iterator.value(), + fileIter->toObject()["url"].toString(), fileIter->toObject()["size"].toInt() }; } - } catch (Json::JsonException& e) { - qWarning() << "Failed to parse versions response" << e.what(); } + } catch (Json::JsonException& e) { + qWarning() << "Failed to parse versions response" << e.what(); + } - delete response; - - setStatus("Adding files..."); + buildZip(resolved); +} - QuaZip zip(output); - if (!zip.open(QuaZip::mdCreate)) { - QFile::remove(output); - emitFailed(tr("Could not create file")); - return; - } +void ModrinthPackExportTask::buildZip(const QMap& resolvedFiles) +{ + setStatus("Adding files..."); + QuaZip zip(output); + if (!zip.open(QuaZip::mdCreate)) { + QFile::remove(output); + emitFailed(tr("Could not create file")); + return; + } - { - QuaZipFile indexFile(&zip); - if (!indexFile.open(QIODevice::WriteOnly, QuaZipNewInfo("modrinth.index.json"))) { - QFile::remove(output); - emitFailed(tr("Could not create index")); - return; - } - indexFile.write(generateIndex(resolved)); - } + QuaZipFile indexFile(&zip); + if (!indexFile.open(QIODevice::WriteOnly, QuaZipNewInfo("modrinth.index.json"))) { + QFile::remove(output); + emitFailed(tr("Could not create index")); + return; + } + indexFile.write(generateIndex(resolvedFiles)); - { - size_t i = 0; - for (const QFileInfo& file : files) { - setProgress(i, files.length()); - QString relative = mc.relativeFilePath(file.absoluteFilePath()); - if (!resolved.contains(relative) && !JlCompress::compressFile(&zip, file.absoluteFilePath(), "overrides/" + relative)) - qWarning() << "Could not compress" << file; - i++; - } - } + QDir mc(instance->gameRoot()); + size_t i = 0; + for (const QFileInfo& file : files) { + setProgress(i, files.length()); + QString relative = mc.relativeFilePath(file.absoluteFilePath()); + if (!resolvedFiles.contains(relative) && !JlCompress::compressFile(&zip, file.absoluteFilePath(), "overrides/" + relative)) + qWarning() << "Could not compress" << file; + i++; + } - zip.close(); + zip.close(); - if (zip.getZipError() != 0) { - QFile::remove(output); - emitFailed(tr("A zip error occured")); - return; - } + if (zip.getZipError() != 0) { + QFile::remove(output); + emitFailed(tr("A zip error occured")); + return; + } - emitSucceeded(); - }); - connect(versionsTask.get(), &NetJob::failed, this, [this](const QString& reason) { emitFailed(reason); }); - versionsTask->start(); + emitSucceeded(); } -QByteArray ModrinthPackExportTask::generateIndex(const QMap& urls) +QByteArray ModrinthPackExportTask::generateIndex(const QMap& resolvedFiles) { QJsonObject obj; obj["formatVersion"] = 1; @@ -186,7 +193,7 @@ QByteArray ModrinthPackExportTask::generateIndex(const QMap iterator(urls); + QMapIterator iterator(resolvedFiles); while (iterator.hasNext()) { iterator.next(); diff --git a/launcher/modplatform/modrinth/ModrinthPackExportTask.h b/launcher/modplatform/modrinth/ModrinthPackExportTask.h index 974c9f0e..4ac00522 100644 --- a/launcher/modplatform/modrinth/ModrinthPackExportTask.h +++ b/launcher/modplatform/modrinth/ModrinthPackExportTask.h @@ -20,6 +20,7 @@ #include "BaseInstance.h" #include "MMCZip.h" +#include "modplatform/modrinth/ModrinthAPI.h" #include "tasks/Task.h" class ModrinthPackExportTask : public Task { @@ -35,15 +36,26 @@ class ModrinthPackExportTask : public Task { void executeTask() override; private: + static const QStringList PREFIXES; + + // inputs const QString name, version, summary; const InstancePtr instance; const QString output; const MMCZip::FilterFunction filter; + ModrinthAPI api; + QFileInfoList files; + QMap fileHashes; + struct ResolvedFile { QString sha1, sha512, url; int size; }; - QByteArray generateIndex(const QMap& urls); + void collectFiles(); + void parseApiResponse(QByteArray* response); + void buildZip(const QMap& resolvedFiles); + + QByteArray generateIndex(const QMap& resolvedFiles); }; \ No newline at end of file -- cgit From f583e617ec86a7538523a99ae008143a787e593b Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Sat, 4 Mar 2023 10:37:52 +0000 Subject: Implement abort (possible broken?) and therefore make it work without crashing! The shared pointer was going out of scope 🤦. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: TheKodeToad --- launcher/modplatform/modrinth/ModrinthPackExportTask.cpp | 14 ++++++++++---- launcher/modplatform/modrinth/ModrinthPackExportTask.h | 2 ++ 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp b/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp index d7a43c7a..c151edf5 100644 --- a/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp +++ b/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp @@ -47,10 +47,16 @@ void ModrinthPackExportTask::executeTask() collectFiles(); QByteArray* response = new QByteArray; - Task::Ptr versionsTask = api.currentVersions(fileHashes.values(), "sha512", response); - connect(versionsTask.get(), &NetJob::succeeded, [this, response]() { parseApiResponse(response); }); - connect(versionsTask.get(), &NetJob::failed, this, &ModrinthPackExportTask::emitFailed); - versionsTask->start(); + task = api.currentVersions(fileHashes.values(), "sha512", response); + connect(task.get(), &NetJob::succeeded, [this, response]() { parseApiResponse(response); }); + connect(task.get(), &NetJob::failed, this, &ModrinthPackExportTask::emitFailed); + task->start(); +} + +bool ModrinthPackExportTask::abort() { + if (!task.isNull()) + return task->abort(); + return false; } void ModrinthPackExportTask::collectFiles() diff --git a/launcher/modplatform/modrinth/ModrinthPackExportTask.h b/launcher/modplatform/modrinth/ModrinthPackExportTask.h index 4ac00522..ec87c1cd 100644 --- a/launcher/modplatform/modrinth/ModrinthPackExportTask.h +++ b/launcher/modplatform/modrinth/ModrinthPackExportTask.h @@ -34,6 +34,7 @@ class ModrinthPackExportTask : public Task { protected: void executeTask() override; + bool abort() override; private: static const QStringList PREFIXES; @@ -47,6 +48,7 @@ class ModrinthPackExportTask : public Task { ModrinthAPI api; QFileInfoList files; QMap fileHashes; + Task::Ptr task; struct ResolvedFile { QString sha1, sha512, url; -- 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(-) 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 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 5d5fcae5010ce0860bef23d161f0b0d698ade1ab Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Mon, 6 Mar 2023 17:22:20 +0000 Subject: Further reduce buggy behaviour Signed-off-by: TheKodeToad --- .../modrinth/ModrinthPackExportTask.cpp | 84 +++++++++++++--------- .../modplatform/modrinth/ModrinthPackExportTask.h | 1 + 2 files changed, 53 insertions(+), 32 deletions(-) diff --git a/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp b/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp index 3c69413d..e12ee923 100644 --- a/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp +++ b/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp @@ -19,6 +19,7 @@ #include "ModrinthPackExportTask.h" #include +#include #include #include #include @@ -56,13 +57,17 @@ void ModrinthPackExportTask::executeTask() bool ModrinthPackExportTask::abort() { - if (!task.isNull() && task->abort()) { + if (task != nullptr) { + if (!task->abort()) + return false; + task = nullptr; emitFailed(tr("Aborted")); return true; } - return false; + pendingAbort = true; + return true; } void ModrinthPackExportTask::collectFiles() @@ -106,6 +111,8 @@ void ModrinthPackExportTask::collectFiles() void ModrinthPackExportTask::parseApiResponse(QByteArray* response) { + task = nullptr; + try { QJsonDocument doc = Json::requireDocument(*response); @@ -137,41 +144,54 @@ void ModrinthPackExportTask::parseApiResponse(QByteArray* response) void ModrinthPackExportTask::buildZip() { - setStatus("Adding files..."); - QuaZip zip(output); - if (!zip.open(QuaZip::mdCreate)) { - QFile::remove(output); - emitFailed(tr("Could not create file")); - return; - } + QtConcurrent::run(QThreadPool::globalInstance(), [this]() { + setStatus("Adding files..."); + QuaZip zip(output); + if (!zip.open(QuaZip::mdCreate)) { + QFile::remove(output); + emitFailed(tr("Could not create file")); + return; + } - QuaZipFile indexFile(&zip); - if (!indexFile.open(QIODevice::WriteOnly, QuaZipNewInfo("modrinth.index.json"))) { - QFile::remove(output); - emitFailed(tr("Could not create index")); - return; - } - indexFile.write(generateIndex()); + if (pendingAbort) { + emitFailed(tr("Aborted")); + return; + } - QDir mc(instance->gameRoot()); - size_t i = 0; - for (const QFileInfo& file : files) { - setProgress(i, files.length()); - QString relative = mc.relativeFilePath(file.absoluteFilePath()); - if (!resolvedFiles.contains(relative) && !JlCompress::compressFile(&zip, file.absoluteFilePath(), "overrides/" + relative)) - qWarning() << "Could not compress" << file; - i++; - } + QuaZipFile indexFile(&zip); + if (!indexFile.open(QIODevice::WriteOnly, QuaZipNewInfo("modrinth.index.json"))) { + QFile::remove(output); + emitFailed(tr("Could not create index")); + return; + } + indexFile.write(generateIndex()); + + QDir mc(instance->gameRoot()); + size_t i = 0; + for (const QFileInfo& file : files) { + if (pendingAbort) { + QFile::remove(output); + emitFailed(tr("Aborted")); + return; + } - zip.close(); + setProgress(i, files.length()); + QString relative = mc.relativeFilePath(file.absoluteFilePath()); + if (!resolvedFiles.contains(relative) && !JlCompress::compressFile(&zip, file.absoluteFilePath(), "overrides/" + relative)) + qWarning() << "Could not compress" << file; + i++; + } - 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/modplatform/modrinth/ModrinthPackExportTask.h b/launcher/modplatform/modrinth/ModrinthPackExportTask.h index d7a42e7b..04578d2c 100644 --- a/launcher/modplatform/modrinth/ModrinthPackExportTask.h +++ b/launcher/modplatform/modrinth/ModrinthPackExportTask.h @@ -55,6 +55,7 @@ class ModrinthPackExportTask : public Task { QMap pendingHashes; QMap resolvedFiles; Task::Ptr task; + bool pendingAbort = false; void collectFiles(); void parseApiResponse(QByteArray* response); -- 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(-) 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(-) 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(-) 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 8837f06e4e97ed966662b52db206facd7f91a489 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Sat, 18 Mar 2023 14:01:41 +0000 Subject: Only add summary if not empty Signed-off-by: TheKodeToad --- launcher/modplatform/modrinth/ModrinthPackExportTask.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp b/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp index e8bc3673..cfd751d5 100644 --- a/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp +++ b/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp @@ -204,7 +204,8 @@ QByteArray ModrinthPackExportTask::generateIndex() obj["game"] = "minecraft"; obj["name"] = name; obj["versionId"] = version; - obj["summary"] = summary; + if (!summary.isEmpty()) + obj["summary"] = summary; MinecraftInstance* mc = dynamic_cast(instance.get()); if (mc) { -- 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(-) 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 710156b9f1b9555cd7e5562c8673074f80457d92 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Sun, 19 Mar 2023 21:25:12 +0000 Subject: Replace native file separator - this was accidentally brought to my attention on Modrinth's guild Signed-off-by: TheKodeToad --- launcher/modplatform/modrinth/ModrinthPackExportTask.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp b/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp index cfd751d5..f86f1ad1 100644 --- a/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp +++ b/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp @@ -237,7 +237,9 @@ QByteArray ModrinthPackExportTask::generateIndex() const ResolvedFile& value = iterator.value(); QJsonObject file; - file["path"] = iterator.key(); + QString path = iterator.key(); + path.replace(QDir::separator(), "/"); + file["path"] = path; file["downloads"] = QJsonArray({ iterator.value().url }); QJsonObject hashes; -- cgit From 46f448dfba2a3c4bffe60f373ee27b1d19873c9e Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Sun, 19 Mar 2023 21:26:25 +0000 Subject: Improve invokeMethod syntax Signed-off-by: TheKodeToad --- launcher/modplatform/modrinth/ModrinthPackExportTask.cpp | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp b/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp index f86f1ad1..46bfab6d 100644 --- a/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp +++ b/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp @@ -152,8 +152,7 @@ void ModrinthPackExportTask::buildZip() } if (pendingAbort) { - QMetaObject::invokeMethod( - this, [this]() { emitAborted(); }, Qt::QueuedConnection); + QMetaObject::invokeMethod(this, &ModrinthPackExportTask::emitAborted, Qt::QueuedConnection); return; } @@ -171,8 +170,7 @@ void ModrinthPackExportTask::buildZip() for (const QFileInfo& file : files) { if (pendingAbort) { QFile::remove(output); - QMetaObject::invokeMethod( - this, [this]() { emitAborted(); }, Qt::QueuedConnection); + QMetaObject::invokeMethod(this, &ModrinthPackExportTask::emitAborted, Qt::QueuedConnection); return; } @@ -192,8 +190,7 @@ void ModrinthPackExportTask::buildZip() return; } - QMetaObject::invokeMethod( - this, [this]() { emitSucceeded(); }, Qt::QueuedConnection); + QMetaObject::invokeMethod(this, &ModrinthPackExportTask::emitSucceeded, Qt::QueuedConnection); }); } @@ -226,6 +223,7 @@ QByteArray ModrinthPackExportTask::generateIndex() dependencies["fabric-loader"] = fabric->m_version; if (forge != nullptr) dependencies["forge"] = forge->m_version; + obj["dependencies"] = dependencies; } -- 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(-) 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 871d647c93944b7dabd070ed49a6eeaa8745d55a Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Thu, 6 Apr 2023 19:18:36 +0100 Subject: Use local metadata Signed-off-by: TheKodeToad --- buildconfig/BuildConfig.h | 2 + .../modrinth/ModrinthPackExportTask.cpp | 87 +++++++++++++++++----- .../modplatform/modrinth/ModrinthPackExportTask.h | 5 +- 3 files changed, 73 insertions(+), 21 deletions(-) diff --git a/buildconfig/BuildConfig.h b/buildconfig/BuildConfig.h index a05d7a9e..38fa3a65 100644 --- a/buildconfig/BuildConfig.h +++ b/buildconfig/BuildConfig.h @@ -36,6 +36,7 @@ #pragma once #include +#include /** * \brief The Config class holds all the build-time information passed from the build system. @@ -160,6 +161,7 @@ class Config { QString MODRINTH_STAGING_URL = "https://staging-api.modrinth.com/v2"; QString MODRINTH_PROD_URL = "https://api.modrinth.com/v2"; + QStringList MODRINTH_MRPACK_HOSTS{"cdn.modrinth.com", "github.com", "raw.githubusercontent.com", "gitlab.com"}; QString FLAME_BASE_URL = "https://api.curseforge.com/v1"; diff --git a/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp b/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp index d630f5d0..2ec51991 100644 --- a/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp +++ b/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp @@ -22,14 +22,15 @@ #include #include #include -#include #include "Json.h" #include "MMCZip.h" #include "minecraft/MinecraftInstance.h" #include "minecraft/PackProfile.h" +#include "minecraft/mod/ModFolderModel.h" #include "modplatform/modrinth/ModrinthAPI.h" -const QStringList ModrinthPackExportTask::PREFIXES = QStringList({ "mods", "coremods", "resourcepacks", "texturepacks", "shaderpacks" }); +const QStringList ModrinthPackExportTask::PREFIXES({ "mods", "coremods", "resourcepacks", "texturepacks", "shaderpacks" }); +const QStringList ModrinthPackExportTask::ALLOWED_HOSTS({ "mods", "coremods", "resourcepacks", "texturepacks", "shaderpacks" }); ModrinthPackExportTask::ModrinthPackExportTask(const QString& name, const QString& version, @@ -45,16 +46,6 @@ void ModrinthPackExportTask::executeTask() setStatus(tr("Searching for files...")); setProgress(0, 0); collectFiles(); - - if (pendingHashes.isEmpty()) - buildZip(); - else { - QByteArray* response = new QByteArray; - task = api.currentVersions(pendingHashes.values(), "sha512", response); - connect(task.get(), &NetJob::succeeded, [this, response]() { parseApiResponse(response); }); - connect(task.get(), &NetJob::failed, this, &ModrinthPackExportTask::emitFailed); - task->start(); - } } bool ModrinthPackExportTask::abort() @@ -83,6 +74,17 @@ void ModrinthPackExportTask::collectFiles() pendingHashes.clear(); resolvedFiles.clear(); + const MinecraftInstance* mcInstance = dynamic_cast(instance.get()); + auto mods = mcInstance->loaderModList(); + mods->update(); + connect(mods.get(), &ModFolderModel::updateFinished, this, &ModrinthPackExportTask::collectHashes); +} + +void ModrinthPackExportTask::collectHashes() +{ + const MinecraftInstance* mcInstance = dynamic_cast(instance.get()); + auto mods = mcInstance->loaderModList(); + QDir mc(instance->gameRoot()); for (QFileInfo file : files) { QString relative = mc.relativeFilePath(file.absoluteFilePath()); @@ -102,13 +104,58 @@ void ModrinthPackExportTask::collectFiles() continue; } - if (!hash.addData(&openFile)) { - qWarning() << "Could not add hash data for" << file; + QByteArray data = openFile.readAll(); + if (openFile.error() != QFileDevice::NoError) { + qWarning() << "Could not read" << file; continue; } + hash.addData(data); + + auto allMods = mods->allMods(); + if (auto modIter = std::find_if(allMods.begin(), allMods.end(), [&file](Mod* mod) { return mod->fileinfo() == file; }); + modIter != allMods.end()) { + Mod* mod = *modIter; + if (mod->metadata() != nullptr) { + QUrl& url = mod->metadata()->url; + // most likely some of these may be from curseforge + if (!url.isEmpty() && BuildConfig.MODRINTH_MRPACK_HOSTS.contains(url.host())) { + qDebug() << "Resolving" << relative << "from index"; + + // we've already read it + // let's go back! + openFile.seek(openFile.size()); + + QCryptographicHash hash2(QCryptographicHash::Algorithm::Sha1); + hash2.addData(data); + + ResolvedFile file{ hash2.result().toHex(), hash.result().toHex(), url.toString(), openFile.size() }; + resolvedFiles[relative] = file; + + // nice! we've managed to resolve based on local metadata! + // no need to enqueue it + continue; + } + } + } + qDebug() << "Enqueueing" << relative << "for Modrinth query"; pendingHashes[relative] = hash.result().toHex(); } + + makeApiRequest(); +} + +void ModrinthPackExportTask::makeApiRequest() +{ + if (pendingHashes.isEmpty()) + buildZip(); + else { + QByteArray* response = new QByteArray; + task = api.currentVersions(pendingHashes.values(), "sha512", response); + connect(task.get(), &NetJob::succeeded, [this, response]() { parseApiResponse(response); }); + connect(task.get(), &NetJob::failed, this, &ModrinthPackExportTask::emitFailed); + task->start(); + } } void ModrinthPackExportTask::parseApiResponse(QByteArray* response) @@ -146,7 +193,7 @@ void ModrinthPackExportTask::parseApiResponse(QByteArray* response) void ModrinthPackExportTask::buildZip() { - QtConcurrent::run(QThreadPool::globalInstance(), [this]() { + QThreadPool::globalInstance()->start([this]() { setStatus("Adding files..."); QuaZip zip(output); if (!zip.open(QuaZip::mdCreate)) { @@ -183,7 +230,7 @@ void ModrinthPackExportTask::buildZip() if (!resolvedFiles.contains(relative) && !JlCompress::compressFile(&zip, file.absoluteFilePath(), "overrides/" + relative)) { QFile::remove(output); QMetaObject::invokeMethod( - this, [this, relative]() { emitFailed(tr("Could not compress %1").arg(relative)); }, Qt::QueuedConnection); + this, [this, relative]() { emitFailed(tr("Could not read and compress %1").arg(relative)); }, Qt::QueuedConnection); return; } i++; @@ -216,10 +263,10 @@ QByteArray ModrinthPackExportTask::generateIndex() if (mc) { auto profile = mc->getPackProfile(); // collect all supported components - auto minecraft = profile->getComponent("net.minecraft"); - auto quilt = profile->getComponent("org.quiltmc.quilt-loader"); - auto fabric = profile->getComponent("net.fabricmc.fabric-loader"); - auto forge = profile->getComponent("net.minecraftforge"); + ComponentPtr minecraft = profile->getComponent("net.minecraft"); + ComponentPtr quilt = profile->getComponent("org.quiltmc.quilt-loader"); + ComponentPtr fabric = profile->getComponent("net.fabricmc.fabric-loader"); + ComponentPtr forge = profile->getComponent("net.minecraftforge"); // convert all available components to mrpack dependencies QJsonObject dependencies; diff --git a/launcher/modplatform/modrinth/ModrinthPackExportTask.h b/launcher/modplatform/modrinth/ModrinthPackExportTask.h index 04578d2c..217956db 100644 --- a/launcher/modplatform/modrinth/ModrinthPackExportTask.h +++ b/launcher/modplatform/modrinth/ModrinthPackExportTask.h @@ -39,10 +39,11 @@ class ModrinthPackExportTask : public Task { private: struct ResolvedFile { QString sha1, sha512, url; - int size; + qint64 size; }; static const QStringList PREFIXES; + static const QStringList ALLOWED_HOSTS; // inputs const QString name, version, summary; @@ -58,6 +59,8 @@ class ModrinthPackExportTask : public Task { bool pendingAbort = false; void collectFiles(); + void collectHashes(); + void makeApiRequest(); void parseApiResponse(QByteArray* response); void buildZip(); -- cgit From 073aaf9b3bbe4edb763dfeb1a5b3d22473e59e41 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Thu, 6 Apr 2023 19:19:35 +0100 Subject: Remove "prototype" field Signed-off-by: TheKodeToad --- launcher/modplatform/modrinth/ModrinthPackExportTask.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp b/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp index 2ec51991..cfb59fc3 100644 --- a/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp +++ b/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp @@ -30,7 +30,6 @@ #include "modplatform/modrinth/ModrinthAPI.h" const QStringList ModrinthPackExportTask::PREFIXES({ "mods", "coremods", "resourcepacks", "texturepacks", "shaderpacks" }); -const QStringList ModrinthPackExportTask::ALLOWED_HOSTS({ "mods", "coremods", "resourcepacks", "texturepacks", "shaderpacks" }); ModrinthPackExportTask::ModrinthPackExportTask(const QString& name, const QString& version, -- cgit From d7a137ad13854d008de438786d6a97b4092bac99 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Thu, 6 Apr 2023 19:24:19 +0100 Subject: Remove more prototype not good code Signed-off-by: TheKodeToad --- .../modrinth/ModrinthPackExportTask.cpp | 22 +++++++++++++--------- .../modplatform/modrinth/ModrinthPackExportTask.h | 2 ++ 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp b/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp index cfb59fc3..772928e9 100644 --- a/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp +++ b/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp @@ -37,7 +37,13 @@ ModrinthPackExportTask::ModrinthPackExportTask(const QString& name, InstancePtr instance, const QString& output, MMCZip::FilterFunction filter) - : name(name), version(version), summary(summary), instance(instance), output(output), filter(filter) + : name(name) + , version(version) + , summary(summary) + , instance(instance) + , mcInstance(dynamic_cast(instance.get())) + , output(output) + , filter(filter) {} void ModrinthPackExportTask::executeTask() @@ -73,17 +79,15 @@ void ModrinthPackExportTask::collectFiles() pendingHashes.clear(); resolvedFiles.clear(); - const MinecraftInstance* mcInstance = dynamic_cast(instance.get()); - auto mods = mcInstance->loaderModList(); - mods->update(); - connect(mods.get(), &ModFolderModel::updateFinished, this, &ModrinthPackExportTask::collectHashes); + if (mcInstance) { + mcInstance->loaderModList()->update(); + connect(mcInstance->loaderModList().get(), &ModFolderModel::updateFinished, this, &ModrinthPackExportTask::collectHashes); + } else + collectHashes(); } void ModrinthPackExportTask::collectHashes() { - const MinecraftInstance* mcInstance = dynamic_cast(instance.get()); - auto mods = mcInstance->loaderModList(); - QDir mc(instance->gameRoot()); for (QFileInfo file : files) { QString relative = mc.relativeFilePath(file.absoluteFilePath()); @@ -110,7 +114,7 @@ void ModrinthPackExportTask::collectHashes() } hash.addData(data); - auto allMods = mods->allMods(); + auto allMods = mcInstance->loaderModList()->allMods(); if (auto modIter = std::find_if(allMods.begin(), allMods.end(), [&file](Mod* mod) { return mod->fileinfo() == file; }); modIter != allMods.end()) { Mod* mod = *modIter; diff --git a/launcher/modplatform/modrinth/ModrinthPackExportTask.h b/launcher/modplatform/modrinth/ModrinthPackExportTask.h index 217956db..021d8a56 100644 --- a/launcher/modplatform/modrinth/ModrinthPackExportTask.h +++ b/launcher/modplatform/modrinth/ModrinthPackExportTask.h @@ -20,6 +20,7 @@ #include "BaseInstance.h" #include "MMCZip.h" +#include "minecraft/MinecraftInstance.h" #include "modplatform/modrinth/ModrinthAPI.h" #include "tasks/Task.h" @@ -48,6 +49,7 @@ class ModrinthPackExportTask : public Task { // inputs const QString name, version, summary; const InstancePtr instance; + const MinecraftInstance* mcInstance; const QString output; const MMCZip::FilterFunction filter; -- cgit From 012d8bb4680b54bedd9b58fb6102c768002cf0ed Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Thu, 6 Apr 2023 19:58:09 +0100 Subject: Revert concurrent syntax Signed-off-by: TheKodeToad --- launcher/modplatform/modrinth/ModrinthPackExportTask.cpp | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp b/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp index 772928e9..f4b2bcd3 100644 --- a/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp +++ b/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp @@ -20,14 +20,12 @@ #include #include -#include #include +#include #include "Json.h" #include "MMCZip.h" -#include "minecraft/MinecraftInstance.h" #include "minecraft/PackProfile.h" #include "minecraft/mod/ModFolderModel.h" -#include "modplatform/modrinth/ModrinthAPI.h" const QStringList ModrinthPackExportTask::PREFIXES({ "mods", "coremods", "resourcepacks", "texturepacks", "shaderpacks" }); @@ -190,13 +188,11 @@ void ModrinthPackExportTask::parseApiResponse(QByteArray* response) qWarning() << "Failed to parse versions response" << e.what(); } pendingHashes.clear(); - - buildZip(); } void ModrinthPackExportTask::buildZip() { - QThreadPool::globalInstance()->start([this]() { + static_cast(QtConcurrent::run(QThreadPool::globalInstance(), [this]() { setStatus("Adding files..."); QuaZip zip(output); if (!zip.open(QuaZip::mdCreate)) { @@ -249,7 +245,7 @@ void ModrinthPackExportTask::buildZip() } QMetaObject::invokeMethod(this, &ModrinthPackExportTask::emitSucceeded, Qt::QueuedConnection); - }); + })); } QByteArray ModrinthPackExportTask::generateIndex() @@ -301,6 +297,7 @@ QByteArray ModrinthPackExportTask::generateIndex() QJsonObject hashes; hashes["sha1"] = value.sha1; hashes["sha512"] = value.sha512; + file["hashes"] = hashes; file["fileSize"] = value.size; -- cgit From b65f4c9536691096d44ae427ff71bfe971592747 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Thu, 6 Apr 2023 19:59:15 +0100 Subject: Better collectFileListRecursively error Signed-off-by: TheKodeToad --- launcher/modplatform/modrinth/ModrinthPackExportTask.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp b/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp index f4b2bcd3..2618074e 100644 --- a/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp +++ b/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp @@ -70,7 +70,7 @@ void ModrinthPackExportTask::collectFiles() { files.clear(); if (!MMCZip::collectFileListRecursively(instance->gameRoot(), nullptr, &files, filter)) { - emitFailed(tr("Could not collect list of files")); + emitFailed(tr("Could not search for files")); return; } -- cgit From 813ccc1381f8cb128d0bdcaac1e62d76e8b74289 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Fri, 7 Apr 2023 11:03:11 +0100 Subject: How did i- Signed-off-by: TheKodeToad --- launcher/modplatform/modrinth/ModrinthPackExportTask.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp b/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp index 2618074e..fc16f912 100644 --- a/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp +++ b/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp @@ -164,7 +164,7 @@ void ModrinthPackExportTask::parseApiResponse(QByteArray* response) task = nullptr; try { - QJsonDocument doc = Json::requireDocument(*response); + const QJsonDocument doc = Json::requireDocument(*response); QMapIterator iterator(pendingHashes); while (iterator.hasNext()) { @@ -188,6 +188,7 @@ void ModrinthPackExportTask::parseApiResponse(QByteArray* response) qWarning() << "Failed to parse versions response" << e.what(); } pendingHashes.clear(); + buildZip(); } void ModrinthPackExportTask::buildZip() -- cgit From 3a7961834abbd28014de89799514510e74f365ab Mon Sep 17 00:00:00 2001 From: Kode Date: Sun, 9 Apr 2023 21:28:40 +0100 Subject: Remove `seek` Don't need it if the data is already in a byte array. Signed-off-by: Kode --- launcher/modplatform/modrinth/ModrinthPackExportTask.cpp | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp b/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp index fc16f912..ed50fd20 100644 --- a/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp +++ b/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp @@ -122,10 +122,6 @@ void ModrinthPackExportTask::collectHashes() if (!url.isEmpty() && BuildConfig.MODRINTH_MRPACK_HOSTS.contains(url.host())) { qDebug() << "Resolving" << relative << "from index"; - // we've already read it - // let's go back! - openFile.seek(openFile.size()); - QCryptographicHash hash2(QCryptographicHash::Algorithm::Sha1); hash2.addData(data); @@ -307,4 +303,4 @@ QByteArray ModrinthPackExportTask::generateIndex() obj["files"] = files; return QJsonDocument(obj).toJson(QJsonDocument::Compact); -} \ No newline at end of file +} -- 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(-) 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 ba17efa3816f13a6c986f2b9223f71fa1d560aac Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Mon, 17 Apr 2023 13:18:25 +0100 Subject: Smol fixes Signed-off-by: TheKodeToad --- .../modrinth/ModrinthPackExportTask.cpp | 59 +++++++++++----------- .../modplatform/modrinth/ModrinthPackExportTask.h | 2 + 2 files changed, 32 insertions(+), 29 deletions(-) diff --git a/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp b/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp index ed50fd20..6da9d3c1 100644 --- a/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp +++ b/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp @@ -28,6 +28,7 @@ #include "minecraft/mod/ModFolderModel.h" const QStringList ModrinthPackExportTask::PREFIXES({ "mods", "coremods", "resourcepacks", "texturepacks", "shaderpacks" }); +const QStringList ModrinthPackExportTask::FILE_EXTENSIONS({ "jar", "litemod", "zip" }); ModrinthPackExportTask::ModrinthPackExportTask(const QString& name, const QString& version, @@ -40,6 +41,7 @@ ModrinthPackExportTask::ModrinthPackExportTask(const QString& name, , summary(summary) , instance(instance) , mcInstance(dynamic_cast(instance.get())) + , gameRoot(instance->gameRoot()) , output(output) , filter(filter) {} @@ -86,18 +88,19 @@ void ModrinthPackExportTask::collectFiles() void ModrinthPackExportTask::collectHashes() { - QDir mc(instance->gameRoot()); - for (QFileInfo file : files) { - QString relative = mc.relativeFilePath(file.absoluteFilePath()); + for (const QFileInfo& file : files) { + const QString relative = gameRoot.relativeFilePath(file.absoluteFilePath()); // require sensible file types - if (!(relative.endsWith(".zip") || relative.endsWith(".jar") || relative.endsWith(".litemod"))) - continue; - if (!std::any_of(PREFIXES.begin(), PREFIXES.end(), [&relative](const QString& prefix) { return relative.startsWith(prefix + QDir::separator()); })) continue; + if (!std::any_of(FILE_EXTENSIONS.begin(), FILE_EXTENSIONS.end(), [&relative](const QString& extension) { + return relative.endsWith('.' + extension) || relative.endsWith('.' + extension + ".disabled"); + })) { + continue; + } - QCryptographicHash hash(QCryptographicHash::Algorithm::Sha512); + QCryptographicHash sha512(QCryptographicHash::Algorithm::Sha512); QFile openFile(file.absoluteFilePath()); if (!openFile.open(QFile::ReadOnly)) { @@ -105,27 +108,27 @@ void ModrinthPackExportTask::collectHashes() continue; } - QByteArray data = openFile.readAll(); + const QByteArray data = openFile.readAll(); if (openFile.error() != QFileDevice::NoError) { qWarning() << "Could not read" << file; continue; } - hash.addData(data); + sha512.addData(data); auto allMods = mcInstance->loaderModList()->allMods(); if (auto modIter = std::find_if(allMods.begin(), allMods.end(), [&file](Mod* mod) { return mod->fileinfo() == file; }); modIter != allMods.end()) { - Mod* mod = *modIter; + const Mod* mod = *modIter; if (mod->metadata() != nullptr) { QUrl& url = mod->metadata()->url; // most likely some of these may be from curseforge if (!url.isEmpty() && BuildConfig.MODRINTH_MRPACK_HOSTS.contains(url.host())) { qDebug() << "Resolving" << relative << "from index"; - QCryptographicHash hash2(QCryptographicHash::Algorithm::Sha1); - hash2.addData(data); + QCryptographicHash sha1(QCryptographicHash::Algorithm::Sha1); + sha1.addData(data); - ResolvedFile file{ hash2.result().toHex(), hash.result().toHex(), url.toString(), openFile.size() }; + ResolvedFile file{ sha1.result().toHex(), sha512.result().toHex(), url.toString(), openFile.size() }; resolvedFiles[relative] = file; // nice! we've managed to resolve based on local metadata! @@ -136,7 +139,7 @@ void ModrinthPackExportTask::collectHashes() } qDebug() << "Enqueueing" << relative << "for Modrinth query"; - pendingHashes[relative] = hash.result().toHex(); + pendingHashes[relative] = sha512.result().toHex(); } makeApiRequest(); @@ -166,11 +169,11 @@ void ModrinthPackExportTask::parseApiResponse(QByteArray* response) while (iterator.hasNext()) { iterator.next(); - QJsonObject obj = doc[iterator.value()].toObject(); + const QJsonObject obj = doc[iterator.value()].toObject(); if (obj.isEmpty()) continue; - QJsonArray files = obj["files"].toArray(); + const QJsonArray files = obj["files"].toArray(); if (auto fileIter = std::find_if(files.begin(), files.end(), [&iterator](const QJsonValue& file) { return file["hashes"]["sha512"] == iterator.value(); }); fileIter != files.end()) { @@ -180,7 +183,7 @@ void ModrinthPackExportTask::parseApiResponse(QByteArray* response) fileIter->toObject()["url"].toString(), fileIter->toObject()["size"].toInt() }; } } - } catch (Json::JsonException& e) { + } catch (const Json::JsonException& e) { qWarning() << "Failed to parse versions response" << e.what(); } pendingHashes.clear(); @@ -212,8 +215,7 @@ void ModrinthPackExportTask::buildZip() } indexFile.write(generateIndex()); - QDir mc(instance->gameRoot()); - size_t i = 0; + size_t progress = 0; for (const QFileInfo& file : files) { if (pendingAbort) { QFile::remove(output); @@ -221,15 +223,15 @@ void ModrinthPackExportTask::buildZip() return; } - setProgress(i, files.length()); - QString relative = mc.relativeFilePath(file.absoluteFilePath()); + setProgress(progress, files.length()); + const QString relative = gameRoot.relativeFilePath(file.absoluteFilePath()); if (!resolvedFiles.contains(relative) && !JlCompress::compressFile(&zip, file.absoluteFilePath(), "overrides/" + relative)) { QFile::remove(output); QMetaObject::invokeMethod( this, [this, relative]() { emitFailed(tr("Could not read and compress %1").arg(relative)); }, Qt::QueuedConnection); return; } - i++; + progress++; } zip.close(); @@ -255,14 +257,13 @@ QByteArray ModrinthPackExportTask::generateIndex() if (!summary.isEmpty()) obj["summary"] = summary; - MinecraftInstance* mc = dynamic_cast(instance.get()); - if (mc) { - auto profile = mc->getPackProfile(); + if (mcInstance) { + auto profile = mcInstance->getPackProfile(); // collect all supported components - ComponentPtr minecraft = profile->getComponent("net.minecraft"); - ComponentPtr quilt = profile->getComponent("org.quiltmc.quilt-loader"); - ComponentPtr fabric = profile->getComponent("net.fabricmc.fabric-loader"); - ComponentPtr forge = profile->getComponent("net.minecraftforge"); + const ComponentPtr minecraft = profile->getComponent("net.minecraft"); + const ComponentPtr quilt = profile->getComponent("org.quiltmc.quilt-loader"); + const ComponentPtr fabric = profile->getComponent("net.fabricmc.fabric-loader"); + const ComponentPtr forge = profile->getComponent("net.minecraftforge"); // convert all available components to mrpack dependencies QJsonObject dependencies; diff --git a/launcher/modplatform/modrinth/ModrinthPackExportTask.h b/launcher/modplatform/modrinth/ModrinthPackExportTask.h index 8b17f644..c00751f2 100644 --- a/launcher/modplatform/modrinth/ModrinthPackExportTask.h +++ b/launcher/modplatform/modrinth/ModrinthPackExportTask.h @@ -44,12 +44,14 @@ class ModrinthPackExportTask : public Task { }; static const QStringList PREFIXES; + static const QStringList FILE_EXTENSIONS; static const QStringList ALLOWED_HOSTS; // inputs const QString name, version, summary; const InstancePtr instance; const MinecraftInstance* mcInstance; + const QDir gameRoot; const QString output; const MMCZip::FilterFunction filter; -- cgit From 2e9403a324bf7a62fd61b4df78d6c9cc13f65d01 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Mon, 17 Apr 2023 13:19:59 +0100 Subject: This was moved Signed-off-by: TheKodeToad --- launcher/modplatform/modrinth/ModrinthPackExportTask.h | 1 - 1 file changed, 1 deletion(-) diff --git a/launcher/modplatform/modrinth/ModrinthPackExportTask.h b/launcher/modplatform/modrinth/ModrinthPackExportTask.h index c00751f2..a0b006b9 100644 --- a/launcher/modplatform/modrinth/ModrinthPackExportTask.h +++ b/launcher/modplatform/modrinth/ModrinthPackExportTask.h @@ -45,7 +45,6 @@ class ModrinthPackExportTask : public Task { static const QStringList PREFIXES; static const QStringList FILE_EXTENSIONS; - static const QStringList ALLOWED_HOSTS; // inputs const QString name, version, summary; -- cgit From 709736d3f9a77206b5b6f809e5e45495f1db1315 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Mon, 17 Apr 2023 13:52:51 +0100 Subject: Make response const I don't think the segfault fix was ideal :P Signed-off-by: TheKodeToad --- launcher/modplatform/modrinth/ModrinthPackExportTask.cpp | 6 ++---- launcher/modplatform/modrinth/ModrinthPackExportTask.h | 2 +- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp b/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp index 6da9d3c1..54932131 100644 --- a/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp +++ b/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp @@ -56,9 +56,7 @@ void ModrinthPackExportTask::executeTask() bool ModrinthPackExportTask::abort() { if (task != nullptr) { - if (!task->abort()) - return false; - + task->abort(); task = nullptr; emitAborted(); return true; @@ -158,7 +156,7 @@ void ModrinthPackExportTask::makeApiRequest() } } -void ModrinthPackExportTask::parseApiResponse(QByteArray* response) +void ModrinthPackExportTask::parseApiResponse(const QByteArray* response) { task = nullptr; diff --git a/launcher/modplatform/modrinth/ModrinthPackExportTask.h b/launcher/modplatform/modrinth/ModrinthPackExportTask.h index a0b006b9..25045cf2 100644 --- a/launcher/modplatform/modrinth/ModrinthPackExportTask.h +++ b/launcher/modplatform/modrinth/ModrinthPackExportTask.h @@ -64,7 +64,7 @@ class ModrinthPackExportTask : public Task { void collectFiles(); void collectHashes(); void makeApiRequest(); - void parseApiResponse(QByteArray* response); + void parseApiResponse(const QByteArray* response); void buildZip(); QByteArray generateIndex(); -- cgit From e0380960fda706a70bef6f63610bcfa68775e21d Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Tue, 2 May 2023 14:23:27 +0100 Subject: Change to use future Signed-off-by: TheKodeToad --- .../modrinth/ModrinthPackExportTask.cpp | 57 +++++++++++++--------- .../modplatform/modrinth/ModrinthPackExportTask.h | 8 ++- 2 files changed, 40 insertions(+), 25 deletions(-) diff --git a/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp b/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp index 54932131..d2ef0653 100644 --- a/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp +++ b/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp @@ -62,8 +62,11 @@ bool ModrinthPackExportTask::abort() return true; } - pendingAbort = true; - return true; + if (buildZipFuture.isRunning()) { + buildZipFuture.cancel(); + return true; + } + return false; } void ModrinthPackExportTask::collectFiles() @@ -190,44 +193,37 @@ void ModrinthPackExportTask::parseApiResponse(const QByteArray* response) void ModrinthPackExportTask::buildZip() { - static_cast(QtConcurrent::run(QThreadPool::globalInstance(), [this]() { - setStatus("Adding files..."); + setStatus("Adding files..."); + + buildZipFuture = QtConcurrent::run(QThreadPool::globalInstance(), [this]() { QuaZip zip(output); if (!zip.open(QuaZip::mdCreate)) { QFile::remove(output); - emitFailed(tr("Could not create file")); - return; + return BuildZipResult(tr("Could not create file")); } - if (pendingAbort) { - QMetaObject::invokeMethod(this, &ModrinthPackExportTask::emitAborted, Qt::QueuedConnection); - return; - } + if (buildZipFuture.isCanceled()) + return BuildZipResult(); QuaZipFile indexFile(&zip); if (!indexFile.open(QIODevice::WriteOnly, QuaZipNewInfo("modrinth.index.json"))) { QFile::remove(output); - QMetaObject::invokeMethod( - this, [this]() { emitFailed(tr("Could not create index")); }, Qt::QueuedConnection); - return; + return BuildZipResult(tr("Could not create index")); } indexFile.write(generateIndex()); size_t progress = 0; for (const QFileInfo& file : files) { - if (pendingAbort) { + if (buildZipFuture.isCanceled()) { QFile::remove(output); - QMetaObject::invokeMethod(this, &ModrinthPackExportTask::emitAborted, Qt::QueuedConnection); - return; + return BuildZipResult(); } setProgress(progress, files.length()); const QString relative = gameRoot.relativeFilePath(file.absoluteFilePath()); if (!resolvedFiles.contains(relative) && !JlCompress::compressFile(&zip, file.absoluteFilePath(), "overrides/" + relative)) { QFile::remove(output); - QMetaObject::invokeMethod( - this, [this, relative]() { emitFailed(tr("Could not read and compress %1").arg(relative)); }, Qt::QueuedConnection); - return; + return BuildZipResult(tr("Could not read and compress %1").arg(relative)); } progress++; } @@ -236,13 +232,26 @@ void ModrinthPackExportTask::buildZip() if (zip.getZipError() != 0) { QFile::remove(output); - QMetaObject::invokeMethod( - this, [this]() { emitFailed(tr("A zip error occurred")); }, Qt::QueuedConnection); - return; + return BuildZipResult(tr("A zip error occurred")); } - QMetaObject::invokeMethod(this, &ModrinthPackExportTask::emitSucceeded, Qt::QueuedConnection); - })); + return BuildZipResult(); + }); + connect(&buildZipWatcher, &QFutureWatcher::finished, this, &ModrinthPackExportTask::finish); + buildZipWatcher.setFuture(buildZipFuture); +} + +void ModrinthPackExportTask::finish() +{ + if (buildZipFuture.isCanceled()) + emitAborted(); + else { + const BuildZipResult result = buildZipFuture.result(); + if (result.has_value()) + emitFailed(result.value()); + else + emitSucceeded(); + } } QByteArray ModrinthPackExportTask::generateIndex() diff --git a/launcher/modplatform/modrinth/ModrinthPackExportTask.h b/launcher/modplatform/modrinth/ModrinthPackExportTask.h index 25045cf2..5426d6da 100644 --- a/launcher/modplatform/modrinth/ModrinthPackExportTask.h +++ b/launcher/modplatform/modrinth/ModrinthPackExportTask.h @@ -18,6 +18,8 @@ #pragma once +#include +#include #include "BaseInstance.h" #include "MMCZip.h" #include "minecraft/MinecraftInstance.h" @@ -54,18 +56,22 @@ class ModrinthPackExportTask : public Task { const QString output; const MMCZip::FilterFunction filter; + typedef std::optional BuildZipResult; + ModrinthAPI api; QFileInfoList files; QMap pendingHashes; QMap resolvedFiles; Task::Ptr task; - bool pendingAbort = false; + QFuture buildZipFuture; + QFutureWatcher buildZipWatcher; void collectFiles(); void collectHashes(); void makeApiRequest(); void parseApiResponse(const QByteArray* response); void buildZip(); + void finish(); QByteArray generateIndex(); }; -- cgit From 37a6ef95f05b474da1ce449bef696258d22a9d9b Mon Sep 17 00:00:00 2001 From: Redson Date: Wed, 10 May 2023 08:25:13 -0300 Subject: feat: Don't hide the settings tab when an instance is running Signed-off-by: Redson --- launcher/ui/pages/instance/InstanceSettingsPage.cpp | 11 ++++++++++- launcher/ui/pages/instance/InstanceSettingsPage.h | 1 + 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/launcher/ui/pages/instance/InstanceSettingsPage.cpp b/launcher/ui/pages/instance/InstanceSettingsPage.cpp index 4b4c73dc..97939461 100644 --- a/launcher/ui/pages/instance/InstanceSettingsPage.cpp +++ b/launcher/ui/pages/instance/InstanceSettingsPage.cpp @@ -60,11 +60,15 @@ InstanceSettingsPage::InstanceSettingsPage(BaseInstance *inst, QWidget *parent) m_settings = inst->settings(); ui->setupUi(this); + // As the signal will (probably) not be triggered once we click edit, let's update it manually instead. + updateRunningStatus(m_instance->isRunning()); + accountMenu = new QMenu(this); // Use undocumented property... https://stackoverflow.com/questions/7121718/create-a-scrollbar-in-a-submenu-qt accountMenu->setStyleSheet("QMenu { menu-scrollable: 1; }"); ui->instanceAccountSelector->setMenu(accountMenu); + connect(m_instance, &BaseInstance::runningStatusChanged, this, &InstanceSettingsPage::updateRunningStatus); connect(ui->openGlobalJavaSettingsButton, &QCommandLinkButton::clicked, this, &InstanceSettingsPage::globalSettingsButtonClicked); connect(APPLICATION, &Application::globalSettingsAboutToOpen, this, &InstanceSettingsPage::applySettings); connect(APPLICATION, &Application::globalSettingsClosed, this, &InstanceSettingsPage::loadSettings); @@ -74,7 +78,7 @@ InstanceSettingsPage::InstanceSettingsPage(BaseInstance *inst, QWidget *parent) bool InstanceSettingsPage::shouldDisplay() const { - return !m_instance->isRunning(); + return true; } InstanceSettingsPage::~InstanceSettingsPage() @@ -552,3 +556,8 @@ void InstanceSettingsPage::updateThresholds() ui->labelMaxMemIcon->setPixmap(pix); } } + +void InstanceSettingsPage::updateRunningStatus(bool running) +{ + setEnabled(!running); +} diff --git a/launcher/ui/pages/instance/InstanceSettingsPage.h b/launcher/ui/pages/instance/InstanceSettingsPage.h index cb6fbae0..89e663bf 100644 --- a/launcher/ui/pages/instance/InstanceSettingsPage.h +++ b/launcher/ui/pages/instance/InstanceSettingsPage.h @@ -81,6 +81,7 @@ public: void updateThresholds(); private slots: + void updateRunningStatus(bool running); void on_javaDetectBtn_clicked(); void on_javaTestBtn_clicked(); void on_javaBrowseBtn_clicked(); -- cgit From 5b8d0254408a6fd3e123eb06b7cbcdd20bd54518 Mon Sep 17 00:00:00 2001 From: Kode Date: Fri, 12 May 2023 14:43:55 +0100 Subject: ty! Co-authored-by: flow Signed-off-by: Kode --- launcher/modplatform/modrinth/ModrinthPackExportTask.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp b/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp index d2ef0653..9a45e46b 100644 --- a/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp +++ b/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp @@ -64,6 +64,7 @@ bool ModrinthPackExportTask::abort() if (buildZipFuture.isRunning()) { buildZipFuture.cancel(); + // NOTE: Here we don't do `emitAborted()` because it will be done when `buildZipFuture` actually cancels, which may not occur immediately. return true; } return false; -- cgit From 79ce7eb1fc9351e689e7106e3dc3a641d9614c9a Mon Sep 17 00:00:00 2001 From: Redson Date: Sat, 13 May 2023 09:00:10 -0300 Subject: fix: `shouldDisplay()` is now redundant. Signed-off-by: Redson --- launcher/ui/pages/instance/InstanceSettingsPage.cpp | 5 ----- launcher/ui/pages/instance/InstanceSettingsPage.h | 1 - 2 files changed, 6 deletions(-) diff --git a/launcher/ui/pages/instance/InstanceSettingsPage.cpp b/launcher/ui/pages/instance/InstanceSettingsPage.cpp index 97939461..46830720 100644 --- a/launcher/ui/pages/instance/InstanceSettingsPage.cpp +++ b/launcher/ui/pages/instance/InstanceSettingsPage.cpp @@ -76,11 +76,6 @@ InstanceSettingsPage::InstanceSettingsPage(BaseInstance *inst, QWidget *parent) updateThresholds(); } -bool InstanceSettingsPage::shouldDisplay() const -{ - return true; -} - InstanceSettingsPage::~InstanceSettingsPage() { delete ui; diff --git a/launcher/ui/pages/instance/InstanceSettingsPage.h b/launcher/ui/pages/instance/InstanceSettingsPage.h index 89e663bf..8bd854ad 100644 --- a/launcher/ui/pages/instance/InstanceSettingsPage.h +++ b/launcher/ui/pages/instance/InstanceSettingsPage.h @@ -75,7 +75,6 @@ public: { return "Instance-settings"; } - virtual bool shouldDisplay() const override; void retranslate() override; void updateThresholds(); -- 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(-) 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 129e959a3b0a7d21965b15d6a65b0a9d22994838 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Sat, 13 May 2023 18:28:45 +0100 Subject: Move setAbortable(true) Signed-off-by: TheKodeToad --- launcher/modplatform/modrinth/ModrinthPackExportTask.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp b/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp index c550fdce..8db89bbd 100644 --- a/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp +++ b/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp @@ -150,13 +150,12 @@ void ModrinthPackExportTask::collectHashes() pendingHashes[relative] = sha512.result().toHex(); } + setAbortable(true); makeApiRequest(); } void ModrinthPackExportTask::makeApiRequest() { - setAbortable(true); - if (pendingHashes.isEmpty()) buildZip(); else { -- cgit From 18cfe219fef2aabc1d3260764c02cd3476615176 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Sat, 13 May 2023 18:58:05 +0100 Subject: Hopefully This Works™ MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: TheKodeToad --- launcher/FileIgnoreProxy.cpp | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/launcher/FileIgnoreProxy.cpp b/launcher/FileIgnoreProxy.cpp index 4fd1ef91..a3b7d505 100644 --- a/launcher/FileIgnoreProxy.cpp +++ b/launcher/FileIgnoreProxy.cpp @@ -129,12 +129,7 @@ bool FileIgnoreProxy::setData(const QModelIndex& index, const QVariant& value, i QString FileIgnoreProxy::relPath(const QString& path) const { - QString prefix = QDir().absoluteFilePath(root); - prefix += '/'; - if (!path.startsWith(prefix)) { - return QString(); - } - return path.mid(prefix.size()); + return QDir(root).relativeFilePath(path); } bool FileIgnoreProxy::setFilterState(QModelIndex index, Qt::CheckState state) -- cgit From 7537ea1ef532f5d8d50ad7aa7e49c4961ddf7b1c Mon Sep 17 00:00:00 2001 From: leo78913 Date: Sun, 14 May 2023 14:57:51 -0300 Subject: make instance settings account selector a comboBox Signed-off-by: leo78913 --- .../ui/pages/instance/InstanceSettingsPage.cpp | 54 ++++++---------------- launcher/ui/pages/instance/InstanceSettingsPage.h | 3 +- launcher/ui/pages/instance/InstanceSettingsPage.ui | 9 +--- 3 files changed, 15 insertions(+), 51 deletions(-) diff --git a/launcher/ui/pages/instance/InstanceSettingsPage.cpp b/launcher/ui/pages/instance/InstanceSettingsPage.cpp index 4b4c73dc..a583ab1d 100644 --- a/launcher/ui/pages/instance/InstanceSettingsPage.cpp +++ b/launcher/ui/pages/instance/InstanceSettingsPage.cpp @@ -60,15 +60,13 @@ InstanceSettingsPage::InstanceSettingsPage(BaseInstance *inst, QWidget *parent) m_settings = inst->settings(); ui->setupUi(this); - accountMenu = new QMenu(this); - // Use undocumented property... https://stackoverflow.com/questions/7121718/create-a-scrollbar-in-a-submenu-qt - accountMenu->setStyleSheet("QMenu { menu-scrollable: 1; }"); - ui->instanceAccountSelector->setMenu(accountMenu); - connect(ui->openGlobalJavaSettingsButton, &QCommandLinkButton::clicked, this, &InstanceSettingsPage::globalSettingsButtonClicked); connect(APPLICATION, &Application::globalSettingsAboutToOpen, this, &InstanceSettingsPage::applySettings); connect(APPLICATION, &Application::globalSettingsClosed, this, &InstanceSettingsPage::loadSettings); + connect(ui->instanceAccountSelector, QOverload::of(&QComboBox::currentIndexChanged), this, &InstanceSettingsPage::changeInstanceAccount); loadSettings(); + + updateThresholds(); } @@ -454,36 +452,17 @@ void InstanceSettingsPage::on_javaTestBtn_clicked() void InstanceSettingsPage::updateAccountsMenu() { - accountMenu->clear(); - + ui->instanceAccountSelector->clear(); auto accounts = APPLICATION->accounts(); int accountIndex = accounts->findAccountByProfileId(m_settings->get("InstanceAccountId").toString()); - MinecraftAccountPtr defaultAccount = accounts->defaultAccount(); - - if (accountIndex != -1 && accounts->at(accountIndex)) { - defaultAccount = accounts->at(accountIndex); - } - - if (defaultAccount) { - ui->instanceAccountSelector->setText(defaultAccount->profileName()); - ui->instanceAccountSelector->setIcon(getFaceForAccount(defaultAccount)); - } else { - ui->instanceAccountSelector->setText(tr("No default account")); - ui->instanceAccountSelector->setIcon(APPLICATION->getThemedIcon("noaccount")); - } for (int i = 0; i < accounts->count(); i++) { MinecraftAccountPtr account = accounts->at(i); - QAction* action = new QAction(account->profileName(), this); - action->setData(i); - action->setCheckable(true); - if (accountIndex == i) { - action->setChecked(true); - } - action->setIcon(getFaceForAccount(account)); - accountMenu->addAction(action); - connect(action, SIGNAL(triggered(bool)), this, SLOT(changeInstanceAccount())); + ui->instanceAccountSelector->addItem(getFaceForAccount(account), account->profileName(), i); + if (i == accountIndex) + ui->instanceAccountSelector->setCurrentIndex(i); } + } QIcon InstanceSettingsPage::getFaceForAccount(MinecraftAccountPtr account) @@ -495,20 +474,13 @@ QIcon InstanceSettingsPage::getFaceForAccount(MinecraftAccountPtr account) return APPLICATION->getThemedIcon("noaccount"); } -void InstanceSettingsPage::changeInstanceAccount() +void InstanceSettingsPage::changeInstanceAccount(int index) { - QAction* sAction = (QAction*)sender(); - - Q_ASSERT(sAction->data().type() == QVariant::Type::Int); - - QVariant data = sAction->data(); - int index = data.toInt(); auto accounts = APPLICATION->accounts(); - auto account = accounts->at(index); - m_settings->set("InstanceAccountId", account->profileId()); - - ui->instanceAccountSelector->setText(account->profileName()); - ui->instanceAccountSelector->setIcon(getFaceForAccount(account)); + if (index != -1 && accounts->at(index) && ui->instanceAccountGroupBox->isChecked()) { + auto account = accounts->at(index); + m_settings->set("InstanceAccountId", account->profileId()); + } } void InstanceSettingsPage::on_maxMemSpinBox_valueChanged(int i) diff --git a/launcher/ui/pages/instance/InstanceSettingsPage.h b/launcher/ui/pages/instance/InstanceSettingsPage.h index cb6fbae0..043c3e25 100644 --- a/launcher/ui/pages/instance/InstanceSettingsPage.h +++ b/launcher/ui/pages/instance/InstanceSettingsPage.h @@ -95,12 +95,11 @@ private slots: void updateAccountsMenu(); QIcon getFaceForAccount(MinecraftAccountPtr account); - void changeInstanceAccount(); + void changeInstanceAccount(int index); private: Ui::InstanceSettingsPage *ui; BaseInstance *m_instance; SettingsObjectPtr m_settings; unique_qobject_ptr checker; - QMenu *accountMenu = nullptr; }; diff --git a/launcher/ui/pages/instance/InstanceSettingsPage.ui b/launcher/ui/pages/instance/InstanceSettingsPage.ui index 1b986184..19d6dc02 100644 --- a/launcher/ui/pages/instance/InstanceSettingsPage.ui +++ b/launcher/ui/pages/instance/InstanceSettingsPage.ui @@ -636,14 +636,7 @@ - - - QToolButton::InstantPopup - - - Qt::ToolButtonTextBesideIcon - - + -- 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(-) 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(-) 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(+) 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 d582bf7f1f803d0ea8422732b46e25ee05f2fcb0 Mon Sep 17 00:00:00 2001 From: seth Date: Sat, 27 May 2023 13:45:28 -0400 Subject: feat(nix): flake-utils -> flake-parts Signed-off-by: seth --- default.nix | 15 ++++++- flake.lock | 68 +++++++++++++++++++---------- flake.nix | 78 +++------------------------------- nix/default.nix | 118 +++++++++++---------------------------------------- nix/dev.nix | 44 +++++++++++++++++++ nix/distribution.nix | 27 ++++++++++++ nix/flake-compat.nix | 9 ---- nix/package.nix | 100 +++++++++++++++++++++++++++++++++++++++++++ 8 files changed, 263 insertions(+), 196 deletions(-) create mode 100644 nix/dev.nix create mode 100644 nix/distribution.nix delete mode 100644 nix/flake-compat.nix create mode 100644 nix/package.nix diff --git a/default.nix b/default.nix index 146942d5..c7d0c267 100644 --- a/default.nix +++ b/default.nix @@ -1 +1,14 @@ -(import nix/flake-compat.nix).defaultNix +( + import + ( + let + lock = builtins.fromJSON (builtins.readFile ./flake.lock); + in + fetchTarball { + url = "https://github.com/edolstra/flake-compat/archive/${lock.nodes.flake-compat.locked.rev}.tar.gz"; + sha256 = lock.nodes.flake-compat.locked.narHash; + } + ) + {src = ./.;} +) +.defaultNix diff --git a/flake.lock b/flake.lock index ad9196a9..f4122f77 100644 --- a/flake.lock +++ b/flake.lock @@ -16,29 +16,34 @@ "type": "github" } }, - "flake-compat_2": { - "flake": false, + "flake-parts": { + "inputs": { + "nixpkgs-lib": "nixpkgs-lib" + }, "locked": { - "lastModified": 1673956053, - "narHash": "sha256-4gtG9iQuiKITOjNQQeQIpoIB6b16fm+504Ch3sNKLd8=", - "owner": "edolstra", - "repo": "flake-compat", - "rev": "35bb57c0c8d8b62bbfd284272c928ceb64ddbde9", + "lastModified": 1683560683, + "narHash": "sha256-XAygPMN5Xnk/W2c1aW0jyEa6lfMDZWlQgiNtmHXytPc=", + "owner": "hercules-ci", + "repo": "flake-parts", + "rev": "006c75898cf814ef9497252b022e91c946ba8e17", "type": "github" }, "original": { - "owner": "edolstra", - "repo": "flake-compat", + "owner": "hercules-ci", + "repo": "flake-parts", "type": "github" } }, "flake-utils": { + "inputs": { + "systems": "systems" + }, "locked": { - "lastModified": 1676283394, - "narHash": "sha256-XX2f9c3iySLCw54rJ/CZs+ZK6IQy7GXNY4nSOyu2QG4=", + "lastModified": 1681202837, + "narHash": "sha256-H+Rh19JDwRtpVPAWp64F+rlEtxUWBAQW28eAi3SRSzg=", "owner": "numtide", "repo": "flake-utils", - "rev": "3db36a8b464d0c4532ba1c7dda728f4576d6d073", + "rev": "cfacdce06f30d2b68473a46042957675eebb3401", "type": "github" }, "original": { @@ -100,33 +105,37 @@ "type": "github" } }, - "nixpkgs-stable": { + "nixpkgs-lib": { "locked": { - "lastModified": 1673800717, - "narHash": "sha256-SFHraUqLSu5cC6IxTprex/nTsI81ZQAtDvlBvGDWfnA=", + "dir": "lib", + "lastModified": 1682879489, + "narHash": "sha256-sASwo8gBt7JDnOOstnps90K1wxmVfyhsTPPNTGBPjjg=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "2f9fd351ec37f5d479556cd48be4ca340da59b8f", + "rev": "da45bf6ec7bbcc5d1e14d3795c025199f28e0de0", "type": "github" }, "original": { + "dir": "lib", "owner": "NixOS", - "ref": "nixos-22.11", + "ref": "nixos-unstable", "repo": "nixpkgs", "type": "github" } }, "pre-commit-hooks": { "inputs": { - "flake-compat": "flake-compat_2", - "flake-utils": [ - "flake-utils" + "flake-compat": [ + "flake-compat" ], + "flake-utils": "flake-utils", "gitignore": "gitignore", "nixpkgs": [ "nixpkgs" ], - "nixpkgs-stable": "nixpkgs-stable" + "nixpkgs-stable": [ + "nixpkgs" + ] }, "locked": { "lastModified": 1678376203, @@ -145,11 +154,26 @@ "root": { "inputs": { "flake-compat": "flake-compat", - "flake-utils": "flake-utils", + "flake-parts": "flake-parts", "libnbtplusplus": "libnbtplusplus", "nixpkgs": "nixpkgs", "pre-commit-hooks": "pre-commit-hooks" } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } } }, "root": "root", diff --git a/flake.nix b/flake.nix index f656703c..c3148fe0 100644 --- a/flake.nix +++ b/flake.nix @@ -3,11 +3,12 @@ inputs = { nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable"; - flake-utils.url = "github:numtide/flake-utils"; + flake-parts.url = "github:hercules-ci/flake-parts"; pre-commit-hooks = { url = "github:cachix/pre-commit-hooks.nix"; inputs.nixpkgs.follows = "nixpkgs"; - inputs.flake-utils.follows = "flake-utils"; + inputs.nixpkgs-stable.follows = "nixpkgs"; + inputs.flake-compat.follows = "flake-compat"; }; flake-compat = { url = "github:edolstra/flake-compat"; @@ -19,73 +20,8 @@ }; }; - outputs = { - self, - nixpkgs, - flake-utils, - pre-commit-hooks, - libnbtplusplus, - ... - }: let - # User-friendly version number. - version = builtins.substring 0 8 self.lastModifiedDate; - - # Supported systems (qtbase is currently broken for "aarch64-darwin") - supportedSystems = with flake-utils.lib.system; [ - x86_64-linux - x86_64-darwin - aarch64-linux - ]; - - packagesFn = pkgs: { - prismlauncher-qt5 = pkgs.libsForQt5.callPackage ./nix { - inherit version self libnbtplusplus; - }; - prismlauncher = pkgs.qt6Packages.callPackage ./nix { - inherit version self libnbtplusplus; - }; - }; - in - flake-utils.lib.eachSystem supportedSystems (system: let - pkgs = nixpkgs.legacyPackages.${system}; - in { - checks = { - pre-commit-check = pre-commit-hooks.lib.${system}.run { - src = ./.; - hooks = { - markdownlint.enable = true; - - alejandra.enable = true; - deadnix.enable = true; - - clang-format = { - enable = - false; # As most of the codebase is **not** formatted, we don't want clang-format yet - types_or = ["c" "c++"]; - }; - }; - }; - }; - - packages = let - packages = packagesFn pkgs; - in - packages // {default = packages.prismlauncher;}; - - devShells.default = pkgs.mkShell { - inherit (self.checks.${system}.pre-commit-check) shellHook; - packages = with pkgs; [ - nodePackages.markdownlint-cli - alejandra - deadnix - clang-tools - ]; - - inputsFrom = [self.packages.${system}.default]; - buildInputs = with pkgs; [ccache ninja]; - }; - }) - // { - overlays.default = final: _: (packagesFn final); - }; + outputs = inputs: + inputs.flake-parts.lib.mkFlake + {inherit inputs;} + {imports = [./nix];}; } diff --git a/nix/default.nix b/nix/default.nix index e0616b6e..7bad1440 100644 --- a/nix/default.nix +++ b/nix/default.nix @@ -1,100 +1,32 @@ { - lib, - stdenv, - cmake, - ninja, - jdk8, - jdk17, - zlib, - file, - wrapQtAppsHook, - xorg, - libpulseaudio, - qtbase, - qtsvg, - qtwayland, - libGL, - quazip, - glfw, - openal, - extra-cmake-modules, - tomlplusplus, - ghc_filesystem, - cmark, - msaClientID ? "", - jdks ? [jdk17 jdk8], - gamemodeSupport ? true, - gamemode, - # flake + inputs, self, - version, - libnbtplusplus, -}: -stdenv.mkDerivation rec { - pname = "prismlauncher"; - inherit version; - - src = lib.cleanSource self; - - nativeBuildInputs = [extra-cmake-modules cmake file jdk17 ninja wrapQtAppsHook]; - buildInputs = - [ - qtbase - qtsvg - zlib - quazip - ghc_filesystem - tomlplusplus - cmark - ] - ++ lib.optional (lib.versionAtLeast qtbase.version "6") qtwayland - ++ lib.optional gamemodeSupport gamemode.dev; + ... +}: { + imports = [ + ./dev.nix + ./distribution.nix + ]; - cmakeFlags = - lib.optionals (msaClientID != "") ["-DLauncher_MSA_CLIENT_ID=${msaClientID}"] - ++ lib.optionals (lib.versionOlder qtbase.version "6") ["-DLauncher_QT_VERSION_MAJOR=5"]; + _module.args = { + # User-friendly version number. + version = builtins.substring 0 8 self.lastModifiedDate; + }; - postUnpack = '' - rm -rf source/libraries/libnbtplusplus - mkdir source/libraries/libnbtplusplus - ln -s ${libnbtplusplus}/* source/libraries/libnbtplusplus - chmod -R +r+w source/libraries/libnbtplusplus - chown -R $USER: source/libraries/libnbtplusplus - ''; + perSystem = {system, ...}: { + # Nixpkgs instantiated for supported systems with our overlay. + _module.args.pkgs = import inputs.nixpkgs { + inherit system; + overlays = [self.overlays.default]; + }; + }; - qtWrapperArgs = let - libpath = with xorg; - lib.makeLibraryPath ([ - libX11 - libXext - libXcursor - libXrandr - libXxf86vm - libpulseaudio - libGL - glfw - openal - stdenv.cc.cc.lib - ] - ++ lib.optional gamemodeSupport gamemode.lib); - in [ - "--set LD_LIBRARY_PATH /run/opengl-driver/lib:${libpath}" - "--prefix PRISMLAUNCHER_JAVA_PATHS : ${lib.makeSearchPath "bin/java" jdks}" - # xorg.xrandr needed for LWJGL [2.9.2, 3) https://github.com/LWJGL/lwjgl/issues/128 - "--prefix PATH : ${lib.makeBinPath [xorg.xrandr]}" + # Supported systems. + systems = [ + "x86_64-linux" + "x86_64-darwin" + "aarch64-linux" + # Disabled due to qtbase being currently broken for "aarch64-darwin." + # "aarch64-darwin" ]; - - meta = with lib; { - homepage = "https://prismlauncher.org/"; - description = "A free, open source launcher for Minecraft"; - longDescription = '' - Allows you to have multiple, separate instances of Minecraft (each with - their own mods, texture packs, saves, etc) and helps you manage them and - their associated options with a simple interface. - ''; - platforms = platforms.linux; - changelog = "https://github.com/PrismLauncher/PrismLauncher/releases/tag/${version}"; - license = licenses.gpl3Only; - maintainers = with maintainers; [minion3665 Scrumplex]; - }; } diff --git a/nix/dev.nix b/nix/dev.nix new file mode 100644 index 00000000..0fe68c4e --- /dev/null +++ b/nix/dev.nix @@ -0,0 +1,44 @@ +{ + inputs, + self, + ... +}: { + perSystem = { + system, + pkgs, + ... + }: { + checks = { + pre-commit-check = inputs.pre-commit-hooks.lib.${system}.run { + src = self; + hooks = { + markdownlint.enable = true; + + alejandra.enable = true; + deadnix.enable = true; + + clang-format = { + enable = + false; # As most of the codebase is **not** formatted, we don't want clang-format yet + types_or = ["c" "c++"]; + }; + }; + }; + }; + + devShells.default = pkgs.mkShell { + inherit (self.checks.${system}.pre-commit-check) shellHook; + packages = with pkgs; [ + nodePackages.markdownlint-cli + alejandra + deadnix + clang-tools + ]; + + inputsFrom = [self.packages.${system}.default]; + buildInputs = with pkgs; [ccache ninja]; + }; + + formatter = pkgs.alejandra; + }; +} diff --git a/nix/distribution.nix b/nix/distribution.nix new file mode 100644 index 00000000..0b223f17 --- /dev/null +++ b/nix/distribution.nix @@ -0,0 +1,27 @@ +{ + inputs, + self, + version, + ... +}: { + perSystem = {pkgs, ...}: { + packages = { + inherit (pkgs) prismlauncher prismlauncher-qt5; + default = pkgs.prismlauncher; + }; + }; + + flake = { + overlays.default = _: prev: let + # Helper function to build prism against different versions of Qt. + mkPrism = qt: + qt.callPackage ./package.nix { + inherit (inputs) libnbtplusplus; + inherit self version; + }; + in { + prismlauncher = mkPrism prev.qt6Packages; + prismlauncher-qt5 = mkPrism prev.libsForQt5; + }; + }; +} diff --git a/nix/flake-compat.nix b/nix/flake-compat.nix deleted file mode 100644 index 7162a6cf..00000000 --- a/nix/flake-compat.nix +++ /dev/null @@ -1,9 +0,0 @@ -let - lock = builtins.fromJSON (builtins.readFile ../flake.lock); - inherit (lock.nodes.flake-compat.locked) rev narHash; - flake-compat = fetchTarball { - url = "https://github.com/edolstra/flake-compat/archive/${rev}.tar.gz"; - sha256 = narHash; - }; -in - import flake-compat {src = ../.;} diff --git a/nix/package.nix b/nix/package.nix new file mode 100644 index 00000000..e0616b6e --- /dev/null +++ b/nix/package.nix @@ -0,0 +1,100 @@ +{ + lib, + stdenv, + cmake, + ninja, + jdk8, + jdk17, + zlib, + file, + wrapQtAppsHook, + xorg, + libpulseaudio, + qtbase, + qtsvg, + qtwayland, + libGL, + quazip, + glfw, + openal, + extra-cmake-modules, + tomlplusplus, + ghc_filesystem, + cmark, + msaClientID ? "", + jdks ? [jdk17 jdk8], + gamemodeSupport ? true, + gamemode, + # flake + self, + version, + libnbtplusplus, +}: +stdenv.mkDerivation rec { + pname = "prismlauncher"; + inherit version; + + src = lib.cleanSource self; + + nativeBuildInputs = [extra-cmake-modules cmake file jdk17 ninja wrapQtAppsHook]; + buildInputs = + [ + qtbase + qtsvg + zlib + quazip + ghc_filesystem + tomlplusplus + cmark + ] + ++ lib.optional (lib.versionAtLeast qtbase.version "6") qtwayland + ++ lib.optional gamemodeSupport gamemode.dev; + + cmakeFlags = + lib.optionals (msaClientID != "") ["-DLauncher_MSA_CLIENT_ID=${msaClientID}"] + ++ lib.optionals (lib.versionOlder qtbase.version "6") ["-DLauncher_QT_VERSION_MAJOR=5"]; + + postUnpack = '' + rm -rf source/libraries/libnbtplusplus + mkdir source/libraries/libnbtplusplus + ln -s ${libnbtplusplus}/* source/libraries/libnbtplusplus + chmod -R +r+w source/libraries/libnbtplusplus + chown -R $USER: source/libraries/libnbtplusplus + ''; + + qtWrapperArgs = let + libpath = with xorg; + lib.makeLibraryPath ([ + libX11 + libXext + libXcursor + libXrandr + libXxf86vm + libpulseaudio + libGL + glfw + openal + stdenv.cc.cc.lib + ] + ++ lib.optional gamemodeSupport gamemode.lib); + in [ + "--set LD_LIBRARY_PATH /run/opengl-driver/lib:${libpath}" + "--prefix PRISMLAUNCHER_JAVA_PATHS : ${lib.makeSearchPath "bin/java" jdks}" + # xorg.xrandr needed for LWJGL [2.9.2, 3) https://github.com/LWJGL/lwjgl/issues/128 + "--prefix PATH : ${lib.makeBinPath [xorg.xrandr]}" + ]; + + meta = with lib; { + homepage = "https://prismlauncher.org/"; + description = "A free, open source launcher for Minecraft"; + longDescription = '' + Allows you to have multiple, separate instances of Minecraft (each with + their own mods, texture packs, saves, etc) and helps you manage them and + their associated options with a simple interface. + ''; + platforms = platforms.linux; + changelog = "https://github.com/PrismLauncher/PrismLauncher/releases/tag/${version}"; + license = licenses.gpl3Only; + maintainers = with maintainers; [minion3665 Scrumplex]; + }; +} -- cgit From 5d14724e66a1911b04dd5091e520751fd7f5ee90 Mon Sep 17 00:00:00 2001 From: seth Date: Sat, 27 May 2023 19:16:36 -0400 Subject: chore(deps): enable nix lockfile maintenance for renovate Signed-off-by: seth --- renovate.json | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/renovate.json b/renovate.json index 39a2b6e9..d97a8dc6 100644 --- a/renovate.json +++ b/renovate.json @@ -2,5 +2,11 @@ "$schema": "https://docs.renovatebot.com/renovate-schema.json", "extends": [ "config:base" - ] + ], + "nix": { + "enabled": true + }, + "lockFileMaintenance": { + "enabled": true + } } -- cgit From a52574b02670229e4731507b11230a47535c223e Mon Sep 17 00:00:00 2001 From: seth Date: Sat, 27 May 2023 19:25:49 -0400 Subject: chore(nix): add nil Signed-off-by: seth --- nix/dev.nix | 2 ++ 1 file changed, 2 insertions(+) diff --git a/nix/dev.nix b/nix/dev.nix index 0fe68c4e..a4ff2cc4 100644 --- a/nix/dev.nix +++ b/nix/dev.nix @@ -16,6 +16,7 @@ alejandra.enable = true; deadnix.enable = true; + nil.enable = true; clang-format = { enable = @@ -33,6 +34,7 @@ alejandra deadnix clang-tools + nil ]; inputsFrom = [self.packages.${system}.default]; -- cgit From acf1946dacb50913305c3edc05705e8e735eafb1 Mon Sep 17 00:00:00 2001 From: seth Date: Sat, 27 May 2023 19:25:58 -0400 Subject: chore(nix): update sources Signed-off-by: seth --- flake.lock | 36 +++++++++--------------------------- 1 file changed, 9 insertions(+), 27 deletions(-) diff --git a/flake.lock b/flake.lock index f4122f77..87586643 100644 --- a/flake.lock +++ b/flake.lock @@ -35,15 +35,12 @@ } }, "flake-utils": { - "inputs": { - "systems": "systems" - }, "locked": { - "lastModified": 1681202837, - "narHash": "sha256-H+Rh19JDwRtpVPAWp64F+rlEtxUWBAQW28eAi3SRSzg=", + "lastModified": 1667395993, + "narHash": "sha256-nuEHfE/LcWyuSWnS8t12N1wc105Qtau+/OdUAjtQ0rA=", "owner": "numtide", "repo": "flake-utils", - "rev": "cfacdce06f30d2b68473a46042957675eebb3401", + "rev": "5aed5285a952e0b949eb3ba02c12fa4fcfef535f", "type": "github" }, "original": { @@ -91,11 +88,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1678693419, - "narHash": "sha256-bbSv5yqZAW6dz+3f3f3pOUZbxpPN+3OgCljgn7P+nnQ=", + "lastModified": 1685012353, + "narHash": "sha256-U3oOge4cHnav8OLGdRVhL45xoRj4Ppd+It6nPC9nNIU=", "owner": "nixos", "repo": "nixpkgs", - "rev": "8e3fad82be64c06fbfb9fd43993aec9ef4623936", + "rev": "aeb75dba965e790de427b73315d5addf91a54955", "type": "github" }, "original": { @@ -138,11 +135,11 @@ ] }, "locked": { - "lastModified": 1678376203, - "narHash": "sha256-3tyYGyC8h7fBwncLZy5nCUjTJPrHbmNwp47LlNLOHSM=", + "lastModified": 1684842236, + "narHash": "sha256-rYWsIXHvNhVQ15RQlBUv67W3YnM+Pd+DuXGMvCBq2IE=", "owner": "cachix", "repo": "pre-commit-hooks.nix", - "rev": "1a20b9708962096ec2481eeb2ddca29ed747770a", + "rev": "61e567d6497bc9556f391faebe5e410e6623217f", "type": "github" }, "original": { @@ -159,21 +156,6 @@ "nixpkgs": "nixpkgs", "pre-commit-hooks": "pre-commit-hooks" } - }, - "systems": { - "locked": { - "lastModified": 1681028828, - "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", - "owner": "nix-systems", - "repo": "default", - "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", - "type": "github" - }, - "original": { - "owner": "nix-systems", - "repo": "default", - "type": "github" - } } }, "root": "root", -- cgit From 86974b046ee0b232b07e3a28cbc1e954d88dd40f Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Sun, 28 May 2023 11:48:09 +0100 Subject: Clarify comment Signed-off-by: TheKodeToad --- launcher/modplatform/modrinth/ModrinthPackExportTask.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp b/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp index 8db89bbd..98fbc218 100644 --- a/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp +++ b/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp @@ -129,7 +129,7 @@ void ModrinthPackExportTask::collectHashes() const Mod* mod = *modIter; if (mod->metadata() != nullptr) { QUrl& url = mod->metadata()->url; - // most likely some of these may be from curseforge + // ensure the url is permitted on modrinth.com if (!url.isEmpty() && BuildConfig.MODRINTH_MRPACK_HOSTS.contains(url.host())) { qDebug() << "Resolving" << relative << "from index"; -- cgit From 6c082403c4e910248a41fd84a1a48522484be2cf Mon Sep 17 00:00:00 2001 From: Trial97 Date: Wed, 31 May 2023 20:23:23 +0300 Subject: Fixed comments Signed-off-by: Trial97 --- launcher/Application.cpp | 33 +++++++++++++-------------------- 1 file changed, 13 insertions(+), 20 deletions(-) diff --git a/launcher/Application.cpp b/launcher/Application.cpp index 430a96af..026d1636 100644 --- a/launcher/Application.cpp +++ b/launcher/Application.cpp @@ -376,13 +376,10 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv) // init the logger { - static const QString logBase = "logs/"+BuildConfig.LAUNCHER_NAME + "-%0.log"; - QDir logDir = QDir(dataPath); - if(!logDir.exists("logs")) { - logDir.mkpath("logs"); //this can fail, but there is no need to throw an error *yet*, since it also triggers the error message below! - } - auto moveFile = [](const QString &oldName, const QString &newName) - { + static const QString logBase = FS::PathCombine("logs", BuildConfig.LAUNCHER_NAME + "-%0.log"); + FS::ensureFolderPathExists( + "logs"); // this can fail, but there is no need to throw an error *yet*, since it also triggers the error message below! + auto moveFile = [](const QString& oldName, const QString& newName) { QFile::remove(newName); QFile::copy(oldName, newName); QFile::remove(oldName); @@ -394,19 +391,15 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv) moveFile(logBase.arg(0), logBase.arg(1)); logFile = std::unique_ptr(new QFile(logBase.arg(0))); - if(!logFile->open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate)) - { - showFatalErrorMessage( - "The launcher data folder is not writable!", - QString( - "The launcher couldn't create a log file - the data folder is not writable.\n" - "\n" - "Make sure you have write permissions to the logs folder.\n" - "(%1)\n" - "\n" - "The launcher cannot continue until you fix this problem." - ).arg(dataPath+"/logs") - ); + if (!logFile->open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate)) { + showFatalErrorMessage("The launcher data folder is not writable!", + QString("The launcher couldn't create a log file - the data folder is not writable.\n" + "\n" + "Make sure you have write permissions to the data folder.\n" + "(%1)\n" + "\n" + "The launcher cannot continue until you fix this problem.") + .arg(dataPath)); return; } qInstallMessageHandler(appDebugOutput); -- cgit From 03b66ba7a5a1e35f47a59a9e1cd5d73ad6685c8e Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Wed, 31 May 2023 13:25:14 -0700 Subject: packaging: make windows nsis installer run the uninstaller for previous install before installing Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- program_info/win_install.nsi.in | 76 ++++++++++++++++++++++++++++++++++++++--- 1 file changed, 72 insertions(+), 4 deletions(-) diff --git a/program_info/win_install.nsi.in b/program_info/win_install.nsi.in index 1d902d5d..27c40039 100644 --- a/program_info/win_install.nsi.in +++ b/program_info/win_install.nsi.in @@ -12,6 +12,8 @@ OutFile "../@Launcher_CommonName@-Setup.exe" !define MUI_ICON "../@Launcher_Branding_ICO@" +!define UNINST_KEY "Software\Microsoft\Windows\CurrentVersion\Uninstall\@Launcher_CommonName@" + ;-------------------------------- ; Pages @@ -269,7 +271,74 @@ VIAddVersionKey /LANG=${LANG_ENGLISH} "ProductVersion" "@Launcher_VERSION_NAME4@ !macroend -;-------------------------------- +;------------------------------------------ +; Uninstall Previous install + +!macro RunUninstall exitcode uninstcommand + Push `${uninstcommand}` + Call RunUninstall + Pop ${exitcode} +!macroend + +; Checks that the uninstaller in the provided command exists and runs it. +Function RunUninstall + Exch $1 ; input uninstcommand + Push $2 ; Uninstaller + Push $3 ; Len + Push $4 ; uninstcommand + StrCpy $4 $1 ; make a copy of the command for later + StrCpy $3 "" + StrCpy $2 $1 1 ; take first char of string + StrCmp $2 '"' quoteloop stringloop + stringloop: ; get string length + StrCpy $2 $1 1 $3 ; get next char + IntOp $3 $3 + 1 ; index += 1 + StrCmp $2 "" +2 stringloop; if empty exit loop + IntOp $3 $3 - 1 ; index -= 1 + Goto run + quoteloop: ; get string length with quotes removed + StrCmp $3 "" 0 +2 ; if index is set skip quote removal + StrCpy $1 $1 "" 1 ; Remove initial quote + IntOp $3 $3 + 1 ; index += 1 + StrCpy $2 $1 1 $3 ; get next char + StrCmp $2 "" +2 ; if empty exit loop + StrCmp $2 '"' 0 quoteloop ; if ending quote exit loop, else loop + run: + StrCpy $2 $1 $3 ; Path to uninstaller ; (copy string up to ending quote - if it exists) + StrCpy $1 161 ; ERROR_BAD_PATHNAME ; set exit code (it get's overwritten with uninstaller exit code if ExecWait call doesn't error) + GetFullPathName $3 "$2\.." ; $InstDir + IfFileExists "$2" 0 +4 + ExecWait $4 $1 ; The file exists, call the saved command + IntCmp $1 0 "" +2 +2 ; Don't delete the installer if it was aborted ; + Delete "$2" ; Delete the uninstaller + RMDir "$3" ; Try to delete $InstDir + Pop $4 + Pop $3 + Pop $2 + Exch $1 ; exitcode +FunctionEnd + +; The "" makes the section hidden. +Section "" UninstallPrevious + + ReadRegStr $0 HKCU "${UNINST_KEY}" "QuietUninstallString" + ${If} $0 == "" + ReadRegStr $0 HKCU "${UNINST_KEY}" "UninstallString" + ${EndIf} + + ${If} $0 != "" + ${AndIf} ${Cmd} `MessageBox MB_YESNO|MB_ICONQUESTION "Uninstall previous version?" /SD IDYES IDYES` + !insertmacro RunUninstall $0 $0 + ${If} $0 <> 0 + MessageBox MB_YESNO|MB_ICONSTOP "Failed to uninstall, continue anyway?" /SD IDYES IDYES +2 + Abort + ${EndIf} + ${EndIf} + +SectionEnd + + +;------------------------------------ ; The stuff to install Section "@Launcher_DisplayName@" @@ -299,11 +368,10 @@ Section "@Launcher_DisplayName@" ${GetParameters} $R0 ${GetOptions} $R0 "/NoUninstaller" $R1 ${If} ${Errors} - !define UNINST_KEY "Software\Microsoft\Windows\CurrentVersion\Uninstall\@Launcher_CommonName@" WriteRegStr HKCU "${UNINST_KEY}" "DisplayName" "@Launcher_DisplayName@" WriteRegStr HKCU "${UNINST_KEY}" "DisplayIcon" "$INSTDIR\@Launcher_APP_BINARY_NAME@.exe" - WriteRegStr HKCU "${UNINST_KEY}" "UninstallString" '"$INSTDIR\uninstall.exe"' - WriteRegStr HKCU "${UNINST_KEY}" "QuietUninstallString" '"$INSTDIR\uninstall.exe" /S' + WriteRegStr HKCU "${UNINST_KEY}" "UninstallString" '"$INSTDIR\uninstall.exe" _?=$INSTDIR' + WriteRegStr HKCU "${UNINST_KEY}" "QuietUninstallString" '"$INSTDIR\uninstall.exe" /S _?=$INSTDIR' WriteRegStr HKCU "${UNINST_KEY}" "InstallLocation" "$INSTDIR" WriteRegStr HKCU "${UNINST_KEY}" "Publisher" "@Launcher_DisplayName@ Contributors" WriteRegStr HKCU "${UNINST_KEY}" "Version" "@Launcher_VERSION_NAME4@" -- cgit From 4593538fc864fe67d4c31376506f4d2b81201895 Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Wed, 31 May 2023 13:54:13 -0700 Subject: fix: typo - space before comment Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- program_info/win_install.nsi.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/program_info/win_install.nsi.in b/program_info/win_install.nsi.in index 27c40039..8389e246 100644 --- a/program_info/win_install.nsi.in +++ b/program_info/win_install.nsi.in @@ -293,7 +293,7 @@ Function RunUninstall stringloop: ; get string length StrCpy $2 $1 1 $3 ; get next char IntOp $3 $3 + 1 ; index += 1 - StrCmp $2 "" +2 stringloop; if empty exit loop + StrCmp $2 "" +2 stringloop ; if empty exit loop IntOp $3 $3 - 1 ; index -= 1 Goto run quoteloop: ; get string length with quotes removed -- cgit From 6a4fb6a27149893f65d09cb69f1ee7f0ad6bcfad Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Thu, 1 Jun 2023 12:40:08 -0700 Subject: packaging: remove redundant "do you want to uninstall previous version" Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- program_info/win_install.nsi.in | 1 - 1 file changed, 1 deletion(-) diff --git a/program_info/win_install.nsi.in b/program_info/win_install.nsi.in index 8389e246..d3b5c256 100644 --- a/program_info/win_install.nsi.in +++ b/program_info/win_install.nsi.in @@ -327,7 +327,6 @@ Section "" UninstallPrevious ${EndIf} ${If} $0 != "" - ${AndIf} ${Cmd} `MessageBox MB_YESNO|MB_ICONQUESTION "Uninstall previous version?" /SD IDYES IDYES` !insertmacro RunUninstall $0 $0 ${If} $0 <> 0 MessageBox MB_YESNO|MB_ICONSTOP "Failed to uninstall, continue anyway?" /SD IDYES IDYES +2 -- cgit From 3a6657596ba19a0310df38ed0c94f292c2de68c9 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Thu, 1 Jun 2023 23:48:48 +0300 Subject: Added migration for old logs Signed-off-by: Trial97 --- launcher/Application.cpp | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/launcher/Application.cpp b/launcher/Application.cpp index 026d1636..e9a3cb89 100644 --- a/launcher/Application.cpp +++ b/launcher/Application.cpp @@ -376,9 +376,18 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv) // init the logger { - static const QString logBase = FS::PathCombine("logs", BuildConfig.LAUNCHER_NAME + "-%0.log"); - FS::ensureFolderPathExists( - "logs"); // this can fail, but there is no need to throw an error *yet*, since it also triggers the error message below! + static const QString baseLogFile = BuildConfig.LAUNCHER_NAME + "-%0.log"; + static const QString logBase = FS::PathCombine("logs", baseLogFile); + if (FS::ensureFolderPathExists("logs")) { // if this did not fail + for (auto i = 0; i <= 4; i++) { + auto oldName = baseLogFile.arg(i); + auto newName = logBase.arg(i); + if (QFile::exists(newName)) // in case there are already files in folder just to be safe add a suffix + newName += ".old"; + QFile::rename(oldName, newName); + } + } + auto moveFile = [](const QString& oldName, const QString& newName) { QFile::remove(newName); QFile::copy(oldName, newName); -- cgit From 17691ab5154ecadf661f1035cfc87b4e4c5e236f Mon Sep 17 00:00:00 2001 From: Trial97 Date: Fri, 2 Jun 2023 01:22:25 +0300 Subject: Made use of moveFile function Signed-off-by: Trial97 --- launcher/Application.cpp | 22 ++++++++-------------- 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/launcher/Application.cpp b/launcher/Application.cpp index e9a3cb89..724e6e44 100644 --- a/launcher/Application.cpp +++ b/launcher/Application.cpp @@ -378,26 +378,20 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv) { static const QString baseLogFile = BuildConfig.LAUNCHER_NAME + "-%0.log"; static const QString logBase = FS::PathCombine("logs", baseLogFile); - if (FS::ensureFolderPathExists("logs")) { // if this did not fail - for (auto i = 0; i <= 4; i++) { - auto oldName = baseLogFile.arg(i); - auto newName = logBase.arg(i); - if (QFile::exists(newName)) // in case there are already files in folder just to be safe add a suffix - newName += ".old"; - QFile::rename(oldName, newName); - } - } - auto moveFile = [](const QString& oldName, const QString& newName) { QFile::remove(newName); QFile::copy(oldName, newName); QFile::remove(oldName); }; + if (FS::ensureFolderPathExists("logs")) { // if this did not fail + for (auto i = 0; i <= 4; i++) + if (auto oldName = baseLogFile.arg(i); + QFile::exists(oldName)) // do not pointlessly delete new files if the old ones are not there + moveFile(oldName, logBase.arg(i)); + } - moveFile(logBase.arg(3), logBase.arg(4)); - moveFile(logBase.arg(2), logBase.arg(3)); - moveFile(logBase.arg(1), logBase.arg(2)); - moveFile(logBase.arg(0), logBase.arg(1)); + for (auto i = 4; i > 0; i--) + moveFile(logBase.arg(i - 1), logBase.arg(i)); logFile = std::unique_ptr(new QFile(logBase.arg(0))); if (!logFile->open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate)) { -- cgit From 0f0cbd4c1faf7584f8f6deff7421ce8d7e79befb Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Fri, 2 Jun 2023 12:41:18 +0200 Subject: refactor(nix): introduce unwrapped packages Signed-off-by: Sefa Eyeoglu --- nix/distribution.nix | 10 +++++---- nix/package.nix | 57 ++++++++++------------------------------------------ 2 files changed, 17 insertions(+), 50 deletions(-) diff --git a/nix/distribution.nix b/nix/distribution.nix index 0b223f17..7c5ef93a 100644 --- a/nix/distribution.nix +++ b/nix/distribution.nix @@ -6,13 +6,13 @@ }: { perSystem = {pkgs, ...}: { packages = { - inherit (pkgs) prismlauncher prismlauncher-qt5; + inherit (pkgs) prismlauncher-qt5-unwrapped prismlauncher-qt5 prismlauncher-unwrapped prismlauncher; default = pkgs.prismlauncher; }; }; flake = { - overlays.default = _: prev: let + overlays.default = final: prev: let # Helper function to build prism against different versions of Qt. mkPrism = qt: qt.callPackage ./package.nix { @@ -20,8 +20,10 @@ inherit self version; }; in { - prismlauncher = mkPrism prev.qt6Packages; - prismlauncher-qt5 = mkPrism prev.libsForQt5; + prismlauncher-qt5-unwrapped = mkPrism final.libsForQt5; + prismlauncher-qt5 = prev.prismlauncher-qt5.override {inherit (final) prismlauncher-unwrapped;}; + prismlauncher-unwrapped = mkPrism final.qt6Packages; + prismlauncher = prev.prismlauncher.override {inherit (final) prismlauncher-unwrapped;}; }; }; } diff --git a/nix/package.nix b/nix/package.nix index e0616b6e..edc266dc 100644 --- a/nix/package.nix +++ b/nix/package.nix @@ -3,86 +3,51 @@ stdenv, cmake, ninja, - jdk8, jdk17, zlib, - file, - wrapQtAppsHook, - xorg, - libpulseaudio, qtbase, - qtsvg, - qtwayland, - libGL, quazip, - glfw, - openal, extra-cmake-modules, tomlplusplus, - ghc_filesystem, cmark, - msaClientID ? "", - jdks ? [jdk17 jdk8], - gamemodeSupport ? true, + ghc_filesystem, gamemode, - # flake + msaClientID ? null, + gamemodeSupport ? true, self, version, libnbtplusplus, }: stdenv.mkDerivation rec { - pname = "prismlauncher"; + pname = "prismlauncher-unwrapped"; inherit version; src = lib.cleanSource self; - nativeBuildInputs = [extra-cmake-modules cmake file jdk17 ninja wrapQtAppsHook]; + nativeBuildInputs = [extra-cmake-modules cmake jdk17 ninja]; buildInputs = [ qtbase - qtsvg zlib quazip ghc_filesystem tomlplusplus cmark ] - ++ lib.optional (lib.versionAtLeast qtbase.version "6") qtwayland - ++ lib.optional gamemodeSupport gamemode.dev; + ++ lib.optional gamemodeSupport gamemode; + + hardeningEnable = ["pie"]; cmakeFlags = - lib.optionals (msaClientID != "") ["-DLauncher_MSA_CLIENT_ID=${msaClientID}"] + lib.optionals (msaClientID != null) ["-DLauncher_MSA_CLIENT_ID=${msaClientID}"] ++ lib.optionals (lib.versionOlder qtbase.version "6") ["-DLauncher_QT_VERSION_MAJOR=5"]; postUnpack = '' rm -rf source/libraries/libnbtplusplus - mkdir source/libraries/libnbtplusplus - ln -s ${libnbtplusplus}/* source/libraries/libnbtplusplus - chmod -R +r+w source/libraries/libnbtplusplus - chown -R $USER: source/libraries/libnbtplusplus + ln -s ${libnbtplusplus} source/libraries/libnbtplusplus ''; - qtWrapperArgs = let - libpath = with xorg; - lib.makeLibraryPath ([ - libX11 - libXext - libXcursor - libXrandr - libXxf86vm - libpulseaudio - libGL - glfw - openal - stdenv.cc.cc.lib - ] - ++ lib.optional gamemodeSupport gamemode.lib); - in [ - "--set LD_LIBRARY_PATH /run/opengl-driver/lib:${libpath}" - "--prefix PRISMLAUNCHER_JAVA_PATHS : ${lib.makeSearchPath "bin/java" jdks}" - # xorg.xrandr needed for LWJGL [2.9.2, 3) https://github.com/LWJGL/lwjgl/issues/128 - "--prefix PATH : ${lib.makeBinPath [xorg.xrandr]}" - ]; + dontWrapQtApps = true; meta = with lib; { homepage = "https://prismlauncher.org/"; -- cgit From 29e532c096e8c89ba3f0e071fbdecf646f9814ea Mon Sep 17 00:00:00 2001 From: seth Date: Fri, 2 Jun 2023 11:53:09 -0400 Subject: fix(nix): fix prismlauncher-qt5 Signed-off-by: seth --- nix/distribution.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nix/distribution.nix b/nix/distribution.nix index 7c5ef93a..0f2e26f3 100644 --- a/nix/distribution.nix +++ b/nix/distribution.nix @@ -21,7 +21,7 @@ }; in { prismlauncher-qt5-unwrapped = mkPrism final.libsForQt5; - prismlauncher-qt5 = prev.prismlauncher-qt5.override {inherit (final) prismlauncher-unwrapped;}; + prismlauncher-qt5 = prev.prismlauncher-qt5.override {prismlauncher-unwrapped = final.prismlauncher-qt5-unwrapped;}; prismlauncher-unwrapped = mkPrism final.qt6Packages; prismlauncher = prev.prismlauncher.override {inherit (final) prismlauncher-unwrapped;}; }; -- cgit From f6f32914de6dbad07cffe786d0f15df03525a1c2 Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Fri, 2 Jun 2023 15:48:02 -0700 Subject: fix: add origonal instance path to allowed_symlinks.txt when copying via symlinks Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- launcher/InstanceCopyTask.cpp | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/launcher/InstanceCopyTask.cpp b/launcher/InstanceCopyTask.cpp index 4ac3b51a..abe97b17 100644 --- a/launcher/InstanceCopyTask.cpp +++ b/launcher/InstanceCopyTask.cpp @@ -39,7 +39,16 @@ void InstanceCopyTask::executeTask() setStatus(tr("Copying instance %1").arg(m_origInstance->name())); auto copySaves = [&]() { - FS::copy savesCopy(FS::PathCombine(m_origInstance->instanceRoot(), "saves"), FS::PathCombine(m_stagingPath, "saves")); + QFileInfo mcDir(FS::PathCombine(m_stagingPath, "minecraft")); + QFileInfo dotMCDir(FS::PathCombine(m_stagingPath, ".minecraft")); + + QString staging_mc_dir; + if (mcDir.exists() && !dotMCDir.exists()) + staging_mc_dir = mcDir.filePath(); + else + staging_mc_dir = dotMCDir.filePath(); + + FS::copy savesCopy(FS::PathCombine(m_origInstance->gameRoot(), "saves"), FS::PathCombine(staging_mc_dir, "saves")); savesCopy.followSymlinks(true); return savesCopy(); @@ -123,6 +132,7 @@ void InstanceCopyTask::copyFinished() emitFailed(tr("Instance folder copy failed.")); return; } + // FIXME: shouldn't this be able to report errors? auto instanceSettings = std::make_shared(FS::PathCombine(m_stagingPath, "instance.cfg")); @@ -134,6 +144,24 @@ void InstanceCopyTask::copyFinished() } if (m_useLinks) inst->addLinkedInstanceId(m_origInstance->id()); + if (m_useLinks) { + auto allowed_symlinks_file = QFileInfo(FS::PathCombine(inst->gameRoot(), "allowed_symlinks.txt")); + + QByteArray allowed_symlinks; + if (allowed_symlinks_file.exists()) { + allowed_symlinks.append(FS::read(allowed_symlinks_file.path())); + if (allowed_symlinks.right(1) != "\n") + allowed_symlinks.append("\n"); // we want to be on a new line + } + allowed_symlinks.append(m_origInstance->gameRoot().toUtf8()); + allowed_symlinks.append("\n"); + if (allowed_symlinks_file.isSymbolicLink()) + FS::deletePath(allowed_symlinks_file + .path()); // we dont want to modify the origonal. also make sure the resulting file is not itself a link. + + FS::write(allowed_symlinks_file.path(), allowed_symlinks); + } + emitSucceeded(); } -- cgit From 8eb10e991f79ef38e1c689d28e34b5f4fda8dc83 Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Fri, 2 Jun 2023 16:14:38 -0700 Subject: fix: use isSymLink (i've made this mistake before but I've made it again) Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- launcher/InstanceCopyTask.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/InstanceCopyTask.cpp b/launcher/InstanceCopyTask.cpp index abe97b17..a0b5635c 100644 --- a/launcher/InstanceCopyTask.cpp +++ b/launcher/InstanceCopyTask.cpp @@ -155,7 +155,7 @@ void InstanceCopyTask::copyFinished() } allowed_symlinks.append(m_origInstance->gameRoot().toUtf8()); allowed_symlinks.append("\n"); - if (allowed_symlinks_file.isSymbolicLink()) + if (allowed_symlinks_file.isSymLink()) FS::deletePath(allowed_symlinks_file .path()); // we dont want to modify the origonal. also make sure the resulting file is not itself a link. -- 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 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 3c87e5d31eb8fb33d6e63a38377c9729a130af45 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Sat, 3 Jun 2023 13:44:09 +0100 Subject: Make mcInstance mutable Signed-off-by: TheKodeToad --- launcher/modplatform/modrinth/ModrinthPackExportTask.cpp | 2 +- launcher/modplatform/modrinth/ModrinthPackExportTask.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp b/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp index 98fbc218..29df90dd 100644 --- a/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp +++ b/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp @@ -40,7 +40,7 @@ ModrinthPackExportTask::ModrinthPackExportTask(const QString& name, , version(version) , summary(summary) , instance(instance) - , mcInstance(dynamic_cast(instance.get())) + , mcInstance(dynamic_cast(instance.get())) , gameRoot(instance->gameRoot()) , output(output) , filter(filter) diff --git a/launcher/modplatform/modrinth/ModrinthPackExportTask.h b/launcher/modplatform/modrinth/ModrinthPackExportTask.h index 5426d6da..af00ffaa 100644 --- a/launcher/modplatform/modrinth/ModrinthPackExportTask.h +++ b/launcher/modplatform/modrinth/ModrinthPackExportTask.h @@ -51,7 +51,7 @@ class ModrinthPackExportTask : public Task { // inputs const QString name, version, summary; const InstancePtr instance; - const MinecraftInstance* mcInstance; + MinecraftInstance* mcInstance; const QDir gameRoot; const QString output; const MMCZip::FilterFunction filter; -- cgit From f613b03efd58e04451e70d4e673adff5837492a9 Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Sat, 3 Jun 2023 08:28:49 -0700 Subject: Typo fix Co-authored-by: Sefa Eyeoglu Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- launcher/InstanceCopyTask.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/InstanceCopyTask.cpp b/launcher/InstanceCopyTask.cpp index a0b5635c..60dcd5a1 100644 --- a/launcher/InstanceCopyTask.cpp +++ b/launcher/InstanceCopyTask.cpp @@ -157,7 +157,7 @@ void InstanceCopyTask::copyFinished() allowed_symlinks.append("\n"); if (allowed_symlinks_file.isSymLink()) FS::deletePath(allowed_symlinks_file - .path()); // we dont want to modify the origonal. also make sure the resulting file is not itself a link. + .path()); // we dont want to modify the original. also make sure the resulting file is not itself a link. FS::write(allowed_symlinks_file.path(), allowed_symlinks); } -- cgit From 5824047ffa4e31f018ddc068c2677ce9b8e5b43d Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Mon, 5 Jun 2023 01:12:16 -0700 Subject: fix(memory leak): cyclic refrence in translations model dl task Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- launcher/translations/TranslationsModel.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/launcher/translations/TranslationsModel.cpp b/launcher/translations/TranslationsModel.cpp index 46db4804..23e55c51 100644 --- a/launcher/translations/TranslationsModel.cpp +++ b/launcher/translations/TranslationsModel.cpp @@ -190,7 +190,7 @@ struct TranslationsModel::Private std::unique_ptr m_qt_translator; std::unique_ptr m_app_translator; - Net::Download::Ptr m_index_task; + Net::Download* m_index_task; QString m_downloadingTranslation; NetJob::Ptr m_dl_job; NetJob::Ptr m_index_job; @@ -673,8 +673,9 @@ void TranslationsModel::downloadIndex() d->m_index_job.reset(new NetJob("Translations Index", APPLICATION->network())); MetaEntryPtr entry = APPLICATION->metacache()->resolveEntry("translations", "index_v2.json"); entry->setStale(true); - d->m_index_task = Net::Download::makeCached(QUrl(BuildConfig.TRANSLATIONS_BASE_URL + "index_v2.json"), entry); - d->m_index_job->addNetAction(d->m_index_task); + auto task = Net::Download::makeCached(QUrl(BuildConfig.TRANSLATIONS_BASE_URL + "index_v2.json"), entry); + d->m_index_task = task.get(); + d->m_index_job->addNetAction(task); connect(d->m_index_job.get(), &NetJob::failed, this, &TranslationsModel::indexFailed); connect(d->m_index_job.get(), &NetJob::succeeded, this, &TranslationsModel::indexReceived); d->m_index_job->start(); -- 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(-) 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 6505a6280111e29b33d829703d1c1a87f90dba7a Mon Sep 17 00:00:00 2001 From: Trial97 Date: Tue, 6 Jun 2023 10:34:05 +0300 Subject: Renamed requires fields Signed-off-by: Trial97 --- launcher/meta/JsonFormat.cpp | 7 +++---- launcher/meta/Version.cpp | 4 ++-- launcher/meta/Version.h | 4 ++-- launcher/meta/VersionList.cpp | 4 ++-- launcher/minecraft/Component.cpp | 4 ++-- launcher/minecraft/OneSixVersionFormat.cpp | 10 +++++----- launcher/minecraft/VersionFile.h | 2 +- launcher/modplatform/atlauncher/ATLPackInstallTask.cpp | 2 +- .../modplatform/atlauncher/AtlUserInteractionSupportImpl.cpp | 2 +- 9 files changed, 19 insertions(+), 20 deletions(-) diff --git a/launcher/meta/JsonFormat.cpp b/launcher/meta/JsonFormat.cpp index 473f37d6..cb2d06ea 100644 --- a/launcher/meta/JsonFormat.cpp +++ b/launcher/meta/JsonFormat.cpp @@ -56,10 +56,10 @@ static Version::Ptr parseCommonVersion(const QString &uid, const QJsonObject &ob version->setType(ensureString(obj, "type", QString())); version->setRecommended(ensureBoolean(obj, QString("recommended"), false)); version->setVolatile(ensureBoolean(obj, QString("volatile"), false)); - RequireSet requires, conflicts; - parseRequires(obj, &requires, "requires"); + RequireSet reqs, conflicts; + parseRequires(obj, &reqs, "requires"); parseRequires(obj, &conflicts, "conflicts"); - version->setRequires(requires, conflicts); + version->setRequires(reqs, conflicts); return version; } @@ -176,7 +176,6 @@ void parseRequires(const QJsonObject& obj, RequireSet* ptr, const char * keyName { if(obj.contains(keyName)) { - QSet requires; auto reqArray = requireArray(obj, keyName); auto iter = reqArray.begin(); while(iter != reqArray.end()) diff --git a/launcher/meta/Version.cpp b/launcher/meta/Version.cpp index e617abf8..0718a420 100644 --- a/launcher/meta/Version.cpp +++ b/launcher/meta/Version.cpp @@ -116,9 +116,9 @@ void Meta::Version::setTime(const qint64 time) emit timeChanged(); } -void Meta::Version::setRequires(const Meta::RequireSet &requires, const Meta::RequireSet &conflicts) +void Meta::Version::setRequires(const Meta::RequireSet &reqs, const Meta::RequireSet &conflicts) { - m_requires = requires; + m_requires = reqs; m_conflicts = conflicts; emit requiresChanged(); } diff --git a/launcher/meta/Version.h b/launcher/meta/Version.h index 78156193..59a96a68 100644 --- a/launcher/meta/Version.h +++ b/launcher/meta/Version.h @@ -63,7 +63,7 @@ public: { return m_time; } - const Meta::RequireSet &requires() const + const Meta::RequireSet &requiredSet() const { return m_requires; } @@ -91,7 +91,7 @@ public: public: // for usage by format parsers only void setType(const QString &type); void setTime(const qint64 time); - void setRequires(const Meta::RequireSet &requires, const Meta::RequireSet &conflicts); + void setRequires(const Meta::RequireSet &reqs, const Meta::RequireSet &conflicts); void setVolatile(bool volatile_); void setRecommended(bool recommended); void setProvidesRecommendations(); diff --git a/launcher/meta/VersionList.cpp b/launcher/meta/VersionList.cpp index 7f001dfc..9f448278 100644 --- a/launcher/meta/VersionList.cpp +++ b/launcher/meta/VersionList.cpp @@ -77,7 +77,7 @@ QVariant VersionList::data(const QModelIndex &index, int role) const case ParentVersionRole: { // FIXME: HACK: this should be generic and be replaced by something else. Anything that is a hard 'equals' dep is a 'parent uid'. - auto & reqs = version->requires(); + auto & reqs = version->requiredSet(); auto iter = std::find_if(reqs.begin(), reqs.end(), [](const Require & req) { return req.uid == "net.minecraft"; @@ -92,7 +92,7 @@ QVariant VersionList::data(const QModelIndex &index, int role) const case UidRole: return version->uid(); case TimeRole: return version->time(); - case RequiresRole: return QVariant::fromValue(version->requires()); + case RequiresRole: return QVariant::fromValue(version->requiredSet()); case SortRole: return version->rawTime(); case VersionPtrRole: return QVariant::fromValue(version); case RecommendedRole: return version->isRecommended(); diff --git a/launcher/minecraft/Component.cpp b/launcher/minecraft/Component.cpp index 7e5b6058..ff81fcbb 100644 --- a/launcher/minecraft/Component.cpp +++ b/launcher/minecraft/Component.cpp @@ -451,9 +451,9 @@ void Component::updateCachedData() m_cachedVolatile = file->m_volatile; changed = true; } - if(!deepCompare(m_cachedRequires, file->requires)) + if(!deepCompare(m_cachedRequires, file->m_requires)) { - m_cachedRequires = file->requires; + m_cachedRequires = file->m_requires; changed = true; } if(!deepCompare(m_cachedConflicts, file->conflicts)) diff --git a/launcher/minecraft/OneSixVersionFormat.cpp b/launcher/minecraft/OneSixVersionFormat.cpp index 888b6860..b586198b 100644 --- a/launcher/minecraft/OneSixVersionFormat.cpp +++ b/launcher/minecraft/OneSixVersionFormat.cpp @@ -276,7 +276,7 @@ VersionFilePtr OneSixVersionFormat::versionFileFromJson(const QJsonDocument &doc if (root.contains("requires")) { - Meta::parseRequires(root, &out->requires); + Meta::parseRequires(root, &out->m_requires); } QString dependsOnMinecraftVersion = root.value("mcVersion").toString(); if(!dependsOnMinecraftVersion.isEmpty()) @@ -284,9 +284,9 @@ VersionFilePtr OneSixVersionFormat::versionFileFromJson(const QJsonDocument &doc Meta::Require mcReq; mcReq.uid = "net.minecraft"; mcReq.equalsVersion = dependsOnMinecraftVersion; - if (out->requires.count(mcReq) == 0) + if (out->m_requires.count(mcReq) == 0) { - out->requires.insert(mcReq); + out->m_requires.insert(mcReq); } } if (root.contains("conflicts")) @@ -392,9 +392,9 @@ QJsonDocument OneSixVersionFormat::versionFileToJson(const VersionFilePtr &patch } root.insert("mods", array); } - if(!patch->requires.empty()) + if(!patch->m_requires.empty()) { - Meta::serializeRequires(root, &patch->requires, "requires"); + Meta::serializeRequires(root, &patch->m_requires, "requires"); } if(!patch->conflicts.empty()) { diff --git a/launcher/minecraft/VersionFile.h b/launcher/minecraft/VersionFile.h index 11c5a3af..8e9dd167 100644 --- a/launcher/minecraft/VersionFile.h +++ b/launcher/minecraft/VersionFile.h @@ -138,7 +138,7 @@ public: /* data */ * Prism Launcher: set of packages this depends on * NOTE: this is shared with the meta format!!! */ - Meta::RequireSet requires; + Meta::RequireSet m_requires; /** * Prism Launcher: set of packages this conflicts with diff --git a/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp b/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp index 96cea7b7..07e0bf23 100644 --- a/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp +++ b/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp @@ -352,7 +352,7 @@ QString PackInstallTask::getVersionForLoader(QString uid) if(m_version.loader.recommended || m_version.loader.latest) { for (int i = 0; i < vlist->versions().size(); i++) { auto version = vlist->versions().at(i); - auto reqs = version->requires(); + auto reqs = version->requiredSet(); // filter by minecraft version, if the loader depends on a certain version. // not all mod loaders depend on a given Minecraft version, so we won't do this diff --git a/launcher/ui/pages/modplatform/atlauncher/AtlUserInteractionSupportImpl.cpp b/launcher/ui/pages/modplatform/atlauncher/AtlUserInteractionSupportImpl.cpp index f5f50cae..3d2d568a 100644 --- a/launcher/ui/pages/modplatform/atlauncher/AtlUserInteractionSupportImpl.cpp +++ b/launcher/ui/pages/modplatform/atlauncher/AtlUserInteractionSupportImpl.cpp @@ -68,7 +68,7 @@ QString AtlUserInteractionSupportImpl::chooseVersion(Meta::VersionList::Ptr vlis // select recommended build for (int i = 0; i < vlist->versions().size(); i++) { auto version = vlist->versions().at(i); - auto reqs = version->requires(); + auto reqs = version->requiredSet(); // filter by minecraft version, if the loader depends on a certain version. if (minecraftVersion != nullptr) { -- cgit From 3a068970f99c1437717651da951cee0762921308 Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Tue, 6 Jun 2023 07:03:13 -0700 Subject: Packaging: file manifest in portable install (#1101) --- .github/workflows/build.yml | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 691e257b..a0a0943a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -375,6 +375,8 @@ jobs: shell: msys2 {0} run: | cmake --install ${{ env.BUILD_DIR }} + touch ${{ env.INSTALL_DIR }}/manifest.txt + for l in $(find ${{ env.INSTALL_DIR }} -type f); do l=$(cygpath -u $l); l=${l#$(pwd)/}; l=${l#${{ env.INSTALL_DIR }}/}; l=${l#./}; echo $l; done >> ${{ env.INSTALL_DIR }}/manifest.txt - name: Package (Windows MSVC) if: runner.os == 'Windows' && matrix.msystem == '' @@ -387,6 +389,10 @@ jobs: Copy-Item D:/a/PrismLauncher/Qt/Tools/OpenSSL/Win_x86/bin/libcrypto-1_1.dll -Destination libcrypto-1_1.dll Copy-Item D:/a/PrismLauncher/Qt/Tools/OpenSSL/Win_x86/bin/libssl-1_1.dll -Destination libssl-1_1.dll } + cd ${{ github.workspace }} + + Get-ChildItem ${{ env.INSTALL_DIR }} -Recurse | ForEach FullName | Resolve-Path -Relative | %{ $_.TrimStart('.\') } | %{ $_.TrimStart('${{ env.INSTALL_DIR }}') } | %{ $_.TrimStart('\') } | Out-File -FilePath ${{ env.INSTALL_DIR }}/manifest.txt + - name: Fetch codesign certificate (Windows) if: runner.os == 'Windows' @@ -411,12 +417,15 @@ jobs: run: | cp -r ${{ env.INSTALL_DIR }} ${{ env.INSTALL_PORTABLE_DIR }} # cmake install on Windows is slow, let's just copy instead cmake --install ${{ env.BUILD_DIR }} --prefix ${{ env.INSTALL_PORTABLE_DIR }} --component portable + for l in $(find ${{ env.INSTALL_PORTABLE_DIR }} -type f); do l=$(cygpath -u $l); l=${l#$(pwd)/}; l=${l#${{ env.INSTALL_PORTABLE_DIR }}/}; l=${l#./}; echo $l; done >> ${{ env.INSTALL_PORTABLE_DIR }}/manifest.txt - name: Package (Windows MSVC, portable) if: runner.os == 'Windows' && matrix.msystem == '' run: | cp -r ${{ env.INSTALL_DIR }} ${{ env.INSTALL_PORTABLE_DIR }} # cmake install on Windows is slow, let's just copy instead cmake --install ${{ env.BUILD_DIR }} --prefix ${{ env.INSTALL_PORTABLE_DIR }} --component portable + + Get-ChildItem ${{ env.INSTALL_PORTABLE_DIR }} -Recurse | ForEach FullName | Resolve-Path -Relative | %{ $_.TrimStart('.\') } | %{ $_.TrimStart('${{ env.INSTALL_PORTABLE_DIR }}') } | %{ $_.TrimStart('\') } | Out-File -FilePath ${{ env.INSTALL_DIR }}/manifest.txt - name: Package (Windows, installer) if: runner.os == 'Windows' @@ -437,6 +446,7 @@ jobs: if: runner.os == 'Linux' run: | cmake --install ${{ env.BUILD_DIR }} --prefix ${{ env.INSTALL_DIR }} + for l in $(find ${{ env.INSTALL_DIR }} -type f); do l=${l#$(pwd)/}; l=${l#${{ env.INSTALL_DIR }}/}; l=${l#./}; echo $l; done > ${{ env.INSTALL_DIR }}/manifest.txt cd ${{ env.INSTALL_DIR }} tar --owner root --group root -czf ../PrismLauncher.tar.gz * @@ -446,6 +456,8 @@ jobs: run: | cmake --install ${{ env.BUILD_DIR }} --prefix ${{ env.INSTALL_PORTABLE_DIR }} cmake --install ${{ env.BUILD_DIR }} --prefix ${{ env.INSTALL_PORTABLE_DIR }} --component portable + for l in $(find ${{ env.INSTALL_PORTABLE_DIR }} -type f); do l=${l#$(pwd)/}; l=${l#${{ env.INSTALL_PORTABLE_DIR }}/}; l=${l#./}; echo $l; done > ${{ env.INSTALL_PORTABLE_DIR }}/manifest.txt + cd ${{ env.INSTALL_PORTABLE_DIR }} tar -czf ../PrismLauncher-portable.tar.gz * -- cgit From e8843417952ae0dc881a1ef8f352e4959f2c572b Mon Sep 17 00:00:00 2001 From: Tayou Date: Tue, 6 Jun 2023 18:15:26 +0200 Subject: save meta custom url as string, not QUrl Signed-off-by: Tayou --- launcher/ui/pages/global/APIPage.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/ui/pages/global/APIPage.cpp b/launcher/ui/pages/global/APIPage.cpp index f662ee1c..dca1b3a6 100644 --- a/launcher/ui/pages/global/APIPage.cpp +++ b/launcher/ui/pages/global/APIPage.cpp @@ -177,7 +177,7 @@ void APIPage::applySettings() metaURL.setScheme("https"); } - s->set("MetaURLOverride", metaURL); + s->set("MetaURLOverride", metaURL.toString()); QString flameKey = ui->flameKey->text(); s->set("FlameKeyOverride", flameKey); QString modrinthToken = ui->modrinthToken->text(); -- cgit From d59a06344a73e028a6f026d9f32027c2b9f73b39 Mon Sep 17 00:00:00 2001 From: leo78913 Date: Sun, 14 May 2023 15:03:32 -0300 Subject: fix main toolbar accounts toolbutton name previously it was not using the selected account name when opening the launcher and i also added an action group to the menu items so it uses radio buttons instead of checkboxes :p Signed-off-by: leo78913 --- launcher/ui/MainWindow.cpp | 31 +++++++++++++++++++++---------- 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp index 72b7db64..fab1185d 100644 --- a/launcher/ui/MainWindow.cpp +++ b/launcher/ui/MainWindow.cpp @@ -199,7 +199,6 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWi helpMenuButton->setPopupMode(QToolButton::InstantPopup); auto accountMenuButton = dynamic_cast(ui->mainToolBar->widgetForAction(ui->actionAccountsButton)); - ui->actionAccountsButton->setMenu(ui->accountsMenu); accountMenuButton->setPopupMode(QToolButton::InstantPopup); } @@ -414,15 +413,6 @@ void MainWindow::keyReleaseEvent(QKeyEvent *event) void MainWindow::retranslateUi() { - auto accounts = APPLICATION->accounts(); - MinecraftAccountPtr defaultAccount = accounts->defaultAccount(); - if(defaultAccount) { - auto profileLabel = profileInUseFilter(defaultAccount->profileName(), defaultAccount->isInUse()); - ui->actionAccountsButton->setText(profileLabel); - } - else { - ui->actionAccountsButton->setText(tr("Accounts")); - } if (m_selectedInstance) { m_statusLeft->setText(m_selectedInstance->getStatusbarDescription()); @@ -432,6 +422,12 @@ void MainWindow::retranslateUi() ui->retranslateUi(this); + MinecraftAccountPtr defaultAccount = APPLICATION->accounts()->defaultAccount(); + if(defaultAccount) { + auto profileLabel = profileInUseFilter(defaultAccount->profileName(), defaultAccount->isInUse()); + ui->actionAccountsButton->setText(profileLabel); + } + changeIconButton->setToolTip(ui->actionChangeInstIcon->toolTip()); renameButton->setToolTip(ui->actionRenameInstance->toolTip()); @@ -673,6 +669,15 @@ void MainWindow::repopulateAccountsMenu() { ui->accountsMenu->clear(); + // NOTE: this is done so the accounts button text is not set to the accounts menu title + QMenu *accountsButtonMenu = ui->actionAccountsButton->menu(); + if (accountsButtonMenu) { + accountsButtonMenu->clear(); + } else { + accountsButtonMenu = new QMenu(this); + ui->actionAccountsButton->setMenu(accountsButtonMenu); + } + auto accounts = APPLICATION->accounts(); MinecraftAccountPtr defaultAccount = accounts->defaultAccount(); @@ -687,6 +692,8 @@ void MainWindow::repopulateAccountsMenu() } } + QActionGroup* accountsGroup = new QActionGroup(this); + if (accounts->count() <= 0) { ui->actionNoAccountsAdded->setEnabled(false); @@ -702,6 +709,7 @@ void MainWindow::repopulateAccountsMenu() QAction *action = new QAction(profileLabel, this); action->setData(i); action->setCheckable(true); + action->setActionGroup(accountsGroup); if (defaultAccount == account) { action->setChecked(true); @@ -730,6 +738,7 @@ void MainWindow::repopulateAccountsMenu() ui->actionNoDefaultAccount->setData(-1); ui->actionNoDefaultAccount->setChecked(!defaultAccount); + ui->actionNoDefaultAccount->setActionGroup(accountsGroup); ui->accountsMenu->addAction(ui->actionNoDefaultAccount); @@ -737,6 +746,8 @@ void MainWindow::repopulateAccountsMenu() ui->accountsMenu->addSeparator(); ui->accountsMenu->addAction(ui->actionManageAccounts); + + accountsButtonMenu->addActions(ui->accountsMenu->actions()); } void MainWindow::updatesAllowedChanged(bool allowed) -- 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(+) 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 From a9302468e78c3e05ff6dd1e0bdcecea99e1a1e99 Mon Sep 17 00:00:00 2001 From: leo78913 Date: Tue, 6 Jun 2023 16:10:01 -0300 Subject: update resource and data pack pack_format_versions Signed-off-by: leo78913 --- launcher/minecraft/mod/DataPack.cpp | 4 +++- launcher/minecraft/mod/ResourcePack.cpp | 3 ++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/launcher/minecraft/mod/DataPack.cpp b/launcher/minecraft/mod/DataPack.cpp index 5c58f6b2..ca75cd2a 100644 --- a/launcher/minecraft/mod/DataPack.cpp +++ b/launcher/minecraft/mod/DataPack.cpp @@ -33,7 +33,9 @@ static const QMap> s_pack_format_versions = { { 4, { Version("1.13"), Version("1.14.4") } }, { 5, { Version("1.15"), Version("1.16.1") } }, { 6, { Version("1.16.2"), Version("1.16.5") } }, { 7, { Version("1.17"), Version("1.17.1") } }, { 8, { Version("1.18"), Version("1.18.1") } }, { 9, { Version("1.18.2"), Version("1.18.2") } }, - { 10, { Version("1.19"), Version("1.19.3") } }, + { 10, { Version("1.19"), Version("1.19.3") } }, { 11, { Version("23w03a"), Version("23w05a") } }, + { 12, { Version("1.19.4"), Version("1.19.4") } }, { 13, { Version("23w12a"), Version("23w14a") } }, + { 14, { Version("23w16a"), Version("23w17a") } }, { 15, { Version("1.20"), Version("1.20") } }, }; void DataPack::setPackFormat(int new_format_id) diff --git a/launcher/minecraft/mod/ResourcePack.cpp b/launcher/minecraft/mod/ResourcePack.cpp index 876d5c3e..759d2b56 100644 --- a/launcher/minecraft/mod/ResourcePack.cpp +++ b/launcher/minecraft/mod/ResourcePack.cpp @@ -18,7 +18,8 @@ static const QMap> s_pack_format_versions = { { 5, { Version("1.15"), Version("1.16.1") } }, { 6, { Version("1.16.2"), Version("1.16.5") } }, { 7, { Version("1.17"), Version("1.17.1") } }, { 8, { Version("1.18"), Version("1.18.2") } }, { 9, { Version("1.19"), Version("1.19.2") } }, { 11, { Version("22w42a"), Version("22w44a") } }, - { 12, { Version("1.19.3"), Version("1.19.3") } }, + { 12, { Version("1.19.3"), Version("1.19.3") } }, { 13, { Version("1.19.4"), Version("1.19.4") } }, + { 14, { Version("1.20"), Version("1.20") } } }; void ResourcePack::setPackFormat(int new_format_id) -- cgit From d12110b47bb1bb5109e41f2cf0f20908ceb8bc10 Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Wed, 7 Jun 2023 06:21:01 -0700 Subject: fix #1118 : use `filePath` not `path` on `QFileInfo` Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- launcher/InstanceCopyTask.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/launcher/InstanceCopyTask.cpp b/launcher/InstanceCopyTask.cpp index 60dcd5a1..57a3143a 100644 --- a/launcher/InstanceCopyTask.cpp +++ b/launcher/InstanceCopyTask.cpp @@ -149,7 +149,7 @@ void InstanceCopyTask::copyFinished() QByteArray allowed_symlinks; if (allowed_symlinks_file.exists()) { - allowed_symlinks.append(FS::read(allowed_symlinks_file.path())); + allowed_symlinks.append(FS::read(allowed_symlinks_file.filePath())); if (allowed_symlinks.right(1) != "\n") allowed_symlinks.append("\n"); // we want to be on a new line } @@ -157,9 +157,9 @@ void InstanceCopyTask::copyFinished() allowed_symlinks.append("\n"); if (allowed_symlinks_file.isSymLink()) FS::deletePath(allowed_symlinks_file - .path()); // we dont want to modify the original. also make sure the resulting file is not itself a link. + .filePath()); // we dont want to modify the original. also make sure the resulting file is not itself a link. - FS::write(allowed_symlinks_file.path(), allowed_symlinks); + FS::write(allowed_symlinks_file.filePath(), allowed_symlinks); } emitSucceeded(); -- cgit From 5b3431b26884aaee695dba97ca5faaddd46a5081 Mon Sep 17 00:00:00 2001 From: DioEgizio <83089242+DioEgizio@users.noreply.github.com> Date: Thu, 8 Jun 2023 14:55:09 +0200 Subject: chore: revert macOS Qt version back to 6.5.0 Qt 6.5.1 seems to cause issues with Rectangle Signed-off-by: DioEgizio <83089242+DioEgizio@users.noreply.github.com> --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a0a0943a..3ce9fb40 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -90,7 +90,7 @@ jobs: qt_ver: 6 qt_host: mac qt_arch: '' - qt_version: '6.5.1' + qt_version: '6.5.0' qt_modules: 'qt5compat qtimageformats' qt_tools: '' -- cgit From 75b1eaed0c71147398788d127df8240936ec72dc Mon Sep 17 00:00:00 2001 From: DioEgizio <83089242+DioEgizio@users.noreply.github.com> Date: Thu, 8 Jun 2023 17:12:49 +0200 Subject: chore: bump macOS requirement to 11.0 Noticed only now that Qt 6.5 bumps the macOS requirement to macOS 11. This was basically already effective in prism since with the Qt 6.5 bump pr macOS 10.15 user's wouldn't be able to run this, but updating the requirement here makes it more clear for the end user trying to run prism on macOS 10.15 Signed-off-by: DioEgizio <83089242+DioEgizio@users.noreply.github.com> --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 3ce9fb40..a6a6ecea 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -86,7 +86,7 @@ jobs: - os: macos-12 name: macOS - macosx_deployment_target: 10.15 + macosx_deployment_target: 11.0 qt_ver: 6 qt_host: mac qt_arch: '' -- cgit From 93436b09401b7f425cffaac49e03756526d04cb3 Mon Sep 17 00:00:00 2001 From: guihkx <626206+guihkx@users.noreply.github.com> Date: Fri, 9 Jun 2023 00:48:05 -0300 Subject: ci: exclude .git directory from the source code tarball Reduces the its final size from 17.1 MiB down to 7.9 MiB. Signed-off-by: guihkx <626206+guihkx@users.noreply.github.com> --- .github/workflows/trigger_release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/trigger_release.yml b/.github/workflows/trigger_release.yml index 3c56a38e..f19b8398 100644 --- a/.github/workflows/trigger_release.yml +++ b/.github/workflows/trigger_release.yml @@ -47,7 +47,7 @@ jobs: mv PrismLauncher-macOS-Legacy*/PrismLauncher.tar.gz PrismLauncher-macOS-Legacy-${{ env.VERSION }}.tar.gz mv PrismLauncher-macOS*/PrismLauncher.tar.gz PrismLauncher-macOS-${{ env.VERSION }}.tar.gz - tar -czf PrismLauncher-${{ env.VERSION }}.tar.gz PrismLauncher-${{ env.VERSION }} + tar --exclude='.git' -czf PrismLauncher-${{ env.VERSION }}.tar.gz PrismLauncher-${{ env.VERSION }} for d in PrismLauncher-Windows-MSVC*; do cd "${d}" || continue -- cgit From f2932c6d0387c41bd876d4af4148a7ffb5c83154 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Fri, 9 Jun 2023 21:23:41 +0300 Subject: Fixed some crashes Signed-off-by: Trial97 --- launcher/net/ByteArraySink.h | 15 ++++++++++++--- launcher/tasks/ConcurrentTask.cpp | 3 +-- launcher/ui/pages/modplatform/ResourcePage.cpp | 9 ++++++--- 3 files changed, 19 insertions(+), 8 deletions(-) diff --git a/launcher/net/ByteArraySink.h b/launcher/net/ByteArraySink.h index 501318a1..0d77727e 100644 --- a/launcher/net/ByteArraySink.h +++ b/launcher/net/ByteArraySink.h @@ -53,7 +53,10 @@ class ByteArraySink : public Sink { public: auto init(QNetworkRequest& request) -> Task::State override { - m_output->clear(); + if (m_output) + m_output->clear(); + else + qWarning() << "ByteArraySink was not cleared because it's not adresable"; if (initAllValidators(request)) return Task::State::Running; return Task::State::Failed; @@ -61,7 +64,10 @@ class ByteArraySink : public Sink { auto write(QByteArray& data) -> Task::State override { - m_output->append(data); + if (m_output) + m_output->append(data); + else + qWarning() << "ByteArraySink no write because it's not adresable"; if (writeAllValidators(data)) return Task::State::Running; return Task::State::Failed; @@ -69,7 +75,10 @@ class ByteArraySink : public Sink { auto abort() -> Task::State override { - m_output->clear(); + if (m_output) + m_output->clear(); + else + qWarning() << "ByteArraySink no clear because it's not adresable"; failAllValidators(); return Task::State::Failed; } diff --git a/launcher/tasks/ConcurrentTask.cpp b/launcher/tasks/ConcurrentTask.cpp index 5ee14505..9aada5e6 100644 --- a/launcher/tasks/ConcurrentTask.cpp +++ b/launcher/tasks/ConcurrentTask.cpp @@ -138,19 +138,18 @@ void ConcurrentTask::startNext() connect(next.get(), &Task::progress, this, [this, next](qint64 current, qint64 total) { subTaskProgress(next, current, total); }); m_doing.insert(next.get(), next); + qsizetype num_starts = qMin(m_queue.size(), m_total_max_size - m_doing.size()); auto task_progress = std::make_shared(next->getUid()); m_task_progress.insert(next->getUid(), task_progress); updateState(); updateStepProgress(*task_progress.get(), Operation::ADDED); - QCoreApplication::processEvents(); QMetaObject::invokeMethod(next.get(), &Task::start, Qt::QueuedConnection); // Allow going up the number of concurrent tasks in case of tasks being added in the middle of a running task. - int num_starts = qMin(m_queue.size(), m_total_max_size - m_doing.size()); for (int i = 0; i < num_starts; i++) QMetaObject::invokeMethod(this, &ConcurrentTask::startNext, Qt::QueuedConnection); } diff --git a/launcher/ui/pages/modplatform/ResourcePage.cpp b/launcher/ui/pages/modplatform/ResourcePage.cpp index 736034ad..2bb86777 100644 --- a/launcher/ui/pages/modplatform/ResourcePage.cpp +++ b/launcher/ui/pages/modplatform/ResourcePage.cpp @@ -240,10 +240,13 @@ void ResourcePage::updateSelectionButton() } m_ui->resourceSelectionButton->setEnabled(true); - if (!getCurrentPack()->isVersionSelected(m_selected_version_index)) { - m_ui->resourceSelectionButton->setText(tr("Select %1 for download").arg(resourceString())); + if (getCurrentPack()) { + if (!getCurrentPack()->isVersionSelected(m_selected_version_index)) + m_ui->resourceSelectionButton->setText(tr("Select %1 for download").arg(resourceString())); + else + m_ui->resourceSelectionButton->setText(tr("Deselect %1 for download").arg(resourceString())); } else { - m_ui->resourceSelectionButton->setText(tr("Deselect %1 for download").arg(resourceString())); + qWarning() << "Try to update selection but there is not a pack selected"; } } -- cgit From b3d743635c86aac583fb802c1e3c7aa25e12dc88 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Fri, 9 Jun 2023 21:29:12 +0300 Subject: Updated the messages Signed-off-by: Trial97 --- launcher/net/ByteArraySink.h | 6 +++--- launcher/ui/pages/modplatform/ResourcePage.cpp | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/launcher/net/ByteArraySink.h b/launcher/net/ByteArraySink.h index 0d77727e..728193b3 100644 --- a/launcher/net/ByteArraySink.h +++ b/launcher/net/ByteArraySink.h @@ -56,7 +56,7 @@ class ByteArraySink : public Sink { if (m_output) m_output->clear(); else - qWarning() << "ByteArraySink was not cleared because it's not adresable"; + qWarning() << "ByteArraySink did not initialize the buffer because it's not addressable"; if (initAllValidators(request)) return Task::State::Running; return Task::State::Failed; @@ -67,7 +67,7 @@ class ByteArraySink : public Sink { if (m_output) m_output->append(data); else - qWarning() << "ByteArraySink no write because it's not adresable"; + qWarning() << "ByteArraySink did not write the buffer because it's not addressable"; if (writeAllValidators(data)) return Task::State::Running; return Task::State::Failed; @@ -78,7 +78,7 @@ class ByteArraySink : public Sink { if (m_output) m_output->clear(); else - qWarning() << "ByteArraySink no clear because it's not adresable"; + qWarning() << "ByteArraySink did not clear the buffer because it's not addressable"; failAllValidators(); return Task::State::Failed; } diff --git a/launcher/ui/pages/modplatform/ResourcePage.cpp b/launcher/ui/pages/modplatform/ResourcePage.cpp index 2bb86777..1d2509d8 100644 --- a/launcher/ui/pages/modplatform/ResourcePage.cpp +++ b/launcher/ui/pages/modplatform/ResourcePage.cpp @@ -246,7 +246,7 @@ void ResourcePage::updateSelectionButton() else m_ui->resourceSelectionButton->setText(tr("Deselect %1 for download").arg(resourceString())); } else { - qWarning() << "Try to update selection but there is not a pack selected"; + qWarning() << "Tried to update the selected button but there is not a pack selected"; } } -- cgit From 5aa1c340dc6c353e34d0fbcac84fb89798a06481 Mon Sep 17 00:00:00 2001 From: Tayou Date: Sun, 11 Jun 2023 01:58:37 +0200 Subject: rainbow konami & toggle Signed-off-by: Tayou --- launcher/ui/MainWindow.cpp | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp index 834f57dd..dfee3b47 100644 --- a/launcher/ui/MainWindow.cpp +++ b/launcher/ui/MainWindow.cpp @@ -470,7 +470,23 @@ void MainWindow::lockToolbars(bool state) void MainWindow::konamiTriggered() { - qDebug() << "Super Secret Mode ACTIVATED!"; + QString gradient = " stop:0 rgba(125, 0, 0, 255), stop:0.166 rgba(125, 125, 0, 255), stop:0.333 rgba(0, 125, 0, 255), stop:0.5 rgba(0, 125, 125, 255), stop:0.666 rgba(0, 0, 125, 255), stop:0.833 rgba(125, 0, 125, 255), stop:1 rgba(125, 0, 0, 255));"; + QString stylesheet = "background-color: qlineargradient(spread:pad, x1:0, y1:0, x2:1, y2:0," + gradient; + if (ui->mainToolBar->styleSheet() == stylesheet) { + ui->mainToolBar->setStyleSheet(""); + ui->instanceToolBar->setStyleSheet(""); + ui->centralWidget->setStyleSheet(""); + ui->newsToolBar->setStyleSheet(""); + ui->statusBar->setStyleSheet(""); + qDebug() << "Super Secret Mode DEACTIVATED!"; + } else { + ui->mainToolBar->setStyleSheet(stylesheet); + ui->instanceToolBar->setStyleSheet("background-color: qlineargradient(spread:pad, x1:0, y1:0, x2:0, y2:1," + gradient); + ui->centralWidget->setStyleSheet("background-color: qlineargradient(spread:pad, x1:0, y1:0, x2:1, y2:1," + gradient); + ui->newsToolBar->setStyleSheet(stylesheet); + ui->statusBar->setStyleSheet(stylesheet); + qDebug() << "Super Secret Mode ACTIVATED!"; + } } void MainWindow::showInstanceContextMenu(const QPoint &pos) -- cgit From d6c7b4e813a254a2fb78547a7947aa913d80dbbb Mon Sep 17 00:00:00 2001 From: leo78913 Date: Sun, 11 Jun 2023 21:49:33 -0300 Subject: add icons to export menu Signed-off-by: leo78913 --- launcher/ui/MainWindow.cpp | 9 ++++++--- launcher/ui/MainWindow.ui | 10 ++++++---- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp index 834f57dd..bb7844d3 100644 --- a/launcher/ui/MainWindow.cpp +++ b/launcher/ui/MainWindow.cpp @@ -187,7 +187,7 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWi } - // set the menu for the folders help, and accounts tool buttons + // set the menu for the folders help, accounts, and export tool buttons { auto foldersMenuButton = dynamic_cast(ui->mainToolBar->widgetForAction(ui->actionFoldersButton)); ui->actionFoldersButton->setMenu(ui->foldersMenu); @@ -201,6 +201,11 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWi auto accountMenuButton = dynamic_cast(ui->mainToolBar->widgetForAction(ui->actionAccountsButton)); accountMenuButton->setPopupMode(QToolButton::InstantPopup); + + auto exportInstanceMenu = new QMenu(this); + exportInstanceMenu->addAction(ui->actionExportInstanceZip); + exportInstanceMenu->addAction(ui->actionExportInstanceMrPack); + ui->actionExportInstance->setMenu(exportInstanceMenu); } // hide, disable and show stuff @@ -397,8 +402,6 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWi // removing this looks stupid view->setFocus(); - ui->actionExportInstance->setMenu(ui->exportInstanceMenu); - retranslateUi(); } diff --git a/launcher/ui/MainWindow.ui b/launcher/ui/MainWindow.ui index 4a89bc10..9e639ab0 100644 --- a/launcher/ui/MainWindow.ui +++ b/launcher/ui/MainWindow.ui @@ -150,10 +150,6 @@ - - - - @@ -467,11 +463,17 @@ + + + Prism Launcher (zip) + + + Modrinth (mrpack) -- cgit From 94ddc8bbf7a65f08079c4cf42deb4eea86b1e53b Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Mon, 12 Jun 2023 14:14:50 +0100 Subject: Could this work? Signed-off-by: TheKodeToad --- launcher/modplatform/modrinth/ModrinthPackExportTask.cpp | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp b/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp index 29df90dd..eff47b37 100644 --- a/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp +++ b/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp @@ -27,7 +27,7 @@ #include "minecraft/PackProfile.h" #include "minecraft/mod/ModFolderModel.h" -const QStringList ModrinthPackExportTask::PREFIXES({ "mods", "coremods", "resourcepacks", "texturepacks", "shaderpacks" }); +const QStringList ModrinthPackExportTask::PREFIXES({ "mods/", "coremods/", "resourcepacks/", "texturepacks/", "shaderpacks/" }); const QStringList ModrinthPackExportTask::FILE_EXTENSIONS({ "jar", "litemod", "zip" }); ModrinthPackExportTask::ModrinthPackExportTask(const QString& name, @@ -99,14 +99,12 @@ void ModrinthPackExportTask::collectHashes() const QString relative = gameRoot.relativeFilePath(file.absoluteFilePath()); // require sensible file types - if (!std::any_of(PREFIXES.begin(), PREFIXES.end(), - [&relative](const QString& prefix) { return relative.startsWith(prefix + QDir::separator()); })) + if (!std::any_of(PREFIXES.begin(), PREFIXES.end(), [&relative](const QString& prefix) { return relative.startsWith(prefix); })) continue; if (!std::any_of(FILE_EXTENSIONS.begin(), FILE_EXTENSIONS.end(), [&relative](const QString& extension) { return relative.endsWith('.' + extension) || relative.endsWith('.' + extension + ".disabled"); - })) { + })) continue; - } QCryptographicHash sha512(QCryptographicHash::Algorithm::Sha512); -- cgit From f4a814b5e639641bad40bcea6abaf34f9c253046 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Mon, 12 Jun 2023 15:46:15 +0100 Subject: Remove unnecessary code Signed-off-by: TheKodeToad --- launcher/modplatform/modrinth/ModrinthPackExportTask.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp b/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp index eff47b37..bff9bf42 100644 --- a/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp +++ b/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp @@ -301,9 +301,7 @@ QByteArray ModrinthPackExportTask::generateIndex() const ResolvedFile& value = iterator.value(); QJsonObject file; - QString path = iterator.key(); - path.replace(QDir::separator(), "/"); - file["path"] = path; + file["path"] = iterator.key(); file["downloads"] = QJsonArray({ iterator.value().url }); QJsonObject hashes; -- cgit From b77fb059083ba04a3ccbc1b6e9ce4f5353b200ad Mon Sep 17 00:00:00 2001 From: Trial97 Date: Tue, 13 Jun 2023 21:07:05 +0300 Subject: Added back the INIFile read function Signed-off-by: Trial97 --- launcher/settings/INIFile.cpp | 85 +++++++++++++++++++++++++++++++++++++++---- tests/INIFile_test.cpp | 56 +++++++++++++++++++--------- 2 files changed, 115 insertions(+), 26 deletions(-) diff --git a/launcher/settings/INIFile.cpp b/launcher/settings/INIFile.cpp index f0347cab..cb909ae7 100644 --- a/launcher/settings/INIFile.cpp +++ b/launcher/settings/INIFile.cpp @@ -45,12 +45,12 @@ #include -INIFile::INIFile() -{ -} +INIFile::INIFile() {} bool INIFile::saveFile(QString fileName) { + if (!contains("ConfigVersion")) + insert("ConfigVersion", "1.1"); QSettings _settings_obj{ fileName, QSettings::Format::IniFormat }; _settings_obj.setFallbacksEnabled(false); @@ -71,6 +71,67 @@ bool INIFile::saveFile(QString fileName) return true; } +QString unescape(QString orig) +{ + QString out; + QChar prev = QChar::Null; + for (auto c : orig) { + if (prev == '\\') { + if (c == 'n') + out += '\n'; + else if (c == 't') + out += '\t'; + else if (c == '#') + out += '#'; + else + out += c; + prev = QChar::Null; + } else { + if (c == '\\') { + prev = c; + continue; + } + out += c; + prev = QChar::Null; + } + } + return out; +} +bool parseOldFileFormat(QIODevice& device, QSettings::SettingsMap& map) +{ + QTextStream in(device.readAll()); +#if QT_VERSION <= QT_VERSION_CHECK(6, 0, 0) + in.setCodec("UTF-8"); +#endif + + QStringList lines = in.readAll().split('\n'); + for (int i = 0; i < lines.count(); i++) { + QString& lineRaw = lines[i]; + // Ignore comments. + int commentIndex = 0; + QString line = lineRaw; + // Search for comments until no more escaped # are available + while ((commentIndex = line.indexOf('#', commentIndex + 1)) != -1) { + if (commentIndex > 0 && line.at(commentIndex - 1) == '\\') { + continue; + } + line = line.left(lineRaw.indexOf('#')).trimmed(); + } + + int eqPos = line.indexOf('='); + if (eqPos == -1) + continue; + QString key = line.left(eqPos).trimmed(); + QString valueStr = line.right(line.length() - eqPos - 1).trimmed(); + + valueStr = unescape(valueStr); + + QVariant value(valueStr); + map.insert(key, value); + } + + return true; +} bool INIFile::loadFile(QString fileName) { @@ -84,10 +145,19 @@ bool INIFile::loadFile(QString fileName) qCritical() << "A format error occurred (e.g. loading a malformed INI file)."; return false; } - - for (auto&& key : _settings_obj.allKeys()) - insert(key, _settings_obj.value(key)); - + if (!_settings_obj.value("ConfigVersion").isValid()) { + QFile file(fileName); + if (!file.open(QIODevice::ReadOnly)) + return false; + QSettings::SettingsMap map; + parseOldFileFormat(file, map); + file.close(); + for (auto&& key : map.keys()) + insert(key, map.value(key)); + insert("ConfigVersion", "1.1"); + } else + for (auto&& key : _settings_obj.allKeys()) + insert(key, _settings_obj.value(key)); return true; } @@ -103,4 +173,3 @@ void INIFile::set(QString key, QVariant val) { this->operator[](key) = val; } - diff --git a/tests/INIFile_test.cpp b/tests/INIFile_test.cpp index 4be8133c..2f49e573 100644 --- a/tests/INIFile_test.cpp +++ b/tests/INIFile_test.cpp @@ -1,24 +1,16 @@ #include +#include #include #include -#include #include -class IniFileTest : public QObject -{ +class IniFileTest : public QObject { Q_OBJECT -private -slots: - void initTestCase() - { - - } - void cleanupTestCase() - { - - } + private slots: + void initTestCase() {} + void cleanupTestCase() {} void test_Escape_data() { @@ -47,17 +39,17 @@ slots: // load INIFile f2; f2.loadFile(filename); - QCOMPARE(f2.get("a","NOT SET").toString(), a); - QCOMPARE(f2.get("b","NOT SET").toString(), b); + QCOMPARE(f2.get("a", "NOT SET").toString(), a); + QCOMPARE(f2.get("b", "NOT SET").toString(), b); } void test_SaveLoadLists() { QString slist_strings = "(\"a\",\"b\",\"c\")"; - QStringList list_strings = {"a", "b", "c"}; + QStringList list_strings = { "a", "b", "c" }; QString slist_numbers = "(1,2,3,10)"; - QList list_numbers = {1, 2, 3, 10}; + QList list_numbers = { 1, 2, 3, 10 }; QString filename = "test_SaveLoadLists.ini"; @@ -72,13 +64,41 @@ slots: QStringList out_list_strings = f2.get("list_strings", QStringList()).toStringList(); qDebug() << "OutStringList" << out_list_strings; - + QList out_list_numbers = QVariantUtils::toList(f2.get("list_numbers", QVariantUtils::fromList(QList()))); qDebug() << "OutNumbersList" << out_list_numbers; QCOMPARE(out_list_strings, list_strings); QCOMPARE(out_list_numbers, list_numbers); } + + void test_SaveAleardyExistingFile() + { + QString fileName = "test_SaveAleardyExistingFile.ini"; + QString fileContent = R"(InstanceType=OneSix +iconKey=vanillia_icon +name=Minecraft Vanillia +OverrideCommands=true +PreLaunchCommand="$INST_JAVA" -jar packwiz-installer-bootstrap.jar link +)"; + QFile file(fileName); + + if (file.open(QFile::WriteOnly | QFile::Text)) { + QTextStream stream(&file); + stream << fileContent; + file.close(); + } + + // load + INIFile f1; + f1.loadFile(fileName); + QCOMPARE(f1.get("PreLaunchCommand", "NOT SET").toString(), "\"$INST_JAVA\" -jar packwiz-installer-bootstrap.jar link"); + f1.saveFile(fileName); + INIFile f2; + f2.loadFile(fileName); + QCOMPARE(f2.get("PreLaunchCommand", "NOT SET").toString(), "\"$INST_JAVA\" -jar packwiz-installer-bootstrap.jar link"); + QCOMPARE(f2.get("ConfigVersion", "NOT SET").toString(), "1.1"); + } }; QTEST_GUILESS_MAIN(IniFileTest) -- cgit