Merge pull request #618 from TheKodeToad/safer-destructive-actions

Fixes https://github.com/PolyMC/PolyMC/issues/948
This commit is contained in:
Sefa Eyeoglu 2022-12-26 17:48:43 +01:00 committed by GitHub
commit 6ea1234a3b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 254 additions and 88 deletions

View File

@ -1,7 +1,8 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* PolyMC - Minecraft Launcher
* Prism Launcher - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
* Copyright (C) 2022 TheKodeToad <TheKodeToad@proton.me>
*
* 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
@ -226,7 +227,7 @@ bool deletePath(QString path)
return err.value() == 0;
}
bool trash(QString path, QString *pathInTrash = nullptr)
bool trash(QString path, QString *pathInTrash)
{
#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
return false;

View File

@ -1,7 +1,8 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* PolyMC - Minecraft Launcher
* Prism Launcher - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
* Copyright (C) 2022 TheKodeToad <TheKodeToad@proton.me>
*
* 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
@ -129,7 +130,7 @@ bool deletePath(QString path);
/**
* Trash a folder / file
*/
bool trash(QString path, QString *pathInTrash);
bool trash(QString path, QString *pathInTrash = nullptr);
QString PathCombine(const QString& path1, const QString& path2);
QString PathCombine(const QString& path1, const QString& path2, const QString& path3);

View File

@ -1,7 +1,8 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* PolyMC - Minecraft Launcher
* Prism Launcher - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
* Copyright (C) 2022 TheKodeToad <TheKodeToad@proton.me>
*
* 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
@ -545,6 +546,10 @@ bool World::replace(World &with)
bool World::destroy()
{
if(!is_valid) return false;
if (FS::trash(m_containerFile.filePath()))
return true;
if (m_containerFile.isDir())
{
QDir d(m_containerFile.filePath());

View File

@ -143,5 +143,9 @@ bool Resource::enable(EnableAction action)
bool Resource::destroy()
{
m_type = ResourceType::UNKNOWN;
if (FS::trash(m_file_info.filePath()))
return true;
return FS::deletePath(m_file_info.filePath());
}

View File

@ -1,8 +1,9 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* PolyMC - Minecraft Launcher
* Prism Launcher - Minecraft Launcher
* Copyright (C) 2022 Lenny McLennington <lenny@sneed.church>
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
* Copyright (C) 2022 TheKodeToad <TheKodeToad@proton.me>
*
* 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
@ -49,11 +50,34 @@
#include <DesktopServices.h>
#include <BuildConfig.h>
QString GuiUtil::uploadPaste(const QString &text, QWidget *parentWidget)
std::optional<QString> GuiUtil::uploadPaste(const QString &name, const QString &text, QWidget *parentWidget)
{
ProgressDialog dialog(parentWidget);
auto pasteTypeSetting = static_cast<PasteUpload::PasteType>(APPLICATION->settings()->get("PastebinType").toInt());
auto pasteCustomAPIBaseSetting = APPLICATION->settings()->get("PastebinCustomAPIBase").toString();
{
QUrl baseUrl;
if (pasteCustomAPIBaseSetting.isEmpty())
baseUrl = PasteUpload::PasteTypes[pasteTypeSetting].defaultBase;
else
baseUrl = pasteCustomAPIBaseSetting;
if (baseUrl.isValid())
{
auto response = CustomMessageBox::selectable(parentWidget, QObject::tr("Confirm Upload"),
QObject::tr("You are about to upload \"%1\" to %2.\n"
"You should double-check for personal information.\n\n"
"Are you sure?")
.arg(name, baseUrl.host()),
QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No, QMessageBox::No)
->exec();
if (response != QMessageBox::Yes)
return {};
}
}
std::unique_ptr<PasteUpload> paste(new PasteUpload(parentWidget, text, pasteCustomAPIBaseSetting, pasteTypeSetting));
dialog.execWithTask(paste.get());

View File

@ -1,10 +1,11 @@
#pragma once
#include <QWidget>
#include <optional>
namespace GuiUtil
{
QString uploadPaste(const QString &text, QWidget *parentWidget);
std::optional<QString> uploadPaste(const QString &name, const QString &text, QWidget *parentWidget);
void setClipboardText(const QString &text);
QStringList BrowseForFiles(QString context, QString caption, QString filter, QString defaultPath, QWidget *parentWidget);
QString BrowseForFile(QString context, QString caption, QString filter, QString defaultPath, QWidget *parentWidget);

View File

@ -1,7 +1,8 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* PolyMC - Minecraft Launcher
* Prism Launcher - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
* Copyright (C) 2022 TheKodeToad <TheKodeToad@proton.me>
*
* 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
@ -2093,21 +2094,23 @@ void MainWindow::on_actionDeleteInstance_triggered()
auto id = m_selectedInstance->id();
auto response =
CustomMessageBox::selectable(this, tr("CAREFUL!"),
tr("About to delete: %1\nThis may be permanent and will completely delete the instance.\n\nAre you sure?")
auto response = CustomMessageBox::selectable(this, tr("Confirm Deletion"),
tr("You are about to delete \"%1\".\n"
"This may be permanent and will completely delete the instance.\n\n"
"Are you sure?")
.arg(m_selectedInstance->name()),
QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No, QMessageBox::No)
->exec();
if (response == QMessageBox::Yes) {
if (response != QMessageBox::Yes)
return;
if (APPLICATION->instances()->trashInstance(id)) {
ui->actionUndoTrashInstance->setEnabled(APPLICATION->instances()->trashedSomething());
return;
}
APPLICATION->instances()->deleteInstance(id);
}
}
void MainWindow::on_actionExportInstance_triggered()

View File

@ -1,4 +1,5 @@
#include "ExternalResourcesPage.h"
#include "ui/dialogs/CustomMessageBox.h"
#include "ui_ExternalResourcesPage.h"
#include "DesktopServices.h"
@ -141,7 +142,6 @@ void ExternalResourcesPage::addItem()
if (!m_controlsEnabled)
return;
auto list = GuiUtil::BrowseForFiles(
helpPage(), tr("Select %1", "Select whatever type of files the page contains. Example: 'Loader Mods'").arg(displayName()),
m_fileSelectionFilter.arg(displayName()), APPLICATION->settings()->get("CentralModsDir").toString(), this->parentWidget());
@ -159,6 +159,48 @@ void ExternalResourcesPage::removeItem()
return;
auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection());
int count = 0;
bool folder = false;
for (auto& i : selection.indexes()) {
if (i.column() == 0) {
count++;
// if a folder is selected, show the confirmation dialog
if (m_model->at(i.row()).fileinfo().isDir())
folder = true;
}
}
QString text;
bool multiple = count > 1;
if (multiple) {
text = tr("You are about to remove %1 items.\n"
"This may be permanent and they will be gone from the folder.\n\n"
"Are you sure?")
.arg(count);
} else if (folder) {
text = tr("You are about to remove the folder \"%1\".\n"
"This may be permanent and it will be gone from the parent folder.\n\n"
"Are you sure?")
.arg(m_model->at(selection.indexes().at(0).row()).fileinfo().fileName());
}
if (!text.isEmpty()) {
auto response = CustomMessageBox::selectable(this, tr("Confirm Removal"), text, QMessageBox::Warning,
QMessageBox::Yes | QMessageBox::No, QMessageBox::No)
->exec();
if (response != QMessageBox::Yes)
return;
}
removeItems(selection);
}
void ExternalResourcesPage::removeItems(const QItemSelection& selection)
{
m_model->deleteResources(selection.indexes());
}
@ -209,4 +251,3 @@ bool ExternalResourcesPage::onSelectionChanged(const QModelIndex& current, const
return true;
}

View File

@ -50,7 +50,8 @@ class ExternalResourcesPage : public QMainWindow, public BasePage {
void filterTextChanged(const QString& newContents);
virtual void addItem();
virtual void removeItem();
void removeItem();
virtual void removeItems(const QItemSelection &selection);
virtual void enableItem();
virtual void disableItem();

View File

@ -1,8 +1,9 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* PolyMC - Minecraft Launcher
* Prism Launcher - Minecraft Launcher
* Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org>
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
* Copyright (C) 2022 TheKodeToad <TheKodeToad@proton.me>
*
* 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
@ -277,28 +278,22 @@ void LogPage::on_btnPaste_clicked()
//FIXME: turn this into a proper task and move the upload logic out of GuiUtil!
m_model->append(
MessageLevel::Launcher,
QString("%2: Log upload triggered at: %1").arg(
QDateTime::currentDateTime().toString(Qt::RFC2822Date),
BuildConfig.LAUNCHER_DISPLAYNAME
QString("Log upload triggered at: %1").arg(
QDateTime::currentDateTime().toString(Qt::RFC2822Date)
)
);
auto url = GuiUtil::uploadPaste(m_model->toPlainText(), this);
if(!url.isEmpty())
auto url = GuiUtil::uploadPaste(tr("Minecraft Log"), m_model->toPlainText(), this);
if(!url.has_value())
{
m_model->append(
MessageLevel::Launcher,
QString("%2: Log uploaded to: %1").arg(
url,
BuildConfig.LAUNCHER_DISPLAYNAME
)
);
m_model->append(MessageLevel::Error, QString("Log upload canceled"));
}
else if (url->isNull())
{
m_model->append(MessageLevel::Error, QString("Log upload failed!"));
}
else
{
m_model->append(
MessageLevel::Error,
QString("%1: Log upload failed!").arg(BuildConfig.LAUNCHER_DISPLAYNAME)
);
m_model->append(MessageLevel::Launcher, QString("Log uploaded to: %1").arg(url.value()));
}
}

View File

@ -1,8 +1,9 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* PolyMC - Minecraft Launcher
* Prism Launcher - Minecraft Launcher
* Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org>
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
* Copyright (C) 2022 TheKodeToad <TheKodeToad@proton.me>
*
* 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
@ -139,13 +140,8 @@ bool ModFolderPage::onSelectionChanged(const QModelIndex& current, const QModelI
return true;
}
void ModFolderPage::removeItem()
void ModFolderPage::removeItems(const QItemSelection &selection)
{
if (!m_controlsEnabled)
return;
auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection());
m_model->deleteMods(selection.indexes());
}

View File

@ -1,8 +1,9 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* PolyMC - Minecraft Launcher
* Prism Launcher - Minecraft Launcher
* Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org>
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
* Copyright (C) 2022 TheKodeToad <TheKodeToad@proton.me>
*
* 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
@ -59,7 +60,7 @@ class ModFolderPage : public ExternalResourcesPage {
private slots:
void runningStateChanged(bool running);
void removeItem() override;
void removeItems(const QItemSelection &selection) override;
void installMods();
void updateMods();

View File

@ -1,7 +1,8 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* PolyMC - Minecraft Launcher
* Prism Launcher - Minecraft Launcher
* Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org>
* Copyright (C) 2022 TheKodeToad <TheKodeToad@proton.me>
*
* 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
@ -204,7 +205,7 @@ void OtherLogsPage::on_btnReload_clicked()
void OtherLogsPage::on_btnPaste_clicked()
{
GuiUtil::uploadPaste(ui->text->toPlainText(), this);
GuiUtil::uploadPaste(m_currentFile, ui->text->toPlainText(), this);
}
void OtherLogsPage::on_btnCopy_clicked()
@ -219,13 +220,21 @@ void OtherLogsPage::on_btnDelete_clicked()
setControlsEnabled(false);
return;
}
if (QMessageBox::question(this, tr("Delete"),
tr("Do you really want to delete %1?").arg(m_currentFile),
QMessageBox::Yes, QMessageBox::No) == QMessageBox::No)
{
if (QMessageBox::question(this, tr("Confirm Deletion"),
tr("You are about to delete \"%1\".\n"
"This may be permanent and it will be gone from the logs folder.\n\n"
"Are you sure?")
.arg(m_currentFile),
QMessageBox::Yes, QMessageBox::No) == QMessageBox::No) {
return;
}
QFile file(FS::PathCombine(m_path, m_currentFile));
if (FS::trash(file.fileName()))
{
return;
}
if (!file.remove())
{
QMessageBox::critical(this, tr("Error"), tr("Unable to delete %1: %2")
@ -243,15 +252,15 @@ void OtherLogsPage::on_btnClean_clicked()
return;
}
QMessageBox *messageBox = new QMessageBox(this);
messageBox->setWindowTitle(tr("Clean up"));
messageBox->setWindowTitle(tr("Confirm Cleanup"));
if(toDelete.size() > 5)
{
messageBox->setText(tr("Do you really want to delete all log files?"));
messageBox->setText(tr("Are you sure you want to delete all log files?"));
messageBox->setDetailedText(toDelete.join('\n'));
}
else
{
messageBox->setText(tr("Do you really want to delete these files?\n%1").arg(toDelete.join('\n')));
messageBox->setText(tr("Are you sure you want to delete all these files?\n%1").arg(toDelete.join('\n')));
}
messageBox->setStandardButtons(QMessageBox::Ok | QMessageBox::Cancel);
messageBox->setDefaultButton(QMessageBox::Ok);
@ -267,6 +276,10 @@ void OtherLogsPage::on_btnClean_clicked()
for(auto item: toDelete)
{
QFile file(FS::PathCombine(m_path, item));
if (FS::trash(file.fileName()))
{
continue;
}
if (!file.remove())
{
failed.push_back(item);

View File

@ -1,8 +1,9 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* PolyMC - Minecraft Launcher
* Prism Launcher - Minecraft Launcher
* Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org>
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
* Copyright (C) 2022 TheKodeToad <TheKodeToad@proton.me>
*
* 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
@ -379,6 +380,24 @@ void ScreenshotsPage::on_actionUpload_triggered()
if (selection.isEmpty())
return;
QString text;
if (selection.size() > 1)
text = tr("You are about to upload %1 screenshots.\n\n"
"Are you sure?")
.arg(selection.size());
else
text =
tr("You are about to upload the selected screenshot.\n\n"
"Are you sure?");
auto response = CustomMessageBox::selectable(this, "Confirm Upload", text, QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No,
QMessageBox::No)
->exec();
if (response != QMessageBox::Yes)
return;
QList<ScreenShot::Ptr> uploaded;
auto job = NetJob::Ptr(new NetJob("Screenshot Upload", APPLICATION->network()));
if(selection.size() < 2)
@ -491,17 +510,32 @@ void ScreenshotsPage::on_actionCopy_File_s_triggered()
void ScreenshotsPage::on_actionDelete_triggered()
{
auto mbox = CustomMessageBox::selectable(
this, tr("Are you sure?"), tr("This will delete all selected screenshots."),
QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No);
std::unique_ptr<QMessageBox> box(mbox);
auto selected = ui->listView->selectionModel()->selectedIndexes();
if (box->exec() != QMessageBox::Yes)
int count = ui->listView->selectionModel()->selectedRows().size();
QString text;
if (count > 1)
text = tr("You are about to delete %1 screenshots.\n"
"This may be permanent and they will be gone from the folder.\n\n"
"Are you sure?")
.arg(count);
else
text = tr("You are about to delete the selected screenshot.\n"
"This may be permanent and it will be gone from the folder.\n\n"
"Are you sure?")
.arg(count);
auto response =
CustomMessageBox::selectable(this, tr("Confirm Deletion"), text, QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No)->exec();
if (response != QMessageBox::Yes)
return;
auto selected = ui->listView->selectionModel()->selectedIndexes();
for (auto item : selected)
{
if (FS::trash(m_model->filePath(item)))
continue;
m_model->remove(item);
}
}

View File

@ -1,8 +1,9 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* PolyMC - Minecraft Launcher
* Prism Launcher - Minecraft Launcher
* Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org>
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
* Copyright (C) 2022 TheKodeToad <TheKodeToad@proton.me>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -35,6 +36,7 @@
*/
#include "ServersPage.h"
#include "ui/dialogs/CustomMessageBox.h"
#include "ui_ServersPage.h"
#include <FileSystem.h>
@ -799,6 +801,17 @@ void ServersPage::on_actionAdd_triggered()
void ServersPage::on_actionRemove_triggered()
{
auto response = CustomMessageBox::selectable(this, tr("Confirm Removal"),
tr("You are about to remove \"%1\".\n"
"This is permanent and the server will be gone from your list forever (A LONG TIME).\n\n"
"Are you sure?")
.arg(m_model->at(currentServer)->m_name),
QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No, QMessageBox::No)
->exec();
if (response != QMessageBox::Yes)
return;
m_model->removeRow(currentServer);
}

View File

@ -318,14 +318,30 @@ void VersionPage::on_actionReload_triggered()
void VersionPage::on_actionRemove_triggered()
{
if (ui->packageView->currentIndex().isValid())
if (!ui->packageView->currentIndex().isValid())
{
return;
}
int index = ui->packageView->currentIndex().row();
auto component = m_profile->getComponent(index);
if (component->isCustom())
{
auto response = CustomMessageBox::selectable(this, tr("Confirm Removal"),
tr("You are about to remove \"%1\".\n"
"This is permanent and will completely remove the custom component.\n\n"
"Are you sure?")
.arg(component->getName()),
QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No, QMessageBox::No)
->exec();
if (response != QMessageBox::Yes)
return;
}
// FIXME: use actual model, not reloading.
if (!m_profile->remove(ui->packageView->currentIndex().row()))
if (!m_profile->remove(index))
{
QMessageBox::critical(this, tr("Error"), tr("Couldn't remove file"));
}
}
updateButtons();
reloadPackProfile();
m_container->refreshContainer();
@ -707,6 +723,19 @@ void VersionPage::on_actionRevert_triggered()
{
return;
}
auto component = m_profile->getComponent(version);
auto response = CustomMessageBox::selectable(this, tr("Confirm Reversion"),
tr("You are about to revert \"%1\".\n"
"This is permanent and will completely revert your customizations.\n\n"
"Are you sure?")
.arg(component->getName()),
QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No, QMessageBox::No)
->exec();
if (response != QMessageBox::Yes)
return;
if(!m_profile->revertToBase(version))
{
// TODO: some error box here

View File

@ -1,8 +1,9 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* PolyMC - Minecraft Launcher
* Prism Launcher - Minecraft Launcher
* Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org>
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
* Copyright (C) 2022 TheKodeToad <TheKodeToad@proton.me>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -35,6 +36,7 @@
*/
#include "WorldListPage.h"
#include "ui/dialogs/CustomMessageBox.h"
#include "ui_WorldListPage.h"
#include "minecraft/WorldList.h"
@ -192,12 +194,14 @@ void WorldListPage::on_actionRemove_triggered()
if(!proxiedIndex.isValid())
return;
auto result = QMessageBox::question(this,
tr("Are you sure?"),
tr("This will remove the selected world permenantly.\n"
"The world will be gone forever (A LONG TIME).\n"
"\n"
"Do you want to continue?"));
auto result = CustomMessageBox::selectable(this, tr("Confirm Deletion"),
tr("You are about to delete \"%1\".\n"
"The world may be gone forever (A LONG TIME).\n\n"
"Are you sure?")
.arg(m_worlds->allWorlds().at(proxiedIndex.row()).name()),
QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No, QMessageBox::No)
->exec();
if(result != QMessageBox::Yes)
{
return;

View File

@ -109,7 +109,7 @@
</action>
<action name="actionRemove">
<property name="text">
<string>Remove</string>
<string>Delete</string>
</property>
</action>
<action name="actionMCEdit">