diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index 773913210..382a31662 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -315,8 +315,6 @@ set(MINECRAFT_SOURCES minecraft/Library.cpp minecraft/Library.h minecraft/MojangDownloadInfo.h - minecraft/UpdateSharedDirectoriesTask.cpp - minecraft/UpdateSharedDirectoriesTask.h minecraft/VanillaInstanceCreationTask.cpp minecraft/VanillaInstanceCreationTask.h minecraft/VersionFile.cpp diff --git a/launcher/FileSystem.cpp b/launcher/FileSystem.cpp index 97969e0db..48654a7c4 100644 --- a/launcher/FileSystem.cpp +++ b/launcher/FileSystem.cpp @@ -55,6 +55,8 @@ #include "DesktopServices.h" #include "PSaveFile.h" #include "StringUtils.h" +#include "ui/dialogs/CustomMessageBox.h" +#include "ui/dialogs/FileConflictDialog.h" #if defined Q_OS_WIN32 #define NOMINMAX @@ -674,6 +676,44 @@ bool move(const QString& source, const QString& dest) return true; } +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; + + // Recursive doesn't make sense if the source isn't a directory. + if (recursive && sourceInfo.isDir()) { + 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(); + + switch(result) { + case FileConflictDialog::Cancel: + return false; + case FileConflictDialog::ChooseDestination: + return FS::deletePath(source); + case FileConflictDialog::ChooseSource: + FS::deletePath(destination); + break; + } + } + + return FS::move(source, destination); +} + bool deletePath(QString path) { std::error_code err; @@ -1711,4 +1751,51 @@ QString getSymLinkTarget(const QString& path) return QFileInfo(path).symLinkTarget(); } +bool tryCreateSymlink(const QString& source, const QString& destination, const QString& symlinkName /* = "symbolic link" */) { + // Make sure that symbolic links are supported. + if (!FS::canLink(source, destination)) { + CustomMessageBox::selectable(nullptr, QObject::tr("Failed"), QObject::tr("Failed to create %1.\nSymbolic links are not supported on the filesystem").arg(symlinkName), QMessageBox::Warning, QMessageBox::Ok)->exec(); + return false; + } + + // Check if the destination already exists. + // If it's already a symlink, it might already be correct. + if (FS::isSymLink(destination)) { + // If the target of the symlink is already the source, there's nothing to do. + if (FS::getSymLinkTarget(destination) == source) { + return true; + } + + FS::deletePath(destination); + } else if (QFileInfo::exists(destination)) { + if (!FS::checkFolderPathEmpty(destination)) { + if (!interactiveMove(destination, source, true)) { + CustomMessageBox::selectable(nullptr, QObject::tr("Failed"), QObject::tr("Failed to create %1.\nEnsure that \"%2\" is empty.").arg(symlinkName).arg(destination), QMessageBox::Warning, QMessageBox::Ok)->exec(); + return false; + } + } + + FS::deletePath(destination); + } + + // Make sure the source folder exists + if (!FS::ensureFolderPathExists(source)) { + CustomMessageBox::selectable(nullptr, QObject::tr("Failed"), QObject::tr("Failed to create %1.\nEnsure that \"%2\" exists.").arg(symlinkName).arg(source), QMessageBox::Warning, QMessageBox::Ok)->exec(); + return false; + } + + FS::create_link folderLink(source, destination); + folderLink.linkRecursively(false); + + if (!folderLink()) { + CustomMessageBox::selectable(nullptr, QObject::tr("Failed"), QObject::tr("Failed to create %1. Error %2: %3") + .arg(symlinkName) + .arg(folderLink.getOSError().value()) + .arg(folderLink.getOSError().message().c_str()), + QMessageBox::Warning, QMessageBox::Ok)->exec(); + } + + return true; +} + } // namespace FS diff --git a/launcher/FileSystem.h b/launcher/FileSystem.h index 455d81457..1681c6093 100644 --- a/launcher/FileSystem.h +++ b/launcher/FileSystem.h @@ -293,6 +293,17 @@ class create_link : public QObject { */ bool move(const QString& source, const QString& dest); +/** + * @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); + /** * Delete a folder recursively */ @@ -587,4 +598,6 @@ bool isSymLink(const QString& path); */ QString getSymLinkTarget(const QString& path); +bool tryCreateSymlink(const QString& source, const QString& destination, const QString& symlinkName = "symbolic link"); + } // namespace FS diff --git a/launcher/minecraft/MinecraftInstance.cpp b/launcher/minecraft/MinecraftInstance.cpp index 506ae0e36..14860d778 100644 --- a/launcher/minecraft/MinecraftInstance.cpp +++ b/launcher/minecraft/MinecraftInstance.cpp @@ -85,7 +85,6 @@ #include "AssetsUtils.h" #include "MinecraftLoadAndCheck.h" #include "PackProfile.h" -#include "minecraft/UpdateSharedDirectoriesTask.h" #include "minecraft/gameoptions/GameOptions.h" #include "minecraft/update/FoldersTask.h" @@ -1317,8 +1316,38 @@ QList MinecraftInstance::getJarMods() const void MinecraftInstance::applySettings() { // Shared directories - m_update_shared_directories_task = std::make_shared(this); - m_update_shared_directories_task->start(); + updateSharedDirectories(); +} + +bool MinecraftInstance::updateSharedDirectories() +{ + const std::vector> sharedDirectories = { + { "UseSharedScreenshotsFolder", m_settings->get("SharedScreenshotsPath").toString(), screenshotsDir() }, + { "UseSharedSavesFolder", m_settings->get("SharedSavesPath").toString(), worldDir() }, + { "UseSharedResourcePacksFolder", m_settings->get("SharedResourcePacksPath").toString(), resourcePacksDir() }, + { "UseSharedResourcePacksFolder", m_settings->get("SharedResourcePacksPath").toString(), texturePacksDir() } + }; + + bool success = true; + for (const auto& [useSetting, source, destination] : sharedDirectories) { + // Create symlink if useSetting is true, remove symlink if false. + if (m_settings->get(useSetting).toBool()) + { + // Try to create symlink, set setting to false if failed. + if(!FS::tryCreateSymlink(source, destination, tr("shared folder"))) { + m_settings->set(useSetting, false); + success = false; + } + } + else { + // Safety check + if (FS::isSymLink(destination)) { + success = FS::deletePath(destination) && success; + } + } + } + + return success; } #include "MinecraftInstance.moc" diff --git a/launcher/minecraft/MinecraftInstance.h b/launcher/minecraft/MinecraftInstance.h index c92eba1be..35e119972 100644 --- a/launcher/minecraft/MinecraftInstance.h +++ b/launcher/minecraft/MinecraftInstance.h @@ -52,7 +52,6 @@ class WorldList; class GameOptions; class LaunchStep; class PackProfile; -class UpdateSharedDirectoriesTask; class MinecraftInstance : public BaseInstance { Q_OBJECT @@ -165,9 +164,10 @@ class MinecraftInstance : public BaseInstance { protected: QMap createCensorFilterFromSession(AuthSessionPtr session); + bool updateSharedDirectories(); + protected: // data std::shared_ptr m_components; - std::shared_ptr m_update_shared_directories_task; mutable std::shared_ptr m_loader_mod_list; mutable std::shared_ptr m_core_mod_list; mutable std::shared_ptr m_nil_mod_list; diff --git a/launcher/minecraft/UpdateSharedDirectoriesTask.cpp b/launcher/minecraft/UpdateSharedDirectoriesTask.cpp deleted file mode 100644 index a72953b52..000000000 --- a/launcher/minecraft/UpdateSharedDirectoriesTask.cpp +++ /dev/null @@ -1,186 +0,0 @@ -#include "UpdateSharedDirectoriesTask.h" - -#include - -#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; - - // Recursive doesn't make sense if the source isn't a directory. - if (recursive && sourceInfo.isDir()) { - 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(); - - switch(result) { - case FileConflictDialog::Cancel: - return false; - case FileConflictDialog::ChooseDestination: - return FS::deletePath(source); - case FileConflictDialog::ChooseSource: - FS::deletePath(destination); - break; - } - } - - 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) - { - setObjectName("TryCreateSymlinkTask"); - } - virtual ~TryCreateSymlinkTask() {} - - protected: - void executeTask() - { - bool create = m_inst->settings()->get(m_setting).toBool(); - - // Check if we have to delete an existing symlink - if (!create) { - // Safety check - if (FS::isSymLink(m_destination)) { - FS::deletePath(m_destination); - } - - emitSucceeded(); - return; - } - - // Make sure that symbolic links are supported. - if (!FS::canLink(m_source, m_destination)) { - fail(tr("Failed to create shared folder.\nSymbolic links are not supported on the filesystem")); - return; - } - - // Check if the destination already exists. - // If it's already a symlink, it might already be correct. - if (FS::isSymLink(m_destination)) { - // If the target of the symlink is already the source, there's nothing to do. - if (FS::getSymLinkTarget(m_destination) == m_source) { - emitSucceeded(); - return; - } - - FS::deletePath(m_destination); - } else if (QFileInfo::exists(m_destination)) { - if (!FS::checkFolderPathEmpty(m_destination)) { - if (!interactiveMove(m_destination, m_source, true)) { - fail(tr("Failed to create shared folder.\nEnsure that \"%1\" is empty.").arg(m_destination)); - return; - } - } - - FS::deletePath(m_destination); - } - - // Make sure the source folder exists - if (!FS::ensureFolderPathExists(m_source)) { - fail(tr("Failed to create shared folder.\nEnsure that \"%1\" exists.").arg(m_source)); - return; - } - - FS::create_link folderLink(m_source, m_destination); - folderLink.linkRecursively(false); - - if (folderLink()) { - emitSucceeded(); - } else { - fail(tr("Failed to create shared folder. Error %1: %2") - .arg(folderLink.getOSError().value()) - .arg(folderLink.getOSError().message().c_str())); - } - - return; - } - - void fail(const QString& reason) - { - m_inst->settings()->set(m_setting, false); - emitFailed(reason); - } - - private: - QString m_source; - QString m_destination; - MinecraftInstance* m_inst; - QString m_setting; -}; - -UpdateSharedDirectoriesTask::UpdateSharedDirectoriesTask(MinecraftInstance* inst) - : Task(), m_inst(inst) -{} - -void UpdateSharedDirectoriesTask::executeTask() -{ - auto tasks = makeShared("UpdateSharedDirectoriesTask"); - - auto screenshotsTask = makeShared(m_inst->settings()->get("SharedScreenshotsPath").toString(), - m_inst->screenshotsDir(), m_inst, "UseSharedScreenshotsFolder"); - connect(screenshotsTask.get(), &Task::failed, this, &UpdateSharedDirectoriesTask::notifyFailed); - tasks->addTask(screenshotsTask); - - auto savesTask = makeShared(m_inst->settings()->get("SharedSavesPath").toString(), m_inst->worldDir(), m_inst, - "UseSharedSavesFolder"); - connect(savesTask.get(), &Task::failed, this, &UpdateSharedDirectoriesTask::notifyFailed); - tasks->addTask(savesTask); - - auto resoucePacksTask = makeShared(m_inst->settings()->get("SharedResourcePacksPath").toString(), - m_inst->resourcePacksDir(), m_inst, "UseSharedResourcePacksFolder"); - connect(resoucePacksTask.get(), &Task::failed, this, &UpdateSharedDirectoriesTask::notifyFailed); - tasks->addTask(resoucePacksTask); - - auto texturePacksTask = makeShared(m_inst->settings()->get("SharedResourcePacksPath").toString(), - m_inst->texturePacksDir(), m_inst, "UseSharedResourcePacksFolder"); - connect(texturePacksTask.get(), &Task::failed, this, &UpdateSharedDirectoriesTask::notifyFailed); - tasks->addTask(texturePacksTask); - - m_tasks = tasks; - - connect(m_tasks.get(), &Task::succeeded, this, &UpdateSharedDirectoriesTask::emitSucceeded); - - m_tasks->start(); -} - -void UpdateSharedDirectoriesTask::notifyFailed(QString reason) -{ - CustomMessageBox::selectable(nullptr, tr("Failed"), reason, QMessageBox::Warning, QMessageBox::Ok)->exec(); - emit failed(reason); -} diff --git a/launcher/minecraft/UpdateSharedDirectoriesTask.h b/launcher/minecraft/UpdateSharedDirectoriesTask.h deleted file mode 100644 index c1c1f852b..000000000 --- a/launcher/minecraft/UpdateSharedDirectoriesTask.h +++ /dev/null @@ -1,21 +0,0 @@ -#pragma once - -#include "tasks/Task.h" - -class MinecraftInstance; - -class UpdateSharedDirectoriesTask : public Task { - public: - explicit UpdateSharedDirectoriesTask(MinecraftInstance* inst); - virtual ~UpdateSharedDirectoriesTask = default; - - protected: - virtual void executeTask() override; - - protected slots: - void notifyFailed(QString reason); - - private: - MinecraftInstance* m_inst; - Task::Ptr m_tasks; -};