Add file conflict dialog

Signed-off-by: Naomi <103967@gmail.com>
This commit is contained in:
Naomi 2024-08-23 09:41:31 +02:00
parent 7673364841
commit 7f35195686
6 changed files with 300 additions and 8 deletions

View File

@ -999,6 +999,8 @@ SET(LAUNCHER_SOURCES
ui/dialogs/ExportPackDialog.h
ui/dialogs/ExportToModListDialog.cpp
ui/dialogs/ExportToModListDialog.h
ui/dialogs/FileConflictDialog.cpp
ui/dialogs/FileConflictDialog.h
ui/dialogs/IconPickerDialog.cpp
ui/dialogs/IconPickerDialog.h
ui/dialogs/ImportResourceDialog.cpp
@ -1174,6 +1176,7 @@ qt_wrap_ui(LAUNCHER_UI
ui/dialogs/ExportInstanceDialog.ui
ui/dialogs/ExportPackDialog.ui
ui/dialogs/ExportToModListDialog.ui
ui/dialogs/FileConflictDialog.ui
ui/dialogs/IconPickerDialog.ui
ui/dialogs/ImportResourceDialog.ui
ui/dialogs/MSALoginDialog.ui

View File

@ -1737,4 +1737,5 @@ QString getSymLinkTarget(const QString& path)
{
return QFileInfo(path).symLinkTarget();
}
} // namespace FS

View File

@ -1,15 +1,67 @@
#include "UpdateGlobalDirectoriesTask.h"
#include <QDirIterator>
#include "Application.h"
#include "FileSystem.h"
#include "minecraft/MinecraftInstance.h"
#include "tasks/ConcurrentTask.h"
#include "ui/dialogs/CustomMessageBox.h"
#include "ui/dialogs/FileConflictDialog.h"
/**
* @brief Move a file or folder, and ask the user what to do in case of a conflict.
* @param source What to move.
* @param destination Where to move it to.
* @param recursive If true, all direct children will be moved 1 by 1.
* If false, the source will be directly moved to the destination.
* @param parent The parent of the dialog.
* @return True if everything could be moved.
*/
bool interactiveMove(const QString& source, const QString& destination, bool recursive = false, QWidget* parent = nullptr)
{
const QFileInfo sourceInfo(source);
// Make sure the source exists.
if (!sourceInfo.exists())
return false;
if (recursive) {
// Recursive doesn't make sense if the source isn't a directory.
if (!sourceInfo.isDir())
return false;
QDirIterator sourceIt(source, QDir::Filter::Files | QDir::Filter::Dirs | QDir::Filter::Hidden | QDir::Filter::NoDotAndDotDot);
while (sourceIt.hasNext()) {
if (!interactiveMove(sourceIt.next(), FS::PathCombine(destination, sourceIt.fileName()), false))
return false;
}
return true;
}
if (QFile(destination).exists()) {
FileConflictDialog dialog(source, destination, true, parent);
FileConflictDialog::Result result = dialog.execWithResult();
if (result == FileConflictDialog::Cancel)
return false;
else if (result == FileConflictDialog::ChooseDestination)
return FS::deletePath(source);
}
return FS::move(source, destination);
}
class TryCreateSymlinkTask : public Task {
public:
explicit TryCreateSymlinkTask(const QString& source, const QString& destination, MinecraftInstance* instance, const QString& setting)
: m_source(source), m_destination(destination), m_inst(instance), m_setting(setting)
explicit TryCreateSymlinkTask(const QString& source,
const QString& destination,
MinecraftInstance* instance,
const QString& setting,
QWidget* parent)
: m_source(source), m_destination(destination), m_inst(instance), m_setting(setting), m_parent(parent)
{
setObjectName("TryCreateSymlinkTask");
}
@ -49,9 +101,11 @@ class TryCreateSymlinkTask : public Task {
FS::deletePath(m_destination);
} else if (FS::checkFolderPathExists(m_destination)) {
if (!FS::checkFolderPathEmpty(m_destination)) {
if (!interactiveMove(m_destination, m_source, true, m_parent)) {
fail(tr("Failed to create global folder.\nEnsure that \"%1\" is empty.").arg(m_destination));
return;
}
}
FS::deletePath(m_destination);
}
@ -81,6 +135,7 @@ class TryCreateSymlinkTask : public Task {
QString m_destination;
MinecraftInstance* m_inst;
QString m_setting;
QWidget* m_parent;
};
UpdateGlobalDirectoriesTask::UpdateGlobalDirectoriesTask(MinecraftInstance* inst, QWidget* parent)
@ -94,22 +149,22 @@ void UpdateGlobalDirectoriesTask::executeTask()
auto tasks = makeShared<ConcurrentTask>(this, "UpdateGlobalDirectoriesTask");
auto screenshotsTask = makeShared<TryCreateSymlinkTask>(m_inst->settings()->get("GlobalScreenshotsPath").toString(),
m_inst->screenshotsDir(), m_inst, "UseGlobalScreenshotsFolder");
m_inst->screenshotsDir(), m_inst, "UseGlobalScreenshotsFolder", m_parent);
connect(screenshotsTask.get(), &Task::failed, this, &UpdateGlobalDirectoriesTask::notifyFailed);
tasks->addTask(screenshotsTask);
auto savesTask = makeShared<TryCreateSymlinkTask>(m_inst->settings()->get("GlobalSavesPath").toString(), m_inst->worldDir(), m_inst,
"UseGlobalSavesFolder");
"UseGlobalSavesFolder", m_parent);
connect(savesTask.get(), &Task::failed, this, &UpdateGlobalDirectoriesTask::notifyFailed);
tasks->addTask(savesTask);
auto resoucePacksTask = makeShared<TryCreateSymlinkTask>(m_inst->settings()->get("GlobalResourcePacksPath").toString(),
m_inst->resourcePacksDir(), m_inst, "UseGlobalResourcePacksFolder");
m_inst->resourcePacksDir(), m_inst, "UseGlobalResourcePacksFolder", m_parent);
connect(resoucePacksTask.get(), &Task::failed, this, &UpdateGlobalDirectoriesTask::notifyFailed);
tasks->addTask(resoucePacksTask);
auto texturePacksTask = makeShared<TryCreateSymlinkTask>(m_inst->settings()->get("GlobalResourcePacksPath").toString(),
m_inst->texturePacksDir(), m_inst, "UseGlobalResourcePacksFolder");
m_inst->texturePacksDir(), m_inst, "UseGlobalResourcePacksFolder", m_parent);
connect(texturePacksTask.get(), &Task::failed, this, &UpdateGlobalDirectoriesTask::notifyFailed);
tasks->addTask(texturePacksTask);

