From d2f674e08fa1b2d55cc9a7340e66e6a9d09b1a9f Mon Sep 17 00:00:00 2001
From: DioEgizio <83089242+DioEgizio@users.noreply.github.com>
Date: Sun, 19 Mar 2023 12:34:48 +0100
Subject: [PATCH 001/104] chore: update qt6 to 6.4.3 on windows and macos
Signed-off-by: DioEgizio <83089242+DioEgizio@users.noreply.github.com>
---
.github/workflows/build.yml | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 9337f3c7..022a04f8 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -68,7 +68,7 @@ jobs:
qt_ver: 6
qt_host: windows
qt_arch: ''
- qt_version: '6.4.2'
+ qt_version: '6.4.3'
qt_modules: 'qt5compat qtimageformats'
qt_tools: ''
@@ -80,7 +80,7 @@ jobs:
qt_ver: 6
qt_host: windows
qt_arch: 'win64_msvc2019_arm64'
- qt_version: '6.4.2'
+ qt_version: '6.4.3'
qt_modules: 'qt5compat qtimageformats'
qt_tools: ''
@@ -90,7 +90,7 @@ jobs:
qt_ver: 6
qt_host: mac
qt_arch: ''
- qt_version: '6.3.0'
+ qt_version: '6.4.3'
qt_modules: 'qt5compat qtimageformats'
qt_tools: ''
From f794e49bb6eadd70c52683e60a700a1d7e9cd17b Mon Sep 17 00:00:00 2001
From: Rachel Powers <508861+Ryex@users.noreply.github.com>
Date: Mon, 6 Feb 2023 23:05:06 -0800
Subject: [PATCH 002/104] we want to make links!
Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com>
---
.gitignore | 2 +
launcher/CMakeLists.txt | 52 ++++
launcher/FileSystem.cpp | 87 ++++++-
launcher/FileSystem.h | 68 ++++-
launcher/InstanceCopyPrefs.cpp | 30 +++
launcher/InstanceCopyPrefs.h | 9 +
launcher/filelink/FileLink.cpp | 119 +++++++++
launcher/filelink/FileLink.h | 52 ++++
launcher/filelink/filelink.exe.manifest | 28 ++
launcher/filelink/main.cpp | 31 +++
launcher/ui/dialogs/CopyInstanceDialog.cpp | 19 ++
launcher/ui/dialogs/CopyInstanceDialog.h | 3 +
launcher/ui/dialogs/CopyInstanceDialog.ui | 200 +++++++++-----
tests/FileSystem_test.cpp | 289 +++++++++++++++++++++
14 files changed, 918 insertions(+), 71 deletions(-)
create mode 100644 launcher/filelink/FileLink.cpp
create mode 100644 launcher/filelink/FileLink.h
create mode 100644 launcher/filelink/filelink.exe.manifest
create mode 100644 launcher/filelink/main.cpp
diff --git a/.gitignore b/.gitignore
index 3340670b..b3e2ee7d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -12,6 +12,8 @@ html/
CMakeLists.txt.user
CMakeLists.txt.user.*
CMakeSettings.json
+/CMakeFiles
+CMakeCache.txt
/.project
/.settings
/.idea
diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt
index 074570e3..2216aa1b 100644
--- a/launcher/CMakeLists.txt
+++ b/launcher/CMakeLists.txt
@@ -559,6 +559,11 @@ set(ATLAUNCHER_SOURCES
modplatform/atlauncher/ATLShareCode.h
)
+set(LINKDAEMON_SOURCES
+ filelink/FileLink.h
+ filelink/FileLink.cpp
+)
+
######## Logging categories ########
ecm_qt_declare_logging_category(CORE_SOURCES
@@ -1107,6 +1112,53 @@ install(TARGETS ${Launcher_Name}
FRAMEWORK DESTINATION ${FRAMEWORK_DEST_DIR} COMPONENT Runtime
)
+if(WIN32)
+ add_library(filelink_logic STATIC ${LINKDAEMON_SOURCES})
+ target_include_directories(filelink_logic PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
+ target_link_libraries(filelink_logic
+ systeminfo
+ BuildConfig
+ Qt${QT_VERSION_MAJOR}::Widgets
+ ghcFilesystem::ghc_filesystem
+ )
+ target_link_libraries(filelink_logic
+ Qt${QT_VERSION_MAJOR}::Core
+ Qt${QT_VERSION_MAJOR}::Xml
+ Qt${QT_VERSION_MAJOR}::Network
+ Qt${QT_VERSION_MAJOR}::Concurrent
+ Qt${QT_VERSION_MAJOR}::Gui
+ Qt${QT_VERSION_MAJOR}::Widgets
+ ${Launcher_QT_LIBS}
+ )
+
+ add_executable("${Launcher_Name}_filelink" WIN32 filelink/main.cpp)
+
+ target_sources("${Launcher_Name}_filelink" PRIVATE filelink/filelink.exe.manifest)
+
+ target_link_libraries("${Launcher_Name}_filelink" filelink_logic)
+
+ if(DEFINED Launcher_APP_BINARY_NAME)
+ set_target_properties("${Launcher_Name}_filelink" PROPERTIES OUTPUT_NAME "${Launcher_APP_BINARY_NAME}_filelink")
+ endif()
+ if(DEFINED Launcher_BINARY_RPATH)
+ SET_TARGET_PROPERTIES("${Launcher_Name}_filelink" PROPERTIES INSTALL_RPATH "${Launcher_BINARY_RPATH}")
+ endif()
+
+ if(CMAKE_GENERATOR MATCHES "Visual Studio")
+ SET_TARGET_PROPERTIES("${Launcher_Name}_filelink" PROPERTIES LINK_FLAGS "/level='requireAdministrator' /uiAccess='false' /SUBSYSTEM:CONSOLE")
+ else()
+ SET_TARGET_PROPERTIES("${Launcher_Name}_filelink" PROPERTIES LINK_FLAGS "/MANIFESTUAC:\"level='requireAdministrator' uiAccess='false'\" /SUBSYSTEM:CONSOLE")
+ endif()
+
+
+ install(TARGETS "${Launcher_Name}_filelink"
+ BUNDLE DESTINATION "." COMPONENT Runtime
+ LIBRARY DESTINATION ${LIBRARY_DEST_DIR} COMPONENT Runtime
+ RUNTIME DESTINATION ${BINARY_DEST_DIR} COMPONENT Runtime
+ FRAMEWORK DESTINATION ${FRAMEWORK_DEST_DIR} COMPONENT Runtime
+ )
+endif()
+
if (UNIX AND APPLE)
# Add Sparkle updater
# It has to be copied here instead of just allowing fixup_bundle to install it, otherwise essential parts of
diff --git a/launcher/FileSystem.cpp b/launcher/FileSystem.cpp
index aee5245d..ec4af98c 100644
--- a/launcher/FileSystem.cpp
+++ b/launcher/FileSystem.cpp
@@ -152,9 +152,11 @@ bool ensureFolderPathExists(QString foldernamepath)
return success;
}
-/// @brief Copies a directory and it's contents from src to dest
-/// @param offset subdirectory form src to copy to dest
-/// @return if there was an error during the filecopy
+/**
+ * @brief Copies a directory and it's contents from src to dest
+ * @param offset subdirectory form src to copy to dest
+ * @return if there was an error during the filecopy
+ */
bool copy::operator()(const QString& offset, bool dryRun)
{
using copy_opts = fs::copy_options;
@@ -215,6 +217,85 @@ bool copy::operator()(const QString& offset, bool dryRun)
return err.value() == 0;
}
+
+/**
+ * @brief links a directory and it's contents from src to dest
+ * @param offset subdirectory form src to link to dest
+ * @return if there was an error during the attempt to link
+ */
+bool create_link::operator()(const QString& offset, bool dryRun)
+{
+ m_linked = 0; // reset counter
+
+ auto src = PathCombine(m_src.absolutePath(), offset);
+ auto dst = PathCombine(m_dst.absolutePath(), offset);
+
+ std::error_code err;
+
+ // you can't hard link a directory so make sure if we deal with a directory we do so recursively
+ if (m_useHardLinks)
+ m_recursive = true;
+
+ // Function that'll do the actual linking
+ auto link_file = [&](QString src_path, QString relative_dst_path) {
+ if (m_matcher && (m_matcher->matches(relative_dst_path) != m_whitelist))
+ return;
+
+ auto dst_path = PathCombine(dst, relative_dst_path);
+ if (!dryRun) {
+
+ ensureFilePathExists(dst_path);
+ if (m_useHardLinks) {
+ if (m_debug)
+ qDebug() << "making hard link:" << src_path << "to" << dst_path;
+ fs::create_hard_link(StringUtils::toStdString(src_path), StringUtils::toStdString(dst_path), err);
+ } else if (fs::is_directory(StringUtils::toStdString(src_path))) {
+ if (m_debug)
+ qDebug() << "making directory_symlink:" << src_path << "to" << dst_path;
+ fs::create_directory_symlink(StringUtils::toStdString(src_path), StringUtils::toStdString(dst_path), err);
+ } else {
+ if (m_debug)
+ qDebug() << "making symlink:" << src_path << "to" << dst_path;
+ fs::create_symlink(StringUtils::toStdString(src_path), StringUtils::toStdString(dst_path), err);
+ }
+
+ }
+ if (err) {
+ qWarning() << "Failed to link files:" << QString::fromStdString(err.message());
+ qDebug() << "Source file:" << src_path;
+ qDebug() << "Destination file:" << dst_path;
+ qDebug() << "Error catagory:" << err.category().name();
+ qDebug() << "Error code:" << err.value();
+ m_last_os_err = err.value();
+ emit linkFailed(src_path, dst_path, err);
+ } else {
+ m_linked++;
+ emit fileLinked(relative_dst_path);
+ }
+
+ };
+
+ if ((!m_recursive) || !fs::is_directory(StringUtils::toStdString(src))) {
+ if (m_debug)
+ qDebug() << "linking single file or dir:" << src << "to" << dst;
+ link_file(src, "");
+ } else {
+ if (m_debug)
+ qDebug() << "linking recursivly:" << src << "to" << dst;
+ QDir src_dir(src);
+ QDirIterator source_it(src, QDir::Filter::Files | QDir::Filter::Hidden, QDirIterator::Subdirectories);
+
+ while (source_it.hasNext()) {
+ auto src_path = source_it.next();
+ auto relative_path = src_dir.relativeFilePath(src_path);
+
+ link_file(src_path, relative_path);
+ }
+ }
+
+ return err.value() == 0;
+}
+
bool move(const QString& source, const QString& dest)
{
std::error_code err;
diff --git a/launcher/FileSystem.h b/launcher/FileSystem.h
index f083f3c7..98f55f96 100644
--- a/launcher/FileSystem.h
+++ b/launcher/FileSystem.h
@@ -77,7 +77,9 @@ bool ensureFilePathExists(QString filenamepath);
*/
bool ensureFolderPathExists(QString filenamepath);
-/// @brief Copies a directory and it's contents from src to dest
+/**
+ * @brief Copies a directory and it's contents from src to dest
+ */
class copy : public QObject {
Q_OBJECT
public:
@@ -122,6 +124,70 @@ class copy : public QObject {
int m_copied;
};
+/**
+ * @brief Copies a directory and it's contents from src to dest
+ */
+class create_link : public QObject {
+ Q_OBJECT
+ public:
+ create_link(const QString& src, const QString& dst, QObject* parent = nullptr) : QObject(parent)
+ {
+ m_src.setPath(src);
+ m_dst.setPath(dst);
+ }
+ create_link& useHardLinks(const bool useHard)
+ {
+ m_useHardLinks = useHard;
+ return *this;
+ }
+ create_link& matcher(const IPathMatcher* filter)
+ {
+ m_matcher = filter;
+ return *this;
+ }
+ create_link& whitelist(bool whitelist)
+ {
+ m_whitelist = whitelist;
+ return *this;
+ }
+ create_link& linkRecursively(bool recursive)
+ {
+ m_recursive = recursive;
+ return *this;
+ }
+ create_link& debug(bool d)
+ {
+ m_debug = d;
+ return *this;
+ }
+
+ int getLastOSError() {
+ return m_last_os_err;
+ }
+
+ bool operator()(bool dryRun = false) { return operator()(QString(), dryRun); }
+
+ int totalLinked() { return m_linked; }
+
+ signals:
+ void fileLinked(const QString& relativeName);
+ void linkFailed(const QString& srcName, const QString& dstName, std::error_code err);
+
+ private:
+ bool operator()(const QString& offset, bool dryRun = false);
+
+ private:
+ bool m_useHardLinks = false;
+ const IPathMatcher* m_matcher = nullptr;
+ bool m_whitelist = false;
+ bool m_recursive = true;
+ QDir m_src;
+ QDir m_dst;
+ int m_linked;
+ bool m_debug = false;
+ int m_last_os_err = 0;
+};
+
/**
* @brief moves a file by renaming it
* @param source source file path
diff --git a/launcher/InstanceCopyPrefs.cpp b/launcher/InstanceCopyPrefs.cpp
index 7b93a516..18a6d704 100644
--- a/launcher/InstanceCopyPrefs.cpp
+++ b/launcher/InstanceCopyPrefs.cpp
@@ -93,6 +93,21 @@ bool InstanceCopyPrefs::isCopyScreenshotsEnabled() const
return copyScreenshots;
}
+bool InstanceCopyPrefs::isLinkFilesEnabled() const
+{
+ return linkFiles;
+}
+
+bool InstanceCopyPrefs::isUseHardLinksEnabled() const
+{
+ return useHardLinks;
+}
+
+bool InstanceCopyPrefs::isLinkWorldsEnabled() const
+{
+ return linkWorlds;
+}
+
// ======= Setters =======
void InstanceCopyPrefs::enableCopySaves(bool b)
{
@@ -133,3 +148,18 @@ void InstanceCopyPrefs::enableCopyScreenshots(bool b)
{
copyScreenshots = b;
}
+
+void InstanceCopyPrefs::enableLinkFiles(bool b)
+{
+ linkFiles = b;
+}
+
+void InstanceCopyPrefs::enableUseHardLinks(bool b)
+{
+ useHardLinks = b;
+}
+
+void InstanceCopyPrefs::enableLinkWorlds(bool b)
+{
+ linkWorlds = b;
+}
diff --git a/launcher/InstanceCopyPrefs.h b/launcher/InstanceCopyPrefs.h
index 6988b2df..25c0f3fc 100644
--- a/launcher/InstanceCopyPrefs.h
+++ b/launcher/InstanceCopyPrefs.h
@@ -19,6 +19,9 @@ struct InstanceCopyPrefs {
[[nodiscard]] bool isCopyServersEnabled() const;
[[nodiscard]] bool isCopyModsEnabled() const;
[[nodiscard]] bool isCopyScreenshotsEnabled() const;
+ [[nodiscard]] bool isLinkFilesEnabled() const;
+ [[nodiscard]] bool isUseHardLinksEnabled() const;
+ [[nodiscard]] bool isLinkWorldsEnabled() const;
// Setters
void enableCopySaves(bool b);
void enableKeepPlaytime(bool b);
@@ -28,6 +31,9 @@ struct InstanceCopyPrefs {
void enableCopyServers(bool b);
void enableCopyMods(bool b);
void enableCopyScreenshots(bool b);
+ void enableLinkFiles(bool b);
+ void enableUseHardLinks(bool b);
+ void enableLinkWorlds(bool b);
protected: // data
bool copySaves = true;
@@ -38,4 +44,7 @@ struct InstanceCopyPrefs {
bool copyServers = true;
bool copyMods = true;
bool copyScreenshots = true;
+ bool linkFiles = false;
+ bool useHardLinks = false;
+ bool linkWorlds = true;
};
diff --git a/launcher/filelink/FileLink.cpp b/launcher/filelink/FileLink.cpp
new file mode 100644
index 00000000..9b5589ab
--- /dev/null
+++ b/launcher/filelink/FileLink.cpp
@@ -0,0 +1,119 @@
+// SPDX-FileCopyrightText: 2022 Rachel Powers <508861+Ryex@users.noreply.github.com>
+//
+// SPDX-License-Identifier: GPL-3.0-only
+
+/*
+ * Prism Launcher - Minecraft Launcher
+ * Copyright (C) 2022 Rachel Powers <508861+Ryex@users.noreply.github.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+#include "FileLink.h"
+#include "BuildConfig.h"
+
+
+#include
+
+#include
+#include
+
+#include
+
+
+#include
+#include
+
+#include
+
+#if defined Q_OS_WIN32
+#ifndef WIN32_LEAN_AND_MEAN
+#define WIN32_LEAN_AND_MEAN
+#endif
+#include
+#include
+#endif
+
+
+
+
+FileLinkApp::FileLinkApp(int &argc, char **argv) : QCoreApplication(argc, argv)
+{
+#if defined Q_OS_WIN32
+ // attach the parent console
+ if(AttachConsole(ATTACH_PARENT_PROCESS))
+ {
+ // if attach succeeds, reopen and sync all the i/o
+ if(freopen("CON", "w", stdout))
+ {
+ std::cout.sync_with_stdio();
+ }
+ if(freopen("CON", "w", stderr))
+ {
+ std::cerr.sync_with_stdio();
+ }
+ if(freopen("CON", "r", stdin))
+ {
+ std::cin.sync_with_stdio();
+ }
+ auto out = GetStdHandle (STD_OUTPUT_HANDLE);
+ DWORD written;
+ const char * endline = "\n";
+ WriteConsole(out, endline, strlen(endline), &written, NULL);
+ consoleAttached = true;
+ }
+#endif
+ setOrganizationName(BuildConfig.LAUNCHER_NAME);
+ setOrganizationDomain(BuildConfig.LAUNCHER_DOMAIN);
+ setApplicationName(BuildConfig.LAUNCHER_NAME + "FileLink");
+ setApplicationVersion(BuildConfig.printableVersionString() + "\n" + BuildConfig.GIT_COMMIT);
+
+ // Commandline parsing
+ QCommandLineParser parser;
+ parser.setApplicationDescription(QObject::tr("a batch MKLINK program for windows to be useed with prismlauncher"));
+
+ parser.addOptions({
+
+ });
+ parser.addHelpOption();
+ parser.addVersionOption();
+
+ parser.process(arguments());
+
+ qDebug() << "link program launched";
+
+}
+
+
+FileLinkApp::~FileLinkApp()
+{
+ qDebug() << "link program shutting down";
+ // Shut down logger by setting the logger function to nothing
+ qInstallMessageHandler(nullptr);
+
+#if defined Q_OS_WIN32
+ // Detach from Windows console
+ if(consoleAttached)
+ {
+ fclose(stdout);
+ fclose(stdin);
+ fclose(stderr);
+ FreeConsole();
+ }
+#endif
+}
+
+
+
+
diff --git a/launcher/filelink/FileLink.h b/launcher/filelink/FileLink.h
new file mode 100644
index 00000000..253d1394
--- /dev/null
+++ b/launcher/filelink/FileLink.h
@@ -0,0 +1,52 @@
+// SPDX-FileCopyrightText: 2022 Rachel Powers <508861+Ryex@users.noreply.github.com>
+//
+// SPDX-License-Identifier: GPL-3.0-only
+
+/*
+ * Prism Launcher - Minecraft Launcher
+ * Copyright (C) 2022 Rachel Powers <508861+Ryex@users.noreply.github.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+#pragma once
+
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+class FileLinkApp : public QCoreApplication
+{
+ // friends for the purpose of limiting access to deprecated stuff
+ Q_OBJECT
+public:
+
+ FileLinkApp(int &argc, char **argv);
+ virtual ~FileLinkApp();
+
+private:
+ QDateTime m_startTime;
+
+#if defined Q_OS_WIN32
+ // used on Windows to attach the standard IO streams
+ bool consoleAttached = false;
+#endif
+};
diff --git a/launcher/filelink/filelink.exe.manifest b/launcher/filelink/filelink.exe.manifest
new file mode 100644
index 00000000..a4e16264
--- /dev/null
+++ b/launcher/filelink/filelink.exe.manifest
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/launcher/filelink/main.cpp b/launcher/filelink/main.cpp
new file mode 100644
index 00000000..7f06795e
--- /dev/null
+++ b/launcher/filelink/main.cpp
@@ -0,0 +1,31 @@
+// SPDX-FileCopyrightText: 2022 Rachel Powers <508861+Ryex@users.noreply.github.com>
+//
+// SPDX-License-Identifier: GPL-3.0-only
+
+/*
+ * Prism Launcher - Minecraft Launcher
+ * Copyright (C) 2022 Rachel Powers <508861+Ryex@users.noreply.github.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+#include "FileLink.h"
+
+int main(int argc, char *argv[])
+{
+
+ FileLinkApp ldh(argc, argv);
+
+ return ldh.exec();
+}
\ No newline at end of file
diff --git a/launcher/ui/dialogs/CopyInstanceDialog.cpp b/launcher/ui/dialogs/CopyInstanceDialog.cpp
index 3f5122f6..981352ae 100644
--- a/launcher/ui/dialogs/CopyInstanceDialog.cpp
+++ b/launcher/ui/dialogs/CopyInstanceDialog.cpp
@@ -85,6 +85,10 @@ CopyInstanceDialog::CopyInstanceDialog(InstancePtr original, QWidget *parent)
ui->copyServersCheckbox->setChecked(m_selectedOptions.isCopyServersEnabled());
ui->copyModsCheckbox->setChecked(m_selectedOptions.isCopyModsEnabled());
ui->copyScreenshotsCheckbox->setChecked(m_selectedOptions.isCopyScreenshotsEnabled());
+
+ ui->linkFilesGroup->setChecked(m_selectedOptions.isLinkFilesEnabled());
+ ui->hardLinksCheckbox->setChecked(m_selectedOptions.isUseHardLinksEnabled());
+ ui->linkWorldsCheckbox->setChecked(m_selectedOptions.isLinkWorldsEnabled());
}
CopyInstanceDialog::~CopyInstanceDialog()
@@ -220,3 +224,18 @@ void CopyInstanceDialog::on_copyScreenshotsCheckbox_stateChanged(int state)
m_selectedOptions.enableCopyScreenshots(state == Qt::Checked);
updateSelectAllCheckbox();
}
+
+void CopyInstanceDialog::on_linkFilesGroup_toggled(bool checked)
+{
+ m_selectedOptions.enableLinkFiles(checked);
+}
+
+void CopyInstanceDialog::on_hardLinksCheckbox_stateChanged(int state)
+{
+ m_selectedOptions.enableUseHardLinks(state == Qt::Checked);
+}
+
+void CopyInstanceDialog::on_linkWorldsCheckbox_stateChanged(int state)
+{
+ m_selectedOptions.enableLinkWorlds(state == Qt::Checked);
+}
diff --git a/launcher/ui/dialogs/CopyInstanceDialog.h b/launcher/ui/dialogs/CopyInstanceDialog.h
index 884501d1..a80faab9 100644
--- a/launcher/ui/dialogs/CopyInstanceDialog.h
+++ b/launcher/ui/dialogs/CopyInstanceDialog.h
@@ -55,6 +55,9 @@ slots:
void on_copyServersCheckbox_stateChanged(int state);
void on_copyModsCheckbox_stateChanged(int state);
void on_copyScreenshotsCheckbox_stateChanged(int state);
+ void on_linkFilesGroup_toggled(bool checked);
+ void on_hardLinksCheckbox_stateChanged(int state);
+ void on_linkWorldsCheckbox_stateChanged(int state);
private:
void checkAllCheckboxes(const bool& b);
diff --git a/launcher/ui/dialogs/CopyInstanceDialog.ui b/launcher/ui/dialogs/CopyInstanceDialog.ui
index b7828fe3..e41ad526 100644
--- a/launcher/ui/dialogs/CopyInstanceDialog.ui
+++ b/launcher/ui/dialogs/CopyInstanceDialog.ui
@@ -9,8 +9,8 @@
0
0
- 341
- 399
+ 525
+ 581
@@ -136,70 +136,126 @@
-
-
-
-
-
+
+
+ Instance copy options
+
+
+
-
+
+
+ Disabling this will still keep the mod loader (ex: Fabric, Quilt, etc.) but erase the mods folder and their configs.
+
+
+ Copy mods
+
+
+
+ -
+
+
+ Copy the in-game options like FOV, max framerate, etc.
+
+
+ Copy game options
+
+
+
+ -
+
+
+ Copy saves
+
+
+
+ -
+
+
+ Copy shader packs
+
+
+
+ -
+
+
+ Copy servers
+
+
+
+ -
+
+
+ true
+
+
+ Copy resource packs
+
+
+
+ -
+
+
+ Keep play time
+
+
+
+ -
+
+
+ Copy screenshots
+
+
+
+
+
+
+ -
+
+
-
+
- Disabling this will still keep the mod loader (ex: Fabric, Quilt, etc.) but erase the mods folder and their configs.
+ Use symbolic links instead of copying files.
-
- Copy mods
+
+ Link files instead of copying them
-
-
- -
-
-
- Copy the in-game options like FOV, max framerate, etc.
+
+ false
-
- Copy game options
-
-
-
- -
-
-
- Copy saves
-
-
-
- -
-
-
- Copy shader packs
-
-
-
- -
-
-
- Copy servers
-
-
-
- -
-
-
+
true
-
- Copy resource packs
-
-
-
- -
-
-
- Keep play time
-
-
-
- -
-
-
- Copy screenshots
+
+ false
+
+
-
+
+
+ Use hard links instead of symbolic links
+
+
+ Use hard links
+
+
+
+ -
+
+
+ World save data will be linked and thus shared between instances.
+
+
+ Link worlds
+
+
+ true
+
+
+ false
+
+
+
+
@@ -210,7 +266,7 @@
Qt::Horizontal
- QDialogButtonBox::Cancel|QDialogButtonBox::Ok
+ QDialogButtonBox::Cancel|QDialogButtonBox::Help|QDialogButtonBox::Ok
@@ -220,10 +276,20 @@
iconButton
instNameTextBox
groupBox
+ selectAllCheckbox
+ keepPlaytimeCheckbox
+ copyScreenshotsCheckbox
+ copySavesCheckbox
+ copyShaderPacksCheckbox
+ copyGameOptionsCheckbox
+ copyServersCheckbox
+ copyResPacksCheckbox
+ copyModsCheckbox
+ linkFilesGroup
+ hardLinksCheckbox
+ linkWorldsCheckbox
-
-
-
+
buttonBox
@@ -232,8 +298,8 @@
accept()
- 254
- 316
+ 263
+ 571
157
@@ -248,8 +314,8 @@
reject()
- 322
- 316
+ 331
+ 571
286
diff --git a/tests/FileSystem_test.cpp b/tests/FileSystem_test.cpp
index 3a5c38d0..ce83aa49 100644
--- a/tests/FileSystem_test.cpp
+++ b/tests/FileSystem_test.cpp
@@ -248,6 +248,295 @@ slots:
{
QCOMPARE(FS::getDesktopDir(), QStandardPaths::writableLocation(QStandardPaths::DesktopLocation));
}
+
+
+ void test_link()
+ {
+ QString folder = QFINDTESTDATA("testdata/FileSystem/test_folder");
+ auto f = [&folder]()
+ {
+ QTemporaryDir tempDir;
+ tempDir.setAutoRemove(true);
+ qDebug() << "From:" << folder << "To:" << tempDir.path();
+
+ QDir target_dir(FS::PathCombine(tempDir.path(), "test_folder"));
+ qDebug() << tempDir.path();
+ qDebug() << target_dir.path();
+ FS::create_link lnk(folder, target_dir.path());
+ lnk.linkRecursively(false);
+ lnk.debug(true);
+ if(!lnk()){
+#if defined Q_OS_WIN32
+ qDebug() << "EXPECTED: Link failure, Windows requires permissions for symlinks";
+ QVERIFY(lnk.getLastOSError() == 1314);
+ return;
+#endif
+ qDebug() << "Link Failed!" << lnk.getLastOSError();
+ }
+
+ for(auto entry: target_dir.entryList())
+ {
+ qDebug() << entry;
+ QFileInfo entry_lnk_info(target_dir.filePath(entry));
+ QVERIFY(!entry_lnk_info.isSymbolicLink());
+ }
+
+ QFileInfo lnk_info(target_dir.path());
+ QVERIFY(lnk_info.exists());
+ QVERIFY(lnk_info.isSymbolicLink());
+
+ QVERIFY(target_dir.entryList().contains("pack.mcmeta"));
+ QVERIFY(target_dir.entryList().contains("assets"));
+ };
+
+ // first try variant without trailing /
+ QVERIFY(!folder.endsWith('/'));
+ f();
+
+ // then variant with trailing /
+ folder.append('/');
+ QVERIFY(folder.endsWith('/'));
+ f();
+ }
+
+ void test_hard_link()
+ {
+ QString folder = QFINDTESTDATA("testdata/FileSystem/test_folder");
+ auto f = [&folder]()
+ {
+ QTemporaryDir tempDir;
+ tempDir.setAutoRemove(true);
+ qDebug() << "From:" << folder << "To:" << tempDir.path();
+
+ QDir target_dir(FS::PathCombine(tempDir.path(), "test_folder"));
+ qDebug() << tempDir.path();
+ qDebug() << target_dir.path();
+ FS::create_link lnk(folder, target_dir.path());
+ lnk.useHardLinks(true);
+ lnk.debug(true);
+ if(!lnk()){
+ qDebug() << "Link Failed!" << lnk.getLastOSError();
+ }
+
+ for(auto entry: target_dir.entryList())
+ {
+ qDebug() << entry;
+ QFileInfo entry_lnk_info(target_dir.filePath(entry));
+ QVERIFY(!entry_lnk_info.isSymbolicLink());
+ QFileInfo entry_orig_info(QDir(folder).filePath(entry));
+ if (!entry_lnk_info.isDir()) {
+ qDebug() << "hard link equivalency?" << entry_lnk_info.absoluteFilePath() << "vs" << entry_orig_info.absoluteFilePath();
+ QVERIFY(std::filesystem::equivalent(entry_lnk_info.filesystemAbsoluteFilePath(), entry_orig_info.filesystemAbsoluteFilePath()));
+ }
+ }
+
+ QFileInfo lnk_info(target_dir.path());
+ QVERIFY(lnk_info.exists());
+ QVERIFY(!lnk_info.isSymbolicLink());
+
+ QVERIFY(target_dir.entryList().contains("pack.mcmeta"));
+ QVERIFY(target_dir.entryList().contains("assets"));
+ };
+
+ // first try variant without trailing /
+ QVERIFY(!folder.endsWith('/'));
+ f();
+
+ // then variant with trailing /
+ folder.append('/');
+ QVERIFY(folder.endsWith('/'));
+ f();
+ }
+
+ void test_link_with_blacklist()
+ {
+ QString folder = QFINDTESTDATA("testdata/FileSystem/test_folder");
+ auto f = [&folder]()
+ {
+ QTemporaryDir tempDir;
+ tempDir.setAutoRemove(true);
+ qDebug() << "From:" << folder << "To:" << tempDir.path();
+
+ QDir target_dir(FS::PathCombine(tempDir.path(), "test_folder"));
+ qDebug() << tempDir.path();
+ qDebug() << target_dir.path();
+ FS::create_link lnk(folder, target_dir.path());
+ lnk.matcher(new RegexpMatcher("[.]?mcmeta"));
+ lnk.linkRecursively(true);
+ lnk.debug(true);
+ if(!lnk()){
+#if defined Q_OS_WIN32
+ qDebug() << "EXPECTED: Link failure, Windows requires permissions for symlinks";
+ QVERIFY(lnk.getLastOSError() == 1314);
+ return;
+#endif
+ qDebug() << "Link Failed!" << lnk.getLastOSError();
+ }
+
+ for(auto entry: target_dir.entryList())
+ {
+ qDebug() << entry;
+ QFileInfo entry_lnk_info(target_dir.filePath(entry));
+ QVERIFY(entry_lnk_info.isSymbolicLink());
+ }
+
+ QFileInfo lnk_info(target_dir.path());
+ QVERIFY(lnk_info.exists());
+ QVERIFY(lnk_info.isSymbolicLink());
+
+ QVERIFY(!target_dir.entryList().contains("pack.mcmeta"));
+ QVERIFY(target_dir.entryList().contains("assets"));
+ };
+
+ // first try variant without trailing /
+ QVERIFY(!folder.endsWith('/'));
+ f();
+
+ // then variant with trailing /
+ folder.append('/');
+ QVERIFY(folder.endsWith('/'));
+ f();
+ }
+
+ void test_link_with_whitelist()
+ {
+ QString folder = QFINDTESTDATA("testdata/FileSystem/test_folder");
+ auto f = [&folder]()
+ {
+ QTemporaryDir tempDir;
+ tempDir.setAutoRemove(true);
+ qDebug() << "From:" << folder << "To:" << tempDir.path();
+
+ QDir target_dir(FS::PathCombine(tempDir.path(), "test_folder"));
+ qDebug() << tempDir.path();
+ qDebug() << target_dir.path();
+ FS::create_link lnk(folder, target_dir.path());
+ lnk.matcher(new RegexpMatcher("[.]?mcmeta"));
+ lnk.whitelist(true);
+ lnk.linkRecursively(true);
+ lnk.debug(true);
+ if(!lnk()){
+#if defined Q_OS_WIN32
+ qDebug() << "EXPECTED: Link failure, Windows requires permissions for symlinks";
+ QVERIFY(lnk.getLastOSError() == 1314);
+ return;
+#endif
+ qDebug() << "Link Failed!" << lnk.getLastOSError();
+ }
+
+ for(auto entry: target_dir.entryList())
+ {
+ qDebug() << entry;
+ QFileInfo entry_lnk_info(target_dir.filePath(entry));
+ QVERIFY(entry_lnk_info.isSymbolicLink());
+ }
+
+ QFileInfo lnk_info(target_dir.path());
+ QVERIFY(lnk_info.exists());
+ QVERIFY(lnk_info.isSymbolicLink());
+
+ QVERIFY(target_dir.entryList().contains("pack.mcmeta"));
+ QVERIFY(!target_dir.entryList().contains("assets"));
+ };
+
+ // first try variant without trailing /
+ QVERIFY(!folder.endsWith('/'));
+ f();
+
+ // then variant with trailing /
+ folder.append('/');
+ QVERIFY(folder.endsWith('/'));
+ f();
+ }
+
+ void test_link_with_dot_hidden()
+ {
+ QString folder = QFINDTESTDATA("testdata/FileSystem/test_folder");
+ auto f = [&folder]()
+ {
+ QTemporaryDir tempDir;
+ tempDir.setAutoRemove(true);
+ qDebug() << "From:" << folder << "To:" << tempDir.path();
+
+ QDir target_dir(FS::PathCombine(tempDir.path(), "test_folder"));
+ qDebug() << tempDir.path();
+ qDebug() << target_dir.path();
+ FS::create_link lnk(folder, target_dir.path());
+ lnk.linkRecursively(true);
+ lnk.debug(true);
+ if(!lnk()){
+#if defined Q_OS_WIN32
+ qDebug() << "EXPECTED: Link failure, Windows requires permissions for symlinks";
+ QVERIFY(lnk.getLastOSError() == 1314);
+ return;
+#endif
+ qDebug() << "Link Failed!" << lnk.getLastOSError();
+ }
+
+ auto filter = QDir::Filter::Files | QDir::Filter::Dirs | QDir::Filter::Hidden;
+
+ for (auto entry: target_dir.entryList(filter)) {
+ qDebug() << entry;
+ QFileInfo entry_lnk_info(target_dir.filePath(entry));
+ QVERIFY(entry_lnk_info.isSymbolicLink());
+ }
+
+ QFileInfo lnk_info(target_dir.path());
+ QVERIFY(lnk_info.exists());
+ QVERIFY(lnk_info.isSymbolicLink());
+
+ QVERIFY(target_dir.entryList(filter).contains(".secret_folder"));
+ target_dir.cd(".secret_folder");
+ QVERIFY(target_dir.entryList(filter).contains(".secret_file.txt"));
+ };
+
+ // first try variant without trailing /
+ QVERIFY(!folder.endsWith('/'));
+ f();
+
+ // then variant with trailing /
+ folder.append('/');
+ QVERIFY(folder.endsWith('/'));
+ f();
+ }
+
+ void test_link_single_file()
+ {
+ QTemporaryDir tempDir;
+ tempDir.setAutoRemove(true);
+
+ {
+ QString file = QFINDTESTDATA("testdata/FileSystem/test_folder/pack.mcmeta");
+
+ qDebug() << "From:" << file << "To:" << tempDir.path();
+
+ QDir target_dir(FS::PathCombine(tempDir.path(), "pack.mcmeta"));
+ qDebug() << tempDir.path();
+ qDebug() << target_dir.path();
+ FS::create_link lnk(file, target_dir.filePath("pack.mcmeta"));
+ lnk.debug(true);
+ if(!lnk()){
+#if defined Q_OS_WIN32
+ qDebug() << "EXPECTED: Link failure, Windows requires permissions for symlinks";
+ QVERIFY(lnk.getLastOSError() == 1314);
+ return;
+#endif
+ qDebug() << "Link Failed!" << lnk.getLastOSError();
+ }
+
+ auto filter = QDir::Filter::Files;
+
+ for (auto entry: target_dir.entryList(filter)) {
+ qDebug() << entry;
+ }
+
+ QFileInfo lnk_info(target_dir.filePath("pack.mcmeta"));
+ QVERIFY(lnk_info.exists());
+ QVERIFY(lnk_info.isSymbolicLink());
+
+ QVERIFY(target_dir.entryList(filter).contains("pack.mcmeta"));
+ }
+ }
};
QTEST_GUILESS_MAIN(FileSystemTest)
From 485f156e57b0fb30e51d1014de745bc6f90b7e3e Mon Sep 17 00:00:00 2001
From: Rachel Powers <508861+Ryex@users.noreply.github.com>
Date: Tue, 7 Feb 2023 02:56:16 -0700
Subject: [PATCH 003/104] working outside windows
Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com>
---
tests/FileSystem_test.cpp | 28 +++++++++++++++-------------
1 file changed, 15 insertions(+), 13 deletions(-)
diff --git a/tests/FileSystem_test.cpp b/tests/FileSystem_test.cpp
index ce83aa49..84671889 100644
--- a/tests/FileSystem_test.cpp
+++ b/tests/FileSystem_test.cpp
@@ -278,12 +278,13 @@ slots:
{
qDebug() << entry;
QFileInfo entry_lnk_info(target_dir.filePath(entry));
- QVERIFY(!entry_lnk_info.isSymbolicLink());
+ if (!entry_lnk_info.isDir())
+ QVERIFY(!entry_lnk_info.isSymLink());
}
QFileInfo lnk_info(target_dir.path());
QVERIFY(lnk_info.exists());
- QVERIFY(lnk_info.isSymbolicLink());
+ QVERIFY(lnk_info.isSymLink());
QVERIFY(target_dir.entryList().contains("pack.mcmeta"));
QVERIFY(target_dir.entryList().contains("assets"));
@@ -303,8 +304,9 @@ slots:
{
QString folder = QFINDTESTDATA("testdata/FileSystem/test_folder");
auto f = [&folder]()
- {
- QTemporaryDir tempDir;
+ {
+ // use working dir to prevent makeing a hard link to a tmpfs or across devices
+ QTemporaryDir tempDir("./tmp");
tempDir.setAutoRemove(true);
qDebug() << "From:" << folder << "To:" << tempDir.path();
@@ -322,7 +324,7 @@ slots:
{
qDebug() << entry;
QFileInfo entry_lnk_info(target_dir.filePath(entry));
- QVERIFY(!entry_lnk_info.isSymbolicLink());
+ QVERIFY(!entry_lnk_info.isSymLink());
QFileInfo entry_orig_info(QDir(folder).filePath(entry));
if (!entry_lnk_info.isDir()) {
qDebug() << "hard link equivalency?" << entry_lnk_info.absoluteFilePath() << "vs" << entry_orig_info.absoluteFilePath();
@@ -332,7 +334,7 @@ slots:
QFileInfo lnk_info(target_dir.path());
QVERIFY(lnk_info.exists());
- QVERIFY(!lnk_info.isSymbolicLink());
+ QVERIFY(!lnk_info.isSymLink());
QVERIFY(target_dir.entryList().contains("pack.mcmeta"));
QVERIFY(target_dir.entryList().contains("assets"));
@@ -377,12 +379,12 @@ slots:
{
qDebug() << entry;
QFileInfo entry_lnk_info(target_dir.filePath(entry));
- QVERIFY(entry_lnk_info.isSymbolicLink());
+ if (!entry_lnk_info.isDir())
+ QVERIFY(entry_lnk_info.isSymLink());
}
QFileInfo lnk_info(target_dir.path());
QVERIFY(lnk_info.exists());
- QVERIFY(lnk_info.isSymbolicLink());
QVERIFY(!target_dir.entryList().contains("pack.mcmeta"));
QVERIFY(target_dir.entryList().contains("assets"));
@@ -428,12 +430,12 @@ slots:
{
qDebug() << entry;
QFileInfo entry_lnk_info(target_dir.filePath(entry));
- QVERIFY(entry_lnk_info.isSymbolicLink());
+ if (!entry_lnk_info.isDir())
+ QVERIFY(entry_lnk_info.isSymLink());
}
QFileInfo lnk_info(target_dir.path());
QVERIFY(lnk_info.exists());
- QVERIFY(lnk_info.isSymbolicLink());
QVERIFY(target_dir.entryList().contains("pack.mcmeta"));
QVERIFY(!target_dir.entryList().contains("assets"));
@@ -478,12 +480,12 @@ slots:
for (auto entry: target_dir.entryList(filter)) {
qDebug() << entry;
QFileInfo entry_lnk_info(target_dir.filePath(entry));
- QVERIFY(entry_lnk_info.isSymbolicLink());
+ if (!entry_lnk_info.isDir())
+ QVERIFY(entry_lnk_info.isSymLink());
}
QFileInfo lnk_info(target_dir.path());
QVERIFY(lnk_info.exists());
- QVERIFY(lnk_info.isSymbolicLink());
QVERIFY(target_dir.entryList(filter).contains(".secret_folder"));
target_dir.cd(".secret_folder");
@@ -532,7 +534,7 @@ slots:
QFileInfo lnk_info(target_dir.filePath("pack.mcmeta"));
QVERIFY(lnk_info.exists());
- QVERIFY(lnk_info.isSymbolicLink());
+ QVERIFY(lnk_info.isSymLink());
QVERIFY(target_dir.entryList(filter).contains("pack.mcmeta"));
}
From 2ceefea5f346985bcc3a61c1562e0d836f1a0a83 Mon Sep 17 00:00:00 2001
From: Rachel Powers <508861+Ryex@users.noreply.github.com>
Date: Tue, 7 Feb 2023 03:27:49 -0700
Subject: [PATCH 004/104] qt5 compatability
Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com>
---
tests/FileSystem_test.cpp | 25 ++++++++++++++++++++++++-
1 file changed, 24 insertions(+), 1 deletion(-)
diff --git a/tests/FileSystem_test.cpp b/tests/FileSystem_test.cpp
index 84671889..395ca5c0 100644
--- a/tests/FileSystem_test.cpp
+++ b/tests/FileSystem_test.cpp
@@ -3,6 +3,26 @@
#include
#include
+#include
+
+// Snippet from https://github.com/gulrak/filesystem#using-it-as-single-file-header
+
+#ifdef __APPLE__
+#include // for deployment target to support pre-catalina targets without std::fs
+#endif // __APPLE__
+
+#if ((defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) || (defined(__cplusplus) && __cplusplus >= 201703L)) && defined(__has_include)
+#if __has_include() && (!defined(__MAC_OS_X_VERSION_MIN_REQUIRED) || __MAC_OS_X_VERSION_MIN_REQUIRED >= 101500)
+#define GHC_USE_STD_FS
+#include
+namespace fs = std::filesystem;
+#endif // MacOS min version check
+#endif // Other OSes version check
+
+#ifndef GHC_USE_STD_FS
+#include
+namespace fs = ghc::filesystem;
+#endif
#include
@@ -328,7 +348,10 @@ slots:
QFileInfo entry_orig_info(QDir(folder).filePath(entry));
if (!entry_lnk_info.isDir()) {
qDebug() << "hard link equivalency?" << entry_lnk_info.absoluteFilePath() << "vs" << entry_orig_info.absoluteFilePath();
- QVERIFY(std::filesystem::equivalent(entry_lnk_info.filesystemAbsoluteFilePath(), entry_orig_info.filesystemAbsoluteFilePath()));
+ QVERIFY(fs::equivalent(
+ fs::path(StringUtils::toStdString(entry_lnk_info.absoluteFilePath())),
+ fs::path(StringUtils::toStdString(entry_orig_info.absoluteFilePath()))
+ ));
}
}
From 32409a361b797342d625bfc6d0726cc330ced760 Mon Sep 17 00:00:00 2001
From: Rachel Powers <508861+Ryex@users.noreply.github.com>
Date: Tue, 7 Feb 2023 03:31:46 -0700
Subject: [PATCH 005/104] fix CMakeLits.txt for non MSVC windows builds
Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com>
---
launcher/CMakeLists.txt | 9 +++++----
1 file changed, 5 insertions(+), 4 deletions(-)
diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt
index 2216aa1b..18d4ce0b 100644
--- a/launcher/CMakeLists.txt
+++ b/launcher/CMakeLists.txt
@@ -559,7 +559,7 @@ set(ATLAUNCHER_SOURCES
modplatform/atlauncher/ATLShareCode.h
)
-set(LINKDAEMON_SOURCES
+set(LINKEXE_SOURCES
filelink/FileLink.h
filelink/FileLink.cpp
)
@@ -1113,7 +1113,7 @@ install(TARGETS ${Launcher_Name}
)
if(WIN32)
- add_library(filelink_logic STATIC ${LINKDAEMON_SOURCES})
+ add_library(filelink_logic STATIC ${LINKEXE_SOURCES})
target_include_directories(filelink_logic PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(filelink_logic
systeminfo
@@ -1144,10 +1144,11 @@ if(WIN32)
SET_TARGET_PROPERTIES("${Launcher_Name}_filelink" PROPERTIES INSTALL_RPATH "${Launcher_BINARY_RPATH}")
endif()
+ # may be unnessacery with manifest
if(CMAKE_GENERATOR MATCHES "Visual Studio")
SET_TARGET_PROPERTIES("${Launcher_Name}_filelink" PROPERTIES LINK_FLAGS "/level='requireAdministrator' /uiAccess='false' /SUBSYSTEM:CONSOLE")
- else()
- SET_TARGET_PROPERTIES("${Launcher_Name}_filelink" PROPERTIES LINK_FLAGS "/MANIFESTUAC:\"level='requireAdministrator' uiAccess='false'\" /SUBSYSTEM:CONSOLE")
+ # else() # link arg /MANIFESTUAC only works with MSVC
+ # SET_TARGET_PROPERTIES("${Launcher_Name}_filelink" PROPERTIES LINK_FLAGS "/MANIFESTUAC:\"level='requireAdministrator' uiAccess='false'\" /SUBSYSTEM:CONSOLE")
endif()
From 6d160a7b7e31034c7a657f30003562c20f9b9c21 Mon Sep 17 00:00:00 2001
From: Rachel Powers <508861+Ryex@users.noreply.github.com>
Date: Wed, 8 Feb 2023 00:35:03 -0800
Subject: [PATCH 006/104] feat: successful process elevation and comunication!
Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com>
---
launcher/CMakeLists.txt | 9 +-
launcher/DesktopServices.cpp | 2 +-
launcher/FileSystem.cpp | 140 ++++++++++++++++++++++---
launcher/FileSystem.h | 57 +++++++++--
launcher/StringUtils.cpp | 15 +++
launcher/StringUtils.h | 2 +
launcher/filelink/FileLink.cpp | 106 ++++++++++++++++++-
launcher/filelink/FileLink.h | 15 +++
tests/FileSystem_test.cpp | 181 ++++++++++++++++++++++-----------
9 files changed, 437 insertions(+), 90 deletions(-)
diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt
index 18d4ce0b..dd62893c 100644
--- a/launcher/CMakeLists.txt
+++ b/launcher/CMakeLists.txt
@@ -562,6 +562,13 @@ set(ATLAUNCHER_SOURCES
set(LINKEXE_SOURCES
filelink/FileLink.h
filelink/FileLink.cpp
+ FileSystem.h
+ FileSystem.cpp
+ Exception.h
+ StringUtils.h
+ StringUtils.cpp
+ DesktopServices.h
+ DesktopServices.cpp
)
######## Logging categories ########
@@ -1126,8 +1133,6 @@ if(WIN32)
Qt${QT_VERSION_MAJOR}::Xml
Qt${QT_VERSION_MAJOR}::Network
Qt${QT_VERSION_MAJOR}::Concurrent
- Qt${QT_VERSION_MAJOR}::Gui
- Qt${QT_VERSION_MAJOR}::Widgets
${Launcher_QT_LIBS}
)
diff --git a/launcher/DesktopServices.cpp b/launcher/DesktopServices.cpp
index 302eaf96..69770e99 100644
--- a/launcher/DesktopServices.cpp
+++ b/launcher/DesktopServices.cpp
@@ -37,7 +37,7 @@
#include
#include
#include
-#include "Application.h"
+//#include "Application.h"
/**
* This shouldn't exist, but until QTBUG-9328 and other unreported bugs are fixed, it needs to be a thing.
diff --git a/launcher/FileSystem.cpp b/launcher/FileSystem.cpp
index ec4af98c..9e51f932 100644
--- a/launcher/FileSystem.cpp
+++ b/launcher/FileSystem.cpp
@@ -36,6 +36,8 @@
#include "FileSystem.h"
+#include "BuildConfig.h"
+
#include
#include
#include
@@ -45,6 +47,7 @@
#include
#include
#include
+#include
#include "DesktopServices.h"
#include "StringUtils.h"
@@ -61,6 +64,11 @@
#include
#include
#include
+//for ShellExecute
+#include
+//#include
+#include
+#include
#else
#include
#endif
@@ -218,19 +226,29 @@ bool copy::operator()(const QString& offset, bool dryRun)
}
+bool create_link::operator()(const QString& offset, bool dryRun)
+{
+
+ for (auto pair : m_path_pairs) {
+ if (!make_link(pair.src, pair.dst, offset, dryRun)) {
+ return false;
+ }
+ }
+ return true;
+}
+
+
/**
* @brief links a directory and it's contents from src to dest
* @param offset subdirectory form src to link to dest
* @return if there was an error during the attempt to link
*/
-bool create_link::operator()(const QString& offset, bool dryRun)
+bool create_link::make_link(const QString& srcPath, const QString& dstPath, const QString& offset, bool dryRun)
{
m_linked = 0; // reset counter
- auto src = PathCombine(m_src.absolutePath(), offset);
- auto dst = PathCombine(m_dst.absolutePath(), offset);
-
- std::error_code err;
+ auto src = PathCombine(QDir(srcPath).absolutePath(), offset);
+ auto dst = PathCombine(QDir(dstPath).absolutePath(), offset);
// you can't hard link a directory so make sure if we deal with a directory we do so recursively
if (m_useHardLinks)
@@ -248,26 +266,25 @@ bool create_link::operator()(const QString& offset, bool dryRun)
if (m_useHardLinks) {
if (m_debug)
qDebug() << "making hard link:" << src_path << "to" << dst_path;
- fs::create_hard_link(StringUtils::toStdString(src_path), StringUtils::toStdString(dst_path), err);
+ fs::create_hard_link(StringUtils::toStdString(src_path), StringUtils::toStdString(dst_path), m_os_err);
} else if (fs::is_directory(StringUtils::toStdString(src_path))) {
if (m_debug)
qDebug() << "making directory_symlink:" << src_path << "to" << dst_path;
- fs::create_directory_symlink(StringUtils::toStdString(src_path), StringUtils::toStdString(dst_path), err);
+ fs::create_directory_symlink(StringUtils::toStdString(src_path), StringUtils::toStdString(dst_path), m_os_err);
} else {
if (m_debug)
qDebug() << "making symlink:" << src_path << "to" << dst_path;
- fs::create_symlink(StringUtils::toStdString(src_path), StringUtils::toStdString(dst_path), err);
+ fs::create_symlink(StringUtils::toStdString(src_path), StringUtils::toStdString(dst_path), m_os_err);
}
}
- if (err) {
- qWarning() << "Failed to link files:" << QString::fromStdString(err.message());
+ if (m_os_err) {
+ qWarning() << "Failed to link files:" << QString::fromStdString(m_os_err.message());
qDebug() << "Source file:" << src_path;
qDebug() << "Destination file:" << dst_path;
- qDebug() << "Error catagory:" << err.category().name();
- qDebug() << "Error code:" << err.value();
- m_last_os_err = err.value();
- emit linkFailed(src_path, dst_path, err);
+ qDebug() << "Error catagory:" << m_os_err.category().name();
+ qDebug() << "Error code:" << m_os_err.value();
+ emit linkFailed(src_path, dst_path, m_os_err);
} else {
m_linked++;
emit fileLinked(relative_dst_path);
@@ -290,10 +307,103 @@ bool create_link::operator()(const QString& offset, bool dryRun)
auto relative_path = src_dir.relativeFilePath(src_path);
link_file(src_path, relative_path);
+ if (m_os_err) return false;
}
}
- return err.value() == 0;
+ return m_os_err.value() == 0;
+}
+
+bool create_link::runPrivlaged(const QString& offset)
+{
+
+ QString serverName = BuildConfig.LAUNCHER_APP_BINARY_NAME + "_filelink_server" + StringUtils::getRandomAlphaNumeric(8);
+
+ connect(&m_linkServer, &QLocalServer::newConnection, this, [&](){
+
+ qDebug() << "Client connected, sending out pairs";
+ // construct block of data to send
+ QByteArray block;
+ QDataStream out(&block, QIODevice::WriteOnly);
+ out.setVersion(QDataStream::Qt_5_15); // choose correct version better?
+
+ qint32 blocksize = quint32(sizeof(quint32));
+ for (auto pair : m_path_pairs) {
+ blocksize += quint32(pair.src.size());
+ blocksize += quint32(pair.dst.size());
+ }
+ qDebug() << "About to write block of size:" << blocksize;
+ out << blocksize;
+
+ out << quint32(m_path_pairs.length());
+ for (auto pair : m_path_pairs) {
+ out << pair.src;
+ out << pair.dst;
+ }
+
+ QLocalSocket *clientConnection = m_linkServer.nextPendingConnection();
+ connect(clientConnection, &QLocalSocket::disconnected,
+ clientConnection, &QLocalSocket::deleteLater);
+
+ qint64 byteswritten = clientConnection->write(block);
+ bool bytesflushed = clientConnection->flush();
+ qDebug() << "block flushed" << byteswritten << bytesflushed;
+ //clientConnection->disconnectFromServer();
+ });
+
+ qDebug() << "Listening on pipe" << serverName;
+ if (!m_linkServer.listen(serverName)) {
+ qDebug() << "Unable to start local pipe server on" << serverName << ":" << m_linkServer.errorString();
+ return false;
+ }
+
+ ExternalLinkFileProcess *linkFileProcess = new ExternalLinkFileProcess(serverName, this);
+ connect(linkFileProcess, &ExternalLinkFileProcess::processExited, this, [&](){
+ emit finishedPrivlaged();
+ });
+ connect(linkFileProcess, &ExternalLinkFileProcess::finished, linkFileProcess, &QObject::deleteLater);
+
+ linkFileProcess->start();
+
+ // linkFileProcess->wait();
+
+ return true;
+}
+
+
+void ExternalLinkFileProcess::runLinkFile() {
+ QString fileLinkExe = PathCombine(QCoreApplication::instance()->applicationDirPath(), BuildConfig.LAUNCHER_APP_BINARY_NAME + "_filelink");
+ QString params = "-s " + m_server;
+
+#if defined Q_OS_WIN32
+ SHELLEXECUTEINFO ShExecInfo;
+ HRESULT hr;
+
+ fileLinkExe = fileLinkExe + ".exe";
+
+ qDebug() << "Running: runas" << fileLinkExe << params;
+
+ LPCWSTR programNameWin = (const wchar_t*) fileLinkExe.utf16();
+ LPCWSTR paramsWin = (const wchar_t*) params.utf16();
+
+ // https://learn.microsoft.com/en-us/windows/win32/api/shellapi/ns-shellapi-shellexecuteinfoa
+ ShExecInfo.cbSize = sizeof(SHELLEXECUTEINFO);
+ ShExecInfo.fMask = SEE_MASK_NOCLOSEPROCESS;
+ ShExecInfo.hwnd = NULL; // Optional. A handle to the owner window, used to display and position any UI that the system might produce while executing this function.
+ ShExecInfo.lpVerb = L"runas"; // elevate to admin, show UAC
+ ShExecInfo.lpFile = programNameWin;
+ ShExecInfo.lpParameters = paramsWin;
+ ShExecInfo.lpDirectory = NULL;
+ ShExecInfo.nShow = SW_NORMAL;
+ ShExecInfo.hInstApp = NULL;
+
+ ShellExecuteEx(&ShExecInfo);
+
+ WaitForSingleObject(ShExecInfo.hProcess, INFINITE);
+ CloseHandle(ShExecInfo.hProcess);
+#endif
+
+ qDebug() << "Process exited";
}
bool move(const QString& source, const QString& dest)
diff --git a/launcher/FileSystem.h b/launcher/FileSystem.h
index 98f55f96..b15d1685 100644
--- a/launcher/FileSystem.h
+++ b/launcher/FileSystem.h
@@ -39,9 +39,13 @@
#include "Exception.h"
#include "pathmatcher/IPathMatcher.h"
+#include
+
#include
#include
#include
+#include
+#include
namespace FS {
@@ -124,16 +128,45 @@ class copy : public QObject {
int m_copied;
};
+struct LinkPair {
+ QString src;
+ QString dst;
+};
+
+class ExternalLinkFileProcess : public QThread
+{
+ Q_OBJECT
+ public:
+ ExternalLinkFileProcess(QString server, QObject* parent = nullptr) : QThread(parent), m_server(server) {}
+
+ void run() override {
+ runLinkFile();
+ emit processExited();
+ }
+
+ signals:
+ void processExited();
+
+ private:
+ void runLinkFile();
+
+ QString m_server;
+};
+
/**
- * @brief Copies a directory and it's contents from src to dest
+ * @brief links (a file / a directory and it's contents) from src to dest
*/
class create_link : public QObject {
Q_OBJECT
public:
+ create_link(const QList path_pairs, QObject* parent = nullptr) : QObject(parent)
+ {
+ m_path_pairs.append(path_pairs);
+ }
create_link(const QString& src, const QString& dst, QObject* parent = nullptr) : QObject(parent)
{
- m_src.setPath(src);
- m_dst.setPath(dst);
+ LinkPair pair = {src, dst};
+ m_path_pairs.append(pair);
}
create_link& useHardLinks(const bool useHard)
{
@@ -161,31 +194,39 @@ class create_link : public QObject {
return *this;
}
- int getLastOSError() {
- return m_last_os_err;
+ std::error_code getOSError() {
+ return m_os_err;
}
bool operator()(bool dryRun = false) { return operator()(QString(), dryRun); }
+ bool runPrivlaged() { return runPrivlaged(QString()); }
+ bool runPrivlaged(const QString& offset);
+
int totalLinked() { return m_linked; }
signals:
void fileLinked(const QString& relativeName);
void linkFailed(const QString& srcName, const QString& dstName, std::error_code err);
+ void finishedPrivlaged();
private:
bool operator()(const QString& offset, bool dryRun = false);
+ bool make_link(const QString& src_path, const QString& dst_path, const QString& offset, bool dryRun);
private:
bool m_useHardLinks = false;
const IPathMatcher* m_matcher = nullptr;
bool m_whitelist = false;
bool m_recursive = true;
- QDir m_src;
- QDir m_dst;
+
+ QList m_path_pairs;
+
int m_linked;
bool m_debug = false;
- int m_last_os_err = 0;
+ std::error_code m_os_err;
+
+ QLocalServer m_linkServer;
};
/**
diff --git a/launcher/StringUtils.cpp b/launcher/StringUtils.cpp
index 0f3c3669..93a44d4c 100644
--- a/launcher/StringUtils.cpp
+++ b/launcher/StringUtils.cpp
@@ -1,5 +1,7 @@
#include "StringUtils.h"
+#include
+
/// If you're wondering where these came from exactly, then know you're not the only one =D
/// TAKEN FROM Qt, because it doesn't expose it intelligently
@@ -74,3 +76,16 @@ int StringUtils::naturalCompare(const QString& s1, const QString& s2, Qt::CaseSe
// The two strings are the same (02 == 2) so fall back to the normal sort
return QString::compare(s1, s2, cs);
}
+
+QString StringUtils::getRandomAlphaNumeric(const int length)
+{
+ const QString possibleCharacters("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789");
+ QString randomString;
+ for(int i=0; i < length; ++i)
+ {
+ int index = QRandomGenerator::global()->bounded(0, possibleCharacters.length());
+ QChar nextChar = possibleCharacters.at(index);
+ randomString.append(nextChar);
+ }
+ return randomString;
+}
diff --git a/launcher/StringUtils.h b/launcher/StringUtils.h
index 1799605b..1ba19555 100644
--- a/launcher/StringUtils.h
+++ b/launcher/StringUtils.h
@@ -29,4 +29,6 @@ inline QString fromStdString(string s)
#endif
int naturalCompare(const QString& s1, const QString& s2, Qt::CaseSensitivity cs);
+
+QString getRandomAlphaNumeric(const int length);
} // namespace StringUtils
diff --git a/launcher/filelink/FileLink.cpp b/launcher/filelink/FileLink.cpp
index 9b5589ab..78486507 100644
--- a/launcher/filelink/FileLink.cpp
+++ b/launcher/filelink/FileLink.cpp
@@ -31,8 +31,6 @@
#include
-
-#include
#include
#include
@@ -48,7 +46,7 @@
-FileLinkApp::FileLinkApp(int &argc, char **argv) : QCoreApplication(argc, argv)
+FileLinkApp::FileLinkApp(int &argc, char **argv) : QCoreApplication(argc, argv), socket(new QLocalSocket(this))
{
#if defined Q_OS_WIN32
// attach the parent console
@@ -81,18 +79,116 @@ FileLinkApp::FileLinkApp(int &argc, char **argv) : QCoreApplication(argc, argv)
// Commandline parsing
QCommandLineParser parser;
- parser.setApplicationDescription(QObject::tr("a batch MKLINK program for windows to be useed with prismlauncher"));
+ parser.setApplicationDescription(QObject::tr("a batch MKLINK program for windows to be used with prismlauncher"));
parser.addOptions({
-
+ {{"s", "server"}, "Join the specified server on launch", "pipe name"}
});
parser.addHelpOption();
parser.addVersionOption();
parser.process(arguments());
+ QString serverToJoin = parser.value("server");
+
qDebug() << "link program launched";
+ if (!serverToJoin.isEmpty()) {
+ qDebug() << "joining server" << serverToJoin;
+ joinServer(serverToJoin);
+ } else {
+ qDebug() << "no server to join";
+ exit();
+ }
+
+}
+
+void FileLinkApp::joinServer(QString server)
+{
+
+ blockSize = 0;
+
+ in.setDevice(&socket);
+ in.setVersion(QDataStream::Qt_5_15);
+
+ connect(&socket, &QLocalSocket::connected, this, [&](){
+ qDebug() << "connected to server";
+ });
+
+ connect(&socket, &QLocalSocket::readyRead, this, &FileLinkApp::readPathPairs);
+
+ connect(&socket, &QLocalSocket::errorOccurred, this, [&](QLocalSocket::LocalSocketError socketError){
+ switch (socketError) {
+ case QLocalSocket::ServerNotFoundError:
+ qDebug() << tr("The host was not found. Please make sure "
+ "that the server is running and that the "
+ "server name is correct.");
+ break;
+ case QLocalSocket::ConnectionRefusedError:
+ qDebug() << tr("The connection was refused by the peer. "
+ "Make sure the server is running, "
+ "and check that the server name "
+ "is correct.");
+ break;
+ case QLocalSocket::PeerClosedError:
+ break;
+ default:
+ qDebug() << tr("The following error occurred: %1.").arg(socket.errorString());
+ }
+ });
+
+ connect(&socket, &QLocalSocket::disconnected, this, [&](){
+ qDebug() << "dissconnected from server";
+ });
+
+ socket.connectToServer(server);
+
+
+}
+
+void FileLinkApp::runLink()
+{
+ qDebug() << "creating link";
+ FS::create_link lnk(m_path_pairs);
+ lnk.debug(true);
+ if (!lnk()) {
+ qDebug() << "Link Failed!" << lnk.getOSError().value() << lnk.getOSError().message().c_str();
+ }
+ //exit();
+ qDebug() << "done, should exit";
+}
+
+void FileLinkApp::readPathPairs()
+{
+ m_path_pairs.clear();
+ qDebug() << "Reading path pairs from server";
+ qDebug() << "bytes avalible" << socket.bytesAvailable();
+ if (blockSize == 0) {
+ // Relies on the fact that QDataStream serializes a quint32 into
+ // sizeof(quint32) bytes
+ if (socket.bytesAvailable() < (int)sizeof(quint32))
+ return;
+ qDebug() << "reading block size";
+ in >> blockSize;
+ }
+ qDebug() << "blocksize is" << blockSize;
+ qDebug() << "bytes avalible" << socket.bytesAvailable();
+ if (socket.bytesAvailable() < blockSize || in.atEnd())
+ return;
+
+ quint32 numPairs;
+ in >> numPairs;
+ qDebug() << "numPairs" << numPairs;
+
+ for(int i = 0; i < numPairs; i++) {
+ FS::LinkPair pair;
+ in >> pair.src;
+ in >> pair.dst;
+ qDebug() << "link" << pair.src << "to" << pair.dst;
+ m_path_pairs.append(pair);
+ }
+
+ runLink();
}
diff --git a/launcher/filelink/FileLink.h b/launcher/filelink/FileLink.h
index 253d1394..5d0ba123 100644
--- a/launcher/filelink/FileLink.h
+++ b/launcher/filelink/FileLink.h
@@ -32,6 +32,11 @@
#include
#include
#include
+#include
+#include
+
+#define PRISM_EXTERNAL_EXE
+#include "FileSystem.h"
class FileLinkApp : public QCoreApplication
{
@@ -43,7 +48,17 @@ public:
virtual ~FileLinkApp();
private:
+
+ void joinServer(QString server);
+ void readPathPairs();
+ void runLink();
+
QDateTime m_startTime;
+ QLocalSocket socket;
+ QDataStream in;
+ quint32 blockSize;
+
+ QList m_path_pairs;
#if defined Q_OS_WIN32
// used on Windows to attach the standard IO streams
diff --git a/tests/FileSystem_test.cpp b/tests/FileSystem_test.cpp
index 395ca5c0..be0a4be0 100644
--- a/tests/FileSystem_test.cpp
+++ b/tests/FileSystem_test.cpp
@@ -2,6 +2,8 @@
#include
#include
+#include
+
#include
#include
@@ -26,6 +28,66 @@ namespace fs = ghc::filesystem;
#include
+
+
+class LinkTask : public Task {
+ Q_OBJECT
+
+ friend class FileSystemTest;
+
+ LinkTask(QString src, QString dst)
+ {
+ m_lnk = new FS::create_link(src, dst, this);
+ m_lnk->debug(true);
+ }
+
+ void matcher(const IPathMatcher *filter)
+ {
+ m_lnk->matcher(filter);
+ }
+
+ void linkRecursively(bool recursive)
+ {
+ m_lnk->linkRecursively(recursive);
+ m_linkRecursive = recursive;
+ }
+
+ void whitelist(bool b)
+ {
+ m_lnk->whitelist(b);
+ }
+
+ private:
+ void executeTask() override
+ {
+ if(!(*m_lnk)()){
+#if defined Q_OS_WIN32
+ if (!m_useHard) {
+ qDebug() << "EXPECTED: Link failure, Windows requires permissions for symlinks";
+
+ qDebug() << "atempting to run with privelage";
+ connect(m_lnk, &FS::create_link::finishedPrivlaged, this, [&](){
+ emitSucceeded();
+ });
+ m_lnk->runPrivlaged();
+ } else {
+ qDebug() << "Link Failed!" << m_lnk->getOSError().value() << m_lnk->getOSError().message().c_str();
+ }
+#else
+ qDebug() << "Link Failed!" << m_lnk->getOSError().value() << m_lnk->getOSError().message().c_str();
+#endif
+ } else {
+ emitSucceeded();
+ }
+
+ };
+
+ FS::create_link *m_lnk;
+ bool m_useHard = false;
+ bool m_linkRecursive = true;
+};
+
+
class FileSystemTest : public QObject
{
Q_OBJECT
@@ -273,7 +335,7 @@ slots:
void test_link()
{
QString folder = QFINDTESTDATA("testdata/FileSystem/test_folder");
- auto f = [&folder]()
+ auto f = [&folder, this]()
{
QTemporaryDir tempDir;
tempDir.setAutoRemove(true);
@@ -282,17 +344,17 @@ slots:
QDir target_dir(FS::PathCombine(tempDir.path(), "test_folder"));
qDebug() << tempDir.path();
qDebug() << target_dir.path();
- FS::create_link lnk(folder, target_dir.path());
- lnk.linkRecursively(false);
- lnk.debug(true);
- if(!lnk()){
-#if defined Q_OS_WIN32
- qDebug() << "EXPECTED: Link failure, Windows requires permissions for symlinks";
- QVERIFY(lnk.getLastOSError() == 1314);
- return;
-#endif
- qDebug() << "Link Failed!" << lnk.getLastOSError();
- }
+
+ LinkTask lnk_tsk(folder, target_dir.path());
+ lnk_tsk.linkRecursively(false);
+ QObject::connect(&lnk_tsk, &Task::finished, [&]{
+ QVERIFY2(lnk_tsk.wasSuccessful(), "Task finished but was not successful when it should have been.");
+ });
+ lnk_tsk.start();
+
+ QVERIFY2(QTest::qWaitFor([&]() {
+ return lnk_tsk.isFinished();
+ }, 100000), "Task didn't finish as it should.");
for(auto entry: target_dir.entryList())
{
@@ -337,7 +399,7 @@ slots:
lnk.useHardLinks(true);
lnk.debug(true);
if(!lnk()){
- qDebug() << "Link Failed!" << lnk.getLastOSError();
+ qDebug() << "Link Failed!" << lnk.getOSError().value() << lnk.getOSError().message().c_str();
}
for(auto entry: target_dir.entryList())
@@ -385,18 +447,19 @@ slots:
QDir target_dir(FS::PathCombine(tempDir.path(), "test_folder"));
qDebug() << tempDir.path();
qDebug() << target_dir.path();
- FS::create_link lnk(folder, target_dir.path());
- lnk.matcher(new RegexpMatcher("[.]?mcmeta"));
- lnk.linkRecursively(true);
- lnk.debug(true);
- if(!lnk()){
-#if defined Q_OS_WIN32
- qDebug() << "EXPECTED: Link failure, Windows requires permissions for symlinks";
- QVERIFY(lnk.getLastOSError() == 1314);
- return;
-#endif
- qDebug() << "Link Failed!" << lnk.getLastOSError();
- }
+
+ LinkTask lnk_tsk(folder, target_dir.path());
+ lnk_tsk.matcher(new RegexpMatcher("[.]?mcmeta"));
+ lnk_tsk.linkRecursively(true);
+ QObject::connect(&lnk_tsk, &Task::finished, [&]{
+ QVERIFY2(lnk_tsk.wasSuccessful(), "Task finished but was not successful when it should have been.");
+ });
+ lnk_tsk.start();
+
+ QVERIFY2(QTest::qWaitFor([&]() {
+ return lnk_tsk.isFinished();
+ }, 100000), "Task didn't finish as it should.");
+
for(auto entry: target_dir.entryList())
{
@@ -435,19 +498,19 @@ slots:
QDir target_dir(FS::PathCombine(tempDir.path(), "test_folder"));
qDebug() << tempDir.path();
qDebug() << target_dir.path();
- FS::create_link lnk(folder, target_dir.path());
- lnk.matcher(new RegexpMatcher("[.]?mcmeta"));
- lnk.whitelist(true);
- lnk.linkRecursively(true);
- lnk.debug(true);
- if(!lnk()){
-#if defined Q_OS_WIN32
- qDebug() << "EXPECTED: Link failure, Windows requires permissions for symlinks";
- QVERIFY(lnk.getLastOSError() == 1314);
- return;
-#endif
- qDebug() << "Link Failed!" << lnk.getLastOSError();
- }
+
+ LinkTask lnk_tsk(folder, target_dir.path());
+ lnk_tsk.matcher(new RegexpMatcher("[.]?mcmeta"));
+ lnk_tsk.linkRecursively(true);
+ lnk_tsk.whitelist(true);
+ QObject::connect(&lnk_tsk, &Task::finished, [&]{
+ QVERIFY2(lnk_tsk.wasSuccessful(), "Task finished but was not successful when it should have been.");
+ });
+ lnk_tsk.start();
+
+ QVERIFY2(QTest::qWaitFor([&]() {
+ return lnk_tsk.isFinished();
+ }, 100000), "Task didn't finish as it should.");
for(auto entry: target_dir.entryList())
{
@@ -486,17 +549,17 @@ slots:
QDir target_dir(FS::PathCombine(tempDir.path(), "test_folder"));
qDebug() << tempDir.path();
qDebug() << target_dir.path();
- FS::create_link lnk(folder, target_dir.path());
- lnk.linkRecursively(true);
- lnk.debug(true);
- if(!lnk()){
-#if defined Q_OS_WIN32
- qDebug() << "EXPECTED: Link failure, Windows requires permissions for symlinks";
- QVERIFY(lnk.getLastOSError() == 1314);
- return;
-#endif
- qDebug() << "Link Failed!" << lnk.getLastOSError();
- }
+
+ LinkTask lnk_tsk(folder, target_dir.path());
+ lnk_tsk.linkRecursively(true);
+ QObject::connect(&lnk_tsk, &Task::finished, [&]{
+ QVERIFY2(lnk_tsk.wasSuccessful(), "Task finished but was not successful when it should have been.");
+ });
+ lnk_tsk.start();
+
+ QVERIFY2(QTest::qWaitFor([&]() {
+ return lnk_tsk.isFinished();
+ }, 100000), "Task didn't finish as it should.");
auto filter = QDir::Filter::Files | QDir::Filter::Dirs | QDir::Filter::Hidden;
@@ -538,16 +601,16 @@ slots:
QDir target_dir(FS::PathCombine(tempDir.path(), "pack.mcmeta"));
qDebug() << tempDir.path();
qDebug() << target_dir.path();
- FS::create_link lnk(file, target_dir.filePath("pack.mcmeta"));
- lnk.debug(true);
- if(!lnk()){
-#if defined Q_OS_WIN32
- qDebug() << "EXPECTED: Link failure, Windows requires permissions for symlinks";
- QVERIFY(lnk.getLastOSError() == 1314);
- return;
-#endif
- qDebug() << "Link Failed!" << lnk.getLastOSError();
- }
+
+ LinkTask lnk_tsk(file, target_dir.filePath("pack.mcmeta"));
+ QObject::connect(&lnk_tsk, &Task::finished, [&]{
+ QVERIFY2(lnk_tsk.wasSuccessful(), "Task finished but was not successful when it should have been.");
+ });
+ lnk_tsk.start();
+
+ QVERIFY2(QTest::qWaitFor([&]() {
+ return lnk_tsk.isFinished();
+ }, 100000), "Task didn't finish as it should.");
auto filter = QDir::Filter::Files;
From 8ba51c790098ec9ebe3d2ef686f823b61c8a3645 Mon Sep 17 00:00:00 2001
From: Rachel Powers <508861+Ryex@users.noreply.github.com>
Date: Wed, 8 Feb 2023 12:36:15 -0800
Subject: [PATCH 007/104] refactor: make complete list of links to make and
send that.
Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com>
---
launcher/FileSystem.cpp | 223 +++++++++++++++++++++------------
launcher/FileSystem.h | 35 ++++--
launcher/filelink/FileLink.cpp | 136 ++++++++++++++++----
launcher/filelink/FileLink.h | 6 +-
tests/FileSystem_test.cpp | 11 +-
5 files changed, 295 insertions(+), 116 deletions(-)
diff --git a/launcher/FileSystem.cpp b/launcher/FileSystem.cpp
index 9e51f932..c48a3bba 100644
--- a/launcher/FileSystem.cpp
+++ b/launcher/FileSystem.cpp
@@ -66,7 +66,6 @@
#include
//for ShellExecute
#include
-//#include
#include
#include
#else
@@ -228,94 +227,120 @@ bool copy::operator()(const QString& offset, bool dryRun)
bool create_link::operator()(const QString& offset, bool dryRun)
{
+ m_linked = 0; // reset counter
+ m_path_results.clear();
+ m_links_to_make.clear();
+
+ m_path_results.clear();
+
+ make_link_list(offset);
+
+ if (!dryRun)
+ return make_links();
- for (auto pair : m_path_pairs) {
- if (!make_link(pair.src, pair.dst, offset, dryRun)) {
- return false;
- }
- }
return true;
}
/**
- * @brief links a directory and it's contents from src to dest
+ * @brief make a list off all the links ot make
* @param offset subdirectory form src to link to dest
* @return if there was an error during the attempt to link
*/
-bool create_link::make_link(const QString& srcPath, const QString& dstPath, const QString& offset, bool dryRun)
+void create_link::make_link_list( const QString& offset)
{
- m_linked = 0; // reset counter
+ for (auto pair : m_path_pairs) {
+ const QString& srcPath = pair.src;
+ const QString& dstPath = pair.dst;
- auto src = PathCombine(QDir(srcPath).absolutePath(), offset);
- auto dst = PathCombine(QDir(dstPath).absolutePath(), offset);
+ auto src = PathCombine(QDir(srcPath).absolutePath(), offset);
+ auto dst = PathCombine(QDir(dstPath).absolutePath(), offset);
- // you can't hard link a directory so make sure if we deal with a directory we do so recursively
- if (m_useHardLinks)
- m_recursive = true;
+ // you can't hard link a directory so make sure if we deal with a directory we do so recursively
+ if (m_useHardLinks)
+ m_recursive = true;
- // Function that'll do the actual linking
- auto link_file = [&](QString src_path, QString relative_dst_path) {
- if (m_matcher && (m_matcher->matches(relative_dst_path) != m_whitelist))
- return;
-
- auto dst_path = PathCombine(dst, relative_dst_path);
- if (!dryRun) {
-
- ensureFilePathExists(dst_path);
- if (m_useHardLinks) {
- if (m_debug)
- qDebug() << "making hard link:" << src_path << "to" << dst_path;
- fs::create_hard_link(StringUtils::toStdString(src_path), StringUtils::toStdString(dst_path), m_os_err);
- } else if (fs::is_directory(StringUtils::toStdString(src_path))) {
- if (m_debug)
- qDebug() << "making directory_symlink:" << src_path << "to" << dst_path;
- fs::create_directory_symlink(StringUtils::toStdString(src_path), StringUtils::toStdString(dst_path), m_os_err);
- } else {
- if (m_debug)
- qDebug() << "making symlink:" << src_path << "to" << dst_path;
- fs::create_symlink(StringUtils::toStdString(src_path), StringUtils::toStdString(dst_path), m_os_err);
+ // Function that'll do the actual linking
+ auto link_file = [&](QString src_path, QString relative_dst_path) {
+ if (m_matcher && (m_matcher->matches(relative_dst_path) != m_whitelist)) {
+ qDebug() << "path" << relative_dst_path << "in black list or not in whitelist";
+ return;
+ }
+
+
+ auto dst_path = PathCombine(dst, relative_dst_path);
+ LinkPair link = {src_path, dst_path};
+ m_links_to_make.append(link);
+ };
+
+ if ((!m_recursive) || !fs::is_directory(StringUtils::toStdString(src))) {
+ if (m_debug)
+ qDebug() << "linking single file or dir:" << src << "to" << dst;
+ link_file(src, "");
+ } else {
+ if (m_debug)
+ qDebug() << "linking recursivly:" << src << "to" << dst;
+ QDir src_dir(src);
+ QDirIterator source_it(src, QDir::Filter::Files | QDir::Filter::Hidden, QDirIterator::Subdirectories);
+
+ while (source_it.hasNext()) {
+ auto src_path = source_it.next();
+ auto relative_path = src_dir.relativeFilePath(src_path);
+
+ link_file(src_path, relative_path);
}
-
}
+ }
+}
+
+bool create_link::make_links()
+{
+ for (auto link : m_links_to_make) {
+
+ QString src_path = link.src;
+ QString dst_path = link.dst;
+
+ ensureFilePathExists(dst_path);
+ if (m_useHardLinks) {
+ if (m_debug)
+ qDebug() << "making hard link:" << src_path << "to" << dst_path;
+ fs::create_hard_link(StringUtils::toStdString(src_path), StringUtils::toStdString(dst_path), m_os_err);
+ } else if (fs::is_directory(StringUtils::toStdString(src_path))) {
+ if (m_debug)
+ qDebug() << "making directory_symlink:" << src_path << "to" << dst_path;
+ fs::create_directory_symlink(StringUtils::toStdString(src_path), StringUtils::toStdString(dst_path), m_os_err);
+ } else {
+ if (m_debug)
+ qDebug() << "making symlink:" << src_path << "to" << dst_path;
+ fs::create_symlink(StringUtils::toStdString(src_path), StringUtils::toStdString(dst_path), m_os_err);
+ }
+
+
if (m_os_err) {
qWarning() << "Failed to link files:" << QString::fromStdString(m_os_err.message());
qDebug() << "Source file:" << src_path;
qDebug() << "Destination file:" << dst_path;
qDebug() << "Error catagory:" << m_os_err.category().name();
qDebug() << "Error code:" << m_os_err.value();
- emit linkFailed(src_path, dst_path, m_os_err);
+ emit linkFailed(src_path, dst_path, QString::fromStdString(m_os_err.message()), m_os_err.value());
} else {
m_linked++;
- emit fileLinked(relative_dst_path);
- }
-
- };
-
- if ((!m_recursive) || !fs::is_directory(StringUtils::toStdString(src))) {
- if (m_debug)
- qDebug() << "linking single file or dir:" << src << "to" << dst;
- link_file(src, "");
- } else {
- if (m_debug)
- qDebug() << "linking recursivly:" << src << "to" << dst;
- QDir src_dir(src);
- QDirIterator source_it(src, QDir::Filter::Files | QDir::Filter::Hidden, QDirIterator::Subdirectories);
-
- while (source_it.hasNext()) {
- auto src_path = source_it.next();
- auto relative_path = src_dir.relativeFilePath(src_path);
-
- link_file(src_path, relative_path);
- if (m_os_err) return false;
+ emit fileLinked(src_path, dst_path);
}
+ if (m_os_err) return false;
}
-
- return m_os_err.value() == 0;
+ return true;
}
-bool create_link::runPrivlaged(const QString& offset)
+void create_link::runPrivlaged(const QString& offset)
{
+ m_linked = 0; // reset counter
+ m_path_results.clear();
+ m_links_to_make.clear();
+
+ bool gotResults = false;
+
+ make_link_list(offset);
QString serverName = BuildConfig.LAUNCHER_APP_BINARY_NAME + "_filelink_server" + StringUtils::getRandomAlphaNumeric(8);
@@ -325,25 +350,72 @@ bool create_link::runPrivlaged(const QString& offset)
// construct block of data to send
QByteArray block;
QDataStream out(&block, QIODevice::WriteOnly);
- out.setVersion(QDataStream::Qt_5_15); // choose correct version better?
+ out.setVersion(QDataStream::Qt_5_0); // choose correct version better?
qint32 blocksize = quint32(sizeof(quint32));
- for (auto pair : m_path_pairs) {
- blocksize += quint32(pair.src.size());
- blocksize += quint32(pair.dst.size());
+ for (auto link : m_links_to_make) {
+ blocksize += quint32(link.src.size());
+ blocksize += quint32(link.dst.size());
}
qDebug() << "About to write block of size:" << blocksize;
out << blocksize;
- out << quint32(m_path_pairs.length());
- for (auto pair : m_path_pairs) {
- out << pair.src;
- out << pair.dst;
+ out << quint32(m_links_to_make.length());
+ for (auto link : m_links_to_make) {
+ out << link.src;
+ out << link.dst;
}
QLocalSocket *clientConnection = m_linkServer.nextPendingConnection();
connect(clientConnection, &QLocalSocket::disconnected,
clientConnection, &QLocalSocket::deleteLater);
+
+ connect(clientConnection, &QLocalSocket::readyRead, this, [&, clientConnection](){
+ QDataStream in;
+ quint32 blockSize = 0;
+ in.setDevice(clientConnection);
+ in.setVersion(QDataStream::Qt_5_0);
+ qDebug() << "Reading path results from client";
+ qDebug() << "bytes avalible" << clientConnection->bytesAvailable();
+
+ // Relies on the fact that QDataStream serializes a quint32 into
+ // sizeof(quint32) bytes
+ if (clientConnection->bytesAvailable() < (int)sizeof(quint32))
+ return;
+ qDebug() << "reading block size";
+ in >> blockSize;
+
+ qDebug() << "blocksize is" << blockSize;
+ qDebug() << "bytes avalible" << clientConnection->bytesAvailable();
+ if (clientConnection->bytesAvailable() < blockSize || in.atEnd())
+ return;
+
+ quint32 numResults;
+ in >> numResults;
+ qDebug() << "numResults" << numResults;
+
+ for(int i = 0; i < numResults; i++) {
+ FS::LinkResult result;
+ in >> result.src;
+ in >> result.dst;
+ in >> result.err_msg;
+ qint32 err_value;
+ in >> err_value;
+ result.err_value = err_value;
+ if (result.err_value) {
+ qDebug() << "privlaged link fail" << result.src << "to" << result.dst << "code" << result.err_value << result.err_msg;
+ emit linkFailed(result.src, result.dst, result.err_msg, result.err_value);
+ } else {
+ qDebug() << "privlaged link success" << result.src << "to" << result.dst;
+ m_linked++;
+ emit fileLinked(result.src, result.dst);
+ }
+ m_path_results.append(result);
+ }
+ gotResults = true;
+ qDebug() << "results recieved, closing connection";
+ clientConnection->close();
+ });
qint64 byteswritten = clientConnection->write(block);
bool bytesflushed = clientConnection->flush();
@@ -354,20 +426,15 @@ bool create_link::runPrivlaged(const QString& offset)
qDebug() << "Listening on pipe" << serverName;
if (!m_linkServer.listen(serverName)) {
qDebug() << "Unable to start local pipe server on" << serverName << ":" << m_linkServer.errorString();
- return false;
+ return;
}
- ExternalLinkFileProcess *linkFileProcess = new ExternalLinkFileProcess(serverName, this);
- connect(linkFileProcess, &ExternalLinkFileProcess::processExited, this, [&](){
- emit finishedPrivlaged();
- });
+ ExternalLinkFileProcess* linkFileProcess = new ExternalLinkFileProcess(serverName, m_useHardLinks, this);
+ connect(linkFileProcess, &ExternalLinkFileProcess::processExited, this, [&]() { emit finishedPrivlaged(gotResults); });
connect(linkFileProcess, &ExternalLinkFileProcess::finished, linkFileProcess, &QObject::deleteLater);
linkFileProcess->start();
- // linkFileProcess->wait();
-
- return true;
}
@@ -375,6 +442,8 @@ void ExternalLinkFileProcess::runLinkFile() {
QString fileLinkExe = PathCombine(QCoreApplication::instance()->applicationDirPath(), BuildConfig.LAUNCHER_APP_BINARY_NAME + "_filelink");
QString params = "-s " + m_server;
+ params += " -H " + QVariant(m_useHardLinks).toString();
+
#if defined Q_OS_WIN32
SHELLEXECUTEINFO ShExecInfo;
HRESULT hr;
diff --git a/launcher/FileSystem.h b/launcher/FileSystem.h
index b15d1685..2e739298 100644
--- a/launcher/FileSystem.h
+++ b/launcher/FileSystem.h
@@ -133,13 +133,22 @@ struct LinkPair {
QString dst;
};
-class ExternalLinkFileProcess : public QThread
-{
+struct LinkResult {
+ QString src;
+ QString dst;
+ QString err_msg;
+ int err_value;
+};
+
+class ExternalLinkFileProcess : public QThread {
Q_OBJECT
public:
- ExternalLinkFileProcess(QString server, QObject* parent = nullptr) : QThread(parent), m_server(server) {}
+ ExternalLinkFileProcess(QString server, bool useHardLinks, QObject* parent = nullptr)
+ : QThread(parent), m_server(server), m_useHardLinks(useHardLinks)
+ {}
- void run() override {
+ void run() override
+ {
runLinkFile();
emit processExited();
}
@@ -150,6 +159,8 @@ class ExternalLinkFileProcess : public QThread
private:
void runLinkFile();
+ bool m_useHardLinks = false;
+
QString m_server;
};
@@ -200,19 +211,21 @@ class create_link : public QObject {
bool operator()(bool dryRun = false) { return operator()(QString(), dryRun); }
- bool runPrivlaged() { return runPrivlaged(QString()); }
- bool runPrivlaged(const QString& offset);
+ void runPrivlaged() { runPrivlaged(QString()); }
+ void runPrivlaged(const QString& offset);
int totalLinked() { return m_linked; }
signals:
- void fileLinked(const QString& relativeName);
- void linkFailed(const QString& srcName, const QString& dstName, std::error_code err);
- void finishedPrivlaged();
+ void fileLinked(const QString& srcName, const QString& dstName);
+ void linkFailed(const QString& srcName, const QString& dstName, const QString& err_msg, int err_value);
+ void finishedPrivlaged(bool gotResults);
+ void finished();
private:
bool operator()(const QString& offset, bool dryRun = false);
- bool make_link(const QString& src_path, const QString& dst_path, const QString& offset, bool dryRun);
+ void make_link_list(const QString& offset);
+ bool make_links();
private:
bool m_useHardLinks = false;
@@ -221,6 +234,8 @@ class create_link : public QObject {
bool m_recursive = true;
QList m_path_pairs;
+ QList m_path_results;
+ QList m_links_to_make;
int m_linked;
bool m_debug = false;
diff --git a/launcher/filelink/FileLink.cpp b/launcher/filelink/FileLink.cpp
index 78486507..a731ecdb 100644
--- a/launcher/filelink/FileLink.cpp
+++ b/launcher/filelink/FileLink.cpp
@@ -23,6 +23,8 @@
#include "FileLink.h"
#include "BuildConfig.h"
+#include "StringUtils.h"
+
#include
@@ -43,6 +45,24 @@
#include
#endif
+// Snippet from https://github.com/gulrak/filesystem#using-it-as-single-file-header
+
+#ifdef __APPLE__
+#include // for deployment target to support pre-catalina targets without std::fs
+#endif // __APPLE__
+
+#if ((defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) || (defined(__cplusplus) && __cplusplus >= 201703L)) && defined(__has_include)
+#if __has_include() && (!defined(__MAC_OS_X_VERSION_MIN_REQUIRED) || __MAC_OS_X_VERSION_MIN_REQUIRED >= 101500)
+#define GHC_USE_STD_FS
+#include
+namespace fs = std::filesystem;
+#endif // MacOS min version check
+#endif // Other OSes version check
+
+#ifndef GHC_USE_STD_FS
+#include
+namespace fs = ghc::filesystem;
+#endif
@@ -82,7 +102,8 @@ FileLinkApp::FileLinkApp(int &argc, char **argv) : QCoreApplication(argc, argv),
parser.setApplicationDescription(QObject::tr("a batch MKLINK program for windows to be used with prismlauncher"));
parser.addOptions({
- {{"s", "server"}, "Join the specified server on launch", "pipe name"}
+ {{"s", "server"}, "Join the specified server on launch", "pipe name"},
+ {{"H", "hard"}, "use hard links insted of symbolic", "true/false"}
});
parser.addHelpOption();
parser.addVersionOption();
@@ -90,6 +111,7 @@ FileLinkApp::FileLinkApp(int &argc, char **argv) : QCoreApplication(argc, argv),
parser.process(arguments());
QString serverToJoin = parser.value("server");
+ m_useHardLinks = QVariant(parser.value("hard")).toBool();
qDebug() << "link program launched";
@@ -109,7 +131,7 @@ void FileLinkApp::joinServer(QString server)
blockSize = 0;
in.setDevice(&socket);
- in.setVersion(QDataStream::Qt_5_15);
+ in.setVersion(QDataStream::Qt_5_0);
connect(&socket, &QLocalSocket::connected, this, [&](){
qDebug() << "connected to server";
@@ -120,25 +142,27 @@ void FileLinkApp::joinServer(QString server)
connect(&socket, &QLocalSocket::errorOccurred, this, [&](QLocalSocket::LocalSocketError socketError){
switch (socketError) {
case QLocalSocket::ServerNotFoundError:
- qDebug() << tr("The host was not found. Please make sure "
- "that the server is running and that the "
- "server name is correct.");
+ qDebug() << ("The host was not found. Please make sure "
+ "that the server is running and that the "
+ "server name is correct.");
break;
case QLocalSocket::ConnectionRefusedError:
- qDebug() << tr("The connection was refused by the peer. "
- "Make sure the server is running, "
- "and check that the server name "
- "is correct.");
+ qDebug() << ("The connection was refused by the peer. "
+ "Make sure the server is running, "
+ "and check that the server name "
+ "is correct.");
break;
case QLocalSocket::PeerClosedError:
+ qDebug() << ("The connection was closed by the peer. ");
break;
default:
- qDebug() << tr("The following error occurred: %1.").arg(socket.errorString());
+ qDebug() << "The following error occurred: " << socket.errorString();
}
});
connect(&socket, &QLocalSocket::disconnected, this, [&](){
- qDebug() << "dissconnected from server";
+ qDebug() << "dissconnected from server, should exit";
+ exit();
});
socket.connectToServer(server);
@@ -147,20 +171,82 @@ void FileLinkApp::joinServer(QString server)
}
void FileLinkApp::runLink()
-{
- qDebug() << "creating link";
- FS::create_link lnk(m_path_pairs);
- lnk.debug(true);
- if (!lnk()) {
- qDebug() << "Link Failed!" << lnk.getOSError().value() << lnk.getOSError().message().c_str();
+{
+
+ std::error_code os_err;
+
+ qDebug() << "creating links";
+
+ for (auto link : m_links_to_make) {
+
+ QString src_path = link.src;
+ QString dst_path = link.dst;
+
+ FS::ensureFilePathExists(dst_path);
+ if (m_useHardLinks) {
+ qDebug() << "making hard link:" << src_path << "to" << dst_path;
+ fs::create_hard_link(StringUtils::toStdString(src_path), StringUtils::toStdString(dst_path), os_err);
+ } else if (fs::is_directory(StringUtils::toStdString(src_path))) {
+ qDebug() << "making directory_symlink:" << src_path << "to" << dst_path;
+ fs::create_directory_symlink(StringUtils::toStdString(src_path), StringUtils::toStdString(dst_path), os_err);
+ } else {
+ qDebug() << "making symlink:" << src_path << "to" << dst_path;
+ fs::create_symlink(StringUtils::toStdString(src_path), StringUtils::toStdString(dst_path), os_err);
+ }
+
+ if (os_err) {
+ qWarning() << "Failed to link files:" << QString::fromStdString(os_err.message());
+ qDebug() << "Source file:" << src_path;
+ qDebug() << "Destination file:" << dst_path;
+ qDebug() << "Error catagory:" << os_err.category().name();
+ qDebug() << "Error code:" << os_err.value();
+
+ FS::LinkResult result = {src_path, dst_path, QString::fromStdString(os_err.message()), os_err.value()};
+ m_path_results.append(result);
+ } else {
+ FS::LinkResult result = {src_path, dst_path};
+ m_path_results.append(result);
+ }
}
- //exit();
- qDebug() << "done, should exit";
+
+ sendResults();
+ qDebug() << "done, should exit soon";
+
+}
+
+void FileLinkApp::sendResults()
+{
+ // construct block of data to send
+ QByteArray block;
+ QDataStream out(&block, QIODevice::WriteOnly);
+ out.setVersion(QDataStream::Qt_5_0);
+
+ qint32 blocksize = quint32(sizeof(quint32));
+ for (auto result : m_path_results) {
+ blocksize += quint32(result.src.size());
+ blocksize += quint32(result.dst.size());
+ blocksize += quint32(result.err_msg.size());
+ blocksize += quint32(sizeof(quint32));
+ }
+ qDebug() << "About to write block of size:" << blocksize;
+ out << blocksize;
+
+ out << quint32(m_path_results.length());
+ for (auto result : m_path_results) {
+ out << result.src;
+ out << result.dst;
+ out << result.err_msg;
+ out << quint32(result.err_value);
+ }
+
+ qint64 byteswritten = socket.write(block);
+ bool bytesflushed = socket.flush();
+ qDebug() << "block flushed" << byteswritten << bytesflushed;
}
void FileLinkApp::readPathPairs()
{
- m_path_pairs.clear();
+ m_links_to_make.clear();
qDebug() << "Reading path pairs from server";
qDebug() << "bytes avalible" << socket.bytesAvailable();
if (blockSize == 0) {
@@ -176,16 +262,16 @@ void FileLinkApp::readPathPairs()
if (socket.bytesAvailable() < blockSize || in.atEnd())
return;
- quint32 numPairs;
- in >> numPairs;
- qDebug() << "numPairs" << numPairs;
+ quint32 numLinks;
+ in >> numLinks;
+ qDebug() << "numLinks" << numLinks;
- for(int i = 0; i < numPairs; i++) {
+ for(int i = 0; i < numLinks; i++) {
FS::LinkPair pair;
in >> pair.src;
in >> pair.dst;
qDebug() << "link" << pair.src << "to" << pair.dst;
- m_path_pairs.append(pair);
+ m_links_to_make.append(pair);
}
runLink();
diff --git a/launcher/filelink/FileLink.h b/launcher/filelink/FileLink.h
index 5d0ba123..d146b8d9 100644
--- a/launcher/filelink/FileLink.h
+++ b/launcher/filelink/FileLink.h
@@ -52,13 +52,17 @@ private:
void joinServer(QString server);
void readPathPairs();
void runLink();
+ void sendResults();
+
+ bool m_useHardLinks = false;
QDateTime m_startTime;
QLocalSocket socket;
QDataStream in;
quint32 blockSize;
- QList m_path_pairs;
+ QList m_links_to_make;
+ QList m_path_results;
#if defined Q_OS_WIN32
// used on Windows to attach the standard IO streams
diff --git a/tests/FileSystem_test.cpp b/tests/FileSystem_test.cpp
index be0a4be0..4ccc4003 100644
--- a/tests/FileSystem_test.cpp
+++ b/tests/FileSystem_test.cpp
@@ -66,12 +66,17 @@ class LinkTask : public Task {
qDebug() << "EXPECTED: Link failure, Windows requires permissions for symlinks";
qDebug() << "atempting to run with privelage";
- connect(m_lnk, &FS::create_link::finishedPrivlaged, this, [&](){
- emitSucceeded();
+ connect(m_lnk, &FS::create_link::finishedPrivlaged, this, [&](bool gotResults){
+ if (gotResults) {
+ emitSucceeded();
+ } else {
+ qDebug() << "Privlaged run exited without results!";
+ emitFailed();
+ }
});
m_lnk->runPrivlaged();
} else {
- qDebug() << "Link Failed!" << m_lnk->getOSError().value() << m_lnk->getOSError().message().c_str();
+ qDebug() << "Link Failed!" << m_lnk->getOSError().value() << m_lnk->getOSError().message().c_str();
}
#else
qDebug() << "Link Failed!" << m_lnk->getOSError().value() << m_lnk->getOSError().message().c_str();
From 59788823785c186af78d8100fce3bdedbed85c80 Mon Sep 17 00:00:00 2001
From: Rachel Powers <508861+Ryex@users.noreply.github.com>
Date: Wed, 8 Feb 2023 14:30:45 -0800
Subject: [PATCH 008/104] feat(symlinks&hardlinks): linkup copy dialog
Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com>
---
launcher/FileSystem.cpp | 5 +-
launcher/FileSystem.h | 9 ++-
launcher/InstanceCopyPrefs.cpp | 8 +--
launcher/InstanceCopyPrefs.h | 6 +-
launcher/InstanceCopyTask.cpp | 73 ++++++++++++++++++++--
launcher/InstanceCopyTask.h | 3 +
launcher/ui/dialogs/CopyInstanceDialog.cpp | 7 ++-
launcher/ui/dialogs/CopyInstanceDialog.h | 2 +-
launcher/ui/dialogs/CopyInstanceDialog.ui | 9 +--
9 files changed, 96 insertions(+), 26 deletions(-)
diff --git a/launcher/FileSystem.cpp b/launcher/FileSystem.cpp
index c48a3bba..c94770ee 100644
--- a/launcher/FileSystem.cpp
+++ b/launcher/FileSystem.cpp
@@ -420,7 +420,7 @@ void create_link::runPrivlaged(const QString& offset)
qint64 byteswritten = clientConnection->write(block);
bool bytesflushed = clientConnection->flush();
qDebug() << "block flushed" << byteswritten << bytesflushed;
- //clientConnection->disconnectFromServer();
+
});
qDebug() << "Listening on pipe" << serverName;
@@ -437,7 +437,6 @@ void create_link::runPrivlaged(const QString& offset)
}
-
void ExternalLinkFileProcess::runLinkFile() {
QString fileLinkExe = PathCombine(QCoreApplication::instance()->applicationDirPath(), BuildConfig.LAUNCHER_APP_BINARY_NAME + "_filelink");
QString params = "-s " + m_server;
@@ -463,7 +462,7 @@ void ExternalLinkFileProcess::runLinkFile() {
ShExecInfo.lpFile = programNameWin;
ShExecInfo.lpParameters = paramsWin;
ShExecInfo.lpDirectory = NULL;
- ShExecInfo.nShow = SW_NORMAL;
+ ShExecInfo.nShow = SW_HIDE;
ShExecInfo.hInstApp = NULL;
ShellExecuteEx(&ShExecInfo);
diff --git a/launcher/FileSystem.h b/launcher/FileSystem.h
index 2e739298..d79096e6 100644
--- a/launcher/FileSystem.h
+++ b/launcher/FileSystem.h
@@ -211,16 +211,21 @@ class create_link : public QObject {
bool operator()(bool dryRun = false) { return operator()(QString(), dryRun); }
+ int totalLinked() { return m_linked; }
+
+
void runPrivlaged() { runPrivlaged(QString()); }
void runPrivlaged(const QString& offset);
- int totalLinked() { return m_linked; }
+ QList getResults() { return m_path_results; }
+
signals:
void fileLinked(const QString& srcName, const QString& dstName);
void linkFailed(const QString& srcName, const QString& dstName, const QString& err_msg, int err_value);
- void finishedPrivlaged(bool gotResults);
void finished();
+ void finishedPrivlaged(bool gotResults);
+
private:
bool operator()(const QString& offset, bool dryRun = false);
diff --git a/launcher/InstanceCopyPrefs.cpp b/launcher/InstanceCopyPrefs.cpp
index 18a6d704..e363d4c6 100644
--- a/launcher/InstanceCopyPrefs.cpp
+++ b/launcher/InstanceCopyPrefs.cpp
@@ -103,9 +103,9 @@ bool InstanceCopyPrefs::isUseHardLinksEnabled() const
return useHardLinks;
}
-bool InstanceCopyPrefs::isLinkWorldsEnabled() const
+bool InstanceCopyPrefs::isDontLinkSavesEnabled() const
{
- return linkWorlds;
+ return dontLinkSaves;
}
// ======= Setters =======
@@ -159,7 +159,7 @@ void InstanceCopyPrefs::enableUseHardLinks(bool b)
useHardLinks = b;
}
-void InstanceCopyPrefs::enableLinkWorlds(bool b)
+void InstanceCopyPrefs::enableDontLinkSaves(bool b)
{
- linkWorlds = b;
+ dontLinkSaves = b;
}
diff --git a/launcher/InstanceCopyPrefs.h b/launcher/InstanceCopyPrefs.h
index 25c0f3fc..61719a06 100644
--- a/launcher/InstanceCopyPrefs.h
+++ b/launcher/InstanceCopyPrefs.h
@@ -21,7 +21,7 @@ struct InstanceCopyPrefs {
[[nodiscard]] bool isCopyScreenshotsEnabled() const;
[[nodiscard]] bool isLinkFilesEnabled() const;
[[nodiscard]] bool isUseHardLinksEnabled() const;
- [[nodiscard]] bool isLinkWorldsEnabled() const;
+ [[nodiscard]] bool isDontLinkSavesEnabled() const;
// Setters
void enableCopySaves(bool b);
void enableKeepPlaytime(bool b);
@@ -33,7 +33,7 @@ struct InstanceCopyPrefs {
void enableCopyScreenshots(bool b);
void enableLinkFiles(bool b);
void enableUseHardLinks(bool b);
- void enableLinkWorlds(bool b);
+ void enableDontLinkSaves(bool b);
protected: // data
bool copySaves = true;
@@ -46,5 +46,5 @@ struct InstanceCopyPrefs {
bool copyScreenshots = true;
bool linkFiles = false;
bool useHardLinks = false;
- bool linkWorlds = true;
+ bool dontLinkSaves = false;
};
diff --git a/launcher/InstanceCopyTask.cpp b/launcher/InstanceCopyTask.cpp
index 188d163b..31c6bdca 100644
--- a/launcher/InstanceCopyTask.cpp
+++ b/launcher/InstanceCopyTask.cpp
@@ -11,6 +11,11 @@ InstanceCopyTask::InstanceCopyTask(InstancePtr origInstance, const InstanceCopyP
m_keepPlaytime = prefs.isKeepPlaytimeEnabled();
QString filters = prefs.getSelectedFiltersAsRegex();
+
+ m_useLinks = prefs.isLinkFilesEnabled();
+ m_useHardLinks = prefs.isUseHardLinksEnabled();
+ m_copySaves = prefs.isDontLinkSavesEnabled() && prefs.isCopySavesEnabled();
+
if (!filters.isEmpty())
{
// Set regex filter:
@@ -25,11 +30,71 @@ void InstanceCopyTask::executeTask()
{
setStatus(tr("Copying instance %1").arg(m_origInstance->name()));
- m_copyFuture = QtConcurrent::run(QThreadPool::globalInstance(), [this]{
- FS::copy folderCopy(m_origInstance->instanceRoot(), m_stagingPath);
- folderCopy.followSymlinks(false).matcher(m_matcher.get());
+ auto copySaves = [&](){
+ FS::copy savesCopy(FS::PathCombine(m_origInstance->instanceRoot(), "saves") , FS::PathCombine(m_stagingPath, "saves"));
+ savesCopy.followSymlinks(false);
- return folderCopy();
+ return savesCopy();
+ };
+
+ m_copyFuture = QtConcurrent::run(QThreadPool::globalInstance(), [this, copySaves]{
+ if (m_useLinks) {
+ FS::create_link folderLink(m_origInstance->instanceRoot(), m_stagingPath);
+ folderLink.linkRecursively(true).useHardLinks(m_useHardLinks).matcher(m_matcher.get());
+
+ bool there_were_errors = false;
+
+ if(!folderLink()){
+#if defined Q_OS_WIN32
+ if (!m_useHardLinks) {
+ qDebug() << "EXPECTED: Link failure, Windows requires permissions for symlinks";
+
+ qDebug() << "atempting to run with privelage";
+
+ QEventLoop loop;
+ bool got_priv_results = false;
+
+ connect(&folderLink, &FS::create_link::finishedPrivlaged, this, [&](bool gotResults){
+ if (!gotResults) {
+ qDebug() << "Privlaged run exited without results!";
+ }
+ got_priv_results = gotResults;
+ loop.quit();
+ });
+ folderLink.runPrivlaged();
+
+ loop.exec(); // wait for the finished signal
+
+ for (auto result : folderLink.getResults()) {
+ if (result.err_value != 0) {
+ there_were_errors = true;
+ }
+ }
+
+ if (m_copySaves) {
+ there_were_errors |= !copySaves();
+ }
+
+ return got_priv_results && !there_were_errors;
+ } else {
+ qDebug() << "Link Failed!" << folderLink.getOSError().value() << folderLink.getOSError().message().c_str();
+ }
+#else
+ qDebug() << "Link Failed!" << folderLink.getOSError().value() << folderLink.getOSError().message().c_str();
+#endif return false;
+ }
+
+ if (m_copySaves) {
+ there_were_errors |= !copySaves();
+ }
+
+ return !there_were_errors;
+ } else {
+ FS::copy folderCopy(m_origInstance->instanceRoot(), m_stagingPath);
+ folderCopy.followSymlinks(false).matcher(m_matcher.get());
+
+ return folderCopy();
+ }
});
connect(&m_copyFutureWatcher, &QFutureWatcher::finished, this, &InstanceCopyTask::copyFinished);
connect(&m_copyFutureWatcher, &QFutureWatcher::canceled, this, &InstanceCopyTask::copyAborted);
diff --git a/launcher/InstanceCopyTask.h b/launcher/InstanceCopyTask.h
index 1f29b854..d9651b07 100644
--- a/launcher/InstanceCopyTask.h
+++ b/launcher/InstanceCopyTask.h
@@ -30,4 +30,7 @@ private:
QFutureWatcher m_copyFutureWatcher;
std::unique_ptr m_matcher;
bool m_keepPlaytime;
+ bool m_useLinks = false;
+ bool m_useHardLinks = false;
+ bool m_copySaves = true;
};
diff --git a/launcher/ui/dialogs/CopyInstanceDialog.cpp b/launcher/ui/dialogs/CopyInstanceDialog.cpp
index 981352ae..e477b4b3 100644
--- a/launcher/ui/dialogs/CopyInstanceDialog.cpp
+++ b/launcher/ui/dialogs/CopyInstanceDialog.cpp
@@ -88,7 +88,7 @@ CopyInstanceDialog::CopyInstanceDialog(InstancePtr original, QWidget *parent)
ui->linkFilesGroup->setChecked(m_selectedOptions.isLinkFilesEnabled());
ui->hardLinksCheckbox->setChecked(m_selectedOptions.isUseHardLinksEnabled());
- ui->linkWorldsCheckbox->setChecked(m_selectedOptions.isLinkWorldsEnabled());
+ ui->dontLinkSavesCheckbox->setChecked(m_selectedOptions.isDontLinkSavesEnabled());
}
CopyInstanceDialog::~CopyInstanceDialog()
@@ -179,6 +179,7 @@ void CopyInstanceDialog::on_selectAllCheckbox_stateChanged(int state)
void CopyInstanceDialog::on_copySavesCheckbox_stateChanged(int state)
{
m_selectedOptions.enableCopySaves(state == Qt::Checked);
+ ui->dontLinkSavesCheckbox->setChecked((state == Qt::Checked) && ui->dontLinkSavesCheckbox->isChecked());
updateSelectAllCheckbox();
}
@@ -235,7 +236,7 @@ void CopyInstanceDialog::on_hardLinksCheckbox_stateChanged(int state)
m_selectedOptions.enableUseHardLinks(state == Qt::Checked);
}
-void CopyInstanceDialog::on_linkWorldsCheckbox_stateChanged(int state)
+void CopyInstanceDialog::on_dontLinkSavesCheckbox_stateChanged(int state)
{
- m_selectedOptions.enableLinkWorlds(state == Qt::Checked);
+ m_selectedOptions.enableDontLinkSaves(state == Qt::Checked);
}
diff --git a/launcher/ui/dialogs/CopyInstanceDialog.h b/launcher/ui/dialogs/CopyInstanceDialog.h
index a80faab9..57775925 100644
--- a/launcher/ui/dialogs/CopyInstanceDialog.h
+++ b/launcher/ui/dialogs/CopyInstanceDialog.h
@@ -57,7 +57,7 @@ slots:
void on_copyScreenshotsCheckbox_stateChanged(int state);
void on_linkFilesGroup_toggled(bool checked);
void on_hardLinksCheckbox_stateChanged(int state);
- void on_linkWorldsCheckbox_stateChanged(int state);
+ void on_dontLinkSavesCheckbox_stateChanged(int state);
private:
void checkAllCheckboxes(const bool& b);
diff --git a/launcher/ui/dialogs/CopyInstanceDialog.ui b/launcher/ui/dialogs/CopyInstanceDialog.ui
index e41ad526..d8eb96eb 100644
--- a/launcher/ui/dialogs/CopyInstanceDialog.ui
+++ b/launcher/ui/dialogs/CopyInstanceDialog.ui
@@ -240,17 +240,14 @@
-
-
+
- World save data will be linked and thus shared between instances.
+ If "copy saves" is selected world save data will be copied instead of linked and thus not shared between instances.
- Link worlds
+ Don't link saves
- true
-
-
false
From 1bed7754e0bf3c009a38818963fe8d0832b36852 Mon Sep 17 00:00:00 2001
From: Rachel Powers <508861+Ryex@users.noreply.github.com>
Date: Wed, 8 Feb 2023 18:39:17 -0700
Subject: [PATCH 009/104] feat(symlinks): make recursive links explicit
Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com>
---
launcher/InstanceCopyPrefs.cpp | 10 +++++++++
launcher/InstanceCopyPrefs.h | 3 +++
launcher/InstanceCopyTask.cpp | 10 +++++----
launcher/InstanceCopyTask.h | 3 ++-
launcher/ui/dialogs/CopyInstanceDialog.cpp | 14 ++++++++++++
launcher/ui/dialogs/CopyInstanceDialog.h | 1 +
launcher/ui/dialogs/CopyInstanceDialog.ui | 25 ++++++++++++++++++++--
7 files changed, 59 insertions(+), 7 deletions(-)
diff --git a/launcher/InstanceCopyPrefs.cpp b/launcher/InstanceCopyPrefs.cpp
index e363d4c6..59825ced 100644
--- a/launcher/InstanceCopyPrefs.cpp
+++ b/launcher/InstanceCopyPrefs.cpp
@@ -103,6 +103,11 @@ bool InstanceCopyPrefs::isUseHardLinksEnabled() const
return useHardLinks;
}
+bool InstanceCopyPrefs::isLinkRecursivelyEnabled() const
+{
+ return linkRecursively;
+}
+
bool InstanceCopyPrefs::isDontLinkSavesEnabled() const
{
return dontLinkSaves;
@@ -154,6 +159,11 @@ void InstanceCopyPrefs::enableLinkFiles(bool b)
linkFiles = b;
}
+void InstanceCopyPrefs::enableLinkRecursively(bool b)
+{
+ linkRecursively = b;
+}
+
void InstanceCopyPrefs::enableUseHardLinks(bool b)
{
useHardLinks = b;
diff --git a/launcher/InstanceCopyPrefs.h b/launcher/InstanceCopyPrefs.h
index 61719a06..9fc9dcab 100644
--- a/launcher/InstanceCopyPrefs.h
+++ b/launcher/InstanceCopyPrefs.h
@@ -20,6 +20,7 @@ struct InstanceCopyPrefs {
[[nodiscard]] bool isCopyModsEnabled() const;
[[nodiscard]] bool isCopyScreenshotsEnabled() const;
[[nodiscard]] bool isLinkFilesEnabled() const;
+ [[nodiscard]] bool isLinkRecursivelyEnabled() const;
[[nodiscard]] bool isUseHardLinksEnabled() const;
[[nodiscard]] bool isDontLinkSavesEnabled() const;
// Setters
@@ -32,6 +33,7 @@ struct InstanceCopyPrefs {
void enableCopyMods(bool b);
void enableCopyScreenshots(bool b);
void enableLinkFiles(bool b);
+ void enableLinkRecursively(bool b);
void enableUseHardLinks(bool b);
void enableDontLinkSaves(bool b);
@@ -45,6 +47,7 @@ struct InstanceCopyPrefs {
bool copyMods = true;
bool copyScreenshots = true;
bool linkFiles = false;
+ bool linkRecursively = false;
bool useHardLinks = false;
bool dontLinkSaves = false;
};
diff --git a/launcher/InstanceCopyTask.cpp b/launcher/InstanceCopyTask.cpp
index 31c6bdca..81502d89 100644
--- a/launcher/InstanceCopyTask.cpp
+++ b/launcher/InstanceCopyTask.cpp
@@ -12,9 +12,11 @@ InstanceCopyTask::InstanceCopyTask(InstancePtr origInstance, const InstanceCopyP
QString filters = prefs.getSelectedFiltersAsRegex();
+
m_useLinks = prefs.isLinkFilesEnabled();
- m_useHardLinks = prefs.isUseHardLinksEnabled();
- m_copySaves = prefs.isDontLinkSavesEnabled() && prefs.isCopySavesEnabled();
+ m_linkRecursively = prefs.isLinkRecursivelyEnabled();
+ m_useHardLinks = prefs.isLinkRecursivelyEnabled() && prefs.isUseHardLinksEnabled();
+ m_copySaves = prefs.isLinkRecursivelyEnabled() && prefs.isDontLinkSavesEnabled() && prefs.isCopySavesEnabled();
if (!filters.isEmpty())
{
@@ -32,7 +34,7 @@ void InstanceCopyTask::executeTask()
auto copySaves = [&](){
FS::copy savesCopy(FS::PathCombine(m_origInstance->instanceRoot(), "saves") , FS::PathCombine(m_stagingPath, "saves"));
- savesCopy.followSymlinks(false);
+ savesCopy.followSymlinks(true);
return savesCopy();
};
@@ -40,7 +42,7 @@ void InstanceCopyTask::executeTask()
m_copyFuture = QtConcurrent::run(QThreadPool::globalInstance(), [this, copySaves]{
if (m_useLinks) {
FS::create_link folderLink(m_origInstance->instanceRoot(), m_stagingPath);
- folderLink.linkRecursively(true).useHardLinks(m_useHardLinks).matcher(m_matcher.get());
+ folderLink.linkRecursively(m_linkRecursively).useHardLinks(m_useHardLinks).matcher(m_matcher.get());
bool there_were_errors = false;
diff --git a/launcher/InstanceCopyTask.h b/launcher/InstanceCopyTask.h
index d9651b07..3dce1662 100644
--- a/launcher/InstanceCopyTask.h
+++ b/launcher/InstanceCopyTask.h
@@ -32,5 +32,6 @@ private:
bool m_keepPlaytime;
bool m_useLinks = false;
bool m_useHardLinks = false;
- bool m_copySaves = true;
+ bool m_copySaves = false;
+ bool m_linkRecursively = false;
};
diff --git a/launcher/ui/dialogs/CopyInstanceDialog.cpp b/launcher/ui/dialogs/CopyInstanceDialog.cpp
index e477b4b3..55962c5a 100644
--- a/launcher/ui/dialogs/CopyInstanceDialog.cpp
+++ b/launcher/ui/dialogs/CopyInstanceDialog.cpp
@@ -87,6 +87,7 @@ CopyInstanceDialog::CopyInstanceDialog(InstancePtr original, QWidget *parent)
ui->copyScreenshotsCheckbox->setChecked(m_selectedOptions.isCopyScreenshotsEnabled());
ui->linkFilesGroup->setChecked(m_selectedOptions.isLinkFilesEnabled());
+ ui->recursiveLinkCheckbox->setChecked(m_selectedOptions.isLinkRecursivelyEnabled());
ui->hardLinksCheckbox->setChecked(m_selectedOptions.isUseHardLinksEnabled());
ui->dontLinkSavesCheckbox->setChecked(m_selectedOptions.isDontLinkSavesEnabled());
}
@@ -231,9 +232,22 @@ void CopyInstanceDialog::on_linkFilesGroup_toggled(bool checked)
m_selectedOptions.enableLinkFiles(checked);
}
+void CopyInstanceDialog::on_recursiveLinkCheckbox_stateChanged(int state)
+{
+ m_selectedOptions.enableLinkRecursively(state == Qt::Checked);
+ if (state != Qt::Checked) {
+ ui->hardLinksCheckbox->setChecked(false);
+ ui->dontLinkSavesCheckbox->setChecked(false);
+ }
+
+}
+
void CopyInstanceDialog::on_hardLinksCheckbox_stateChanged(int state)
{
m_selectedOptions.enableUseHardLinks(state == Qt::Checked);
+ if (state == Qt::Checked && !ui->recursiveLinkCheckbox->isChecked()) {
+ ui->recursiveLinkCheckbox->setChecked(true);
+ }
}
void CopyInstanceDialog::on_dontLinkSavesCheckbox_stateChanged(int state)
diff --git a/launcher/ui/dialogs/CopyInstanceDialog.h b/launcher/ui/dialogs/CopyInstanceDialog.h
index 57775925..2fc6f38a 100644
--- a/launcher/ui/dialogs/CopyInstanceDialog.h
+++ b/launcher/ui/dialogs/CopyInstanceDialog.h
@@ -56,6 +56,7 @@ slots:
void on_copyModsCheckbox_stateChanged(int state);
void on_copyScreenshotsCheckbox_stateChanged(int state);
void on_linkFilesGroup_toggled(bool checked);
+ void on_recursiveLinkCheckbox_stateChanged(int state);
void on_hardLinksCheckbox_stateChanged(int state);
void on_dontLinkSavesCheckbox_stateChanged(int state);
diff --git a/launcher/ui/dialogs/CopyInstanceDialog.ui b/launcher/ui/dialogs/CopyInstanceDialog.ui
index d8eb96eb..8df0d3db 100644
--- a/launcher/ui/dialogs/CopyInstanceDialog.ui
+++ b/launcher/ui/dialogs/CopyInstanceDialog.ui
@@ -209,6 +209,16 @@
+ -
+
+
+ Advanced Copy Options
+
+
+ Qt::AlignCenter
+
+
+
-
-
@@ -229,8 +239,18 @@
false
+
-
+
+
+ Link files recursively
+
+
+
-
+
+ false
+
Use hard links instead of symbolic links
@@ -242,7 +262,7 @@
-
- If "copy saves" is selected world save data will be copied instead of linked and thus not shared between instances.
+ If "copy saves" is selected world save data will be copied instead of linked and thus not shared between instances.
Don't link saves
@@ -283,8 +303,9 @@
copyResPacksCheckbox
copyModsCheckbox
linkFilesGroup
+ recursiveLinkCheckbox
hardLinksCheckbox
- linkWorldsCheckbox
+ dontLinkSavesCheckbox
From c9105e525e175ee8181ab0a6998d0e21526f116d Mon Sep 17 00:00:00 2001
From: Rachel Powers <508861+Ryex@users.noreply.github.com>
Date: Wed, 8 Feb 2023 18:52:50 -0700
Subject: [PATCH 010/104] fix: follow symlinks when exporting
Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com>
---
launcher/FileSystem.h | 2 +-
launcher/MMCZip.cpp | 9 ++++++---
launcher/MMCZip.h | 6 ++++--
launcher/ui/dialogs/ExportInstanceDialog.cpp | 5 ++++-
4 files changed, 15 insertions(+), 7 deletions(-)
diff --git a/launcher/FileSystem.h b/launcher/FileSystem.h
index d79096e6..782a2f40 100644
--- a/launcher/FileSystem.h
+++ b/launcher/FileSystem.h
@@ -144,7 +144,7 @@ class ExternalLinkFileProcess : public QThread {
Q_OBJECT
public:
ExternalLinkFileProcess(QString server, bool useHardLinks, QObject* parent = nullptr)
- : QThread(parent), m_server(server), m_useHardLinks(useHardLinks)
+ : QThread(parent), m_useHardLinks(useHardLinks), m_server(server)
{}
void run() override
diff --git a/launcher/MMCZip.cpp b/launcher/MMCZip.cpp
index 1eda43fe..b4b663c1 100644
--- a/launcher/MMCZip.cpp
+++ b/launcher/MMCZip.cpp
@@ -94,20 +94,23 @@ bool MMCZip::mergeZipFiles(QuaZip *into, QFileInfo from, QSet &containe
return true;
}
-bool MMCZip::compressDirFiles(QuaZip *zip, QString dir, QFileInfoList files)
+bool MMCZip::compressDirFiles(QuaZip *zip, QString dir, QFileInfoList files, bool followSymlinks)
{
QDir directory(dir);
if (!directory.exists()) return false;
for (auto e : files) {
auto filePath = directory.relativeFilePath(e.absoluteFilePath());
- if( !JlCompress::compressFile(zip, e.absoluteFilePath(), filePath)) return false;
+ auto srcPath = e.absoluteFilePath();
+ if (followSymlinks)
+ srcPath = e.canonicalFilePath();
+ if( !JlCompress::compressFile(zip, srcPath, filePath)) return false;
}
return true;
}
-bool MMCZip::compressDirFiles(QString fileCompressed, QString dir, QFileInfoList files)
+bool MMCZip::compressDirFiles(QString fileCompressed, QString dir, QFileInfoList files, bool followSymlinks)
{
QuaZip zip(fileCompressed);
QDir().mkpath(QFileInfo(fileCompressed).absolutePath());
diff --git a/launcher/MMCZip.h b/launcher/MMCZip.h
index 81f9cb90..2a78f830 100644
--- a/launcher/MMCZip.h
+++ b/launcher/MMCZip.h
@@ -59,18 +59,20 @@ namespace MMCZip
* \param zip target archive
* \param dir directory that will be compressed (to compress with relative paths)
* \param files list of files to compress
+ * \param followSymlinks should follow symlinks when compressing file data
* \return true for success or false for failure
*/
- bool compressDirFiles(QuaZip *zip, QString dir, QFileInfoList files);
+ bool compressDirFiles(QuaZip *zip, QString dir, QFileInfoList files, bool followSymlinks = false);
/**
* Compress directory, by providing a list of files to compress
* \param fileCompressed target archive file
* \param dir directory that will be compressed (to compress with relative paths)
* \param files list of files to compress
+ * \param followSymlinks should follow symlinks when compressing file data
* \return true for success or false for failure
*/
- bool compressDirFiles(QString fileCompressed, QString dir, QFileInfoList files);
+ bool compressDirFiles(QString fileCompressed, QString dir, QFileInfoList files, bool followSymlinks = false);
/**
* take a source jar, add mods to it, resulting in target jar
diff --git a/launcher/ui/dialogs/ExportInstanceDialog.cpp b/launcher/ui/dialogs/ExportInstanceDialog.cpp
index f13e36e8..07ec3c70 100644
--- a/launcher/ui/dialogs/ExportInstanceDialog.cpp
+++ b/launcher/ui/dialogs/ExportInstanceDialog.cpp
@@ -45,6 +45,8 @@
#include
#include
#include
+#include
+
#include "StringUtils.h"
#include "SeparatorPrefixTree.h"
#include "Application.h"
@@ -429,7 +431,8 @@ bool ExportInstanceDialog::doExport()
QMessageBox::warning(this, tr("Error"), tr("Unable to export instance"));
return false;
}
- if (!MMCZip::compressDirFiles(output, m_instance->instanceRoot(), files))
+
+ if (!MMCZip::compressDirFiles(output, m_instance->instanceRoot(), files, true))
{
QMessageBox::warning(this, tr("Error"), tr("Unable to export instance"));
return false;
From c5bbe42b57075a4b428d0be1c1ca9f51701a1a7c Mon Sep 17 00:00:00 2001
From: Rachel Powers <508861+Ryex@users.noreply.github.com>
Date: Wed, 8 Feb 2023 23:42:13 -0700
Subject: [PATCH 011/104] feat: reflink / Clone support!
Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com>
---
launcher/FileSystem.cpp | 206 ++++++++++++++++++++++++++++++++++++++++
launcher/FileSystem.h | 141 +++++++++++++++++++++++++++
2 files changed, 347 insertions(+)
diff --git a/launcher/FileSystem.cpp b/launcher/FileSystem.cpp
index c94770ee..7b7fc80b 100644
--- a/launcher/FileSystem.cpp
+++ b/launcher/FileSystem.cpp
@@ -3,6 +3,7 @@
* Prism Launcher - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu
* Copyright (C) 2022 TheKodeToad
+ * Copyright (C) 2022 Rachel Powers <508861+Ryex@users.noreply.github.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -35,6 +36,9 @@
*/
#include "FileSystem.h"
+#include
+#include
+#include
#include "BuildConfig.h"
@@ -48,6 +52,7 @@
#include
#include
#include
+#include
#include "DesktopServices.h"
#include "StringUtils.h"
@@ -91,6 +96,18 @@ namespace fs = std::filesystem;
namespace fs = ghc::filesystem;
#endif
+
+// clone
+#if defined(Q_OS_LINUX)
+#include
+#include /* Definition of FICLONE* constants */
+#include
+#include
+#elif defined(Q_OS_MACOS) || defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD)
+#include
+#include
+#endif
+
namespace FS {
void ensureExists(const QDir& dir)
@@ -831,4 +848,193 @@ bool overrideFolder(QString overwritten_path, QString override_path)
return err.value() == 0;
}
+
+/**
+ * @brief colect information about the filesystem under a file
+ *
+ */
+FilesystemInfo statFS(QString path)
+{
+
+ FilesystemInfo info;
+
+ QStorageInfo storage_info(path);
+
+ QString fsTypeName = QString::fromStdString(storage_info.fileSystemType().toStdString());
+
+ for (auto fs_type_pair : s_filesystem_type_names_inverse.toStdMap()) {
+ auto fs_type_name = fs_type_pair.first;
+ auto fs_type = fs_type_pair.second;
+
+ if(fsTypeName.contains(fs_type_name.toLower())) {
+ info.fsType = fs_type;
+ break;
+ }
+ }
+
+ info.blockSize = storage_info.blockSize();
+ info.bytesAvailable = storage_info.bytesAvailable();
+ info.bytesFree = storage_info.bytesFree();
+ info.bytesTotal = storage_info.bytesTotal();
+
+ info.name = storage_info.name();
+ info.rootPath = storage_info.rootPath();
+
+ return info;
+}
+
+/**
+ * @brief if the Filesystem is reflink/clone capable
+ *
+ */
+bool canCloneOnFS(const QString& path)
+{
+ FilesystemInfo info = statFS(path);
+ return canCloneOnFS(info);
+}
+bool canCloneOnFS(const FilesystemInfo& info)
+{
+ return canCloneOnFS(info.fsType);
+}
+bool canCloneOnFS(FilesystemType type)
+{
+ return s_clone_filesystems.contains(type);
+}
+
+/**
+ * @brief if the Filesystem is reflink/clone capable and both paths are on the same device
+ *
+ */
+bool canClone(const QString& src, const QString& dst)
+{
+ auto srcVInfo = statFS(src);
+ auto dstVInfo = statFS(dst);
+
+ bool sameDevice = srcVInfo.rootPath == dstVInfo.rootPath;
+
+ return sameDevice && canCloneOnFS(srcVInfo) && canCloneOnFS(dstVInfo);
+}
+
+/**
+ * @brief reflink/clones a directory and it's contents from src to dest
+ * @param offset subdirectory form src to copy to dest
+ * @return if there was an error during the filecopy
+ */
+bool clone::operator()(const QString& offset, bool dryRun)
+{
+
+ if (!canClone(m_src.absolutePath(), m_dst.absolutePath())) {
+ qWarning() << "Can not clone: not same device or not clone/reflink filesystem";
+ qDebug() << "Source path:" << m_src.absolutePath();
+ qDebug() << "Destination path:" << m_dst.absolutePath();
+ emit cloneFailed(m_src.absolutePath(), m_dst.absolutePath());
+ return false;
+ }
+
+ m_cloned = 0; // reset counter
+
+ auto src = PathCombine(m_src.absolutePath(), offset);
+ auto dst = PathCombine(m_dst.absolutePath(), offset);
+
+ std::error_code err;
+
+ // Function that'll do the actual cloneing
+ auto cloneFile = [&](QString src_path, QString relative_dst_path) {
+ if (m_matcher && (m_matcher->matches(relative_dst_path) != m_whitelist))
+ return;
+
+ auto dst_path = PathCombine(dst, relative_dst_path);
+ if (!dryRun) {
+ ensureFilePathExists(dst_path);
+ clone_file(src_path, dst_path, err);
+ }
+ if (err) {
+ qWarning() << "Failed to clone files:" << QString::fromStdString(err.message());
+ qDebug() << "Source file:" << src_path;
+ qDebug() << "Destination file:" << dst_path;
+ }
+ m_cloned++;
+ emit fileCloned(src_path, dst_path);
+ };
+
+ // We can't use copy_opts::recursive because we need to take into account the
+ // blacklisted paths, so we iterate over the source directory, and if there's no blacklist
+ // match, we copy the file.
+ QDir src_dir(src);
+ QDirIterator source_it(src, QDir::Filter::Files | QDir::Filter::Hidden, QDirIterator::Subdirectories);
+
+ while (source_it.hasNext()) {
+ auto src_path = source_it.next();
+ auto relative_path = src_dir.relativeFilePath(src_path);
+
+ cloneFile(src_path, relative_path);
+ }
+
+ // If the root src is not a directory, the previous iterator won't run.
+ if (!fs::is_directory(StringUtils::toStdString(src)))
+ cloneFile(src, "");
+
+ return err.value() == 0;
+}
+
+/**
+ * @brief clone/reflink file from src to dst
+ *
+ */
+bool clone_file(const QString& src, const QString& dst, std::error_code& ec)
+{
+ auto src_path = StringUtils::toStdString(QFileInfo(src).absoluteFilePath());
+ auto dst_path = StringUtils::toStdString(QFileInfo(dst).absoluteFilePath());
+
+#if defined(Q_OS_WIN)
+ qWarning("clone/reflink not supported on windows!");
+ ec = std::make_error_code(std::errc::not_supported);
+ return false;
+#elif defined(Q_OS_LINUX)
+
+ // https://man7.org/linux/man-pages/man2/ioctl_ficlone.2.html
+
+ int src_fd = open(src_path.c_str(), O_RDONLY);
+ if(!src_fd) {
+ qWarning() << "Failed to open file:" << src_path.c_str();
+ qDebug() << "Error:" << strerror(errno);
+ ec = std::make_error_code(static_cast(errno));
+ return false;
+ }
+ int dst_fd = open(dst_path.c_str(), O_WRONLY | O_TRUNC);
+ if(!dst_fd) {
+ qWarning() << "Failed to open file:" << dst_path.c_str();
+ qDebug() << "Error:" << strerror(errno);
+ ec = std::make_error_code(static_cast(errno));
+ return false;
+ }
+ // attempt to clone
+ if(!ioctl(dst_fd, FICLONE, src_fd)){
+ qWarning() << "Failed to clone file:" << src_path.c_str() << "to" << dst_path.c_str();
+ qDebug() << "Error:" << strerror(errno);
+ ec = std::make_error_code(static_cast(errno));
+ return false;
+ }
+
+#elif defined(Q_OS_MACOS) || defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD)
+ // TODO: use clonefile
+ // clonefile(const char * src, const char * dst, int flags);
+ // https://www.manpagez.com/man/2/clonefile/
+
+ if (!clonefile(src_path.c_str(), dst_path.c_str(), 0)) {
+ qWarning() << "Failed to clone file:" << src_path.c_str() << "to" << dst_path.c_str();
+ qDebug() << "Error:" << strerror(errno);
+ ec = std::make_error_code(static_cast(errno));
+ return false;
+ }
+
+#else
+ qWarning("clone/reflink not supported! unknown OS");
+ ec = std::make_error_code(std::errc::not_supported);
+ return false;
+#endif
+
+ return true;
+}
+
}
diff --git a/launcher/FileSystem.h b/launcher/FileSystem.h
index 782a2f40..531036dd 100644
--- a/launcher/FileSystem.h
+++ b/launcher/FileSystem.h
@@ -3,6 +3,7 @@
* Prism Launcher - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu
* Copyright (C) 2022 TheKodeToad
+ * Copyright (C) 2022 Rachel Powers <508861+Ryex@users.noreply.github.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -313,4 +314,144 @@ bool overrideFolder(QString overwritten_path, QString override_path);
* Creates a shortcut to the specified target file at the specified destination path.
*/
bool createShortcut(QString destination, QString target, QStringList args, QString name, QString icon);
+
+enum class FilesystemType {
+ FAT,
+ NTFS,
+ EXT,
+ EXT_2_OLD,
+ EXT_2_3_4,
+ XFS,
+ BTRFS,
+ NFS,
+ ZFS,
+ APFS,
+ HFS,
+ HFSPLUS,
+ HFSX,
+ UNKNOWN
+};
+
+static const QMap s_filesystem_type_names = {
+ {FilesystemType::FAT, QString("FAT")},
+ {FilesystemType::NTFS, QString("NTFS")},
+ {FilesystemType::EXT, QString("EXT")},
+ {FilesystemType::EXT_2_OLD, QString("EXT2_OLD")},
+ {FilesystemType::EXT_2_3_4, QString("EXT2/3/4")},
+ {FilesystemType::XFS, QString("XFS")},
+ {FilesystemType::BTRFS, QString("BTRFS")},
+ {FilesystemType::NFS, QString("NFS")},
+ {FilesystemType::ZFS, QString("ZFS")},
+ {FilesystemType::APFS, QString("APFS")},
+ {FilesystemType::HFS, QString("HFS")},
+ {FilesystemType::HFSPLUS, QString("HFSPLUS")},
+ {FilesystemType::HFSX, QString("HFSX")},
+ {FilesystemType::UNKNOWN, QString("UNKNOWN")}
+};
+
+static const QMap s_filesystem_type_names_inverse = {
+ {QString("FAT"), FilesystemType::FAT},
+ {QString("NTFS"), FilesystemType::NTFS},
+ {QString("EXT2_OLD"), FilesystemType::EXT_2_OLD},
+ {QString("EXT2"), FilesystemType::EXT_2_3_4},
+ {QString("EXT3"), FilesystemType::EXT_2_3_4},
+ {QString("EXT4"), FilesystemType::EXT_2_3_4},
+ {QString("EXT"), FilesystemType::EXT},
+ {QString("XFS"), FilesystemType::XFS},
+ {QString("BTRFS"), FilesystemType::BTRFS},
+ {QString("NFS"), FilesystemType::NFS},
+ {QString("ZFS"), FilesystemType::ZFS},
+ {QString("APFS"), FilesystemType::APFS},
+ {QString("HFSPLUS"), FilesystemType::HFSPLUS},
+ {QString("HFSX"), FilesystemType::HFSX},
+ {QString("HFS"), FilesystemType::HFS},
+ {QString("UNKNOWN"), FilesystemType::UNKNOWN}
+};
+
+inline QString getFilesystemTypeName(FilesystemType type) {
+ return s_filesystem_type_names.constFind(type).value();
+}
+
+struct FilesystemInfo {
+ FilesystemType fsType = FilesystemType::UNKNOWN;
+ int blockSize;
+ qint64 bytesAvailable;
+ qint64 bytesFree;
+ qint64 bytesTotal;
+ QString name;
+ QString rootPath;
+};
+
+/**
+ * @brief colect information about the filesystem under a file
+ *
+ */
+FilesystemInfo statFS(QString path);
+
+
+static const QList s_clone_filesystems = {
+ FilesystemType::BTRFS, FilesystemType::APFS, FilesystemType::ZFS, FilesystemType::XFS
+};
+
+/**
+ * @brief if the Filesystem is reflink/clone capable
+ *
+ */
+bool canCloneOnFS(const QString& path);
+bool canCloneOnFS(const FilesystemInfo& info);
+bool canCloneOnFS(FilesystemType type);
+
+/**
+ * @brief if the Filesystem is reflink/clone capable and both are on the same device
+ *
+ */
+bool canClone(const QString& src, const QString& dst);
+
+/**
+ * @brief Copies a directory and it's contents from src to dest
+ */
+class clone : public QObject {
+ Q_OBJECT
+ public:
+ clone(const QString& src, const QString& dst, QObject* parent = nullptr) : QObject(parent)
+ {
+ m_src.setPath(src);
+ m_dst.setPath(dst);
+ }
+ clone& matcher(const IPathMatcher* filter)
+ {
+ m_matcher = filter;
+ return *this;
+ }
+ clone& whitelist(bool whitelist)
+ {
+ m_whitelist = whitelist;
+ return *this;
+ }
+
+ bool operator()(bool dryRun = false) { return operator()(QString(), dryRun); }
+
+ int totalCloned() { return m_cloned; }
+
+ signals:
+ void fileCloned(const QString& src, const QString& dst);
+ void cloneFailed(const QString& src, const QString& dst);
+
+ private:
+ bool operator()(const QString& offset, bool dryRun = false);
+
+ private:
+ const IPathMatcher* m_matcher = nullptr;
+ bool m_whitelist = false;
+ QDir m_src;
+ QDir m_dst;
+ int m_cloned;
+};
+
+/**
+ * @brief clone/reflink file from src to dst
+ *
+ */
+bool clone_file(const QString& src, const QString& dst, std::error_code& ec);
+
}
From 397e7f036339b09569317300423261f2b37d6119 Mon Sep 17 00:00:00 2001
From: Rachel Powers <508861+Ryex@users.noreply.github.com>
Date: Thu, 9 Feb 2023 02:02:40 -0700
Subject: [PATCH 012/104] feat(reflink): hook up relink / clone on the copy
dialog
Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com>
---
launcher/FileSystem.cpp | 26 ++++++++++---
launcher/FileSystem.h | 3 ++
launcher/InstanceCopyPrefs.cpp | 10 +++++
launcher/InstanceCopyPrefs.h | 3 ++
launcher/InstanceCopyTask.cpp | 11 +++++-
launcher/InstanceCopyTask.h | 1 +
launcher/ui/dialogs/CopyInstanceDialog.cpp | 28 +++++++++++++-
launcher/ui/dialogs/CopyInstanceDialog.h | 5 +++
launcher/ui/dialogs/CopyInstanceDialog.ui | 43 ++++++++++++++++++++--
9 files changed, 119 insertions(+), 11 deletions(-)
diff --git a/launcher/FileSystem.cpp b/launcher/FileSystem.cpp
index 7b7fc80b..fd09842f 100644
--- a/launcher/FileSystem.cpp
+++ b/launcher/FileSystem.cpp
@@ -103,6 +103,7 @@ namespace fs = ghc::filesystem;
#include /* Definition of FICLONE* constants */
#include
#include
+#include
#elif defined(Q_OS_MACOS) || defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD)
#include
#include
@@ -880,6 +881,9 @@ FilesystemInfo statFS(QString path)
info.name = storage_info.name();
info.rootPath = storage_info.rootPath();
+ qDebug() << "Pulling filesystem info for" << info.rootPath;
+ qDebug() << "Filesystem: " << fsTypeName << "detected" << getFilesystemTypeName(info.fsType);
+
return info;
}
@@ -995,33 +999,45 @@ bool clone_file(const QString& src, const QString& dst, std::error_code& ec)
// https://man7.org/linux/man-pages/man2/ioctl_ficlone.2.html
int src_fd = open(src_path.c_str(), O_RDONLY);
- if(!src_fd) {
+ if(src_fd == -1) {
qWarning() << "Failed to open file:" << src_path.c_str();
qDebug() << "Error:" << strerror(errno);
ec = std::make_error_code(static_cast(errno));
return false;
}
- int dst_fd = open(dst_path.c_str(), O_WRONLY | O_TRUNC);
- if(!dst_fd) {
+ int dst_fd = open(dst_path.c_str(), O_CREAT | O_WRONLY | O_TRUNC, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);
+ if(dst_fd == -1) {
qWarning() << "Failed to open file:" << dst_path.c_str();
qDebug() << "Error:" << strerror(errno);
ec = std::make_error_code(static_cast(errno));
+ close(src_fd);
return false;
}
// attempt to clone
- if(!ioctl(dst_fd, FICLONE, src_fd)){
+ if(ioctl(dst_fd, FICLONE, src_fd) == -1){
qWarning() << "Failed to clone file:" << src_path.c_str() << "to" << dst_path.c_str();
qDebug() << "Error:" << strerror(errno);
ec = std::make_error_code(static_cast(errno));
+ close(src_fd);
+ close(dst_fd);
return false;
}
+ if(close(src_fd)) {
+ qWarning() << "Failed to close file:" << src_path.c_str();
+ qDebug() << "Error:" << strerror(errno);
+ }
+ if(close(dst_fd)) {
+ qWarning() << "Failed to close file:" << dst_path.c_str();
+ qDebug() << "Error:" << strerror(errno);
+ }
#elif defined(Q_OS_MACOS) || defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD)
// TODO: use clonefile
// clonefile(const char * src, const char * dst, int flags);
// https://www.manpagez.com/man/2/clonefile/
- if (!clonefile(src_path.c_str(), dst_path.c_str(), 0)) {
+ qDebug() << "attempting file clone via clonefile" << src << "to" << dst;
+ if (clonefile(src_path.c_str(), dst_path.c_str(), 0) == -1) {
qWarning() << "Failed to clone file:" << src_path.c_str() << "to" << dst_path.c_str();
qDebug() << "Error:" << strerror(errno);
ec = std::make_error_code(static_cast(errno));
diff --git a/launcher/FileSystem.h b/launcher/FileSystem.h
index 531036dd..aa28de93 100644
--- a/launcher/FileSystem.h
+++ b/launcher/FileSystem.h
@@ -329,6 +329,7 @@ enum class FilesystemType {
HFS,
HFSPLUS,
HFSX,
+ FUSEBLK,
UNKNOWN
};
@@ -346,6 +347,7 @@ static const QMap s_filesystem_type_names = {
{FilesystemType::HFS, QString("HFS")},
{FilesystemType::HFSPLUS, QString("HFSPLUS")},
{FilesystemType::HFSX, QString("HFSX")},
+ {FilesystemType::FUSEBLK, QString("FUSEBLK")},
{FilesystemType::UNKNOWN, QString("UNKNOWN")}
};
@@ -365,6 +367,7 @@ static const QMap s_filesystem_type_names_inverse = {
{QString("HFSPLUS"), FilesystemType::HFSPLUS},
{QString("HFSX"), FilesystemType::HFSX},
{QString("HFS"), FilesystemType::HFS},
+ {QString("FUSEBLK"), FilesystemType::FUSEBLK},
{QString("UNKNOWN"), FilesystemType::UNKNOWN}
};
diff --git a/launcher/InstanceCopyPrefs.cpp b/launcher/InstanceCopyPrefs.cpp
index 59825ced..f2aa5e69 100644
--- a/launcher/InstanceCopyPrefs.cpp
+++ b/launcher/InstanceCopyPrefs.cpp
@@ -113,6 +113,11 @@ bool InstanceCopyPrefs::isDontLinkSavesEnabled() const
return dontLinkSaves;
}
+bool InstanceCopyPrefs::isUseCloneEnabled() const
+{
+ return useClone;
+}
+
// ======= Setters =======
void InstanceCopyPrefs::enableCopySaves(bool b)
{
@@ -173,3 +178,8 @@ void InstanceCopyPrefs::enableDontLinkSaves(bool b)
{
dontLinkSaves = b;
}
+
+void InstanceCopyPrefs::enableUseClone(bool b)
+{
+ useClone = b;
+}
\ No newline at end of file
diff --git a/launcher/InstanceCopyPrefs.h b/launcher/InstanceCopyPrefs.h
index 9fc9dcab..583fdd4c 100644
--- a/launcher/InstanceCopyPrefs.h
+++ b/launcher/InstanceCopyPrefs.h
@@ -23,6 +23,7 @@ struct InstanceCopyPrefs {
[[nodiscard]] bool isLinkRecursivelyEnabled() const;
[[nodiscard]] bool isUseHardLinksEnabled() const;
[[nodiscard]] bool isDontLinkSavesEnabled() const;
+ [[nodiscard]] bool isUseCloneEnabled() const;
// Setters
void enableCopySaves(bool b);
void enableKeepPlaytime(bool b);
@@ -36,6 +37,7 @@ struct InstanceCopyPrefs {
void enableLinkRecursively(bool b);
void enableUseHardLinks(bool b);
void enableDontLinkSaves(bool b);
+ void enableUseClone(bool b);
protected: // data
bool copySaves = true;
@@ -50,4 +52,5 @@ struct InstanceCopyPrefs {
bool linkRecursively = false;
bool useHardLinks = false;
bool dontLinkSaves = false;
+ bool useClone = false;
};
diff --git a/launcher/InstanceCopyTask.cpp b/launcher/InstanceCopyTask.cpp
index 81502d89..b3ea54b0 100644
--- a/launcher/InstanceCopyTask.cpp
+++ b/launcher/InstanceCopyTask.cpp
@@ -17,6 +17,7 @@ InstanceCopyTask::InstanceCopyTask(InstancePtr origInstance, const InstanceCopyP
m_linkRecursively = prefs.isLinkRecursivelyEnabled();
m_useHardLinks = prefs.isLinkRecursivelyEnabled() && prefs.isUseHardLinksEnabled();
m_copySaves = prefs.isLinkRecursivelyEnabled() && prefs.isDontLinkSavesEnabled() && prefs.isCopySavesEnabled();
+ m_useClone = prefs.isUseCloneEnabled();
if (!filters.isEmpty())
{
@@ -40,7 +41,12 @@ void InstanceCopyTask::executeTask()
};
m_copyFuture = QtConcurrent::run(QThreadPool::globalInstance(), [this, copySaves]{
- if (m_useLinks) {
+ if (m_useClone) {
+ FS::clone folderClone(m_origInstance->instanceRoot(), m_stagingPath);
+ folderClone.matcher(m_matcher.get());
+
+ return folderClone();
+ } else if (m_useLinks) {
FS::create_link folderLink(m_origInstance->instanceRoot(), m_stagingPath);
folderLink.linkRecursively(m_linkRecursively).useHardLinks(m_useHardLinks).matcher(m_matcher.get());
@@ -83,7 +89,8 @@ void InstanceCopyTask::executeTask()
}
#else
qDebug() << "Link Failed!" << folderLink.getOSError().value() << folderLink.getOSError().message().c_str();
-#endif return false;
+#endif
+ return false;
}
if (m_copySaves) {
diff --git a/launcher/InstanceCopyTask.h b/launcher/InstanceCopyTask.h
index 3dce1662..aea9d99a 100644
--- a/launcher/InstanceCopyTask.h
+++ b/launcher/InstanceCopyTask.h
@@ -34,4 +34,5 @@ private:
bool m_useHardLinks = false;
bool m_copySaves = false;
bool m_linkRecursively = false;
+ bool m_useClone = false;
};
diff --git a/launcher/ui/dialogs/CopyInstanceDialog.cpp b/launcher/ui/dialogs/CopyInstanceDialog.cpp
index 55962c5a..c51bc067 100644
--- a/launcher/ui/dialogs/CopyInstanceDialog.cpp
+++ b/launcher/ui/dialogs/CopyInstanceDialog.cpp
@@ -46,6 +46,7 @@
#include "icons/IconList.h"
#include "BaseInstance.h"
#include "InstanceList.h"
+#include "FileSystem.h"
CopyInstanceDialog::CopyInstanceDialog(InstancePtr original, QWidget *parent)
:QDialog(parent), ui(new Ui::CopyInstanceDialog), m_original(original)
@@ -85,11 +86,22 @@ CopyInstanceDialog::CopyInstanceDialog(InstancePtr original, QWidget *parent)
ui->copyServersCheckbox->setChecked(m_selectedOptions.isCopyServersEnabled());
ui->copyModsCheckbox->setChecked(m_selectedOptions.isCopyModsEnabled());
ui->copyScreenshotsCheckbox->setChecked(m_selectedOptions.isCopyScreenshotsEnabled());
-
+
ui->linkFilesGroup->setChecked(m_selectedOptions.isLinkFilesEnabled());
ui->recursiveLinkCheckbox->setChecked(m_selectedOptions.isLinkRecursivelyEnabled());
ui->hardLinksCheckbox->setChecked(m_selectedOptions.isUseHardLinksEnabled());
ui->dontLinkSavesCheckbox->setChecked(m_selectedOptions.isDontLinkSavesEnabled());
+
+ auto detectedOS = FS::statFS(m_original->instanceRoot()).fsType;
+ m_cloneSupported = FS::canCloneOnFS(detectedOS);
+
+ if (m_cloneSupported) {
+ ui->cloneSupportedLabel->setText(tr("Clone / Reflink is supported on (%1)").arg(FS::getFilesystemTypeName(detectedOS)));
+ } else {
+ ui->cloneSupportedLabel->setText(tr("Clone / Reflink not supported on (%1)").arg(FS::getFilesystemTypeName(detectedOS)));
+ }
+
+ updateUseCloneCheckbox();
}
CopyInstanceDialog::~CopyInstanceDialog()
@@ -152,6 +164,12 @@ void CopyInstanceDialog::updateSelectAllCheckbox()
ui->selectAllCheckbox->blockSignals(false);
}
+void CopyInstanceDialog::updateUseCloneCheckbox()
+{
+ ui->useCloneCheckbox->setEnabled(m_cloneSupported && !ui->linkFilesGroup->isChecked());
+ ui->useCloneCheckbox->setChecked(m_cloneSupported && m_selectedOptions.isUseCloneEnabled());
+}
+
void CopyInstanceDialog::on_iconButton_clicked()
{
IconPickerDialog dlg(this);
@@ -230,6 +248,7 @@ void CopyInstanceDialog::on_copyScreenshotsCheckbox_stateChanged(int state)
void CopyInstanceDialog::on_linkFilesGroup_toggled(bool checked)
{
m_selectedOptions.enableLinkFiles(checked);
+ updateUseCloneCheckbox();
}
void CopyInstanceDialog::on_recursiveLinkCheckbox_stateChanged(int state)
@@ -254,3 +273,10 @@ void CopyInstanceDialog::on_dontLinkSavesCheckbox_stateChanged(int state)
{
m_selectedOptions.enableDontLinkSaves(state == Qt::Checked);
}
+
+void CopyInstanceDialog::on_useCloneCheckbox_stateChanged(int state)
+{
+ m_selectedOptions.enableUseClone(m_cloneSupported && (state == Qt::Checked));
+ ui->linkFilesGroup->setEnabled(!m_selectedOptions.isUseCloneEnabled());
+ updateUseCloneCheckbox();
+}
\ No newline at end of file
diff --git a/launcher/ui/dialogs/CopyInstanceDialog.h b/launcher/ui/dialogs/CopyInstanceDialog.h
index 2fc6f38a..859c643c 100644
--- a/launcher/ui/dialogs/CopyInstanceDialog.h
+++ b/launcher/ui/dialogs/CopyInstanceDialog.h
@@ -16,6 +16,7 @@
#pragma once
#include
+#include "BaseInstance.h"
#include "BaseVersion.h"
#include "InstanceCopyPrefs.h"
@@ -59,13 +60,17 @@ slots:
void on_recursiveLinkCheckbox_stateChanged(int state);
void on_hardLinksCheckbox_stateChanged(int state);
void on_dontLinkSavesCheckbox_stateChanged(int state);
+ void on_useCloneCheckbox_stateChanged(int state);
private:
void checkAllCheckboxes(const bool& b);
void updateSelectAllCheckbox();
+ void updateUseCloneCheckbox();
+
/* data */
Ui::CopyInstanceDialog *ui;
QString InstIconKey;
InstancePtr m_original;
InstanceCopyPrefs m_selectedOptions;
+ bool m_cloneSupported = false;
};
diff --git a/launcher/ui/dialogs/CopyInstanceDialog.ui b/launcher/ui/dialogs/CopyInstanceDialog.ui
index 8df0d3db..ce8657f6 100644
--- a/launcher/ui/dialogs/CopyInstanceDialog.ui
+++ b/launcher/ui/dialogs/CopyInstanceDialog.ui
@@ -9,8 +9,8 @@
0
0
- 525
- 581
+ 531
+ 640
@@ -275,6 +275,44 @@
+ -
+
+
+ Clone / Reflink (Copy On Write) Options
+
+
+
-
+
+
+ false
+
+
+ Use Clone / Reflink
+
+
+
+ -
+
+
+
+ 1
+ 0
+
+
+
+ Clone / Reflink not supported on this filesystem
+
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+
+
+ 4
+
+
+
+
+
+
-
@@ -302,7 +340,6 @@
copyServersCheckbox
copyResPacksCheckbox
copyModsCheckbox
- linkFilesGroup
recursiveLinkCheckbox
hardLinksCheckbox
dontLinkSavesCheckbox
From bc8336a4b115fd190e068f57159d925683ba3930 Mon Sep 17 00:00:00 2001
From: Rachel Powers <508861+Ryex@users.noreply.github.com>
Date: Thu, 9 Feb 2023 16:19:38 -0700
Subject: [PATCH 013/104] fix: cleanup UI, detect FAT and turn off links
Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com>
---
launcher/DesktopServices.cpp | 1 -
launcher/FileSystem.cpp | 28 ++++++
launcher/FileSystem.h | 24 ++++-
launcher/InstanceCopyPrefs.cpp | 8 +-
launcher/InstanceCopyPrefs.h | 6 +-
launcher/InstanceCopyTask.cpp | 2 +-
launcher/ui/dialogs/CopyInstanceDialog.cpp | 50 +++++++---
launcher/ui/dialogs/CopyInstanceDialog.h | 6 +-
launcher/ui/dialogs/CopyInstanceDialog.ui | 111 ++++++++++++++-------
9 files changed, 175 insertions(+), 61 deletions(-)
diff --git a/launcher/DesktopServices.cpp b/launcher/DesktopServices.cpp
index 69770e99..2984a1b4 100644
--- a/launcher/DesktopServices.cpp
+++ b/launcher/DesktopServices.cpp
@@ -37,7 +37,6 @@
#include
#include
#include
-//#include "Application.h"
/**
* This shouldn't exist, but until QTBUG-9328 and other unreported bugs are fixed, it needs to be a thing.
diff --git a/launcher/FileSystem.cpp b/launcher/FileSystem.cpp
index fd09842f..c363f6ea 100644
--- a/launcher/FileSystem.cpp
+++ b/launcher/FileSystem.cpp
@@ -1053,4 +1053,32 @@ bool clone_file(const QString& src, const QString& dst, std::error_code& ec)
return true;
}
+
+/**
+ * @brief if the Filesystem is symlink capable
+ *
+ */
+bool canLinkOnFS(const QString& path)
+{
+ FilesystemInfo info = statFS(path);
+ return canLinkOnFS(info);
+}
+bool canLinkOnFS(const FilesystemInfo& info)
+{
+ return canLinkOnFS(info.fsType);
+}
+bool canLinkOnFS(FilesystemType type)
+{
+ return !s_non_link_filesystems.contains(type);
+}
+/**
+ * @brief if the Filesystem is symlink capable on both ends
+ *
+ */
+bool canLink(const QString& src, const QString& dst)
+{
+ return canLinkOnFS(src) && canLinkOnFS(dst);
+}
+
+
}
diff --git a/launcher/FileSystem.h b/launcher/FileSystem.h
index aa28de93..83ff99a4 100644
--- a/launcher/FileSystem.h
+++ b/launcher/FileSystem.h
@@ -330,6 +330,7 @@ enum class FilesystemType {
HFSPLUS,
HFSX,
FUSEBLK,
+ F2FS,
UNKNOWN
};
@@ -348,6 +349,7 @@ static const QMap s_filesystem_type_names = {
{FilesystemType::HFSPLUS, QString("HFSPLUS")},
{FilesystemType::HFSX, QString("HFSX")},
{FilesystemType::FUSEBLK, QString("FUSEBLK")},
+ {FilesystemType::F2FS, QString("F2FS")},
{FilesystemType::UNKNOWN, QString("UNKNOWN")}
};
@@ -368,6 +370,7 @@ static const QMap s_filesystem_type_names_inverse = {
{QString("HFSX"), FilesystemType::HFSX},
{QString("HFS"), FilesystemType::HFS},
{QString("FUSEBLK"), FilesystemType::FUSEBLK},
+ {QString("F2FS"), FilesystemType::F2FS},
{QString("UNKNOWN"), FilesystemType::UNKNOWN}
};
@@ -405,7 +408,7 @@ bool canCloneOnFS(const FilesystemInfo& info);
bool canCloneOnFS(FilesystemType type);
/**
- * @brief if the Filesystem is reflink/clone capable and both are on the same device
+ * @brief if the Filesystems are reflink/clone capable and both are on the same device
*
*/
bool canClone(const QString& src, const QString& dst);
@@ -457,4 +460,23 @@ class clone : public QObject {
*/
bool clone_file(const QString& src, const QString& dst, std::error_code& ec);
+
+static const QList s_non_link_filesystems = {
+ FilesystemType::FAT,
+};
+
+/**
+ * @brief if the Filesystem is symlink capable
+ *
+ */
+bool canLinkOnFS(const QString& path);
+bool canLinkOnFS(const FilesystemInfo& info);
+bool canLinkOnFS(FilesystemType type);
+
+/**
+ * @brief if the Filesystem is symlink capable on both ends
+ *
+ */
+bool canLink(const QString& src, const QString& dst);
+
}
diff --git a/launcher/InstanceCopyPrefs.cpp b/launcher/InstanceCopyPrefs.cpp
index f2aa5e69..c03d0aae 100644
--- a/launcher/InstanceCopyPrefs.cpp
+++ b/launcher/InstanceCopyPrefs.cpp
@@ -93,9 +93,9 @@ bool InstanceCopyPrefs::isCopyScreenshotsEnabled() const
return copyScreenshots;
}
-bool InstanceCopyPrefs::isLinkFilesEnabled() const
+bool InstanceCopyPrefs::isUseSymLinksEnabled() const
{
- return linkFiles;
+ return useSymLinks;
}
bool InstanceCopyPrefs::isUseHardLinksEnabled() const
@@ -159,9 +159,9 @@ void InstanceCopyPrefs::enableCopyScreenshots(bool b)
copyScreenshots = b;
}
-void InstanceCopyPrefs::enableLinkFiles(bool b)
+void InstanceCopyPrefs::enableUseSymLinks(bool b)
{
- linkFiles = b;
+ useSymLinks = b;
}
void InstanceCopyPrefs::enableLinkRecursively(bool b)
diff --git a/launcher/InstanceCopyPrefs.h b/launcher/InstanceCopyPrefs.h
index 583fdd4c..14ae9551 100644
--- a/launcher/InstanceCopyPrefs.h
+++ b/launcher/InstanceCopyPrefs.h
@@ -19,7 +19,7 @@ struct InstanceCopyPrefs {
[[nodiscard]] bool isCopyServersEnabled() const;
[[nodiscard]] bool isCopyModsEnabled() const;
[[nodiscard]] bool isCopyScreenshotsEnabled() const;
- [[nodiscard]] bool isLinkFilesEnabled() const;
+ [[nodiscard]] bool isUseSymLinksEnabled() const;
[[nodiscard]] bool isLinkRecursivelyEnabled() const;
[[nodiscard]] bool isUseHardLinksEnabled() const;
[[nodiscard]] bool isDontLinkSavesEnabled() const;
@@ -33,7 +33,7 @@ struct InstanceCopyPrefs {
void enableCopyServers(bool b);
void enableCopyMods(bool b);
void enableCopyScreenshots(bool b);
- void enableLinkFiles(bool b);
+ void enableUseSymLinks(bool b);
void enableLinkRecursively(bool b);
void enableUseHardLinks(bool b);
void enableDontLinkSaves(bool b);
@@ -48,7 +48,7 @@ struct InstanceCopyPrefs {
bool copyServers = true;
bool copyMods = true;
bool copyScreenshots = true;
- bool linkFiles = false;
+ bool useSymLinks = false;
bool linkRecursively = false;
bool useHardLinks = false;
bool dontLinkSaves = false;
diff --git a/launcher/InstanceCopyTask.cpp b/launcher/InstanceCopyTask.cpp
index b3ea54b0..ba0052fa 100644
--- a/launcher/InstanceCopyTask.cpp
+++ b/launcher/InstanceCopyTask.cpp
@@ -13,7 +13,7 @@ InstanceCopyTask::InstanceCopyTask(InstancePtr origInstance, const InstanceCopyP
QString filters = prefs.getSelectedFiltersAsRegex();
- m_useLinks = prefs.isLinkFilesEnabled();
+ m_useLinks = prefs.isUseSymLinksEnabled();
m_linkRecursively = prefs.isLinkRecursivelyEnabled();
m_useHardLinks = prefs.isLinkRecursivelyEnabled() && prefs.isUseHardLinksEnabled();
m_copySaves = prefs.isLinkRecursivelyEnabled() && prefs.isDontLinkSavesEnabled() && prefs.isCopySavesEnabled();
diff --git a/launcher/ui/dialogs/CopyInstanceDialog.cpp b/launcher/ui/dialogs/CopyInstanceDialog.cpp
index c51bc067..c6cbefcf 100644
--- a/launcher/ui/dialogs/CopyInstanceDialog.cpp
+++ b/launcher/ui/dialogs/CopyInstanceDialog.cpp
@@ -87,21 +87,26 @@ CopyInstanceDialog::CopyInstanceDialog(InstancePtr original, QWidget *parent)
ui->copyModsCheckbox->setChecked(m_selectedOptions.isCopyModsEnabled());
ui->copyScreenshotsCheckbox->setChecked(m_selectedOptions.isCopyScreenshotsEnabled());
- ui->linkFilesGroup->setChecked(m_selectedOptions.isLinkFilesEnabled());
- ui->recursiveLinkCheckbox->setChecked(m_selectedOptions.isLinkRecursivelyEnabled());
+ ui->symbolicLinksCheckbox->setChecked(m_selectedOptions.isUseSymLinksEnabled());
ui->hardLinksCheckbox->setChecked(m_selectedOptions.isUseHardLinksEnabled());
+
+ ui->recursiveLinkCheckbox->setChecked(m_selectedOptions.isLinkRecursivelyEnabled());
ui->dontLinkSavesCheckbox->setChecked(m_selectedOptions.isDontLinkSavesEnabled());
auto detectedOS = FS::statFS(m_original->instanceRoot()).fsType;
+
m_cloneSupported = FS::canCloneOnFS(detectedOS);
+ m_linkSupported = FS::canLinkOnFS(detectedOS);
if (m_cloneSupported) {
- ui->cloneSupportedLabel->setText(tr("Clone / Reflink is supported on (%1)").arg(FS::getFilesystemTypeName(detectedOS)));
+ ui->cloneSupportedLabel->setText(tr("Reflinks are supported on %1").arg(FS::getFilesystemTypeName(detectedOS)));
} else {
- ui->cloneSupportedLabel->setText(tr("Clone / Reflink not supported on (%1)").arg(FS::getFilesystemTypeName(detectedOS)));
+ ui->cloneSupportedLabel->setText(tr("Reflinks aren't supported on %1").arg(FS::getFilesystemTypeName(detectedOS)));
}
+ updateLinkOptions();
updateUseCloneCheckbox();
+
}
CopyInstanceDialog::~CopyInstanceDialog()
@@ -170,6 +175,21 @@ void CopyInstanceDialog::updateUseCloneCheckbox()
ui->useCloneCheckbox->setChecked(m_cloneSupported && m_selectedOptions.isUseCloneEnabled());
}
+void CopyInstanceDialog::updateLinkOptions()
+{
+ ui->symbolicLinksCheckbox->setEnabled(m_linkSupported && !ui->hardLinksCheckbox->isChecked());
+ ui->hardLinksCheckbox->setEnabled(m_linkSupported && !ui->symbolicLinksCheckbox->isChecked());
+
+ ui->symbolicLinksCheckbox->setChecked(m_linkSupported && m_selectedOptions.isUseSymLinksEnabled());
+ ui->hardLinksCheckbox->setChecked(m_linkSupported && m_selectedOptions.isUseHardLinksEnabled());
+
+ bool linksInUse = (ui->symbolicLinksCheckbox->isChecked() || ui->hardLinksCheckbox->isChecked());
+ ui->recursiveLinkCheckbox->setEnabled(m_linkSupported && linksInUse && !ui->hardLinksCheckbox->isChecked());
+ ui->dontLinkSavesCheckbox->setEnabled(m_linkSupported && linksInUse);
+ ui->recursiveLinkCheckbox->setChecked(m_linkSupported && linksInUse && m_selectedOptions.isLinkRecursivelyEnabled());
+ ui->dontLinkSavesCheckbox->setChecked(m_linkSupported && linksInUse && m_selectedOptions.isDontLinkSavesEnabled());
+}
+
void CopyInstanceDialog::on_iconButton_clicked()
{
IconPickerDialog dlg(this);
@@ -245,10 +265,20 @@ void CopyInstanceDialog::on_copyScreenshotsCheckbox_stateChanged(int state)
updateSelectAllCheckbox();
}
-void CopyInstanceDialog::on_linkFilesGroup_toggled(bool checked)
+void CopyInstanceDialog::on_symbolicLinksCheckbox_stateChanged(int state)
{
- m_selectedOptions.enableLinkFiles(checked);
+ m_selectedOptions.enableUseSymLinks(state == Qt::Checked);
updateUseCloneCheckbox();
+ updateLinkOptions();
+}
+
+void CopyInstanceDialog::on_hardLinksCheckbox_stateChanged(int state)
+{
+ m_selectedOptions.enableUseHardLinks(state == Qt::Checked);
+ if (state == Qt::Checked && !ui->recursiveLinkCheckbox->isChecked()) {
+ ui->recursiveLinkCheckbox->setChecked(true);
+ }
+ updateLinkOptions();
}
void CopyInstanceDialog::on_recursiveLinkCheckbox_stateChanged(int state)
@@ -261,14 +291,6 @@ void CopyInstanceDialog::on_recursiveLinkCheckbox_stateChanged(int state)
}
-void CopyInstanceDialog::on_hardLinksCheckbox_stateChanged(int state)
-{
- m_selectedOptions.enableUseHardLinks(state == Qt::Checked);
- if (state == Qt::Checked && !ui->recursiveLinkCheckbox->isChecked()) {
- ui->recursiveLinkCheckbox->setChecked(true);
- }
-}
-
void CopyInstanceDialog::on_dontLinkSavesCheckbox_stateChanged(int state)
{
m_selectedOptions.enableDontLinkSaves(state == Qt::Checked);
diff --git a/launcher/ui/dialogs/CopyInstanceDialog.h b/launcher/ui/dialogs/CopyInstanceDialog.h
index 859c643c..2dea3795 100644
--- a/launcher/ui/dialogs/CopyInstanceDialog.h
+++ b/launcher/ui/dialogs/CopyInstanceDialog.h
@@ -56,9 +56,9 @@ slots:
void on_copyServersCheckbox_stateChanged(int state);
void on_copyModsCheckbox_stateChanged(int state);
void on_copyScreenshotsCheckbox_stateChanged(int state);
- void on_linkFilesGroup_toggled(bool checked);
- void on_recursiveLinkCheckbox_stateChanged(int state);
+ void on_symbolicLinksCheckbox_stateChanged(int state);
void on_hardLinksCheckbox_stateChanged(int state);
+ void on_recursiveLinkCheckbox_stateChanged(int state);
void on_dontLinkSavesCheckbox_stateChanged(int state);
void on_useCloneCheckbox_stateChanged(int state);
@@ -66,6 +66,7 @@ private:
void checkAllCheckboxes(const bool& b);
void updateSelectAllCheckbox();
void updateUseCloneCheckbox();
+ void updateLinkOptions();
/* data */
Ui::CopyInstanceDialog *ui;
@@ -73,4 +74,5 @@ private:
InstancePtr m_original;
InstanceCopyPrefs m_selectedOptions;
bool m_cloneSupported = false;
+ bool m_linkSupported = false;
};
diff --git a/launcher/ui/dialogs/CopyInstanceDialog.ui b/launcher/ui/dialogs/CopyInstanceDialog.ui
index ce8657f6..7bf75c2d 100644
--- a/launcher/ui/dialogs/CopyInstanceDialog.ui
+++ b/launcher/ui/dialogs/CopyInstanceDialog.ui
@@ -9,8 +9,8 @@
0
0
- 531
- 640
+ 527
+ 699
@@ -138,7 +138,7 @@
-
- Instance copy options
+ Instance Copy Options
-
@@ -224,53 +224,92 @@
-
- Use symbolic links instead of copying files.
+ Use symbolic or hard links instead of copying files.
- Link files instead of copying them
+ Symbolic and Hard Link Options
false
- true
+ false
false
-
-
+
- Link files recursively
+ Links are supported on most filesystems except FAT
+
+
+ Qt::AlignCenter
-
-
-
- false
+
+
+ 6
-
- Use hard links instead of symbolic links
+
+ 6
-
- Use hard links
+
+ 6
-
-
- -
-
-
- If "copy saves" is selected world save data will be copied instead of linked and thus not shared between instances.
+
+ 6
-
- Don't link saves
-
-
- false
-
-
+
-
+
+
+ true
+
+
+ Use hard links instead of symbolic links
+
+
+ Use hard links
+
+
+
+ -
+
+
+ false
+
+
+ Link files recursively
+
+
+
+ -
+
+
+ false
+
+
+ If "copy saves" is selected world save data will be copied instead of linked and thus not shared between instances.
+
+
+ Don't link saves
+
+
+ false
+
+
+
+ -
+
+
+ Use symbloic links
+
+
+
+
@@ -278,7 +317,7 @@
-
- Clone / Reflink (Copy On Write) Options
+ CoW (Copy-on-Write) Options
-
@@ -287,7 +326,7 @@
false
- Use Clone / Reflink
+ Clone instead of copying
@@ -300,7 +339,7 @@
- Clone / Reflink not supported on this filesystem
+ Your filesystem and/or OS doesn't support reflinks
Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
@@ -340,9 +379,11 @@
copyServersCheckbox
copyResPacksCheckbox
copyModsCheckbox
+ symbolicLinksCheckbox
recursiveLinkCheckbox
hardLinksCheckbox
dontLinkSavesCheckbox
+ useCloneCheckbox
@@ -353,8 +394,8 @@
accept()
- 263
- 571
+ 269
+ 692
157
@@ -369,8 +410,8 @@
reject()
- 331
- 571
+ 337
+ 692
286
From 2837236d81b882f041a1cefadc86ca9d5f09ceeb Mon Sep 17 00:00:00 2001
From: Rachel Powers <508861+Ryex@users.noreply.github.com>
Date: Thu, 9 Feb 2023 19:48:40 -0700
Subject: [PATCH 014/104] fix: intelegent recursive links & symlink follow on
export
Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com>
---
launcher/FileSystem.cpp | 66 ++++++++-
launcher/FileSystem.h | 28 +++-
launcher/InstanceCopyPrefs.cpp | 9 ++
launcher/InstanceCopyPrefs.h | 1 +
launcher/InstanceCopyTask.cpp | 16 ++-
launcher/MMCZip.cpp | 11 +-
launcher/ui/dialogs/CopyInstanceDialog.cpp | 21 ++-
tests/FileSystem_test.cpp | 148 +++++++++++++++++++++
8 files changed, 278 insertions(+), 22 deletions(-)
diff --git a/launcher/FileSystem.cpp b/launcher/FileSystem.cpp
index c363f6ea..4ee3899b 100644
--- a/launcher/FileSystem.cpp
+++ b/launcher/FileSystem.cpp
@@ -242,6 +242,14 @@ bool copy::operator()(const QString& offset, bool dryRun)
return err.value() == 0;
}
+/// qDebug print support for the LinkPair struct
+QDebug operator<<(QDebug debug, const LinkPair& lp)
+{
+ QDebugStateSaver saver(debug);
+
+ debug.nospace() << "LinkPair{ src: " << lp.src << " , dst: " << lp.dst << " }";
+ return debug;
+}
bool create_link::operator()(const QString& offset, bool dryRun)
{
@@ -265,7 +273,7 @@ bool create_link::operator()(const QString& offset, bool dryRun)
* @param offset subdirectory form src to link to dest
* @return if there was an error during the attempt to link
*/
-void create_link::make_link_list( const QString& offset)
+void create_link::make_link_list(const QString& offset)
{
for (auto pair : m_path_pairs) {
const QString& srcPath = pair.src;
@@ -297,14 +305,26 @@ void create_link::make_link_list( const QString& offset)
link_file(src, "");
} else {
if (m_debug)
- qDebug() << "linking recursivly:" << src << "to" << dst;
+ qDebug() << "linking recursivly:" << src << "to" << dst << "max_depth:" << m_max_depth;
QDir src_dir(src);
QDirIterator source_it(src, QDir::Filter::Files | QDir::Filter::Hidden, QDirIterator::Subdirectories);
+ QStringList linkedPaths;
+
while (source_it.hasNext()) {
auto src_path = source_it.next();
auto relative_path = src_dir.relativeFilePath(src_path);
+ if (m_max_depth >= 0 && PathDepth(relative_path) > m_max_depth){
+ relative_path = PathTruncate(relative_path, m_max_depth);
+ src_path = src_dir.filePath(relative_path);
+ if (linkedPaths.contains(src_path)) {
+ continue;
+ }
+ }
+
+ linkedPaths.append(src_path);
+
link_file(src_path, relative_path);
}
}
@@ -312,7 +332,7 @@ void create_link::make_link_list( const QString& offset)
}
bool create_link::make_links()
-{
+{
for (auto link : m_links_to_make) {
QString src_path = link.src;
@@ -556,11 +576,49 @@ QString PathCombine(const QString& path1, const QString& path2, const QString& p
return PathCombine(PathCombine(path1, path2, path3), path4);
}
-QString AbsolutePath(QString path)
+QString AbsolutePath(const QString& path)
{
return QFileInfo(path).absolutePath();
}
+int PathDepth(const QString& path)
+{
+ if (path.isEmpty()) return 0;
+
+ QFileInfo info(path);
+
+ auto parts = QDir::toNativeSeparators(info.path()).split(QDir::separator(), Qt::SkipEmptyParts);
+
+ int numParts = QDir::toNativeSeparators(info.path()).split(QDir::separator(), Qt::SkipEmptyParts).length();
+ numParts -= parts.count(".");
+ numParts -= parts.count("..") * 2;
+
+ return numParts;
+}
+
+QString PathTruncate(const QString& path, int depth)
+{
+ if (path.isEmpty() || (depth < 0) ) return "";
+
+ QString trunc = QFileInfo(path).path();
+
+ if (PathDepth(trunc) > depth ) {
+ return PathTruncate(trunc, depth);
+ }
+
+ auto parts = QDir::toNativeSeparators(trunc).split(QDir::separator(), Qt::SkipEmptyParts);
+ if (parts.startsWith(".") && !path.startsWith(".")) {
+ parts.removeFirst();
+ }
+ if (path.startsWith(QDir::separator())) {
+ parts.prepend("");
+ }
+
+ trunc = parts.join(QDir::separator());
+
+ return trunc;
+}
+
QString ResolveExecutable(QString path)
{
if (path.isEmpty()) {
diff --git a/launcher/FileSystem.h b/launcher/FileSystem.h
index 83ff99a4..7485206a 100644
--- a/launcher/FileSystem.h
+++ b/launcher/FileSystem.h
@@ -200,6 +200,11 @@ class create_link : public QObject {
m_recursive = recursive;
return *this;
}
+ create_link& setMaxDepth(int depth)
+ {
+ m_max_depth = depth;
+ return *this;
+ }
create_link& debug(bool d)
{
m_debug = d;
@@ -239,6 +244,9 @@ class create_link : public QObject {
bool m_whitelist = false;
bool m_recursive = true;
+ /// @brief >= -1 = infinite, 0 = link files at src/* to dest/*, 1 = link files at src/*/* to dest/*/*, etc.
+ int m_max_depth = -1;
+
QList m_path_pairs;
QList m_path_results;
QList m_links_to_make;
@@ -272,7 +280,25 @@ QString PathCombine(const QString& path1, const QString& path2);
QString PathCombine(const QString& path1, const QString& path2, const QString& path3);
QString PathCombine(const QString& path1, const QString& path2, const QString& path3, const QString& path4);
-QString AbsolutePath(QString path);
+QString AbsolutePath(const QString& path);
+
+/**
+ * @brief depth of path. "foo.txt" -> 0 , "bar/foo.txt" -> 1, /baz/bar/foo.txt -> 2, etc.
+ *
+ * @param path path to measure
+ * @return int number of componants before base path
+ */
+int PathDepth(const QString& path);
+
+
+/**
+ * @brief cut off segments of path untill it is a max of length depth
+ *
+ * @param path path to truncate
+ * @param depth max depth of new path
+ * @return QString truncated path
+ */
+QString PathTruncate(const QString& path, int depth);
/**
* Resolve an executable
diff --git a/launcher/InstanceCopyPrefs.cpp b/launcher/InstanceCopyPrefs.cpp
index c03d0aae..0650002b 100644
--- a/launcher/InstanceCopyPrefs.cpp
+++ b/launcher/InstanceCopyPrefs.cpp
@@ -16,8 +16,13 @@ bool InstanceCopyPrefs::allTrue() const
copyScreenshots;
}
+
// Returns a single RegEx string of the selected folders/files to filter out (ex: ".minecraft/saves|.minecraft/server.dat")
QString InstanceCopyPrefs::getSelectedFiltersAsRegex() const
+{
+ return getSelectedFiltersAsRegex({});
+}
+QString InstanceCopyPrefs::getSelectedFiltersAsRegex(const QStringList& additionalFilters) const
{
QStringList filters;
@@ -42,6 +47,10 @@ QString InstanceCopyPrefs::getSelectedFiltersAsRegex() const
if(!copyScreenshots)
filters << "screenshots";
+ for (auto filter : additionalFilters) {
+ filters << filter;
+ }
+
// If we have any filters to add, join them as a single regex string to return:
if (!filters.isEmpty()) {
const QString MC_ROOT = "[.]?minecraft/";
diff --git a/launcher/InstanceCopyPrefs.h b/launcher/InstanceCopyPrefs.h
index 14ae9551..c7bde068 100644
--- a/launcher/InstanceCopyPrefs.h
+++ b/launcher/InstanceCopyPrefs.h
@@ -10,6 +10,7 @@ struct InstanceCopyPrefs {
public:
[[nodiscard]] bool allTrue() const;
[[nodiscard]] QString getSelectedFiltersAsRegex() const;
+ [[nodiscard]] QString getSelectedFiltersAsRegex(const QStringList& additionalFilters) const;
// Getters
[[nodiscard]] bool isCopySavesEnabled() const;
[[nodiscard]] bool isKeepPlaytimeEnabled() const;
diff --git a/launcher/InstanceCopyTask.cpp b/launcher/InstanceCopyTask.cpp
index ba0052fa..40babd0f 100644
--- a/launcher/InstanceCopyTask.cpp
+++ b/launcher/InstanceCopyTask.cpp
@@ -4,13 +4,14 @@
#include "NullInstance.h"
#include "pathmatcher/RegexpMatcher.h"
#include
+#include
InstanceCopyTask::InstanceCopyTask(InstancePtr origInstance, const InstanceCopyPrefs& prefs)
{
m_origInstance = origInstance;
m_keepPlaytime = prefs.isKeepPlaytimeEnabled();
- QString filters = prefs.getSelectedFiltersAsRegex();
+
m_useLinks = prefs.isUseSymLinksEnabled();
@@ -18,6 +19,14 @@ InstanceCopyTask::InstanceCopyTask(InstancePtr origInstance, const InstanceCopyP
m_useHardLinks = prefs.isLinkRecursivelyEnabled() && prefs.isUseHardLinksEnabled();
m_copySaves = prefs.isLinkRecursivelyEnabled() && prefs.isDontLinkSavesEnabled() && prefs.isCopySavesEnabled();
m_useClone = prefs.isUseCloneEnabled();
+
+ QString filters = prefs.getSelectedFiltersAsRegex();
+ if (m_useLinks || m_useHardLinks) {
+ if (!filters.isEmpty()) filters += "|";
+ filters += "instance.cfg";
+ }
+
+ qDebug() << "CopyFilters:" << filters;
if (!filters.isEmpty())
{
@@ -46,9 +55,10 @@ void InstanceCopyTask::executeTask()
folderClone.matcher(m_matcher.get());
return folderClone();
- } else if (m_useLinks) {
+ } else if (m_useLinks || m_useHardLinks) {
FS::create_link folderLink(m_origInstance->instanceRoot(), m_stagingPath);
- folderLink.linkRecursively(m_linkRecursively).useHardLinks(m_useHardLinks).matcher(m_matcher.get());
+ int depth = m_linkRecursively ? -1 : 0; // we need to at least link the top level instead of the instance folder
+ folderLink.linkRecursively(true).setMaxDepth(depth).useHardLinks(m_useHardLinks).matcher(m_matcher.get());
bool there_were_errors = false;
diff --git a/launcher/MMCZip.cpp b/launcher/MMCZip.cpp
index b4b663c1..1a336375 100644
--- a/launcher/MMCZip.cpp
+++ b/launcher/MMCZip.cpp
@@ -102,8 +102,13 @@ bool MMCZip::compressDirFiles(QuaZip *zip, QString dir, QFileInfoList files, boo
for (auto e : files) {
auto filePath = directory.relativeFilePath(e.absoluteFilePath());
auto srcPath = e.absoluteFilePath();
- if (followSymlinks)
- srcPath = e.canonicalFilePath();
+ if (followSymlinks) {
+ if (e.isSymLink()) {
+ srcPath = e.symLinkTarget();
+ } else {
+ srcPath = e.canonicalFilePath();
+ }
+ }
if( !JlCompress::compressFile(zip, srcPath, filePath)) return false;
}
@@ -119,7 +124,7 @@ bool MMCZip::compressDirFiles(QString fileCompressed, QString dir, QFileInfoList
return false;
}
- auto result = compressDirFiles(&zip, dir, files);
+ auto result = compressDirFiles(&zip, dir, files, followSymlinks);
zip.close();
if(zip.getZipError()!=0) {
diff --git a/launcher/ui/dialogs/CopyInstanceDialog.cpp b/launcher/ui/dialogs/CopyInstanceDialog.cpp
index c6cbefcf..9fe129f1 100644
--- a/launcher/ui/dialogs/CopyInstanceDialog.cpp
+++ b/launcher/ui/dialogs/CopyInstanceDialog.cpp
@@ -171,17 +171,18 @@ void CopyInstanceDialog::updateSelectAllCheckbox()
void CopyInstanceDialog::updateUseCloneCheckbox()
{
- ui->useCloneCheckbox->setEnabled(m_cloneSupported && !ui->linkFilesGroup->isChecked());
- ui->useCloneCheckbox->setChecked(m_cloneSupported && m_selectedOptions.isUseCloneEnabled());
+ ui->useCloneCheckbox->setEnabled(m_cloneSupported && !ui->symbolicLinksCheckbox->isChecked() && !ui->hardLinksCheckbox->isChecked());
+ ui->useCloneCheckbox->setChecked(m_cloneSupported && m_selectedOptions.isUseCloneEnabled() && !ui->symbolicLinksCheckbox->isChecked() &&
+ !ui->hardLinksCheckbox->isChecked());
}
void CopyInstanceDialog::updateLinkOptions()
{
- ui->symbolicLinksCheckbox->setEnabled(m_linkSupported && !ui->hardLinksCheckbox->isChecked());
- ui->hardLinksCheckbox->setEnabled(m_linkSupported && !ui->symbolicLinksCheckbox->isChecked());
+ ui->symbolicLinksCheckbox->setEnabled(m_linkSupported && !ui->hardLinksCheckbox->isChecked() && !ui->useCloneCheckbox->isChecked());
+ ui->hardLinksCheckbox->setEnabled(m_linkSupported && !ui->symbolicLinksCheckbox->isChecked() && !ui->useCloneCheckbox->isChecked());
- ui->symbolicLinksCheckbox->setChecked(m_linkSupported && m_selectedOptions.isUseSymLinksEnabled());
- ui->hardLinksCheckbox->setChecked(m_linkSupported && m_selectedOptions.isUseHardLinksEnabled());
+ ui->symbolicLinksCheckbox->setChecked(m_linkSupported && m_selectedOptions.isUseSymLinksEnabled() && !ui->useCloneCheckbox->isChecked());
+ ui->hardLinksCheckbox->setChecked(m_linkSupported && m_selectedOptions.isUseHardLinksEnabled() && !ui->useCloneCheckbox->isChecked());
bool linksInUse = (ui->symbolicLinksCheckbox->isChecked() || ui->hardLinksCheckbox->isChecked());
ui->recursiveLinkCheckbox->setEnabled(m_linkSupported && linksInUse && !ui->hardLinksCheckbox->isChecked());
@@ -278,16 +279,14 @@ void CopyInstanceDialog::on_hardLinksCheckbox_stateChanged(int state)
if (state == Qt::Checked && !ui->recursiveLinkCheckbox->isChecked()) {
ui->recursiveLinkCheckbox->setChecked(true);
}
+ updateUseCloneCheckbox();
updateLinkOptions();
}
void CopyInstanceDialog::on_recursiveLinkCheckbox_stateChanged(int state)
{
m_selectedOptions.enableLinkRecursively(state == Qt::Checked);
- if (state != Qt::Checked) {
- ui->hardLinksCheckbox->setChecked(false);
- ui->dontLinkSavesCheckbox->setChecked(false);
- }
+ updateLinkOptions();
}
@@ -299,6 +298,6 @@ void CopyInstanceDialog::on_dontLinkSavesCheckbox_stateChanged(int state)
void CopyInstanceDialog::on_useCloneCheckbox_stateChanged(int state)
{
m_selectedOptions.enableUseClone(m_cloneSupported && (state == Qt::Checked));
- ui->linkFilesGroup->setEnabled(!m_selectedOptions.isUseCloneEnabled());
updateUseCloneCheckbox();
+ updateLinkOptions();
}
\ No newline at end of file
diff --git a/tests/FileSystem_test.cpp b/tests/FileSystem_test.cpp
index 4ccc4003..4418dd62 100644
--- a/tests/FileSystem_test.cpp
+++ b/tests/FileSystem_test.cpp
@@ -57,6 +57,11 @@ class LinkTask : public Task {
m_lnk->whitelist(b);
}
+ void setMaxDepth(int depth)
+ {
+ m_lnk->setMaxDepth(depth);
+ }
+
private:
void executeTask() override
{
@@ -630,6 +635,149 @@ slots:
QVERIFY(target_dir.entryList(filter).contains("pack.mcmeta"));
}
}
+
+ void test_link_with_max_depth()
+ {
+ QString folder = QFINDTESTDATA("testdata/FileSystem/test_folder");
+ auto f = [&folder, this]()
+ {
+ QTemporaryDir tempDir;
+ tempDir.setAutoRemove(true);
+ qDebug() << "From:" << folder << "To:" << tempDir.path();
+
+ QDir target_dir(FS::PathCombine(tempDir.path(), "test_folder"));
+ qDebug() << tempDir.path();
+ qDebug() << target_dir.path();
+
+ LinkTask lnk_tsk(folder, target_dir.path());
+ lnk_tsk.linkRecursively(true);
+ lnk_tsk.setMaxDepth(0);
+ QObject::connect(&lnk_tsk, &Task::finished, [&]{
+ QVERIFY2(lnk_tsk.wasSuccessful(), "Task finished but was not successful when it should have been.");
+ });
+ lnk_tsk.start();
+
+ QVERIFY2(QTest::qWaitFor([&]() {
+ return lnk_tsk.isFinished();
+ }, 100000), "Task didn't finish as it should.");
+
+ QVERIFY(!QFileInfo(target_dir.path()).isSymLink());
+
+ auto filter = QDir::Filter::Files | QDir::Filter::Dirs | QDir::Filter::Hidden;
+ for(auto entry: target_dir.entryList(filter))
+ {
+ qDebug() << entry;
+ if (entry == "." || entry == "..") continue;
+ QFileInfo entry_lnk_info(target_dir.filePath(entry));
+ QVERIFY(entry_lnk_info.isSymLink());
+ }
+
+ QFileInfo lnk_info(target_dir.path());
+ QVERIFY(lnk_info.exists());
+ QVERIFY(!lnk_info.isSymLink());
+
+ QVERIFY(target_dir.entryList().contains("pack.mcmeta"));
+ QVERIFY(target_dir.entryList().contains("assets"));
+
+
+ };
+
+ // first try variant without trailing /
+ QVERIFY(!folder.endsWith('/'));
+ f();
+
+ // then variant with trailing /
+ folder.append('/');
+ QVERIFY(folder.endsWith('/'));
+ f();
+ }
+
+ void test_link_with_no_max_depth()
+ {
+ QString folder = QFINDTESTDATA("testdata/FileSystem/test_folder");
+ auto f = [&folder]()
+ {
+ QTemporaryDir tempDir;
+ tempDir.setAutoRemove(true);
+ qDebug() << "From:" << folder << "To:" << tempDir.path();
+
+ QDir target_dir(FS::PathCombine(tempDir.path(), "test_folder"));
+ qDebug() << tempDir.path();
+ qDebug() << target_dir.path();
+
+ LinkTask lnk_tsk(folder, target_dir.path());
+ lnk_tsk.linkRecursively(true);
+ lnk_tsk.setMaxDepth(-1);
+ QObject::connect(&lnk_tsk, &Task::finished, [&]{
+ QVERIFY2(lnk_tsk.wasSuccessful(), "Task finished but was not successful when it should have been.");
+ });
+ lnk_tsk.start();
+
+ QVERIFY2(QTest::qWaitFor([&]() {
+ return lnk_tsk.isFinished();
+ }, 100000), "Task didn't finish as it should.");
+
+
+ std::function verify_check = [&](QString check_path) {
+ QDir check_dir(check_path);
+ auto filter = QDir::Filter::Files | QDir::Filter::Dirs | QDir::Filter::Hidden;
+ for(auto entry: check_dir.entryList(filter))
+ {
+ QFileInfo entry_lnk_info(check_dir.filePath(entry));
+ qDebug() << entry << check_dir.filePath(entry);
+ if (!entry_lnk_info.isDir()){
+ QVERIFY(entry_lnk_info.isSymLink());
+ } else if (entry != "." && entry != "..") {
+ qDebug() << "Decending tree to verify symlinks:" << check_dir.filePath(entry);
+ verify_check(entry_lnk_info.filePath());
+ }
+ }
+ };
+
+ verify_check(target_dir.path());
+
+
+ QFileInfo lnk_info(target_dir.path());
+ QVERIFY(lnk_info.exists());
+
+ QVERIFY(target_dir.entryList().contains("pack.mcmeta"));
+ QVERIFY(target_dir.entryList().contains("assets"));
+ };
+
+ // first try variant without trailing /
+ QVERIFY(!folder.endsWith('/'));
+ f();
+
+ // then variant with trailing /
+ folder.append('/');
+ QVERIFY(folder.endsWith('/'));
+ f();
+ }
+
+ void test_path_depth() {
+ QCOMPARE_EQ(FS::PathDepth(""), 0);
+ QCOMPARE_EQ(FS::PathDepth("."), 0);
+ QCOMPARE_EQ(FS::PathDepth("foo.txt"), 0);
+ QCOMPARE_EQ(FS::PathDepth("./foo.txt"), 0);
+ QCOMPARE_EQ(FS::PathDepth("./bar/foo.txt"), 1);
+ QCOMPARE_EQ(FS::PathDepth("../bar/foo.txt"), 0);
+ QCOMPARE_EQ(FS::PathDepth("/bar/foo.txt"), 1);
+ QCOMPARE_EQ(FS::PathDepth("baz/bar/foo.txt"), 2);
+ QCOMPARE_EQ(FS::PathDepth("/baz/bar/foo.txt"), 2);
+ QCOMPARE_EQ(FS::PathDepth("./baz/bar/foo.txt"), 2);
+ QCOMPARE_EQ(FS::PathDepth("/baz/../bar/foo.txt"), 1);
+ }
+
+ void test_path_trunc() {
+ QCOMPARE_EQ(FS::PathTruncate("", 0), "");
+ QCOMPARE_EQ(FS::PathTruncate("foo.txt", 0), "");
+ QCOMPARE_EQ(FS::PathTruncate("foo.txt", 1), "");
+ QCOMPARE_EQ(FS::PathTruncate("./bar/foo.txt", 0), "./bar");
+ QCOMPARE_EQ(FS::PathTruncate("./bar/foo.txt", 1), "./bar");
+ QCOMPARE_EQ(FS::PathTruncate("/bar/foo.txt", 1), "/bar");
+ QCOMPARE_EQ(FS::PathTruncate("bar/foo.txt", 1), "bar");
+ QCOMPARE_EQ(FS::PathTruncate("baz/bar/foo.txt", 2), "baz/bar");
+ }
};
QTEST_GUILESS_MAIN(FileSystemTest)
From 34ac8b3ec345a0c1c909111e8e0a89fa84c13673 Mon Sep 17 00:00:00 2001
From: Rachel Powers <508861+Ryex@users.noreply.github.com>
Date: Thu, 9 Feb 2023 20:02:20 -0700
Subject: [PATCH 015/104] fix: Qt < 5.14.0 compat
Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com>
---
launcher/FileSystem.cpp | 12 +++++++++++-
1 file changed, 11 insertions(+), 1 deletion(-)
diff --git a/launcher/FileSystem.cpp b/launcher/FileSystem.cpp
index 4ee3899b..57b796ad 100644
--- a/launcher/FileSystem.cpp
+++ b/launcher/FileSystem.cpp
@@ -38,6 +38,7 @@
#include "FileSystem.h"
#include
#include
+#include
#include
#include "BuildConfig.h"
@@ -587,9 +588,13 @@ int PathDepth(const QString& path)
QFileInfo info(path);
+#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0)
+ auto parts = QDir::toNativeSeparators(info.path()).split(QDir::separator(), QString::SkipEmptyParts);
+#else
auto parts = QDir::toNativeSeparators(info.path()).split(QDir::separator(), Qt::SkipEmptyParts);
+#endif
- int numParts = QDir::toNativeSeparators(info.path()).split(QDir::separator(), Qt::SkipEmptyParts).length();
+ int numParts = parts.length();
numParts -= parts.count(".");
numParts -= parts.count("..") * 2;
@@ -606,7 +611,12 @@ QString PathTruncate(const QString& path, int depth)
return PathTruncate(trunc, depth);
}
+#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0)
+ auto parts = QDir::toNativeSeparators(trunc).split(QDir::separator(), QString::SkipEmptyParts);
+#else
auto parts = QDir::toNativeSeparators(trunc).split(QDir::separator(), Qt::SkipEmptyParts);
+#endif
+
if (parts.startsWith(".") && !path.startsWith(".")) {
parts.removeFirst();
}
From cd2419137d68781354325d77c0392ab0ee1b65de Mon Sep 17 00:00:00 2001
From: Rachel Powers <508861+Ryex@users.noreply.github.com>
Date: Thu, 9 Feb 2023 20:12:36 -0700
Subject: [PATCH 016/104] fix: better test compareison (also qt5 compat)
Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com>
---
tests/FileSystem_test.cpp | 38 +++++++++++++++++++-------------------
1 file changed, 19 insertions(+), 19 deletions(-)
diff --git a/tests/FileSystem_test.cpp b/tests/FileSystem_test.cpp
index 4418dd62..ab78c150 100644
--- a/tests/FileSystem_test.cpp
+++ b/tests/FileSystem_test.cpp
@@ -755,28 +755,28 @@ slots:
}
void test_path_depth() {
- QCOMPARE_EQ(FS::PathDepth(""), 0);
- QCOMPARE_EQ(FS::PathDepth("."), 0);
- QCOMPARE_EQ(FS::PathDepth("foo.txt"), 0);
- QCOMPARE_EQ(FS::PathDepth("./foo.txt"), 0);
- QCOMPARE_EQ(FS::PathDepth("./bar/foo.txt"), 1);
- QCOMPARE_EQ(FS::PathDepth("../bar/foo.txt"), 0);
- QCOMPARE_EQ(FS::PathDepth("/bar/foo.txt"), 1);
- QCOMPARE_EQ(FS::PathDepth("baz/bar/foo.txt"), 2);
- QCOMPARE_EQ(FS::PathDepth("/baz/bar/foo.txt"), 2);
- QCOMPARE_EQ(FS::PathDepth("./baz/bar/foo.txt"), 2);
- QCOMPARE_EQ(FS::PathDepth("/baz/../bar/foo.txt"), 1);
+ QCOMPARE(FS::PathDepth(""), 0);
+ QCOMPARE(FS::PathDepth("."), 0);
+ QCOMPARE(FS::PathDepth("foo.txt"), 0);
+ QCOMPARE(FS::PathDepth("./foo.txt"), 0);
+ QCOMPARE(FS::PathDepth("./bar/foo.txt"), 1);
+ QCOMPARE(FS::PathDepth("../bar/foo.txt"), 0);
+ QCOMPARE(FS::PathDepth("/bar/foo.txt"), 1);
+ QCOMPARE(FS::PathDepth("baz/bar/foo.txt"), 2);
+ QCOMPARE(FS::PathDepth("/baz/bar/foo.txt"), 2);
+ QCOMPARE(FS::PathDepth("./baz/bar/foo.txt"), 2);
+ QCOMPARE(FS::PathDepth("/baz/../bar/foo.txt"), 1);
}
void test_path_trunc() {
- QCOMPARE_EQ(FS::PathTruncate("", 0), "");
- QCOMPARE_EQ(FS::PathTruncate("foo.txt", 0), "");
- QCOMPARE_EQ(FS::PathTruncate("foo.txt", 1), "");
- QCOMPARE_EQ(FS::PathTruncate("./bar/foo.txt", 0), "./bar");
- QCOMPARE_EQ(FS::PathTruncate("./bar/foo.txt", 1), "./bar");
- QCOMPARE_EQ(FS::PathTruncate("/bar/foo.txt", 1), "/bar");
- QCOMPARE_EQ(FS::PathTruncate("bar/foo.txt", 1), "bar");
- QCOMPARE_EQ(FS::PathTruncate("baz/bar/foo.txt", 2), "baz/bar");
+ QCOMPARE(FS::PathTruncate("", 0), "");
+ QCOMPARE(FS::PathTruncate("foo.txt", 0), "");
+ QCOMPARE(FS::PathTruncate("foo.txt", 1), "");
+ QCOMPARE(FS::PathTruncate("./bar/foo.txt", 0), "./bar");
+ QCOMPARE(FS::PathTruncate("./bar/foo.txt", 1), "./bar");
+ QCOMPARE(FS::PathTruncate("/bar/foo.txt", 1), "/bar");
+ QCOMPARE(FS::PathTruncate("bar/foo.txt", 1), "bar");
+ QCOMPARE(FS::PathTruncate("baz/bar/foo.txt", 2), "baz/bar");
}
};
From 3a0e4546c2a1914c18f71622727997a2a7518ad2 Mon Sep 17 00:00:00 2001
From: Rachel Powers <508861+Ryex@users.noreply.github.com>
Date: Thu, 9 Feb 2023 22:07:07 -0800
Subject: [PATCH 017/104] fix: windows test compat fix: compiler warning on int
qint32 compare
Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com>
---
launcher/FileSystem.cpp | 5 ++---
tests/FileSystem_test.cpp | 20 ++++++++++++--------
2 files changed, 14 insertions(+), 11 deletions(-)
diff --git a/launcher/FileSystem.cpp b/launcher/FileSystem.cpp
index 57b796ad..c3a0c273 100644
--- a/launcher/FileSystem.cpp
+++ b/launcher/FileSystem.cpp
@@ -433,7 +433,7 @@ void create_link::runPrivlaged(const QString& offset)
in >> numResults;
qDebug() << "numResults" << numResults;
- for(int i = 0; i < numResults; i++) {
+ for(quint32 i = 0; i < numResults; i++) {
FS::LinkResult result;
in >> result.src;
in >> result.dst;
@@ -484,7 +484,6 @@ void ExternalLinkFileProcess::runLinkFile() {
#if defined Q_OS_WIN32
SHELLEXECUTEINFO ShExecInfo;
- HRESULT hr;
fileLinkExe = fileLinkExe + ".exe";
@@ -620,7 +619,7 @@ QString PathTruncate(const QString& path, int depth)
if (parts.startsWith(".") && !path.startsWith(".")) {
parts.removeFirst();
}
- if (path.startsWith(QDir::separator())) {
+ if (QDir::toNativeSeparators(path).startsWith(QDir::separator())) {
parts.prepend("");
}
diff --git a/tests/FileSystem_test.cpp b/tests/FileSystem_test.cpp
index ab78c150..169f0669 100644
--- a/tests/FileSystem_test.cpp
+++ b/tests/FileSystem_test.cpp
@@ -1,4 +1,5 @@
#include
+#include
#include
#include
@@ -769,14 +770,17 @@ slots:
}
void test_path_trunc() {
- QCOMPARE(FS::PathTruncate("", 0), "");
- QCOMPARE(FS::PathTruncate("foo.txt", 0), "");
- QCOMPARE(FS::PathTruncate("foo.txt", 1), "");
- QCOMPARE(FS::PathTruncate("./bar/foo.txt", 0), "./bar");
- QCOMPARE(FS::PathTruncate("./bar/foo.txt", 1), "./bar");
- QCOMPARE(FS::PathTruncate("/bar/foo.txt", 1), "/bar");
- QCOMPARE(FS::PathTruncate("bar/foo.txt", 1), "bar");
- QCOMPARE(FS::PathTruncate("baz/bar/foo.txt", 2), "baz/bar");
+ QCOMPARE(FS::PathTruncate("", 0), QDir::toNativeSeparators(""));
+ QCOMPARE(FS::PathTruncate("foo.txt", 0), QDir::toNativeSeparators(""));
+ QCOMPARE(FS::PathTruncate("foo.txt", 1), QDir::toNativeSeparators(""));
+ QCOMPARE(FS::PathTruncate("./bar/foo.txt", 0), QDir::toNativeSeparators("./bar"));
+ QCOMPARE(FS::PathTruncate("./bar/foo.txt", 1), QDir::toNativeSeparators("./bar"));
+ QCOMPARE(FS::PathTruncate("/bar/foo.txt", 1), QDir::toNativeSeparators("/bar"));
+ QCOMPARE(FS::PathTruncate("bar/foo.txt", 1), QDir::toNativeSeparators("bar"));
+ QCOMPARE(FS::PathTruncate("baz/bar/foo.txt", 2), QDir::toNativeSeparators("baz/bar"));
+#if defined(Q_OS_WIN)
+ QCOMPARE(FS::PathTruncate("C:\\bar\\foo.txt", 1), QDir::toNativeSeparators("C:\\bar"));
+#endif
}
};
From 2e8d04aad0d4fe3e742e4b29f4e23dc91b8ef838 Mon Sep 17 00:00:00 2001
From: Rachel Powers <508861+Ryex@users.noreply.github.com>
Date: Fri, 10 Feb 2023 05:34:48 -0800
Subject: [PATCH 018/104] feat: support reflink on windows via winbtrfs!
https://github.com/maharmstone/btrfs
Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com>
---
launcher/FileSystem.cpp | 200 ++++++++++++++++++++++++++++++++++----
launcher/FileSystem.h | 21 +++-
launcher/InstanceList.cpp | 2 +-
3 files changed, 200 insertions(+), 23 deletions(-)
diff --git a/launcher/FileSystem.cpp b/launcher/FileSystem.cpp
index c3a0c273..4037d346 100644
--- a/launcher/FileSystem.cpp
+++ b/launcher/FileSystem.cpp
@@ -108,6 +108,13 @@ namespace fs = ghc::filesystem;
#elif defined(Q_OS_MACOS) || defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD)
#include
#include
+#elif defined(Q_OS_WIN)
+// winbtrfs clone vs rundll32 shellbtrfs.dll,ReflinkCopy
+#include
+#include
+#include
+// refs
+#include
#endif
namespace FS {
@@ -916,25 +923,44 @@ bool overrideFolder(QString overwritten_path, QString override_path)
return err.value() == 0;
}
+/**
+ * @brief path to the near ancestor that exsists
+ *
+ */
+QString NearestExistentAncestor(const QString& path)
+{
+ if(QFileInfo::exists(path)) return path;
+
+ QDir dir(path);
+ if(!dir.makeAbsolute()) return {};
+ do
+ {
+ dir.setPath(QDir::cleanPath(dir.filePath(QStringLiteral(".."))));
+ }
+ while(!dir.exists() && !dir.isRoot());
+
+ return dir.exists() ? dir.path() : QString();
+}
/**
* @brief colect information about the filesystem under a file
*
*/
-FilesystemInfo statFS(QString path)
+FilesystemInfo statFS(const QString& path)
{
FilesystemInfo info;
- QStorageInfo storage_info(path);
+ QStorageInfo storage_info(NearestExistentAncestor(path));
QString fsTypeName = QString::fromStdString(storage_info.fileSystemType().toStdString());
+ qDebug() << "Qt reports Filesystem at" << path << "root:" << storage_info.rootPath() << "as" << fsTypeName;
for (auto fs_type_pair : s_filesystem_type_names_inverse.toStdMap()) {
auto fs_type_name = fs_type_pair.first;
auto fs_type = fs_type_pair.second;
- if(fsTypeName.contains(fs_type_name.toLower())) {
+ if(fsTypeName.toLower().contains(fs_type_name.toLower())) {
info.fsType = fs_type;
break;
}
@@ -948,9 +974,6 @@ FilesystemInfo statFS(QString path)
info.name = storage_info.name();
info.rootPath = storage_info.rootPath();
- qDebug() << "Pulling filesystem info for" << info.rootPath;
- qDebug() << "Filesystem: " << fsTypeName << "detected" << getFilesystemTypeName(info.fsType);
-
return info;
}
@@ -1054,15 +1077,156 @@ bool clone::operator()(const QString& offset, bool dryRun)
*/
bool clone_file(const QString& src, const QString& dst, std::error_code& ec)
{
- auto src_path = StringUtils::toStdString(QFileInfo(src).absoluteFilePath());
- auto dst_path = StringUtils::toStdString(QFileInfo(dst).absoluteFilePath());
+ auto src_path = StringUtils::toStdString(QDir::toNativeSeparators(QFileInfo(src).absoluteFilePath()));
+ auto dst_path = StringUtils::toStdString(QDir::toNativeSeparators(QFileInfo(dst).absoluteFilePath()));
#if defined(Q_OS_WIN)
- qWarning("clone/reflink not supported on windows!");
- ec = std::make_error_code(std::errc::not_supported);
- return false;
+ FilesystemInfo srcinfo = statFS(src);
+ if (srcinfo.fsType == FilesystemType::BTRFS) {
+ FilesystemInfo dstinfo = statFS(dst);
+ if (dstinfo.fsType != FilesystemType::BTRFS || (srcinfo.rootPath != dstinfo.rootPath)){
+ qWarning() << "winbtrfs clone must be to the same device! src and dst root paths do not match.";
+ qWarning() << "check out https://github.com/maharmstone/btrfs for btrfs support!";
+ ec = std::make_error_code(std::errc::not_supported);
+ return false;
+ }
+
+ qWarning() << "clone/reflink of btrfs on windows! assuming winbtrfs is in use and calling shellbtrfs.dll via rundll32.exe";
+
+ if (!winbtrfs_clone(src_path, dst_path, ec))
+ return false;
+
+ // There is no return value from rundll32.exe so we must check if the file exsists ourselves
+
+ QFileInfo dstInfo(dst);
+ if (!dstInfo.exists() || !dstInfo.isFile() || dstInfo.isSymLink()) {
+ // shellbtrfs.dll,ReflinkCopyW is curently broken https://github.com/maharmstone/btrfs/issues/556
+ // lets try a little workaround
+ // find the misnamed file
+ qDebug() << dst << "is missing. ReflinkCopyW may still be broken, trying workaround.";
+ QString badDst = QDir(dstInfo.absolutePath()).path() + dstInfo.fileName();
+ qDebug() << "trying" << badDst;
+ QFileInfo badDstInfo(badDst);
+ if (badDstInfo.exists() && badDstInfo.isFile()) {
+ qDebug() << badDst << "exists! moving it to the correct location.";
+ if(!move(badDstInfo.absoluteFilePath(), dstInfo.absoluteFilePath())) {
+ qDebug() << "move from" << badDst << "to" << dst << "failed";
+ ec = std::make_error_code(std::errc::no_such_file_or_directory);
+ return false;
+ }
+ } else {
+ // oof, clone failure?
+ qWarning() << "clone/reflink on winbtrfs did not succeed: file" << dst << "does not appear to exsist";
+ ec = std::make_error_code(std::errc::no_such_file_or_directory);
+ return false;
+ }
+ }
+
+ } else if (srcinfo.fsType == FilesystemType::REFS) {
+ qWarning() << "clone/reflink not yet supported on windows ReFS!";
+ ec = std::make_error_code(std::errc::not_supported);
+ return false;
+ } else {
+ qWarning() << "clone/reflink not supported on windows outside of winbtrfs or ReFS!";
+ qWarning() << "check out https://github.com/maharmstone/btrfs for btrfs support!";
+ ec = std::make_error_code(std::errc::not_supported);
+ return false;
+ }
#elif defined(Q_OS_LINUX)
+ if(!linux_ficlone(src_path, dst_path, ec))
+ return false;
+
+#elif defined(Q_OS_MACOS) || defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD)
+
+ if(!macos_bsd_clonefile(src_path, dst_path, ec))
+ return false;
+
+#else
+ qWarning() << "clone/reflink not supported! unknown OS";
+ ec = std::make_error_code(std::errc::not_supported);
+ return false;
+#endif
+
+ return true;
+}
+
+#if defined(Q_OS_WIN)
+typedef void (__stdcall *f_ReflinkCopyW)(HWND hwnd, HINSTANCE hinst, LPWSTR lpszCmdLine, int nCmdShow);
+
+bool winbtrfs_clone(const std::wstring& src_path, const std::wstring& dst_path, std::error_code& ec)
+{
+ // https://github.com/maharmstone/btrfs
+ QString cmdLine = QString("\"%1\" \"%2\"").arg(src_path, dst_path);
+
+ std::wstring wstr = cmdLine.toStdWString(); // temp buffer to copy the data and avoid side effect of non const cast
+
+ LPWSTR cmdLineWin = (wchar_t*)wstr.c_str();
+
+ // https://github.com/maharmstone/btrfs/blob/9da54911dd6f3713a1c4c7be40338a3da126f4e6/src/shellext/contextmenu.cpp#L1609
+ HINSTANCE shellbtrfsDLL = LoadLibrary(L"shellbtrfs.dll");
+
+ if (shellbtrfsDLL == NULL) {
+ ec = std::make_error_code(std::errc::not_supported);
+ qWarning() << "cannot locate the shellbtrfs.dll file, reflink copy not supported";
+ return false;
+ }
+
+ f_ReflinkCopyW ReflinkCopyW = (f_ReflinkCopyW)GetProcAddress(shellbtrfsDLL, "ReflinkCopyW");
+
+ if (!ReflinkCopyW) {
+ ec = std::make_error_code(std::errc::not_supported);
+ qWarning() << "cannot locate the ReflinkCopyW function from shellbtrfs.dll, reflink copy not supported";
+ return false;
+ }
+
+ qDebug() << "Calling ReflinkCopyW from shellbtrfs.dll with:" << cmdLine;
+
+ ReflinkCopyW(0, 0, cmdLineWin, 1);
+
+ FreeLibrary(shellbtrfsDLL);
+
+ return true;
+}
+
+bool refs_clone(const std::wstring& src_path, const std::wstring& dst_path, std::error_code& ec)
+{
+ //https://learn.microsoft.com/en-us/windows/win32/api/winioctl/ni-winioctl-fsctl_duplicate_extents_to_file
+ //https://github.com/microsoft/CopyOnWrite/blob/main/lib/Windows/WindowsCopyOnWriteFilesystem.cs#L94
+ std::wstring existingFile = src_path.c_str();
+ std::wstring newLink = dst_path.c_str();
+
+
+ HANDLE hExistingFile = CreateFile(src_path.c_str(), GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);
+ if (hExistingFile == INVALID_HANDLE_VALUE)
+ {
+ return false;
+ }
+
+ HANDLE hNewFile = CreateFile(dst_path.c_str(), GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, 0, NULL);
+ if (hNewFile == INVALID_HANDLE_VALUE)
+ {
+ CloseHandle(hExistingFile);
+ return false;
+ }
+
+ DWORD bytesReturned;
+
+ // FIXME: ReFS requires that cloned regions reside on a disk cluster boundary.
+ // FIXME: ERROR_BLOCK_TOO_MANY_REFERENCES can occure https://docs.microsoft.com/en-us/windows-server/storage/refs/block-cloning#functionality-restrictions-and-remarks
+ BOOL result = DeviceIoControl(hExistingFile, FSCTL_DUPLICATE_EXTENTS_TO_FILE, hNewFile, sizeof(hNewFile), NULL, 0, &bytesReturned, NULL);
+
+ CloseHandle(hNewFile);
+ CloseHandle(hExistingFile);
+
+ return (result != 0);
+
+}
+
+
+#elif defined(Q_OS_LINUX)
+bool linux_ficlone(const std::string& src_path, const std::string& dst_path, std::error_code& ec)
+{
// https://man7.org/linux/man-pages/man2/ioctl_ficlone.2.html
int src_fd = open(src_path.c_str(), O_RDONLY);
@@ -1097,9 +1261,12 @@ bool clone_file(const QString& src, const QString& dst, std::error_code& ec)
qWarning() << "Failed to close file:" << dst_path.c_str();
qDebug() << "Error:" << strerror(errno);
}
+ return true;
+}
#elif defined(Q_OS_MACOS) || defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD)
- // TODO: use clonefile
+bool macos_bsd_clonefile(const std::string& src_path, const std::string& dst_path, std::error_code& ec)
+{
// clonefile(const char * src, const char * dst, int flags);
// https://www.manpagez.com/man/2/clonefile/
@@ -1110,16 +1277,9 @@ bool clone_file(const QString& src, const QString& dst, std::error_code& ec)
ec = std::make_error_code(static_cast(errno));
return false;
}
-
-#else
- qWarning("clone/reflink not supported! unknown OS");
- ec = std::make_error_code(std::errc::not_supported);
- return false;
-#endif
-
return true;
}
-
+#endif
/**
* @brief if the Filesystem is symlink capable
diff --git a/launcher/FileSystem.h b/launcher/FileSystem.h
index 7485206a..3f6b78e5 100644
--- a/launcher/FileSystem.h
+++ b/launcher/FileSystem.h
@@ -344,6 +344,7 @@ bool createShortcut(QString destination, QString target, QStringList args, QStri
enum class FilesystemType {
FAT,
NTFS,
+ REFS,
EXT,
EXT_2_OLD,
EXT_2_3_4,
@@ -363,6 +364,7 @@ enum class FilesystemType {
static const QMap s_filesystem_type_names = {
{FilesystemType::FAT, QString("FAT")},
{FilesystemType::NTFS, QString("NTFS")},
+ {FilesystemType::REFS, QString("REFS")},
{FilesystemType::EXT, QString("EXT")},
{FilesystemType::EXT_2_OLD, QString("EXT2_OLD")},
{FilesystemType::EXT_2_3_4, QString("EXT2/3/4")},
@@ -382,6 +384,7 @@ static const QMap s_filesystem_type_names = {
static const QMap s_filesystem_type_names_inverse = {
{QString("FAT"), FilesystemType::FAT},
{QString("NTFS"), FilesystemType::NTFS},
+ {QString("REFS"), FilesystemType::REFS},
{QString("EXT2_OLD"), FilesystemType::EXT_2_OLD},
{QString("EXT2"), FilesystemType::EXT_2_3_4},
{QString("EXT3"), FilesystemType::EXT_2_3_4},
@@ -414,15 +417,21 @@ struct FilesystemInfo {
QString rootPath;
};
+/**
+ * @brief path to the near ancestor that exsists
+ *
+ */
+QString NearestExistentAncestor(const QString& path);
+
/**
* @brief colect information about the filesystem under a file
*
*/
-FilesystemInfo statFS(QString path);
+FilesystemInfo statFS(const QString& path);
static const QList s_clone_filesystems = {
- FilesystemType::BTRFS, FilesystemType::APFS, FilesystemType::ZFS, FilesystemType::XFS
+ FilesystemType::BTRFS, FilesystemType::APFS, FilesystemType::ZFS, FilesystemType::XFS, FilesystemType::REFS
};
/**
@@ -486,6 +495,14 @@ class clone : public QObject {
*/
bool clone_file(const QString& src, const QString& dst, std::error_code& ec);
+#if defined(Q_OS_WIN)
+bool winbtrfs_clone(const std::wstring& src_path, const std::wstring& dst_path, std::error_code& ec);
+bool refs_clone(const std::wstring& src_path, const std::wstring& dst_path, std::error_code& ec);
+#elif defined(Q_OS_LINUX)
+bool linux_ficlone(const std::string& src_path, const std::string& dst_path, std::error_code& ec);
+#elif defined(Q_OS_MACOS) || defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD)
+bool macos_bsd_clonefile(const std::string& src_path, const std::string& dst_path, std::error_code& ec);
+#endif
static const QList s_non_link_filesystems = {
FilesystemType::FAT,
diff --git a/launcher/InstanceList.cpp b/launcher/InstanceList.cpp
index 68e3e92c..1ca16ae9 100644
--- a/launcher/InstanceList.cpp
+++ b/launcher/InstanceList.cpp
@@ -865,7 +865,7 @@ Task* InstanceList::wrapInstanceTask(InstanceTask* task)
QString InstanceList::getStagedInstancePath()
{
- QString key = QUuid::createUuid().toString();
+ QString key = QUuid::createUuid().toString().remove("{").remove("}");
QString tempDir = ".LAUNCHER_TEMP/";
QString relPath = FS::PathCombine(tempDir, key);
QDir rootPath(m_instDir);
From 1210c3256d20133cc793b2a5b4f22f02e6519818 Mon Sep 17 00:00:00 2001
From: Rachel Powers <508861+Ryex@users.noreply.github.com>
Date: Fri, 10 Feb 2023 06:49:52 -0700
Subject: [PATCH 019/104] fix: macos compat after refactor of `clonefile`
Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com>
---
launcher/FileSystem.cpp | 9 +++++++--
1 file changed, 7 insertions(+), 2 deletions(-)
diff --git a/launcher/FileSystem.cpp b/launcher/FileSystem.cpp
index 4037d346..69e2d36a 100644
--- a/launcher/FileSystem.cpp
+++ b/launcher/FileSystem.cpp
@@ -1191,6 +1191,7 @@ bool winbtrfs_clone(const std::wstring& src_path, const std::wstring& dst_path,
bool refs_clone(const std::wstring& src_path, const std::wstring& dst_path, std::error_code& ec)
{
+#if defined(FSCTL_DUPLICATE_EXTENTS_TO_FILE)
//https://learn.microsoft.com/en-us/windows/win32/api/winioctl/ni-winioctl-fsctl_duplicate_extents_to_file
//https://github.com/microsoft/CopyOnWrite/blob/main/lib/Windows/WindowsCopyOnWriteFilesystem.cs#L94
std::wstring existingFile = src_path.c_str();
@@ -1220,7 +1221,11 @@ bool refs_clone(const std::wstring& src_path, const std::wstring& dst_path, std:
CloseHandle(hExistingFile);
return (result != 0);
-
+#else
+ ec = std::make_error_code(std::errc::not_supported);
+ qWarning() << "not built with refs support";
+ return false;
+#endif
}
@@ -1270,7 +1275,7 @@ bool macos_bsd_clonefile(const std::string& src_path, const std::string& dst_pat
// clonefile(const char * src, const char * dst, int flags);
// https://www.manpagez.com/man/2/clonefile/
- qDebug() << "attempting file clone via clonefile" << src << "to" << dst;
+ qDebug() << "attempting file clone via clonefile" << src_path.c_str() << "to" << dst_path.c_str();
if (clonefile(src_path.c_str(), dst_path.c_str(), 0) == -1) {
qWarning() << "Failed to clone file:" << src_path.c_str() << "to" << dst_path.c_str();
qDebug() << "Error:" << strerror(errno);
From 9939367db7568249efd47701105323a4801a9413 Mon Sep 17 00:00:00 2001
From: Rachel Powers <508861+Ryex@users.noreply.github.com>
Date: Fri, 10 Feb 2023 16:41:48 -0800
Subject: [PATCH 020/104] feat(reflink): ioctl_clone for winbtrfs & ReFS
Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com>
---
launcher/FileSystem.cpp | 356 ++++++++++++++++++++++++++++++++++++----
launcher/FileSystem.h | 10 ++
2 files changed, 334 insertions(+), 32 deletions(-)
diff --git a/launcher/FileSystem.cpp b/launcher/FileSystem.cpp
index 69e2d36a..948ec55e 100644
--- a/launcher/FileSystem.cpp
+++ b/launcher/FileSystem.cpp
@@ -59,6 +59,7 @@
#include "StringUtils.h"
#if defined Q_OS_WIN32
+#define NOMINMAX
#define WIN32_LEAN_AND_MEAN
#include
#include
@@ -113,6 +114,7 @@ namespace fs = ghc::filesystem;
#include
#include
#include
+#include
// refs
#include
#endif
@@ -953,14 +955,13 @@ FilesystemInfo statFS(const QString& path)
QStorageInfo storage_info(NearestExistentAncestor(path));
- QString fsTypeName = QString::fromStdString(storage_info.fileSystemType().toStdString());
- qDebug() << "Qt reports Filesystem at" << path << "root:" << storage_info.rootPath() << "as" << fsTypeName;
+ info.fsTypeName = QString::fromStdString(storage_info.fileSystemType().toStdString());
for (auto fs_type_pair : s_filesystem_type_names_inverse.toStdMap()) {
auto fs_type_name = fs_type_pair.first;
auto fs_type = fs_type_pair.second;
- if(fsTypeName.toLower().contains(fs_type_name.toLower())) {
+ if(info.fsTypeName.toLower().contains(fs_type_name.toLower())) {
info.fsType = fs_type;
break;
}
@@ -1043,7 +1044,7 @@ bool clone::operator()(const QString& offset, bool dryRun)
clone_file(src_path, dst_path, err);
}
if (err) {
- qWarning() << "Failed to clone files:" << QString::fromStdString(err.message());
+ qDebug() << "Failed to clone files:" << QString::fromStdString(err.message());
qDebug() << "Source file:" << src_path;
qDebug() << "Destination file:" << dst_path;
}
@@ -1082,8 +1083,23 @@ bool clone_file(const QString& src, const QString& dst, std::error_code& ec)
#if defined(Q_OS_WIN)
FilesystemInfo srcinfo = statFS(src);
- if (srcinfo.fsType == FilesystemType::BTRFS) {
- FilesystemInfo dstinfo = statFS(dst);
+ FilesystemInfo dstinfo = statFS(dst);
+
+ if (((srcinfo.fsType == FilesystemType::BTRFS && dstinfo.fsType == FilesystemType::BTRFS) ||
+ (srcinfo.fsType == FilesystemType::REFS && dstinfo.fsType == FilesystemType::REFS)) &&
+ USE_IOCTL_CLONE)
+ {
+ if (srcinfo.rootPath != dstinfo.rootPath) {
+ qWarning() << "clones must be to the same device! src and dst root paths do not match.";
+ ec = std::make_error_code(std::errc::not_supported);
+ return false;
+ }
+
+ qDebug() << "ioctl clone" << src << "to" << dst;
+ if (!ioctl_clone(src_path, dst_path, ec))
+ return false;
+
+ } else if (srcinfo.fsType == FilesystemType::BTRFS) {
if (dstinfo.fsType != FilesystemType::BTRFS || (srcinfo.rootPath != dstinfo.rootPath)){
qWarning() << "winbtrfs clone must be to the same device! src and dst root paths do not match.";
qWarning() << "check out https://github.com/maharmstone/btrfs for btrfs support!";
@@ -1091,12 +1107,12 @@ bool clone_file(const QString& src, const QString& dst, std::error_code& ec)
return false;
}
- qWarning() << "clone/reflink of btrfs on windows! assuming winbtrfs is in use and calling shellbtrfs.dll via rundll32.exe";
+ qDebug() << "clone/reflink of btrfs on windows! assuming winbtrfs is in use and calling shellbtrfs.dll,ReflinkCopyW";
if (!winbtrfs_clone(src_path, dst_path, ec))
return false;
- // There is no return value from rundll32.exe so we must check if the file exsists ourselves
+ // There is no return value from ReflinkCopyW so we must check if the file exsists ourselves
QFileInfo dstInfo(dst);
if (!dstInfo.exists() || !dstInfo.isFile() || dstInfo.isSymLink()) {
@@ -1116,16 +1132,24 @@ bool clone_file(const QString& src, const QString& dst, std::error_code& ec)
}
} else {
// oof, clone failure?
- qWarning() << "clone/reflink on winbtrfs did not succeed: file" << dst << "does not appear to exsist";
+ qDebug() << "clone/reflink on winbtrfs did not succeed: file" << dst << "does not appear to exist";
ec = std::make_error_code(std::errc::no_such_file_or_directory);
return false;
}
}
} else if (srcinfo.fsType == FilesystemType::REFS) {
- qWarning() << "clone/reflink not yet supported on windows ReFS!";
- ec = std::make_error_code(std::errc::not_supported);
- return false;
+ if (dstinfo.fsType != FilesystemType::REFS || (srcinfo.rootPath != dstinfo.rootPath)){
+ qWarning() << "ReFS clone must be to the same device! src and dst root paths do not match.";
+ ec = std::make_error_code(std::errc::not_supported);
+ return false;
+ }
+
+ qDebug() << "clone/reflink of ReFS on windows!";
+
+ if (!refs_clone(src_path, dst_path, ec))
+ return false;
+
} else {
qWarning() << "clone/reflink not supported on windows outside of winbtrfs or ReFS!";
qWarning() << "check out https://github.com/maharmstone/btrfs for btrfs support!";
@@ -1154,6 +1178,14 @@ bool clone_file(const QString& src, const QString& dst, std::error_code& ec)
#if defined(Q_OS_WIN)
typedef void (__stdcall *f_ReflinkCopyW)(HWND hwnd, HINSTANCE hinst, LPWSTR lpszCmdLine, int nCmdShow);
+const long WinMaxChunkSize = 1L << 31; // 2GB
+
+static long RoundUpToPowerOf2(long originalValue, long roundingMultiplePowerOf2)
+{
+ long mask = roundingMultiplePowerOf2 - 1;
+ return (originalValue + mask) & ~mask;
+}
+
bool winbtrfs_clone(const std::wstring& src_path, const std::wstring& dst_path, std::error_code& ec)
{
// https://github.com/maharmstone/btrfs
@@ -1194,33 +1226,118 @@ bool refs_clone(const std::wstring& src_path, const std::wstring& dst_path, std:
#if defined(FSCTL_DUPLICATE_EXTENTS_TO_FILE)
//https://learn.microsoft.com/en-us/windows/win32/api/winioctl/ni-winioctl-fsctl_duplicate_extents_to_file
//https://github.com/microsoft/CopyOnWrite/blob/main/lib/Windows/WindowsCopyOnWriteFilesystem.cs#L94
- std::wstring existingFile = src_path.c_str();
- std::wstring newLink = dst_path.c_str();
+ QString qSourcePath = StringUtils::fromStdString(src_path);
+ QString sourceVolumePath = statFS(qSourcePath).rootPath;
+ std::wstring source_volume_path = StringUtils::toStdString(sourceVolumePath);
+ unsigned long sectorsPerCluster;
+ unsigned long bytesPerSector;
+ unsigned long numberOfFreeClusters;
+ unsigned long totalNumberOfClusters;
+
+ if(!GetDiskFreeSpace(source_volume_path.c_str(), §orsPerCluster, &bytesPerSector, &numberOfFreeClusters, &totalNumberOfClusters )){
+ ec = std::error_code(GetLastError(), std::system_category());
+ qDebug() << "Failed to get disk info for source volume" << sourceVolumePath;
+ return false;
+ }
+
+ long srcClusterSize = (long)(sectorsPerCluster * bytesPerSector);
- HANDLE hExistingFile = CreateFile(src_path.c_str(), GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);
- if (hExistingFile == INVALID_HANDLE_VALUE)
+ HANDLE hSourceFile = CreateFile(src_path.c_str(), GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);
+ if (hSourceFile == INVALID_HANDLE_VALUE)
{
+ ec = std::error_code(GetLastError(), std::system_category());
+ qDebug() << "Failed to open source file" << src_path.c_str();
return false;
}
- HANDLE hNewFile = CreateFile(dst_path.c_str(), GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, 0, NULL);
- if (hNewFile == INVALID_HANDLE_VALUE)
+ HANDLE hDestFile = CreateFile(dst_path.c_str(), GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, 0, NULL);
+ if (hDestFile == INVALID_HANDLE_VALUE)
{
- CloseHandle(hExistingFile);
+ ec = std::error_code(GetLastError(), std::system_category());
+ CloseHandle(hSourceFile);
+ qDebug() << "Failed to open dest file" << dst_path.c_str();
return false;
}
- DWORD bytesReturned;
+ DWORD bytesReturned = 0;
- // FIXME: ReFS requires that cloned regions reside on a disk cluster boundary.
- // FIXME: ERROR_BLOCK_TOO_MANY_REFERENCES can occure https://docs.microsoft.com/en-us/windows-server/storage/refs/block-cloning#functionality-restrictions-and-remarks
- BOOL result = DeviceIoControl(hExistingFile, FSCTL_DUPLICATE_EXTENTS_TO_FILE, hNewFile, sizeof(hNewFile), NULL, 0, &bytesReturned, NULL);
+ // Set the destination to be sparse while we clone.
+ // Important to avoid allocating zero-backed real storage when calling SetFileInformationByHandle()
+ // below which will just be released when cloning file extents.
+
+ if (!DeviceIoControl(hDestFile, FSCTL_SET_SPARSE, nullptr, 0, nullptr, 0, &bytesReturned, nullptr)) {
+ qDebug() << "Failed to set file sparseness for destination file" << dst_path.c_str();
+ ec = std::error_code(GetLastError(), std::system_category());
+ return false;
+ }
- CloseHandle(hNewFile);
- CloseHandle(hExistingFile);
+ LARGE_INTEGER sourceFileLengthStruct;
+ if (!GetFileSizeEx(hSourceFile, &sourceFileLengthStruct)) {
+ ec = std::error_code(GetLastError(), std::system_category());
+ qDebug() << "Failed to get file info for source file" << src_path.c_str();
+ return false;
+ }
- return (result != 0);
+ long sourceFileLength = sourceFileLengthStruct.QuadPart;
+
+ // Set the destination on-disk size the same as the source.
+ FILE_END_OF_FILE_INFO fileSizeInfo{sourceFileLength};
+ if (!SetFileInformationByHandle(hDestFile, FILE_INFO_BY_HANDLE_CLASS::FileEndOfFileInfo,
+ &fileSizeInfo, sizeof(FILE_END_OF_FILE_INFO)))
+ {
+ ec = std::error_code(GetLastError(), std::system_category());
+ qDebug() << "Failed to set end of file on destination file" << dst_path.c_str();
+ return false;
+ }
+
+ DUPLICATE_EXTENTS_DATA duplicateExtentsData = DUPLICATE_EXTENTS_DATA{ hSourceFile };
+
+ long fileSizeRoundedUpToClusterBoundary = RoundUpToPowerOf2(sourceFileLength, srcClusterSize);
+ long sourceOffset = 0;
+ while(sourceOffset < sourceFileLength)
+ {
+ duplicateExtentsData.SourceFileOffset.QuadPart = sourceOffset;
+ duplicateExtentsData.TargetFileOffset.QuadPart = sourceOffset;
+ long thisChunkSize = std::min(fileSizeRoundedUpToClusterBoundary - sourceOffset, WinMaxChunkSize);
+ duplicateExtentsData.ByteCount.QuadPart = thisChunkSize;
+
+ DWORD numBytesReturned = 0;
+ bool ioctlResult = DeviceIoControl(
+ hDestFile,
+ FSCTL_DUPLICATE_EXTENTS_TO_FILE,
+ &duplicateExtentsData,
+ sizeof(DUPLICATE_EXTENTS_DATA),
+ nullptr,
+ 0,
+ &numBytesReturned,
+ nullptr);
+ if (!ioctlResult)
+ {
+ DWORD err = GetLastError();
+ ec = std::error_code(err, std::system_category());
+ QString additionalMessage;
+ if (err == ERROR_BLOCK_TOO_MANY_REFERENCES)
+ {
+ static const int MaxClonesPerFile = 8175;
+ additionalMessage = QString(
+ " This is ERROR_BLOCK_TOO_MANY_REFERENCES and may mean you have surpassed the maximum "
+ "allowed %1 references for a single file. "
+ "See https://docs.microsoft.com/en-us/windows-server/storage/refs/block-cloning#functionality-restrictions-and-remarks"
+ ).arg(MaxClonesPerFile);
+
+ }
+ qWarning() << "Failed copy-on-write cloning from source file" << src_path.c_str() << "to" << dst_path.c_str() << "." << additionalMessage;
+ return false;
+ }
+
+ sourceOffset += thisChunkSize;
+ }
+
+ CloseHandle(hDestFile);
+ CloseHandle(hSourceFile);
+
+ return true;
#else
ec = std::make_error_code(std::errc::not_supported);
qWarning() << "not built with refs support";
@@ -1228,6 +1345,181 @@ bool refs_clone(const std::wstring& src_path, const std::wstring& dst_path, std:
#endif
}
+bool ioctl_clone(const std::wstring& src_path, const std::wstring& dst_path, std::error_code& ec)
+{
+ /**
+ * This algorithm inspired from https://github.com/0xbadfca11/reflink
+ * LICENSE MIT
+ *
+ */
+
+ HANDLE hSourceFile = CreateFileW(src_path.c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, 0, nullptr);
+ if (hSourceFile == INVALID_HANDLE_VALUE)
+ {
+ ec = std::error_code(GetLastError(), std::system_category());
+ qDebug() << "Failed to open source file" << src_path.c_str();
+ return false;
+ }
+
+ ULONG fs_flags;
+ if (!GetVolumeInformationByHandleW(hSourceFile, nullptr, 0, nullptr, nullptr, &fs_flags, nullptr, 0))
+ {
+ ec = std::error_code(GetLastError(), std::system_category());
+ qDebug() << "Failed to get Filesystem information for " << src_path.c_str();
+ CloseHandle(hSourceFile);
+ return false;
+ }
+ if (!(fs_flags & FILE_SUPPORTS_BLOCK_REFCOUNTING))
+ {
+ SetLastError(ERROR_NOT_CAPABLE);
+ ec = std::error_code(GetLastError(), std::system_category());
+ qWarning() << "Filesystem at " << src_path.c_str() << " does not support reflink";
+ CloseHandle(hSourceFile);
+ return false;
+ }
+
+ FILE_END_OF_FILE_INFO sourceFileLength;
+ if (!GetFileSizeEx(hSourceFile, &sourceFileLength.EndOfFile))
+ {
+ ec = std::error_code(GetLastError(), std::system_category());
+ qDebug() << "Failed to size of source file" << src_path.c_str();
+ CloseHandle(hSourceFile);
+ return false;
+ }
+ FILE_BASIC_INFO sourceFileBasicInfo;
+ if (!GetFileInformationByHandleEx(hSourceFile, FileBasicInfo, &sourceFileBasicInfo, sizeof(sourceFileBasicInfo)))
+ {
+ ec = std::error_code(GetLastError(), std::system_category());
+ qDebug() << "Failed to source file info" << src_path.c_str();
+ CloseHandle(hSourceFile);
+ return false;
+ }
+ ULONG junk;
+ FSCTL_GET_INTEGRITY_INFORMATION_BUFFER sourceFileIntegrity;
+ if (!DeviceIoControl(hSourceFile, FSCTL_GET_INTEGRITY_INFORMATION, nullptr, 0, &sourceFileIntegrity, sizeof(sourceFileIntegrity), &junk, nullptr))
+ {
+ ec = std::error_code(GetLastError(), std::system_category());
+ qDebug() << "Failed to source file integrity info" << src_path.c_str();
+ CloseHandle(hSourceFile);
+ return false;
+ }
+
+ HANDLE hDestFile = CreateFileW(dst_path.c_str(), GENERIC_READ | GENERIC_WRITE | DELETE, 0, nullptr, CREATE_NEW, 0, hSourceFile);
+
+ if (hDestFile == INVALID_HANDLE_VALUE)
+ {
+ ec = std::error_code(GetLastError(), std::system_category());
+ qDebug() << "Failed to open dest file" << dst_path.c_str();
+ CloseHandle(hSourceFile);
+ return false;
+ }
+ FILE_DISPOSITION_INFO destFileDispose = { TRUE };
+ if (!SetFileInformationByHandle(hDestFile, FileDispositionInfo, &destFileDispose, sizeof(destFileDispose)))
+ {
+ ec = std::error_code(GetLastError(), std::system_category());
+ qDebug() << "Failed to set dest file info" << dst_path.c_str();
+ CloseHandle(hSourceFile);
+ CloseHandle(hDestFile);
+ return false;
+ }
+
+ if (!DeviceIoControl(hDestFile, FSCTL_SET_SPARSE, nullptr, 0, nullptr, 0, &junk, nullptr))
+ {
+ ec = std::error_code(GetLastError(), std::system_category());
+ qDebug() << "Failed to set dest sparseness" << dst_path.c_str();
+ CloseHandle(hSourceFile);
+ CloseHandle(hDestFile);
+ return false;
+ }
+ FSCTL_SET_INTEGRITY_INFORMATION_BUFFER setDestFileintegrity = { sourceFileIntegrity.ChecksumAlgorithm, sourceFileIntegrity.Reserved, sourceFileIntegrity.Flags };
+ if (!DeviceIoControl(hDestFile, FSCTL_SET_INTEGRITY_INFORMATION, &setDestFileintegrity, sizeof(setDestFileintegrity), nullptr, 0, nullptr, nullptr))
+ {
+ ec = std::error_code(GetLastError(), std::system_category());
+ qDebug() << "Failed to set dest file integrity info" << dst_path.c_str();
+ CloseHandle(hSourceFile);
+ CloseHandle(hDestFile);
+ return false;
+ }
+ if (!SetFileInformationByHandle(hDestFile, FileEndOfFileInfo, &sourceFileLength, sizeof(sourceFileLength)))
+ {
+ ec = std::error_code(GetLastError(), std::system_category());
+ qDebug() << "Failed to set dest file size" << dst_path.c_str();
+ CloseHandle(hSourceFile);
+ CloseHandle(hDestFile);
+ return false;
+ }
+
+ const LONG64 splitThreshold = (1LL << 32) - sourceFileIntegrity.ClusterSizeInBytes;
+
+ DUPLICATE_EXTENTS_DATA dupExtent;
+ dupExtent.FileHandle = hSourceFile;
+ for (LONG64 offset = 0, remain = RoundUpToPowerOf2(sourceFileLength.EndOfFile.QuadPart, sourceFileIntegrity.ClusterSizeInBytes); remain > 0; offset += splitThreshold, remain -= splitThreshold)
+ {
+ dupExtent.SourceFileOffset.QuadPart = dupExtent.TargetFileOffset.QuadPart = offset;
+ dupExtent.ByteCount.QuadPart = std::min(splitThreshold, remain);
+
+ _ASSERTE(dupExtent.SourceFileOffset.QuadPart % sourceFileIntegrity.ClusterSizeInBytes == 0);
+ _ASSERTE(dupExtent.ByteCount.QuadPart % sourceFileIntegrity.ClusterSizeInBytes == 0);
+ _ASSERTE(dupExtent.ByteCount.QuadPart <= UINT32_MAX);
+ _RPT3(_CRT_WARN, "Remain=%llx\nOffset=%llx\nLength=%llx\n\n", remain, dupExtent.SourceFileOffset.QuadPart, dupExtent.ByteCount.QuadPart);
+
+ if (!DeviceIoControl(hDestFile, FSCTL_DUPLICATE_EXTENTS_TO_FILE, &dupExtent, sizeof(dupExtent), nullptr, 0, &junk, nullptr))
+ {
+ DWORD err = GetLastError();
+ QString additionalMessage;
+ if (err == ERROR_BLOCK_TOO_MANY_REFERENCES)
+ {
+ static const int MaxClonesPerFile = 8175;
+ additionalMessage = QString(
+ " This is ERROR_BLOCK_TOO_MANY_REFERENCES and may mean you have surpassed the maximum "
+ "allowed %1 references for a single file. "
+ "See https://docs.microsoft.com/en-us/windows-server/storage/refs/block-cloning#functionality-restrictions-and-remarks"
+ ).arg(MaxClonesPerFile);
+
+ }
+ ec = std::error_code(err, std::system_category());
+ qDebug() << "Failed copy-on-write cloning of" << src_path.c_str() << "to" << dst_path.c_str() << "with error" << err << additionalMessage;
+ CloseHandle(hSourceFile);
+ CloseHandle(hDestFile);
+ return false;
+ }
+ }
+
+ if (!(sourceFileBasicInfo.FileAttributes & FILE_ATTRIBUTE_SPARSE_FILE))
+ {
+ FILE_SET_SPARSE_BUFFER setDestSparse = { FALSE };
+ if (!DeviceIoControl(hDestFile, FSCTL_SET_SPARSE, &setDestSparse, sizeof(setDestSparse), nullptr, 0, &junk, nullptr))
+ {
+ qDebug() << "Failed to set dest file sparseness" << dst_path.c_str();
+ CloseHandle(hSourceFile);
+ CloseHandle(hDestFile);
+ return false;
+ }
+ }
+
+ sourceFileBasicInfo.CreationTime.QuadPart = 0;
+ if (!SetFileInformationByHandle(hDestFile, FileBasicInfo, &sourceFileBasicInfo, sizeof(sourceFileBasicInfo)))
+ {
+ qDebug() << "Failed to set dest file creation time" << dst_path.c_str();
+ CloseHandle(hSourceFile);
+ CloseHandle(hDestFile);
+ return false;
+ }
+ if (!FlushFileBuffers(hDestFile))
+ {
+ qDebug() << "Failed to flush dest file buffer" << dst_path.c_str();
+ CloseHandle(hSourceFile);
+ CloseHandle(hDestFile);
+ return false;
+ }
+ destFileDispose = { FALSE };
+ bool result = !!SetFileInformationByHandle(hDestFile, FileDispositionInfo, &destFileDispose, sizeof(destFileDispose));
+
+ CloseHandle(hSourceFile);
+ CloseHandle(hDestFile);
+
+ return result;
+}
#elif defined(Q_OS_LINUX)
bool linux_ficlone(const std::string& src_path, const std::string& dst_path, std::error_code& ec)
@@ -1236,14 +1528,14 @@ bool linux_ficlone(const std::string& src_path, const std::string& dst_path, std
int src_fd = open(src_path.c_str(), O_RDONLY);
if(src_fd == -1) {
- qWarning() << "Failed to open file:" << src_path.c_str();
+ qDebug() << "Failed to open file:" << src_path.c_str();
qDebug() << "Error:" << strerror(errno);
ec = std::make_error_code(static_cast(errno));
return false;
}
int dst_fd = open(dst_path.c_str(), O_CREAT | O_WRONLY | O_TRUNC, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);
if(dst_fd == -1) {
- qWarning() << "Failed to open file:" << dst_path.c_str();
+ qDebug() << "Failed to open file:" << dst_path.c_str();
qDebug() << "Error:" << strerror(errno);
ec = std::make_error_code(static_cast(errno));
close(src_fd);
@@ -1251,7 +1543,7 @@ bool linux_ficlone(const std::string& src_path, const std::string& dst_path, std
}
// attempt to clone
if(ioctl(dst_fd, FICLONE, src_fd) == -1){
- qWarning() << "Failed to clone file:" << src_path.c_str() << "to" << dst_path.c_str();
+ qDebug() << "Failed to clone file:" << src_path.c_str() << "to" << dst_path.c_str();
qDebug() << "Error:" << strerror(errno);
ec = std::make_error_code(static_cast(errno));
close(src_fd);
@@ -1259,11 +1551,11 @@ bool linux_ficlone(const std::string& src_path, const std::string& dst_path, std
return false;
}
if(close(src_fd)) {
- qWarning() << "Failed to close file:" << src_path.c_str();
+ qDebug() << "Failed to close file:" << src_path.c_str();
qDebug() << "Error:" << strerror(errno);
}
if(close(dst_fd)) {
- qWarning() << "Failed to close file:" << dst_path.c_str();
+ qDebug() << "Failed to close file:" << dst_path.c_str();
qDebug() << "Error:" << strerror(errno);
}
return true;
@@ -1277,7 +1569,7 @@ bool macos_bsd_clonefile(const std::string& src_path, const std::string& dst_pat
qDebug() << "attempting file clone via clonefile" << src_path.c_str() << "to" << dst_path.c_str();
if (clonefile(src_path.c_str(), dst_path.c_str(), 0) == -1) {
- qWarning() << "Failed to clone file:" << src_path.c_str() << "to" << dst_path.c_str();
+ qDebug() << "Failed to clone file:" << src_path.c_str() << "to" << dst_path.c_str();
qDebug() << "Error:" << strerror(errno);
ec = std::make_error_code(static_cast(errno));
return false;
diff --git a/launcher/FileSystem.h b/launcher/FileSystem.h
index 3f6b78e5..4c011309 100644
--- a/launcher/FileSystem.h
+++ b/launcher/FileSystem.h
@@ -409,6 +409,7 @@ inline QString getFilesystemTypeName(FilesystemType type) {
struct FilesystemInfo {
FilesystemType fsType = FilesystemType::UNKNOWN;
+ QString fsTypeName;
int blockSize;
qint64 bytesAvailable;
qint64 bytesFree;
@@ -496,8 +497,17 @@ class clone : public QObject {
bool clone_file(const QString& src, const QString& dst, std::error_code& ec);
#if defined(Q_OS_WIN)
+
+#ifndef FSCTL_DUPLICATE_EXTENTS_TO_FILE
+#define FSCTL_DUPLICATE_EXTENTS_TO_FILE CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 209, METHOD_BUFFERED, FILE_WRITE_DATA )
+#endif
+
bool winbtrfs_clone(const std::wstring& src_path, const std::wstring& dst_path, std::error_code& ec);
bool refs_clone(const std::wstring& src_path, const std::wstring& dst_path, std::error_code& ec);
+bool ioctl_clone(const std::wstring& src_path, const std::wstring& dst_path, std::error_code& ec);
+
+#define USE_IOCTL_CLONE true
+
#elif defined(Q_OS_LINUX)
bool linux_ficlone(const std::string& src_path, const std::string& dst_path, std::error_code& ec);
#elif defined(Q_OS_MACOS) || defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD)
From 7870cf28e55c090543591304b05a7ef5031e1157 Mon Sep 17 00:00:00 2001
From: Rachel Powers <508861+Ryex@users.noreply.github.com>
Date: Fri, 10 Feb 2023 19:06:49 -0800
Subject: [PATCH 021/104] fix: add missing mingw defs
Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com>
---
launcher/FileSystem.cpp | 39 ++++++++++++++++++++++++++++++++++++++-
launcher/FileSystem.h | 4 ----
2 files changed, 38 insertions(+), 5 deletions(-)
diff --git a/launcher/FileSystem.cpp b/launcher/FileSystem.cpp
index 948ec55e..a9461bb0 100644
--- a/launcher/FileSystem.cpp
+++ b/launcher/FileSystem.cpp
@@ -117,8 +117,45 @@ namespace fs = ghc::filesystem;
#include
// refs
#include
+# if defined(__MINGW32__)
+# include
+# endif
#endif
+#if defined(Q_OS_WIN) && defined(__MINGW32__)
+
+typedef struct _DUPLICATE_EXTENTS_DATA {
+ HANDLE FileHandle;
+ LARGE_INTEGER SourceFileOffset;
+ LARGE_INTEGER TargetFileOffset;
+ LARGE_INTEGER ByteCount;
+} DUPLICATE_EXTENTS_DATA, *PDUPLICATE_EXTENTS_DATA;
+
+typedef struct _FSCTL_GET_INTEGRITY_INFORMATION_BUFFER {
+ WORD ChecksumAlgorithm; // Checksum algorithm. e.g. CHECKSUM_TYPE_UNCHANGED, CHECKSUM_TYPE_NONE, CHECKSUM_TYPE_CRC32
+ WORD Reserved; // Must be 0
+ DWORD Flags; // FSCTL_INTEGRITY_FLAG_xxx
+ DWORD ChecksumChunkSizeInBytes;
+ DWORD ClusterSizeInBytes;
+} FSCTL_GET_INTEGRITY_INFORMATION_BUFFER, *PFSCTL_GET_INTEGRITY_INFORMATION_BUFFER;
+
+typedef struct _FSCTL_SET_INTEGRITY_INFORMATION_BUFFER {
+ WORD ChecksumAlgorithm; // Checksum algorithm. e.g. CHECKSUM_TYPE_UNCHANGED, CHECKSUM_TYPE_NONE, CHECKSUM_TYPE_CRC32
+ WORD Reserved; // Must be 0
+ DWORD Flags; // FSCTL_INTEGRITY_FLAG_xxx
+} FSCTL_SET_INTEGRITY_INFORMATION_BUFFER, *PFSCTL_SET_INTEGRITY_INFORMATION_BUFFER;
+
+#define FSCTL_DUPLICATE_EXTENTS_TO_FILE CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 209, METHOD_BUFFERED, FILE_WRITE_DATA )
+#define FSCTL_GET_INTEGRITY_INFORMATION CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 159, METHOD_BUFFERED, FILE_ANY_ACCESS) // FSCTL_GET_INTEGRITY_INFORMATION_BUFFER
+#define FSCTL_SET_INTEGRITY_INFORMATION CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 160, METHOD_BUFFERED, FILE_READ_DATA | FILE_WRITE_DATA) // FSCTL_SET_INTEGRITY_INFORMATION_BUFFER
+
+
+#define ERROR_NOT_CAPABLE 775L
+#define ERROR_BLOCK_TOO_MANY_REFERENCES 347L
+
+#endif
+
+
namespace FS {
void ensureExists(const QDir& dir)
@@ -1279,7 +1316,7 @@ bool refs_clone(const std::wstring& src_path, const std::wstring& dst_path, std:
return false;
}
- long sourceFileLength = sourceFileLengthStruct.QuadPart;
+ DWORD sourceFileLength = sourceFileLengthStruct.QuadPart;
// Set the destination on-disk size the same as the source.
FILE_END_OF_FILE_INFO fileSizeInfo{sourceFileLength};
diff --git a/launcher/FileSystem.h b/launcher/FileSystem.h
index 4c011309..cafbd2a8 100644
--- a/launcher/FileSystem.h
+++ b/launcher/FileSystem.h
@@ -498,10 +498,6 @@ bool clone_file(const QString& src, const QString& dst, std::error_code& ec);
#if defined(Q_OS_WIN)
-#ifndef FSCTL_DUPLICATE_EXTENTS_TO_FILE
-#define FSCTL_DUPLICATE_EXTENTS_TO_FILE CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 209, METHOD_BUFFERED, FILE_WRITE_DATA )
-#endif
-
bool winbtrfs_clone(const std::wstring& src_path, const std::wstring& dst_path, std::error_code& ec);
bool refs_clone(const std::wstring& src_path, const std::wstring& dst_path, std::error_code& ec);
bool ioctl_clone(const std::wstring& src_path, const std::wstring& dst_path, std::error_code& ec);
From 9f441a9678f56c5fb5efbc415b3faff176609b9c Mon Sep 17 00:00:00 2001
From: Rachel Powers <508861+Ryex@users.noreply.github.com>
Date: Sat, 11 Feb 2023 22:51:53 -0800
Subject: [PATCH 022/104] feat: Add UAC icon when symlinking on windows.
Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com>
---
launcher/CMakeLists.txt | 8 --------
launcher/ui/dialogs/CopyInstanceDialog.cpp | 23 +++++++++++++++++-----
launcher/ui/dialogs/CopyInstanceDialog.ui | 5 ++++-
3 files changed, 22 insertions(+), 14 deletions(-)
diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt
index dd62893c..b47f5746 100644
--- a/launcher/CMakeLists.txt
+++ b/launcher/CMakeLists.txt
@@ -1149,14 +1149,6 @@ if(WIN32)
SET_TARGET_PROPERTIES("${Launcher_Name}_filelink" PROPERTIES INSTALL_RPATH "${Launcher_BINARY_RPATH}")
endif()
- # may be unnessacery with manifest
- if(CMAKE_GENERATOR MATCHES "Visual Studio")
- SET_TARGET_PROPERTIES("${Launcher_Name}_filelink" PROPERTIES LINK_FLAGS "/level='requireAdministrator' /uiAccess='false' /SUBSYSTEM:CONSOLE")
- # else() # link arg /MANIFESTUAC only works with MSVC
- # SET_TARGET_PROPERTIES("${Launcher_Name}_filelink" PROPERTIES LINK_FLAGS "/MANIFESTUAC:\"level='requireAdministrator' uiAccess='false'\" /SUBSYSTEM:CONSOLE")
- endif()
-
-
install(TARGETS "${Launcher_Name}_filelink"
BUNDLE DESTINATION "." COMPONENT Runtime
LIBRARY DESTINATION ${LIBRARY_DEST_DIR} COMPONENT Runtime
diff --git a/launcher/ui/dialogs/CopyInstanceDialog.cpp b/launcher/ui/dialogs/CopyInstanceDialog.cpp
index 9fe129f1..495e98e9 100644
--- a/launcher/ui/dialogs/CopyInstanceDialog.cpp
+++ b/launcher/ui/dialogs/CopyInstanceDialog.cpp
@@ -93,17 +93,25 @@ CopyInstanceDialog::CopyInstanceDialog(InstancePtr original, QWidget *parent)
ui->recursiveLinkCheckbox->setChecked(m_selectedOptions.isLinkRecursivelyEnabled());
ui->dontLinkSavesCheckbox->setChecked(m_selectedOptions.isDontLinkSavesEnabled());
- auto detectedOS = FS::statFS(m_original->instanceRoot()).fsType;
+ auto detectedFS = FS::statFS(m_original->instanceRoot()).fsType;
- m_cloneSupported = FS::canCloneOnFS(detectedOS);
- m_linkSupported = FS::canLinkOnFS(detectedOS);
+ m_cloneSupported = FS::canCloneOnFS(detectedFS);
+ m_linkSupported = FS::canLinkOnFS(detectedFS);
if (m_cloneSupported) {
- ui->cloneSupportedLabel->setText(tr("Reflinks are supported on %1").arg(FS::getFilesystemTypeName(detectedOS)));
+ ui->cloneSupportedLabel->setText(tr("Reflinks are supported on %1").arg(FS::getFilesystemTypeName(detectedFS)));
} else {
- ui->cloneSupportedLabel->setText(tr("Reflinks aren't supported on %1").arg(FS::getFilesystemTypeName(detectedOS)));
+ ui->cloneSupportedLabel->setText(tr("Reflinks aren't supported on %1").arg(FS::getFilesystemTypeName(detectedFS)));
}
+#if defined(Q_OS_WIN)
+ ui->symbolicLinksCheckbox->setIcon(style()->standardIcon(QStyle::SP_VistaShield));
+ ui->symbolicLinksCheckbox->setToolTip(
+ tr("Use symbolic links instead of copying files.") +
+ tr("\nOn windows symbolic links may require admin permision to create.")
+ );
+#endif
+
updateLinkOptions();
updateUseCloneCheckbox();
@@ -189,6 +197,11 @@ void CopyInstanceDialog::updateLinkOptions()
ui->dontLinkSavesCheckbox->setEnabled(m_linkSupported && linksInUse);
ui->recursiveLinkCheckbox->setChecked(m_linkSupported && linksInUse && m_selectedOptions.isLinkRecursivelyEnabled());
ui->dontLinkSavesCheckbox->setChecked(m_linkSupported && linksInUse && m_selectedOptions.isDontLinkSavesEnabled());
+
+#if defined(Q_OS_WIN)
+ auto OkButton = ui->buttonBox->button(QDialogButtonBox::Ok);
+ OkButton->setIcon(m_selectedOptions.isUseSymLinksEnabled() ? style()->standardIcon(QStyle::SP_VistaShield) : QIcon());
+#endif
}
void CopyInstanceDialog::on_iconButton_clicked()
diff --git a/launcher/ui/dialogs/CopyInstanceDialog.ui b/launcher/ui/dialogs/CopyInstanceDialog.ui
index 7bf75c2d..58442f73 100644
--- a/launcher/ui/dialogs/CopyInstanceDialog.ui
+++ b/launcher/ui/dialogs/CopyInstanceDialog.ui
@@ -269,7 +269,7 @@
true
- Use hard links instead of symbolic links
+ Use hard links instead of symbolic links.
Use hard links
@@ -307,6 +307,9 @@
Use symbloic links
+
+ Use symbolic links instead of copying files.
+
From a1053a4c5ac8651be3b57814918e28179eb2a1f9 Mon Sep 17 00:00:00 2001
From: Rachel Powers <508861+Ryex@users.noreply.github.com>
Date: Sun, 12 Feb 2023 02:44:39 -0700
Subject: [PATCH 023/104] feat: warnings when instance resources are linked
Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com>
---
launcher/FileSystem.cpp | 4 +++
launcher/FileSystem.h | 2 ++
launcher/minecraft/World.cpp | 24 +++++++++++++++
launcher/minecraft/World.h | 15 ++++++++++
launcher/minecraft/WorldList.cpp | 30 +++++++++++++++++--
launcher/minecraft/WorldList.h | 5 +++-
launcher/minecraft/mod/ModFolderModel.cpp | 20 +++++++++++++
launcher/minecraft/mod/Resource.cpp | 20 +++++++++++++
launcher/minecraft/mod/Resource.h | 13 ++++++++
.../minecraft/mod/ResourceFolderModel.cpp | 26 ++++++++++++++++
launcher/minecraft/mod/ResourceFolderModel.h | 2 ++
.../minecraft/mod/ResourcePackFolderModel.cpp | 20 +++++++++++++
launcher/ui/pages/instance/WorldListPage.cpp | 1 +
13 files changed, 179 insertions(+), 3 deletions(-)
diff --git a/launcher/FileSystem.cpp b/launcher/FileSystem.cpp
index a9461bb0..af2ba299 100644
--- a/launcher/FileSystem.cpp
+++ b/launcher/FileSystem.cpp
@@ -1641,5 +1641,9 @@ bool canLink(const QString& src, const QString& dst)
return canLinkOnFS(src) && canLinkOnFS(dst);
}
+uintmax_t hardLinkCount(const QString& path)
+{
+ return fs::hard_link_count(StringUtils::toStdString(path));
+}
}
diff --git a/launcher/FileSystem.h b/launcher/FileSystem.h
index cafbd2a8..361993eb 100644
--- a/launcher/FileSystem.h
+++ b/launcher/FileSystem.h
@@ -528,4 +528,6 @@ bool canLinkOnFS(FilesystemType type);
*/
bool canLink(const QString& src, const QString& dst);
+uintmax_t hardLinkCount(const QString& path);
+
}
diff --git a/launcher/minecraft/World.cpp b/launcher/minecraft/World.cpp
index d310f8b9..54fb9434 100644
--- a/launcher/minecraft/World.cpp
+++ b/launcher/minecraft/World.cpp
@@ -56,6 +56,8 @@
#include
+#include "FileSystem.h"
+
using std::optional;
using std::nullopt;
@@ -567,3 +569,25 @@ bool World::operator==(const World &other) const
{
return is_valid == other.is_valid && folderName() == other.folderName();
}
+
+bool World::isSymLinkUnder(const QString& instPath) const
+{
+ if (isSymLink())
+ return true;
+
+ auto instDir = QDir(instPath);
+
+ auto relAbsPath = instDir.relativeFilePath(m_containerFile.absoluteFilePath());
+ auto relCanonPath = instDir.relativeFilePath(m_containerFile.canonicalFilePath());
+
+ return relAbsPath != relCanonPath;
+}
+
+bool World::isMoreThanOneHardLink() const
+{
+ if (m_containerFile.isDir())
+ {
+ return FS::hardLinkCount(QDir(m_containerFile.absoluteFilePath()).filePath("level.dat")) > 1;
+ }
+ return FS::hardLinkCount(m_containerFile.absoluteFilePath()) > 1;
+}
diff --git a/launcher/minecraft/World.h b/launcher/minecraft/World.h
index 8327253a..10328cce 100644
--- a/launcher/minecraft/World.h
+++ b/launcher/minecraft/World.h
@@ -95,6 +95,21 @@ public:
// WEAK compare operator - used for replacing worlds
bool operator==(const World &other) const;
+ [[nodiscard]] auto isSymLink() const -> bool{ return m_containerFile.isSymLink(); }
+
+ /**
+ * @brief Take a instance path, checks if the file pointed to by the resource is a symlink or under a symlink in that instance
+ *
+ * @param instPath path to an instance directory
+ * @return true
+ * @return false
+ */
+ [[nodiscard]] bool isSymLinkUnder(const QString& instPath) const;
+
+ [[nodiscard]] bool isMoreThanOneHardLink() const;
+
+ QString canonicalFilePath() const { return m_containerFile.canonicalFilePath(); }
+
private:
void readFromZip(const QFileInfo &file);
void readFromFS(const QFileInfo &file);
diff --git a/launcher/minecraft/WorldList.cpp b/launcher/minecraft/WorldList.cpp
index ae29a972..1262fa1d 100644
--- a/launcher/minecraft/WorldList.cpp
+++ b/launcher/minecraft/WorldList.cpp
@@ -128,6 +128,10 @@ bool WorldList::isValid()
return m_dir.exists() && m_dir.isReadable();
}
+QString WorldList::instDirPath() const {
+ return QFileInfo(m_dir.filePath("../..")).absoluteFilePath();
+}
+
bool WorldList::deleteWorld(int index)
{
if (index >= worlds.size() || index < 0)
@@ -173,7 +177,7 @@ bool WorldList::resetIcon(int row)
int WorldList::columnCount(const QModelIndex &parent) const
{
- return parent.isValid()? 0 : 4;
+ return parent.isValid()? 0 : 5;
}
QVariant WorldList::data(const QModelIndex &index, int role) const
@@ -207,6 +211,14 @@ QVariant WorldList::data(const QModelIndex &index, int role) const
case SizeColumn:
return locale.formattedDataSize(world.bytes());
+ case InfoColumn:
+ if (world.isSymLinkUnder(instDirPath())) {
+ return tr("This world is symbolicly linked from elsewhere.");
+ }
+ if (world.isMoreThanOneHardLink()) {
+ return tr("\nThis world is hard linked elsewhere.");
+ }
+ return "";
default:
return QVariant();
}
@@ -222,7 +234,16 @@ QVariant WorldList::data(const QModelIndex &index, int role) const
}
case Qt::ToolTipRole:
- {
+ {
+ if (column == InfoColumn) {
+ if (world.isSymLinkUnder(instDirPath())) {
+ return tr("Warning: This world is symbolicly linked from elsewhere. Editing it will also change the origonal") +
+ tr("\nCanonical Path: %1").arg(world.canonicalFilePath());
+ }
+ if (world.isMoreThanOneHardLink()) {
+ return tr("Warning: This world is hard linked elsewhere. Editing it will also change the origonal");
+ }
+ }
return world.folderName();
}
case ObjectRole:
@@ -274,6 +295,9 @@ QVariant WorldList::headerData(int section, Qt::Orientation orientation, int rol
case SizeColumn:
//: World size on disk
return tr("Size");
+ case InfoColumn:
+ //: special warnings?
+ return tr("Info");
default:
return QVariant();
}
@@ -289,6 +313,8 @@ QVariant WorldList::headerData(int section, Qt::Orientation orientation, int rol
return tr("Date and time the world was last played.");
case SizeColumn:
return tr("Size of the world on disk.");
+ case InfoColumn:
+ return tr("Information and warnings about the world.");
default:
return QVariant();
}
diff --git a/launcher/minecraft/WorldList.h b/launcher/minecraft/WorldList.h
index 08294755..bd32dd4e 100644
--- a/launcher/minecraft/WorldList.h
+++ b/launcher/minecraft/WorldList.h
@@ -33,7 +33,8 @@ public:
NameColumn,
GameModeColumn,
LastPlayedColumn,
- SizeColumn
+ SizeColumn,
+ InfoColumn
};
enum Roles
@@ -112,6 +113,8 @@ public:
return m_dir;
}
+ QString instDirPath() const;
+
const QList &allWorlds() const
{
return worlds;
diff --git a/launcher/minecraft/mod/ModFolderModel.cpp b/launcher/minecraft/mod/ModFolderModel.cpp
index 3f31b93c..e2053f92 100644
--- a/launcher/minecraft/mod/ModFolderModel.cpp
+++ b/launcher/minecraft/mod/ModFolderModel.cpp
@@ -39,13 +39,17 @@
#include
#include
#include
+#include
#include
#include
+#include
#include
#include
#include
#include
+#include "Application.h"
+
#include "minecraft/mod/tasks/LocalModParseTask.h"
#include "minecraft/mod/tasks/ModFolderLoadTask.h"
#include "modplatform/ModIndex.h"
@@ -97,8 +101,24 @@ QVariant ModFolderModel::data(const QModelIndex &index, int role) const
}
case Qt::ToolTipRole:
+ if (column == NAME_COLUMN) {
+ if (at(row)->isSymLinkUnder(instDirPath())) {
+ return m_resources[row]->internal_id() +
+ tr("\nWarning: This resource is symbolicly linked from elsewhere. Editing it will also change the origonal") +
+ tr("\nCanonical Path: %1").arg(at(row)->fileinfo().canonicalFilePath());
+ }
+ if (at(row)->isMoreThanOneHardLink()) {
+ return m_resources[row]->internal_id() +
+ tr("\nWarning: This resource is hard linked elsewhere. Editing it will also change the origonal");
+ }
+ }
return m_resources[row]->internal_id();
+ case Qt::DecorationRole: {
+ if (column == NAME_COLUMN && (at(row)->isSymLinkUnder(instDirPath()) || at(row)->isMoreThanOneHardLink()))
+ return APPLICATION->getThemedIcon("status-yellow");
+ return {};
+ }
case Qt::CheckStateRole:
switch (column)
{
diff --git a/launcher/minecraft/mod/Resource.cpp b/launcher/minecraft/mod/Resource.cpp
index 0d35d755..a0b8a4bb 100644
--- a/launcher/minecraft/mod/Resource.cpp
+++ b/launcher/minecraft/mod/Resource.cpp
@@ -1,6 +1,8 @@
#include "Resource.h"
+
#include
+#include
#include "FileSystem.h"
@@ -152,3 +154,21 @@ bool Resource::destroy()
return FS::deletePath(m_file_info.filePath());
}
+
+bool Resource::isSymLinkUnder(const QString& instPath) const
+{
+ if (isSymLink())
+ return true;
+
+ auto instDir = QDir(instPath);
+
+ auto relAbsPath = instDir.relativeFilePath(m_file_info.absoluteFilePath());
+ auto relCanonPath = instDir.relativeFilePath(m_file_info.canonicalFilePath());
+
+ return relAbsPath != relCanonPath;
+}
+
+bool Resource::isMoreThanOneHardLink() const
+{
+ return FS::hardLinkCount(m_file_info.absoluteFilePath()) > 1;
+}
diff --git a/launcher/minecraft/mod/Resource.h b/launcher/minecraft/mod/Resource.h
index 0c37f3a3..a5e9ae91 100644
--- a/launcher/minecraft/mod/Resource.h
+++ b/launcher/minecraft/mod/Resource.h
@@ -94,6 +94,19 @@ class Resource : public QObject {
// Delete all files of this resource.
bool destroy();
+ [[nodiscard]] auto isSymLink() const -> bool { return m_file_info.isSymLink(); }
+
+ /**
+ * @brief Take a instance path, checks if the file pointed to by the resource is a symlink or under a symlink in that instance
+ *
+ * @param instPath path to an instance directory
+ * @return true
+ * @return false
+ */
+ [[nodiscard]] bool isSymLinkUnder(const QString& instPath) const;
+
+ [[nodiscard]] bool isMoreThanOneHardLink() const;
+
protected:
/* The file corresponding to this resource. */
QFileInfo m_file_info;
diff --git a/launcher/minecraft/mod/ResourceFolderModel.cpp b/launcher/minecraft/mod/ResourceFolderModel.cpp
index f2a77c12..d1748c1c 100644
--- a/launcher/minecraft/mod/ResourceFolderModel.cpp
+++ b/launcher/minecraft/mod/ResourceFolderModel.cpp
@@ -2,10 +2,14 @@
#include
#include
+#include
+#include
#include
+#include
#include
#include
+#include "Application.h"
#include "FileSystem.h"
#include "minecraft/mod/tasks/BasicFolderLoadTask.h"
@@ -417,7 +421,25 @@ QVariant ResourceFolderModel::data(const QModelIndex& index, int role) const
return {};
}
case Qt::ToolTipRole:
+ if (column == NAME_COLUMN) {
+ if (at(row).isSymLinkUnder(instDirPath())) {
+ return m_resources[row]->internal_id() +
+ tr("\nWarning: This resource is symbolicly linked from elsewhere. Editing it will also change the origonal") +
+ tr("\nCanonical Path: %1").arg(at(row).fileinfo().canonicalFilePath());;
+ }
+ if (at(row).isMoreThanOneHardLink()) {
+ return m_resources[row]->internal_id() +
+ tr("\nWarning: This resource is hard linked elsewhere. Editing it will also change the origonal");
+ }
+ }
+
return m_resources[row]->internal_id();
+ case Qt::DecorationRole: {
+ if (column == NAME_COLUMN && (at(row).isSymLinkUnder(instDirPath()) || at(row).isMoreThanOneHardLink()))
+ return APPLICATION->getThemedIcon("status-yellow");
+
+ return {};
+ }
case Qt::CheckStateRole:
switch (column) {
case ACTIVE_COLUMN:
@@ -531,3 +553,7 @@ void ResourceFolderModel::enableInteraction(bool enabled)
return (compare_result.first < 0);
return (compare_result.first > 0);
}
+
+QString ResourceFolderModel::instDirPath() const {
+ return QFileInfo(m_dir.filePath("../..")).absoluteFilePath();
+}
diff --git a/launcher/minecraft/mod/ResourceFolderModel.h b/launcher/minecraft/mod/ResourceFolderModel.h
index 3bd78870..f840b2de 100644
--- a/launcher/minecraft/mod/ResourceFolderModel.h
+++ b/launcher/minecraft/mod/ResourceFolderModel.h
@@ -125,6 +125,8 @@ class ResourceFolderModel : public QAbstractListModel {
[[nodiscard]] bool lessThan(const QModelIndex& source_left, const QModelIndex& source_right) const override;
};
+ QString instDirPath() const;
+
public slots:
void enableInteraction(bool enabled);
void disableInteraction(bool disabled) { enableInteraction(!disabled); }
diff --git a/launcher/minecraft/mod/ResourcePackFolderModel.cpp b/launcher/minecraft/mod/ResourcePackFolderModel.cpp
index da4bd091..56584a34 100644
--- a/launcher/minecraft/mod/ResourcePackFolderModel.cpp
+++ b/launcher/minecraft/mod/ResourcePackFolderModel.cpp
@@ -36,6 +36,10 @@
#include "ResourcePackFolderModel.h"
+#include
+#include
+
+#include "Application.h"
#include "Version.h"
#include "minecraft/mod/tasks/BasicFolderLoadTask.h"
@@ -78,12 +82,28 @@ QVariant ResourcePackFolderModel::data(const QModelIndex& index, int role) const
default:
return {};
}
+ case Qt::DecorationRole: {
+ if (column == NAME_COLUMN && (at(row)->isSymLinkUnder(instDirPath()) || at(row)->isMoreThanOneHardLink()))
+ return APPLICATION->getThemedIcon("status-yellow");
+ return {};
+ }
case Qt::ToolTipRole: {
if (column == PackFormatColumn) {
//: The string being explained by this is in the format: ID (Lower version - Upper version)
return tr("The resource pack format ID, as well as the Minecraft versions it was designed for.");
}
+ if (column == NAME_COLUMN) {
+ if (at(row)->isSymLinkUnder(instDirPath())) {
+ return m_resources[row]->internal_id() +
+ tr("\nWarning: This resource is symbolicly linked from elsewhere. Editing it will also change the origonal") +
+ tr("\nCanonical Path: %1").arg(at(row)->fileinfo().canonicalFilePath());;
+ }
+ if (at(row)->isMoreThanOneHardLink()) {
+ return m_resources[row]->internal_id() +
+ tr("\nWarning: This resource is hard linked elsewhere. Editing it will also change the origonal");
+ }
+ }
return m_resources[row]->internal_id();
}
case Qt::CheckStateRole:
diff --git a/launcher/ui/pages/instance/WorldListPage.cpp b/launcher/ui/pages/instance/WorldListPage.cpp
index d4a395d9..b6ad159e 100644
--- a/launcher/ui/pages/instance/WorldListPage.cpp
+++ b/launcher/ui/pages/instance/WorldListPage.cpp
@@ -107,6 +107,7 @@ WorldListPage::WorldListPage(BaseInstance *inst, std::shared_ptr worl
auto head = ui->worldTreeView->header();
head->setSectionResizeMode(0, QHeaderView::Stretch);
head->setSectionResizeMode(1, QHeaderView::ResizeToContents);
+ head->setSectionResizeMode(4, QHeaderView::ResizeToContents);
connect(ui->worldTreeView->selectionModel(), &QItemSelectionModel::currentChanged, this, &WorldListPage::worldChanged);
worldChanged(QModelIndex(), QModelIndex());
From a0e03c41c034ddbc330789c7639f02a4a8ac1a10 Mon Sep 17 00:00:00 2001
From: Rachel Powers <508861+Ryex@users.noreply.github.com>
Date: Sun, 12 Feb 2023 02:59:52 -0700
Subject: [PATCH 024/104] fix: typos
Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com>
---
launcher/minecraft/WorldList.cpp | 6 +++---
launcher/minecraft/mod/ModFolderModel.cpp | 4 ++--
launcher/minecraft/mod/ResourceFolderModel.cpp | 4 ++--
launcher/minecraft/mod/ResourcePackFolderModel.cpp | 4 ++--
launcher/ui/dialogs/CopyInstanceDialog.ui | 2 +-
5 files changed, 10 insertions(+), 10 deletions(-)
diff --git a/launcher/minecraft/WorldList.cpp b/launcher/minecraft/WorldList.cpp
index 1262fa1d..43733110 100644
--- a/launcher/minecraft/WorldList.cpp
+++ b/launcher/minecraft/WorldList.cpp
@@ -213,7 +213,7 @@ QVariant WorldList::data(const QModelIndex &index, int role) const
case InfoColumn:
if (world.isSymLinkUnder(instDirPath())) {
- return tr("This world is symbolicly linked from elsewhere.");
+ return tr("This world is symbolically linked from elsewhere.");
}
if (world.isMoreThanOneHardLink()) {
return tr("\nThis world is hard linked elsewhere.");
@@ -237,11 +237,11 @@ QVariant WorldList::data(const QModelIndex &index, int role) const
{
if (column == InfoColumn) {
if (world.isSymLinkUnder(instDirPath())) {
- return tr("Warning: This world is symbolicly linked from elsewhere. Editing it will also change the origonal") +
+ return tr("Warning: This world is symbolically linked from elsewhere. Editing it will also change the original") +
tr("\nCanonical Path: %1").arg(world.canonicalFilePath());
}
if (world.isMoreThanOneHardLink()) {
- return tr("Warning: This world is hard linked elsewhere. Editing it will also change the origonal");
+ return tr("Warning: This world is hard linked elsewhere. Editing it will also change the original");
}
}
return world.folderName();
diff --git a/launcher/minecraft/mod/ModFolderModel.cpp b/launcher/minecraft/mod/ModFolderModel.cpp
index e2053f92..943f30ad 100644
--- a/launcher/minecraft/mod/ModFolderModel.cpp
+++ b/launcher/minecraft/mod/ModFolderModel.cpp
@@ -104,12 +104,12 @@ QVariant ModFolderModel::data(const QModelIndex &index, int role) const
if (column == NAME_COLUMN) {
if (at(row)->isSymLinkUnder(instDirPath())) {
return m_resources[row]->internal_id() +
- tr("\nWarning: This resource is symbolicly linked from elsewhere. Editing it will also change the origonal") +
+ tr("\nWarning: This resource is symbolically linked from elsewhere. Editing it will also change the original") +
tr("\nCanonical Path: %1").arg(at(row)->fileinfo().canonicalFilePath());
}
if (at(row)->isMoreThanOneHardLink()) {
return m_resources[row]->internal_id() +
- tr("\nWarning: This resource is hard linked elsewhere. Editing it will also change the origonal");
+ tr("\nWarning: This resource is hard linked elsewhere. Editing it will also change the original");
}
}
return m_resources[row]->internal_id();
diff --git a/launcher/minecraft/mod/ResourceFolderModel.cpp b/launcher/minecraft/mod/ResourceFolderModel.cpp
index d1748c1c..95b7651f 100644
--- a/launcher/minecraft/mod/ResourceFolderModel.cpp
+++ b/launcher/minecraft/mod/ResourceFolderModel.cpp
@@ -424,12 +424,12 @@ QVariant ResourceFolderModel::data(const QModelIndex& index, int role) const
if (column == NAME_COLUMN) {
if (at(row).isSymLinkUnder(instDirPath())) {
return m_resources[row]->internal_id() +
- tr("\nWarning: This resource is symbolicly linked from elsewhere. Editing it will also change the origonal") +
+ tr("\nWarning: This resource is symbolically linked from elsewhere. Editing it will also change the original") +
tr("\nCanonical Path: %1").arg(at(row).fileinfo().canonicalFilePath());;
}
if (at(row).isMoreThanOneHardLink()) {
return m_resources[row]->internal_id() +
- tr("\nWarning: This resource is hard linked elsewhere. Editing it will also change the origonal");
+ tr("\nWarning: This resource is hard linked elsewhere. Editing it will also change the original");
}
}
diff --git a/launcher/minecraft/mod/ResourcePackFolderModel.cpp b/launcher/minecraft/mod/ResourcePackFolderModel.cpp
index 56584a34..1fcfa909 100644
--- a/launcher/minecraft/mod/ResourcePackFolderModel.cpp
+++ b/launcher/minecraft/mod/ResourcePackFolderModel.cpp
@@ -96,12 +96,12 @@ QVariant ResourcePackFolderModel::data(const QModelIndex& index, int role) const
if (column == NAME_COLUMN) {
if (at(row)->isSymLinkUnder(instDirPath())) {
return m_resources[row]->internal_id() +
- tr("\nWarning: This resource is symbolicly linked from elsewhere. Editing it will also change the origonal") +
+ tr("\nWarning: This resource is symbolically linked from elsewhere. Editing it will also change the original") +
tr("\nCanonical Path: %1").arg(at(row)->fileinfo().canonicalFilePath());;
}
if (at(row)->isMoreThanOneHardLink()) {
return m_resources[row]->internal_id() +
- tr("\nWarning: This resource is hard linked elsewhere. Editing it will also change the origonal");
+ tr("\nWarning: This resource is hard linked elsewhere. Editing it will also change the original");
}
}
return m_resources[row]->internal_id();
diff --git a/launcher/ui/dialogs/CopyInstanceDialog.ui b/launcher/ui/dialogs/CopyInstanceDialog.ui
index 58442f73..009f5b88 100644
--- a/launcher/ui/dialogs/CopyInstanceDialog.ui
+++ b/launcher/ui/dialogs/CopyInstanceDialog.ui
@@ -305,7 +305,7 @@
-
- Use symbloic links
+ Use symbolic links
Use symbolic links instead of copying files.
From 536da704fc1ad66ac754b9fae119df7eb6fa1268 Mon Sep 17 00:00:00 2001
From: Rachel Powers <508861+Ryex@users.noreply.github.com>
Date: Mon, 13 Feb 2023 16:48:58 -0700
Subject: [PATCH 025/104] refactor: cleanupFilesystem.cpp * remove now
redundant reflink/clone code for windows * remove unnessacery debug code that
could slow things down
Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com>
---
launcher/FileSystem.cpp | 315 +++++++++-------------------------------
launcher/FileSystem.h | 67 +++++++--
2 files changed, 117 insertions(+), 265 deletions(-)
diff --git a/launcher/FileSystem.cpp b/launcher/FileSystem.cpp
index af2ba299..d6e6b92f 100644
--- a/launcher/FileSystem.cpp
+++ b/launcher/FileSystem.cpp
@@ -962,6 +962,37 @@ bool overrideFolder(QString overwritten_path, QString override_path)
return err.value() == 0;
}
+QString getFilesystemTypeName(FilesystemType type) {
+ auto iter = s_filesystem_type_names.constFind(type);
+ if (iter != s_filesystem_type_names.constEnd()){
+ return iter.value();
+ }
+ return getFilesystemTypeName(FilesystemType::UNKNOWN);
+}
+
+FilesystemType getFilesystemTypeFuzzy(const QString& name)
+{
+ auto iter = s_filesystem_type_names_inverse.constFind(name.toUpper());
+ if (iter != s_filesystem_type_names_inverse.constEnd()){
+ return iter.value();
+ }
+ return FilesystemType::UNKNOWN;
+}
+
+FilesystemType getFilesystemType(const QString& name)
+{
+ for (auto fs_type_pair : s_filesystem_type_names_inverse.toStdMap()) {
+ auto fs_type_name = fs_type_pair.first;
+ auto fs_type = fs_type_pair.second;
+
+ if(name.toUpper().contains(fs_type_name.toUpper())) {
+ return fs_type;
+
+ }
+ }
+ return FilesystemType::UNKNOWN;
+}
+
/**
* @brief path to the near ancestor that exsists
*
@@ -994,15 +1025,7 @@ FilesystemInfo statFS(const QString& path)
info.fsTypeName = QString::fromStdString(storage_info.fileSystemType().toStdString());
- for (auto fs_type_pair : s_filesystem_type_names_inverse.toStdMap()) {
- auto fs_type_name = fs_type_pair.first;
- auto fs_type = fs_type_pair.second;
-
- if(info.fsTypeName.toLower().contains(fs_type_name.toLower())) {
- info.fsType = fs_type;
- break;
- }
- }
+ info.fsType = getFilesystemTypeFuzzy(info.fsTypeName);
info.blockSize = storage_info.blockSize();
info.bytesAvailable = storage_info.bytesAvailable();
@@ -1029,7 +1052,7 @@ bool canCloneOnFS(const FilesystemInfo& info)
return canCloneOnFS(info.fsType);
}
bool canCloneOnFS(FilesystemType type)
-{
+{
return s_clone_filesystems.contains(type);
}
@@ -1081,7 +1104,7 @@ bool clone::operator()(const QString& offset, bool dryRun)
clone_file(src_path, dst_path, err);
}
if (err) {
- qDebug() << "Failed to clone files:" << QString::fromStdString(err.message());
+ qDebug() << "Failed to clone files: error" << err.value() << "message" << QString::fromStdString(err.message());
qDebug() << "Source file:" << src_path;
qDebug() << "Destination file:" << dst_path;
}
@@ -1118,104 +1141,55 @@ bool clone_file(const QString& src, const QString& dst, std::error_code& ec)
auto src_path = StringUtils::toStdString(QDir::toNativeSeparators(QFileInfo(src).absoluteFilePath()));
auto dst_path = StringUtils::toStdString(QDir::toNativeSeparators(QFileInfo(dst).absoluteFilePath()));
-#if defined(Q_OS_WIN)
FilesystemInfo srcinfo = statFS(src);
FilesystemInfo dstinfo = statFS(dst);
- if (((srcinfo.fsType == FilesystemType::BTRFS && dstinfo.fsType == FilesystemType::BTRFS) ||
- (srcinfo.fsType == FilesystemType::REFS && dstinfo.fsType == FilesystemType::REFS)) &&
- USE_IOCTL_CLONE)
+
+
+ if ((srcinfo.rootPath != dstinfo.rootPath) || (srcinfo.fsType != dstinfo.fsType))
{
- if (srcinfo.rootPath != dstinfo.rootPath) {
- qWarning() << "clones must be to the same device! src and dst root paths do not match.";
- ec = std::make_error_code(std::errc::not_supported);
- return false;
- }
-
- qDebug() << "ioctl clone" << src << "to" << dst;
- if (!ioctl_clone(src_path, dst_path, ec))
- return false;
-
- } else if (srcinfo.fsType == FilesystemType::BTRFS) {
- if (dstinfo.fsType != FilesystemType::BTRFS || (srcinfo.rootPath != dstinfo.rootPath)){
- qWarning() << "winbtrfs clone must be to the same device! src and dst root paths do not match.";
- qWarning() << "check out https://github.com/maharmstone/btrfs for btrfs support!";
- ec = std::make_error_code(std::errc::not_supported);
- return false;
- }
-
- qDebug() << "clone/reflink of btrfs on windows! assuming winbtrfs is in use and calling shellbtrfs.dll,ReflinkCopyW";
-
- if (!winbtrfs_clone(src_path, dst_path, ec))
- return false;
-
- // There is no return value from ReflinkCopyW so we must check if the file exsists ourselves
-
- QFileInfo dstInfo(dst);
- if (!dstInfo.exists() || !dstInfo.isFile() || dstInfo.isSymLink()) {
- // shellbtrfs.dll,ReflinkCopyW is curently broken https://github.com/maharmstone/btrfs/issues/556
- // lets try a little workaround
- // find the misnamed file
- qDebug() << dst << "is missing. ReflinkCopyW may still be broken, trying workaround.";
- QString badDst = QDir(dstInfo.absolutePath()).path() + dstInfo.fileName();
- qDebug() << "trying" << badDst;
- QFileInfo badDstInfo(badDst);
- if (badDstInfo.exists() && badDstInfo.isFile()) {
- qDebug() << badDst << "exists! moving it to the correct location.";
- if(!move(badDstInfo.absoluteFilePath(), dstInfo.absoluteFilePath())) {
- qDebug() << "move from" << badDst << "to" << dst << "failed";
- ec = std::make_error_code(std::errc::no_such_file_or_directory);
- return false;
- }
- } else {
- // oof, clone failure?
- qDebug() << "clone/reflink on winbtrfs did not succeed: file" << dst << "does not appear to exist";
- ec = std::make_error_code(std::errc::no_such_file_or_directory);
- return false;
- }
- }
-
- } else if (srcinfo.fsType == FilesystemType::REFS) {
- if (dstinfo.fsType != FilesystemType::REFS || (srcinfo.rootPath != dstinfo.rootPath)){
- qWarning() << "ReFS clone must be to the same device! src and dst root paths do not match.";
- ec = std::make_error_code(std::errc::not_supported);
- return false;
- }
-
- qDebug() << "clone/reflink of ReFS on windows!";
-
- if (!refs_clone(src_path, dst_path, ec))
- return false;
-
- } else {
- qWarning() << "clone/reflink not supported on windows outside of winbtrfs or ReFS!";
- qWarning() << "check out https://github.com/maharmstone/btrfs for btrfs support!";
ec = std::make_error_code(std::errc::not_supported);
+ qWarning() << "reflink/clone must be to the same device and filesystem! src and dst root filesystems do not match.";
return false;
}
+
+#if defined(Q_OS_WIN)
+
+ if (!win_ioctl_clone(src_path, dst_path, ec)) {
+ qDebug() << "failed win_ioctl_clone";
+ qWarning() << "clone/reflink not supported on windows outside of btrfs or ReFS!";
+ qWarning() << "check out https://github.com/maharmstone/btrfs for btrfs support!";
+ return false;
+ }
+
+
+
#elif defined(Q_OS_LINUX)
- if(!linux_ficlone(src_path, dst_path, ec))
+ if(!linux_ficlone(src_path, dst_path, ec)) {
+ qDebug() << "failed linux_ficlone:";
return false;
-
+ }
+
#elif defined(Q_OS_MACOS) || defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD)
- if(!macos_bsd_clonefile(src_path, dst_path, ec))
+ if(!macos_bsd_clonefile(src_path, dst_path, ec)) {
+ qDebug() << "failed macos_bsd_clonefile:";
return false;
-
+ }
+
#else
+
qWarning() << "clone/reflink not supported! unknown OS";
ec = std::make_error_code(std::errc::not_supported);
return false;
+
#endif
return true;
}
#if defined(Q_OS_WIN)
-typedef void (__stdcall *f_ReflinkCopyW)(HWND hwnd, HINSTANCE hinst, LPWSTR lpszCmdLine, int nCmdShow);
-
-const long WinMaxChunkSize = 1L << 31; // 2GB
static long RoundUpToPowerOf2(long originalValue, long roundingMultiplePowerOf2)
{
@@ -1223,171 +1197,15 @@ static long RoundUpToPowerOf2(long originalValue, long roundingMultiplePowerOf2)
return (originalValue + mask) & ~mask;
}
-bool winbtrfs_clone(const std::wstring& src_path, const std::wstring& dst_path, std::error_code& ec)
-{
- // https://github.com/maharmstone/btrfs
- QString cmdLine = QString("\"%1\" \"%2\"").arg(src_path, dst_path);
-
- std::wstring wstr = cmdLine.toStdWString(); // temp buffer to copy the data and avoid side effect of non const cast
-
- LPWSTR cmdLineWin = (wchar_t*)wstr.c_str();
-
- // https://github.com/maharmstone/btrfs/blob/9da54911dd6f3713a1c4c7be40338a3da126f4e6/src/shellext/contextmenu.cpp#L1609
- HINSTANCE shellbtrfsDLL = LoadLibrary(L"shellbtrfs.dll");
-
- if (shellbtrfsDLL == NULL) {
- ec = std::make_error_code(std::errc::not_supported);
- qWarning() << "cannot locate the shellbtrfs.dll file, reflink copy not supported";
- return false;
- }
-
- f_ReflinkCopyW ReflinkCopyW = (f_ReflinkCopyW)GetProcAddress(shellbtrfsDLL, "ReflinkCopyW");
-
- if (!ReflinkCopyW) {
- ec = std::make_error_code(std::errc::not_supported);
- qWarning() << "cannot locate the ReflinkCopyW function from shellbtrfs.dll, reflink copy not supported";
- return false;
- }
-
- qDebug() << "Calling ReflinkCopyW from shellbtrfs.dll with:" << cmdLine;
-
- ReflinkCopyW(0, 0, cmdLineWin, 1);
-
- FreeLibrary(shellbtrfsDLL);
-
- return true;
-}
-
-bool refs_clone(const std::wstring& src_path, const std::wstring& dst_path, std::error_code& ec)
-{
-#if defined(FSCTL_DUPLICATE_EXTENTS_TO_FILE)
- //https://learn.microsoft.com/en-us/windows/win32/api/winioctl/ni-winioctl-fsctl_duplicate_extents_to_file
- //https://github.com/microsoft/CopyOnWrite/blob/main/lib/Windows/WindowsCopyOnWriteFilesystem.cs#L94
- QString qSourcePath = StringUtils::fromStdString(src_path);
- QString sourceVolumePath = statFS(qSourcePath).rootPath;
- std::wstring source_volume_path = StringUtils::toStdString(sourceVolumePath);
-
- unsigned long sectorsPerCluster;
- unsigned long bytesPerSector;
- unsigned long numberOfFreeClusters;
- unsigned long totalNumberOfClusters;
-
- if(!GetDiskFreeSpace(source_volume_path.c_str(), §orsPerCluster, &bytesPerSector, &numberOfFreeClusters, &totalNumberOfClusters )){
- ec = std::error_code(GetLastError(), std::system_category());
- qDebug() << "Failed to get disk info for source volume" << sourceVolumePath;
- return false;
- }
-
- long srcClusterSize = (long)(sectorsPerCluster * bytesPerSector);
-
- HANDLE hSourceFile = CreateFile(src_path.c_str(), GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);
- if (hSourceFile == INVALID_HANDLE_VALUE)
- {
- ec = std::error_code(GetLastError(), std::system_category());
- qDebug() << "Failed to open source file" << src_path.c_str();
- return false;
- }
-
- HANDLE hDestFile = CreateFile(dst_path.c_str(), GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, 0, NULL);
- if (hDestFile == INVALID_HANDLE_VALUE)
- {
- ec = std::error_code(GetLastError(), std::system_category());
- CloseHandle(hSourceFile);
- qDebug() << "Failed to open dest file" << dst_path.c_str();
- return false;
- }
-
- DWORD bytesReturned = 0;
-
- // Set the destination to be sparse while we clone.
- // Important to avoid allocating zero-backed real storage when calling SetFileInformationByHandle()
- // below which will just be released when cloning file extents.
-
- if (!DeviceIoControl(hDestFile, FSCTL_SET_SPARSE, nullptr, 0, nullptr, 0, &bytesReturned, nullptr)) {
- qDebug() << "Failed to set file sparseness for destination file" << dst_path.c_str();
- ec = std::error_code(GetLastError(), std::system_category());
- return false;
- }
-
- LARGE_INTEGER sourceFileLengthStruct;
- if (!GetFileSizeEx(hSourceFile, &sourceFileLengthStruct)) {
- ec = std::error_code(GetLastError(), std::system_category());
- qDebug() << "Failed to get file info for source file" << src_path.c_str();
- return false;
- }
-
- DWORD sourceFileLength = sourceFileLengthStruct.QuadPart;
-
- // Set the destination on-disk size the same as the source.
- FILE_END_OF_FILE_INFO fileSizeInfo{sourceFileLength};
- if (!SetFileInformationByHandle(hDestFile, FILE_INFO_BY_HANDLE_CLASS::FileEndOfFileInfo,
- &fileSizeInfo, sizeof(FILE_END_OF_FILE_INFO)))
- {
- ec = std::error_code(GetLastError(), std::system_category());
- qDebug() << "Failed to set end of file on destination file" << dst_path.c_str();
- return false;
- }
-
- DUPLICATE_EXTENTS_DATA duplicateExtentsData = DUPLICATE_EXTENTS_DATA{ hSourceFile };
-
- long fileSizeRoundedUpToClusterBoundary = RoundUpToPowerOf2(sourceFileLength, srcClusterSize);
- long sourceOffset = 0;
- while(sourceOffset < sourceFileLength)
- {
- duplicateExtentsData.SourceFileOffset.QuadPart = sourceOffset;
- duplicateExtentsData.TargetFileOffset.QuadPart = sourceOffset;
- long thisChunkSize = std::min(fileSizeRoundedUpToClusterBoundary - sourceOffset, WinMaxChunkSize);
- duplicateExtentsData.ByteCount.QuadPart = thisChunkSize;
-
- DWORD numBytesReturned = 0;
- bool ioctlResult = DeviceIoControl(
- hDestFile,
- FSCTL_DUPLICATE_EXTENTS_TO_FILE,
- &duplicateExtentsData,
- sizeof(DUPLICATE_EXTENTS_DATA),
- nullptr,
- 0,
- &numBytesReturned,
- nullptr);
- if (!ioctlResult)
- {
- DWORD err = GetLastError();
- ec = std::error_code(err, std::system_category());
- QString additionalMessage;
- if (err == ERROR_BLOCK_TOO_MANY_REFERENCES)
- {
- static const int MaxClonesPerFile = 8175;
- additionalMessage = QString(
- " This is ERROR_BLOCK_TOO_MANY_REFERENCES and may mean you have surpassed the maximum "
- "allowed %1 references for a single file. "
- "See https://docs.microsoft.com/en-us/windows-server/storage/refs/block-cloning#functionality-restrictions-and-remarks"
- ).arg(MaxClonesPerFile);
-
- }
- qWarning() << "Failed copy-on-write cloning from source file" << src_path.c_str() << "to" << dst_path.c_str() << "." << additionalMessage;
- return false;
- }
-
- sourceOffset += thisChunkSize;
- }
-
- CloseHandle(hDestFile);
- CloseHandle(hSourceFile);
-
- return true;
-#else
- ec = std::make_error_code(std::errc::not_supported);
- qWarning() << "not built with refs support";
- return false;
-#endif
-}
-
bool ioctl_clone(const std::wstring& src_path, const std::wstring& dst_path, std::error_code& ec)
{
/**
* This algorithm inspired from https://github.com/0xbadfca11/reflink
* LICENSE MIT
*
+ * Additional references
+ * https://learn.microsoft.com/en-us/windows/win32/api/winioctl/ni-winioctl-fsctl_duplicate_extents_to_file
+ * https://github.com/microsoft/CopyOnWrite/blob/main/lib/Windows/WindowsCopyOnWriteFilesystem.cs#L94
*/
HANDLE hSourceFile = CreateFileW(src_path.c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, 0, nullptr);
@@ -1495,11 +1313,6 @@ bool ioctl_clone(const std::wstring& src_path, const std::wstring& dst_path, std
dupExtent.SourceFileOffset.QuadPart = dupExtent.TargetFileOffset.QuadPart = offset;
dupExtent.ByteCount.QuadPart = std::min(splitThreshold, remain);
- _ASSERTE(dupExtent.SourceFileOffset.QuadPart % sourceFileIntegrity.ClusterSizeInBytes == 0);
- _ASSERTE(dupExtent.ByteCount.QuadPart % sourceFileIntegrity.ClusterSizeInBytes == 0);
- _ASSERTE(dupExtent.ByteCount.QuadPart <= UINT32_MAX);
- _RPT3(_CRT_WARN, "Remain=%llx\nOffset=%llx\nLength=%llx\n\n", remain, dupExtent.SourceFileOffset.QuadPart, dupExtent.ByteCount.QuadPart);
-
if (!DeviceIoControl(hDestFile, FSCTL_DUPLICATE_EXTENTS_TO_FILE, &dupExtent, sizeof(dupExtent), nullptr, 0, &junk, nullptr))
{
DWORD err = GetLastError();
@@ -1559,6 +1372,7 @@ bool ioctl_clone(const std::wstring& src_path, const std::wstring& dst_path, std
}
#elif defined(Q_OS_LINUX)
+
bool linux_ficlone(const std::string& src_path, const std::string& dst_path, std::error_code& ec)
{
// https://man7.org/linux/man-pages/man2/ioctl_ficlone.2.html
@@ -1599,6 +1413,7 @@ bool linux_ficlone(const std::string& src_path, const std::string& dst_path, std
}
#elif defined(Q_OS_MACOS) || defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD)
+
bool macos_bsd_clonefile(const std::string& src_path, const std::string& dst_path, std::error_code& ec)
{
// clonefile(const char * src, const char * dst, int flags);
diff --git a/launcher/FileSystem.h b/launcher/FileSystem.h
index 361993eb..84526c11 100644
--- a/launcher/FileSystem.h
+++ b/launcher/FileSystem.h
@@ -361,12 +361,20 @@ enum class FilesystemType {
UNKNOWN
};
+/**
+ * @brief Ordered Mapping of enum types to reported filesystem names
+ * this maping is non exsaustive, it just attempts to capture the filesystems which could be reasonalbly be in use .
+ * all string values are in uppercase, use `QString.toUpper()` or equivalent during lookup.
+ *
+ * QMap is ordered
+ *
+ */
static const QMap s_filesystem_type_names = {
{FilesystemType::FAT, QString("FAT")},
{FilesystemType::NTFS, QString("NTFS")},
{FilesystemType::REFS, QString("REFS")},
{FilesystemType::EXT, QString("EXT")},
- {FilesystemType::EXT_2_OLD, QString("EXT2_OLD")},
+ {FilesystemType::EXT_2_OLD, QString("EXT_2_OLD")},
{FilesystemType::EXT_2_3_4, QString("EXT2/3/4")},
{FilesystemType::XFS, QString("XFS")},
{FilesystemType::BTRFS, QString("BTRFS")},
@@ -381,15 +389,27 @@ static const QMap s_filesystem_type_names = {
{FilesystemType::UNKNOWN, QString("UNKNOWN")}
};
+
+/**
+ * @brief Ordered Mapping of reported filesystem names to enum types
+ * this maping is non exsaustive, it just attempts to capture the many way these filesystems could be reported.
+ * all keys are in uppercase, use `QString.toUpper()` or equivalent during lookup.
+ *
+ * QMap is ordered
+ *
+ */
static const QMap s_filesystem_type_names_inverse = {
{QString("FAT"), FilesystemType::FAT},
{QString("NTFS"), FilesystemType::NTFS},
{QString("REFS"), FilesystemType::REFS},
{QString("EXT2_OLD"), FilesystemType::EXT_2_OLD},
+ {QString("EXT_2_OLD"), FilesystemType::EXT_2_OLD},
{QString("EXT2"), FilesystemType::EXT_2_3_4},
{QString("EXT3"), FilesystemType::EXT_2_3_4},
{QString("EXT4"), FilesystemType::EXT_2_3_4},
- {QString("EXT"), FilesystemType::EXT},
+ {QString("EXT2/3/4"), FilesystemType::EXT_2_3_4},
+ {QString("EXT_2_3_4"), FilesystemType::EXT_2_3_4},
+ {QString("EXT"), FilesystemType::EXT}, // must come after all other EXT variants to prevent greedy detection
{QString("XFS"), FilesystemType::XFS},
{QString("BTRFS"), FilesystemType::BTRFS},
{QString("NFS"), FilesystemType::NFS},
@@ -403,9 +423,32 @@ static const QMap s_filesystem_type_names_inverse = {
{QString("UNKNOWN"), FilesystemType::UNKNOWN}
};
-inline QString getFilesystemTypeName(FilesystemType type) {
- return s_filesystem_type_names.constFind(type).value();
-}
+/**
+ * @brief Get the string name of Filesystem enum object
+ *
+ * @param type
+ * @return QString
+ */
+QString getFilesystemTypeName(FilesystemType type);
+
+/**
+ * @brief Get the Filesystem enum object from a name
+ * Does a lookup of the type name and returns an exact match
+ *
+ * @param name
+ * @return FilesystemType
+ */
+FilesystemType getFilesystemType(const QString& name);
+
+/**
+ * @brief Get the Filesystem enum object from a name
+ * Does a fuzzy lookup of the type name and returns an apropreate match
+ *
+ * @param name
+ * @return FilesystemType
+ */
+FilesystemType getFilesystemTypeFuzzy(const QString& name);
+
struct FilesystemInfo {
FilesystemType fsType = FilesystemType::UNKNOWN;
@@ -430,9 +473,9 @@ QString NearestExistentAncestor(const QString& path);
*/
FilesystemInfo statFS(const QString& path);
-
-static const QList s_clone_filesystems = {
- FilesystemType::BTRFS, FilesystemType::APFS, FilesystemType::ZFS, FilesystemType::XFS, FilesystemType::REFS
+static const QList s_clone_filesystems = {
+ FilesystemType::BTRFS, FilesystemType::APFS, FilesystemType::ZFS,
+ FilesystemType::XFS, FilesystemType::REFS
};
/**
@@ -497,13 +540,7 @@ class clone : public QObject {
bool clone_file(const QString& src, const QString& dst, std::error_code& ec);
#if defined(Q_OS_WIN)
-
-bool winbtrfs_clone(const std::wstring& src_path, const std::wstring& dst_path, std::error_code& ec);
-bool refs_clone(const std::wstring& src_path, const std::wstring& dst_path, std::error_code& ec);
-bool ioctl_clone(const std::wstring& src_path, const std::wstring& dst_path, std::error_code& ec);
-
-#define USE_IOCTL_CLONE true
-
+bool win_ioctl_clone(const std::wstring& src_path, const std::wstring& dst_path, std::error_code& ec);
#elif defined(Q_OS_LINUX)
bool linux_ficlone(const std::string& src_path, const std::string& dst_path, std::error_code& ec);
#elif defined(Q_OS_MACOS) || defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD)
From 72292f4e03e75f04ddddffc08bb5842e7ae8c071 Mon Sep 17 00:00:00 2001
From: Rachel Powers <508861+Ryex@users.noreply.github.com>
Date: Mon, 13 Feb 2023 17:20:44 -0700
Subject: [PATCH 026/104] fix: windows compile broke move winapi defs into
#ifndef blocks don't check explicitly for __mingw__ define function name
win_ioctl_clone didn't get updated in teh last commit
Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com>
---
launcher/FileSystem.cpp | 30 ++++++++++++++++++++++++------
1 file changed, 24 insertions(+), 6 deletions(-)
diff --git a/launcher/FileSystem.cpp b/launcher/FileSystem.cpp
index d6e6b92f..3d66d844 100644
--- a/launcher/FileSystem.cpp
+++ b/launcher/FileSystem.cpp
@@ -122,7 +122,12 @@ namespace fs = ghc::filesystem;
# endif
#endif
-#if defined(Q_OS_WIN) && defined(__MINGW32__)
+#if defined(Q_OS_WIN)
+
+
+#ifndef FSCTL_DUPLICATE_EXTENTS_TO_FILE
+
+#define FSCTL_DUPLICATE_EXTENTS_TO_FILE CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 209, METHOD_BUFFERED, FILE_WRITE_DATA )
typedef struct _DUPLICATE_EXTENTS_DATA {
HANDLE FileHandle;
@@ -131,6 +136,12 @@ typedef struct _DUPLICATE_EXTENTS_DATA {
LARGE_INTEGER ByteCount;
} DUPLICATE_EXTENTS_DATA, *PDUPLICATE_EXTENTS_DATA;
+#endif
+
+#ifndef FSCTL_GET_INTEGRITY_INFORMATION
+
+#define FSCTL_GET_INTEGRITY_INFORMATION CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 159, METHOD_BUFFERED, FILE_ANY_ACCESS) // FSCTL_GET_INTEGRITY_INFORMATION_BUFFER
+
typedef struct _FSCTL_GET_INTEGRITY_INFORMATION_BUFFER {
WORD ChecksumAlgorithm; // Checksum algorithm. e.g. CHECKSUM_TYPE_UNCHANGED, CHECKSUM_TYPE_NONE, CHECKSUM_TYPE_CRC32
WORD Reserved; // Must be 0
@@ -139,19 +150,26 @@ typedef struct _FSCTL_GET_INTEGRITY_INFORMATION_BUFFER {
DWORD ClusterSizeInBytes;
} FSCTL_GET_INTEGRITY_INFORMATION_BUFFER, *PFSCTL_GET_INTEGRITY_INFORMATION_BUFFER;
+#endif
+
+#ifndef FSCTL_SET_INTEGRITY_INFORMATION
+
+#define FSCTL_SET_INTEGRITY_INFORMATION CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 160, METHOD_BUFFERED, FILE_READ_DATA | FILE_WRITE_DATA) // FSCTL_SET_INTEGRITY_INFORMATION_BUFFER
+
typedef struct _FSCTL_SET_INTEGRITY_INFORMATION_BUFFER {
WORD ChecksumAlgorithm; // Checksum algorithm. e.g. CHECKSUM_TYPE_UNCHANGED, CHECKSUM_TYPE_NONE, CHECKSUM_TYPE_CRC32
WORD Reserved; // Must be 0
DWORD Flags; // FSCTL_INTEGRITY_FLAG_xxx
} FSCTL_SET_INTEGRITY_INFORMATION_BUFFER, *PFSCTL_SET_INTEGRITY_INFORMATION_BUFFER;
-#define FSCTL_DUPLICATE_EXTENTS_TO_FILE CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 209, METHOD_BUFFERED, FILE_WRITE_DATA )
-#define FSCTL_GET_INTEGRITY_INFORMATION CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 159, METHOD_BUFFERED, FILE_ANY_ACCESS) // FSCTL_GET_INTEGRITY_INFORMATION_BUFFER
-#define FSCTL_SET_INTEGRITY_INFORMATION CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 160, METHOD_BUFFERED, FILE_READ_DATA | FILE_WRITE_DATA) // FSCTL_SET_INTEGRITY_INFORMATION_BUFFER
-
+#endif
+#ifndef ERROR_NOT_CAPABLE
#define ERROR_NOT_CAPABLE 775L
+#endif
+#ifndef ERROR_BLOCK_TOO_MANY_REFERENCES
#define ERROR_BLOCK_TOO_MANY_REFERENCES 347L
+#endif
#endif
@@ -1197,7 +1215,7 @@ static long RoundUpToPowerOf2(long originalValue, long roundingMultiplePowerOf2)
return (originalValue + mask) & ~mask;
}
-bool ioctl_clone(const std::wstring& src_path, const std::wstring& dst_path, std::error_code& ec)
+bool win_ioctl_clone(const std::wstring& src_path, const std::wstring& dst_path, std::error_code& ec)
{
/**
* This algorithm inspired from https://github.com/0xbadfca11/reflink
From 562ae676a5467611c86d04d1da4124df242c8194 Mon Sep 17 00:00:00 2001
From: Rachel Powers <508861+Ryex@users.noreply.github.com>
Date: Mon, 13 Feb 2023 17:39:56 -0700
Subject: [PATCH 027/104] fix: mingw still missing typedefs
Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com>
---
launcher/FileSystem.cpp | 32 ++++++++++++++++----------------
1 file changed, 16 insertions(+), 16 deletions(-)
diff --git a/launcher/FileSystem.cpp b/launcher/FileSystem.cpp
index 3d66d844..6ef1eea7 100644
--- a/launcher/FileSystem.cpp
+++ b/launcher/FileSystem.cpp
@@ -122,12 +122,9 @@ namespace fs = ghc::filesystem;
# endif
#endif
-#if defined(Q_OS_WIN)
+#if defined(Q_OS_WIN)
-
-#ifndef FSCTL_DUPLICATE_EXTENTS_TO_FILE
-
-#define FSCTL_DUPLICATE_EXTENTS_TO_FILE CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 209, METHOD_BUFFERED, FILE_WRITE_DATA )
+#if defined(__MINGW32__)
typedef struct _DUPLICATE_EXTENTS_DATA {
HANDLE FileHandle;
@@ -136,12 +133,6 @@ typedef struct _DUPLICATE_EXTENTS_DATA {
LARGE_INTEGER ByteCount;
} DUPLICATE_EXTENTS_DATA, *PDUPLICATE_EXTENTS_DATA;
-#endif
-
-#ifndef FSCTL_GET_INTEGRITY_INFORMATION
-
-#define FSCTL_GET_INTEGRITY_INFORMATION CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 159, METHOD_BUFFERED, FILE_ANY_ACCESS) // FSCTL_GET_INTEGRITY_INFORMATION_BUFFER
-
typedef struct _FSCTL_GET_INTEGRITY_INFORMATION_BUFFER {
WORD ChecksumAlgorithm; // Checksum algorithm. e.g. CHECKSUM_TYPE_UNCHANGED, CHECKSUM_TYPE_NONE, CHECKSUM_TYPE_CRC32
WORD Reserved; // Must be 0
@@ -150,11 +141,6 @@ typedef struct _FSCTL_GET_INTEGRITY_INFORMATION_BUFFER {
DWORD ClusterSizeInBytes;
} FSCTL_GET_INTEGRITY_INFORMATION_BUFFER, *PFSCTL_GET_INTEGRITY_INFORMATION_BUFFER;
-#endif
-
-#ifndef FSCTL_SET_INTEGRITY_INFORMATION
-
-#define FSCTL_SET_INTEGRITY_INFORMATION CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 160, METHOD_BUFFERED, FILE_READ_DATA | FILE_WRITE_DATA) // FSCTL_SET_INTEGRITY_INFORMATION_BUFFER
typedef struct _FSCTL_SET_INTEGRITY_INFORMATION_BUFFER {
WORD ChecksumAlgorithm; // Checksum algorithm. e.g. CHECKSUM_TYPE_UNCHANGED, CHECKSUM_TYPE_NONE, CHECKSUM_TYPE_CRC32
@@ -164,9 +150,23 @@ typedef struct _FSCTL_SET_INTEGRITY_INFORMATION_BUFFER {
#endif
+
+#ifndef FSCTL_DUPLICATE_EXTENTS_TO_FILE
+#define FSCTL_DUPLICATE_EXTENTS_TO_FILE CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 209, METHOD_BUFFERED, FILE_WRITE_DATA )
+#endif
+
+#ifndef FSCTL_GET_INTEGRITY_INFORMATION
+#define FSCTL_GET_INTEGRITY_INFORMATION CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 159, METHOD_BUFFERED, FILE_ANY_ACCESS) // FSCTL_GET_INTEGRITY_INFORMATION_BUFFER
+#endif
+
+#ifndef FSCTL_SET_INTEGRITY_INFORMATION
+#define FSCTL_SET_INTEGRITY_INFORMATION CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 160, METHOD_BUFFERED, FILE_READ_DATA | FILE_WRITE_DATA) // FSCTL_SET_INTEGRITY_INFORMATION_BUFFER
+#endif
+
#ifndef ERROR_NOT_CAPABLE
#define ERROR_NOT_CAPABLE 775L
#endif
+
#ifndef ERROR_BLOCK_TOO_MANY_REFERENCES
#define ERROR_BLOCK_TOO_MANY_REFERENCES 347L
#endif
From 656bfd36f607579021a4eaa0893350640b6a3882 Mon Sep 17 00:00:00 2001
From: Rachel Powers <508861+Ryex@users.noreply.github.com>
Date: Wed, 15 Feb 2023 17:58:14 -0800
Subject: [PATCH 028/104] fix: ensure filelink.exe is included in setup.exe
Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com>
---
program_info/win_install.nsi.in | 2 ++
1 file changed, 2 insertions(+)
diff --git a/program_info/win_install.nsi.in b/program_info/win_install.nsi.in
index 49e22500..f8918c58 100644
--- a/program_info/win_install.nsi.in
+++ b/program_info/win_install.nsi.in
@@ -281,6 +281,7 @@ Section "@Launcher_DisplayName@"
SetOutPath $INSTDIR
File "@Launcher_APP_BINARY_NAME@.exe"
+ File "@Launcher_APP_BINARY_NAME@_filelink.exe"
File "qt.conf"
File *.dll
File /r "iconengines"
@@ -361,6 +362,7 @@ Section "Uninstall"
DeleteRegKey HKCU SOFTWARE\@Launcher_CommonName@
Delete $INSTDIR\@Launcher_APP_BINARY_NAME@.exe
+ Delete $INSTDIR\@Launcher_APP_BINARY_NAME@_filelink.exe
Delete $INSTDIR\qt.conf
Delete $INSTDIR\*.dll
From 3ec92acfe7a843a34018fc142439888c4ca5dba0 Mon Sep 17 00:00:00 2001
From: Rachel Powers <508861+Ryex@users.noreply.github.com>
Date: Thu, 16 Feb 2023 20:01:59 -0800
Subject: [PATCH 029/104] fix: use noexcept overload of
std::filesystem::hard_link_count
Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com>
---
launcher/FileSystem.cpp | 8 +++++++-
1 file changed, 7 insertions(+), 1 deletion(-)
diff --git a/launcher/FileSystem.cpp b/launcher/FileSystem.cpp
index 6ef1eea7..45c73d24 100644
--- a/launcher/FileSystem.cpp
+++ b/launcher/FileSystem.cpp
@@ -1476,7 +1476,13 @@ bool canLink(const QString& src, const QString& dst)
uintmax_t hardLinkCount(const QString& path)
{
- return fs::hard_link_count(StringUtils::toStdString(path));
+ std::error_code err;
+ int count = fs::hard_link_count(StringUtils::toStdString(path), err);
+ if (err) {
+ qWarning() << "Failed to count hard links for" << path << ":" << QString::fromStdString(err.message());
+ count = 0;
+ }
+ return count;
}
}
From 1ca2c59f2ed7739b4b7d50c7212e292a4432da93 Mon Sep 17 00:00:00 2001
From: Rachel Powers <508861+Ryex@users.noreply.github.com>
Date: Wed, 15 Feb 2023 22:01:27 -0700
Subject: [PATCH 030/104] feat: track instance copies that use links confirm
deleations when other instances link to it
Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com>
---
launcher/BaseInstance.cpp | 36 ++++++++++++++++++++++++
launcher/BaseInstance.h | 6 ++++
launcher/InstanceCopyTask.cpp | 2 ++
launcher/InstanceList.cpp | 10 +++++++
launcher/InstanceList.h | 2 ++
launcher/settings/INIFile.cpp | 18 ++++++++++++
launcher/settings/INIFile.h | 35 ++++++++++++++++++++++++
launcher/settings/SettingsObject.cpp | 13 +++++++++
launcher/settings/SettingsObject.h | 41 ++++++++++++++++++++++++++++
launcher/ui/MainWindow.cpp | 14 ++++++++++
tests/INIFile_test.cpp | 38 ++++++++++++++++++++++++--
11 files changed, 213 insertions(+), 2 deletions(-)
diff --git a/launcher/BaseInstance.cpp b/launcher/BaseInstance.cpp
index 8680361c..6428be43 100644
--- a/launcher/BaseInstance.cpp
+++ b/launcher/BaseInstance.cpp
@@ -40,6 +40,8 @@
#include
#include
#include
+#include
+#include
#include "settings/INISettingsObject.h"
#include "settings/Setting.h"
@@ -64,6 +66,8 @@ BaseInstance::BaseInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr s
m_settings->registerSetting("totalTimePlayed", 0);
m_settings->registerSetting("lastTimePlayed", 0);
+ m_settings->registerSetting("linkedInstancesList", "[]");
+
// Game time override
auto gameTimeOverride = m_settings->registerSetting("OverrideGameTime", false);
m_settings->registerOverride(globalSettings->getSetting("ShowGameTime"), gameTimeOverride);
@@ -182,6 +186,38 @@ bool BaseInstance::shouldStopOnConsoleOverflow() const
return m_settings->get("ConsoleOverflowStop").toBool();
}
+QStringList BaseInstance::getLinkedInstances() const
+{
+ return m_settings->getList("linkedInstancesList");
+}
+
+void BaseInstance::setLinkedInstances(const QStringList& list)
+{
+ auto linkedInstancesList = m_settings->getList("linkedInstancesList");
+ m_settings->setList("linkedInstancesList", list);
+}
+
+void BaseInstance::addLinkedInstanceId(const QString& id)
+{
+ auto linkedInstancesList = m_settings->getList("linkedInstancesList");
+ linkedInstancesList.append(id);
+ setLinkedInstances(linkedInstancesList);
+}
+
+bool BaseInstance::removeLinkedInstanceId(const QString& id)
+{
+ auto linkedInstancesList = m_settings->getList("linkedInstancesList");
+ int numRemoved = linkedInstancesList.removeAll(id);
+ setLinkedInstances(linkedInstancesList);
+ return numRemoved > 0;
+}
+
+bool BaseInstance::isLinkedToInstanceId(const QString& id) const
+{
+ auto linkedInstancesList = m_settings->getList("linkedInstancesList");
+ return linkedInstancesList.contains(id);
+}
+
void BaseInstance::iconUpdated(QString key)
{
if(iconKey() == key)
diff --git a/launcher/BaseInstance.h b/launcher/BaseInstance.h
index a2a4f824..83a8064f 100644
--- a/launcher/BaseInstance.h
+++ b/launcher/BaseInstance.h
@@ -282,6 +282,12 @@ public:
int getConsoleMaxLines() const;
bool shouldStopOnConsoleOverflow() const;
+ QStringList getLinkedInstances() const;
+ void setLinkedInstances(const QStringList& list);
+ void addLinkedInstanceId(const QString& id);
+ bool removeLinkedInstanceId(const QString& id);
+ bool isLinkedToInstanceId(const QString& id) const;
+
protected:
void changeStatus(Status newStatus);
diff --git a/launcher/InstanceCopyTask.cpp b/launcher/InstanceCopyTask.cpp
index 40babd0f..e0a4de0b 100644
--- a/launcher/InstanceCopyTask.cpp
+++ b/launcher/InstanceCopyTask.cpp
@@ -137,6 +137,8 @@ void InstanceCopyTask::copyFinished()
if(!m_keepPlaytime) {
inst->resetTimePlayed();
}
+ if (m_useLinks)
+ inst->addLinkedInstanceId(m_origInstance->id());
emitSucceeded();
}
diff --git a/launcher/InstanceList.cpp b/launcher/InstanceList.cpp
index 1ca16ae9..179bfb9a 100644
--- a/launcher/InstanceList.cpp
+++ b/launcher/InstanceList.cpp
@@ -129,6 +129,16 @@ QMimeData* InstanceList::mimeData(const QModelIndexList& indexes) const
return mimeData;
}
+QStringList InstanceList::getLinkedInstancesById(const QString &id) const
+{
+ QStringList linkedInstances;
+ for (auto inst : m_instances) {
+ if (inst->isLinkedToInstanceId(id))
+ linkedInstances.append(inst->id());
+ }
+ return linkedInstances;
+}
+
int InstanceList::rowCount(const QModelIndex& parent) const
{
Q_UNUSED(parent);
diff --git a/launcher/InstanceList.h b/launcher/InstanceList.h
index edacba3c..48bede07 100644
--- a/launcher/InstanceList.h
+++ b/launcher/InstanceList.h
@@ -154,6 +154,8 @@ public:
QStringList mimeTypes() const override;
QMimeData *mimeData(const QModelIndexList &indexes) const override;
+ QStringList getLinkedInstancesById(const QString &id) const;
+
signals:
void dataIsInvalid();
void instancesChanged();
diff --git a/launcher/settings/INIFile.cpp b/launcher/settings/INIFile.cpp
index 733cd444..e48e6f47 100644
--- a/launcher/settings/INIFile.cpp
+++ b/launcher/settings/INIFile.cpp
@@ -183,3 +183,21 @@ void INIFile::set(QString key, QVariant val)
{
this->operator[](key) = val;
}
+
+void INIFile::setList(QString key, QVariantList val)
+{
+ QString stringList = QJsonDocument(QVariant(val).toJsonArray()).toJson(QJsonDocument::Compact);
+
+ this->operator[](key) = stringList;
+}
+
+QVariantList INIFile::getList(QString key, QVariantList def) const
+{
+ if (this->contains(key)) {
+ auto src = this->operator[](key);
+
+ return QJsonDocument::fromJson(src.toByteArray()).toVariant().toList();
+ }
+
+ return def;
+}
diff --git a/launcher/settings/INIFile.h b/launcher/settings/INIFile.h
index 4313e829..86bf0898 100644
--- a/launcher/settings/INIFile.h
+++ b/launcher/settings/INIFile.h
@@ -19,6 +19,9 @@
#include
#include
+#include
+#include
+
// Sectionless INI parser (for instance config files)
class INIFile : public QMap
{
@@ -33,4 +36,36 @@ public:
void set(QString key, QVariant val);
static QString unescape(QString orig);
static QString escape(QString orig);
+
+ void setList(QString key, QVariantList val);
+ template void setList(QString key, QList val)
+ {
+ QVariantList variantList;
+ variantList.reserve(val.size());
+ for (const T& v : val)
+ {
+ variantList.append(v);
+ }
+
+ this->setList(key, variantList);
+ }
+
+ QVariantList getList(QString key, QVariantList def) const;
+ template QList getList(QString key, QList def) const
+ {
+ if (this->contains(key)) {
+ QVariant src = this->operator[](key);
+ QVariantList variantList = QJsonDocument::fromJson(src.toByteArray()).toVariant().toList();
+
+ QListTList;
+ TList.reserve(variantList.size());
+ for (const QVariant& v : variantList)
+ {
+ TList.append(v.value());
+ }
+ return TList;
+ }
+
+ return def;
+ }
};
diff --git a/launcher/settings/SettingsObject.cpp b/launcher/settings/SettingsObject.cpp
index 8a0bc045..4c51d6e9 100644
--- a/launcher/settings/SettingsObject.cpp
+++ b/launcher/settings/SettingsObject.cpp
@@ -121,6 +121,19 @@ bool SettingsObject::contains(const QString &id)
return m_settings.contains(id);
}
+bool SettingsObject::setList(const QString &id, QVariantList value)
+{
+ QString stringList = QJsonDocument(QVariant(value).toJsonArray()).toJson(QJsonDocument::Compact);
+
+ return set(id, stringList);
+}
+
+QVariantList SettingsObject::getList(const QString &id)
+{
+ QVariant value = this->get(id);
+ return QJsonDocument::fromJson(value.toByteArray()).toVariant().toList();
+}
+
bool SettingsObject::reload()
{
for (auto setting : m_settings.values())
diff --git a/launcher/settings/SettingsObject.h b/launcher/settings/SettingsObject.h
index 6200bc3a..ff430172 100644
--- a/launcher/settings/SettingsObject.h
+++ b/launcher/settings/SettingsObject.h
@@ -19,6 +19,8 @@
#include
#include
#include
+#include
+#include
#include
class Setting;
@@ -142,6 +144,45 @@ public:
*/
bool contains(const QString &id);
+ /*!
+ * \brief Sets the value of the setting with the given ID with a json list.
+ * If no setting with the given ID exists, returns false
+ * \param id The ID of the setting to change.
+ * \param value The new value of the setting.
+ */
+ bool setList(const QString &id, QVariantList value);
+ template bool setList(const QString &id, QList val)
+ {
+ QVariantList variantList;
+ variantList.reserve(val.size());
+ for (const T& v : val)
+ {
+ variantList.append(v);
+ }
+
+ return setList(id, variantList);
+ }
+
+ /**
+ * \brief Gets the value of the setting with the given ID as if it were a json list.
+ * \param id The ID of the setting to change.
+ * \return The setting's value as a QVariantList.
+ * If no setting with the given ID exists, returns an empty QVariantList.
+ */
+ QVariantList getList(const QString &id);
+ template QList getList(const QString &id)
+ {
+ QVariantList variantList = this->getList(id);
+
+ QListTList;
+ TList.reserve(variantList.size());
+ for (const QVariant& v : variantList)
+ {
+ TList.append(v.value());
+ }
+ return TList;
+ }
+
/*!
* \brief Reloads the settings and emit signals for changed settings
* \return True if reloading was successful
diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp
index 8490b292..a6aa8320 100644
--- a/launcher/ui/MainWindow.cpp
+++ b/launcher/ui/MainWindow.cpp
@@ -1337,6 +1337,20 @@ void MainWindow::on_actionDeleteInstance_triggered()
if (response != QMessageBox::Yes)
return;
+ auto linkedInstances = APPLICATION->instances()->getLinkedInstancesById(id);
+ if (!linkedInstances.empty()) {
+ response = CustomMessageBox::selectable(
+ this, tr("There are linked instances"),
+ tr("The folowing Instance(s) might reference files in this instance:\n\n"
+ "%1\n\n"
+ "Deleting it could break the other instance(s), \n\n"
+ "Are you sure?").arg(linkedInstances.join("\n")),
+ QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No, QMessageBox::No
+ )->exec();
+ if (response != QMessageBox::Yes)
+ return;
+ }
+
if (APPLICATION->instances()->trashInstance(id)) {
ui->actionUndoTrashInstance->setEnabled(APPLICATION->instances()->trashedSomething());
return;
diff --git a/tests/INIFile_test.cpp b/tests/INIFile_test.cpp
index b64b031b..d13937c0 100644
--- a/tests/INIFile_test.cpp
+++ b/tests/INIFile_test.cpp
@@ -1,7 +1,10 @@
#include
+#include
+#include
#include
+
class IniFileTest : public QObject
{
Q_OBJECT
@@ -52,8 +55,39 @@ slots:
// load
INIFile f2;
f2.loadFile(filename);
- QCOMPARE(a, f2.get("a","NOT SET").toString());
- QCOMPARE(b, f2.get("b","NOT SET").toString());
+ 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"};
+
+ QString slist_numbers = "[1,2,3,10]";
+ QList list_numbers = {1, 2, 3, 10};
+
+ QString filename = "test_SaveLoadLists.ini";
+
+ INIFile f;
+ f.setList("list_strings", list_strings);
+ f.setList("list_numbers", list_numbers);
+ f.saveFile(filename);
+
+ // load
+ INIFile f2;
+ f2.loadFile(filename);
+
+ QStringList out_list_strings = f2.getList("list_strings", QStringList());
+ qDebug() << "OutStringList" << out_list_strings;
+
+ QList out_list_numbers = f2.getList("list_numbers", QList());
+ qDebug() << "OutNumbersList" << out_list_numbers;
+
+ QCOMPARE(f2.get("list_strings","NOT SET").toString(), slist_strings);
+ QCOMPARE(out_list_strings, list_strings);
+ QCOMPARE(f2.get("list_numbers","NOT SET").toString(), slist_numbers);
+ QCOMPARE(out_list_numbers, list_numbers);
}
};
From e0ef86340f72ce508034815f1c5f8c695d31140d Mon Sep 17 00:00:00 2001
From: Rachel Powers <508861+Ryex@users.noreply.github.com>
Date: Thu, 16 Feb 2023 03:31:04 -0700
Subject: [PATCH 031/104] feat: connect new help button
help-pages/instance-copy
Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com>
---
launcher/ui/dialogs/CopyInstanceDialog.cpp | 11 +++++++++++
launcher/ui/dialogs/CopyInstanceDialog.h | 3 +++
2 files changed, 14 insertions(+)
diff --git a/launcher/ui/dialogs/CopyInstanceDialog.cpp b/launcher/ui/dialogs/CopyInstanceDialog.cpp
index 495e98e9..62c0bb39 100644
--- a/launcher/ui/dialogs/CopyInstanceDialog.cpp
+++ b/launcher/ui/dialogs/CopyInstanceDialog.cpp
@@ -37,6 +37,7 @@
#include
#include "Application.h"
+#include "BuildConfig.h"
#include "CopyInstanceDialog.h"
#include "ui_CopyInstanceDialog.h"
@@ -47,6 +48,7 @@
#include "BaseInstance.h"
#include "InstanceList.h"
#include "FileSystem.h"
+#include "DesktopServices.h"
CopyInstanceDialog::CopyInstanceDialog(InstancePtr original, QWidget *parent)
:QDialog(parent), ui(new Ui::CopyInstanceDialog), m_original(original)
@@ -114,6 +116,9 @@ CopyInstanceDialog::CopyInstanceDialog(InstancePtr original, QWidget *parent)
updateLinkOptions();
updateUseCloneCheckbox();
+
+ auto HelpButton = ui->buttonBox->button(QDialogButtonBox::Help);
+ connect(HelpButton, &QPushButton::clicked, this, &CopyInstanceDialog::help);
}
@@ -157,6 +162,12 @@ const InstanceCopyPrefs& CopyInstanceDialog::getChosenOptions() const
return m_selectedOptions;
}
+
+void CopyInstanceDialog::help()
+{
+ DesktopServices::openUrl(QUrl(BuildConfig.HELP_URL.arg("instance-copy")));
+}
+
void CopyInstanceDialog::checkAllCheckboxes(const bool& b)
{
ui->keepPlaytimeCheckbox->setChecked(b);
diff --git a/launcher/ui/dialogs/CopyInstanceDialog.h b/launcher/ui/dialogs/CopyInstanceDialog.h
index 2dea3795..c447bee9 100644
--- a/launcher/ui/dialogs/CopyInstanceDialog.h
+++ b/launcher/ui/dialogs/CopyInstanceDialog.h
@@ -42,6 +42,9 @@ public:
QString iconKey() const;
const InstanceCopyPrefs& getChosenOptions() const;
+public slots:
+ void help();
+
private
slots:
void on_iconButton_clicked();
From ae289c923c4f896dca7e6696eef7ca35b10be9bf Mon Sep 17 00:00:00 2001
From: Rachel Powers <508861+Ryex@users.noreply.github.com>
Date: Wed, 22 Feb 2023 17:40:07 -0700
Subject: [PATCH 032/104] fix: clean up initial review comments (flowin)
Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com>
---
launcher/FileSystem.cpp | 15 +--
launcher/FileSystem.h | 6 +-
launcher/InstanceCopyTask.cpp | 6 +-
launcher/ui/dialogs/CopyInstanceDialog.ui | 133 +++++++++++-----------
tests/FileSystem_test.cpp | 6 +-
5 files changed, 84 insertions(+), 82 deletions(-)
diff --git a/launcher/FileSystem.cpp b/launcher/FileSystem.cpp
index 45c73d24..2bd0cf52 100644
--- a/launcher/FileSystem.cpp
+++ b/launcher/FileSystem.cpp
@@ -36,10 +36,6 @@
*/
#include "FileSystem.h"
-#include
-#include
-#include
-#include
#include "BuildConfig.h"
@@ -48,6 +44,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -435,7 +432,7 @@ bool create_link::make_links()
return true;
}
-void create_link::runPrivlaged(const QString& offset)
+void create_link::runPrivileged(const QString& offset)
{
m_linked = 0; // reset counter
m_path_results.clear();
@@ -506,10 +503,10 @@ void create_link::runPrivlaged(const QString& offset)
in >> err_value;
result.err_value = err_value;
if (result.err_value) {
- qDebug() << "privlaged link fail" << result.src << "to" << result.dst << "code" << result.err_value << result.err_msg;
+ qDebug() << "privileged link fail" << result.src << "to" << result.dst << "code" << result.err_value << result.err_msg;
emit linkFailed(result.src, result.dst, result.err_msg, result.err_value);
} else {
- qDebug() << "privlaged link success" << result.src << "to" << result.dst;
+ qDebug() << "privileged link success" << result.src << "to" << result.dst;
m_linked++;
emit fileLinked(result.src, result.dst);
}
@@ -533,7 +530,7 @@ void create_link::runPrivlaged(const QString& offset)
}
ExternalLinkFileProcess* linkFileProcess = new ExternalLinkFileProcess(serverName, m_useHardLinks, this);
- connect(linkFileProcess, &ExternalLinkFileProcess::processExited, this, [&]() { emit finishedPrivlaged(gotResults); });
+ connect(linkFileProcess, &ExternalLinkFileProcess::processExited, this, [&]() { emit finishedPrivileged(gotResults); });
connect(linkFileProcess, &ExternalLinkFileProcess::finished, linkFileProcess, &QObject::deleteLater);
linkFileProcess->start();
@@ -1041,7 +1038,7 @@ FilesystemInfo statFS(const QString& path)
QStorageInfo storage_info(NearestExistentAncestor(path));
- info.fsTypeName = QString::fromStdString(storage_info.fileSystemType().toStdString());
+ info.fsTypeName = storage_info.fileSystemType();
info.fsType = getFilesystemTypeFuzzy(info.fsTypeName);
diff --git a/launcher/FileSystem.h b/launcher/FileSystem.h
index 84526c11..71175bb4 100644
--- a/launcher/FileSystem.h
+++ b/launcher/FileSystem.h
@@ -220,8 +220,8 @@ class create_link : public QObject {
int totalLinked() { return m_linked; }
- void runPrivlaged() { runPrivlaged(QString()); }
- void runPrivlaged(const QString& offset);
+ void runPrivileged() { runPrivileged(QString()); }
+ void runPrivileged(const QString& offset);
QList getResults() { return m_path_results; }
@@ -230,7 +230,7 @@ class create_link : public QObject {
void fileLinked(const QString& srcName, const QString& dstName);
void linkFailed(const QString& srcName, const QString& dstName, const QString& err_msg, int err_value);
void finished();
- void finishedPrivlaged(bool gotResults);
+ void finishedPrivileged(bool gotResults);
private:
diff --git a/launcher/InstanceCopyTask.cpp b/launcher/InstanceCopyTask.cpp
index e0a4de0b..5ef7a7fd 100644
--- a/launcher/InstanceCopyTask.cpp
+++ b/launcher/InstanceCopyTask.cpp
@@ -72,14 +72,14 @@ void InstanceCopyTask::executeTask()
QEventLoop loop;
bool got_priv_results = false;
- connect(&folderLink, &FS::create_link::finishedPrivlaged, this, [&](bool gotResults){
+ connect(&folderLink, &FS::create_link::finishedPrivileged, this, [&](bool gotResults){
if (!gotResults) {
- qDebug() << "Privlaged run exited without results!";
+ qDebug() << "Privileged run exited without results!";
}
got_priv_results = gotResults;
loop.quit();
});
- folderLink.runPrivlaged();
+ folderLink.runPrivileged();
loop.exec(); // wait for the finished signal
diff --git a/launcher/ui/dialogs/CopyInstanceDialog.ui b/launcher/ui/dialogs/CopyInstanceDialog.ui
index 009f5b88..3101acec 100644
--- a/launcher/ui/dialogs/CopyInstanceDialog.ui
+++ b/launcher/ui/dialogs/CopyInstanceDialog.ui
@@ -9,8 +9,8 @@
0
0
- 527
- 699
+ 531
+ 653
@@ -112,35 +112,19 @@
- -
-
-
-
-
-
-
- 0
- 0
-
-
-
- Qt::LeftToRight
-
-
- Select all
-
-
- false
-
-
-
-
-
-
Instance Copy Options
+
-
+
+
+ Keep play time
+
+
+
-
@@ -151,6 +135,16 @@
+ -
+
+
+ true
+
+
+ Copy resource packs
+
+
+
-
@@ -161,13 +155,6 @@
- -
-
-
- Copy saves
-
-
-
-
@@ -182,20 +169,10 @@
- -
-
-
- true
-
+
-
+
- Copy resource packs
-
-
-
- -
-
-
- Keep play time
+ Copy saves
@@ -206,9 +183,35 @@
+ -
+
+
+
+ 0
+ 0
+
+
+
+ Qt::LeftToRight
+
+
+ Select all
+
+
+ false
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
-
@@ -250,7 +253,7 @@
-
-
+
6
@@ -263,24 +266,14 @@
6
-
-
-
-
- true
-
-
- Use hard links instead of symbolic links.
-
-
- Use hard links
-
-
-
-
false
+
+ Link each resource individually instead of linking whole folders at once
+
Link files recursively
@@ -302,14 +295,27 @@
- -
-
-
- Use symbolic links
+
-
+
+
+ true
+
+ Use hard links instead of copying files.
+
+
+ Use hard links
+
+
+
+ -
+
Use symbolic links instead of copying files.
+
+ Use symbolic links
+
@@ -373,7 +379,6 @@
iconButton
instNameTextBox
groupBox
- selectAllCheckbox
keepPlaytimeCheckbox
copyScreenshotsCheckbox
copySavesCheckbox
diff --git a/tests/FileSystem_test.cpp b/tests/FileSystem_test.cpp
index 169f0669..19565a99 100644
--- a/tests/FileSystem_test.cpp
+++ b/tests/FileSystem_test.cpp
@@ -72,15 +72,15 @@ class LinkTask : public Task {
qDebug() << "EXPECTED: Link failure, Windows requires permissions for symlinks";
qDebug() << "atempting to run with privelage";
- connect(m_lnk, &FS::create_link::finishedPrivlaged, this, [&](bool gotResults){
+ connect(m_lnk, &FS::create_link::finishedPrivileged, this, [&](bool gotResults){
if (gotResults) {
emitSucceeded();
} else {
- qDebug() << "Privlaged run exited without results!";
+ qDebug() << "Privileged run exited without results!";
emitFailed();
}
});
- m_lnk->runPrivlaged();
+ m_lnk->runPrivileged();
} else {
qDebug() << "Link Failed!" << m_lnk->getOSError().value() << m_lnk->getOSError().message().c_str();
}
From dc5402349e8c65945d46f1539fd92823a05a391c Mon Sep 17 00:00:00 2001
From: Rachel Powers <508861+Ryex@users.noreply.github.com>
Date: Thu, 23 Feb 2023 19:08:35 -0700
Subject: [PATCH 033/104] refactor: use UUID toString mode
Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com>
---
launcher/FileSystem.cpp | 2 +-
launcher/InstanceList.cpp | 2 +-
launcher/StringUtils.cpp | 14 +++-----------
launcher/StringUtils.h | 2 +-
4 files changed, 6 insertions(+), 14 deletions(-)
diff --git a/launcher/FileSystem.cpp b/launcher/FileSystem.cpp
index 2bd0cf52..714af4a0 100644
--- a/launcher/FileSystem.cpp
+++ b/launcher/FileSystem.cpp
@@ -442,7 +442,7 @@ void create_link::runPrivileged(const QString& offset)
make_link_list(offset);
- QString serverName = BuildConfig.LAUNCHER_APP_BINARY_NAME + "_filelink_server" + StringUtils::getRandomAlphaNumeric(8);
+ QString serverName = BuildConfig.LAUNCHER_APP_BINARY_NAME + "_filelink_server" + StringUtils::getRandomAlphaNumeric();
connect(&m_linkServer, &QLocalServer::newConnection, this, [&](){
diff --git a/launcher/InstanceList.cpp b/launcher/InstanceList.cpp
index 179bfb9a..5b33d678 100644
--- a/launcher/InstanceList.cpp
+++ b/launcher/InstanceList.cpp
@@ -875,7 +875,7 @@ Task* InstanceList::wrapInstanceTask(InstanceTask* task)
QString InstanceList::getStagedInstancePath()
{
- QString key = QUuid::createUuid().toString().remove("{").remove("}");
+ QString key = QUuid::createUuid().toString(QUuid::WithoutBraces);
QString tempDir = ".LAUNCHER_TEMP/";
QString relPath = FS::PathCombine(tempDir, key);
QDir rootPath(m_instDir);
diff --git a/launcher/StringUtils.cpp b/launcher/StringUtils.cpp
index 93a44d4c..2fa56501 100644
--- a/launcher/StringUtils.cpp
+++ b/launcher/StringUtils.cpp
@@ -1,6 +1,6 @@
#include "StringUtils.h"
-#include
+#include
/// If you're wondering where these came from exactly, then know you're not the only one =D
@@ -77,15 +77,7 @@ int StringUtils::naturalCompare(const QString& s1, const QString& s2, Qt::CaseSe
return QString::compare(s1, s2, cs);
}
-QString StringUtils::getRandomAlphaNumeric(const int length)
+QString StringUtils::getRandomAlphaNumeric()
{
- const QString possibleCharacters("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789");
- QString randomString;
- for(int i=0; i < length; ++i)
- {
- int index = QRandomGenerator::global()->bounded(0, possibleCharacters.length());
- QChar nextChar = possibleCharacters.at(index);
- randomString.append(nextChar);
- }
- return randomString;
+ return QUuid::createUuid().toString(QUuid::Id128);
}
diff --git a/launcher/StringUtils.h b/launcher/StringUtils.h
index 1ba19555..c4a6ab31 100644
--- a/launcher/StringUtils.h
+++ b/launcher/StringUtils.h
@@ -30,5 +30,5 @@ inline QString fromStdString(string s)
int naturalCompare(const QString& s1, const QString& s2, Qt::CaseSensitivity cs);
-QString getRandomAlphaNumeric(const int length);
+QString getRandomAlphaNumeric();
} // namespace StringUtils
From 458c2f38bc8e560832ca09b846edaa9ddc64f58d Mon Sep 17 00:00:00 2001
From: Rachel Powers <508861+Ryex@users.noreply.github.com>
Date: Fri, 3 Mar 2023 06:33:37 -0700
Subject: [PATCH 034/104] cleanup: code review sugestions
clean up translation strings
Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com>
---
launcher/BaseInstance.cpp | 24 +++++++++----------
launcher/InstanceCopyTask.cpp | 4 ----
launcher/filelink/filelink.exe.manifest | 2 +-
launcher/filelink/main.cpp | 2 +-
launcher/minecraft/WorldList.cpp | 6 ++---
launcher/minecraft/mod/ModFolderModel.cpp | 7 +++---
.../minecraft/mod/ResourceFolderModel.cpp | 7 +++---
.../minecraft/mod/ResourcePackFolderModel.cpp | 7 +++---
launcher/ui/dialogs/CopyInstanceDialog.cpp | 2 +-
9 files changed, 30 insertions(+), 31 deletions(-)
diff --git a/launcher/BaseInstance.cpp b/launcher/BaseInstance.cpp
index 6428be43..ad45aa2d 100644
--- a/launcher/BaseInstance.cpp
+++ b/launcher/BaseInstance.cpp
@@ -66,7 +66,7 @@ BaseInstance::BaseInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr s
m_settings->registerSetting("totalTimePlayed", 0);
m_settings->registerSetting("lastTimePlayed", 0);
- m_settings->registerSetting("linkedInstancesList", "[]");
+ m_settings->registerSetting("linkedInstances", "[]");
// Game time override
auto gameTimeOverride = m_settings->registerSetting("OverrideGameTime", false);
@@ -188,34 +188,34 @@ bool BaseInstance::shouldStopOnConsoleOverflow() const
QStringList BaseInstance::getLinkedInstances() const
{
- return m_settings->getList("linkedInstancesList");
+ return m_settings->getList("linkedInstances");
}
void BaseInstance::setLinkedInstances(const QStringList& list)
{
- auto linkedInstancesList = m_settings->getList("linkedInstancesList");
- m_settings->setList("linkedInstancesList", list);
+ auto linkedInstances = m_settings->getList("linkedInstances");
+ m_settings->setList("linkedInstances", list);
}
void BaseInstance::addLinkedInstanceId(const QString& id)
{
- auto linkedInstancesList = m_settings->getList("linkedInstancesList");
- linkedInstancesList.append(id);
- setLinkedInstances(linkedInstancesList);
+ auto linkedInstances = m_settings->getList("linkedInstances");
+ linkedInstances.append(id);
+ setLinkedInstances(linkedInstances);
}
bool BaseInstance::removeLinkedInstanceId(const QString& id)
{
- auto linkedInstancesList = m_settings->getList("linkedInstancesList");
- int numRemoved = linkedInstancesList.removeAll(id);
- setLinkedInstances(linkedInstancesList);
+ auto linkedInstances = m_settings->getList("linkedInstances");
+ int numRemoved = linkedInstances.removeAll(id);
+ setLinkedInstances(linkedInstances);
return numRemoved > 0;
}
bool BaseInstance::isLinkedToInstanceId(const QString& id) const
{
- auto linkedInstancesList = m_settings->getList("linkedInstancesList");
- return linkedInstancesList.contains(id);
+ auto linkedInstances = m_settings->getList("linkedInstances");
+ return linkedInstances.contains(id);
}
void BaseInstance::iconUpdated(QString key)
diff --git a/launcher/InstanceCopyTask.cpp b/launcher/InstanceCopyTask.cpp
index 5ef7a7fd..6bd56de3 100644
--- a/launcher/InstanceCopyTask.cpp
+++ b/launcher/InstanceCopyTask.cpp
@@ -10,10 +10,6 @@ InstanceCopyTask::InstanceCopyTask(InstancePtr origInstance, const InstanceCopyP
{
m_origInstance = origInstance;
m_keepPlaytime = prefs.isKeepPlaytimeEnabled();
-
-
-
-
m_useLinks = prefs.isUseSymLinksEnabled();
m_linkRecursively = prefs.isLinkRecursivelyEnabled();
m_useHardLinks = prefs.isLinkRecursivelyEnabled() && prefs.isUseHardLinksEnabled();
diff --git a/launcher/filelink/filelink.exe.manifest b/launcher/filelink/filelink.exe.manifest
index a4e16264..239aa978 100644
--- a/launcher/filelink/filelink.exe.manifest
+++ b/launcher/filelink/filelink.exe.manifest
@@ -25,4 +25,4 @@
-
\ No newline at end of file
+
diff --git a/launcher/filelink/main.cpp b/launcher/filelink/main.cpp
index 7f06795e..4a22ff18 100644
--- a/launcher/filelink/main.cpp
+++ b/launcher/filelink/main.cpp
@@ -28,4 +28,4 @@ int main(int argc, char *argv[])
FileLinkApp ldh(argc, argv);
return ldh.exec();
-}
\ No newline at end of file
+}
diff --git a/launcher/minecraft/WorldList.cpp b/launcher/minecraft/WorldList.cpp
index 43733110..3681bcda 100644
--- a/launcher/minecraft/WorldList.cpp
+++ b/launcher/minecraft/WorldList.cpp
@@ -237,11 +237,11 @@ QVariant WorldList::data(const QModelIndex &index, int role) const
{
if (column == InfoColumn) {
if (world.isSymLinkUnder(instDirPath())) {
- return tr("Warning: This world is symbolically linked from elsewhere. Editing it will also change the original") +
- tr("\nCanonical Path: %1").arg(world.canonicalFilePath());
+ return tr("Warning: This world is symbolically linked from elsewhere. Editing it will also change the original."
+ "\nCanonical Path: %1").arg(world.canonicalFilePath());
}
if (world.isMoreThanOneHardLink()) {
- return tr("Warning: This world is hard linked elsewhere. Editing it will also change the original");
+ return tr("Warning: This world is hard linked elsewhere. Editing it will also change the original.");
}
}
return world.folderName();
diff --git a/launcher/minecraft/mod/ModFolderModel.cpp b/launcher/minecraft/mod/ModFolderModel.cpp
index 943f30ad..91d16175 100644
--- a/launcher/minecraft/mod/ModFolderModel.cpp
+++ b/launcher/minecraft/mod/ModFolderModel.cpp
@@ -104,12 +104,13 @@ QVariant ModFolderModel::data(const QModelIndex &index, int role) const
if (column == NAME_COLUMN) {
if (at(row)->isSymLinkUnder(instDirPath())) {
return m_resources[row]->internal_id() +
- tr("\nWarning: This resource is symbolically linked from elsewhere. Editing it will also change the original") +
- tr("\nCanonical Path: %1").arg(at(row)->fileinfo().canonicalFilePath());
+ tr("\nWarning: This resource is symbolically linked from elsewhere. Editing it will also change the original."
+ "\nCanonical Path: %1")
+ .arg(at(row)->fileinfo().canonicalFilePath());
}
if (at(row)->isMoreThanOneHardLink()) {
return m_resources[row]->internal_id() +
- tr("\nWarning: This resource is hard linked elsewhere. Editing it will also change the original");
+ tr("\nWarning: This resource is hard linked elsewhere. Editing it will also change the original.");
}
}
return m_resources[row]->internal_id();
diff --git a/launcher/minecraft/mod/ResourceFolderModel.cpp b/launcher/minecraft/mod/ResourceFolderModel.cpp
index 95b7651f..29a0c736 100644
--- a/launcher/minecraft/mod/ResourceFolderModel.cpp
+++ b/launcher/minecraft/mod/ResourceFolderModel.cpp
@@ -424,12 +424,13 @@ QVariant ResourceFolderModel::data(const QModelIndex& index, int role) const
if (column == NAME_COLUMN) {
if (at(row).isSymLinkUnder(instDirPath())) {
return m_resources[row]->internal_id() +
- tr("\nWarning: This resource is symbolically linked from elsewhere. Editing it will also change the original") +
- tr("\nCanonical Path: %1").arg(at(row).fileinfo().canonicalFilePath());;
+ tr("\nWarning: This resource is symbolically linked from elsewhere. Editing it will also change the original."
+ "\nCanonical Path: %1")
+ .arg(at(row).fileinfo().canonicalFilePath());;
}
if (at(row).isMoreThanOneHardLink()) {
return m_resources[row]->internal_id() +
- tr("\nWarning: This resource is hard linked elsewhere. Editing it will also change the original");
+ tr("\nWarning: This resource is hard linked elsewhere. Editing it will also change the original.");
}
}
diff --git a/launcher/minecraft/mod/ResourcePackFolderModel.cpp b/launcher/minecraft/mod/ResourcePackFolderModel.cpp
index 1fcfa909..0480d8ba 100644
--- a/launcher/minecraft/mod/ResourcePackFolderModel.cpp
+++ b/launcher/minecraft/mod/ResourcePackFolderModel.cpp
@@ -96,12 +96,13 @@ QVariant ResourcePackFolderModel::data(const QModelIndex& index, int role) const
if (column == NAME_COLUMN) {
if (at(row)->isSymLinkUnder(instDirPath())) {
return m_resources[row]->internal_id() +
- tr("\nWarning: This resource is symbolically linked from elsewhere. Editing it will also change the original") +
- tr("\nCanonical Path: %1").arg(at(row)->fileinfo().canonicalFilePath());;
+ tr("\nWarning: This resource is symbolically linked from elsewhere. Editing it will also change the original."
+ "\nCanonical Path: %1")
+ .arg(at(row)->fileinfo().canonicalFilePath());;
}
if (at(row)->isMoreThanOneHardLink()) {
return m_resources[row]->internal_id() +
- tr("\nWarning: This resource is hard linked elsewhere. Editing it will also change the original");
+ tr("\nWarning: This resource is hard linked elsewhere. Editing it will also change the original.");
}
}
return m_resources[row]->internal_id();
diff --git a/launcher/ui/dialogs/CopyInstanceDialog.cpp b/launcher/ui/dialogs/CopyInstanceDialog.cpp
index 62c0bb39..ced57ae0 100644
--- a/launcher/ui/dialogs/CopyInstanceDialog.cpp
+++ b/launcher/ui/dialogs/CopyInstanceDialog.cpp
@@ -324,4 +324,4 @@ void CopyInstanceDialog::on_useCloneCheckbox_stateChanged(int state)
m_selectedOptions.enableUseClone(m_cloneSupported && (state == Qt::Checked));
updateUseCloneCheckbox();
updateLinkOptions();
-}
\ No newline at end of file
+}
From a96519cbdc61387a79182fb81b016e5d73105713 Mon Sep 17 00:00:00 2001
From: Rachel Powers <508861+Ryex@users.noreply.github.com>
Date: Fri, 3 Mar 2023 06:39:13 -0700
Subject: [PATCH 035/104] workflow: add filelink.exe to SignTool call
Signed-off-by: Rachel Powers <508861+Ryex@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 022a04f8..df1a38fc 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -396,7 +396,7 @@ jobs:
if (Get-Content ./codesign.pfx){
cd ${{ env.INSTALL_DIR }}
# We ship the exact same executable for portable and non-portable editions, so signing just once is fine
- SignTool sign /fd sha256 /td sha256 /f ../codesign.pfx /p '${{ secrets.WINDOWS_CODESIGN_PASSWORD }}' /tr http://timestamp.digicert.com prismlauncher.exe
+ SignTool sign /fd sha256 /td sha256 /f ../codesign.pfx /p '${{ secrets.WINDOWS_CODESIGN_PASSWORD }}' /tr http://timestamp.digicert.com prismlauncher.exe prismlauncher_filelink.exe
} else {
":warning: Skipped code signing for Windows, as certificate was not present." >> $env:GITHUB_STEP_SUMMARY
}
From 0bec0046bbab911909aacb4d02525b3b85597447 Mon Sep 17 00:00:00 2001
From: Rachel Powers <508861+Ryex@users.noreply.github.com>
Date: Fri, 3 Mar 2023 07:28:59 -0700
Subject: [PATCH 036/104] format: clang-format to fix windows fallout
it looked fine over in vscod on windows but
as soon as I opened it on linux via Helix
the chaos was clear
Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com>
---
launcher/FileSystem.cpp | 487 ++++++++++++++++++----------------------
1 file changed, 220 insertions(+), 267 deletions(-)
diff --git a/launcher/FileSystem.cpp b/launcher/FileSystem.cpp
index 714af4a0..c913b43e 100644
--- a/launcher/FileSystem.cpp
+++ b/launcher/FileSystem.cpp
@@ -44,9 +44,9 @@
#include
#include
#include
-#include
#include
#include
+#include
#include
#include
#include
@@ -68,10 +68,10 @@
#include
#include
#include
-//for ShellExecute
-#include
-#include
+// for ShellExecute
#include
+#include
+#include
#else
#include
#endif
@@ -79,47 +79,46 @@
// Snippet from https://github.com/gulrak/filesystem#using-it-as-single-file-header
#ifdef __APPLE__
-#include // for deployment target to support pre-catalina targets without std::fs
-#endif // __APPLE__
+#include // for deployment target to support pre-catalina targets without std::fs
+#endif // __APPLE__
#if ((defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) || (defined(__cplusplus) && __cplusplus >= 201703L)) && defined(__has_include)
#if __has_include() && (!defined(__MAC_OS_X_VERSION_MIN_REQUIRED) || __MAC_OS_X_VERSION_MIN_REQUIRED >= 101500)
#define GHC_USE_STD_FS
#include
namespace fs = std::filesystem;
-#endif // MacOS min version check
-#endif // Other OSes version check
+#endif // MacOS min version check
+#endif // Other OSes version check
#ifndef GHC_USE_STD_FS
#include
namespace fs = ghc::filesystem;
#endif
-
// clone
#if defined(Q_OS_LINUX)
-#include
-#include /* Definition of FICLONE* constants */
-#include
#include
+#include /* Definition of FICLONE* constants */
+#include
+#include
#include
#elif defined(Q_OS_MACOS) || defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD)
#include
#include
#elif defined(Q_OS_WIN)
// winbtrfs clone vs rundll32 shellbtrfs.dll,ReflinkCopy
-#include
+#include
#include
#include
-#include
+#include
// refs
#include
-# if defined(__MINGW32__)
-# include
-# endif
+#if defined(__MINGW32__)
+#include
+#endif
#endif
-#if defined(Q_OS_WIN)
+#if defined(Q_OS_WIN)
#if defined(__MINGW32__)
@@ -131,46 +130,45 @@ typedef struct _DUPLICATE_EXTENTS_DATA {
} DUPLICATE_EXTENTS_DATA, *PDUPLICATE_EXTENTS_DATA;
typedef struct _FSCTL_GET_INTEGRITY_INFORMATION_BUFFER {
- WORD ChecksumAlgorithm; // Checksum algorithm. e.g. CHECKSUM_TYPE_UNCHANGED, CHECKSUM_TYPE_NONE, CHECKSUM_TYPE_CRC32
- WORD Reserved; // Must be 0
- DWORD Flags; // FSCTL_INTEGRITY_FLAG_xxx
+ WORD ChecksumAlgorithm; // Checksum algorithm. e.g. CHECKSUM_TYPE_UNCHANGED, CHECKSUM_TYPE_NONE, CHECKSUM_TYPE_CRC32
+ WORD Reserved; // Must be 0
+ DWORD Flags; // FSCTL_INTEGRITY_FLAG_xxx
DWORD ChecksumChunkSizeInBytes;
DWORD ClusterSizeInBytes;
} FSCTL_GET_INTEGRITY_INFORMATION_BUFFER, *PFSCTL_GET_INTEGRITY_INFORMATION_BUFFER;
-
typedef struct _FSCTL_SET_INTEGRITY_INFORMATION_BUFFER {
- WORD ChecksumAlgorithm; // Checksum algorithm. e.g. CHECKSUM_TYPE_UNCHANGED, CHECKSUM_TYPE_NONE, CHECKSUM_TYPE_CRC32
- WORD Reserved; // Must be 0
- DWORD Flags; // FSCTL_INTEGRITY_FLAG_xxx
+ WORD ChecksumAlgorithm; // Checksum algorithm. e.g. CHECKSUM_TYPE_UNCHANGED, CHECKSUM_TYPE_NONE, CHECKSUM_TYPE_CRC32
+ WORD Reserved; // Must be 0
+ DWORD Flags; // FSCTL_INTEGRITY_FLAG_xxx
} FSCTL_SET_INTEGRITY_INFORMATION_BUFFER, *PFSCTL_SET_INTEGRITY_INFORMATION_BUFFER;
#endif
-
#ifndef FSCTL_DUPLICATE_EXTENTS_TO_FILE
-#define FSCTL_DUPLICATE_EXTENTS_TO_FILE CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 209, METHOD_BUFFERED, FILE_WRITE_DATA )
+#define FSCTL_DUPLICATE_EXTENTS_TO_FILE CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 209, METHOD_BUFFERED, FILE_WRITE_DATA)
#endif
#ifndef FSCTL_GET_INTEGRITY_INFORMATION
-#define FSCTL_GET_INTEGRITY_INFORMATION CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 159, METHOD_BUFFERED, FILE_ANY_ACCESS) // FSCTL_GET_INTEGRITY_INFORMATION_BUFFER
+#define FSCTL_GET_INTEGRITY_INFORMATION \
+ CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 159, METHOD_BUFFERED, FILE_ANY_ACCESS) // FSCTL_GET_INTEGRITY_INFORMATION_BUFFER
#endif
#ifndef FSCTL_SET_INTEGRITY_INFORMATION
-#define FSCTL_SET_INTEGRITY_INFORMATION CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 160, METHOD_BUFFERED, FILE_READ_DATA | FILE_WRITE_DATA) // FSCTL_SET_INTEGRITY_INFORMATION_BUFFER
+#define FSCTL_SET_INTEGRITY_INFORMATION \
+ CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 160, METHOD_BUFFERED, FILE_READ_DATA | FILE_WRITE_DATA) // FSCTL_SET_INTEGRITY_INFORMATION_BUFFER
#endif
#ifndef ERROR_NOT_CAPABLE
-#define ERROR_NOT_CAPABLE 775L
+#define ERROR_NOT_CAPABLE 775L
#endif
#ifndef ERROR_BLOCK_TOO_MANY_REFERENCES
-#define ERROR_BLOCK_TOO_MANY_REFERENCES 347L
+#define ERROR_BLOCK_TOO_MANY_REFERENCES 347L
#endif
#endif
-
namespace FS {
void ensureExists(const QDir& dir)
@@ -313,23 +311,22 @@ QDebug operator<<(QDebug debug, const LinkPair& lp)
return debug;
}
-bool create_link::operator()(const QString& offset, bool dryRun)
+bool create_link::operator()(const QString& offset, bool dryRun)
{
m_linked = 0; // reset counter
m_path_results.clear();
m_links_to_make.clear();
m_path_results.clear();
-
+
make_link_list(offset);
-
+
if (!dryRun)
return make_links();
return true;
}
-
/**
* @brief make a list off all the links ot make
* @param offset subdirectory form src to link to dest
@@ -354,13 +351,12 @@ void create_link::make_link_list(const QString& offset)
qDebug() << "path" << relative_dst_path << "in black list or not in whitelist";
return;
}
-
auto dst_path = PathCombine(dst, relative_dst_path);
- LinkPair link = {src_path, dst_path};
- m_links_to_make.append(link);
+ LinkPair link = { src_path, dst_path };
+ m_links_to_make.append(link);
};
-
+
if ((!m_recursive) || !fs::is_directory(StringUtils::toStdString(src))) {
if (m_debug)
qDebug() << "linking single file or dir:" << src << "to" << dst;
@@ -377,7 +373,7 @@ void create_link::make_link_list(const QString& offset)
auto src_path = source_it.next();
auto relative_path = src_dir.relativeFilePath(src_path);
- if (m_max_depth >= 0 && PathDepth(relative_path) > m_max_depth){
+ if (m_max_depth >= 0 && PathDepth(relative_path) > m_max_depth) {
relative_path = PathTruncate(relative_path, m_max_depth);
src_path = src_dir.filePath(relative_path);
if (linkedPaths.contains(src_path)) {
@@ -390,13 +386,12 @@ void create_link::make_link_list(const QString& offset)
link_file(src_path, relative_path);
}
}
- }
+ }
}
bool create_link::make_links()
-{
+{
for (auto link : m_links_to_make) {
-
QString src_path = link.src;
QString dst_path = link.dst;
@@ -414,7 +409,6 @@ bool create_link::make_links()
qDebug() << "making symlink:" << src_path << "to" << dst_path;
fs::create_symlink(StringUtils::toStdString(src_path), StringUtils::toStdString(dst_path), m_os_err);
}
-
if (m_os_err) {
qWarning() << "Failed to link files:" << QString::fromStdString(m_os_err.message());
@@ -427,7 +421,8 @@ bool create_link::make_links()
m_linked++;
emit fileLinked(src_path, dst_path);
}
- if (m_os_err) return false;
+ if (m_os_err)
+ return false;
}
return true;
}
@@ -444,17 +439,16 @@ void create_link::runPrivileged(const QString& offset)
QString serverName = BuildConfig.LAUNCHER_APP_BINARY_NAME + "_filelink_server" + StringUtils::getRandomAlphaNumeric();
- connect(&m_linkServer, &QLocalServer::newConnection, this, [&](){
-
+ connect(&m_linkServer, &QLocalServer::newConnection, this, [&]() {
qDebug() << "Client connected, sending out pairs";
// construct block of data to send
QByteArray block;
QDataStream out(&block, QIODevice::WriteOnly);
- out.setVersion(QDataStream::Qt_5_0); // choose correct version better?
+ out.setVersion(QDataStream::Qt_5_0); // choose correct version better?
qint32 blocksize = quint32(sizeof(quint32));
for (auto link : m_links_to_make) {
- blocksize += quint32(link.src.size());
+ blocksize += quint32(link.src.size());
blocksize += quint32(link.dst.size());
}
qDebug() << "About to write block of size:" << blocksize;
@@ -462,15 +456,14 @@ void create_link::runPrivileged(const QString& offset)
out << quint32(m_links_to_make.length());
for (auto link : m_links_to_make) {
- out << link.src;
+ out << link.src;
out << link.dst;
}
- QLocalSocket *clientConnection = m_linkServer.nextPendingConnection();
- connect(clientConnection, &QLocalSocket::disconnected,
- clientConnection, &QLocalSocket::deleteLater);
-
- connect(clientConnection, &QLocalSocket::readyRead, this, [&, clientConnection](){
+ QLocalSocket* clientConnection = m_linkServer.nextPendingConnection();
+ connect(clientConnection, &QLocalSocket::disconnected, clientConnection, &QLocalSocket::deleteLater);
+
+ connect(clientConnection, &QLocalSocket::readyRead, this, [&, clientConnection]() {
QDataStream in;
quint32 blockSize = 0;
in.setDevice(clientConnection);
@@ -489,18 +482,18 @@ void create_link::runPrivileged(const QString& offset)
qDebug() << "bytes avalible" << clientConnection->bytesAvailable();
if (clientConnection->bytesAvailable() < blockSize || in.atEnd())
return;
-
+
quint32 numResults;
in >> numResults;
qDebug() << "numResults" << numResults;
- for(quint32 i = 0; i < numResults; i++) {
+ for (quint32 i = 0; i < numResults; i++) {
FS::LinkResult result;
in >> result.src;
in >> result.dst;
in >> result.err_msg;
qint32 err_value;
- in >> err_value;
+ in >> err_value;
result.err_value = err_value;
if (result.err_value) {
qDebug() << "privileged link fail" << result.src << "to" << result.dst << "code" << result.err_value << result.err_msg;
@@ -520,7 +513,6 @@ void create_link::runPrivileged(const QString& offset)
qint64 byteswritten = clientConnection->write(block);
bool bytesflushed = clientConnection->flush();
qDebug() << "block flushed" << byteswritten << bytesflushed;
-
});
qDebug() << "Listening on pipe" << serverName;
@@ -534,11 +526,12 @@ void create_link::runPrivileged(const QString& offset)
connect(linkFileProcess, &ExternalLinkFileProcess::finished, linkFileProcess, &QObject::deleteLater);
linkFileProcess->start();
-
}
-void ExternalLinkFileProcess::runLinkFile() {
- QString fileLinkExe = PathCombine(QCoreApplication::instance()->applicationDirPath(), BuildConfig.LAUNCHER_APP_BINARY_NAME + "_filelink");
+void ExternalLinkFileProcess::runLinkFile()
+{
+ QString fileLinkExe =
+ PathCombine(QCoreApplication::instance()->applicationDirPath(), BuildConfig.LAUNCHER_APP_BINARY_NAME + "_filelink");
QString params = "-s " + m_server;
params += " -H " + QVariant(m_useHardLinks).toString();
@@ -550,14 +543,15 @@ void ExternalLinkFileProcess::runLinkFile() {
qDebug() << "Running: runas" << fileLinkExe << params;
- LPCWSTR programNameWin = (const wchar_t*) fileLinkExe.utf16();
- LPCWSTR paramsWin = (const wchar_t*) params.utf16();
+ LPCWSTR programNameWin = (const wchar_t*)fileLinkExe.utf16();
+ LPCWSTR paramsWin = (const wchar_t*)params.utf16();
// https://learn.microsoft.com/en-us/windows/win32/api/shellapi/ns-shellapi-shellexecuteinfoa
ShExecInfo.cbSize = sizeof(SHELLEXECUTEINFO);
ShExecInfo.fMask = SEE_MASK_NOCLOSEPROCESS;
- ShExecInfo.hwnd = NULL; // Optional. A handle to the owner window, used to display and position any UI that the system might produce while executing this function.
- ShExecInfo.lpVerb = L"runas"; // elevate to admin, show UAC
+ ShExecInfo.hwnd = NULL; // Optional. A handle to the owner window, used to display and position any UI that the system might produce
+ // while executing this function.
+ ShExecInfo.lpVerb = L"runas"; // elevate to admin, show UAC
ShExecInfo.lpFile = programNameWin;
ShExecInfo.lpParameters = paramsWin;
ShExecInfo.lpDirectory = NULL;
@@ -602,7 +596,7 @@ bool deletePath(QString path)
return err.value() == 0;
}
-bool trash(QString path, QString *pathInTrash)
+bool trash(QString path, QString* pathInTrash)
{
#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
return false;
@@ -644,7 +638,8 @@ QString AbsolutePath(const QString& path)
int PathDepth(const QString& path)
{
- if (path.isEmpty()) return 0;
+ if (path.isEmpty())
+ return 0;
QFileInfo info(path);
@@ -657,17 +652,18 @@ int PathDepth(const QString& path)
int numParts = parts.length();
numParts -= parts.count(".");
numParts -= parts.count("..") * 2;
-
+
return numParts;
}
QString PathTruncate(const QString& path, int depth)
{
- if (path.isEmpty() || (depth < 0) ) return "";
+ if (path.isEmpty() || (depth < 0))
+ return "";
QString trunc = QFileInfo(path).path();
- if (PathDepth(trunc) > depth ) {
+ if (PathDepth(trunc) > depth) {
return PathTruncate(trunc, depth);
}
@@ -786,11 +782,7 @@ bool createShortcut(QString destination, QString target, QStringList args, QStri
stream << "#!/bin/bash"
<< "\n";
- stream << "\""
- << target
- << "\" "
- << argstring
- << "\n";
+ stream << "\"" << target << "\" " << argstring << "\n";
stream.flush();
f.close();
@@ -813,8 +805,7 @@ bool createShortcut(QString destination, QString target, QStringList args, QStri
<< "\n";
stream << "Exec=\"" << target.toLocal8Bit() << "\"" << argstring.toLocal8Bit() << "\n";
stream << "Name=" << name.toLocal8Bit() << "\n";
- if (!icon.isEmpty())
- {
+ if (!icon.isEmpty()) {
stream << "Icon=" << icon.toLocal8Bit() << "\n";
}
@@ -827,55 +818,45 @@ bool createShortcut(QString destination, QString target, QStringList args, QStri
#elif defined(Q_OS_WIN)
QFileInfo targetInfo(target);
- if (!targetInfo.exists())
- {
+ if (!targetInfo.exists()) {
qWarning() << "Target file does not exist!";
return false;
}
target = targetInfo.absoluteFilePath();
- if (target.length() >= MAX_PATH)
- {
+ if (target.length() >= MAX_PATH) {
qWarning() << "Target file path is too long!";
return false;
}
- if (!icon.isEmpty() && icon.length() >= MAX_PATH)
- {
+ if (!icon.isEmpty() && icon.length() >= MAX_PATH) {
qWarning() << "Icon path is too long!";
return false;
}
destination += ".lnk";
- if (destination.length() >= MAX_PATH)
- {
+ if (destination.length() >= MAX_PATH) {
qWarning() << "Destination path is too long!";
return false;
}
QString argStr;
int argCount = args.count();
- for (int i = 0; i < argCount; i++)
- {
- if (args[i].contains(' '))
- {
+ for (int i = 0; i < argCount; i++) {
+ if (args[i].contains(' ')) {
argStr.append('"').append(args[i]).append('"');
- }
- else
- {
+ } else {
argStr.append(args[i]);
}
- if (i < argCount - 1)
- {
+ if (i < argCount - 1) {
argStr.append(" ");
}
}
- if (argStr.length() >= MAX_PATH)
- {
+ if (argStr.length() >= MAX_PATH) {
qWarning() << "Arguments string is too long!";
return false;
}
@@ -884,8 +865,7 @@ bool createShortcut(QString destination, QString target, QStringList args, QStri
// ...yes, you need to initialize the entire COM stack just to make a shortcut
hres = CoInitialize(nullptr);
- if (FAILED(hres))
- {
+ if (FAILED(hres)) {
qWarning() << "Failed to initialize COM!";
return false;
}
@@ -896,8 +876,7 @@ bool createShortcut(QString destination, QString target, QStringList args, QStri
// create an IShellLink instance - this stores the shortcut's attributes
hres = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLink, (LPVOID*)&psl);
- if (SUCCEEDED(hres))
- {
+ if (SUCCEEDED(hres)) {
wmemset(wsz, 0, MAX_PATH);
target.toWCharArray(wsz);
psl->SetPath(wsz);
@@ -908,10 +887,9 @@ bool createShortcut(QString destination, QString target, QStringList args, QStri
wmemset(wsz, 0, MAX_PATH);
targetInfo.absolutePath().toWCharArray(wsz);
- psl->SetWorkingDirectory(wsz); // "Starts in" attribute
+ psl->SetWorkingDirectory(wsz); // "Starts in" attribute
- if (!icon.isEmpty())
- {
+ if (!icon.isEmpty()) {
wmemset(wsz, 0, MAX_PATH);
icon.toWCharArray(wsz);
psl->SetIconLocation(wsz, 0);
@@ -921,27 +899,21 @@ bool createShortcut(QString destination, QString target, QStringList args, QStri
// this is the interface that will actually let us save the shortcut to disk!
IPersistFile* ppf;
hres = psl->QueryInterface(IID_IPersistFile, (LPVOID*)&ppf);
- if (SUCCEEDED(hres))
- {
+ if (SUCCEEDED(hres)) {
wmemset(wsz, 0, MAX_PATH);
destination.toWCharArray(wsz);
hres = ppf->Save(wsz, TRUE);
- if (FAILED(hres))
- {
+ if (FAILED(hres)) {
qWarning() << "IPresistFile->Save() failed";
qWarning() << "hres = " << hres;
}
ppf->Release();
- }
- else
- {
+ } else {
qWarning() << "Failed to query IPersistFile interface from IShellLink instance";
qWarning() << "hres = " << hres;
}
psl->Release();
- }
- else
- {
+ } else {
qWarning() << "Failed to create IShellLink instance";
qWarning() << "hres = " << hres;
}
@@ -977,32 +949,32 @@ bool overrideFolder(QString overwritten_path, QString override_path)
return err.value() == 0;
}
-QString getFilesystemTypeName(FilesystemType type) {
+QString getFilesystemTypeName(FilesystemType type)
+{
auto iter = s_filesystem_type_names.constFind(type);
- if (iter != s_filesystem_type_names.constEnd()){
+ if (iter != s_filesystem_type_names.constEnd()) {
return iter.value();
}
- return getFilesystemTypeName(FilesystemType::UNKNOWN);
+ return getFilesystemTypeName(FilesystemType::UNKNOWN);
}
FilesystemType getFilesystemTypeFuzzy(const QString& name)
{
auto iter = s_filesystem_type_names_inverse.constFind(name.toUpper());
- if (iter != s_filesystem_type_names_inverse.constEnd()){
+ if (iter != s_filesystem_type_names_inverse.constEnd()) {
return iter.value();
}
- return FilesystemType::UNKNOWN;
+ return FilesystemType::UNKNOWN;
}
FilesystemType getFilesystemType(const QString& name)
-{
+{
for (auto fs_type_pair : s_filesystem_type_names_inverse.toStdMap()) {
auto fs_type_name = fs_type_pair.first;
auto fs_type = fs_type_pair.second;
- if(name.toUpper().contains(fs_type_name.toUpper())) {
+ if (name.toUpper().contains(fs_type_name.toUpper())) {
return fs_type;
-
}
}
return FilesystemType::UNKNOWN;
@@ -1010,30 +982,29 @@ FilesystemType getFilesystemType(const QString& name)
/**
* @brief path to the near ancestor that exsists
- *
+ *
*/
QString NearestExistentAncestor(const QString& path)
{
- if(QFileInfo::exists(path)) return path;
+ if (QFileInfo::exists(path))
+ return path;
QDir dir(path);
- if(!dir.makeAbsolute()) return {};
- do
- {
+ if (!dir.makeAbsolute())
+ return {};
+ do {
dir.setPath(QDir::cleanPath(dir.filePath(QStringLiteral(".."))));
- }
- while(!dir.exists() && !dir.isRoot());
+ } while (!dir.exists() && !dir.isRoot());
return dir.exists() ? dir.path() : QString();
}
/**
* @brief colect information about the filesystem under a file
- *
+ *
*/
FilesystemInfo statFS(const QString& path)
{
-
FilesystemInfo info;
QStorageInfo storage_info(NearestExistentAncestor(path));
@@ -1054,11 +1025,11 @@ FilesystemInfo statFS(const QString& path)
}
/**
- * @brief if the Filesystem is reflink/clone capable
- *
+ * @brief if the Filesystem is reflink/clone capable
+ *
*/
bool canCloneOnFS(const QString& path)
-{
+{
FilesystemInfo info = statFS(path);
return canCloneOnFS(info);
}
@@ -1067,13 +1038,13 @@ bool canCloneOnFS(const FilesystemInfo& info)
return canCloneOnFS(info.fsType);
}
bool canCloneOnFS(FilesystemType type)
-{
+{
return s_clone_filesystems.contains(type);
}
/**
* @brief if the Filesystem is reflink/clone capable and both paths are on the same device
- *
+ *
*/
bool canClone(const QString& src, const QString& dst)
{
@@ -1092,7 +1063,6 @@ bool canClone(const QString& src, const QString& dst)
*/
bool clone::operator()(const QString& offset, bool dryRun)
{
-
if (!canClone(m_src.absolutePath(), m_dst.absolutePath())) {
qWarning() << "Can not clone: not same device or not clone/reflink filesystem";
qDebug() << "Source path:" << m_src.absolutePath();
@@ -1149,20 +1119,17 @@ bool clone::operator()(const QString& offset, bool dryRun)
/**
* @brief clone/reflink file from src to dst
- *
+ *
*/
bool clone_file(const QString& src, const QString& dst, std::error_code& ec)
-{
+{
auto src_path = StringUtils::toStdString(QDir::toNativeSeparators(QFileInfo(src).absoluteFilePath()));
auto dst_path = StringUtils::toStdString(QDir::toNativeSeparators(QFileInfo(dst).absoluteFilePath()));
FilesystemInfo srcinfo = statFS(src);
FilesystemInfo dstinfo = statFS(dst);
-
-
- if ((srcinfo.rootPath != dstinfo.rootPath) || (srcinfo.fsType != dstinfo.fsType))
- {
+ if ((srcinfo.rootPath != dstinfo.rootPath) || (srcinfo.fsType != dstinfo.fsType)) {
ec = std::make_error_code(std::errc::not_supported);
qWarning() << "reflink/clone must be to the same device and filesystem! src and dst root filesystems do not match.";
return false;
@@ -1177,22 +1144,20 @@ bool clone_file(const QString& src, const QString& dst, std::error_code& ec)
return false;
}
-
-
#elif defined(Q_OS_LINUX)
- if(!linux_ficlone(src_path, dst_path, ec)) {
+ if (!linux_ficlone(src_path, dst_path, ec)) {
qDebug() << "failed linux_ficlone:";
return false;
}
-
+
#elif defined(Q_OS_MACOS) || defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD)
-
- if(!macos_bsd_clonefile(src_path, dst_path, ec)) {
+
+ if (!macos_bsd_clonefile(src_path, dst_path, ec)) {
qDebug() << "failed macos_bsd_clonefile:";
return false;
}
-
+
#else
qWarning() << "clone/reflink not supported! unknown OS";
@@ -1217,168 +1182,156 @@ bool win_ioctl_clone(const std::wstring& src_path, const std::wstring& dst_path,
/**
* This algorithm inspired from https://github.com/0xbadfca11/reflink
* LICENSE MIT
- *
+ *
* Additional references
* https://learn.microsoft.com/en-us/windows/win32/api/winioctl/ni-winioctl-fsctl_duplicate_extents_to_file
* https://github.com/microsoft/CopyOnWrite/blob/main/lib/Windows/WindowsCopyOnWriteFilesystem.cs#L94
*/
HANDLE hSourceFile = CreateFileW(src_path.c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, 0, nullptr);
- if (hSourceFile == INVALID_HANDLE_VALUE)
- {
- ec = std::error_code(GetLastError(), std::system_category());
+ if (hSourceFile == INVALID_HANDLE_VALUE) {
+ ec = std::error_code(GetLastError(), std::system_category());
qDebug() << "Failed to open source file" << src_path.c_str();
return false;
- }
+ }
- ULONG fs_flags;
- if (!GetVolumeInformationByHandleW(hSourceFile, nullptr, 0, nullptr, nullptr, &fs_flags, nullptr, 0))
- {
- ec = std::error_code(GetLastError(), std::system_category());
+ ULONG fs_flags;
+ if (!GetVolumeInformationByHandleW(hSourceFile, nullptr, 0, nullptr, nullptr, &fs_flags, nullptr, 0)) {
+ ec = std::error_code(GetLastError(), std::system_category());
qDebug() << "Failed to get Filesystem information for " << src_path.c_str();
CloseHandle(hSourceFile);
- return false;
- }
- if (!(fs_flags & FILE_SUPPORTS_BLOCK_REFCOUNTING))
- {
- SetLastError(ERROR_NOT_CAPABLE);
- ec = std::error_code(GetLastError(), std::system_category());
+ return false;
+ }
+ if (!(fs_flags & FILE_SUPPORTS_BLOCK_REFCOUNTING)) {
+ SetLastError(ERROR_NOT_CAPABLE);
+ ec = std::error_code(GetLastError(), std::system_category());
qWarning() << "Filesystem at " << src_path.c_str() << " does not support reflink";
CloseHandle(hSourceFile);
return false;
- }
+ }
- FILE_END_OF_FILE_INFO sourceFileLength;
- if (!GetFileSizeEx(hSourceFile, &sourceFileLength.EndOfFile))
- {
- ec = std::error_code(GetLastError(), std::system_category());
+ FILE_END_OF_FILE_INFO sourceFileLength;
+ if (!GetFileSizeEx(hSourceFile, &sourceFileLength.EndOfFile)) {
+ ec = std::error_code(GetLastError(), std::system_category());
qDebug() << "Failed to size of source file" << src_path.c_str();
CloseHandle(hSourceFile);
return false;
- }
- FILE_BASIC_INFO sourceFileBasicInfo;
- if (!GetFileInformationByHandleEx(hSourceFile, FileBasicInfo, &sourceFileBasicInfo, sizeof(sourceFileBasicInfo)))
- {
- ec = std::error_code(GetLastError(), std::system_category());
+ }
+ FILE_BASIC_INFO sourceFileBasicInfo;
+ if (!GetFileInformationByHandleEx(hSourceFile, FileBasicInfo, &sourceFileBasicInfo, sizeof(sourceFileBasicInfo))) {
+ ec = std::error_code(GetLastError(), std::system_category());
qDebug() << "Failed to source file info" << src_path.c_str();
CloseHandle(hSourceFile);
- return false;
- }
- ULONG junk;
- FSCTL_GET_INTEGRITY_INFORMATION_BUFFER sourceFileIntegrity;
- if (!DeviceIoControl(hSourceFile, FSCTL_GET_INTEGRITY_INFORMATION, nullptr, 0, &sourceFileIntegrity, sizeof(sourceFileIntegrity), &junk, nullptr))
- {
- ec = std::error_code(GetLastError(), std::system_category());
+ return false;
+ }
+ ULONG junk;
+ FSCTL_GET_INTEGRITY_INFORMATION_BUFFER sourceFileIntegrity;
+ if (!DeviceIoControl(hSourceFile, FSCTL_GET_INTEGRITY_INFORMATION, nullptr, 0, &sourceFileIntegrity, sizeof(sourceFileIntegrity), &junk,
+ nullptr)) {
+ ec = std::error_code(GetLastError(), std::system_category());
qDebug() << "Failed to source file integrity info" << src_path.c_str();
CloseHandle(hSourceFile);
- return false;
- }
+ return false;
+ }
- HANDLE hDestFile = CreateFileW(dst_path.c_str(), GENERIC_READ | GENERIC_WRITE | DELETE, 0, nullptr, CREATE_NEW, 0, hSourceFile);
+ HANDLE hDestFile = CreateFileW(dst_path.c_str(), GENERIC_READ | GENERIC_WRITE | DELETE, 0, nullptr, CREATE_NEW, 0, hSourceFile);
- if (hDestFile == INVALID_HANDLE_VALUE)
- {
- ec = std::error_code(GetLastError(), std::system_category());
+ if (hDestFile == INVALID_HANDLE_VALUE) {
+ ec = std::error_code(GetLastError(), std::system_category());
qDebug() << "Failed to open dest file" << dst_path.c_str();
CloseHandle(hSourceFile);
- return false;
- }
- FILE_DISPOSITION_INFO destFileDispose = { TRUE };
- if (!SetFileInformationByHandle(hDestFile, FileDispositionInfo, &destFileDispose, sizeof(destFileDispose)))
- {
- ec = std::error_code(GetLastError(), std::system_category());
+ return false;
+ }
+ FILE_DISPOSITION_INFO destFileDispose = { TRUE };
+ if (!SetFileInformationByHandle(hDestFile, FileDispositionInfo, &destFileDispose, sizeof(destFileDispose))) {
+ ec = std::error_code(GetLastError(), std::system_category());
qDebug() << "Failed to set dest file info" << dst_path.c_str();
CloseHandle(hSourceFile);
CloseHandle(hDestFile);
- return false;
- }
+ return false;
+ }
- if (!DeviceIoControl(hDestFile, FSCTL_SET_SPARSE, nullptr, 0, nullptr, 0, &junk, nullptr))
- {
- ec = std::error_code(GetLastError(), std::system_category());
+ if (!DeviceIoControl(hDestFile, FSCTL_SET_SPARSE, nullptr, 0, nullptr, 0, &junk, nullptr)) {
+ ec = std::error_code(GetLastError(), std::system_category());
qDebug() << "Failed to set dest sparseness" << dst_path.c_str();
CloseHandle(hSourceFile);
CloseHandle(hDestFile);
- return false;
- }
- FSCTL_SET_INTEGRITY_INFORMATION_BUFFER setDestFileintegrity = { sourceFileIntegrity.ChecksumAlgorithm, sourceFileIntegrity.Reserved, sourceFileIntegrity.Flags };
- if (!DeviceIoControl(hDestFile, FSCTL_SET_INTEGRITY_INFORMATION, &setDestFileintegrity, sizeof(setDestFileintegrity), nullptr, 0, nullptr, nullptr))
- {
- ec = std::error_code(GetLastError(), std::system_category());
+ return false;
+ }
+ FSCTL_SET_INTEGRITY_INFORMATION_BUFFER setDestFileintegrity = { sourceFileIntegrity.ChecksumAlgorithm, sourceFileIntegrity.Reserved,
+ sourceFileIntegrity.Flags };
+ if (!DeviceIoControl(hDestFile, FSCTL_SET_INTEGRITY_INFORMATION, &setDestFileintegrity, sizeof(setDestFileintegrity), nullptr, 0,
+ nullptr, nullptr)) {
+ ec = std::error_code(GetLastError(), std::system_category());
qDebug() << "Failed to set dest file integrity info" << dst_path.c_str();
CloseHandle(hSourceFile);
CloseHandle(hDestFile);
- return false;
- }
- if (!SetFileInformationByHandle(hDestFile, FileEndOfFileInfo, &sourceFileLength, sizeof(sourceFileLength)))
- {
- ec = std::error_code(GetLastError(), std::system_category());
+ return false;
+ }
+ if (!SetFileInformationByHandle(hDestFile, FileEndOfFileInfo, &sourceFileLength, sizeof(sourceFileLength))) {
+ ec = std::error_code(GetLastError(), std::system_category());
qDebug() << "Failed to set dest file size" << dst_path.c_str();
CloseHandle(hSourceFile);
CloseHandle(hDestFile);
- return false;
- }
+ return false;
+ }
- const LONG64 splitThreshold = (1LL << 32) - sourceFileIntegrity.ClusterSizeInBytes;
+ const LONG64 splitThreshold = (1LL << 32) - sourceFileIntegrity.ClusterSizeInBytes;
- DUPLICATE_EXTENTS_DATA dupExtent;
- dupExtent.FileHandle = hSourceFile;
- for (LONG64 offset = 0, remain = RoundUpToPowerOf2(sourceFileLength.EndOfFile.QuadPart, sourceFileIntegrity.ClusterSizeInBytes); remain > 0; offset += splitThreshold, remain -= splitThreshold)
- {
- dupExtent.SourceFileOffset.QuadPart = dupExtent.TargetFileOffset.QuadPart = offset;
- dupExtent.ByteCount.QuadPart = std::min(splitThreshold, remain);
+ DUPLICATE_EXTENTS_DATA dupExtent;
+ dupExtent.FileHandle = hSourceFile;
+ for (LONG64 offset = 0, remain = RoundUpToPowerOf2(sourceFileLength.EndOfFile.QuadPart, sourceFileIntegrity.ClusterSizeInBytes);
+ remain > 0; offset += splitThreshold, remain -= splitThreshold) {
+ dupExtent.SourceFileOffset.QuadPart = dupExtent.TargetFileOffset.QuadPart = offset;
+ dupExtent.ByteCount.QuadPart = std::min(splitThreshold, remain);
- if (!DeviceIoControl(hDestFile, FSCTL_DUPLICATE_EXTENTS_TO_FILE, &dupExtent, sizeof(dupExtent), nullptr, 0, &junk, nullptr))
- {
+ if (!DeviceIoControl(hDestFile, FSCTL_DUPLICATE_EXTENTS_TO_FILE, &dupExtent, sizeof(dupExtent), nullptr, 0, &junk, nullptr)) {
DWORD err = GetLastError();
QString additionalMessage;
- if (err == ERROR_BLOCK_TOO_MANY_REFERENCES)
- {
+ if (err == ERROR_BLOCK_TOO_MANY_REFERENCES) {
static const int MaxClonesPerFile = 8175;
- additionalMessage = QString(
- " This is ERROR_BLOCK_TOO_MANY_REFERENCES and may mean you have surpassed the maximum "
- "allowed %1 references for a single file. "
- "See https://docs.microsoft.com/en-us/windows-server/storage/refs/block-cloning#functionality-restrictions-and-remarks"
- ).arg(MaxClonesPerFile);
-
+ additionalMessage =
+ QString(
+ " This is ERROR_BLOCK_TOO_MANY_REFERENCES and may mean you have surpassed the maximum "
+ "allowed %1 references for a single file. "
+ "See "
+ "https://docs.microsoft.com/en-us/windows-server/storage/refs/block-cloning#functionality-restrictions-and-remarks")
+ .arg(MaxClonesPerFile);
}
- ec = std::error_code(err, std::system_category());
- qDebug() << "Failed copy-on-write cloning of" << src_path.c_str() << "to" << dst_path.c_str() << "with error" << err << additionalMessage;
+ ec = std::error_code(err, std::system_category());
+ qDebug() << "Failed copy-on-write cloning of" << src_path.c_str() << "to" << dst_path.c_str() << "with error" << err
+ << additionalMessage;
CloseHandle(hSourceFile);
CloseHandle(hDestFile);
- return false;
- }
- }
+ return false;
+ }
+ }
- if (!(sourceFileBasicInfo.FileAttributes & FILE_ATTRIBUTE_SPARSE_FILE))
- {
- FILE_SET_SPARSE_BUFFER setDestSparse = { FALSE };
- if (!DeviceIoControl(hDestFile, FSCTL_SET_SPARSE, &setDestSparse, sizeof(setDestSparse), nullptr, 0, &junk, nullptr))
- {
+ if (!(sourceFileBasicInfo.FileAttributes & FILE_ATTRIBUTE_SPARSE_FILE)) {
+ FILE_SET_SPARSE_BUFFER setDestSparse = { FALSE };
+ if (!DeviceIoControl(hDestFile, FSCTL_SET_SPARSE, &setDestSparse, sizeof(setDestSparse), nullptr, 0, &junk, nullptr)) {
qDebug() << "Failed to set dest file sparseness" << dst_path.c_str();
CloseHandle(hSourceFile);
CloseHandle(hDestFile);
- return false;
- }
- }
+ return false;
+ }
+ }
- sourceFileBasicInfo.CreationTime.QuadPart = 0;
- if (!SetFileInformationByHandle(hDestFile, FileBasicInfo, &sourceFileBasicInfo, sizeof(sourceFileBasicInfo)))
- {
+ sourceFileBasicInfo.CreationTime.QuadPart = 0;
+ if (!SetFileInformationByHandle(hDestFile, FileBasicInfo, &sourceFileBasicInfo, sizeof(sourceFileBasicInfo))) {
qDebug() << "Failed to set dest file creation time" << dst_path.c_str();
CloseHandle(hSourceFile);
CloseHandle(hDestFile);
- return false;
- }
- if (!FlushFileBuffers(hDestFile))
- {
+ return false;
+ }
+ if (!FlushFileBuffers(hDestFile)) {
qDebug() << "Failed to flush dest file buffer" << dst_path.c_str();
CloseHandle(hSourceFile);
CloseHandle(hDestFile);
- return false;
- }
- destFileDispose = { FALSE };
- bool result = !!SetFileInformationByHandle(hDestFile, FileDispositionInfo, &destFileDispose, sizeof(destFileDispose));
+ return false;
+ }
+ destFileDispose = { FALSE };
+ bool result = !!SetFileInformationByHandle(hDestFile, FileDispositionInfo, &destFileDispose, sizeof(destFileDispose));
CloseHandle(hSourceFile);
CloseHandle(hDestFile);
@@ -1393,14 +1346,14 @@ bool linux_ficlone(const std::string& src_path, const std::string& dst_path, std
// https://man7.org/linux/man-pages/man2/ioctl_ficlone.2.html
int src_fd = open(src_path.c_str(), O_RDONLY);
- if(src_fd == -1) {
+ if (src_fd == -1) {
qDebug() << "Failed to open file:" << src_path.c_str();
qDebug() << "Error:" << strerror(errno);
ec = std::make_error_code(static_cast(errno));
return false;
}
int dst_fd = open(dst_path.c_str(), O_CREAT | O_WRONLY | O_TRUNC, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);
- if(dst_fd == -1) {
+ if (dst_fd == -1) {
qDebug() << "Failed to open file:" << dst_path.c_str();
qDebug() << "Error:" << strerror(errno);
ec = std::make_error_code(static_cast(errno));
@@ -1408,7 +1361,7 @@ bool linux_ficlone(const std::string& src_path, const std::string& dst_path, std
return false;
}
// attempt to clone
- if(ioctl(dst_fd, FICLONE, src_fd) == -1){
+ if (ioctl(dst_fd, FICLONE, src_fd) == -1) {
qDebug() << "Failed to clone file:" << src_path.c_str() << "to" << dst_path.c_str();
qDebug() << "Error:" << strerror(errno);
ec = std::make_error_code(static_cast(errno));
@@ -1416,11 +1369,11 @@ bool linux_ficlone(const std::string& src_path, const std::string& dst_path, std
close(dst_fd);
return false;
}
- if(close(src_fd)) {
+ if (close(src_fd)) {
qDebug() << "Failed to close file:" << src_path.c_str();
qDebug() << "Error:" << strerror(errno);
}
- if(close(dst_fd)) {
+ if (close(dst_fd)) {
qDebug() << "Failed to close file:" << dst_path.c_str();
qDebug() << "Error:" << strerror(errno);
}
@@ -1446,8 +1399,8 @@ bool macos_bsd_clonefile(const std::string& src_path, const std::string& dst_pat
#endif
/**
- * @brief if the Filesystem is symlink capable
- *
+ * @brief if the Filesystem is symlink capable
+ *
*/
bool canLinkOnFS(const QString& path)
{
@@ -1464,22 +1417,22 @@ bool canLinkOnFS(FilesystemType type)
}
/**
* @brief if the Filesystem is symlink capable on both ends
- *
+ *
*/
bool canLink(const QString& src, const QString& dst)
{
- return canLinkOnFS(src) && canLinkOnFS(dst);
+ return canLinkOnFS(src) && canLinkOnFS(dst);
}
uintmax_t hardLinkCount(const QString& path)
{
std::error_code err;
- int count = fs::hard_link_count(StringUtils::toStdString(path), err);
+ int count = fs::hard_link_count(StringUtils::toStdString(path), err);
if (err) {
- qWarning() << "Failed to count hard links for" << path << ":" << QString::fromStdString(err.message());
- count = 0;
+ qWarning() << "Failed to count hard links for" << path << ":" << QString::fromStdString(err.message());
+ count = 0;
}
return count;
}
-}
+} // namespace FS
From a28193430c3830af294cbba92d68e9bcccd7dfd2 Mon Sep 17 00:00:00 2001
From: Rachel Powers <508861+Ryex@users.noreply.github.com>
Date: Tue, 14 Mar 2023 16:17:43 -0700
Subject: [PATCH 037/104] fix: adjust geometry and add missing tooltip
Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com>
---
launcher/ui/dialogs/CopyInstanceDialog.ui | 20 ++++++++++++++++++--
1 file changed, 18 insertions(+), 2 deletions(-)
diff --git a/launcher/ui/dialogs/CopyInstanceDialog.ui b/launcher/ui/dialogs/CopyInstanceDialog.ui
index 3101acec..5060debc 100644
--- a/launcher/ui/dialogs/CopyInstanceDialog.ui
+++ b/launcher/ui/dialogs/CopyInstanceDialog.ui
@@ -9,8 +9,8 @@
0
0
- 531
- 653
+ 575
+ 695
@@ -334,11 +334,27 @@
false
+
+ Files cloned with reflinks take up no extra space until they are modified.
+
Clone instead of copying
+ -
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
-
From 0c986ba4d006740947603afb2e18dd9d2ffdfd2f Mon Sep 17 00:00:00 2001
From: Rachel Powers <508861+Ryex@users.noreply.github.com>
Date: Mon, 20 Mar 2023 16:38:40 -0700
Subject: [PATCH 038/104] spelling and formatting
Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com>
---
launcher/FileSystem.cpp | 14 +-
launcher/FileSystem.h | 185 ++++++++++-----------
launcher/InstanceCopyTask.cpp | 45 +++--
launcher/filelink/FileLink.cpp | 110 +++++-------
launcher/filelink/FileLink.h | 20 +--
launcher/filelink/main.cpp | 5 +-
launcher/ui/dialogs/CopyInstanceDialog.cpp | 40 ++---
launcher/ui/dialogs/CopyInstanceDialog.h | 21 +--
8 files changed, 192 insertions(+), 248 deletions(-)
diff --git a/launcher/FileSystem.cpp b/launcher/FileSystem.cpp
index c913b43e..640cf9be 100644
--- a/launcher/FileSystem.cpp
+++ b/launcher/FileSystem.cpp
@@ -328,7 +328,7 @@ bool create_link::operator()(const QString& offset, bool dryRun)
}
/**
- * @brief make a list off all the links ot make
+ * @brief make a list off all the links to
* @param offset subdirectory form src to link to dest
* @return if there was an error during the attempt to link
*/
@@ -363,7 +363,7 @@ void create_link::make_link_list(const QString& offset)
link_file(src, "");
} else {
if (m_debug)
- qDebug() << "linking recursivly:" << src << "to" << dst << "max_depth:" << m_max_depth;
+ qDebug() << "linking recursively:" << src << "to" << dst << "max_depth:" << m_max_depth;
QDir src_dir(src);
QDirIterator source_it(src, QDir::Filter::Files | QDir::Filter::Hidden, QDirIterator::Subdirectories);
@@ -414,7 +414,7 @@ bool create_link::make_links()
qWarning() << "Failed to link files:" << QString::fromStdString(m_os_err.message());
qDebug() << "Source file:" << src_path;
qDebug() << "Destination file:" << dst_path;
- qDebug() << "Error catagory:" << m_os_err.category().name();
+ qDebug() << "Error category:" << m_os_err.category().name();
qDebug() << "Error code:" << m_os_err.value();
emit linkFailed(src_path, dst_path, QString::fromStdString(m_os_err.message()), m_os_err.value());
} else {
@@ -469,7 +469,7 @@ void create_link::runPrivileged(const QString& offset)
in.setDevice(clientConnection);
in.setVersion(QDataStream::Qt_5_0);
qDebug() << "Reading path results from client";
- qDebug() << "bytes avalible" << clientConnection->bytesAvailable();
+ qDebug() << "bytes available" << clientConnection->bytesAvailable();
// Relies on the fact that QDataStream serializes a quint32 into
// sizeof(quint32) bytes
@@ -479,7 +479,7 @@ void create_link::runPrivileged(const QString& offset)
in >> blockSize;
qDebug() << "blocksize is" << blockSize;
- qDebug() << "bytes avalible" << clientConnection->bytesAvailable();
+ qDebug() << "bytes available" << clientConnection->bytesAvailable();
if (clientConnection->bytesAvailable() < blockSize || in.atEnd())
return;
@@ -506,7 +506,7 @@ void create_link::runPrivileged(const QString& offset)
m_path_results.append(result);
}
gotResults = true;
- qDebug() << "results recieved, closing connection";
+ qDebug() << "results received, closing connection";
clientConnection->close();
});
@@ -981,7 +981,7 @@ FilesystemType getFilesystemType(const QString& name)
}
/**
- * @brief path to the near ancestor that exsists
+ * @brief path to the near ancestor that exists
*
*/
QString NearestExistentAncestor(const QString& path)
diff --git a/launcher/FileSystem.h b/launcher/FileSystem.h
index 71175bb4..673c3563 100644
--- a/launcher/FileSystem.h
+++ b/launcher/FileSystem.h
@@ -44,9 +44,9 @@
#include
#include
+#include
#include
#include
-#include
namespace FS {
@@ -84,7 +84,7 @@ bool ensureFolderPathExists(QString filenamepath);
/**
* @brief Copies a directory and it's contents from src to dest
- */
+ */
class copy : public QObject {
Q_OBJECT
public:
@@ -167,17 +167,14 @@ class ExternalLinkFileProcess : public QThread {
/**
* @brief links (a file / a directory and it's contents) from src to dest
- */
+ */
class create_link : public QObject {
Q_OBJECT
public:
- create_link(const QList path_pairs, QObject* parent = nullptr) : QObject(parent)
- {
- m_path_pairs.append(path_pairs);
- }
+ create_link(const QList path_pairs, QObject* parent = nullptr) : QObject(parent) { m_path_pairs.append(path_pairs); }
create_link(const QString& src, const QString& dst, QObject* parent = nullptr) : QObject(parent)
{
- LinkPair pair = {src, dst};
+ LinkPair pair = { src, dst };
m_path_pairs.append(pair);
}
create_link& useHardLinks(const bool useHard)
@@ -211,28 +208,23 @@ class create_link : public QObject {
return *this;
}
- std::error_code getOSError() {
- return m_os_err;
- }
+ std::error_code getOSError() { return m_os_err; }
bool operator()(bool dryRun = false) { return operator()(QString(), dryRun); }
int totalLinked() { return m_linked; }
-
void runPrivileged() { runPrivileged(QString()); }
void runPrivileged(const QString& offset);
QList getResults() { return m_path_results; }
-
signals:
void fileLinked(const QString& srcName, const QString& dstName);
void linkFailed(const QString& srcName, const QString& dstName, const QString& err_msg, int err_value);
void finished();
void finishedPrivileged(bool gotResults);
-
private:
bool operator()(const QString& offset, bool dryRun = false);
void make_link_list(const QString& offset);
@@ -262,9 +254,9 @@ class create_link : public QObject {
* @brief moves a file by renaming it
* @param source source file path
* @param dest destination filepath
- *
+ *
*/
-bool move(const QString& source, const QString& dest);
+bool move(const QString& source, const QString& dest);
/**
* Delete a folder recursively
@@ -274,7 +266,7 @@ bool deletePath(QString path);
/**
* Trash a folder / file
*/
-bool trash(QString path, QString *pathInTrash = nullptr);
+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);
@@ -284,16 +276,15 @@ QString AbsolutePath(const QString& path);
/**
* @brief depth of path. "foo.txt" -> 0 , "bar/foo.txt" -> 1, /baz/bar/foo.txt -> 2, etc.
- *
+ *
* @param path path to measure
- * @return int number of componants before base path
+ * @return int number of components before base path
*/
int PathDepth(const QString& path);
-
/**
- * @brief cut off segments of path untill it is a max of length depth
- *
+ * @brief cut off segments of path until it is a max of length depth
+ *
* @param path path to truncate
* @param depth max depth of new path
* @return QString truncated path
@@ -363,124 +354,118 @@ enum class FilesystemType {
/**
* @brief Ordered Mapping of enum types to reported filesystem names
- * this maping is non exsaustive, it just attempts to capture the filesystems which could be reasonalbly be in use .
+ * this mapping is non exsaustive, it just attempts to capture the filesystems which could be reasonalbly be in use .
* all string values are in uppercase, use `QString.toUpper()` or equivalent during lookup.
- *
+ *
* QMap is ordered
- *
+ *
*/
-static const QMap s_filesystem_type_names = {
- {FilesystemType::FAT, QString("FAT")},
- {FilesystemType::NTFS, QString("NTFS")},
- {FilesystemType::REFS, QString("REFS")},
- {FilesystemType::EXT, QString("EXT")},
- {FilesystemType::EXT_2_OLD, QString("EXT_2_OLD")},
- {FilesystemType::EXT_2_3_4, QString("EXT2/3/4")},
- {FilesystemType::XFS, QString("XFS")},
- {FilesystemType::BTRFS, QString("BTRFS")},
- {FilesystemType::NFS, QString("NFS")},
- {FilesystemType::ZFS, QString("ZFS")},
- {FilesystemType::APFS, QString("APFS")},
- {FilesystemType::HFS, QString("HFS")},
- {FilesystemType::HFSPLUS, QString("HFSPLUS")},
- {FilesystemType::HFSX, QString("HFSX")},
- {FilesystemType::FUSEBLK, QString("FUSEBLK")},
- {FilesystemType::F2FS, QString("F2FS")},
- {FilesystemType::UNKNOWN, QString("UNKNOWN")}
-};
-
+static const QMap s_filesystem_type_names = { { FilesystemType::FAT, QString("FAT") },
+ { FilesystemType::NTFS, QString("NTFS") },
+ { FilesystemType::REFS, QString("REFS") },
+ { FilesystemType::EXT, QString("EXT") },
+ { FilesystemType::EXT_2_OLD, QString("EXT_2_OLD") },
+ { FilesystemType::EXT_2_3_4, QString("EXT2/3/4") },
+ { FilesystemType::XFS, QString("XFS") },
+ { FilesystemType::BTRFS, QString("BTRFS") },
+ { FilesystemType::NFS, QString("NFS") },
+ { FilesystemType::ZFS, QString("ZFS") },
+ { FilesystemType::APFS, QString("APFS") },
+ { FilesystemType::HFS, QString("HFS") },
+ { FilesystemType::HFSPLUS, QString("HFSPLUS") },
+ { FilesystemType::HFSX, QString("HFSX") },
+ { FilesystemType::FUSEBLK, QString("FUSEBLK") },
+ { FilesystemType::F2FS, QString("F2FS") },
+ { FilesystemType::UNKNOWN, QString("UNKNOWN") } };
/**
* @brief Ordered Mapping of reported filesystem names to enum types
- * this maping is non exsaustive, it just attempts to capture the many way these filesystems could be reported.
+ * this mapping is non exsaustive, it just attempts to capture the many way these filesystems could be reported.
* all keys are in uppercase, use `QString.toUpper()` or equivalent during lookup.
- *
+ *
* QMap is ordered
- *
+ *
*/
static const QMap s_filesystem_type_names_inverse = {
- {QString("FAT"), FilesystemType::FAT},
- {QString("NTFS"), FilesystemType::NTFS},
- {QString("REFS"), FilesystemType::REFS},
- {QString("EXT2_OLD"), FilesystemType::EXT_2_OLD},
- {QString("EXT_2_OLD"), FilesystemType::EXT_2_OLD},
- {QString("EXT2"), FilesystemType::EXT_2_3_4},
- {QString("EXT3"), FilesystemType::EXT_2_3_4},
- {QString("EXT4"), FilesystemType::EXT_2_3_4},
- {QString("EXT2/3/4"), FilesystemType::EXT_2_3_4},
- {QString("EXT_2_3_4"), FilesystemType::EXT_2_3_4},
- {QString("EXT"), FilesystemType::EXT}, // must come after all other EXT variants to prevent greedy detection
- {QString("XFS"), FilesystemType::XFS},
- {QString("BTRFS"), FilesystemType::BTRFS},
- {QString("NFS"), FilesystemType::NFS},
- {QString("ZFS"), FilesystemType::ZFS},
- {QString("APFS"), FilesystemType::APFS},
- {QString("HFSPLUS"), FilesystemType::HFSPLUS},
- {QString("HFSX"), FilesystemType::HFSX},
- {QString("HFS"), FilesystemType::HFS},
- {QString("FUSEBLK"), FilesystemType::FUSEBLK},
- {QString("F2FS"), FilesystemType::F2FS},
- {QString("UNKNOWN"), FilesystemType::UNKNOWN}
+ { QString("FAT"), FilesystemType::FAT },
+ { QString("NTFS"), FilesystemType::NTFS },
+ { QString("REFS"), FilesystemType::REFS },
+ { QString("EXT2_OLD"), FilesystemType::EXT_2_OLD },
+ { QString("EXT_2_OLD"), FilesystemType::EXT_2_OLD },
+ { QString("EXT2"), FilesystemType::EXT_2_3_4 },
+ { QString("EXT3"), FilesystemType::EXT_2_3_4 },
+ { QString("EXT4"), FilesystemType::EXT_2_3_4 },
+ { QString("EXT2/3/4"), FilesystemType::EXT_2_3_4 },
+ { QString("EXT_2_3_4"), FilesystemType::EXT_2_3_4 },
+ { QString("EXT"), FilesystemType::EXT }, // must come after all other EXT variants to prevent greedy detection
+ { QString("XFS"), FilesystemType::XFS },
+ { QString("BTRFS"), FilesystemType::BTRFS },
+ { QString("NFS"), FilesystemType::NFS },
+ { QString("ZFS"), FilesystemType::ZFS },
+ { QString("APFS"), FilesystemType::APFS },
+ { QString("HFSPLUS"), FilesystemType::HFSPLUS },
+ { QString("HFSX"), FilesystemType::HFSX },
+ { QString("HFS"), FilesystemType::HFS },
+ { QString("FUSEBLK"), FilesystemType::FUSEBLK },
+ { QString("F2FS"), FilesystemType::F2FS },
+ { QString("UNKNOWN"), FilesystemType::UNKNOWN }
};
/**
* @brief Get the string name of Filesystem enum object
- *
- * @param type
- * @return QString
+ *
+ * @param type
+ * @return QString
*/
QString getFilesystemTypeName(FilesystemType type);
/**
* @brief Get the Filesystem enum object from a name
- * Does a lookup of the type name and returns an exact match
+ * Does a lookup of the type name and returns an exact match
*
- * @param name
- * @return FilesystemType
+ * @param name
+ * @return FilesystemType
*/
FilesystemType getFilesystemType(const QString& name);
/**
* @brief Get the Filesystem enum object from a name
* Does a fuzzy lookup of the type name and returns an apropreate match
- *
- * @param name
- * @return FilesystemType
+ *
+ * @param name
+ * @return FilesystemType
*/
FilesystemType getFilesystemTypeFuzzy(const QString& name);
-
struct FilesystemInfo {
FilesystemType fsType = FilesystemType::UNKNOWN;
QString fsTypeName;
- int blockSize;
- qint64 bytesAvailable;
- qint64 bytesFree;
- qint64 bytesTotal;
+ int blockSize;
+ qint64 bytesAvailable;
+ qint64 bytesFree;
+ qint64 bytesTotal;
QString name;
QString rootPath;
};
/**
- * @brief path to the near ancestor that exsists
- *
+ * @brief path to the near ancestor that exists
+ *
*/
QString NearestExistentAncestor(const QString& path);
/**
* @brief colect information about the filesystem under a file
- *
+ *
*/
FilesystemInfo statFS(const QString& path);
-static const QList s_clone_filesystems = {
- FilesystemType::BTRFS, FilesystemType::APFS, FilesystemType::ZFS,
- FilesystemType::XFS, FilesystemType::REFS
-};
+static const QList s_clone_filesystems = { FilesystemType::BTRFS, FilesystemType::APFS, FilesystemType::ZFS,
+ FilesystemType::XFS, FilesystemType::REFS };
/**
- * @brief if the Filesystem is reflink/clone capable
- *
+ * @brief if the Filesystem is reflink/clone capable
+ *
*/
bool canCloneOnFS(const QString& path);
bool canCloneOnFS(const FilesystemInfo& info);
@@ -488,13 +473,13 @@ bool canCloneOnFS(FilesystemType type);
/**
* @brief if the Filesystems are reflink/clone capable and both are on the same device
- *
+ *
*/
bool canClone(const QString& src, const QString& dst);
/**
* @brief Copies a directory and it's contents from src to dest
- */
+ */
class clone : public QObject {
Q_OBJECT
public:
@@ -535,7 +520,7 @@ class clone : public QObject {
/**
* @brief clone/reflink file from src to dst
- *
+ *
*/
bool clone_file(const QString& src, const QString& dst, std::error_code& ec);
@@ -552,8 +537,8 @@ static const QList s_non_link_filesystems = {
};
/**
- * @brief if the Filesystem is symlink capable
- *
+ * @brief if the Filesystem is symlink capable
+ *
*/
bool canLinkOnFS(const QString& path);
bool canLinkOnFS(const FilesystemInfo& info);
@@ -561,10 +546,10 @@ bool canLinkOnFS(FilesystemType type);
/**
* @brief if the Filesystem is symlink capable on both ends
- *
+ *
*/
bool canLink(const QString& src, const QString& dst);
uintmax_t hardLinkCount(const QString& path);
-}
+} // namespace FS
diff --git a/launcher/InstanceCopyTask.cpp b/launcher/InstanceCopyTask.cpp
index 6bd56de3..4ac3b51a 100644
--- a/launcher/InstanceCopyTask.cpp
+++ b/launcher/InstanceCopyTask.cpp
@@ -1,10 +1,10 @@
#include "InstanceCopyTask.h"
-#include "settings/INISettingsObject.h"
+#include
+#include
#include "FileSystem.h"
#include "NullInstance.h"
#include "pathmatcher/RegexpMatcher.h"
-#include
-#include
+#include "settings/INISettingsObject.h"
InstanceCopyTask::InstanceCopyTask(InstancePtr origInstance, const InstanceCopyPrefs& prefs)
{
@@ -15,17 +15,17 @@ InstanceCopyTask::InstanceCopyTask(InstancePtr origInstance, const InstanceCopyP
m_useHardLinks = prefs.isLinkRecursivelyEnabled() && prefs.isUseHardLinksEnabled();
m_copySaves = prefs.isLinkRecursivelyEnabled() && prefs.isDontLinkSavesEnabled() && prefs.isCopySavesEnabled();
m_useClone = prefs.isUseCloneEnabled();
-
+
QString filters = prefs.getSelectedFiltersAsRegex();
if (m_useLinks || m_useHardLinks) {
- if (!filters.isEmpty()) filters += "|";
+ if (!filters.isEmpty())
+ filters += "|";
filters += "instance.cfg";
- }
+ }
qDebug() << "CopyFilters:" << filters;
- if (!filters.isEmpty())
- {
+ if (!filters.isEmpty()) {
// Set regex filter:
// FIXME: get this from the original instance type...
auto matcherReal = new RegexpMatcher(filters);
@@ -38,14 +38,14 @@ 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"));
+ auto copySaves = [&]() {
+ FS::copy savesCopy(FS::PathCombine(m_origInstance->instanceRoot(), "saves"), FS::PathCombine(m_stagingPath, "saves"));
savesCopy.followSymlinks(true);
return savesCopy();
};
- m_copyFuture = QtConcurrent::run(QThreadPool::globalInstance(), [this, copySaves]{
+ m_copyFuture = QtConcurrent::run(QThreadPool::globalInstance(), [this, copySaves] {
if (m_useClone) {
FS::clone folderClone(m_origInstance->instanceRoot(), m_stagingPath);
folderClone.matcher(m_matcher.get());
@@ -53,22 +53,22 @@ void InstanceCopyTask::executeTask()
return folderClone();
} else if (m_useLinks || m_useHardLinks) {
FS::create_link folderLink(m_origInstance->instanceRoot(), m_stagingPath);
- int depth = m_linkRecursively ? -1 : 0; // we need to at least link the top level instead of the instance folder
+ int depth = m_linkRecursively ? -1 : 0; // we need to at least link the top level instead of the instance folder
folderLink.linkRecursively(true).setMaxDepth(depth).useHardLinks(m_useHardLinks).matcher(m_matcher.get());
bool there_were_errors = false;
- if(!folderLink()){
+ if (!folderLink()) {
#if defined Q_OS_WIN32
if (!m_useHardLinks) {
qDebug() << "EXPECTED: Link failure, Windows requires permissions for symlinks";
- qDebug() << "atempting to run with privelage";
+ qDebug() << "attempting to run with privelage";
QEventLoop loop;
bool got_priv_results = false;
- connect(&folderLink, &FS::create_link::finishedPrivileged, this, [&](bool gotResults){
+ connect(&folderLink, &FS::create_link::finishedPrivileged, this, [&](bool gotResults) {
if (!gotResults) {
qDebug() << "Privileged run exited without results!";
}
@@ -77,7 +77,7 @@ void InstanceCopyTask::executeTask()
});
folderLink.runPrivileged();
- loop.exec(); // wait for the finished signal
+ loop.exec(); // wait for the finished signal
for (auto result : folderLink.getResults()) {
if (result.err_value != 0) {
@@ -88,17 +88,17 @@ void InstanceCopyTask::executeTask()
if (m_copySaves) {
there_were_errors |= !copySaves();
}
-
+
return got_priv_results && !there_were_errors;
} else {
qDebug() << "Link Failed!" << folderLink.getOSError().value() << folderLink.getOSError().message().c_str();
- }
+ }
#else
qDebug() << "Link Failed!" << folderLink.getOSError().value() << folderLink.getOSError().message().c_str();
-#endif
+#endif
return false;
}
-
+
if (m_copySaves) {
there_were_errors |= !copySaves();
}
@@ -119,8 +119,7 @@ void InstanceCopyTask::executeTask()
void InstanceCopyTask::copyFinished()
{
auto successful = m_copyFuture.result();
- if(!successful)
- {
+ if (!successful) {
emitFailed(tr("Instance folder copy failed."));
return;
}
@@ -130,7 +129,7 @@ void InstanceCopyTask::copyFinished()
InstancePtr inst(new NullInstance(m_globalSettings, instanceSettings, m_stagingPath));
inst->setName(name());
inst->setIconKey(m_instIcon);
- if(!m_keepPlaytime) {
+ if (!m_keepPlaytime) {
inst->resetTimePlayed();
}
if (m_useLinks)
diff --git a/launcher/filelink/FileLink.cpp b/launcher/filelink/FileLink.cpp
index a731ecdb..ded46061 100644
--- a/launcher/filelink/FileLink.cpp
+++ b/launcher/filelink/FileLink.cpp
@@ -25,7 +25,6 @@
#include "StringUtils.h"
-
#include
#include
@@ -41,53 +40,47 @@
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
-#include
#include
+#include
#endif
// Snippet from https://github.com/gulrak/filesystem#using-it-as-single-file-header
#ifdef __APPLE__
-#include // for deployment target to support pre-catalina targets without std::fs
-#endif // __APPLE__
+#include // for deployment target to support pre-catalina targets without std::fs
+#endif // __APPLE__
#if ((defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) || (defined(__cplusplus) && __cplusplus >= 201703L)) && defined(__has_include)
#if __has_include() && (!defined(__MAC_OS_X_VERSION_MIN_REQUIRED) || __MAC_OS_X_VERSION_MIN_REQUIRED >= 101500)
#define GHC_USE_STD_FS
#include
namespace fs = std::filesystem;
-#endif // MacOS min version check
-#endif // Other OSes version check
+#endif // MacOS min version check
+#endif // Other OSes version check
#ifndef GHC_USE_STD_FS
#include
namespace fs = ghc::filesystem;
#endif
-
-
-FileLinkApp::FileLinkApp(int &argc, char **argv) : QCoreApplication(argc, argv), socket(new QLocalSocket(this))
+FileLinkApp::FileLinkApp(int& argc, char** argv) : QCoreApplication(argc, argv), socket(new QLocalSocket(this))
{
#if defined Q_OS_WIN32
// attach the parent console
- if(AttachConsole(ATTACH_PARENT_PROCESS))
- {
+ if (AttachConsole(ATTACH_PARENT_PROCESS)) {
// if attach succeeds, reopen and sync all the i/o
- if(freopen("CON", "w", stdout))
- {
+ if (freopen("CON", "w", stdout)) {
std::cout.sync_with_stdio();
}
- if(freopen("CON", "w", stderr))
- {
+ if (freopen("CON", "w", stderr)) {
std::cerr.sync_with_stdio();
}
- if(freopen("CON", "r", stdin))
- {
+ if (freopen("CON", "r", stdin)) {
std::cin.sync_with_stdio();
}
- auto out = GetStdHandle (STD_OUTPUT_HANDLE);
+ auto out = GetStdHandle(STD_OUTPUT_HANDLE);
DWORD written;
- const char * endline = "\n";
+ const char* endline = "\n";
WriteConsole(out, endline, strlen(endline), &written, NULL);
consoleAttached = true;
}
@@ -101,10 +94,8 @@ FileLinkApp::FileLinkApp(int &argc, char **argv) : QCoreApplication(argc, argv),
QCommandLineParser parser;
parser.setApplicationDescription(QObject::tr("a batch MKLINK program for windows to be used with prismlauncher"));
- parser.addOptions({
- {{"s", "server"}, "Join the specified server on launch", "pipe name"},
- {{"H", "hard"}, "use hard links insted of symbolic", "true/false"}
- });
+ parser.addOptions({ { { "s", "server" }, "Join the specified server on launch", "pipe name" },
+ { { "H", "hard" }, "use hard links instead of symbolic", "true/false" } });
parser.addHelpOption();
parser.addVersionOption();
@@ -122,63 +113,57 @@ FileLinkApp::FileLinkApp(int &argc, char **argv) : QCoreApplication(argc, argv),
qDebug() << "no server to join";
exit();
}
-
}
void FileLinkApp::joinServer(QString server)
{
-
- blockSize = 0;
+ blockSize = 0;
in.setDevice(&socket);
in.setVersion(QDataStream::Qt_5_0);
- connect(&socket, &QLocalSocket::connected, this, [&](){
- qDebug() << "connected to server";
- });
+ connect(&socket, &QLocalSocket::connected, this, [&]() { qDebug() << "connected to server"; });
connect(&socket, &QLocalSocket::readyRead, this, &FileLinkApp::readPathPairs);
- connect(&socket, &QLocalSocket::errorOccurred, this, [&](QLocalSocket::LocalSocketError socketError){
+ connect(&socket, &QLocalSocket::errorOccurred, this, [&](QLocalSocket::LocalSocketError socketError) {
switch (socketError) {
- case QLocalSocket::ServerNotFoundError:
- qDebug() << ("The host was not found. Please make sure "
+ case QLocalSocket::ServerNotFoundError:
+ qDebug()
+ << ("The host was not found. Please make sure "
"that the server is running and that the "
"server name is correct.");
- break;
- case QLocalSocket::ConnectionRefusedError:
- qDebug() << ("The connection was refused by the peer. "
+ break;
+ case QLocalSocket::ConnectionRefusedError:
+ qDebug()
+ << ("The connection was refused by the peer. "
"Make sure the server is running, "
"and check that the server name "
"is correct.");
- break;
- case QLocalSocket::PeerClosedError:
- qDebug() << ("The connection was closed by the peer. ");
- break;
- default:
- qDebug() << "The following error occurred: " << socket.errorString();
+ break;
+ case QLocalSocket::PeerClosedError:
+ qDebug() << ("The connection was closed by the peer. ");
+ break;
+ default:
+ qDebug() << "The following error occurred: " << socket.errorString();
}
});
- connect(&socket, &QLocalSocket::disconnected, this, [&](){
- qDebug() << "dissconnected from server, should exit";
+ connect(&socket, &QLocalSocket::disconnected, this, [&]() {
+ qDebug() << "disconnected from server, should exit";
exit();
});
socket.connectToServer(server);
-
-
}
void FileLinkApp::runLink()
-{
-
+{
std::error_code os_err;
qDebug() << "creating links";
for (auto link : m_links_to_make) {
-
QString src_path = link.src;
QString dst_path = link.dst;
@@ -192,26 +177,25 @@ void FileLinkApp::runLink()
} else {
qDebug() << "making symlink:" << src_path << "to" << dst_path;
fs::create_symlink(StringUtils::toStdString(src_path), StringUtils::toStdString(dst_path), os_err);
- }
+ }
if (os_err) {
qWarning() << "Failed to link files:" << QString::fromStdString(os_err.message());
qDebug() << "Source file:" << src_path;
qDebug() << "Destination file:" << dst_path;
- qDebug() << "Error catagory:" << os_err.category().name();
+ qDebug() << "Error category:" << os_err.category().name();
qDebug() << "Error code:" << os_err.value();
- FS::LinkResult result = {src_path, dst_path, QString::fromStdString(os_err.message()), os_err.value()};
+ FS::LinkResult result = { src_path, dst_path, QString::fromStdString(os_err.message()), os_err.value() };
m_path_results.append(result);
} else {
- FS::LinkResult result = {src_path, dst_path};
+ FS::LinkResult result = { src_path, dst_path };
m_path_results.append(result);
}
}
sendResults();
qDebug() << "done, should exit soon";
-
}
void FileLinkApp::sendResults()
@@ -245,10 +229,10 @@ void FileLinkApp::sendResults()
}
void FileLinkApp::readPathPairs()
-{
+{
m_links_to_make.clear();
qDebug() << "Reading path pairs from server";
- qDebug() << "bytes avalible" << socket.bytesAvailable();
+ qDebug() << "bytes available" << socket.bytesAvailable();
if (blockSize == 0) {
// Relies on the fact that QDataStream serializes a quint32 into
// sizeof(quint32) bytes
@@ -258,15 +242,15 @@ void FileLinkApp::readPathPairs()
in >> blockSize;
}
qDebug() << "blocksize is" << blockSize;
- qDebug() << "bytes avalible" << socket.bytesAvailable();
+ qDebug() << "bytes available" << socket.bytesAvailable();
if (socket.bytesAvailable() < blockSize || in.atEnd())
return;
-
+
quint32 numLinks;
in >> numLinks;
qDebug() << "numLinks" << numLinks;
- for(int i = 0; i < numLinks; i++) {
+ for (int i = 0; i < numLinks; i++) {
FS::LinkPair pair;
in >> pair.src;
in >> pair.dst;
@@ -277,17 +261,15 @@ void FileLinkApp::readPathPairs()
runLink();
}
-
FileLinkApp::~FileLinkApp()
-{
+{
qDebug() << "link program shutting down";
// Shut down logger by setting the logger function to nothing
qInstallMessageHandler(nullptr);
#if defined Q_OS_WIN32
// Detach from Windows console
- if(consoleAttached)
- {
+ if (consoleAttached) {
fclose(stdout);
fclose(stdin);
fclose(stderr);
@@ -295,7 +277,3 @@ FileLinkApp::~FileLinkApp()
}
#endif
}
-
-
-
-
diff --git a/launcher/filelink/FileLink.h b/launcher/filelink/FileLink.h
index d146b8d9..4c47d9bb 100644
--- a/launcher/filelink/FileLink.h
+++ b/launcher/filelink/FileLink.h
@@ -25,30 +25,26 @@
#include
#include
-#include
+#include
+#include
#include
#include
#include
-#include
-#include
-#include
-#include
#include
+#include
+#include
#define PRISM_EXTERNAL_EXE
#include "FileSystem.h"
-class FileLinkApp : public QCoreApplication
-{
+class FileLinkApp : public QCoreApplication {
// friends for the purpose of limiting access to deprecated stuff
Q_OBJECT
-public:
-
- FileLinkApp(int &argc, char **argv);
+ public:
+ FileLinkApp(int& argc, char** argv);
virtual ~FileLinkApp();
-private:
-
+ private:
void joinServer(QString server);
void readPathPairs();
void runLink();
diff --git a/launcher/filelink/main.cpp b/launcher/filelink/main.cpp
index 4a22ff18..83566a3c 100644
--- a/launcher/filelink/main.cpp
+++ b/launcher/filelink/main.cpp
@@ -22,10 +22,9 @@
#include "FileLink.h"
-int main(int argc, char *argv[])
+int main(int argc, char* argv[])
{
-
FileLinkApp ldh(argc, argv);
-
+
return ldh.exec();
}
diff --git a/launcher/ui/dialogs/CopyInstanceDialog.cpp b/launcher/ui/dialogs/CopyInstanceDialog.cpp
index ced57ae0..347bd39f 100644
--- a/launcher/ui/dialogs/CopyInstanceDialog.cpp
+++ b/launcher/ui/dialogs/CopyInstanceDialog.cpp
@@ -43,15 +43,15 @@
#include "ui/dialogs/IconPickerDialog.h"
-#include "BaseVersion.h"
-#include "icons/IconList.h"
#include "BaseInstance.h"
-#include "InstanceList.h"
-#include "FileSystem.h"
+#include "BaseVersion.h"
#include "DesktopServices.h"
+#include "FileSystem.h"
+#include "InstanceList.h"
+#include "icons/IconList.h"
-CopyInstanceDialog::CopyInstanceDialog(InstancePtr original, QWidget *parent)
- :QDialog(parent), ui(new Ui::CopyInstanceDialog), m_original(original)
+CopyInstanceDialog::CopyInstanceDialog(InstancePtr original, QWidget* parent)
+ : QDialog(parent), ui(new Ui::CopyInstanceDialog), m_original(original)
{
ui->setupUi(this);
resize(minimumSizeHint());
@@ -74,8 +74,7 @@ CopyInstanceDialog::CopyInstanceDialog(InstancePtr original, QWidget *parent)
groupList.push_front("");
ui->groupBox->addItems(groupList);
int index = groupList.indexOf(APPLICATION->instances()->getInstanceGroup(m_original->id()));
- if(index == -1)
- {
+ if (index == -1) {
index = 0;
}
ui->groupBox->setCurrentIndex(index);
@@ -108,10 +107,8 @@ CopyInstanceDialog::CopyInstanceDialog(InstancePtr original, QWidget *parent)
#if defined(Q_OS_WIN)
ui->symbolicLinksCheckbox->setIcon(style()->standardIcon(QStyle::SP_VistaShield));
- ui->symbolicLinksCheckbox->setToolTip(
- tr("Use symbolic links instead of copying files.") +
- tr("\nOn windows symbolic links may require admin permision to create.")
- );
+ ui->symbolicLinksCheckbox->setToolTip(tr("Use symbolic links instead of copying files.") +
+ tr("\nOn windows symbolic links may require admin permission to create."));
#endif
updateLinkOptions();
@@ -119,7 +116,6 @@ CopyInstanceDialog::CopyInstanceDialog(InstancePtr original, QWidget *parent)
auto HelpButton = ui->buttonBox->button(QDialogButtonBox::Help);
connect(HelpButton, &QPushButton::clicked, this, &CopyInstanceDialog::help);
-
}
CopyInstanceDialog::~CopyInstanceDialog()
@@ -131,8 +127,7 @@ void CopyInstanceDialog::updateDialogState()
{
auto allowOK = !instName().isEmpty();
auto OkButton = ui->buttonBox->button(QDialogButtonBox::Ok);
- if(OkButton->isEnabled() != allowOK)
- {
+ if (OkButton->isEnabled() != allowOK) {
OkButton->setEnabled(allowOK);
}
}
@@ -140,8 +135,7 @@ void CopyInstanceDialog::updateDialogState()
QString CopyInstanceDialog::instName() const
{
auto result = ui->instNameTextBox->text().trimmed();
- if(result.size())
- {
+ if (result.size()) {
return result;
}
return QString();
@@ -162,7 +156,6 @@ const InstanceCopyPrefs& CopyInstanceDialog::getChosenOptions() const
return m_selectedOptions;
}
-
void CopyInstanceDialog::help()
{
DesktopServices::openUrl(QUrl(BuildConfig.HELP_URL.arg("instance-copy")));
@@ -200,7 +193,8 @@ void CopyInstanceDialog::updateLinkOptions()
ui->symbolicLinksCheckbox->setEnabled(m_linkSupported && !ui->hardLinksCheckbox->isChecked() && !ui->useCloneCheckbox->isChecked());
ui->hardLinksCheckbox->setEnabled(m_linkSupported && !ui->symbolicLinksCheckbox->isChecked() && !ui->useCloneCheckbox->isChecked());
- ui->symbolicLinksCheckbox->setChecked(m_linkSupported && m_selectedOptions.isUseSymLinksEnabled() && !ui->useCloneCheckbox->isChecked());
+ ui->symbolicLinksCheckbox->setChecked(m_linkSupported && m_selectedOptions.isUseSymLinksEnabled() &&
+ !ui->useCloneCheckbox->isChecked());
ui->hardLinksCheckbox->setChecked(m_linkSupported && m_selectedOptions.isUseHardLinksEnabled() && !ui->useCloneCheckbox->isChecked());
bool linksInUse = (ui->symbolicLinksCheckbox->isChecked() || ui->hardLinksCheckbox->isChecked());
@@ -220,15 +214,13 @@ void CopyInstanceDialog::on_iconButton_clicked()
IconPickerDialog dlg(this);
dlg.execWithSelection(InstIconKey);
- if (dlg.result() == QDialog::Accepted)
- {
+ if (dlg.result() == QDialog::Accepted) {
InstIconKey = dlg.selectedIconKey;
ui->iconButton->setIcon(APPLICATION->icons()->getIcon(InstIconKey));
}
}
-
-void CopyInstanceDialog::on_instNameTextBox_textChanged(const QString &arg1)
+void CopyInstanceDialog::on_instNameTextBox_textChanged(const QString& arg1)
{
updateDialogState();
}
@@ -247,7 +239,6 @@ void CopyInstanceDialog::on_copySavesCheckbox_stateChanged(int state)
updateSelectAllCheckbox();
}
-
void CopyInstanceDialog::on_keepPlaytimeCheckbox_stateChanged(int state)
{
m_selectedOptions.enableKeepPlaytime(state == Qt::Checked);
@@ -311,7 +302,6 @@ void CopyInstanceDialog::on_recursiveLinkCheckbox_stateChanged(int state)
{
m_selectedOptions.enableLinkRecursively(state == Qt::Checked);
updateLinkOptions();
-
}
void CopyInstanceDialog::on_dontLinkSavesCheckbox_stateChanged(int state)
diff --git a/launcher/ui/dialogs/CopyInstanceDialog.h b/launcher/ui/dialogs/CopyInstanceDialog.h
index c447bee9..698c6e93 100644
--- a/launcher/ui/dialogs/CopyInstanceDialog.h
+++ b/launcher/ui/dialogs/CopyInstanceDialog.h
@@ -22,17 +22,15 @@
class BaseInstance;
-namespace Ui
-{
+namespace Ui {
class CopyInstanceDialog;
}
-class CopyInstanceDialog : public QDialog
-{
+class CopyInstanceDialog : public QDialog {
Q_OBJECT
-public:
- explicit CopyInstanceDialog(InstancePtr original, QWidget *parent = 0);
+ public:
+ explicit CopyInstanceDialog(InstancePtr original, QWidget* parent = 0);
~CopyInstanceDialog();
void updateDialogState();
@@ -42,13 +40,12 @@ public:
QString iconKey() const;
const InstanceCopyPrefs& getChosenOptions() const;
-public slots:
+ public slots:
void help();
-private
-slots:
+ private slots:
void on_iconButton_clicked();
- void on_instNameTextBox_textChanged(const QString &arg1);
+ void on_instNameTextBox_textChanged(const QString& arg1);
// Checkboxes
void on_selectAllCheckbox_stateChanged(int state);
void on_copySavesCheckbox_stateChanged(int state);
@@ -65,14 +62,14 @@ slots:
void on_dontLinkSavesCheckbox_stateChanged(int state);
void on_useCloneCheckbox_stateChanged(int state);
-private:
+ private:
void checkAllCheckboxes(const bool& b);
void updateSelectAllCheckbox();
void updateUseCloneCheckbox();
void updateLinkOptions();
/* data */
- Ui::CopyInstanceDialog *ui;
+ Ui::CopyInstanceDialog* ui;
QString InstIconKey;
InstancePtr m_original;
InstanceCopyPrefs m_selectedOptions;
From 44bf32e729294d8ede069f9fd952b9104b34b695 Mon Sep 17 00:00:00 2001
From: Sefa Eyeoglu
Date: Tue, 21 Mar 2023 18:28:32 +0100
Subject: [PATCH 039/104] fix: handle partial lines in LoggedProcess
Fixes PrismLauncher/PrismLauncher#930
Co-authored-by: flow
Signed-off-by: Sefa Eyeoglu
---
launcher/LoggedProcess.cpp | 17 +++++++++++++++--
1 file changed, 15 insertions(+), 2 deletions(-)
diff --git a/launcher/LoggedProcess.cpp b/launcher/LoggedProcess.cpp
index 6447f5c6..43a9c4f6 100644
--- a/launcher/LoggedProcess.cpp
+++ b/launcher/LoggedProcess.cpp
@@ -1,7 +1,8 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
- * PolyMC - Minecraft Launcher
- * Copyright (C) 2022 Sefa Eyeoglu
+ * Prism Launcher - Minecraft Launcher
+ * Copyright (C) 2022,2023 Sefa Eyeoglu
+ * Copyright (c) 2023 flowln
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -60,14 +61,26 @@ LoggedProcess::~LoggedProcess()
}
}
+static QString m_leftover_line;
+
QStringList reprocess(const QByteArray& data, QTextDecoder& decoder)
{
auto str = decoder.toUnicode(data);
+
+ // FIXME: Flush this out when process exits
+ if (!m_leftover_line.isEmpty()) {
+ str.prepend(m_leftover_line);
+ m_leftover_line = "";
+ }
+
#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0)
auto lines = str.remove(QChar::CarriageReturn).split(QChar::LineFeed, QString::SkipEmptyParts);
#else
auto lines = str.remove(QChar::CarriageReturn).split(QChar::LineFeed, Qt::SkipEmptyParts);
#endif
+
+ if (!str.endsWith(QChar::LineFeed))
+ m_leftover_line = lines.takeLast();
return lines;
}
From 77932061bc244b301a9697281880ed2a8bae9e31 Mon Sep 17 00:00:00 2001
From: Sefa Eyeoglu
Date: Tue, 21 Mar 2023 18:33:41 +0100
Subject: [PATCH 040/104] chore: update ignores for Nix
Signed-off-by: Sefa Eyeoglu
---
.gitignore | 8 +++-----
1 file changed, 3 insertions(+), 5 deletions(-)
diff --git a/.gitignore b/.gitignore
index 3340670b..5c589a5f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -24,10 +24,6 @@ Debug
build
/build-*
-# direnv / Nix
-.direnv/
-.pre-commit-config.yaml
-
# Install dirs
install
/install-*
@@ -48,7 +44,9 @@ run/
.cache/
# Nix/NixOS
-result/
+.direnv/
+.pre-commit-config.yaml
+result
# Flatpak
.flatpak-builder
From 02bf086c09a26fb7dfd9a95d2a6f81a7a7d4c161 Mon Sep 17 00:00:00 2001
From: Rachel Powers <508861+Ryex@users.noreply.github.com>
Date: Tue, 21 Mar 2023 11:07:20 -0700
Subject: [PATCH 041/104] feat: watch sub directories for mods
Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com>
---
launcher/ui/dialogs/BlockedModsDialog.cpp | 29 ++++++++++++++++-------
launcher/ui/dialogs/BlockedModsDialog.h | 1 +
2 files changed, 22 insertions(+), 8 deletions(-)
diff --git a/launcher/ui/dialogs/BlockedModsDialog.cpp b/launcher/ui/dialogs/BlockedModsDialog.cpp
index ff885f10..05f50c06 100644
--- a/launcher/ui/dialogs/BlockedModsDialog.cpp
+++ b/launcher/ui/dialogs/BlockedModsDialog.cpp
@@ -39,7 +39,6 @@
#include
#include
#include
-#include
#include
BlockedModsDialog::BlockedModsDialog(QWidget* parent, const QString& title, const QString& text, QList& mods)
@@ -89,11 +88,11 @@ void BlockedModsDialog::dragEnterEvent(QDragEnterEvent* e)
void BlockedModsDialog::dropEvent(QDropEvent* e)
{
for (QUrl& url : e->mimeData()->urls()) {
- if (url.scheme().isEmpty()) { // ensure isLocalFile() works correctly
+ if (url.scheme().isEmpty()) { // ensure isLocalFile() works correctly
url.setScheme("file");
}
- if (!url.isLocalFile()) { // can't drop external files here.
+ if (!url.isLocalFile()) { // can't drop external files here.
continue;
}
@@ -172,7 +171,7 @@ void BlockedModsDialog::update()
}
}
-/// @brief Signal fired when a watched direcotry has changed
+/// @brief Signal fired when a watched directory has changed
/// @param path the path to the changed directory
void BlockedModsDialog::directoryChanged(QString path)
{
@@ -186,8 +185,22 @@ void BlockedModsDialog::setupWatch()
{
const QString downloadsFolder = APPLICATION->settings()->get("DownloadsDir").toString();
const QString modsFolder = APPLICATION->settings()->get("CentralModsDir").toString();
- m_watcher.addPath(downloadsFolder);
- m_watcher.addPath(modsFolder);
+ watchPath(downloadsFolder, true);
+ watchPath(modsFolder, true);
+}
+
+void BlockedModsDialog::watchPath(QString path, bool watch_subdirectories)
+{
+ m_watcher.addPath(path);
+
+ if (!watch_subdirectories)
+ return;
+
+ QDirIterator it(path, QDirIterator::Subdirectories);
+ while (it.hasNext()) {
+ QString dir = it.next();
+ watchPath(it.next(), watch_subdirectories);
+ }
}
/// @brief scan all watched folder
@@ -221,7 +234,7 @@ void BlockedModsDialog::scanPath(QString path, bool start_task)
}
}
-/// @brief add a hashing task for the file located at path, add the path to the pending set if the hasing task is already running
+/// @brief add a hashing task for the file located at path, add the path to the pending set if the hashing task is already running
/// @param path the path to the local file being hashed
void BlockedModsDialog::addHashTask(QString path)
{
@@ -328,7 +341,7 @@ void BlockedModsDialog::validateMatchedMods()
}
}
-/// @brief run hash task or mark a pending run if it is already runing
+/// @brief run hash task or mark a pending run if it is already running
void BlockedModsDialog::runHashTask()
{
if (!m_hashing_task->isRunning()) {
diff --git a/launcher/ui/dialogs/BlockedModsDialog.h b/launcher/ui/dialogs/BlockedModsDialog.h
index 014f488a..01077aa5 100644
--- a/launcher/ui/dialogs/BlockedModsDialog.h
+++ b/launcher/ui/dialogs/BlockedModsDialog.h
@@ -79,6 +79,7 @@ class BlockedModsDialog : public QDialog {
void update();
void directoryChanged(QString path);
void setupWatch();
+ void watchPath(QString path, bool watch_subdirectories = false);
void scanPaths();
void scanPath(QString path, bool start_task);
void addHashTask(QString path);
From ef50e5595e2b4c50a0f25534ca71e6d898c5b090 Mon Sep 17 00:00:00 2001
From: Rachel Powers <508861+Ryex@users.noreply.github.com>
Date: Tue, 21 Mar 2023 12:10:28 -0700
Subject: [PATCH 042/104] fix: don't try to watch the entier filesystem by
watching parent links
Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com>
---
launcher/ui/dialogs/BlockedModsDialog.cpp | 10 +++++++---
1 file changed, 7 insertions(+), 3 deletions(-)
diff --git a/launcher/ui/dialogs/BlockedModsDialog.cpp b/launcher/ui/dialogs/BlockedModsDialog.cpp
index 05f50c06..ac06625e 100644
--- a/launcher/ui/dialogs/BlockedModsDialog.cpp
+++ b/launcher/ui/dialogs/BlockedModsDialog.cpp
@@ -191,15 +191,19 @@ void BlockedModsDialog::setupWatch()
void BlockedModsDialog::watchPath(QString path, bool watch_subdirectories)
{
+ qDebug() << "[Blocked Mods Dialog] Adding Watch Path:" << path;
m_watcher.addPath(path);
if (!watch_subdirectories)
return;
- QDirIterator it(path, QDirIterator::Subdirectories);
+ QDirIterator it(path, QDir::Filter::Dirs, QDirIterator::NoIteratorFlags);
while (it.hasNext()) {
- QString dir = it.next();
- watchPath(it.next(), watch_subdirectories);
+ QString dir_path = it.next();
+ QDir to_watch_dir(dir_path);
+ if (to_watch_dir.dirName() == "." || to_watch_dir.dirName() == "..")
+ continue;
+ watchPath(dir_path, watch_subdirectories);
}
}
From 9418c62d95d8a9e829b087386cba2e2b6137daf1 Mon Sep 17 00:00:00 2001
From: Sefa Eyeoglu
Date: Wed, 22 Mar 2023 10:32:17 +0100
Subject: [PATCH 043/104] refactor: reprocess log lines per instance
Signed-off-by: Sefa Eyeoglu
---
launcher/LoggedProcess.cpp | 5 +----
launcher/LoggedProcess.h | 7 +++++--
2 files changed, 6 insertions(+), 6 deletions(-)
diff --git a/launcher/LoggedProcess.cpp b/launcher/LoggedProcess.cpp
index 43a9c4f6..c8d5c34e 100644
--- a/launcher/LoggedProcess.cpp
+++ b/launcher/LoggedProcess.cpp
@@ -61,13 +61,10 @@ LoggedProcess::~LoggedProcess()
}
}
-static QString m_leftover_line;
-
-QStringList reprocess(const QByteArray& data, QTextDecoder& decoder)
+QStringList LoggedProcess::reprocess(const QByteArray& data, QTextDecoder& decoder)
{
auto str = decoder.toUnicode(data);
- // FIXME: Flush this out when process exits
if (!m_leftover_line.isEmpty()) {
str.prepend(m_leftover_line);
m_leftover_line = "";
diff --git a/launcher/LoggedProcess.h b/launcher/LoggedProcess.h
index 2360d1ea..af3ed79f 100644
--- a/launcher/LoggedProcess.h
+++ b/launcher/LoggedProcess.h
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
- * PolyMC - Minecraft Launcher
- * Copyright (C) 2022 Sefa Eyeoglu
+ * Prism Launcher - Minecraft Launcher
+ * Copyright (C) 2022,2023 Sefa Eyeoglu
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -88,9 +88,12 @@ private slots:
private:
void changeState(LoggedProcess::State state);
+ QStringList reprocess(const QByteArray& data, QTextDecoder& decoder);
+
private:
QTextDecoder m_err_decoder = QTextDecoder(QTextCodec::codecForLocale());
QTextDecoder m_out_decoder = QTextDecoder(QTextCodec::codecForLocale());
+ QString m_leftover_line;
bool m_killed = false;
State m_state = NotRunning;
int m_exit_code = 0;
From 4c013e59f0e9a481bc63281c0d9e349827419d37 Mon Sep 17 00:00:00 2001
From: DioEgizio <83089242+DioEgizio@users.noreply.github.com>
Date: Sat, 25 Mar 2023 10:45:34 +0100
Subject: [PATCH 044/104] divide minecraftpage into tabs
this way small screen users can use the launcher settings without having window a bigger than their actual screens
Signed-off-by: DioEgizio <83089242+DioEgizio@users.noreply.github.com>
---
launcher/ui/pages/global/MinecraftPage.cpp | 1 -
launcher/ui/pages/global/MinecraftPage.ui | 153 +++++++++++----------
2 files changed, 84 insertions(+), 70 deletions(-)
diff --git a/launcher/ui/pages/global/MinecraftPage.cpp b/launcher/ui/pages/global/MinecraftPage.cpp
index cc597fe0..eca3e865 100644
--- a/launcher/ui/pages/global/MinecraftPage.cpp
+++ b/launcher/ui/pages/global/MinecraftPage.cpp
@@ -46,7 +46,6 @@
MinecraftPage::MinecraftPage(QWidget *parent) : QWidget(parent), ui(new Ui::MinecraftPage)
{
ui->setupUi(this);
- ui->tabWidget->tabBar()->hide();
loadSettings();
updateCheckboxStuff();
}
diff --git a/launcher/ui/pages/global/MinecraftPage.ui b/launcher/ui/pages/global/MinecraftPage.ui
index 640f436d..cff071bf 100644
--- a/launcher/ui/pages/global/MinecraftPage.ui
+++ b/launcher/ui/pages/global/MinecraftPage.ui
@@ -7,7 +7,7 @@
0
0
936
- 1134
+ 541
@@ -39,7 +39,7 @@
- Minecraft
+ General
-
@@ -111,68 +111,6 @@
- -
-
-
- Native library workarounds
-
-
-
-
-
-
- Use system installation of &GLFW
-
-
-
- -
-
-
- Use system installation of &OpenAL
-
-
-
-
-
-
- -
-
-
- Performance
-
-
-
-
-
-
- <html><head/><body><p>Enable Feral Interactive's GameMode, to potentially improve gaming performance.</p></body></html>
-
-
- Enable Feral GameMode
-
-
-
- -
-
-
- <html><head/><body><p>Enable MangoHud's advanced performance overlay.</p></body></html>
-
-
- Enable MangoHud
-
-
-
- -
-
-
- <html><head/><body><p>Use the discrete GPU instead of the primary GPU.</p></body></html>
-
-
- Use discrete GPU
-
-
-
-
-
-
-
@@ -247,6 +185,88 @@
+
+
+ System-related tweaks
+
+
+ -
+
+
+ Native library workarounds
+
+
+
-
+
+
+ Use system installation of &GLFW
+
+
+
+ -
+
+
+ Use system installation of &OpenAL
+
+
+
+
+
+
+ -
+
+
+ Performance
+
+
+
-
+
+
+ <html><head/><body><p>Enable Feral Interactive's GameMode, to potentially improve gaming performance.</p></body></html>
+
+
+ Enable Feral GameMode
+
+
+
+ -
+
+
+ <html><head/><body><p>Enable MangoHud's advanced performance overlay.</p></body></html>
+
+
+ Enable MangoHud
+
+
+
+ -
+
+
+ <html><head/><body><p>Use the discrete GPU instead of the primary GPU.</p></body></html>
+
+
+ Use discrete GPU
+
+
+
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+
+ 20
+ 40
+
+
+
+
+
+
@@ -255,11 +275,6 @@
maximizedCheckBox
windowWidthSpinBox
windowHeightSpinBox
- useNativeGLFWCheck
- useNativeOpenALCheck
- enableFeralGamemodeCheck
- enableMangoHud
- useDiscreteGpuCheck
From a0045ece075d5caf5d0b6982002dd6bd845ddf0f Mon Sep 17 00:00:00 2001
From: Rachel Powers <508861+Ryex@users.noreply.github.com>
Date: Mon, 27 Mar 2023 19:01:53 -0700
Subject: [PATCH 045/104] feat: add setting to watch recursively
Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com>
---
launcher/Application.cpp | 1 +
launcher/ui/dialogs/BlockedModsDialog.cpp | 3 +-
launcher/ui/pages/global/LauncherPage.cpp | 6 +++
launcher/ui/pages/global/LauncherPage.h | 1 +
launcher/ui/pages/global/LauncherPage.ui | 64 +++++++++++++----------
5 files changed, 47 insertions(+), 28 deletions(-)
diff --git a/launcher/Application.cpp b/launcher/Application.cpp
index 879af535..1fc31549 100644
--- a/launcher/Application.cpp
+++ b/launcher/Application.cpp
@@ -517,6 +517,7 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
m_settings->registerSetting({"CentralModsDir", "ModsDir"}, "mods");
m_settings->registerSetting("IconsDir", "icons");
m_settings->registerSetting("DownloadsDir", QStandardPaths::writableLocation(QStandardPaths::DownloadLocation));
+ m_settings->registerSetting("DownloadsDirWatchRecursive", false);
// Editors
m_settings->registerSetting("JsonEditor", QString());
diff --git a/launcher/ui/dialogs/BlockedModsDialog.cpp b/launcher/ui/dialogs/BlockedModsDialog.cpp
index ac06625e..36df5f46 100644
--- a/launcher/ui/dialogs/BlockedModsDialog.cpp
+++ b/launcher/ui/dialogs/BlockedModsDialog.cpp
@@ -185,7 +185,8 @@ void BlockedModsDialog::setupWatch()
{
const QString downloadsFolder = APPLICATION->settings()->get("DownloadsDir").toString();
const QString modsFolder = APPLICATION->settings()->get("CentralModsDir").toString();
- watchPath(downloadsFolder, true);
+ const bool downloadsFolderWatchRecursive = APPLICATION->settings()->get("DownloadsDirWatchRecursive").toBool();
+ watchPath(downloadsFolder, downloadsFolderWatchRecursive);
watchPath(modsFolder, true);
}
diff --git a/launcher/ui/pages/global/LauncherPage.cpp b/launcher/ui/pages/global/LauncherPage.cpp
index 324eb461..20e02230 100644
--- a/launcher/ui/pages/global/LauncherPage.cpp
+++ b/launcher/ui/pages/global/LauncherPage.cpp
@@ -184,6 +184,11 @@ void LauncherPage::on_downloadsDirBrowseBtn_clicked()
}
}
+void LauncherPage::on_downloadsDirWatchRecursiveCheckBox_clicked()
+{
+ // incase anything needs to be done here
+}
+
void LauncherPage::on_metadataDisableBtn_clicked()
{
ui->metadataWarningLabel->setHidden(!ui->metadataDisableBtn->isChecked());
@@ -217,6 +222,7 @@ void LauncherPage::applySettings()
s->set("CentralModsDir", ui->modsDirTextBox->text());
s->set("IconsDir", ui->iconsDirTextBox->text());
s->set("DownloadsDir", ui->downloadsDirTextBox->text());
+ s->set("DownloadsDirWatchRecursive", ui->downloadsDirWatchRecursiveCheckBox->isChecked());
auto sortMode = (InstSortMode)ui->sortingModeGroup->checkedId();
switch (sortMode)
diff --git a/launcher/ui/pages/global/LauncherPage.h b/launcher/ui/pages/global/LauncherPage.h
index 33f66f1b..c5ebbe39 100644
--- a/launcher/ui/pages/global/LauncherPage.h
+++ b/launcher/ui/pages/global/LauncherPage.h
@@ -90,6 +90,7 @@ slots:
void on_iconsDirBrowseBtn_clicked();
void on_downloadsDirBrowseBtn_clicked();
void on_metadataDisableBtn_clicked();
+ void on_downloadsDirWatchRecursiveCheckBox_clicked();
/*!
* Updates the font preview
diff --git a/launcher/ui/pages/global/LauncherPage.ui b/launcher/ui/pages/global/LauncherPage.ui
index 923b7f95..6279d879 100644
--- a/launcher/ui/pages/global/LauncherPage.ui
+++ b/launcher/ui/pages/global/LauncherPage.ui
@@ -67,19 +67,16 @@
Folders
- -
-
+
-
+
- ...
+ &Downloads:
+
+
+ downloadsDirTextBox
- -
-
-
- -
-
-
-
@@ -90,16 +87,25 @@
+ -
+
+
+ -
+
+
-
- -
-
+
-
+
- ...
+ ...
+ -
+
+
-
@@ -117,6 +123,20 @@
+ -
+
+
+ ...
+
+
+
+ -
+
+
+ ...
+
+
+
-
@@ -127,23 +147,13 @@
- -
-
-
- &Downloads:
+
-
+
+
+ when looking for mods in places like the blocked mods dialog Prismlauncher will check in sub folders of your downloads folder too.
-
- downloadsDirTextBox
-
-
-
- -
-
-
- -
-
- ...
+ Check downloads folder recursively
From df17f5e899c5cb67d9bf7c4193ef6fb651993ee3 Mon Sep 17 00:00:00 2001
From: Rachel Powers <508861+Ryex@users.noreply.github.com>
Date: Mon, 27 Mar 2023 19:11:26 -0700
Subject: [PATCH 046/104] fix: use QDir::Filter::NoDotAndDotDot
Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com>
---
launcher/ui/dialogs/BlockedModsDialog.cpp | 5 +----
1 file changed, 1 insertion(+), 4 deletions(-)
diff --git a/launcher/ui/dialogs/BlockedModsDialog.cpp b/launcher/ui/dialogs/BlockedModsDialog.cpp
index 36df5f46..7b3a395a 100644
--- a/launcher/ui/dialogs/BlockedModsDialog.cpp
+++ b/launcher/ui/dialogs/BlockedModsDialog.cpp
@@ -198,12 +198,9 @@ void BlockedModsDialog::watchPath(QString path, bool watch_subdirectories)
if (!watch_subdirectories)
return;
- QDirIterator it(path, QDir::Filter::Dirs, QDirIterator::NoIteratorFlags);
+ QDirIterator it(path, QDir::Filter::Dirs | QDir::Filter::NoDotAndDotDot, QDirIterator::NoIteratorFlags);
while (it.hasNext()) {
QString dir_path = it.next();
- QDir to_watch_dir(dir_path);
- if (to_watch_dir.dirName() == "." || to_watch_dir.dirName() == "..")
- continue;
watchPath(dir_path, watch_subdirectories);
}
}
From c7dc115365006a8bff8ca93a01a6df4134eb92e9 Mon Sep 17 00:00:00 2001
From: seth
Date: Fri, 31 Mar 2023 11:13:18 -0400
Subject: [PATCH 047/104] fix: search for newer mangohud vulkan layers
Signed-off-by: seth
---
launcher/MangoHud.cpp | 20 +++++++++++++++++---
1 file changed, 17 insertions(+), 3 deletions(-)
diff --git a/launcher/MangoHud.cpp b/launcher/MangoHud.cpp
index d635518e..076d3064 100644
--- a/launcher/MangoHud.cpp
+++ b/launcher/MangoHud.cpp
@@ -75,9 +75,23 @@ QString getLibraryString()
}
for (QString vkLayer : vkLayerList) {
- QString filePath = FS::PathCombine(vkLayer, "MangoHud.json");
- if (!QFile::exists(filePath))
- continue;
+ QStringList vkArchitectures = { "x86_64", "aarch64" };
+
+ QString filePath = "";
+ // prefer to use architecture specific vulkan layers
+ for (QString arch: vkArchitectures) {
+ QString tryPath = FS::PathCombine(vkLayer, QString("MangoHud.%1.json").arg(arch));
+ if (QFile::exists(tryPath)) {
+ filePath = tryPath;
+ }
+ }
+
+ if (filePath.isEmpty()) {
+ filePath = FS::PathCombine(vkLayer, "MangoHud.json");
+ // bail out if no mangohud layers are found
+ if (!QFile::exists(filePath))
+ continue;
+ }
auto conf = Json::requireDocument(filePath, vkLayer);
auto confObject = Json::requireObject(conf, vkLayer);
From 3e3b92d4c1a93f1430b7d222622758d0da1f4ac8 Mon Sep 17 00:00:00 2001
From: seth
Date: Fri, 31 Mar 2023 20:09:26 +0000
Subject: [PATCH 048/104] chore: improve detection of newer vulkan layers
i've been scrump'd
Co-authored-by: Sefa Eyeoglu
Signed-off-by: seth
---
launcher/MangoHud.cpp | 19 ++++++++-----------
1 file changed, 8 insertions(+), 11 deletions(-)
diff --git a/launcher/MangoHud.cpp b/launcher/MangoHud.cpp
index 076d3064..890aca57 100644
--- a/launcher/MangoHud.cpp
+++ b/launcher/MangoHud.cpp
@@ -75,23 +75,20 @@ QString getLibraryString()
}
for (QString vkLayer : vkLayerList) {
- QStringList vkArchitectures = { "x86_64", "aarch64" };
+ // prefer to use architecture specific vulkan layers
+ QStringList manifestNames = { "MangoHud.x86_64.json", "MangoHud.aarch64.json", "MangoHud.json" };
QString filePath = "";
- // prefer to use architecture specific vulkan layers
- for (QString arch: vkArchitectures) {
- QString tryPath = FS::PathCombine(vkLayer, QString("MangoHud.%1.json").arg(arch));
+ for (QString manifestName : manifestNames) {
+ QString tryPath = FS::PathCombine(vkLayer, manifestName);
if (QFile::exists(tryPath)) {
- filePath = tryPath;
+ filePath = tryPath;
+ break;
}
}
- if (filePath.isEmpty()) {
- filePath = FS::PathCombine(vkLayer, "MangoHud.json");
- // bail out if no mangohud layers are found
- if (!QFile::exists(filePath))
- continue;
- }
+ if (filePath.isEmpty())
+ continue;
auto conf = Json::requireDocument(filePath, vkLayer);
auto confObject = Json::requireObject(conf, vkLayer);
From 4df4b4390086171b9ee78a8cd7efb32412292485 Mon Sep 17 00:00:00 2001
From: Rachel Powers <508861+Ryex@users.noreply.github.com>
Date: Fri, 31 Mar 2023 18:25:29 -0700
Subject: [PATCH 049/104] fix: Apply suggestions from code review (string
changes)
Co-authored-by: flow
Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com>
---
launcher/ui/MainWindow.cpp | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp
index a6aa8320..e20a7613 100644
--- a/launcher/ui/MainWindow.cpp
+++ b/launcher/ui/MainWindow.cpp
@@ -1341,10 +1341,10 @@ void MainWindow::on_actionDeleteInstance_triggered()
if (!linkedInstances.empty()) {
response = CustomMessageBox::selectable(
this, tr("There are linked instances"),
- tr("The folowing Instance(s) might reference files in this instance:\n\n"
+ tr("The folowing instance(s) might reference files in this instance:\n\n"
"%1\n\n"
"Deleting it could break the other instance(s), \n\n"
- "Are you sure?").arg(linkedInstances.join("\n")),
+ "Do you wish to proceed?").arg(linkedInstances.join("\n")),
QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No, QMessageBox::No
)->exec();
if (response != QMessageBox::Yes)
From 538092b72728fa34bafc873e16abaa7f318a945c Mon Sep 17 00:00:00 2001
From: Rachel Powers <508861+Ryex@users.noreply.github.com>
Date: Fri, 31 Mar 2023 20:22:07 -0700
Subject: [PATCH 050/104] fix: typos, CamelCase to camelCase the new names
Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com>
---
launcher/FileSystem.cpp | 30 +++++++-------
launcher/FileSystem.h | 87 ++++++++++++++++++++-------------------
tests/FileSystem_test.cpp | 40 +++++++++---------
3 files changed, 81 insertions(+), 76 deletions(-)
diff --git a/launcher/FileSystem.cpp b/launcher/FileSystem.cpp
index 640cf9be..c046ee86 100644
--- a/launcher/FileSystem.cpp
+++ b/launcher/FileSystem.cpp
@@ -328,7 +328,7 @@ bool create_link::operator()(const QString& offset, bool dryRun)
}
/**
- * @brief make a list off all the links to
+ * @brief Make a list of all the links to make
* @param offset subdirectory form src to link to dest
* @return if there was an error during the attempt to link
*/
@@ -363,7 +363,7 @@ void create_link::make_link_list(const QString& offset)
link_file(src, "");
} else {
if (m_debug)
- qDebug() << "linking recursively:" << src << "to" << dst << "max_depth:" << m_max_depth;
+ qDebug() << "linking recursively:" << src << "to" << dst << ", max_depth:" << m_max_depth;
QDir src_dir(src);
QDirIterator source_it(src, QDir::Filter::Files | QDir::Filter::Hidden, QDirIterator::Subdirectories);
@@ -373,8 +373,8 @@ void create_link::make_link_list(const QString& offset)
auto src_path = source_it.next();
auto relative_path = src_dir.relativeFilePath(src_path);
- if (m_max_depth >= 0 && PathDepth(relative_path) > m_max_depth) {
- relative_path = PathTruncate(relative_path, m_max_depth);
+ if (m_max_depth >= 0 && pathDepth(relative_path) > m_max_depth){
+ relative_path = pathTruncate(relative_path, m_max_depth);
src_path = src_dir.filePath(relative_path);
if (linkedPaths.contains(src_path)) {
continue;
@@ -394,20 +394,22 @@ bool create_link::make_links()
for (auto link : m_links_to_make) {
QString src_path = link.src;
QString dst_path = link.dst;
+ auto src_path_std = StringUtils::toStdString(link.src);
+ auto dst_path_std = StringUtils::toStdString(link.dst);
ensureFilePathExists(dst_path);
if (m_useHardLinks) {
if (m_debug)
qDebug() << "making hard link:" << src_path << "to" << dst_path;
- fs::create_hard_link(StringUtils::toStdString(src_path), StringUtils::toStdString(dst_path), m_os_err);
- } else if (fs::is_directory(StringUtils::toStdString(src_path))) {
+ fs::create_hard_link(src_path_std, dst_path_std, m_os_err);
+ } else if (fs::is_directory(src_path_std)) {
if (m_debug)
qDebug() << "making directory_symlink:" << src_path << "to" << dst_path;
- fs::create_directory_symlink(StringUtils::toStdString(src_path), StringUtils::toStdString(dst_path), m_os_err);
+ fs::create_directory_symlink(src_path_std, dst_path_std, m_os_err);
} else {
if (m_debug)
qDebug() << "making symlink:" << src_path << "to" << dst_path;
- fs::create_symlink(StringUtils::toStdString(src_path), StringUtils::toStdString(dst_path), m_os_err);
+ fs::create_symlink(src_path_std, dst_path_std, m_os_err);
}
if (m_os_err) {
@@ -636,7 +638,7 @@ QString AbsolutePath(const QString& path)
return QFileInfo(path).absolutePath();
}
-int PathDepth(const QString& path)
+int pathDepth(const QString& path)
{
if (path.isEmpty())
return 0;
@@ -656,15 +658,15 @@ int PathDepth(const QString& path)
return numParts;
}
-QString PathTruncate(const QString& path, int depth)
+QString pathTruncate(const QString& path, int depth)
{
if (path.isEmpty() || (depth < 0))
return "";
QString trunc = QFileInfo(path).path();
- if (PathDepth(trunc) > depth) {
- return PathTruncate(trunc, depth);
+ if (pathDepth(trunc) > depth ) {
+ return pathTruncate(trunc, depth);
}
#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0)
@@ -984,7 +986,7 @@ FilesystemType getFilesystemType(const QString& name)
* @brief path to the near ancestor that exists
*
*/
-QString NearestExistentAncestor(const QString& path)
+QString nearestExistentAncestor(const QString& path)
{
if (QFileInfo::exists(path))
return path;
@@ -1007,7 +1009,7 @@ FilesystemInfo statFS(const QString& path)
{
FilesystemInfo info;
- QStorageInfo storage_info(NearestExistentAncestor(path));
+ QStorageInfo storage_info(nearestExistentAncestor(path));
info.fsTypeName = storage_info.fileSystemType();
diff --git a/launcher/FileSystem.h b/launcher/FileSystem.h
index 673c3563..47044d93 100644
--- a/launcher/FileSystem.h
+++ b/launcher/FileSystem.h
@@ -280,7 +280,7 @@ QString AbsolutePath(const QString& path);
* @param path path to measure
* @return int number of components before base path
*/
-int PathDepth(const QString& path);
+int pathDepth(const QString& path);
/**
* @brief cut off segments of path until it is a max of length depth
@@ -289,7 +289,7 @@ int PathDepth(const QString& path);
* @param depth max depth of new path
* @return QString truncated path
*/
-QString PathTruncate(const QString& path, int depth);
+QString pathTruncate(const QString& path, int depth);
/**
* Resolve an executable
@@ -360,23 +360,26 @@ enum class FilesystemType {
* QMap is ordered
*
*/
-static const QMap s_filesystem_type_names = { { FilesystemType::FAT, QString("FAT") },
- { FilesystemType::NTFS, QString("NTFS") },
- { FilesystemType::REFS, QString("REFS") },
- { FilesystemType::EXT, QString("EXT") },
- { FilesystemType::EXT_2_OLD, QString("EXT_2_OLD") },
- { FilesystemType::EXT_2_3_4, QString("EXT2/3/4") },
- { FilesystemType::XFS, QString("XFS") },
- { FilesystemType::BTRFS, QString("BTRFS") },
- { FilesystemType::NFS, QString("NFS") },
- { FilesystemType::ZFS, QString("ZFS") },
- { FilesystemType::APFS, QString("APFS") },
- { FilesystemType::HFS, QString("HFS") },
- { FilesystemType::HFSPLUS, QString("HFSPLUS") },
- { FilesystemType::HFSX, QString("HFSX") },
- { FilesystemType::FUSEBLK, QString("FUSEBLK") },
- { FilesystemType::F2FS, QString("F2FS") },
- { FilesystemType::UNKNOWN, QString("UNKNOWN") } };
+static const QMap s_filesystem_type_names = {
+ {FilesystemType::FAT, QStringLiteral("FAT")},
+ {FilesystemType::NTFS, QStringLiteral("NTFS")},
+ {FilesystemType::REFS, QStringLiteral("REFS")},
+ {FilesystemType::EXT, QStringLiteral("EXT")},
+ {FilesystemType::EXT_2_OLD, QStringLiteral("EXT_2_OLD")},
+ {FilesystemType::EXT_2_3_4, QStringLiteral("EXT2/3/4")},
+ {FilesystemType::XFS, QStringLiteral("XFS")},
+ {FilesystemType::BTRFS, QStringLiteral("BTRFS")},
+ {FilesystemType::NFS, QStringLiteral("NFS")},
+ {FilesystemType::ZFS, QStringLiteral("ZFS")},
+ {FilesystemType::APFS, QStringLiteral("APFS")},
+ {FilesystemType::HFS, QStringLiteral("HFS")},
+ {FilesystemType::HFSPLUS, QStringLiteral("HFSPLUS")},
+ {FilesystemType::HFSX, QStringLiteral("HFSX")},
+ {FilesystemType::FUSEBLK, QStringLiteral("FUSEBLK")},
+ {FilesystemType::F2FS, QStringLiteral("F2FS")},
+ {FilesystemType::UNKNOWN, QStringLiteral("UNKNOWN")}
+};
+
/**
* @brief Ordered Mapping of reported filesystem names to enum types
@@ -387,28 +390,28 @@ static const QMap s_filesystem_type_names = { { Filesys
*
*/
static const QMap s_filesystem_type_names_inverse = {
- { QString("FAT"), FilesystemType::FAT },
- { QString("NTFS"), FilesystemType::NTFS },
- { QString("REFS"), FilesystemType::REFS },
- { QString("EXT2_OLD"), FilesystemType::EXT_2_OLD },
- { QString("EXT_2_OLD"), FilesystemType::EXT_2_OLD },
- { QString("EXT2"), FilesystemType::EXT_2_3_4 },
- { QString("EXT3"), FilesystemType::EXT_2_3_4 },
- { QString("EXT4"), FilesystemType::EXT_2_3_4 },
- { QString("EXT2/3/4"), FilesystemType::EXT_2_3_4 },
- { QString("EXT_2_3_4"), FilesystemType::EXT_2_3_4 },
- { QString("EXT"), FilesystemType::EXT }, // must come after all other EXT variants to prevent greedy detection
- { QString("XFS"), FilesystemType::XFS },
- { QString("BTRFS"), FilesystemType::BTRFS },
- { QString("NFS"), FilesystemType::NFS },
- { QString("ZFS"), FilesystemType::ZFS },
- { QString("APFS"), FilesystemType::APFS },
- { QString("HFSPLUS"), FilesystemType::HFSPLUS },
- { QString("HFSX"), FilesystemType::HFSX },
- { QString("HFS"), FilesystemType::HFS },
- { QString("FUSEBLK"), FilesystemType::FUSEBLK },
- { QString("F2FS"), FilesystemType::F2FS },
- { QString("UNKNOWN"), FilesystemType::UNKNOWN }
+ {QStringLiteral("FAT"), FilesystemType::FAT},
+ {QStringLiteral("NTFS"), FilesystemType::NTFS},
+ {QStringLiteral("REFS"), FilesystemType::REFS},
+ {QStringLiteral("EXT2_OLD"), FilesystemType::EXT_2_OLD},
+ {QStringLiteral("EXT_2_OLD"), FilesystemType::EXT_2_OLD},
+ {QStringLiteral("EXT2"), FilesystemType::EXT_2_3_4},
+ {QStringLiteral("EXT3"), FilesystemType::EXT_2_3_4},
+ {QStringLiteral("EXT4"), FilesystemType::EXT_2_3_4},
+ {QStringLiteral("EXT2/3/4"), FilesystemType::EXT_2_3_4},
+ {QStringLiteral("EXT_2_3_4"), FilesystemType::EXT_2_3_4},
+ {QStringLiteral("EXT"), FilesystemType::EXT}, // must come after all other EXT variants to prevent greedy detection
+ {QStringLiteral("XFS"), FilesystemType::XFS},
+ {QStringLiteral("BTRFS"), FilesystemType::BTRFS},
+ {QStringLiteral("NFS"), FilesystemType::NFS},
+ {QStringLiteral("ZFS"), FilesystemType::ZFS},
+ {QStringLiteral("APFS"), FilesystemType::APFS},
+ {QStringLiteral("HFSPLUS"), FilesystemType::HFSPLUS},
+ {QStringLiteral("HFSX"), FilesystemType::HFSX},
+ {QStringLiteral("HFS"), FilesystemType::HFS},
+ {QStringLiteral("FUSEBLK"), FilesystemType::FUSEBLK},
+ {QStringLiteral("F2FS"), FilesystemType::F2FS},
+ {QStringLiteral("UNKNOWN"), FilesystemType::UNKNOWN}
};
/**
@@ -452,7 +455,7 @@ struct FilesystemInfo {
* @brief path to the near ancestor that exists
*
*/
-QString NearestExistentAncestor(const QString& path);
+QString nearestExistentAncestor(const QString& path);
/**
* @brief colect information about the filesystem under a file
diff --git a/tests/FileSystem_test.cpp b/tests/FileSystem_test.cpp
index 19565a99..ec1f0bcf 100644
--- a/tests/FileSystem_test.cpp
+++ b/tests/FileSystem_test.cpp
@@ -756,30 +756,30 @@ slots:
}
void test_path_depth() {
- QCOMPARE(FS::PathDepth(""), 0);
- QCOMPARE(FS::PathDepth("."), 0);
- QCOMPARE(FS::PathDepth("foo.txt"), 0);
- QCOMPARE(FS::PathDepth("./foo.txt"), 0);
- QCOMPARE(FS::PathDepth("./bar/foo.txt"), 1);
- QCOMPARE(FS::PathDepth("../bar/foo.txt"), 0);
- QCOMPARE(FS::PathDepth("/bar/foo.txt"), 1);
- QCOMPARE(FS::PathDepth("baz/bar/foo.txt"), 2);
- QCOMPARE(FS::PathDepth("/baz/bar/foo.txt"), 2);
- QCOMPARE(FS::PathDepth("./baz/bar/foo.txt"), 2);
- QCOMPARE(FS::PathDepth("/baz/../bar/foo.txt"), 1);
+ QCOMPARE(FS::pathDepth(""), 0);
+ QCOMPARE(FS::pathDepth("."), 0);
+ QCOMPARE(FS::pathDepth("foo.txt"), 0);
+ QCOMPARE(FS::pathDepth("./foo.txt"), 0);
+ QCOMPARE(FS::pathDepth("./bar/foo.txt"), 1);
+ QCOMPARE(FS::pathDepth("../bar/foo.txt"), 0);
+ QCOMPARE(FS::pathDepth("/bar/foo.txt"), 1);
+ QCOMPARE(FS::pathDepth("baz/bar/foo.txt"), 2);
+ QCOMPARE(FS::pathDepth("/baz/bar/foo.txt"), 2);
+ QCOMPARE(FS::pathDepth("./baz/bar/foo.txt"), 2);
+ QCOMPARE(FS::pathDepth("/baz/../bar/foo.txt"), 1);
}
void test_path_trunc() {
- QCOMPARE(FS::PathTruncate("", 0), QDir::toNativeSeparators(""));
- QCOMPARE(FS::PathTruncate("foo.txt", 0), QDir::toNativeSeparators(""));
- QCOMPARE(FS::PathTruncate("foo.txt", 1), QDir::toNativeSeparators(""));
- QCOMPARE(FS::PathTruncate("./bar/foo.txt", 0), QDir::toNativeSeparators("./bar"));
- QCOMPARE(FS::PathTruncate("./bar/foo.txt", 1), QDir::toNativeSeparators("./bar"));
- QCOMPARE(FS::PathTruncate("/bar/foo.txt", 1), QDir::toNativeSeparators("/bar"));
- QCOMPARE(FS::PathTruncate("bar/foo.txt", 1), QDir::toNativeSeparators("bar"));
- QCOMPARE(FS::PathTruncate("baz/bar/foo.txt", 2), QDir::toNativeSeparators("baz/bar"));
+ QCOMPARE(FS::pathTruncate("", 0), QDir::toNativeSeparators(""));
+ QCOMPARE(FS::pathTruncate("foo.txt", 0), QDir::toNativeSeparators(""));
+ QCOMPARE(FS::pathTruncate("foo.txt", 1), QDir::toNativeSeparators(""));
+ QCOMPARE(FS::pathTruncate("./bar/foo.txt", 0), QDir::toNativeSeparators("./bar"));
+ QCOMPARE(FS::pathTruncate("./bar/foo.txt", 1), QDir::toNativeSeparators("./bar"));
+ QCOMPARE(FS::pathTruncate("/bar/foo.txt", 1), QDir::toNativeSeparators("/bar"));
+ QCOMPARE(FS::pathTruncate("bar/foo.txt", 1), QDir::toNativeSeparators("bar"));
+ QCOMPARE(FS::pathTruncate("baz/bar/foo.txt", 2), QDir::toNativeSeparators("baz/bar"));
#if defined(Q_OS_WIN)
- QCOMPARE(FS::PathTruncate("C:\\bar\\foo.txt", 1), QDir::toNativeSeparators("C:\\bar"));
+ QCOMPARE(FS::pathTruncate("C:\\bar\\foo.txt", 1), QDir::toNativeSeparators("C:\\bar"));
#endif
}
};
From 81b140629070c96853ccdc50fe30256ffe1c0636 Mon Sep 17 00:00:00 2001
From: seth
Date: Sat, 1 Apr 2023 12:58:15 -0400
Subject: [PATCH 051/104] feat(nix): use ninja for builds
Signed-off-by: seth
---
nix/default.nix | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/nix/default.nix b/nix/default.nix
index 6d4f3f24..cd782f90 100644
--- a/nix/default.nix
+++ b/nix/default.nix
@@ -2,6 +2,7 @@
lib,
stdenv,
cmake,
+ ninja,
jdk8,
jdk17,
zlib,
@@ -33,7 +34,7 @@ stdenv.mkDerivation rec {
src = lib.cleanSource self;
- nativeBuildInputs = [extra-cmake-modules cmake file jdk17 wrapQtAppsHook];
+ nativeBuildInputs = [extra-cmake-modules cmake file jdk17 ninja wrapQtAppsHook];
buildInputs =
[
qtbase
From 4055e34320e738753a6e8f5431790f078daf974b Mon Sep 17 00:00:00 2001
From: seth
Date: Sat, 1 Apr 2023 12:56:09 -0400
Subject: [PATCH 052/104] chore: use system architecture to detect vulkan
layers
Signed-off-by: seth
---
launcher/MangoHud.cpp | 12 ++++++++++--
1 file changed, 10 insertions(+), 2 deletions(-)
diff --git a/launcher/MangoHud.cpp b/launcher/MangoHud.cpp
index 890aca57..90e48e29 100644
--- a/launcher/MangoHud.cpp
+++ b/launcher/MangoHud.cpp
@@ -19,6 +19,7 @@
#include
#include
#include
+#include
#include
#include "MangoHud.h"
@@ -76,7 +77,13 @@ QString getLibraryString()
for (QString vkLayer : vkLayerList) {
// prefer to use architecture specific vulkan layers
- QStringList manifestNames = { "MangoHud.x86_64.json", "MangoHud.aarch64.json", "MangoHud.json" };
+ QString currentArch = QSysInfo::currentCpuArchitecture();
+
+ if (currentArch == "arm64") {
+ currentArch = "aarch64";
+ }
+
+ QStringList manifestNames = { QString("MangoHud.%1.json").arg(currentArch), "MangoHud.json" };
QString filePath = "";
for (QString manifestName : manifestNames) {
@@ -87,8 +94,9 @@ QString getLibraryString()
}
}
- if (filePath.isEmpty())
+ if (filePath.isEmpty()) {
continue;
+ }
auto conf = Json::requireDocument(filePath, vkLayer);
auto confObject = Json::requireObject(conf, vkLayer);
From 732bc536926d86015ff702c2ec46606787432009 Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Sun, 2 Apr 2023 03:14:42 +0000
Subject: [PATCH 053/104] chore(deps): update flatpak/flatpak-github-actions
action to v6
---
.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 022a04f8..ae683795 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -563,7 +563,7 @@ jobs:
submodules: 'true'
- name: Build Flatpak (Linux)
if: inputs.build_type == 'Debug'
- uses: flatpak/flatpak-github-actions/flatpak-builder@v5
+ uses: flatpak/flatpak-github-actions/flatpak-builder@v6
with:
bundle: "Prism Launcher.flatpak"
manifest-path: flatpak/org.prismlauncher.PrismLauncher.yml
From ea7f03770c86076751ddea96d9b7b89358a2d746 Mon Sep 17 00:00:00 2001
From: Sefa Eyeoglu
Date: Sun, 2 Apr 2023 11:33:04 +0200
Subject: [PATCH 054/104] refactor(nix): use qtWrapperArgs
Signed-off-by: Sefa Eyeoglu
---
nix/default.nix | 14 ++++++--------
1 file changed, 6 insertions(+), 8 deletions(-)
diff --git a/nix/default.nix b/nix/default.nix
index cd782f90..f219d5d2 100644
--- a/nix/default.nix
+++ b/nix/default.nix
@@ -50,7 +50,6 @@ stdenv.mkDerivation rec {
cmakeFlags =
lib.optionals (msaClientID != "") ["-DLauncher_MSA_CLIENT_ID=${msaClientID}"]
++ lib.optionals (lib.versionOlder qtbase.version "6") ["-DLauncher_QT_VERSION_MAJOR=5"];
- dontWrapQtApps = true;
postUnpack = ''
rm -rf source/libraries/libnbtplusplus
@@ -60,7 +59,7 @@ stdenv.mkDerivation rec {
chown -R $USER: source/libraries/libnbtplusplus
'';
- postInstall = let
+ qtWrapperArgs = let
libpath = with xorg;
lib.makeLibraryPath [
libX11
@@ -74,13 +73,12 @@ stdenv.mkDerivation rec {
openal
stdenv.cc.cc.lib
];
- in ''
+ 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
- wrapQtApp $out/bin/prismlauncher \
- --set LD_LIBRARY_PATH /run/opengl-driver/lib:${libpath} \
- --prefix PRISMLAUNCHER_JAVA_PATHS : ${lib.makeSearchPath "bin/java" jdks} \
- --prefix PATH : ${lib.makeBinPath [xorg.xrandr]}
- '';
+ "--prefix PATH : ${lib.makeBinPath [xorg.xrandr]}"
+ ];
meta = with lib; {
homepage = "https://prismlauncher.org/";
From 70364884a963d35234711c750fd6abdc6ebfb635 Mon Sep 17 00:00:00 2001
From: Sefa Eyeoglu
Date: Sun, 2 Apr 2023 11:35:35 +0200
Subject: [PATCH 055/104] feat(nix): add support for GameMode
Signed-off-by: Sefa Eyeoglu
---
nix/default.nix | 30 +++++++++++++++++-------------
1 file changed, 17 insertions(+), 13 deletions(-)
diff --git a/nix/default.nix b/nix/default.nix
index f219d5d2..e0616b6e 100644
--- a/nix/default.nix
+++ b/nix/default.nix
@@ -23,6 +23,8 @@
cmark,
msaClientID ? "",
jdks ? [jdk17 jdk8],
+ gamemodeSupport ? true,
+ gamemode,
# flake
self,
version,
@@ -45,7 +47,8 @@ stdenv.mkDerivation rec {
tomlplusplus
cmark
]
- ++ lib.optional (lib.versionAtLeast qtbase.version "6") qtwayland;
+ ++ lib.optional (lib.versionAtLeast qtbase.version "6") qtwayland
+ ++ lib.optional gamemodeSupport gamemode.dev;
cmakeFlags =
lib.optionals (msaClientID != "") ["-DLauncher_MSA_CLIENT_ID=${msaClientID}"]
@@ -61,18 +64,19 @@ stdenv.mkDerivation rec {
qtWrapperArgs = let
libpath = with xorg;
- lib.makeLibraryPath [
- libX11
- libXext
- libXcursor
- libXrandr
- libXxf86vm
- libpulseaudio
- libGL
- glfw
- openal
- stdenv.cc.cc.lib
- ];
+ 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}"
From ba2b5c3a65089e3807ebd57a1504591f8dab4049 Mon Sep 17 00:00:00 2001
From: Rachel Powers <508861+Ryex@users.noreply.github.com>
Date: Sun, 2 Apr 2023 16:39:13 -0700
Subject: [PATCH 056/104] fix: Apply suggestions from code review
-expand columspan on new UI element
-improve tooltip
Co-authored-by: flow
Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com>
---
launcher/ui/pages/global/LauncherPage.ui | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/launcher/ui/pages/global/LauncherPage.ui b/launcher/ui/pages/global/LauncherPage.ui
index 6279d879..906efd1a 100644
--- a/launcher/ui/pages/global/LauncherPage.ui
+++ b/launcher/ui/pages/global/LauncherPage.ui
@@ -147,10 +147,10 @@
- -
+
-
- when looking for mods in places like the blocked mods dialog Prismlauncher will check in sub folders of your downloads folder too.
+ When enabled, in addition to the downloads folder, its sub folders will also be searched when looking for resources (e.g. when looking for blocked mods on CurseForge).
Check downloads folder recursively
From 5ce7874280f648e1db240ad922a2e62a7ccedea2 Mon Sep 17 00:00:00 2001
From: Rachel Powers <508861+Ryex@users.noreply.github.com>
Date: Sun, 2 Apr 2023 18:55:21 -0700
Subject: [PATCH 057/104] fix: no loops in watch paths! >:(
Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com>
---
launcher/ui/dialogs/BlockedModsDialog.cpp | 7 +++++--
launcher/ui/pages/global/LauncherPage.cpp | 5 -----
launcher/ui/pages/global/LauncherPage.h | 1 -
3 files changed, 5 insertions(+), 8 deletions(-)
diff --git a/launcher/ui/dialogs/BlockedModsDialog.cpp b/launcher/ui/dialogs/BlockedModsDialog.cpp
index 7b3a395a..90078a98 100644
--- a/launcher/ui/dialogs/BlockedModsDialog.cpp
+++ b/launcher/ui/dialogs/BlockedModsDialog.cpp
@@ -192,6 +192,9 @@ void BlockedModsDialog::setupWatch()
void BlockedModsDialog::watchPath(QString path, bool watch_subdirectories)
{
+ if (m_watcher.directories().contains(path))
+ return; // don't watch the same path twice (no loops!)
+
qDebug() << "[Blocked Mods Dialog] Adding Watch Path:" << path;
m_watcher.addPath(path);
@@ -200,8 +203,8 @@ void BlockedModsDialog::watchPath(QString path, bool watch_subdirectories)
QDirIterator it(path, QDir::Filter::Dirs | QDir::Filter::NoDotAndDotDot, QDirIterator::NoIteratorFlags);
while (it.hasNext()) {
- QString dir_path = it.next();
- watchPath(dir_path, watch_subdirectories);
+ QString watch_dir = QDir(it.next()).canonicalPath(); // resolve symlinks and relative paths
+ watchPath(watch_dir, watch_subdirectories);
}
}
diff --git a/launcher/ui/pages/global/LauncherPage.cpp b/launcher/ui/pages/global/LauncherPage.cpp
index 20e02230..51652320 100644
--- a/launcher/ui/pages/global/LauncherPage.cpp
+++ b/launcher/ui/pages/global/LauncherPage.cpp
@@ -184,11 +184,6 @@ void LauncherPage::on_downloadsDirBrowseBtn_clicked()
}
}
-void LauncherPage::on_downloadsDirWatchRecursiveCheckBox_clicked()
-{
- // incase anything needs to be done here
-}
-
void LauncherPage::on_metadataDisableBtn_clicked()
{
ui->metadataWarningLabel->setHidden(!ui->metadataDisableBtn->isChecked());
diff --git a/launcher/ui/pages/global/LauncherPage.h b/launcher/ui/pages/global/LauncherPage.h
index c5ebbe39..33f66f1b 100644
--- a/launcher/ui/pages/global/LauncherPage.h
+++ b/launcher/ui/pages/global/LauncherPage.h
@@ -90,7 +90,6 @@ slots:
void on_iconsDirBrowseBtn_clicked();
void on_downloadsDirBrowseBtn_clicked();
void on_metadataDisableBtn_clicked();
- void on_downloadsDirWatchRecursiveCheckBox_clicked();
/*!
* Updates the font preview
From 0cf1fc72a37f6c46e0e3fd2b3dd7a62e78a58574 Mon Sep 17 00:00:00 2001
From: DioEgizio <83089242+DioEgizio@users.noreply.github.com>
Date: Mon, 3 Apr 2023 16:01:07 +0200
Subject: [PATCH 058/104] chore: update to qt 6.5.0 on macos
Signed-off-by: DioEgizio <83089242+DioEgizio@users.noreply.github.com>
---
.github/workflows/build.yml | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index ae683795..43c71a76 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.4.3'
+ qt_version: '6.5.0'
qt_modules: 'qt5compat qtimageformats'
qt_tools: ''
@@ -208,6 +208,8 @@ jobs:
if: runner.os == 'Windows' && matrix.architecture == 'arm64'
uses: jurplel/install-qt-action@v3
with:
+ aqtversion: '==3.1.*'
+ py7zrversion: '>=0.20.2'
version: ${{ matrix.qt_version }}
host: 'windows'
target: 'desktop'
@@ -223,6 +225,8 @@ jobs:
if: runner.os == 'Linux' && matrix.qt_ver == 6 || runner.os == 'macOS' || (runner.os == 'Windows' && matrix.msystem == '')
uses: jurplel/install-qt-action@v3
with:
+ aqtversion: '==3.1.*'
+ py7zrversion: '>=0.20.2'
version: ${{ matrix.qt_version }}
host: ${{ matrix.qt_host }}
target: 'desktop'
From 62c59820cf21d35f97d79bd75c946cfd282be5b8 Mon Sep 17 00:00:00 2001
From: Rachel Powers <508861+Ryex@users.noreply.github.com>
Date: Mon, 3 Apr 2023 15:26:26 -0700
Subject: [PATCH 059/104] fix: harden watchPath. NO DUPLICATES! >:(
Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com>
---
launcher/ui/dialogs/BlockedModsDialog.cpp | 11 +++++++----
1 file changed, 7 insertions(+), 4 deletions(-)
diff --git a/launcher/ui/dialogs/BlockedModsDialog.cpp b/launcher/ui/dialogs/BlockedModsDialog.cpp
index 90078a98..e7404f2d 100644
--- a/launcher/ui/dialogs/BlockedModsDialog.cpp
+++ b/launcher/ui/dialogs/BlockedModsDialog.cpp
@@ -192,16 +192,19 @@ void BlockedModsDialog::setupWatch()
void BlockedModsDialog::watchPath(QString path, bool watch_subdirectories)
{
- if (m_watcher.directories().contains(path))
+ auto to_watch = QFileInfo(path);
+ auto to_watch_path = to_watch.canonicalPath();
+ if (m_watcher.directories().contains(to_watch_path))
return; // don't watch the same path twice (no loops!)
qDebug() << "[Blocked Mods Dialog] Adding Watch Path:" << path;
- m_watcher.addPath(path);
+ m_watcher.addPath(to_watch_path);
- if (!watch_subdirectories)
+ if (!to_watch.isDir() || !watch_subdirectories)
return;
- QDirIterator it(path, QDir::Filter::Dirs | QDir::Filter::NoDotAndDotDot, QDirIterator::NoIteratorFlags);
+
+ QDirIterator it(to_watch_path, QDir::Filter::Dirs | QDir::Filter::NoDotAndDotDot, QDirIterator::NoIteratorFlags);
while (it.hasNext()) {
QString watch_dir = QDir(it.next()).canonicalPath(); // resolve symlinks and relative paths
watchPath(watch_dir, watch_subdirectories);
From 5b50b806ec5954aa3822443969d22ea79faa07c5 Mon Sep 17 00:00:00 2001
From: Rachel Powers <508861+Ryex@users.noreply.github.com>
Date: Mon, 3 Apr 2023 17:14:06 -0700
Subject: [PATCH 060/104] refactor: remove data duplication in statis FS Names
Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com>
---
launcher/CMakeLists.txt | 12 +++----
launcher/FileSystem.cpp | 22 ++++++-------
launcher/FileSystem.h | 70 +++++++++++------------------------------
3 files changed, 35 insertions(+), 69 deletions(-)
diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt
index b47f5746..03d3fcbf 100644
--- a/launcher/CMakeLists.txt
+++ b/launcher/CMakeLists.txt
@@ -1123,17 +1123,17 @@ if(WIN32)
add_library(filelink_logic STATIC ${LINKEXE_SOURCES})
target_include_directories(filelink_logic PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(filelink_logic
- systeminfo
+ # systeminfo
BuildConfig
- Qt${QT_VERSION_MAJOR}::Widgets
+ # Qt${QT_VERSION_MAJOR}::Widgets
ghcFilesystem::ghc_filesystem
)
target_link_libraries(filelink_logic
Qt${QT_VERSION_MAJOR}::Core
- Qt${QT_VERSION_MAJOR}::Xml
- Qt${QT_VERSION_MAJOR}::Network
- Qt${QT_VERSION_MAJOR}::Concurrent
- ${Launcher_QT_LIBS}
+ # Qt${QT_VERSION_MAJOR}::Xml
+ # Qt${QT_VERSION_MAJOR}::Network
+ # Qt${QT_VERSION_MAJOR}::Concurrent
+ # ${Launcher_QT_LIBS}
)
add_executable("${Launcher_Name}_filelink" WIN32 filelink/main.cpp)
diff --git a/launcher/FileSystem.cpp b/launcher/FileSystem.cpp
index c046ee86..869fbe36 100644
--- a/launcher/FileSystem.cpp
+++ b/launcher/FileSystem.cpp
@@ -955,29 +955,29 @@ QString getFilesystemTypeName(FilesystemType type)
{
auto iter = s_filesystem_type_names.constFind(type);
if (iter != s_filesystem_type_names.constEnd()) {
- return iter.value();
+ return iter.value().constFirst();
}
return getFilesystemTypeName(FilesystemType::UNKNOWN);
}
FilesystemType getFilesystemTypeFuzzy(const QString& name)
{
- auto iter = s_filesystem_type_names_inverse.constFind(name.toUpper());
- if (iter != s_filesystem_type_names_inverse.constEnd()) {
- return iter.value();
+ for (auto iter = s_filesystem_type_names.constBegin(); iter != s_filesystem_type_names.constEnd(); ++iter) {
+ auto fs_names = iter.value();
+ for (auto fs_name : fs_names) {
+ if (name.toUpper().contains(fs_name.toUpper()))
+ return iter.key();
+ }
}
return FilesystemType::UNKNOWN;
}
FilesystemType getFilesystemType(const QString& name)
{
- for (auto fs_type_pair : s_filesystem_type_names_inverse.toStdMap()) {
- auto fs_type_name = fs_type_pair.first;
- auto fs_type = fs_type_pair.second;
-
- if (name.toUpper().contains(fs_type_name.toUpper())) {
- return fs_type;
- }
+ for (auto iter = s_filesystem_type_names.constBegin(); iter != s_filesystem_type_names.constEnd(); ++iter) {
+ auto fs_names = iter.value();
+ if(fs_names.contains(name.toUpper()))
+ return iter.key();
}
return FilesystemType::UNKNOWN;
}
diff --git a/launcher/FileSystem.h b/launcher/FileSystem.h
index 47044d93..cb581d0c 100644
--- a/launcher/FileSystem.h
+++ b/launcher/FileSystem.h
@@ -360,58 +360,24 @@ enum class FilesystemType {
* QMap is ordered
*
*/
-static const QMap s_filesystem_type_names = {
- {FilesystemType::FAT, QStringLiteral("FAT")},
- {FilesystemType::NTFS, QStringLiteral("NTFS")},
- {FilesystemType::REFS, QStringLiteral("REFS")},
- {FilesystemType::EXT, QStringLiteral("EXT")},
- {FilesystemType::EXT_2_OLD, QStringLiteral("EXT_2_OLD")},
- {FilesystemType::EXT_2_3_4, QStringLiteral("EXT2/3/4")},
- {FilesystemType::XFS, QStringLiteral("XFS")},
- {FilesystemType::BTRFS, QStringLiteral("BTRFS")},
- {FilesystemType::NFS, QStringLiteral("NFS")},
- {FilesystemType::ZFS, QStringLiteral("ZFS")},
- {FilesystemType::APFS, QStringLiteral("APFS")},
- {FilesystemType::HFS, QStringLiteral("HFS")},
- {FilesystemType::HFSPLUS, QStringLiteral("HFSPLUS")},
- {FilesystemType::HFSX, QStringLiteral("HFSX")},
- {FilesystemType::FUSEBLK, QStringLiteral("FUSEBLK")},
- {FilesystemType::F2FS, QStringLiteral("F2FS")},
- {FilesystemType::UNKNOWN, QStringLiteral("UNKNOWN")}
-};
-
-
-/**
- * @brief Ordered Mapping of reported filesystem names to enum types
- * this mapping is non exsaustive, it just attempts to capture the many way these filesystems could be reported.
- * all keys are in uppercase, use `QString.toUpper()` or equivalent during lookup.
- *
- * QMap is ordered
- *
- */
-static const QMap s_filesystem_type_names_inverse = {
- {QStringLiteral("FAT"), FilesystemType::FAT},
- {QStringLiteral("NTFS"), FilesystemType::NTFS},
- {QStringLiteral("REFS"), FilesystemType::REFS},
- {QStringLiteral("EXT2_OLD"), FilesystemType::EXT_2_OLD},
- {QStringLiteral("EXT_2_OLD"), FilesystemType::EXT_2_OLD},
- {QStringLiteral("EXT2"), FilesystemType::EXT_2_3_4},
- {QStringLiteral("EXT3"), FilesystemType::EXT_2_3_4},
- {QStringLiteral("EXT4"), FilesystemType::EXT_2_3_4},
- {QStringLiteral("EXT2/3/4"), FilesystemType::EXT_2_3_4},
- {QStringLiteral("EXT_2_3_4"), FilesystemType::EXT_2_3_4},
- {QStringLiteral("EXT"), FilesystemType::EXT}, // must come after all other EXT variants to prevent greedy detection
- {QStringLiteral("XFS"), FilesystemType::XFS},
- {QStringLiteral("BTRFS"), FilesystemType::BTRFS},
- {QStringLiteral("NFS"), FilesystemType::NFS},
- {QStringLiteral("ZFS"), FilesystemType::ZFS},
- {QStringLiteral("APFS"), FilesystemType::APFS},
- {QStringLiteral("HFSPLUS"), FilesystemType::HFSPLUS},
- {QStringLiteral("HFSX"), FilesystemType::HFSX},
- {QStringLiteral("HFS"), FilesystemType::HFS},
- {QStringLiteral("FUSEBLK"), FilesystemType::FUSEBLK},
- {QStringLiteral("F2FS"), FilesystemType::F2FS},
- {QStringLiteral("UNKNOWN"), FilesystemType::UNKNOWN}
+static const QMap s_filesystem_type_names = {
+ {FilesystemType::FAT, { "FAT" }},
+ {FilesystemType::NTFS, { "NTFS" }},
+ {FilesystemType::REFS, { "REFS" }},
+ {FilesystemType::EXT_2_OLD, { "EXT_2_OLD", "EXT2_OLD" }},
+ {FilesystemType::EXT_2_3_4, { "EXT2/3/4", "EXT_2_3_4", "EXT2", "EXT3", "EXT4" }},
+ {FilesystemType::EXT, { "EXT" }},
+ {FilesystemType::XFS, { "XFS" }},
+ {FilesystemType::BTRFS, { "BTRFS" }},
+ {FilesystemType::NFS, { "NFS" }},
+ {FilesystemType::ZFS, { "ZFS" }},
+ {FilesystemType::APFS, { "APFS" }},
+ {FilesystemType::HFS, { "HFS" }},
+ {FilesystemType::HFSPLUS, { "HFSPLUS" }},
+ {FilesystemType::HFSX, { "HFSX" }},
+ {FilesystemType::FUSEBLK, { "FUSEBLK" }},
+ {FilesystemType::F2FS, { "F2FS" }},
+ {FilesystemType::UNKNOWN, { "UNKNOWN" }}
};
/**
From 197be9cfd0299edba57fe6fe1d7a7841ed0bb771 Mon Sep 17 00:00:00 2001
From: Rachel Powers <508861+Ryex@users.noreply.github.com>
Date: Mon, 3 Apr 2023 18:00:56 -0700
Subject: [PATCH 061/104] fix: remove fixed datastream version
Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com>
---
launcher/FileSystem.cpp | 3 +--
launcher/filelink/FileLink.cpp | 2 --
2 files changed, 1 insertion(+), 4 deletions(-)
diff --git a/launcher/FileSystem.cpp b/launcher/FileSystem.cpp
index 869fbe36..bd985c5b 100644
--- a/launcher/FileSystem.cpp
+++ b/launcher/FileSystem.cpp
@@ -446,7 +446,6 @@ void create_link::runPrivileged(const QString& offset)
// construct block of data to send
QByteArray block;
QDataStream out(&block, QIODevice::WriteOnly);
- out.setVersion(QDataStream::Qt_5_0); // choose correct version better?
qint32 blocksize = quint32(sizeof(quint32));
for (auto link : m_links_to_make) {
@@ -469,7 +468,7 @@ void create_link::runPrivileged(const QString& offset)
QDataStream in;
quint32 blockSize = 0;
in.setDevice(clientConnection);
- in.setVersion(QDataStream::Qt_5_0);
+
qDebug() << "Reading path results from client";
qDebug() << "bytes available" << clientConnection->bytesAvailable();
diff --git a/launcher/filelink/FileLink.cpp b/launcher/filelink/FileLink.cpp
index ded46061..c9599b82 100644
--- a/launcher/filelink/FileLink.cpp
+++ b/launcher/filelink/FileLink.cpp
@@ -120,7 +120,6 @@ void FileLinkApp::joinServer(QString server)
blockSize = 0;
in.setDevice(&socket);
- in.setVersion(QDataStream::Qt_5_0);
connect(&socket, &QLocalSocket::connected, this, [&]() { qDebug() << "connected to server"; });
@@ -203,7 +202,6 @@ void FileLinkApp::sendResults()
// construct block of data to send
QByteArray block;
QDataStream out(&block, QIODevice::WriteOnly);
- out.setVersion(QDataStream::Qt_5_0);
qint32 blocksize = quint32(sizeof(quint32));
for (auto result : m_path_results) {
From 41c5e523b294501ca96f0c283fe8e837f01e13a3 Mon Sep 17 00:00:00 2001
From: Rachel Powers <508861+Ryex@users.noreply.github.com>
Date: Mon, 3 Apr 2023 18:09:01 -0700
Subject: [PATCH 062/104] fix: add back QT::Widgets link
Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com>
---
launcher/CMakeLists.txt | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt
index 03d3fcbf..71e54c85 100644
--- a/launcher/CMakeLists.txt
+++ b/launcher/CMakeLists.txt
@@ -1125,7 +1125,7 @@ if(WIN32)
target_link_libraries(filelink_logic
# systeminfo
BuildConfig
- # Qt${QT_VERSION_MAJOR}::Widgets
+ Qt${QT_VERSION_MAJOR}::Widgets
ghcFilesystem::ghc_filesystem
)
target_link_libraries(filelink_logic
@@ -1133,7 +1133,7 @@ if(WIN32)
# Qt${QT_VERSION_MAJOR}::Xml
# Qt${QT_VERSION_MAJOR}::Network
# Qt${QT_VERSION_MAJOR}::Concurrent
- # ${Launcher_QT_LIBS}
+ ${Launcher_QT_LIBS}
)
add_executable("${Launcher_Name}_filelink" WIN32 filelink/main.cpp)
From de20258aa7b846e7c7e5c7ef5abf9613157cb4a0 Mon Sep 17 00:00:00 2001
From: Rachel Powers <508861+Ryex@users.noreply.github.com>
Date: Mon, 3 Apr 2023 18:30:28 -0700
Subject: [PATCH 063/104] fix: filelink needs network for local socket
Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com>
---
launcher/CMakeLists.txt | 4 +---
1 file changed, 1 insertion(+), 3 deletions(-)
diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt
index 71e54c85..4dbb9e6e 100644
--- a/launcher/CMakeLists.txt
+++ b/launcher/CMakeLists.txt
@@ -1123,15 +1123,13 @@ if(WIN32)
add_library(filelink_logic STATIC ${LINKEXE_SOURCES})
target_include_directories(filelink_logic PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(filelink_logic
- # systeminfo
BuildConfig
Qt${QT_VERSION_MAJOR}::Widgets
ghcFilesystem::ghc_filesystem
)
target_link_libraries(filelink_logic
Qt${QT_VERSION_MAJOR}::Core
- # Qt${QT_VERSION_MAJOR}::Xml
- # Qt${QT_VERSION_MAJOR}::Network
+ Qt${QT_VERSION_MAJOR}::Network
# Qt${QT_VERSION_MAJOR}::Concurrent
${Launcher_QT_LIBS}
)
From 0ce30495796627e7591587ca1e977be98178f225 Mon Sep 17 00:00:00 2001
From: Rachel Powers <508861+Ryex@users.noreply.github.com>
Date: Mon, 3 Apr 2023 18:48:28 -0700
Subject: [PATCH 064/104] fix: sysinfo libs needed too
Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com>
---
launcher/CMakeLists.txt | 5 ++---
1 file changed, 2 insertions(+), 3 deletions(-)
diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt
index 4dbb9e6e..4de0f73b 100644
--- a/launcher/CMakeLists.txt
+++ b/launcher/CMakeLists.txt
@@ -1123,11 +1123,10 @@ if(WIN32)
add_library(filelink_logic STATIC ${LINKEXE_SOURCES})
target_include_directories(filelink_logic PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(filelink_logic
+ systeminfo
BuildConfig
- Qt${QT_VERSION_MAJOR}::Widgets
ghcFilesystem::ghc_filesystem
- )
- target_link_libraries(filelink_logic
+ Qt${QT_VERSION_MAJOR}::Widgets
Qt${QT_VERSION_MAJOR}::Core
Qt${QT_VERSION_MAJOR}::Network
# Qt${QT_VERSION_MAJOR}::Concurrent
From 2321d9c065c363dac8304ab6c826289adb9c86a7 Mon Sep 17 00:00:00 2001
From: Rachel Powers <508861+Ryex@users.noreply.github.com>
Date: Fri, 7 Apr 2023 18:36:35 -0700
Subject: [PATCH 065/104] fix: canonical*File*Path()
Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com>
---
launcher/ui/dialogs/BlockedModsDialog.cpp | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/launcher/ui/dialogs/BlockedModsDialog.cpp b/launcher/ui/dialogs/BlockedModsDialog.cpp
index e7404f2d..f2ef734b 100644
--- a/launcher/ui/dialogs/BlockedModsDialog.cpp
+++ b/launcher/ui/dialogs/BlockedModsDialog.cpp
@@ -193,7 +193,7 @@ void BlockedModsDialog::setupWatch()
void BlockedModsDialog::watchPath(QString path, bool watch_subdirectories)
{
auto to_watch = QFileInfo(path);
- auto to_watch_path = to_watch.canonicalPath();
+ auto to_watch_path = to_watch.canonicalFilePath();
if (m_watcher.directories().contains(to_watch_path))
return; // don't watch the same path twice (no loops!)
From c56db0408bc7bb01e9807fd02858f27328ca6b79 Mon Sep 17 00:00:00 2001
From: Rachel Powers <508861+Ryex@users.noreply.github.com>
Date: Sat, 8 Apr 2023 07:26:56 -0700
Subject: [PATCH 066/104] fix: load setting state with page. don't translate
"..."
Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com>
---
launcher/ui/pages/global/LauncherPage.cpp | 1 +
launcher/ui/pages/global/LauncherPage.ui | 2 +-
2 files changed, 2 insertions(+), 1 deletion(-)
diff --git a/launcher/ui/pages/global/LauncherPage.cpp b/launcher/ui/pages/global/LauncherPage.cpp
index 51652320..816dde72 100644
--- a/launcher/ui/pages/global/LauncherPage.cpp
+++ b/launcher/ui/pages/global/LauncherPage.cpp
@@ -275,6 +275,7 @@ void LauncherPage::loadSettings()
ui->modsDirTextBox->setText(s->get("CentralModsDir").toString());
ui->iconsDirTextBox->setText(s->get("IconsDir").toString());
ui->downloadsDirTextBox->setText(s->get("DownloadsDir").toString());
+ ui->downloadsDirWatchRecursiveCheckBox->setChecked(s->get("DownloadsDirWatchRecursive").toBool());
QString sortMode = s->get("InstSortMode").toString();
diff --git a/launcher/ui/pages/global/LauncherPage.ui b/launcher/ui/pages/global/LauncherPage.ui
index 906efd1a..55bd3eea 100644
--- a/launcher/ui/pages/global/LauncherPage.ui
+++ b/launcher/ui/pages/global/LauncherPage.ui
@@ -99,7 +99,7 @@
-
- ...
+ ...
From a9881115073cc73e668f14b23851914ddfa9b4e7 Mon Sep 17 00:00:00 2001
From: Sefa Eyeoglu
Date: Sat, 8 Apr 2023 18:48:02 +0200
Subject: [PATCH 067/104] fix: do not apply system theme on launch
Closes PrismLauncher/PrismLauncher#490
Regression introduced by PrismLauncher/PrismLauncher#249
Signed-off-by: Sefa Eyeoglu
---
launcher/Application.cpp | 8 +++-----
launcher/Application.h | 2 +-
launcher/ui/themes/ITheme.cpp | 2 +-
launcher/ui/themes/ITheme.h | 2 +-
launcher/ui/themes/SystemTheme.cpp | 8 ++++++--
launcher/ui/themes/SystemTheme.h | 2 +-
launcher/ui/themes/ThemeManager.cpp | 8 ++++----
launcher/ui/themes/ThemeManager.h | 4 ++--
8 files changed, 19 insertions(+), 17 deletions(-)
diff --git a/launcher/Application.cpp b/launcher/Application.cpp
index 879af535..c33694e9 100644
--- a/launcher/Application.cpp
+++ b/launcher/Application.cpp
@@ -830,9 +830,7 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
}
});
- {
- applyCurrentlySelectedTheme();
- }
+ applyCurrentlySelectedTheme(true);
updateCapabilities();
@@ -1107,9 +1105,9 @@ QList Application::getValidApplicationThemes()
return m_themeManager->getValidApplicationThemes();
}
-void Application::applyCurrentlySelectedTheme()
+void Application::applyCurrentlySelectedTheme(bool initial)
{
- m_themeManager->applyCurrentlySelectedTheme();
+ m_themeManager->applyCurrentlySelectedTheme(initial);
}
void Application::setApplicationTheme(const QString& name)
diff --git a/launcher/Application.h b/launcher/Application.h
index 91c5fc63..0d24a4e9 100644
--- a/launcher/Application.h
+++ b/launcher/Application.h
@@ -120,7 +120,7 @@ public:
void setIconTheme(const QString& name);
- void applyCurrentlySelectedTheme();
+ void applyCurrentlySelectedTheme(bool initial = false);
QList getValidApplicationThemes();
diff --git a/launcher/ui/themes/ITheme.cpp b/launcher/ui/themes/ITheme.cpp
index 22043e44..8f0757e1 100644
--- a/launcher/ui/themes/ITheme.cpp
+++ b/launcher/ui/themes/ITheme.cpp
@@ -38,7 +38,7 @@
#include
#include "Application.h"
-void ITheme::apply()
+void ITheme::apply(bool)
{
APPLICATION->setStyleSheet(QString());
QApplication::setStyle(QStyleFactory::create(qtTheme()));
diff --git a/launcher/ui/themes/ITheme.h b/launcher/ui/themes/ITheme.h
index 2e5b7f25..a0a638bd 100644
--- a/launcher/ui/themes/ITheme.h
+++ b/launcher/ui/themes/ITheme.h
@@ -41,7 +41,7 @@ class QStyle;
class ITheme {
public:
virtual ~ITheme() {}
- virtual void apply();
+ virtual void apply(bool initial);
virtual QString id() = 0;
virtual QString name() = 0;
virtual bool hasStyleSheet() = 0;
diff --git a/launcher/ui/themes/SystemTheme.cpp b/launcher/ui/themes/SystemTheme.cpp
index 24875e33..a95bc875 100644
--- a/launcher/ui/themes/SystemTheme.cpp
+++ b/launcher/ui/themes/SystemTheme.cpp
@@ -60,9 +60,13 @@ SystemTheme::SystemTheme()
themeDebugLog() << "System theme not found, defaulted to Fusion";
}
-void SystemTheme::apply()
+void SystemTheme::apply(bool initial)
{
- ITheme::apply();
+ // See https://github.com/MultiMC/Launcher/issues/1790
+ // or https://github.com/PrismLauncher/PrismLauncher/issues/490
+ if (initial)
+ return;
+ ITheme::apply(initial);
}
QString SystemTheme::id()
diff --git a/launcher/ui/themes/SystemTheme.h b/launcher/ui/themes/SystemTheme.h
index b5c03def..05f31233 100644
--- a/launcher/ui/themes/SystemTheme.h
+++ b/launcher/ui/themes/SystemTheme.h
@@ -40,7 +40,7 @@ class SystemTheme : public ITheme {
public:
SystemTheme();
virtual ~SystemTheme() {}
- void apply() override;
+ void apply(bool initial) override;
QString id() override;
QString name() override;
diff --git a/launcher/ui/themes/ThemeManager.cpp b/launcher/ui/themes/ThemeManager.cpp
index 13406485..94ac8a24 100644
--- a/launcher/ui/themes/ThemeManager.cpp
+++ b/launcher/ui/themes/ThemeManager.cpp
@@ -116,22 +116,22 @@ void ThemeManager::setIconTheme(const QString& name)
QIcon::setThemeName(name);
}
-void ThemeManager::applyCurrentlySelectedTheme()
+void ThemeManager::applyCurrentlySelectedTheme(bool initial)
{
setIconTheme(APPLICATION->settings()->get("IconTheme").toString());
themeDebugLog() << "<> Icon theme set.";
- setApplicationTheme(APPLICATION->settings()->get("ApplicationTheme").toString());
+ setApplicationTheme(APPLICATION->settings()->get("ApplicationTheme").toString(), initial);
themeDebugLog() << "<> Application theme set.";
}
-void ThemeManager::setApplicationTheme(const QString& name)
+void ThemeManager::setApplicationTheme(const QString& name, bool initial)
{
auto systemPalette = qApp->palette();
auto themeIter = m_themes.find(name);
if (themeIter != m_themes.end()) {
auto& theme = themeIter->second;
themeDebugLog() << "applying theme" << theme->name();
- theme->apply();
+ theme->apply(initial);
} else {
themeWarningLog() << "Tried to set invalid theme:" << name;
}
diff --git a/launcher/ui/themes/ThemeManager.h b/launcher/ui/themes/ThemeManager.h
index 9af44b5a..87f36d9c 100644
--- a/launcher/ui/themes/ThemeManager.h
+++ b/launcher/ui/themes/ThemeManager.h
@@ -37,8 +37,8 @@ class ThemeManager {
QList getValidApplicationThemes();
void setIconTheme(const QString& name);
- void applyCurrentlySelectedTheme();
- void setApplicationTheme(const QString& name);
+ void applyCurrentlySelectedTheme(bool initial = false);
+ void setApplicationTheme(const QString& name, bool initial = false);
///
/// Returns the cat based on selected cat and with events (Birthday, XMas, etc.)
From 371c8395731152ba198849d03e491760256c3232 Mon Sep 17 00:00:00 2001
From: DioEgizio <83089242+DioEgizio@users.noreply.github.com>
Date: Sat, 8 Apr 2023 19:38:46 +0200
Subject: [PATCH 068/104] chore: update Qt to 6.5.0 on Windows
Signed-off-by: DioEgizio <83089242+DioEgizio@users.noreply.github.com>
---
.github/workflows/build.yml | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 43c71a76..4369d249 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -68,7 +68,7 @@ jobs:
qt_ver: 6
qt_host: windows
qt_arch: ''
- qt_version: '6.4.3'
+ qt_version: '6.5.0'
qt_modules: 'qt5compat qtimageformats'
qt_tools: ''
@@ -80,7 +80,7 @@ jobs:
qt_ver: 6
qt_host: windows
qt_arch: 'win64_msvc2019_arm64'
- qt_version: '6.4.3'
+ qt_version: '6.5.0'
qt_modules: 'qt5compat qtimageformats'
qt_tools: ''
From a02f67ed0e9716a9dc2540b1353e7f25fb056d4b Mon Sep 17 00:00:00 2001
From: Rachel Powers <508861+Ryex@users.noreply.github.com>
Date: Wed, 12 Apr 2023 11:30:02 -0700
Subject: [PATCH 069/104] refactor: rename watch_subdirectories ->
watch_recurisve (prevent confusion of behavior)
Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com>
---
launcher/ui/dialogs/BlockedModsDialog.cpp | 6 +++---
launcher/ui/dialogs/BlockedModsDialog.h | 2 +-
2 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/launcher/ui/dialogs/BlockedModsDialog.cpp b/launcher/ui/dialogs/BlockedModsDialog.cpp
index f2ef734b..ba453df6 100644
--- a/launcher/ui/dialogs/BlockedModsDialog.cpp
+++ b/launcher/ui/dialogs/BlockedModsDialog.cpp
@@ -190,7 +190,7 @@ void BlockedModsDialog::setupWatch()
watchPath(modsFolder, true);
}
-void BlockedModsDialog::watchPath(QString path, bool watch_subdirectories)
+void BlockedModsDialog::watchPath(QString path, bool watch_recursive)
{
auto to_watch = QFileInfo(path);
auto to_watch_path = to_watch.canonicalFilePath();
@@ -200,14 +200,14 @@ void BlockedModsDialog::watchPath(QString path, bool watch_subdirectories)
qDebug() << "[Blocked Mods Dialog] Adding Watch Path:" << path;
m_watcher.addPath(to_watch_path);
- if (!to_watch.isDir() || !watch_subdirectories)
+ if (!to_watch.isDir() || !watch_recursive)
return;
QDirIterator it(to_watch_path, QDir::Filter::Dirs | QDir::Filter::NoDotAndDotDot, QDirIterator::NoIteratorFlags);
while (it.hasNext()) {
QString watch_dir = QDir(it.next()).canonicalPath(); // resolve symlinks and relative paths
- watchPath(watch_dir, watch_subdirectories);
+ watchPath(watch_dir, watch_recursive);
}
}
diff --git a/launcher/ui/dialogs/BlockedModsDialog.h b/launcher/ui/dialogs/BlockedModsDialog.h
index 01077aa5..e3b7c975 100644
--- a/launcher/ui/dialogs/BlockedModsDialog.h
+++ b/launcher/ui/dialogs/BlockedModsDialog.h
@@ -79,7 +79,7 @@ class BlockedModsDialog : public QDialog {
void update();
void directoryChanged(QString path);
void setupWatch();
- void watchPath(QString path, bool watch_subdirectories = false);
+ void watchPath(QString path, bool watch_recursive = false);
void scanPaths();
void scanPath(QString path, bool start_task);
void addHashTask(QString path);
From 51095c5a2722ed693c3ac3e2ec597601d00f491e Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Fri, 14 Apr 2023 06:31:06 +0000
Subject: [PATCH 070/104] chore(deps): update hendrikmuhs/ccache-action action
to v1.2.9
---
.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 4369d249..663bfc5f 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -152,7 +152,7 @@ jobs:
- name: Setup ccache
if: (runner.os != 'Windows' || matrix.msystem == '') && inputs.build_type == 'Debug'
- uses: hendrikmuhs/ccache-action@v1.2.8
+ uses: hendrikmuhs/ccache-action@v1.2.9
with:
key: ${{ matrix.os }}-qt${{ matrix.qt_ver }}-${{ matrix.architecture }}
From 12f0d51c0cd03d660425566264b502736b104310 Mon Sep 17 00:00:00 2001
From: Rachel Powers <508861+Ryex@users.noreply.github.com>
Date: Mon, 17 Apr 2023 17:51:34 -0700
Subject: [PATCH 071/104] Fix: signal/slot macro -> func pointer & network
fixes - convert qt connect calls to use function pointers instead of the
signal/slot macros wherever practical (UI classes were mostly left alone,
target was tasks and processes) - give signals an explicit receivers to use
the static method over the instance method wherever practical - ensure
networks tasks are using the `errorOccured` signal added in Qt5.15 over the
deprecated `error` signal - ensure all networks tasks have an sslErrors
signal connected - add seemingly missing `MinecraftAccount::authSucceeded`
connection for `MSAInteractive` login flow
Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com>
---
launcher/JavaCommon.cpp | 6 ++--
launcher/LoggedProcess.cpp | 8 ++---
launcher/icons/IconList.cpp | 5 ++-
launcher/java/JavaChecker.cpp | 14 ++++-----
launcher/java/JavaCheckerJob.cpp | 2 +-
launcher/launch/steps/Update.cpp | 6 ++--
launcher/minecraft/WorldList.cpp | 3 +-
launcher/minecraft/auth/AuthRequest.cpp | 22 ++++++-------
launcher/minecraft/auth/MinecraftAccount.cpp | 16 +++++-----
launcher/minecraft/services/CapeChange.cpp | 33 +++++++++++++++-----
launcher/minecraft/services/CapeChange.h | 1 +
launcher/minecraft/services/SkinDelete.cpp | 22 ++++++++++---
launcher/minecraft/services/SkinDelete.h | 1 +
launcher/minecraft/services/SkinUpload.cpp | 22 ++++++++++---
launcher/minecraft/services/SkinUpload.h | 1 +
launcher/net/Download.cpp | 6 ++--
launcher/net/Download.h | 2 +-
launcher/net/HttpMetaCache.cpp | 2 +-
launcher/net/NetAction.h | 11 +++++++
launcher/net/Upload.cpp | 10 ++++--
launcher/net/Upload.h | 2 +-
launcher/screenshots/ImgurAlbumCreation.cpp | 10 ++++--
launcher/screenshots/ImgurUpload.cpp | 10 ++++--
launcher/settings/SettingsObject.cpp | 9 +++---
launcher/tools/JProfiler.cpp | 4 +--
launcher/tools/JVisualVM.cpp | 4 +--
launcher/ui/pages/instance/VersionPage.cpp | 2 +-
27 files changed, 148 insertions(+), 86 deletions(-)
diff --git a/launcher/JavaCommon.cpp b/launcher/JavaCommon.cpp
index 52cc868a..e29e2270 100644
--- a/launcher/JavaCommon.cpp
+++ b/launcher/JavaCommon.cpp
@@ -122,8 +122,7 @@ void JavaCommon::TestCheck::run()
return;
}
checker.reset(new JavaChecker());
- connect(checker.get(), SIGNAL(checkFinished(JavaCheckResult)), this,
- SLOT(checkFinished(JavaCheckResult)));
+ connect(checker.get(), &JavaChecker::checkFinished, this, &JavaCommon::TestCheck::checkFinished);
checker->m_path = m_path;
checker->performCheck();
}
@@ -137,8 +136,7 @@ void JavaCommon::TestCheck::checkFinished(JavaCheckResult result)
return;
}
checker.reset(new JavaChecker());
- connect(checker.get(), SIGNAL(checkFinished(JavaCheckResult)), this,
- SLOT(checkFinishedWithArgs(JavaCheckResult)));
+ connect(checker.get(), &JavaChecker::checkFinished, this, &JavaCommon::TestCheck::checkFinishedWithArgs);
checker->m_path = m_path;
checker->m_args = m_args;
checker->m_minMem = m_minMem;
diff --git a/launcher/LoggedProcess.cpp b/launcher/LoggedProcess.cpp
index c8d5c34e..763a9b5c 100644
--- a/launcher/LoggedProcess.cpp
+++ b/launcher/LoggedProcess.cpp
@@ -44,11 +44,11 @@ LoggedProcess::LoggedProcess(QObject *parent) : QProcess(parent)
// QProcess has a strange interface... let's map a lot of those into a few.
connect(this, &QProcess::readyReadStandardOutput, this, &LoggedProcess::on_stdOut);
connect(this, &QProcess::readyReadStandardError, this, &LoggedProcess::on_stdErr);
- connect(this, SIGNAL(finished(int,QProcess::ExitStatus)), SLOT(on_exit(int,QProcess::ExitStatus)));
-#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
- connect(this, SIGNAL(errorOccurred(QProcess::ProcessError)), this, SLOT(on_error(QProcess::ProcessError)));
+ connect(this, QOverload::of(&QProcess::finished), this, &LoggedProcess::on_exit);
+#if QT_VERSION >= QT_VERSION_CHECK(5, 6, 0) // &QProcess::errorOccurred added in 5.6
+ connect(this, &QProcess::errorOccurred, this, &LoggedProcess::on_error);
#else
- connect(this, SIGNAL(error(QProcess::ProcessError)), this, SLOT(on_error(QProcess::ProcessError)));
+ connect(this, QOverload::of(&QProcess::error), this, &LoggedProcess::on_error);
#endif
connect(this, &QProcess::stateChanged, this, &LoggedProcess::on_stateChange);
}
diff --git a/launcher/icons/IconList.cpp b/launcher/icons/IconList.cpp
index 1dfc6432..13174f6e 100644
--- a/launcher/icons/IconList.cpp
+++ b/launcher/icons/IconList.cpp
@@ -66,9 +66,8 @@ IconList::IconList(const QStringList &builtinPaths, QString path, QObject *paren
m_watcher.reset(new QFileSystemWatcher());
is_watching = false;
- connect(m_watcher.get(), SIGNAL(directoryChanged(QString)),
- SLOT(directoryChanged(QString)));
- connect(m_watcher.get(), SIGNAL(fileChanged(QString)), SLOT(fileChanged(QString)));
+ connect(m_watcher.get(), &QFileSystemWatcher::directoryChanged, this, &IconList::directoryChanged);
+ connect(m_watcher.get(), &QFileSystemWatcher::fileChanged, this, &IconList::fileChanged);
directoryChanged(path);
diff --git a/launcher/java/JavaChecker.cpp b/launcher/java/JavaChecker.cpp
index 041583d1..922580ce 100644
--- a/launcher/java/JavaChecker.cpp
+++ b/launcher/java/JavaChecker.cpp
@@ -87,15 +87,15 @@ void JavaChecker::performCheck()
process->setProcessEnvironment(CleanEnviroment());
qDebug() << "Running java checker: " + m_path + args.join(" ");;
- connect(process.get(), SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(finished(int, QProcess::ExitStatus)));
-#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
- connect(process.get(), SIGNAL(errorOccurred(QProcess::ProcessError)), this, SLOT(error(QProcess::ProcessError)));
+ connect(process.get(), QOverload::of(&QProcess::finished), this, &JavaChecker::finished);
+#if QT_VERSION >= QT_VERSION_CHECK(5, 6, 0) // &QProcess::errorOccurred added in 5.6
+ connect(process.get(), &QProcess::errorOccurred, this, &JavaChecker::error);
#else
- connect(process.get(), SIGNAL(error(QProcess::ProcessError)), this, SLOT(error(QProcess::ProcessError)));
+ connect(process.get(), &QProcess::error, this, &JavaChecker::error);
#endif
- connect(process.get(), SIGNAL(readyReadStandardOutput()), this, SLOT(stdoutReady()));
- connect(process.get(), SIGNAL(readyReadStandardError()), this, SLOT(stderrReady()));
- connect(&killTimer, SIGNAL(timeout()), SLOT(timeout()));
+ connect(process.get(), &QProcess::readyReadStandardOutput, this, &JavaChecker::stdoutReady);
+ connect(process.get(), &QProcess::readyReadStandardError, this, &JavaChecker::stderrReady);
+ connect(&killTimer, &QTimer::timeout, this, &JavaChecker::timeout);
killTimer.setSingleShot(true);
killTimer.start(15000);
process->start();
diff --git a/launcher/java/JavaCheckerJob.cpp b/launcher/java/JavaCheckerJob.cpp
index 67d70066..48274974 100644
--- a/launcher/java/JavaCheckerJob.cpp
+++ b/launcher/java/JavaCheckerJob.cpp
@@ -38,7 +38,7 @@ void JavaCheckerJob::executeTask()
for (auto iter : javacheckers)
{
javaresults.append(JavaCheckResult());
- connect(iter.get(), SIGNAL(checkFinished(JavaCheckResult)), SLOT(partFinished(JavaCheckResult)));
+ connect(iter.get(), &JavaChecker::checkFinished, this, &JavaCheckerJob::partFinished);
iter->performCheck();
}
}
diff --git a/launcher/launch/steps/Update.cpp b/launcher/launch/steps/Update.cpp
index 28bd153d..b67316b0 100644
--- a/launcher/launch/steps/Update.cpp
+++ b/launcher/launch/steps/Update.cpp
@@ -26,9 +26,9 @@ void Update::executeTask()
m_updateTask.reset(m_parent->instance()->createUpdateTask(m_mode));
if(m_updateTask)
{
- connect(m_updateTask.get(), SIGNAL(finished()), this, SLOT(updateFinished()));
- connect(m_updateTask.get(), &Task::progress, this, &Task::setProgress);
- connect(m_updateTask.get(), &Task::status, this, &Task::setStatus);
+ connect(m_updateTask.get(), &Task::finished, this, &Update::updateFinished);
+ connect(m_updateTask.get(), &Task::progress, this, &Update::setProgress);
+ connect(m_updateTask.get(), &Task::status, this, &Update::setStatus);
emit progressReportingRequest();
return;
}
diff --git a/launcher/minecraft/WorldList.cpp b/launcher/minecraft/WorldList.cpp
index ae29a972..de21c474 100644
--- a/launcher/minecraft/WorldList.cpp
+++ b/launcher/minecraft/WorldList.cpp
@@ -53,8 +53,7 @@ WorldList::WorldList(const QString &dir)
m_dir.setSorting(QDir::Name | QDir::IgnoreCase | QDir::LocaleAware);
m_watcher = new QFileSystemWatcher(this);
is_watching = false;
- connect(m_watcher, SIGNAL(directoryChanged(QString)), this,
- SLOT(directoryChanged(QString)));
+ connect(m_watcher, &QFileSystemWatcher::directoryChanged, this, &WorldList::directoryChanged);
}
void WorldList::startWatching()
diff --git a/launcher/minecraft/auth/AuthRequest.cpp b/launcher/minecraft/auth/AuthRequest.cpp
index bb82e1e2..a21634b7 100644
--- a/launcher/minecraft/auth/AuthRequest.cpp
+++ b/launcher/minecraft/auth/AuthRequest.cpp
@@ -55,12 +55,12 @@ void AuthRequest::get(const QNetworkRequest &req, int timeout/* = 60*1000*/) {
reply_ = APPLICATION->network()->get(request_);
status_ = Requesting;
timedReplies_.add(new Katabasis::Reply(reply_, timeout));
-#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
- connect(reply_, SIGNAL(errorOccurred(QNetworkReply::NetworkError)), this, SLOT(onRequestError(QNetworkReply::NetworkError)));
-#else
- connect(reply_, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(onRequestError(QNetworkReply::NetworkError)));
+#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) // QNetworkReply::errorOccurred added in 5.15
+ connect(reply_, &QNetworkReply::errorOccurred, this, &AuthRequest::onRequestError);
+#else // &QNetworkReply::error SIGNAL depricated
+ connect(reply_, QOverload::of(&QNetworkReply::error), this, &AuthRequest::onRequestError);
#endif
- connect(reply_, SIGNAL(finished()), this, SLOT(onRequestFinished()));
+ connect(reply_, &QNetworkReply::finished, this, &AuthRequest::onRequestFinished);
connect(reply_, &QNetworkReply::sslErrors, this, &AuthRequest::onSslErrors);
}
@@ -70,14 +70,14 @@ void AuthRequest::post(const QNetworkRequest &req, const QByteArray &data, int t
status_ = Requesting;
reply_ = APPLICATION->network()->post(request_, data_);
timedReplies_.add(new Katabasis::Reply(reply_, timeout));
-#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
- connect(reply_, SIGNAL(errorOccurred(QNetworkReply::NetworkError)), this, SLOT(onRequestError(QNetworkReply::NetworkError)));
-#else
- connect(reply_, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(onRequestError(QNetworkReply::NetworkError)));
+#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) // QNetworkReply::errorOccurred added in 5.15
+ connect(reply_, &QNetworkReply::errorOccurred, this, &AuthRequest::onRequestError);
+#else // &QNetworkReply::error SIGNAL depricated
+ connect(reply_, QOverload::of(&QNetworkReply::error), this, &AuthRequest::onRequestError);
#endif
- connect(reply_, SIGNAL(finished()), this, SLOT(onRequestFinished()));
+ connect(reply_, &QNetworkReply::finished, this, &AuthRequest::onRequestFinished);
connect(reply_, &QNetworkReply::sslErrors, this, &AuthRequest::onSslErrors);
- connect(reply_, SIGNAL(uploadProgress(qint64,qint64)), this, SLOT(onUploadProgress(qint64,qint64)));
+ connect(reply_, &QNetworkReply::uploadProgress, this, &AuthRequest::onUploadProgress);
}
void AuthRequest::onRequestFinished() {
diff --git a/launcher/minecraft/auth/MinecraftAccount.cpp b/launcher/minecraft/auth/MinecraftAccount.cpp
index 48cf5d42..3b050ac0 100644
--- a/launcher/minecraft/auth/MinecraftAccount.cpp
+++ b/launcher/minecraft/auth/MinecraftAccount.cpp
@@ -133,8 +133,8 @@ shared_qobject_ptr MinecraftAccount::login(QString password) {
Q_ASSERT(m_currentTask.get() == nullptr);
m_currentTask.reset(new MojangLogin(&data, password));
- connect(m_currentTask.get(), SIGNAL(succeeded()), SLOT(authSucceeded()));
- connect(m_currentTask.get(), SIGNAL(failed(QString)), SLOT(authFailed(QString)));
+ connect(m_currentTask.get(), &Task::succeeded, this, &MinecraftAccount::authSucceeded);
+ connect(m_currentTask.get(), &Task::failed, this, &MinecraftAccount::authFailed);
connect(m_currentTask.get(), &Task::aborted, this, [this]{ authFailed(tr("Aborted")); });
emit activityChanged(true);
return m_currentTask;
@@ -144,8 +144,8 @@ shared_qobject_ptr MinecraftAccount::loginMSA() {
Q_ASSERT(m_currentTask.get() == nullptr);
m_currentTask.reset(new MSAInteractive(&data));
- connect(m_currentTask.get(), SIGNAL(succeeded()), SLOT(authSucceeded()));
- connect(m_currentTask.get(), SIGNAL(failed(QString)), SLOT(authFailed(QString)));
+ connect(m_currentTask.get(), &Task::succeeded, this, &MinecraftAccount::authSucceeded);
+ connect(m_currentTask.get(), &Task::failed, this, &MinecraftAccount::authFailed);
connect(m_currentTask.get(), &Task::aborted, this, [this]{ authFailed(tr("Aborted")); });
emit activityChanged(true);
return m_currentTask;
@@ -155,8 +155,8 @@ shared_qobject_ptr MinecraftAccount::loginOffline() {
Q_ASSERT(m_currentTask.get() == nullptr);
m_currentTask.reset(new OfflineLogin(&data));
- connect(m_currentTask.get(), SIGNAL(succeeded()), SLOT(authSucceeded()));
- connect(m_currentTask.get(), SIGNAL(failed(QString)), SLOT(authFailed(QString)));
+ connect(m_currentTask.get(), &Task::succeeded, this, &MinecraftAccount::authSucceeded);
+ connect(m_currentTask.get(), &Task::failed, this, &MinecraftAccount::authFailed);
connect(m_currentTask.get(), &Task::aborted, this, [this]{ authFailed(tr("Aborted")); });
emit activityChanged(true);
return m_currentTask;
@@ -177,8 +177,8 @@ shared_qobject_ptr MinecraftAccount::refresh() {
m_currentTask.reset(new MojangRefresh(&data));
}
- connect(m_currentTask.get(), SIGNAL(succeeded()), SLOT(authSucceeded()));
- connect(m_currentTask.get(), SIGNAL(failed(QString)), SLOT(authFailed(QString)));
+ connect(m_currentTask.get(), &Task::succeeded, this, &MinecraftAccount::authSucceeded);
+ connect(m_currentTask.get(), &Task::failed, this, &MinecraftAccount::authFailed);
connect(m_currentTask.get(), &Task::aborted, this, [this]{ authFailed(tr("Aborted")); });
emit activityChanged(true);
return m_currentTask;
diff --git a/launcher/minecraft/services/CapeChange.cpp b/launcher/minecraft/services/CapeChange.cpp
index c73a11b6..1d5ea36d 100644
--- a/launcher/minecraft/services/CapeChange.cpp
+++ b/launcher/minecraft/services/CapeChange.cpp
@@ -54,9 +54,14 @@ void CapeChange::setCape(QString& cape) {
setStatus(tr("Equipping cape"));
m_reply = shared_qobject_ptr(rep);
- connect(rep, &QNetworkReply::uploadProgress, this, &Task::setProgress);
- connect(rep, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(downloadError(QNetworkReply::NetworkError)));
- connect(rep, SIGNAL(finished()), this, SLOT(downloadFinished()));
+ connect(rep, &QNetworkReply::uploadProgress, this, &CapeChange::setProgress);
+#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) // QNetworkReply::errorOccurred added in 5.15
+ connect(rep, &QNetworkReply::errorOccurred, this, &CapeChange::downloadError);
+#else
+ connect(rep, QOverload::of(&QNetworkReply::error), this, &CapeChange::downloadError);
+#endif
+ connect(rep, &QNetworkReply::sslErrors, this, &CapeChange::sslErrors);
+ connect(rep, &QNetworkReply::finished, this, &CapeChange::downloadFinished);
}
void CapeChange::clearCape() {
@@ -68,13 +73,14 @@ void CapeChange::clearCape() {
setStatus(tr("Removing cape"));
m_reply = shared_qobject_ptr(rep);
- connect(rep, &QNetworkReply::uploadProgress, this, &Task::setProgress);
-#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
- connect(rep, SIGNAL(errorOccurred(QNetworkReply::NetworkError)), this, SLOT(downloadError(QNetworkReply::NetworkError)));
+ connect(rep, &QNetworkReply::uploadProgress, this, &CapeChange::setProgress);
+#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) // QNetworkReply::errorOccurred added in 5.15
+ connect(rep, &QNetworkReply::errorOccurred, this, &CapeChange::downloadError);
#else
- connect(rep, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(downloadError(QNetworkReply::NetworkError)));
+ connect(rep, QOverload::of(&QNetworkReply::error), this, &CapeChange::downloadError);
#endif
- connect(rep, SIGNAL(finished()), this, SLOT(downloadFinished()));
+ connect(rep, &QNetworkReply::sslErrors, this, &CapeChange::sslErrors);
+ connect(rep, &QNetworkReply::finished, this, &CapeChange::downloadFinished);
}
@@ -95,6 +101,17 @@ void CapeChange::downloadError(QNetworkReply::NetworkError error)
emitFailed(m_reply->errorString());
}
+void CapeChange::sslErrors(const QList& errors)
+{
+ int i = 1;
+ for (auto error : errors) {
+ qCritical() << "Cape change SSL Error #" << i << " : " << error.errorString();
+ auto cert = error.certificate();
+ qCritical() << "Certificate in question:\n" << cert.toText();
+ i++;
+ }
+}
+
void CapeChange::downloadFinished()
{
// if the download failed
diff --git a/launcher/minecraft/services/CapeChange.h b/launcher/minecraft/services/CapeChange.h
index 185d69b6..38069f90 100644
--- a/launcher/minecraft/services/CapeChange.h
+++ b/launcher/minecraft/services/CapeChange.h
@@ -27,6 +27,7 @@ protected:
public slots:
void downloadError(QNetworkReply::NetworkError);
+ void sslErrors(const QList& errors);
void downloadFinished();
};
diff --git a/launcher/minecraft/services/SkinDelete.cpp b/launcher/minecraft/services/SkinDelete.cpp
index 921bd094..fbaaeacb 100644
--- a/launcher/minecraft/services/SkinDelete.cpp
+++ b/launcher/minecraft/services/SkinDelete.cpp
@@ -53,13 +53,14 @@ void SkinDelete::executeTask()
m_reply = shared_qobject_ptr(rep);
setStatus(tr("Deleting skin"));
- connect(rep, &QNetworkReply::uploadProgress, this, &Task::setProgress);
-#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
- connect(rep, SIGNAL(errorOccurred(QNetworkReply::NetworkError)), this, SLOT(downloadError(QNetworkReply::NetworkError)));
+ connect(rep, &QNetworkReply::uploadProgress, this, &SkinDelete::setProgress);
+#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) // QNetworkReply::errorOccurred added in 5.15
+ connect(rep, &QNetworkReply::errorOccurred, this, &SkinDelete::downloadError);
#else
- connect(rep, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(downloadError(QNetworkReply::NetworkError)));
+ connect(rep, QOverload::of(&QNetworkReply::error), this, &SkinDelete::downloadError);
#endif
- connect(rep, SIGNAL(finished()), this, SLOT(downloadFinished()));
+ connect(rep, &QNetworkReply::sslErrors, this, &SkinDelete::sslErrors);
+ connect(rep, &QNetworkReply::finished, this, &SkinDelete::downloadFinished);
}
void SkinDelete::downloadError(QNetworkReply::NetworkError error)
@@ -69,6 +70,17 @@ void SkinDelete::downloadError(QNetworkReply::NetworkError error)
emitFailed(m_reply->errorString());
}
+void SkinDelete::sslErrors(const QList& errors)
+{
+ int i = 1;
+ for (auto error : errors) {
+ qCritical() << "Skin Delete SSL Error #" << i << " : " << error.errorString();
+ auto cert = error.certificate();
+ qCritical() << "Certificate in question:\n" << cert.toText();
+ i++;
+ }
+}
+
void SkinDelete::downloadFinished()
{
// if the download failed
diff --git a/launcher/minecraft/services/SkinDelete.h b/launcher/minecraft/services/SkinDelete.h
index 83a84685..b9a1c9d3 100644
--- a/launcher/minecraft/services/SkinDelete.h
+++ b/launcher/minecraft/services/SkinDelete.h
@@ -22,5 +22,6 @@ protected:
public slots:
void downloadError(QNetworkReply::NetworkError);
+ void sslErrors(const QList& errors);
void downloadFinished();
};
diff --git a/launcher/minecraft/services/SkinUpload.cpp b/launcher/minecraft/services/SkinUpload.cpp
index c7987875..711f8739 100644
--- a/launcher/minecraft/services/SkinUpload.cpp
+++ b/launcher/minecraft/services/SkinUpload.cpp
@@ -78,13 +78,14 @@ void SkinUpload::executeTask()
m_reply = shared_qobject_ptr(rep);
setStatus(tr("Uploading skin"));
- connect(rep, &QNetworkReply::uploadProgress, this, &Task::setProgress);
-#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
- connect(rep, SIGNAL(errorOccurred(QNetworkReply::NetworkError)), this, SLOT(downloadError(QNetworkReply::NetworkError)));
+ connect(rep, &QNetworkReply::uploadProgress, this, &SkinUpload::setProgress);
+#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) // QNetworkReply::errorOccurred added in 5.15
+ connect(rep, &QNetworkReply::errorOccurred, this, &SkinUpload::downloadError);
#else
- connect(rep, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(downloadError(QNetworkReply::NetworkError)));
+ connect(rep, QOverload::of(&QNetworkReply::error), this, &SkinUpload::downloadError);
#endif
- connect(rep, SIGNAL(finished()), this, SLOT(downloadFinished()));
+ connect(rep, &QNetworkReply::sslErrors, this, &SkinUpload::sslErrors);
+ connect(rep, &QNetworkReply::finished, this, &SkinUpload::downloadFinished);
}
void SkinUpload::downloadError(QNetworkReply::NetworkError error)
@@ -94,6 +95,17 @@ void SkinUpload::downloadError(QNetworkReply::NetworkError error)
emitFailed(m_reply->errorString());
}
+void SkinUpload::sslErrors(const QList& errors)
+{
+ int i = 1;
+ for (auto error : errors) {
+ qCritical() << "Skin Upload SSL Error #" << i << " : " << error.errorString();
+ auto cert = error.certificate();
+ qCritical() << "Certificate in question:\n" << cert.toText();
+ i++;
+ }
+}
+
void SkinUpload::downloadFinished()
{
// if the download failed
diff --git a/launcher/minecraft/services/SkinUpload.h b/launcher/minecraft/services/SkinUpload.h
index 2c1f0a2e..ac8c5b36 100644
--- a/launcher/minecraft/services/SkinUpload.h
+++ b/launcher/minecraft/services/SkinUpload.h
@@ -32,6 +32,7 @@ protected:
public slots:
void downloadError(QNetworkReply::NetworkError);
+ void sslErrors(const QList& errors);
void downloadFinished();
};
diff --git a/launcher/net/Download.cpp b/launcher/net/Download.cpp
index e8a1d0b0..30c1953f 100644
--- a/launcher/net/Download.cpp
+++ b/launcher/net/Download.cpp
@@ -129,10 +129,10 @@ void Download::executeTask()
m_reply.reset(rep);
connect(rep, &QNetworkReply::downloadProgress, this, &Download::downloadProgress);
connect(rep, &QNetworkReply::finished, this, &Download::downloadFinished);
-#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
- connect(rep, SIGNAL(errorOccurred(QNetworkReply::NetworkError)), SLOT(downloadError(QNetworkReply::NetworkError)));
+#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) // QNetworkReply::errorOccurred added in 5.15
+ connect(rep, &QNetworkReply::errorOccurred, this, &Download::downloadError);
#else
- connect(rep, SIGNAL(error(QNetworkReply::NetworkError)), SLOT(downloadError(QNetworkReply::NetworkError)));
+ connect(rep, QOverload::of(&QNetworkReply::error), this, &Download::downloadError);
#endif
connect(rep, &QNetworkReply::sslErrors, this, &Download::sslErrors);
connect(rep, &QNetworkReply::readyRead, this, &Download::downloadReadyRead);
diff --git a/launcher/net/Download.h b/launcher/net/Download.h
index 7e1df322..01ec46db 100644
--- a/launcher/net/Download.h
+++ b/launcher/net/Download.h
@@ -70,7 +70,7 @@ class Download : public NetAction {
protected slots:
void downloadProgress(qint64 bytesReceived, qint64 bytesTotal) override;
void downloadError(QNetworkReply::NetworkError error) override;
- void sslErrors(const QList& errors);
+ void sslErrors(const QList& errors) override;
void downloadFinished() override;
void downloadReadyRead() override;
diff --git a/launcher/net/HttpMetaCache.cpp b/launcher/net/HttpMetaCache.cpp
index 0d7ca769..0ec82251 100644
--- a/launcher/net/HttpMetaCache.cpp
+++ b/launcher/net/HttpMetaCache.cpp
@@ -55,7 +55,7 @@ HttpMetaCache::HttpMetaCache(QString path) : QObject(), m_index_file(path)
saveBatchingTimer.setSingleShot(true);
saveBatchingTimer.setTimerType(Qt::VeryCoarseTimer);
- connect(&saveBatchingTimer, SIGNAL(timeout()), SLOT(SaveNow()));
+ connect(&saveBatchingTimer, &QTimer::timeout, this, &HttpMetaCache::SaveNow);
}
HttpMetaCache::~HttpMetaCache()
diff --git a/launcher/net/NetAction.h b/launcher/net/NetAction.h
index 38fe058b..1ff8f601 100644
--- a/launcher/net/NetAction.h
+++ b/launcher/net/NetAction.h
@@ -61,6 +61,17 @@ class NetAction : public Task {
virtual void downloadFinished() = 0;
virtual void downloadReadyRead() = 0;
+ virtual void sslErrors(const QList& errors) {
+ int i = 1;
+ for (auto error : errors) {
+ qCritical() << "Network SSL Error #" << i << " : " << error.errorString();
+ auto cert = error.certificate();
+ qCritical() << "Certificate in question:\n" << cert.toText();
+ i++;
+ }
+
+ };
+
public slots:
void startAction(shared_qobject_ptr network)
{
diff --git a/launcher/net/Upload.cpp b/launcher/net/Upload.cpp
index ccf43c2d..f3cdb786 100644
--- a/launcher/net/Upload.cpp
+++ b/launcher/net/Upload.cpp
@@ -232,9 +232,13 @@ namespace Net {
QNetworkReply* rep = m_network->post(request, m_post_data);
m_reply.reset(rep);
- connect(rep, SIGNAL(downloadProgress(qint64, qint64)), SLOT(downloadProgress(qint64, qint64)));
- connect(rep, SIGNAL(finished()), SLOT(downloadFinished()));
- connect(rep, SIGNAL(error(QNetworkReply::NetworkError)), SLOT(downloadError(QNetworkReply::NetworkError)));
+ connect(rep, &QNetworkReply::downloadProgress, this, &Upload::downloadProgress);
+ connect(rep, &QNetworkReply::finished, this, &Upload::downloadFinished);
+#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) // QNetworkReply::errorOccurred added in 5.15
+ connect(rep, &QNetworkReply::errorOccurred, this, &Upload::downloadError);
+#else
+ connect(rep, QOverload::of(&QNetworkReply::error), this, &Upload::downloadError);
+#endif
connect(rep, &QNetworkReply::sslErrors, this, &Upload::sslErrors);
connect(rep, &QNetworkReply::readyRead, this, &Upload::downloadReadyRead);
}
diff --git a/launcher/net/Upload.h b/launcher/net/Upload.h
index 5a0b2e74..f58b746a 100644
--- a/launcher/net/Upload.h
+++ b/launcher/net/Upload.h
@@ -54,7 +54,7 @@ namespace Net {
protected slots:
void downloadProgress(qint64 bytesReceived, qint64 bytesTotal) override;
void downloadError(QNetworkReply::NetworkError error) override;
- void sslErrors(const QList & errors);
+ void sslErrors(const QList & errors) override;
void downloadFinished() override;
void downloadReadyRead() override;
diff --git a/launcher/screenshots/ImgurAlbumCreation.cpp b/launcher/screenshots/ImgurAlbumCreation.cpp
index a72c32d3..ab425f1a 100644
--- a/launcher/screenshots/ImgurAlbumCreation.cpp
+++ b/launcher/screenshots/ImgurAlbumCreation.cpp
@@ -74,17 +74,20 @@ void ImgurAlbumCreation::executeTask()
m_reply.reset(rep);
connect(rep, &QNetworkReply::uploadProgress, this, &ImgurAlbumCreation::downloadProgress);
connect(rep, &QNetworkReply::finished, this, &ImgurAlbumCreation::downloadFinished);
-#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
- connect(rep, SIGNAL(errorOccurred(QNetworkReply::NetworkError)), SLOT(downloadError(QNetworkReply::NetworkError)));
+#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) // QNetworkReply::errorOccurred added in 5.15
+ connect(rep, &QNetworkReply::errorOccurred, this, &ImgurAlbumCreation::downloadError);
#else
- connect(rep, SIGNAL(error(QNetworkReply::NetworkError)), SLOT(downloadError(QNetworkReply::NetworkError)));
+ connect(rep, QOverload::of(&QNetworkReply::error), this, &ImgurAlbumCreation::downloadError);
#endif
+ connect(rep, &QNetworkReply::sslErrors, this, &ImgurAlbumCreation::sslErrors);
}
+
void ImgurAlbumCreation::downloadError(QNetworkReply::NetworkError error)
{
qDebug() << m_reply->errorString();
m_state = State::Failed;
}
+
void ImgurAlbumCreation::downloadFinished()
{
if (m_state != State::Failed)
@@ -120,6 +123,7 @@ void ImgurAlbumCreation::downloadFinished()
return;
}
}
+
void ImgurAlbumCreation::downloadProgress(qint64 bytesReceived, qint64 bytesTotal)
{
setProgress(bytesReceived, bytesTotal);
diff --git a/launcher/screenshots/ImgurUpload.cpp b/launcher/screenshots/ImgurUpload.cpp
index f8ac9bc2..a50f9afa 100644
--- a/launcher/screenshots/ImgurUpload.cpp
+++ b/launcher/screenshots/ImgurUpload.cpp
@@ -89,12 +89,14 @@ void ImgurUpload::executeTask()
m_reply.reset(rep);
connect(rep, &QNetworkReply::uploadProgress, this, &ImgurUpload::downloadProgress);
connect(rep, &QNetworkReply::finished, this, &ImgurUpload::downloadFinished);
-#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
- connect(rep, SIGNAL(errorOccurred(QNetworkReply::NetworkError)), SLOT(downloadError(QNetworkReply::NetworkError)));
+#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) // QNetworkReply::errorOccurred added in 5.15
+ connect(rep, &QNetworkReply::errorOccurred, this, &ImgurUpload::downloadError);
#else
- connect(rep, SIGNAL(error(QNetworkReply::NetworkError)), SLOT(downloadError(QNetworkReply::NetworkError)));
+ connect(rep, QOverload::of(&QNetworkReply::error), this, &ImgurUpload::downloadError);
#endif
+ connect(rep, &QNetworkReply::sslErrors, this, &ImgurUpload::sslErrors);
}
+
void ImgurUpload::downloadError(QNetworkReply::NetworkError error)
{
qCritical() << "ImgurUpload failed with error" << m_reply->errorString() << "Server reply:\n" << m_reply->readAll();
@@ -108,6 +110,7 @@ void ImgurUpload::downloadError(QNetworkReply::NetworkError error)
m_reply.reset();
emitFailed();
}
+
void ImgurUpload::downloadFinished()
{
if(finished)
@@ -144,6 +147,7 @@ void ImgurUpload::downloadFinished()
emit succeeded();
return;
}
+
void ImgurUpload::downloadProgress(qint64 bytesReceived, qint64 bytesTotal)
{
setProgress(bytesReceived, bytesTotal);
diff --git a/launcher/settings/SettingsObject.cpp b/launcher/settings/SettingsObject.cpp
index 8a0bc045..634acd34 100644
--- a/launcher/settings/SettingsObject.cpp
+++ b/launcher/settings/SettingsObject.cpp
@@ -132,11 +132,10 @@ bool SettingsObject::reload()
void SettingsObject::connectSignals(const Setting &setting)
{
- connect(&setting, SIGNAL(SettingChanged(const Setting &, QVariant)),
- SLOT(changeSetting(const Setting &, QVariant)));
- connect(&setting, SIGNAL(SettingChanged(const Setting &, QVariant)),
+ connect(&setting, &Setting::SettingChanged, this, &SettingsObject::changeSetting);
+ connect(&setting, SIGNAL(SettingChanged(const Setting &, QVariant)), this,
SIGNAL(SettingChanged(const Setting &, QVariant)));
- connect(&setting, SIGNAL(settingReset(Setting)), SLOT(resetSetting(const Setting &)));
- connect(&setting, SIGNAL(settingReset(Setting)), SIGNAL(settingReset(const Setting &)));
+ connect(&setting, &Setting::settingReset, this, &SettingsObject::resetSetting);
+ connect(&setting, SIGNAL(settingReset(Setting)), this, SIGNAL(settingReset(const Setting &)));
}
diff --git a/launcher/tools/JProfiler.cpp b/launcher/tools/JProfiler.cpp
index 1dc0d109..15c0cab6 100644
--- a/launcher/tools/JProfiler.cpp
+++ b/launcher/tools/JProfiler.cpp
@@ -68,8 +68,8 @@ void JProfiler::beginProfilingImpl(shared_qobject_ptr process)
profiler->setArguments(profilerArgs);
profiler->setProgram(profilerProgram);
- connect(profiler, SIGNAL(started()), SLOT(profilerStarted()));
- connect(profiler, SIGNAL(finished(int, QProcess::ExitStatus)), SLOT(profilerFinished(int,QProcess::ExitStatus)));
+ connect(profiler, &QProcess::started, this, &JProfiler::profilerStarted);
+ connect(profiler, QOverload::of(&QProcess::finished), this, &JProfiler::profilerFinished);
m_profilerProcess = profiler;
profiler->start();
diff --git a/launcher/tools/JVisualVM.cpp b/launcher/tools/JVisualVM.cpp
index b1acc3c0..28ffb9cd 100644
--- a/launcher/tools/JVisualVM.cpp
+++ b/launcher/tools/JVisualVM.cpp
@@ -57,8 +57,8 @@ void JVisualVM::beginProfilingImpl(shared_qobject_ptr process)
profiler->setArguments(profilerArgs);
profiler->setProgram(programPath);
- connect(profiler, SIGNAL(started()), SLOT(profilerStarted()));
- connect(profiler, SIGNAL(finished(int, QProcess::ExitStatus)), SLOT(profilerFinished(int,QProcess::ExitStatus)));
+ connect(profiler, &QProcess::started, this, &JVisualVM::profilerStarted);
+ connect(profiler, QOverload::of(&QProcess::finished), this, &JVisualVM::profilerFinished);
profiler->start();
m_profilerProcess = profiler;
diff --git a/launcher/ui/pages/instance/VersionPage.cpp b/launcher/ui/pages/instance/VersionPage.cpp
index 7fff3b93..fffb96f2 100644
--- a/launcher/ui/pages/instance/VersionPage.cpp
+++ b/launcher/ui/pages/instance/VersionPage.cpp
@@ -501,7 +501,7 @@ void VersionPage::on_actionDownload_All_triggered()
return;
}
ProgressDialog tDialog(this);
- connect(updateTask.get(), SIGNAL(failed(QString)), SLOT(onGameUpdateError(QString)));
+ connect(updateTask.get(), &Task::failed, this, &VersionPage::onGameUpdateError);
// FIXME: unused return value
tDialog.execWithTask(updateTask.get());
updateButtons();
From f41426f3945ff60a26b34935bb1b0b43d8de800b Mon Sep 17 00:00:00 2001
From: Japa
Date: Fri, 21 Apr 2023 00:30:38 -0300
Subject: [PATCH 072/104] Instance Description displays the last launch date
Initial Draft using the Standard C++ Library, still requires testing.
Signed-off-by: Japa
---
launcher/minecraft/MinecraftInstance.cpp | 13 ++++++++++++-
1 file changed, 12 insertions(+), 1 deletion(-)
diff --git a/launcher/minecraft/MinecraftInstance.cpp b/launcher/minecraft/MinecraftInstance.cpp
index af4da5d0..f9f4a56b 100644
--- a/launcher/minecraft/MinecraftInstance.cpp
+++ b/launcher/minecraft/MinecraftInstance.cpp
@@ -925,7 +925,18 @@ QString MinecraftInstance::getStatusbarDescription()
if(m_settings->get("ShowGameTime").toBool())
{
if (lastTimePlayed() > 0) {
- description.append(tr(", last played for %1").arg(Time::prettifyDuration(lastTimePlayed())));
+ struct tm * localTime_format;
+ localTime_format = localtime(lastLaunchTime());
+
+ char lastLaunchTime_formatted[13];
+ strftime(lastLaunchTime_formatted,13,"%Ex",localTime_format);
+
+ description.append(
+ tr(", last played at %1 for %2").arg(
+ lastLaunchTime_formatted,
+ Time::prettifyDuration(lastTimePlayed())
+ )
+ );
}
if (totalTimePlayed() > 0) {
From 92cda68480f3f9bd5b5184314a2dfcdbdda7543b Mon Sep 17 00:00:00 2001
From: Japa
Date: Fri, 21 Apr 2023 11:18:17 -0300
Subject: [PATCH 073/104] Update launcher/minecraft/MinecraftInstance.cpp
Co-authored-by: Rachel Powers <508861+Ryex@users.noreply.github.com>
Signed-off-by: Japa
---
launcher/minecraft/MinecraftInstance.cpp | 16 ++++------------
1 file changed, 4 insertions(+), 12 deletions(-)
diff --git a/launcher/minecraft/MinecraftInstance.cpp b/launcher/minecraft/MinecraftInstance.cpp
index f9f4a56b..3ab8b3af 100644
--- a/launcher/minecraft/MinecraftInstance.cpp
+++ b/launcher/minecraft/MinecraftInstance.cpp
@@ -925,18 +925,10 @@ QString MinecraftInstance::getStatusbarDescription()
if(m_settings->get("ShowGameTime").toBool())
{
if (lastTimePlayed() > 0) {
- struct tm * localTime_format;
- localTime_format = localtime(lastLaunchTime());
-
- char lastLaunchTime_formatted[13];
- strftime(lastLaunchTime_formatted,13,"%Ex",localTime_format);
-
- description.append(
- tr(", last played at %1 for %2").arg(
- lastLaunchTime_formatted,
- Time::prettifyDuration(lastTimePlayed())
- )
- );
+ QDateTime lastLaunchTime = QDateTime::fromMSecsSinceEpoch(lastLaunch());
+ description.append(tr(", last played at %1 for %2")
+ .arg(QLocale::system().toString(lastLaunchTime, QLocale::ShortFormat))
+ .arg(Time::prettifyDuration(lastTimePlayed())));
}
if (totalTimePlayed() > 0) {
From 672f5cf160b8630210f07d250989278f99c4e0ad Mon Sep 17 00:00:00 2001
From: Japa
Date: Fri, 21 Apr 2023 19:46:33 -0300
Subject: [PATCH 074/104] Update launcher/minecraft/MinecraftInstance.cpp
Co-authored-by: Sefa Eyeoglu
Signed-off-by: Japa
---
launcher/minecraft/MinecraftInstance.cpp | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/launcher/minecraft/MinecraftInstance.cpp b/launcher/minecraft/MinecraftInstance.cpp
index 3ab8b3af..3183b41d 100644
--- a/launcher/minecraft/MinecraftInstance.cpp
+++ b/launcher/minecraft/MinecraftInstance.cpp
@@ -927,7 +927,7 @@ QString MinecraftInstance::getStatusbarDescription()
if (lastTimePlayed() > 0) {
QDateTime lastLaunchTime = QDateTime::fromMSecsSinceEpoch(lastLaunch());
description.append(tr(", last played at %1 for %2")
- .arg(QLocale::system().toString(lastLaunchTime, QLocale::ShortFormat))
+ .arg(QLocale().toString(lastLaunchTime, QLocale::ShortFormat))
.arg(Time::prettifyDuration(lastTimePlayed())));
}
From 7298f9c273bbfe31bf6c81af7fb6405b8baaeb13 Mon Sep 17 00:00:00 2001
From: Japa
Date: Wed, 26 Apr 2023 16:06:17 -0300
Subject: [PATCH 075/104] Fixed typo
Signed-off-by: Japa
---
launcher/minecraft/MinecraftInstance.cpp | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/launcher/minecraft/MinecraftInstance.cpp b/launcher/minecraft/MinecraftInstance.cpp
index 3183b41d..b2171a85 100644
--- a/launcher/minecraft/MinecraftInstance.cpp
+++ b/launcher/minecraft/MinecraftInstance.cpp
@@ -926,7 +926,7 @@ QString MinecraftInstance::getStatusbarDescription()
{
if (lastTimePlayed() > 0) {
QDateTime lastLaunchTime = QDateTime::fromMSecsSinceEpoch(lastLaunch());
- description.append(tr(", last played at %1 for %2")
+ description.append(tr(", last played on %1 for %2")
.arg(QLocale().toString(lastLaunchTime, QLocale::ShortFormat))
.arg(Time::prettifyDuration(lastTimePlayed())));
}
From ff07714e8c955003bb3deec84ec6cce6fc3ef5e6 Mon Sep 17 00:00:00 2001
From: Sefa Eyeoglu
Date: Thu, 27 Apr 2023 16:01:40 +0200
Subject: [PATCH 076/104] chore: remove FTB modpack support
We have been contacted by Feed the Beast to drop support for the FTB
modpack browser from Prism Launcher.
Signed-off-by: Sefa Eyeoglu
---
launcher/CMakeLists.txt | 16 -
.../modpacksch/FTBPackInstallTask.cpp | 387 ------------------
.../modpacksch/FTBPackInstallTask.h | 101 -----
.../modpacksch/FTBPackManifest.cpp | 195 ---------
.../modplatform/modpacksch/FTBPackManifest.h | 168 --------
launcher/ui/dialogs/NewInstanceDialog.cpp | 2 -
.../pages/modplatform/ftb/FtbFilterModel.cpp | 93 -----
.../ui/pages/modplatform/ftb/FtbFilterModel.h | 51 ---
.../ui/pages/modplatform/ftb/FtbListModel.cpp | 304 --------------
.../ui/pages/modplatform/ftb/FtbListModel.h | 83 ----
launcher/ui/pages/modplatform/ftb/FtbPage.cpp | 199 ---------
launcher/ui/pages/modplatform/ftb/FtbPage.h | 105 -----
launcher/ui/pages/modplatform/ftb/FtbPage.ui | 86 ----
13 files changed, 1790 deletions(-)
delete mode 100644 launcher/modplatform/modpacksch/FTBPackInstallTask.cpp
delete mode 100644 launcher/modplatform/modpacksch/FTBPackInstallTask.h
delete mode 100644 launcher/modplatform/modpacksch/FTBPackManifest.cpp
delete mode 100644 launcher/modplatform/modpacksch/FTBPackManifest.h
delete mode 100644 launcher/ui/pages/modplatform/ftb/FtbFilterModel.cpp
delete mode 100644 launcher/ui/pages/modplatform/ftb/FtbFilterModel.h
delete mode 100644 launcher/ui/pages/modplatform/ftb/FtbListModel.cpp
delete mode 100644 launcher/ui/pages/modplatform/ftb/FtbListModel.h
delete mode 100644 launcher/ui/pages/modplatform/ftb/FtbPage.cpp
delete mode 100644 launcher/ui/pages/modplatform/ftb/FtbPage.h
delete mode 100644 launcher/ui/pages/modplatform/ftb/FtbPage.ui
diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt
index 074570e3..ee36175f 100644
--- a/launcher/CMakeLists.txt
+++ b/launcher/CMakeLists.txt
@@ -524,13 +524,6 @@ set(MODRINTH_SOURCES
modplatform/modrinth/ModrinthInstanceCreationTask.h
)
-set(MODPACKSCH_SOURCES
- modplatform/modpacksch/FTBPackInstallTask.h
- modplatform/modpacksch/FTBPackInstallTask.cpp
- modplatform/modpacksch/FTBPackManifest.h
- modplatform/modpacksch/FTBPackManifest.cpp
-)
-
set(PACKWIZ_SOURCES
modplatform/packwiz/Packwiz.h
modplatform/packwiz/Packwiz.cpp
@@ -599,7 +592,6 @@ set(LOGIC_SOURCES
${FTB_SOURCES}
${FLAME_SOURCES}
${MODRINTH_SOURCES}
- ${MODPACKSCH_SOURCES}
${PACKWIZ_SOURCES}
${TECHNIC_SOURCES}
${ATLAUNCHER_SOURCES}
@@ -797,13 +789,6 @@ SET(LAUNCHER_SOURCES
ui/pages/modplatform/atlauncher/AtlUserInteractionSupportImpl.cpp
ui/pages/modplatform/atlauncher/AtlUserInteractionSupportImpl.h
- ui/pages/modplatform/ftb/FtbFilterModel.cpp
- ui/pages/modplatform/ftb/FtbFilterModel.h
- ui/pages/modplatform/ftb/FtbListModel.cpp
- ui/pages/modplatform/ftb/FtbListModel.h
- ui/pages/modplatform/ftb/FtbPage.cpp
- ui/pages/modplatform/ftb/FtbPage.h
-
ui/pages/modplatform/legacy_ftb/Page.cpp
ui/pages/modplatform/legacy_ftb/Page.h
ui/pages/modplatform/legacy_ftb/ListModel.h
@@ -978,7 +963,6 @@ qt_wrap_ui(LAUNCHER_UI
ui/pages/modplatform/flame/FlamePage.ui
ui/pages/modplatform/legacy_ftb/Page.ui
ui/pages/modplatform/ImportPage.ui
- ui/pages/modplatform/ftb/FtbPage.ui
ui/pages/modplatform/modrinth/ModrinthPage.ui
ui/pages/modplatform/technic/TechnicPage.ui
ui/widgets/InstanceCardWidget.ui
diff --git a/launcher/modplatform/modpacksch/FTBPackInstallTask.cpp b/launcher/modplatform/modpacksch/FTBPackInstallTask.cpp
deleted file mode 100644
index 68d4751c..00000000
--- a/launcher/modplatform/modpacksch/FTBPackInstallTask.cpp
+++ /dev/null
@@ -1,387 +0,0 @@
-// SPDX-License-Identifier: GPL-3.0-only
-/*
- * Prism Launcher - Minecraft Launcher
- * Copyright (C) 2022 flowln
- * Copyright (c) 2022 Jamie Mansfield
- * Copyright (C) 2022 Sefa Eyeoglu
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, version 3.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- *
- * This file incorporates work covered by the following copyright and
- * permission notice:
- *
- * Copyright 2020-2021 Jamie Mansfield
- * Copyright 2020-2021 Petr Mrazek
- *
- * 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 "FTBPackInstallTask.h"
-
-#include "FileSystem.h"
-#include "Json.h"
-#include "minecraft/MinecraftInstance.h"
-#include "minecraft/PackProfile.h"
-#include "modplatform/flame/PackManifest.h"
-#include "net/ChecksumValidator.h"
-#include "settings/INISettingsObject.h"
-
-#include "Application.h"
-#include "BuildConfig.h"
-#include "ui/dialogs/BlockedModsDialog.h"
-
-namespace ModpacksCH {
-
-PackInstallTask::PackInstallTask(Modpack pack, QString version, QWidget* parent)
- : m_pack(std::move(pack)), m_version_name(std::move(version)), m_parent(parent)
-{}
-
-bool PackInstallTask::abort()
-{
- if (!canAbort())
- return false;
-
- bool aborted = true;
-
- if (m_net_job)
- aborted &= m_net_job->abort();
- if (m_mod_id_resolver_task)
- aborted &= m_mod_id_resolver_task->abort();
-
- return aborted ? InstanceTask::abort() : false;
-}
-
-void PackInstallTask::executeTask()
-{
- setStatus(tr("Getting the manifest..."));
- setAbortable(false);
-
- // Find pack version
- auto version_it = std::find_if(m_pack.versions.constBegin(), m_pack.versions.constEnd(),
- [this](ModpacksCH::VersionInfo const& a) { return a.name == m_version_name; });
-
- if (version_it == m_pack.versions.constEnd()) {
- emitFailed(tr("Failed to find pack version %1").arg(m_version_name));
- return;
- }
-
- auto version = *version_it;
-
- auto netJob = makeShared("ModpacksCH::VersionFetch", APPLICATION->network());
-
- auto searchUrl = QString(BuildConfig.MODPACKSCH_API_BASE_URL + "public/modpack/%1/%2").arg(m_pack.id).arg(version.id);
- netJob->addNetAction(Net::Download::makeByteArray(QUrl(searchUrl), &m_response));
-
- QObject::connect(netJob.get(), &NetJob::succeeded, this, &PackInstallTask::onManifestDownloadSucceeded);
- QObject::connect(netJob.get(), &NetJob::failed, this, &PackInstallTask::onManifestDownloadFailed);
- QObject::connect(netJob.get(), &NetJob::aborted, this, &PackInstallTask::abort);
- QObject::connect(netJob.get(), &NetJob::progress, this, &PackInstallTask::setProgress);
-
- m_net_job = netJob;
-
- setAbortable(true);
- netJob->start();
-}
-
-void PackInstallTask::onManifestDownloadSucceeded()
-{
- m_net_job.reset();
-
- QJsonParseError parse_error{};
- QJsonDocument doc = QJsonDocument::fromJson(m_response, &parse_error);
- if (parse_error.error != QJsonParseError::NoError) {
- qWarning() << "Error while parsing JSON response from ModpacksCH at " << parse_error.offset
- << " reason: " << parse_error.errorString();
- qWarning() << m_response;
- return;
- }
-
- ModpacksCH::Version version;
- try {
- auto obj = Json::requireObject(doc);
- ModpacksCH::loadVersion(version, obj);
- } catch (const JSONValidationError& e) {
- emitFailed(tr("Could not understand pack manifest:\n") + e.cause());
- return;
- }
-
- m_version = version;
-
- resolveMods();
-}
-
-void PackInstallTask::resolveMods()
-{
- setStatus(tr("Resolving mods..."));
- setAbortable(false);
- setProgress(0, 100);
-
- m_file_id_map.clear();
-
- Flame::Manifest manifest;
- int index = 0;
-
- for (auto const& file : m_version.files) {
- if (!file.serverOnly && file.url.isEmpty()) {
- if (file.curseforge.file_id <= 0) {
- emitFailed(tr("Invalid manifest: There's no information available to download the file '%1'!").arg(file.name));
- return;
- }
-
- Flame::File flame_file;
- flame_file.projectId = file.curseforge.project_id;
- flame_file.fileId = file.curseforge.file_id;
- flame_file.hash = file.sha1;
-
- manifest.files.insert(flame_file.fileId, flame_file);
- m_file_id_map.append(flame_file.fileId);
- } else {
- m_file_id_map.append(-1);
- }
-
- index++;
- }
-
- m_mod_id_resolver_task.reset(new Flame::FileResolvingTask(APPLICATION->network(), manifest));
-
- connect(m_mod_id_resolver_task.get(), &Flame::FileResolvingTask::succeeded, this, &PackInstallTask::onResolveModsSucceeded);
- connect(m_mod_id_resolver_task.get(), &Flame::FileResolvingTask::failed, this, &PackInstallTask::onResolveModsFailed);
- connect(m_mod_id_resolver_task.get(), &Flame::FileResolvingTask::aborted, this, &PackInstallTask::abort);
- connect(m_mod_id_resolver_task.get(), &Flame::FileResolvingTask::progress, this, &PackInstallTask::setProgress);
-
- setAbortable(true);
-
- m_mod_id_resolver_task->start();
-}
-
-void PackInstallTask::onResolveModsSucceeded()
-{
- auto anyBlocked = false;
-
- Flame::Manifest results = m_mod_id_resolver_task->getResults();
- for (int index = 0; index < m_file_id_map.size(); index++) {
- auto const file_id = m_file_id_map.at(index);
- if (file_id < 0)
- continue;
-
- Flame::File results_file = results.files[file_id];
- VersionFile& local_file = m_version.files[index];
-
- // First check for blocked mods
- if (!results_file.resolved || results_file.url.isEmpty()) {
- BlockedMod blocked_mod;
- blocked_mod.name = local_file.name;
- blocked_mod.websiteUrl = results_file.websiteUrl;
- blocked_mod.hash = results_file.hash;
- blocked_mod.matched = false;
- blocked_mod.localPath = "";
- blocked_mod.targetFolder = results_file.targetFolder;
-
- m_blocked_mods.append(blocked_mod);
-
- anyBlocked = true;
- } else {
- local_file.url = results_file.url.toString();
- }
- }
-
- m_mod_id_resolver_task.reset();
-
- if (anyBlocked) {
- qDebug() << "Blocked files found, displaying file list";
-
- BlockedModsDialog message_dialog(m_parent, tr("Blocked files found"),
- tr("The following files are not available for download in third party launchers.
"
- "You will need to manually download them and add them to the instance."),
- m_blocked_mods);
-
- message_dialog.setModal(true);
-
- if (message_dialog.exec() == QDialog::Accepted) {
- qDebug() << "Post dialog blocked mods list: " << m_blocked_mods;
- createInstance();
- } else {
- abort();
- }
-
- } else {
- createInstance();
- }
-}
-
-void PackInstallTask::createInstance()
-{
- setAbortable(false);
-
- setStatus(tr("Creating the instance..."));
- QCoreApplication::processEvents();
-
- auto instanceConfigPath = FS::PathCombine(m_stagingPath, "instance.cfg");
- auto instanceSettings = std::make_shared(instanceConfigPath);
-
- MinecraftInstance instance(m_globalSettings, instanceSettings, m_stagingPath);
- auto components = instance.getPackProfile();
- components->buildingFromScratch();
-
- for (auto target : m_version.targets) {
- if (target.type == "game" && target.name == "minecraft") {
- components->setComponentVersion("net.minecraft", target.version, true);
- break;
- }
- }
-
- for (auto target : m_version.targets) {
- if (target.type != "modloader")
- continue;
-
- if (target.name == "forge") {
- components->setComponentVersion("net.minecraftforge", target.version);
- } else if (target.name == "fabric") {
- components->setComponentVersion("net.fabricmc.fabric-loader", target.version);
- }
- }
-
- // install any jar mods
- QDir jarModsDir(FS::PathCombine(m_stagingPath, "minecraft", "jarmods"));
- if (jarModsDir.exists()) {
- QStringList jarMods;
-
- for (const auto& info : jarModsDir.entryInfoList(QDir::NoDotAndDotDot | QDir::Files)) {
- jarMods.push_back(info.absoluteFilePath());
- }
-
- components->installJarMods(jarMods);
- }
-
- components->saveNow();
-
- instance.setName(name());
- instance.setIconKey(m_instIcon);
- instance.setManagedPack("modpacksch", QString::number(m_pack.id), m_pack.name, QString::number(m_version.id), m_version.name);
-
- instance.saveNow();
-
- onCreateInstanceSucceeded();
-}
-
-void PackInstallTask::onCreateInstanceSucceeded()
-{
- downloadPack();
-}
-
-void PackInstallTask::downloadPack()
-{
- setStatus(tr("Downloading mods..."));
- setAbortable(false);
-
- auto jobPtr = makeShared(tr("Mod download"), APPLICATION->network());
- for (auto const& file : m_version.files) {
- if (file.serverOnly || file.url.isEmpty())
- continue;
-
- auto path = FS::PathCombine(m_stagingPath, ".minecraft", file.path, file.name);
- qDebug() << "Will try to download" << file.url << "to" << path;
-
- QFileInfo file_info(file.name);
-
- auto dl = Net::Download::makeFile(file.url, path);
- if (!file.sha1.isEmpty()) {
- auto rawSha1 = QByteArray::fromHex(file.sha1.toLatin1());
- dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Sha1, rawSha1));
- }
-
- jobPtr->addNetAction(dl);
- }
-
- connect(jobPtr.get(), &NetJob::succeeded, this, &PackInstallTask::onModDownloadSucceeded);
- connect(jobPtr.get(), &NetJob::failed, this, &PackInstallTask::onModDownloadFailed);
- connect(jobPtr.get(), &NetJob::aborted, this, &PackInstallTask::abort);
- connect(jobPtr.get(), &NetJob::progress, this, &PackInstallTask::setProgress);
-
- m_net_job = jobPtr;
-
- setAbortable(true);
- jobPtr->start();
-}
-
-void PackInstallTask::onModDownloadSucceeded()
-{
- m_net_job.reset();
- if (!m_blocked_mods.isEmpty()) {
- copyBlockedMods();
- }
- emitSucceeded();
-}
-
-void PackInstallTask::onManifestDownloadFailed(QString reason)
-{
- m_net_job.reset();
- emitFailed(reason);
-}
-void PackInstallTask::onResolveModsFailed(QString reason)
-{
- m_net_job.reset();
- emitFailed(reason);
-}
-void PackInstallTask::onCreateInstanceFailed(QString reason)
-{
- emitFailed(reason);
-}
-void PackInstallTask::onModDownloadFailed(QString reason)
-{
- m_net_job.reset();
- emitFailed(reason);
-}
-
-/// @brief copy the matched blocked mods to the instance staging area
-void PackInstallTask::copyBlockedMods()
-{
- setStatus(tr("Copying Blocked Mods..."));
- setAbortable(false);
- int i = 0;
- int total = m_blocked_mods.length();
- setProgress(i, total);
- for (auto const& mod : m_blocked_mods) {
- if (!mod.matched) {
- qDebug() << mod.name << "was not matched to a local file, skipping copy";
- continue;
- }
-
- auto dest_path = FS::PathCombine(m_stagingPath, ".minecraft", mod.targetFolder, mod.name);
-
- setStatus(tr("Copying Blocked Mods (%1 out of %2 are done)").arg(QString::number(i), QString::number(total)));
-
- qDebug() << "Will try to copy" << mod.localPath << "to" << dest_path;
-
- if (!FS::copy(mod.localPath, dest_path)()) {
- qDebug() << "Copy of" << mod.localPath << "to" << dest_path << "Failed";
- }
-
- i++;
- setProgress(i, total);
- }
-
- setAbortable(true);
-}
-
-} // namespace ModpacksCH
diff --git a/launcher/modplatform/modpacksch/FTBPackInstallTask.h b/launcher/modplatform/modpacksch/FTBPackInstallTask.h
deleted file mode 100644
index 97b1eb0b..00000000
--- a/launcher/modplatform/modpacksch/FTBPackInstallTask.h
+++ /dev/null
@@ -1,101 +0,0 @@
-// SPDX-License-Identifier: GPL-3.0-only
-/*
- * Prism Launcher - Minecraft Launcher
- * Copyright (C) 2022 flowln
- * Copyright (C) 2022 Sefa Eyeoglu
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, version 3.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- *
- * This file incorporates work covered by the following copyright and
- * permission notice:
- *
- * Copyright 2020-2021 Jamie Mansfield
- * Copyright 2020-2021 Petr Mrazek
- *
- * 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 "FTBPackManifest.h"
-
-#include "InstanceTask.h"
-#include "QObjectPtr.h"
-#include "modplatform/flame/FileResolvingTask.h"
-#include "net/NetJob.h"
-#include "ui/dialogs/BlockedModsDialog.h"
-
-#include
-
-namespace ModpacksCH {
-
-class PackInstallTask final : public InstanceTask
-{
- Q_OBJECT
-
-public:
- explicit PackInstallTask(Modpack pack, QString version, QWidget* parent = nullptr);
- ~PackInstallTask() override = default;
-
- bool abort() override;
-
-protected:
- void executeTask() override;
-
-private slots:
- void onManifestDownloadSucceeded();
- void onResolveModsSucceeded();
- void onCreateInstanceSucceeded();
- void onModDownloadSucceeded();
-
- void onManifestDownloadFailed(QString reason);
- void onResolveModsFailed(QString reason);
- void onCreateInstanceFailed(QString reason);
- void onModDownloadFailed(QString reason);
-
-private:
- void resolveMods();
- void createInstance();
- void downloadPack();
- void copyBlockedMods();
-
-private:
- NetJob::Ptr m_net_job = nullptr;
- shared_qobject_ptr m_mod_id_resolver_task = nullptr;
-
- QList m_file_id_map;
-
- QByteArray m_response;
-
- Modpack m_pack;
- QString m_version_name;
- Version m_version;
-
- QMap m_files_to_copy;
- QList m_blocked_mods;
-
- //FIXME: nuke
- QWidget* m_parent;
-};
-
-}
diff --git a/launcher/modplatform/modpacksch/FTBPackManifest.cpp b/launcher/modplatform/modpacksch/FTBPackManifest.cpp
deleted file mode 100644
index 421527ae..00000000
--- a/launcher/modplatform/modpacksch/FTBPackManifest.cpp
+++ /dev/null
@@ -1,195 +0,0 @@
-// SPDX-License-Identifier: GPL-3.0-only
-/*
- * PolyMC - Minecraft Launcher
- * Copyright (C) 2022 Sefa Eyeoglu
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, version 3.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- *
- * This file incorporates work covered by the following copyright and
- * permission notice:
- *
- * Copyright 2020 Jamie Mansfield
- * Copyright 2020-2021 Petr Mrazek
- *
- * 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 "FTBPackManifest.h"
-
-#include "Json.h"
-
-static void loadSpecs(ModpacksCH::Specs & s, QJsonObject & obj)
-{
- s.id = Json::requireInteger(obj, "id");
- s.minimum = Json::requireInteger(obj, "minimum");
- s.recommended = Json::requireInteger(obj, "recommended");
-}
-
-static void loadTag(ModpacksCH::Tag & t, QJsonObject & obj)
-{
- t.id = Json::requireInteger(obj, "id");
- t.name = Json::requireString(obj, "name");
-}
-
-static void loadArt(ModpacksCH::Art & a, QJsonObject & obj)
-{
- a.id = Json::requireInteger(obj, "id");
- a.url = Json::requireString(obj, "url");
- a.type = Json::requireString(obj, "type");
- a.width = Json::requireInteger(obj, "width");
- a.height = Json::requireInteger(obj, "height");
- a.compressed = Json::requireBoolean(obj, "compressed");
- a.sha1 = Json::requireString(obj, "sha1");
- a.size = Json::requireInteger(obj, "size");
- a.updated = Json::requireInteger(obj, "updated");
-}
-
-static void loadAuthor(ModpacksCH::Author & a, QJsonObject & obj)
-{
- a.id = Json::requireInteger(obj, "id");
- a.name = Json::requireString(obj, "name");
- a.type = Json::requireString(obj, "type");
- a.website = Json::requireString(obj, "website");
- a.updated = Json::requireInteger(obj, "updated");
-}
-
-static void loadVersionInfo(ModpacksCH::VersionInfo & v, QJsonObject & obj)
-{
- v.id = Json::requireInteger(obj, "id");
- v.name = Json::requireString(obj, "name");
- v.type = Json::requireString(obj, "type");
- v.updated = Json::requireInteger(obj, "updated");
- auto specs = Json::requireObject(obj, "specs");
- loadSpecs(v.specs, specs);
-}
-
-void ModpacksCH::loadModpack(ModpacksCH::Modpack & m, QJsonObject & obj)
-{
- m.id = Json::requireInteger(obj, "id");
- m.name = Json::requireString(obj, "name");
- m.synopsis = Json::requireString(obj, "synopsis");
- m.description = Json::requireString(obj, "description");
- m.type = Json::requireString(obj, "type");
- m.featured = Json::requireBoolean(obj, "featured");
- m.installs = Json::requireInteger(obj, "installs");
- m.plays = Json::requireInteger(obj, "plays");
- m.updated = Json::requireInteger(obj, "updated");
- m.refreshed = Json::requireInteger(obj, "refreshed");
- auto artArr = Json::requireArray(obj, "art");
- for (QJsonValueRef artRaw : artArr)
- {
- auto artObj = Json::requireObject(artRaw);
- ModpacksCH::Art art;
- loadArt(art, artObj);
- m.art.append(art);
- }
- auto authorArr = Json::requireArray(obj, "authors");
- for (QJsonValueRef authorRaw : authorArr)
- {
- auto authorObj = Json::requireObject(authorRaw);
- ModpacksCH::Author author;
- loadAuthor(author, authorObj);
- m.authors.append(author);
- }
- auto versionArr = Json::requireArray(obj, "versions");
- for (QJsonValueRef versionRaw : versionArr)
- {
- auto versionObj = Json::requireObject(versionRaw);
- ModpacksCH::VersionInfo version;
- loadVersionInfo(version, versionObj);
- m.versions.append(version);
- }
- auto tagArr = Json::requireArray(obj, "tags");
- for (QJsonValueRef tagRaw : tagArr)
- {
- auto tagObj = Json::requireObject(tagRaw);
- ModpacksCH::Tag tag;
- loadTag(tag, tagObj);
- m.tags.append(tag);
- }
- m.updated = Json::requireInteger(obj, "updated");
-}
-
-static void loadVersionTarget(ModpacksCH::VersionTarget & a, QJsonObject & obj)
-{
- a.id = Json::requireInteger(obj, "id");
- a.name = Json::requireString(obj, "name");
- a.type = Json::requireString(obj, "type");
- a.version = Json::requireString(obj, "version");
- a.updated = Json::requireInteger(obj, "updated");
-}
-
-static void loadVersionFile(ModpacksCH::VersionFile & a, QJsonObject & obj)
-{
- a.id = Json::requireInteger(obj, "id");
- a.type = Json::requireString(obj, "type");
- a.path = Json::requireString(obj, "path");
- a.name = Json::requireString(obj, "name");
- a.version = Json::requireString(obj, "version");
- a.url = Json::ensureString(obj, "url"); // optional
- a.sha1 = Json::requireString(obj, "sha1");
- a.size = Json::requireInteger(obj, "size");
- a.clientOnly = Json::requireBoolean(obj, "clientonly");
- a.serverOnly = Json::requireBoolean(obj, "serveronly");
- a.optional = Json::requireBoolean(obj, "optional");
- a.updated = Json::requireInteger(obj, "updated");
- auto curseforgeObj = Json::ensureObject(obj, "curseforge"); // optional
- a.curseforge.project_id = Json::ensureInteger(curseforgeObj, "project");
- a.curseforge.file_id = Json::ensureInteger(curseforgeObj, "file");
-}
-
-void ModpacksCH::loadVersion(ModpacksCH::Version & m, QJsonObject & obj)
-{
- m.id = Json::requireInteger(obj, "id");
- m.parent = Json::requireInteger(obj, "parent");
- m.name = Json::requireString(obj, "name");
- m.type = Json::requireString(obj, "type");
- m.installs = Json::requireInteger(obj, "installs");
- m.plays = Json::requireInteger(obj, "plays");
- m.updated = Json::requireInteger(obj, "updated");
- m.refreshed = Json::requireInteger(obj, "refreshed");
- auto specs = Json::requireObject(obj, "specs");
- loadSpecs(m.specs, specs);
- auto targetArr = Json::requireArray(obj, "targets");
- for (QJsonValueRef targetRaw : targetArr)
- {
- auto versionObj = Json::requireObject(targetRaw);
- ModpacksCH::VersionTarget target;
- loadVersionTarget(target, versionObj);
- m.targets.append(target);
- }
- auto fileArr = Json::requireArray(obj, "files");
- for (QJsonValueRef fileRaw : fileArr)
- {
- auto fileObj = Json::requireObject(fileRaw);
- ModpacksCH::VersionFile file;
- loadVersionFile(file, fileObj);
- m.files.append(file);
- }
-}
-
-//static void loadVersionChangelog(ModpacksCH::VersionChangelog & m, QJsonObject & obj)
-//{
-// m.content = Json::requireString(obj, "content");
-// m.updated = Json::requireInteger(obj, "updated");
-//}
diff --git a/launcher/modplatform/modpacksch/FTBPackManifest.h b/launcher/modplatform/modpacksch/FTBPackManifest.h
deleted file mode 100644
index a8b6f35e..00000000
--- a/launcher/modplatform/modpacksch/FTBPackManifest.h
+++ /dev/null
@@ -1,168 +0,0 @@
-// SPDX-License-Identifier: GPL-3.0-only
-/*
- * PolyMC - Minecraft Launcher
- * Copyright (C) 2022 Sefa Eyeoglu
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, version 3.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- *
- * This file incorporates work covered by the following copyright and
- * permission notice:
- *
- * Copyright 2020-2021 Jamie Mansfield
- * Copyright 2020 Petr Mrazek
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#pragma once
-
-#include
-#include
-#include
-#include
-#include
-
-namespace ModpacksCH
-{
-
-struct Specs
-{
- int id;
- int minimum;
- int recommended;
-};
-
-struct Tag
-{
- int id;
- QString name;
-};
-
-struct Art
-{
- int id;
- QString url;
- QString type;
- int width;
- int height;
- bool compressed;
- QString sha1;
- int size;
- int64_t updated;
-};
-
-struct Author
-{
- int id;
- QString name;
- QString type;
- QString website;
- int64_t updated;
-};
-
-struct VersionInfo
-{
- int id;
- QString name;
- QString type;
- int64_t updated;
- Specs specs;
-};
-
-struct Modpack
-{
- int id;
- QString name;
- QString synopsis;
- QString description;
- QString type;
- bool featured;
- int installs;
- int plays;
- int64_t updated;
- int64_t refreshed;
- QVector art;
- QVector authors;
- QVector versions;
- QVector tags;
-};
-
-struct VersionTarget
-{
- int id;
- QString type;
- QString name;
- QString version;
- int64_t updated;
-};
-
-struct VersionFileCurseForge
-{
- int project_id;
- int file_id;
-};
-
-struct VersionFile
-{
- int id;
- QString type;
- QString path;
- QString name;
- QString version;
- QString url;
- QString sha1;
- int size;
- bool clientOnly;
- bool serverOnly;
- bool optional;
- int64_t updated;
- VersionFileCurseForge curseforge;
-};
-
-struct Version
-{
- int id;
- int parent;
- QString name;
- QString type;
- int installs;
- int plays;
- int64_t updated;
- int64_t refreshed;
- Specs specs;
- QVector targets;
- QVector files;
-};
-
-struct VersionChangelog
-{
- QString content;
- int64_t updated;
-};
-
-void loadModpack(Modpack & m, QJsonObject & obj);
-
-void loadVersion(Version & m, QJsonObject & obj);
-}
-
-Q_DECLARE_METATYPE(ModpacksCH::Modpack)
diff --git a/launcher/ui/dialogs/NewInstanceDialog.cpp b/launcher/ui/dialogs/NewInstanceDialog.cpp
index df182f09..64ed7673 100644
--- a/launcher/ui/dialogs/NewInstanceDialog.cpp
+++ b/launcher/ui/dialogs/NewInstanceDialog.cpp
@@ -56,7 +56,6 @@
#include "ui/widgets/PageContainer.h"
#include "ui/pages/modplatform/VanillaPage.h"
#include "ui/pages/modplatform/atlauncher/AtlPage.h"
-#include "ui/pages/modplatform/ftb/FtbPage.h"
#include "ui/pages/modplatform/legacy_ftb/Page.h"
#include "ui/pages/modplatform/flame/FlamePage.h"
#include "ui/pages/modplatform/ImportPage.h"
@@ -168,7 +167,6 @@ QList NewInstanceDialog::getPages()
pages.append(new AtlPage(this));
if (APPLICATION->capabilities() & Application::SupportsFlame)
pages.append(new FlamePage(this));
- pages.append(new FtbPage(this));
pages.append(new LegacyFTB::Page(this));
pages.append(new ModrinthPage(this));
pages.append(new TechnicPage(this));
diff --git a/launcher/ui/pages/modplatform/ftb/FtbFilterModel.cpp b/launcher/ui/pages/modplatform/ftb/FtbFilterModel.cpp
deleted file mode 100644
index e2b548f2..00000000
--- a/launcher/ui/pages/modplatform/ftb/FtbFilterModel.cpp
+++ /dev/null
@@ -1,93 +0,0 @@
-/*
- * Copyright 2020-2021 Jamie Mansfield
- *
- * 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 "FtbFilterModel.h"
-
-#include
-
-#include "modplatform/modpacksch/FTBPackManifest.h"
-
-#include "StringUtils.h"
-
-namespace Ftb {
-
-FilterModel::FilterModel(QObject *parent) : QSortFilterProxyModel(parent)
-{
- currentSorting = Sorting::ByPlays;
- sortings.insert(tr("Sort by Plays"), Sorting::ByPlays);
- sortings.insert(tr("Sort by Installs"), Sorting::ByInstalls);
- sortings.insert(tr("Sort by Name"), Sorting::ByName);
-}
-
-const QMap FilterModel::getAvailableSortings()
-{
- return sortings;
-}
-
-QString FilterModel::translateCurrentSorting()
-{
- return sortings.key(currentSorting);
-}
-
-void FilterModel::setSorting(Sorting sorting)
-{
- currentSorting = sorting;
- invalidate();
-}
-
-FilterModel::Sorting FilterModel::getCurrentSorting()
-{
- return currentSorting;
-}
-
-void FilterModel::setSearchTerm(const QString& term)
-{
- searchTerm = term.trimmed();
- invalidate();
-}
-
-bool FilterModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const
-{
- if (searchTerm.isEmpty()) {
- return true;
- }
-
- auto index = sourceModel()->index(sourceRow, 0, sourceParent);
- auto pack = sourceModel()->data(index, Qt::UserRole).value();
- return pack.name.contains(searchTerm, Qt::CaseInsensitive);
-}
-
-bool FilterModel::lessThan(const QModelIndex &left, const QModelIndex &right) const
-{
- ModpacksCH::Modpack leftPack = sourceModel()->data(left, Qt::UserRole).value();
- ModpacksCH::Modpack rightPack = sourceModel()->data(right, Qt::UserRole).value();
-
- if (currentSorting == ByPlays) {
- return leftPack.plays < rightPack.plays;
- }
- else if (currentSorting == ByInstalls) {
- return leftPack.installs < rightPack.installs;
- }
- else if (currentSorting == ByName) {
- return StringUtils::naturalCompare(leftPack.name, rightPack.name, Qt::CaseSensitive) >= 0;
- }
-
- // Invalid sorting set, somehow...
- qWarning() << "Invalid sorting set!";
- return true;
-}
-
-}
diff --git a/launcher/ui/pages/modplatform/ftb/FtbFilterModel.h b/launcher/ui/pages/modplatform/ftb/FtbFilterModel.h
deleted file mode 100644
index 1be28e99..00000000
--- a/launcher/ui/pages/modplatform/ftb/FtbFilterModel.h
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright 2020-2021 Jamie Mansfield
- *
- * 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
-
-namespace Ftb {
-
-class FilterModel : public QSortFilterProxyModel
-{
- Q_OBJECT
-
-public:
- FilterModel(QObject* parent = Q_NULLPTR);
- enum Sorting {
- ByPlays,
- ByInstalls,
- ByName,
- };
- const QMap getAvailableSortings();
- QString translateCurrentSorting();
- void setSorting(Sorting sorting);
- Sorting getCurrentSorting();
- void setSearchTerm(const QString& term);
-
-protected:
- bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override;
- bool lessThan(const QModelIndex &left, const QModelIndex &right) const override;
-
-private:
- QMap sortings;
- Sorting currentSorting;
- QString searchTerm { "" };
-
-};
-
-}
diff --git a/launcher/ui/pages/modplatform/ftb/FtbListModel.cpp b/launcher/ui/pages/modplatform/ftb/FtbListModel.cpp
deleted file mode 100644
index e8065415..00000000
--- a/launcher/ui/pages/modplatform/ftb/FtbListModel.cpp
+++ /dev/null
@@ -1,304 +0,0 @@
-/*
- * Copyright 2020-2021 Jamie Mansfield
- *
- * 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 "FtbListModel.h"
-
-#include "BuildConfig.h"
-#include "Application.h"
-#include "Json.h"
-
-#include
-
-namespace Ftb {
-
-ListModel::ListModel(QObject *parent) : QAbstractListModel(parent)
-{
-}
-
-ListModel::~ListModel()
-{
-}
-
-int ListModel::rowCount(const QModelIndex &parent) const
-{
- return parent.isValid() ? 0 : modpacks.size();
-}
-
-int ListModel::columnCount(const QModelIndex &parent) const
-{
- return parent.isValid() ? 0 : 1;
-}
-
-QVariant ListModel::data(const QModelIndex &index, int role) const
-{
- int pos = index.row();
- if(pos >= modpacks.size() || pos < 0 || !index.isValid())
- {
- return QString("INVALID INDEX %1").arg(pos);
- }
-
- ModpacksCH::Modpack pack = modpacks.at(pos);
- if(role == Qt::DisplayRole)
- {
- return pack.name;
- }
- else if (role == Qt::ToolTipRole)
- {
- return pack.synopsis;
- }
- else if(role == Qt::DecorationRole)
- {
- QIcon placeholder = APPLICATION->getThemedIcon("screenshot-placeholder");
-
- auto iter = m_logoMap.find(pack.name);
- if (iter != m_logoMap.end()) {
- auto & logo = *iter;
- if(!logo.result.isNull()) {
- return logo.result;
- }
- return placeholder;
- }
-
- for(auto art : pack.art) {
- if(art.type == "square") {
- ((ListModel *)this)->requestLogo(pack.name, art.url);
- }
- }
- return placeholder;
- }
- else if(role == Qt::UserRole)
- {
- QVariant v;
- v.setValue(pack);
- return v;
- }
-
- return QVariant();
-}
-
-void ListModel::getLogo(const QString &logo, const QString &logoUrl, LogoCallback callback)
-{
- if(m_logoMap.contains(logo))
- {
- callback(APPLICATION->metacache()->resolveEntry("ModpacksCHPacks", QString("logos/%1").arg(logo.section(".", 0, 0)))->getFullPath());
- }
- else
- {
- requestLogo(logo, logoUrl);
- }
-}
-
-void ListModel::request()
-{
- m_aborted = false;
-
- beginResetModel();
- modpacks.clear();
- endResetModel();
-
- auto netJob = makeShared("Ftb::Request", APPLICATION->network());
- auto url = QString(BuildConfig.MODPACKSCH_API_BASE_URL + "public/modpack/all");
- netJob->addNetAction(Net::Download::makeByteArray(QUrl(url), &response));
- jobPtr = netJob;
- jobPtr->start();
-
- QObject::connect(netJob.get(), &NetJob::succeeded, this, &ListModel::requestFinished);
- QObject::connect(netJob.get(), &NetJob::failed, this, &ListModel::requestFailed);
-}
-
-void ListModel::abortRequest()
-{
- m_aborted = jobPtr->abort();
- jobPtr.reset();
-}
-
-void ListModel::requestFinished()
-{
- jobPtr.reset();
- remainingPacks.clear();
-
- QJsonParseError parse_error {};
- QJsonDocument doc = QJsonDocument::fromJson(response, &parse_error);
- if(parse_error.error != QJsonParseError::NoError) {
- qWarning() << "Error while parsing JSON response from ModpacksCH at " << parse_error.offset << " reason: " << parse_error.errorString();
- qWarning() << response;
- return;
- }
-
- auto packs = doc.object().value("packs").toArray();
- for(auto pack : packs) {
- auto packId = pack.toInt();
- remainingPacks.append(packId);
- }
-
- if(!remainingPacks.isEmpty()) {
- currentPack = remainingPacks.at(0);
- requestPack();
- }
-}
-
-void ListModel::requestFailed(QString reason)
-{
- jobPtr.reset();
- remainingPacks.clear();
-}
-
-void ListModel::requestPack()
-{
- auto netJob = makeShared("Ftb::Search", APPLICATION->network());
- auto searchUrl = QString(BuildConfig.MODPACKSCH_API_BASE_URL + "public/modpack/%1").arg(currentPack);
- netJob->addNetAction(Net::Download::makeByteArray(QUrl(searchUrl), &response));
- jobPtr = netJob;
- jobPtr->start();
-
- QObject::connect(netJob.get(), &NetJob::succeeded, this, &ListModel::packRequestFinished);
- QObject::connect(netJob.get(), &NetJob::failed, this, &ListModel::packRequestFailed);
-}
-
-void ListModel::packRequestFinished()
-{
- if (!jobPtr || m_aborted)
- return;
-
- jobPtr.reset();
- remainingPacks.removeOne(currentPack);
-
- QJsonParseError parse_error;
- QJsonDocument doc = QJsonDocument::fromJson(response, &parse_error);
-
- if(parse_error.error != QJsonParseError::NoError) {
- qWarning() << "Error while parsing JSON response from ModpacksCH at " << parse_error.offset << " reason: " << parse_error.errorString();
- qWarning() << response;
- return;
- }
-
- auto obj = doc.object();
-
- ModpacksCH::Modpack pack;
- try
- {
- ModpacksCH::loadModpack(pack, obj);
- }
- catch (const JSONValidationError &e)
- {
- qDebug() << QString::fromUtf8(response);
- qWarning() << "Error while reading pack manifest from ModpacksCH: " << e.cause();
- return;
- }
-
- // Since there is no guarantee that packs have a version, this will just
- // ignore those "dud" packs.
- if (pack.versions.empty())
- {
- qWarning() << "ModpacksCH Pack " << pack.id << " ignored. reason: lacking any versions";
- }
- else
- {
- beginInsertRows(QModelIndex(), modpacks.size(), modpacks.size());
- modpacks.append(pack);
- endInsertRows();
- }
-
- if(!remainingPacks.isEmpty()) {
- currentPack = remainingPacks.at(0);
- requestPack();
- }
-}
-
-void ListModel::packRequestFailed(QString reason)
-{
- jobPtr.reset();
- remainingPacks.removeOne(currentPack);
-}
-
-void ListModel::logoLoaded(QString logo, bool stale)
-{
- auto & logoObj = m_logoMap[logo];
- logoObj.downloadJob.reset();
- QString smallPath = logoObj.fullpath + ".small";
-
- QFileInfo smallInfo(smallPath);
-
- if(stale || !smallInfo.exists()) {
- QImage image(logoObj.fullpath);
- if (image.isNull())
- {
- logoObj.failed = true;
- return;
- }
- QImage small;
- if (image.width() > image.height()) {
- small = image.scaledToWidth(512).scaledToWidth(256, Qt::SmoothTransformation);
- }
- else {
- small = image.scaledToHeight(512).scaledToHeight(256, Qt::SmoothTransformation);
- }
- QPoint offset((256 - small.width()) / 2, (256 - small.height()) / 2);
- QImage square(QSize(256, 256), QImage::Format_ARGB32);
- square.fill(Qt::transparent);
-
- QPainter painter(&square);
- painter.drawImage(offset, small);
- painter.end();
-
- square.save(logoObj.fullpath + ".small", "PNG");
- }
-
- logoObj.result = QIcon(logoObj.fullpath + ".small");
- for(int i = 0; i < modpacks.size(); i++) {
- if(modpacks[i].name == logo) {
- emit dataChanged(createIndex(i, 0), createIndex(i, 0), {Qt::DecorationRole});
- }
- }
-}
-
-void ListModel::logoFailed(QString logo)
-{
- m_logoMap[logo].failed = true;
- m_logoMap[logo].downloadJob.reset();
-}
-
-void ListModel::requestLogo(QString logo, QString url)
-{
- if(m_logoMap.contains(logo)) {
- return;
- }
-
- MetaEntryPtr entry = APPLICATION->metacache()->resolveEntry("ModpacksCHPacks", QString("logos/%1").arg(logo.section(".", 0, 0)));
-
- bool stale = entry->isStale();
-
- auto job = makeShared(QString("ModpacksCH Icon Download %1").arg(logo), APPLICATION->network());
- job->addNetAction(Net::Download::makeCached(QUrl(url), entry));
-
- auto fullPath = entry->getFullPath();
- QObject::connect(job.get(), &NetJob::finished, this, [this, logo, fullPath, stale]
- {
- logoLoaded(logo, stale);
- });
-
- QObject::connect(job.get(), &NetJob::failed, this, [this, logo]
- {
- logoFailed(logo);
- });
-
- auto &newLogoEntry = m_logoMap[logo];
- newLogoEntry.downloadJob = job;
- newLogoEntry.fullpath = fullPath;
- job->start();
-}
-
-}
diff --git a/launcher/ui/pages/modplatform/ftb/FtbListModel.h b/launcher/ui/pages/modplatform/ftb/FtbListModel.h
deleted file mode 100644
index d7a120f0..00000000
--- a/launcher/ui/pages/modplatform/ftb/FtbListModel.h
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * Copyright 2020-2021 Jamie Mansfield
- *
- * 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 "modplatform/modpacksch/FTBPackManifest.h"
-#include "net/NetJob.h"
-#include
-
-namespace Ftb {
-
-struct Logo {
- QString fullpath;
- NetJob::Ptr downloadJob;
- QIcon result;
- bool failed = false;
-};
-
-typedef QMap LogoMap;
-typedef std::function LogoCallback;
-
-class ListModel : public QAbstractListModel
-{
- Q_OBJECT
-
-public:
- ListModel(QObject *parent);
- virtual ~ListModel();
-
- int rowCount(const QModelIndex &parent) const override;
- int columnCount(const QModelIndex &parent) const override;
- QVariant data(const QModelIndex &index, int role) const override;
-
- void request();
- void abortRequest();
-
- void getLogo(const QString &logo, const QString &logoUrl, LogoCallback callback);
-
- [[nodiscard]] bool isMakingRequest() const { return jobPtr.get(); }
- [[nodiscard]] bool wasAborted() const { return m_aborted; }
-
-private slots:
- void requestFinished();
- void requestFailed(QString reason);
-
- void requestPack();
- void packRequestFinished();
- void packRequestFailed(QString reason);
-
- void logoFailed(QString logo);
- void logoLoaded(QString logo, bool stale);
-
-private:
- void requestLogo(QString file, QString url);
-
-private:
- bool m_aborted = false;
-
- QList modpacks;
- LogoMap m_logoMap;
-
- NetJob::Ptr jobPtr;
- int currentPack;
- QList remainingPacks;
- QByteArray response;
-};
-
-}
diff --git a/launcher/ui/pages/modplatform/ftb/FtbPage.cpp b/launcher/ui/pages/modplatform/ftb/FtbPage.cpp
deleted file mode 100644
index 7d59a6ae..00000000
--- a/launcher/ui/pages/modplatform/ftb/FtbPage.cpp
+++ /dev/null
@@ -1,199 +0,0 @@
-// SPDX-License-Identifier: GPL-3.0-only
-/*
- * PolyMC - Minecraft Launcher
- * Copyright (c) 2022 Jamie Mansfield
- * Copyright (C) 2022 Sefa Eyeoglu
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, version 3.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- *
- * This file incorporates work covered by the following copyright and
- * permission notice:
- *
- * Copyright 2020-2021 Jamie Mansfield
- * Copyright 2021 Philip T
- *
- * 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 "FtbPage.h"
-#include "ui_FtbPage.h"
-
-#include
-
-#include "ui/dialogs/NewInstanceDialog.h"
-#include "modplatform/modpacksch/FTBPackInstallTask.h"
-
-#include "Markdown.h"
-
-FtbPage::FtbPage(NewInstanceDialog* dialog, QWidget *parent)
- : QWidget(parent), ui(new Ui::FtbPage), dialog(dialog)
-{
- ui->setupUi(this);
-
- filterModel = new Ftb::FilterModel(this);
- listModel = new Ftb::ListModel(this);
- filterModel->setSourceModel(listModel);
- ui->packView->setModel(filterModel);
- ui->packView->setSortingEnabled(true);
- ui->packView->header()->hide();
- ui->packView->setIndentation(0);
-
- ui->searchEdit->installEventFilter(this);
-
- ui->versionSelectionBox->view()->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
- ui->versionSelectionBox->view()->parentWidget()->setMaximumHeight(300);
-
- for(int i = 0; i < filterModel->getAvailableSortings().size(); i++)
- {
- ui->sortByBox->addItem(filterModel->getAvailableSortings().keys().at(i));
- }
- ui->sortByBox->setCurrentText(filterModel->translateCurrentSorting());
-
- connect(ui->searchEdit, &QLineEdit::textChanged, this, &FtbPage::triggerSearch);
- connect(ui->sortByBox, &QComboBox::currentTextChanged, this, &FtbPage::onSortingSelectionChanged);
- connect(ui->packView->selectionModel(), &QItemSelectionModel::currentChanged, this, &FtbPage::onSelectionChanged);
- connect(ui->versionSelectionBox, &QComboBox::currentTextChanged, this, &FtbPage::onVersionSelectionChanged);
-
- ui->packDescription->setMetaEntry("FTBPacks");
-}
-
-FtbPage::~FtbPage()
-{
- delete ui;
-}
-
-bool FtbPage::eventFilter(QObject* watched, QEvent* event)
-{
- if (watched == ui->searchEdit && event->type() == QEvent::KeyPress) {
- QKeyEvent* keyEvent = static_cast(event);
- if (keyEvent->key() == Qt::Key_Return) {
- triggerSearch();
- keyEvent->accept();
- return true;
- }
- }
- return QWidget::eventFilter(watched, event);
-}
-
-bool FtbPage::shouldDisplay() const
-{
- return true;
-}
-
-void FtbPage::retranslate()
-{
- ui->retranslateUi(this);
-}
-
-void FtbPage::openedImpl()
-{
- if(!initialised || listModel->wasAborted())
- {
- listModel->request();
- initialised = true;
- }
-
- suggestCurrent();
-}
-
-void FtbPage::closedImpl()
-{
- if (listModel->isMakingRequest())
- listModel->abortRequest();
-}
-
-void FtbPage::suggestCurrent()
-{
- if(!isOpened)
- {
- return;
- }
-
- if (selectedVersion.isEmpty())
- {
- dialog->setSuggestedPack();
- return;
- }
-
- dialog->setSuggestedPack(selected.name, selectedVersion, new ModpacksCH::PackInstallTask(selected, selectedVersion, this));
- for(auto art : selected.art) {
- if(art.type == "square") {
- QString editedLogoName;
- editedLogoName = selected.name;
-
- listModel->getLogo(selected.name, art.url, [this, editedLogoName](QString logo)
- {
- dialog->setSuggestedIconFromFile(logo + ".small", editedLogoName);
- });
- }
- }
-}
-
-void FtbPage::triggerSearch()
-{
- filterModel->setSearchTerm(ui->searchEdit->text());
-}
-
-void FtbPage::onSortingSelectionChanged(QString data)
-{
- auto toSet = filterModel->getAvailableSortings().value(data);
- filterModel->setSorting(toSet);
-}
-
-void FtbPage::onSelectionChanged(QModelIndex first, QModelIndex second)
-{
- ui->versionSelectionBox->clear();
-
- if(!first.isValid())
- {
- if(isOpened)
- {
- dialog->setSuggestedPack();
- }
- return;
- }
-
- selected = filterModel->data(first, Qt::UserRole).value();
-
- QString output = markdownToHTML(selected.description.toUtf8());
- ui->packDescription->setHtml(output);
-
- // reverse foreach, so that the newest versions are first
- for (auto i = selected.versions.size(); i--;) {
- ui->versionSelectionBox->addItem(selected.versions.at(i).name);
- }
-
- suggestCurrent();
-}
-
-void FtbPage::onVersionSelectionChanged(QString data)
-{
- if(data.isNull() || data.isEmpty())
- {
- selectedVersion = "";
- return;
- }
-
- selectedVersion = data;
- suggestCurrent();
-}
diff --git a/launcher/ui/pages/modplatform/ftb/FtbPage.h b/launcher/ui/pages/modplatform/ftb/FtbPage.h
deleted file mode 100644
index 631ae7f5..00000000
--- a/launcher/ui/pages/modplatform/ftb/FtbPage.h
+++ /dev/null
@@ -1,105 +0,0 @@
-// SPDX-License-Identifier: GPL-3.0-only
-/*
- * PolyMC - Minecraft Launcher
- * Copyright (c) 2022 Jamie Mansfield
- *
- * 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 "FtbFilterModel.h"
-#include "FtbListModel.h"
-
-#include
-
-#include "Application.h"
-#include "ui/pages/BasePage.h"
-#include "tasks/Task.h"
-
-namespace Ui
-{
- class FtbPage;
-}
-
-class NewInstanceDialog;
-
-class FtbPage : public QWidget, public BasePage
-{
-Q_OBJECT
-
-public:
- explicit FtbPage(NewInstanceDialog* dialog, QWidget *parent = 0);
- virtual ~FtbPage();
- virtual QString displayName() const override
- {
- return "FTB";
- }
- virtual QIcon icon() const override
- {
- return APPLICATION->getThemedIcon("ftb_logo");
- }
- virtual QString id() const override
- {
- return "ftb";
- }
- virtual QString helpPage() const override
- {
- return "FTB-platform";
- }
- virtual bool shouldDisplay() const override;
- void retranslate() override;
-
- void openedImpl() override;
- void closedImpl() override;
-
- bool eventFilter(QObject * watched, QEvent * event) override;
-
-private:
- void suggestCurrent();
-
-private slots:
- void triggerSearch();
-
- void onSortingSelectionChanged(QString data);
- void onSelectionChanged(QModelIndex first, QModelIndex second);
- void onVersionSelectionChanged(QString data);
-
-private:
- Ui::FtbPage *ui = nullptr;
- NewInstanceDialog* dialog = nullptr;
- Ftb::ListModel* listModel = nullptr;
- Ftb::FilterModel* filterModel = nullptr;
-
- ModpacksCH::Modpack selected;
- QString selectedVersion;
-
- bool initialised { false };
-};
diff --git a/launcher/ui/pages/modplatform/ftb/FtbPage.ui b/launcher/ui/pages/modplatform/ftb/FtbPage.ui
deleted file mode 100644
index 8de0f4e6..00000000
--- a/launcher/ui/pages/modplatform/ftb/FtbPage.ui
+++ /dev/null
@@ -1,86 +0,0 @@
-
-
- FtbPage
-
-
-
- 0
- 0
- 875
- 745
-
-
-
- -
-
-
-
-
-
- -
-
-
- Version selected:
-
-
- Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
-
-
-
- -
-
-
-
-
- -
-
-
- Search and filter...
-
-
- true
-
-
-
- -
-
-
-
-
-
- true
-
-
-
- 48
- 48
-
-
-
-
- -
-
-
- true
-
-
- true
-
-
-
-
-
-
-
-
-
- ProjectDescriptionPage
- QTextBrowser
- ui/widgets/ProjectDescriptionPage.h
-
-
-
- searchEdit
- versionSelectionBox
-
-
-
-
From 788fa40c2ae0e9786c070f4593a4e7ff6efc77d3 Mon Sep 17 00:00:00 2001
From: Rachel Powers <508861+Ryex@users.noreply.github.com>
Date: Sat, 29 Apr 2023 18:05:48 -0700
Subject: [PATCH 077/104] refactor: Move ini to use QSettings && drop
get/setList functions
Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com>
---
launcher/BaseInstance.cpp | 12 +-
launcher/CMakeLists.txt | 1 +
launcher/FileSystem.cpp | 3 +-
launcher/QVariantUtils.h | 70 ++++++++
launcher/StringUtils.cpp | 36 ++++
launcher/StringUtils.h | 36 ++++
.../minecraft/mod/tasks/LocalModParseTask.cpp | 6 +-
launcher/settings/INIFile.cpp | 155 ++++--------------
launcher/settings/INIFile.h | 76 ++++-----
launcher/settings/SettingsObject.cpp | 13 --
launcher/settings/SettingsObject.h | 39 -----
tests/INIFile_test.cpp | 24 +--
12 files changed, 220 insertions(+), 251 deletions(-)
create mode 100644 launcher/QVariantUtils.h
diff --git a/launcher/BaseInstance.cpp b/launcher/BaseInstance.cpp
index ad45aa2d..a8fce879 100644
--- a/launcher/BaseInstance.cpp
+++ b/launcher/BaseInstance.cpp
@@ -188,25 +188,25 @@ bool BaseInstance::shouldStopOnConsoleOverflow() const
QStringList BaseInstance::getLinkedInstances() const
{
- return m_settings->getList("linkedInstances");
+ return m_settings->get("linkedInstances").toStringList();
}
void BaseInstance::setLinkedInstances(const QStringList& list)
{
- auto linkedInstances = m_settings->getList("linkedInstances");
- m_settings->setList("linkedInstances", list);
+ auto linkedInstances = m_settings->get("linkedInstances").toStringList();
+ m_settings->set("linkedInstances", list);
}
void BaseInstance::addLinkedInstanceId(const QString& id)
{
- auto linkedInstances = m_settings->getList("linkedInstances");
+ auto linkedInstances = m_settings->get("linkedInstances").toStringList();
linkedInstances.append(id);
setLinkedInstances(linkedInstances);
}
bool BaseInstance::removeLinkedInstanceId(const QString& id)
{
- auto linkedInstances = m_settings->getList("linkedInstances");
+ auto linkedInstances = m_settings->get("linkedInstances").toStringList();
int numRemoved = linkedInstances.removeAll(id);
setLinkedInstances(linkedInstances);
return numRemoved > 0;
@@ -214,7 +214,7 @@ bool BaseInstance::removeLinkedInstanceId(const QString& id)
bool BaseInstance::isLinkedToInstanceId(const QString& id) const
{
- auto linkedInstances = m_settings->getList("linkedInstances");
+ auto linkedInstances = m_settings->get("linkedInstances").toStringList();
return linkedInstances.contains(id);
}
diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt
index 4de0f73b..def73b85 100644
--- a/launcher/CMakeLists.txt
+++ b/launcher/CMakeLists.txt
@@ -26,6 +26,7 @@ set(CORE_SOURCES
MMCZip.cpp
StringUtils.h
StringUtils.cpp
+ QVariantUtils.h
RuntimeContext.h
# Basic instance manipulation tasks (derived from InstanceTask)
diff --git a/launcher/FileSystem.cpp b/launcher/FileSystem.cpp
index bd985c5b..d98526df 100644
--- a/launcher/FileSystem.cpp
+++ b/launcher/FileSystem.cpp
@@ -329,8 +329,7 @@ bool create_link::operator()(const QString& offset, bool dryRun)
/**
* @brief Make a list of all the links to make
- * @param offset subdirectory form src to link to dest
- * @return if there was an error during the attempt to link
+ * @param offset subdirectory of src to link to dest
*/
void create_link::make_link_list(const QString& offset)
{
diff --git a/launcher/QVariantUtils.h b/launcher/QVariantUtils.h
new file mode 100644
index 00000000..7e422c3e
--- /dev/null
+++ b/launcher/QVariantUtils.h
@@ -0,0 +1,70 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * Prism Launcher - Minecraft Launcher
+ * Copyright (C) 2022 Sefa Eyeoglu
+ * Copyright (C) 2023 flowln
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * 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
+
+namespace QVariantUtils {
+
+template
+inline QList toList(QVariant src) {
+ QVariantList variantList = src.toList();
+
+ QList list_t;
+ list_t.reserve(variantList.size());
+ for (const QVariant& v : variantList)
+ {
+ list_t.append(v.value());
+ }
+ return list_t;
+}
+
+template
+inline QVariant fromList(QList val) {
+ QVariantList variantList;
+ variantList.reserve(val.size());
+ for (const T& v : val)
+ {
+ variantList.append(v);
+ }
+
+ return variantList;
+}
+
+}
\ No newline at end of file
diff --git a/launcher/StringUtils.cpp b/launcher/StringUtils.cpp
index 2fa56501..d109d63d 100644
--- a/launcher/StringUtils.cpp
+++ b/launcher/StringUtils.cpp
@@ -1,3 +1,39 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * Prism Launcher - Minecraft Launcher
+ * Copyright (C) 2022 Sefa Eyeoglu
+ * Copyright (C) 2023 flowln
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * 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 "StringUtils.h"
#include
diff --git a/launcher/StringUtils.h b/launcher/StringUtils.h
index c4a6ab31..36c8cf8f 100644
--- a/launcher/StringUtils.h
+++ b/launcher/StringUtils.h
@@ -1,3 +1,39 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * Prism Launcher - Minecraft Launcher
+ * Copyright (C) 2022 Sefa Eyeoglu
+ * Copyright (C) 2023 flowln
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * 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
diff --git a/launcher/minecraft/mod/tasks/LocalModParseTask.cpp b/launcher/minecraft/mod/tasks/LocalModParseTask.cpp
index da27a505..5342d693 100644
--- a/launcher/minecraft/mod/tasks/LocalModParseTask.cpp
+++ b/launcher/minecraft/mod/tasks/LocalModParseTask.cpp
@@ -242,7 +242,7 @@ ModDetails ReadQuiltModInfo(QByteArray contents)
return details;
}
-ModDetails ReadForgeInfo(QByteArray contents)
+ModDetails ReadForgeInfo(QString fileName)
{
ModDetails details;
// Read the data
@@ -250,7 +250,7 @@ ModDetails ReadForgeInfo(QByteArray contents)
details.mod_id = "Forge";
details.homeurl = "http://www.minecraftforge.net/forum/";
INIFile ini;
- if (!ini.loadFile(contents))
+ if (!ini.loadFile(fileName))
return details;
QString major = ini.get("forge.major.number", "0").toString();
@@ -422,7 +422,7 @@ bool processZIP(Mod& mod, ProcessingLevel level)
return false;
}
- details = ReadForgeInfo(file.readAll());
+ details = ReadForgeInfo(file.getFileName());
file.close();
zip.close();
diff --git a/launcher/settings/INIFile.cpp b/launcher/settings/INIFile.cpp
index e48e6f47..f0347cab 100644
--- a/launcher/settings/INIFile.cpp
+++ b/launcher/settings/INIFile.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 flowln
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -42,132 +43,51 @@
#include
#include