View File

@ -0,0 +1,85 @@
#include "FileConflictDialog.h"
#include "ui_FileConflictDialog.h"
#include <QDialogButtonBox>
#include <QFileInfo>
#include <QPushButton>
#include "Application.h"
FileConflictDialog::FileConflictDialog(QString source, QString destination, bool move, QWidget* parent)
: QDialog(parent), ui(new Ui::FileConflictDialog), m_result(Result::Cancel)
{
ui->setupUi(this);
QLocale locale;
// Setup buttons
connect(ui->buttonBox->button(QDialogButtonBox::Cancel), &QPushButton::clicked, this, &FileConflictDialog::cancel);
if (move) {
setWindowTitle("File conflict while moving files");
auto chooseSourceButton = ui->buttonBox->addButton(tr("Keep source"), QDialogButtonBox::DestructiveRole);
chooseSourceButton->setIcon(APPLICATION->getThemedIcon("delete"));
connect(chooseSourceButton, &QPushButton::clicked, this, &FileConflictDialog::chooseSource);
auto chooseDestinationButton = ui->buttonBox->addButton(tr("Keep destination"), QDialogButtonBox::DestructiveRole);
chooseDestinationButton->setIcon(APPLICATION->getThemedIcon("delete"));
connect(chooseDestinationButton, &QPushButton::clicked, this, &FileConflictDialog::chooseDestination);
} else {
setWindowTitle("File conflict while copying files");
auto chooseSourceButton = ui->buttonBox->addButton(tr("Overwrite destination"), QDialogButtonBox::DestructiveRole);
chooseSourceButton->setIcon(APPLICATION->getThemedIcon("delete"));
connect(chooseSourceButton, &QPushButton::clicked, this, &FileConflictDialog::chooseSource);
auto chooseDestinationButton = ui->buttonBox->addButton(tr("Skip"), QDialogButtonBox::DestructiveRole);
connect(chooseDestinationButton, &QPushButton::clicked, this, &FileConflictDialog::chooseDestination);
}
// Setup info
QFileInfo sourceInfo(source);
ui->sourceInfoLabel->setText(tr("<b>Name:</b> %1<br/><b>Size:</b> %2<br/><b>Last modified:</b> %3")
.arg(source, sourceInfo.isDir() ? "-" : locale.formattedDataSize(sourceInfo.size()),
sourceInfo.lastModified().toString(locale.dateTimeFormat())));
QFileInfo destinationInfo(destination);
ui->destinationInfoLabel->setText(tr("<b>Name:</b> %1<br/><b>Size:</b> %2<br/><b>Last modified:</b> %3")
.arg(destination,
destinationInfo.isDir() ? "-" : locale.formattedDataSize(destinationInfo.size()),
destinationInfo.lastModified().toString(locale.dateTimeFormat())));
}
FileConflictDialog::~FileConflictDialog()
{
delete ui;
}
FileConflictDialog::Result FileConflictDialog::execWithResult()
{
exec();
return m_result;
}
FileConflictDialog::Result FileConflictDialog::getResult() const
{
return m_result;
}
void FileConflictDialog::chooseSource()
{
m_result = Result::ChooseSource;
accept();
}
void FileConflictDialog::chooseDestination()
{
m_result = Result::ChooseDestination;
accept();
}
void FileConflictDialog::cancel()
{
m_result = Result::Cancel;
reject();
}

View File

@ -0,0 +1,34 @@
#pragma once
#include <QDialog>
namespace Ui {
class FileConflictDialog;
}
class FileConflictDialog : public QDialog {
Q_OBJECT
public:
enum Result { Cancel, ChooseSource, ChooseDestination };
/// @brief Create a new file conflict dialog
/// @param source The source path. What to copy/move.
/// @param destination The destination path. Where to copy/move.
/// @param move Whether the conflict is for a move or copy action
/// @param parent The parent of the dialog
explicit FileConflictDialog(QString source, QString destination, bool move = false, QWidget* parent = nullptr);
~FileConflictDialog() override;
Result execWithResult();
Result getResult() const;
private slots:
void chooseSource();
void chooseDestination();
void cancel();
private:
Ui::FileConflictDialog* ui;
Result m_result;
};

View File

@ -0,0 +1,114 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>FileConflictDialog</class>
<widget class="QDialog" name="FileConflictDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>411</width>
<height>219</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="windowTitle">
<string/>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item alignment="Qt::AlignmentFlag::AlignTop">
<widget class="QLabel" name="label">
<property name="text">
<string>Would you like to overwrite the destination?</string>
</property>
<property name="alignment">
<set>Qt::AlignmentFlag::AlignCenter</set>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="sizeConstraint">
<enum>QLayout::SizeConstraint::SetDefaultConstraint</enum>
</property>
<item>
<layout class="QVBoxLayout" name="sourceLayout" stretch="0,1">
<item>
<widget class="QLabel" name="sourceLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:700;&quot;&gt;Source&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="alignment">
<set>Qt::AlignmentFlag::AlignHCenter|Qt::AlignmentFlag::AlignTop</set>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="sourceInfoLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;b&gt;Name:&lt;/b&gt;&lt;/p&gt;&lt;p&gt;Size:&lt;/p&gt;&lt;p&gt;Date:&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="alignment">
<set>Qt::AlignmentFlag::AlignLeading|Qt::AlignmentFlag::AlignLeft|Qt::AlignmentFlag::AlignTop</set>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QVBoxLayout" name="destinationLayout" stretch="0,1">
<item>
<widget class="QLabel" name="destinationLabel">
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:700;&quot;&gt;Destination&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="alignment">
<set>Qt::AlignmentFlag::AlignHCenter|Qt::AlignmentFlag::AlignTop</set>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="destinationInfoLabel">
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Name:&lt;/p&gt;&lt;p&gt;Size:&lt;/p&gt;&lt;p&gt;Date:&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="alignment">
<set>Qt::AlignmentFlag::AlignLeading|Qt::AlignmentFlag::AlignLeft|Qt::AlignmentFlag::AlignTop</set>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Orientation::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::StandardButton::Cancel</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>