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 01/50] 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 02/50] 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 03/50] 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 04/50] 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 05/50] 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 06/50] 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 07/50] 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 08/50] 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 09/50] 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 10/50] 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 11/50] 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 12/50] 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 13/50] 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 14/50] 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 15/50] 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 16/50] 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 17/50] 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 18/50] 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 19/50] 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 20/50] 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 21/50] 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 22/50] 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 23/50] 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 24/50] 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 25/50] 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 26/50] 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 27/50] 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 28/50] 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 29/50] 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 30/50] 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 31/50] 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 32/50] 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 33/50] 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 34/50] 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 35/50] 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 36/50] 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 37/50] 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 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 38/50] 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 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 39/50] 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 40/50] 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 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 41/50] 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 42/50] 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 43/50] 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 44/50] 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 45/50] 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 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 46/50] 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
+#include
+
INIFile::INIFile()
{
}
-QString INIFile::unescape(QString orig)
-{
- QString out;
- QChar prev = QChar::Null;
- for(auto c: orig)
- {
- if(prev == '\\')
- {
- if(c == 'n')
- out += '\n';
- else if(c == 't')
- out += '\t';
- else if(c == '#')
- out += '#';
- else
- out += c;
- prev = QChar::Null;
- }
- else
- {
- if(c == '\\')
- {
- prev = c;
- continue;
- }
- out += c;
- prev = QChar::Null;
- }
- }
- return out;
-}
-
-QString INIFile::escape(QString orig)
-{
- QString out;
- for(auto c: orig)
- {
- if(c == '\n')
- out += "\\n";
- else if (c == '\t')
- out += "\\t";
- else if(c == '\\')
- out += "\\\\";
- else if(c == '#')
- out += "\\#";
- else
- out += c;
- }
- return out;
-}
-
bool INIFile::saveFile(QString fileName)
{
- QByteArray outArray;
- for (Iterator iter = begin(); iter != end(); iter++)
- {
- QString value = iter.value().toString();
- value = escape(value);
- outArray.append(iter.key().toUtf8());
- outArray.append('=');
- outArray.append(value.toUtf8());
- outArray.append('\n');
- }
+ QSettings _settings_obj{ fileName, QSettings::Format::IniFormat };
+ _settings_obj.setFallbacksEnabled(false);
+
+ for (Iterator iter = begin(); iter != end(); iter++)
+ _settings_obj.setValue(iter.key(), iter.value());
+
+ _settings_obj.sync();
+
+ if (auto status = _settings_obj.status(); status != QSettings::Status::NoError) {
+ // Shouldn't be possible!
+ Q_ASSERT(status != QSettings::Status::FormatError);
+
+ if (status == QSettings::Status::AccessError)
+ qCritical() << "An access error occurred (e.g. trying to write to a read-only file).";
- try
- {
- FS::write(fileName, outArray);
- }
- catch (const Exception &e)
- {
- qCritical() << e.what();
return false;
}
return true;
}
-
bool INIFile::loadFile(QString fileName)
{
- QFile file(fileName);
- if (!file.open(QIODevice::ReadOnly))
+ QSettings _settings_obj{ fileName, QSettings::Format::IniFormat };
+ _settings_obj.setFallbacksEnabled(false);
+
+ if (auto status = _settings_obj.status(); status != QSettings::Status::NoError) {
+ if (status == QSettings::Status::AccessError)
+ qCritical() << "An access error occurred (e.g. trying to write to a read-only file).";
+ if (status == QSettings::Status::FormatError)
+ qCritical() << "A format error occurred (e.g. loading a malformed INI file).";
return false;
- bool success = loadFile(file.readAll());
- file.close();
- return success;
-}
-
-bool INIFile::loadFile(QByteArray file)
-{
- QTextStream in(file);
-#if QT_VERSION <= QT_VERSION_CHECK(6, 0, 0)
- in.setCodec("UTF-8");
-#endif
-
- QStringList lines = in.readAll().split('\n');
- for (int i = 0; i < lines.count(); i++)
- {
- QString &lineRaw = lines[i];
- // Ignore comments.
- int commentIndex = 0;
- QString line = lineRaw;
- // Search for comments until no more escaped # are available
- while((commentIndex = line.indexOf('#', commentIndex + 1)) != -1) {
- if(commentIndex > 0 && line.at(commentIndex - 1) == '\\') {
- continue;
- }
- line = line.left(lineRaw.indexOf('#')).trimmed();
- }
-
- int eqPos = line.indexOf('=');
- if (eqPos == -1)
- continue;
- QString key = line.left(eqPos).trimmed();
- QString valueStr = line.right(line.length() - eqPos - 1).trimmed();
-
- valueStr = unescape(valueStr);
-
- QVariant value(valueStr);
- this->operator[](key) = value;
}
+ for (auto&& key : _settings_obj.allKeys())
+ insert(key, _settings_obj.value(key));
+
return true;
}
@@ -184,20 +104,3 @@ 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 86bf0898..0d5c05eb 100644
--- a/launcher/settings/INIFile.h
+++ b/launcher/settings/INIFile.h
@@ -1,16 +1,37 @@
-/* Copyright 2013-2021 MultiMC Contributors
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * Prism Launcher - Minecraft Launcher
+ * Copyright (C) 2022 Sefa Eyeoglu
+ * Copyright (C) 2023 flowln
*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3.
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
*
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * This file incorporates work covered by the following copyright and
+ * permission notice:
+ *
+ * Copyright 2013-2021 MultiMC Contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
*/
#pragma once
@@ -28,44 +49,9 @@ class INIFile : public QMap
public:
explicit INIFile();
- bool loadFile(QByteArray file);
bool loadFile(QString fileName);
bool saveFile(QString fileName);
QVariant get(QString key, QVariant def) const;
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 4c51d6e9..8a0bc045 100644
--- a/launcher/settings/SettingsObject.cpp
+++ b/launcher/settings/SettingsObject.cpp
@@ -121,19 +121,6 @@ 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 ff430172..4d735511 100644
--- a/launcher/settings/SettingsObject.h
+++ b/launcher/settings/SettingsObject.h
@@ -144,45 +144,6 @@ 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/tests/INIFile_test.cpp b/tests/INIFile_test.cpp
index d13937c0..4be8133c 100644
--- a/tests/INIFile_test.cpp
+++ b/tests/INIFile_test.cpp
@@ -4,6 +4,7 @@
#include
#include
+#include
class IniFileTest : public QObject
{
@@ -30,15 +31,6 @@ slots:
QTest::newRow("Escape sequences 2") << "\"\n\n\"";
QTest::newRow("Hashtags") << "some data#something";
}
- void test_Escape()
- {
- QFETCH(QString, through);
-
- QString there = INIFile::escape(through);
- QString back = INIFile::unescape(there);
-
- QCOMPARE(back, through);
- }
void test_SaveLoad()
{
@@ -61,32 +53,30 @@ slots:
void test_SaveLoadLists()
{
- QString slist_strings = "[\"a\",\"b\",\"c\"]";
+ QString slist_strings = "(\"a\",\"b\",\"c\")";
QStringList list_strings = {"a", "b", "c"};
- QString slist_numbers = "[1,2,3,10]";
+ 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.set("list_strings", list_strings);
+ f.set("list_numbers", QVariantUtils::fromList(list_numbers));
f.saveFile(filename);
// load
INIFile f2;
f2.loadFile(filename);
- QStringList out_list_strings = f2.getList("list_strings", QStringList());
+ QStringList out_list_strings = f2.get("list_strings", QStringList()).toStringList();
qDebug() << "OutStringList" << out_list_strings;
- QList out_list_numbers = f2.getList("list_numbers", QList());
+ QList out_list_numbers = QVariantUtils::toList(f2.get("list_numbers", QVariantUtils::fromList(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 d80dee2a54a172fa19c0bc21486ee43ef2e3514d Mon Sep 17 00:00:00 2001
From: Rachel Powers <508861+Ryex@users.noreply.github.com>
Date: Sat, 29 Apr 2023 19:38:51 -0700
Subject: [PATCH 47/50] refactor: pass instance ptr to resource models. use it
to find instance root.
Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com>
---
launcher/minecraft/MinecraftInstance.cpp | 14 +++++++-------
launcher/minecraft/WorldList.cpp | 6 +++---
launcher/minecraft/WorldList.h | 4 +++-
launcher/minecraft/mod/ModFolderModel.cpp | 3 ++-
launcher/minecraft/mod/ModFolderModel.h | 2 +-
launcher/minecraft/mod/ResourceFolderModel.cpp | 7 ++++---
launcher/minecraft/mod/ResourceFolderModel.h | 5 ++++-
.../minecraft/mod/ResourcePackFolderModel.cpp | 3 ++-
.../minecraft/mod/ResourcePackFolderModel.h | 2 +-
launcher/minecraft/mod/ShaderPackFolderModel.h | 4 +++-
.../minecraft/mod/TexturePackFolderModel.cpp | 4 +++-
.../minecraft/mod/TexturePackFolderModel.h | 2 +-
launcher/ui/MainWindow.cpp | 4 ++--
launcher/ui/dialogs/CopyInstanceDialog.cpp | 2 +-
tests/ResourceFolderModel_test.cpp | 18 ++++++++++++------
15 files changed, 49 insertions(+), 31 deletions(-)
diff --git a/launcher/minecraft/MinecraftInstance.cpp b/launcher/minecraft/MinecraftInstance.cpp
index af4da5d0..6b7ca84f 100644
--- a/launcher/minecraft/MinecraftInstance.cpp
+++ b/launcher/minecraft/MinecraftInstance.cpp
@@ -1111,7 +1111,7 @@ std::shared_ptr MinecraftInstance::loaderModList() const
if (!m_loader_mod_list)
{
bool is_indexed = !APPLICATION->settings()->get("ModMetadataDisabled").toBool();
- m_loader_mod_list.reset(new ModFolderModel(modsRoot(), is_indexed));
+ m_loader_mod_list.reset(new ModFolderModel(modsRoot(), shared_from_this(), is_indexed));
m_loader_mod_list->disableInteraction(isRunning());
connect(this, &BaseInstance::runningStatusChanged, m_loader_mod_list.get(), &ModFolderModel::disableInteraction);
}
@@ -1123,7 +1123,7 @@ std::shared_ptr MinecraftInstance::coreModList() const
if (!m_core_mod_list)
{
bool is_indexed = !APPLICATION->settings()->get("ModMetadataDisabled").toBool();
- m_core_mod_list.reset(new ModFolderModel(coreModsDir(), is_indexed));
+ m_core_mod_list.reset(new ModFolderModel(coreModsDir(), shared_from_this(), is_indexed));
m_core_mod_list->disableInteraction(isRunning());
connect(this, &BaseInstance::runningStatusChanged, m_core_mod_list.get(), &ModFolderModel::disableInteraction);
}
@@ -1135,7 +1135,7 @@ std::shared_ptr MinecraftInstance::nilModList() const
if (!m_nil_mod_list)
{
bool is_indexed = !APPLICATION->settings()->get("ModMetadataDisabled").toBool();
- m_nil_mod_list.reset(new ModFolderModel(nilModsDir(), is_indexed, false));
+ m_nil_mod_list.reset(new ModFolderModel(nilModsDir(), shared_from_this(), is_indexed, false));
m_nil_mod_list->disableInteraction(isRunning());
connect(this, &BaseInstance::runningStatusChanged, m_nil_mod_list.get(), &ModFolderModel::disableInteraction);
}
@@ -1146,7 +1146,7 @@ std::shared_ptr MinecraftInstance::resourcePackList() c
{
if (!m_resource_pack_list)
{
- m_resource_pack_list.reset(new ResourcePackFolderModel(resourcePacksDir()));
+ m_resource_pack_list.reset(new ResourcePackFolderModel(resourcePacksDir(), shared_from_this()));
}
return m_resource_pack_list;
}
@@ -1155,7 +1155,7 @@ std::shared_ptr MinecraftInstance::texturePackList() con
{
if (!m_texture_pack_list)
{
- m_texture_pack_list.reset(new TexturePackFolderModel(texturePacksDir()));
+ m_texture_pack_list.reset(new TexturePackFolderModel(texturePacksDir(), shared_from_this()));
}
return m_texture_pack_list;
}
@@ -1164,7 +1164,7 @@ std::shared_ptr MinecraftInstance::shaderPackList() const
{
if (!m_shader_pack_list)
{
- m_shader_pack_list.reset(new ShaderPackFolderModel(shaderPacksDir()));
+ m_shader_pack_list.reset(new ShaderPackFolderModel(shaderPacksDir(), shared_from_this()));
}
return m_shader_pack_list;
}
@@ -1173,7 +1173,7 @@ std::shared_ptr MinecraftInstance::worldList() const
{
if (!m_world_list)
{
- m_world_list.reset(new WorldList(worldDir()));
+ m_world_list.reset(new WorldList(worldDir(), shared_from_this()));
}
return m_world_list;
}
diff --git a/launcher/minecraft/WorldList.cpp b/launcher/minecraft/WorldList.cpp
index 3681bcda..62e55cd4 100644
--- a/launcher/minecraft/WorldList.cpp
+++ b/launcher/minecraft/WorldList.cpp
@@ -45,8 +45,8 @@
#include
#include
-WorldList::WorldList(const QString &dir)
- : QAbstractListModel(), m_dir(dir)
+WorldList::WorldList(const QString &dir, std::shared_ptr instance)
+ : QAbstractListModel(), m_instance(instance), m_dir(dir)
{
FS::ensureFolderPathExists(m_dir.absolutePath());
m_dir.setFilter(QDir::Readable | QDir::NoDotAndDotDot | QDir::Files | QDir::Dirs);
@@ -129,7 +129,7 @@ bool WorldList::isValid()
}
QString WorldList::instDirPath() const {
- return QFileInfo(m_dir.filePath("../..")).absoluteFilePath();
+ return QFileInfo(m_instance->instanceRoot()).absoluteFilePath();
}
bool WorldList::deleteWorld(int index)
diff --git a/launcher/minecraft/WorldList.h b/launcher/minecraft/WorldList.h
index bd32dd4e..10fb4e3c 100644
--- a/launcher/minecraft/WorldList.h
+++ b/launcher/minecraft/WorldList.h
@@ -21,6 +21,7 @@
#include
#include
#include "minecraft/World.h"
+#include "BaseInstance.h"
class QFileSystemWatcher;
@@ -49,7 +50,7 @@ public:
IconFileRole
};
- WorldList(const QString &dir);
+ WorldList(const QString &dir, std::shared_ptr instance);
virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;
@@ -127,6 +128,7 @@ signals:
void changed();
protected:
+ std::shared_ptr m_instance;
QFileSystemWatcher *m_watcher;
bool is_watching;
QDir m_dir;
diff --git a/launcher/minecraft/mod/ModFolderModel.cpp b/launcher/minecraft/mod/ModFolderModel.cpp
index 91d16175..6ae25d33 100644
--- a/launcher/minecraft/mod/ModFolderModel.cpp
+++ b/launcher/minecraft/mod/ModFolderModel.cpp
@@ -54,7 +54,8 @@
#include "minecraft/mod/tasks/ModFolderLoadTask.h"
#include "modplatform/ModIndex.h"
-ModFolderModel::ModFolderModel(const QString &dir, bool is_indexed, bool create_dir) : ResourceFolderModel(QDir(dir), nullptr, create_dir), m_is_indexed(is_indexed)
+ModFolderModel::ModFolderModel(const QString& dir, std::shared_ptr instance, bool is_indexed, bool create_dir)
+ : ResourceFolderModel(QDir(dir), instance, nullptr, create_dir), m_is_indexed(is_indexed)
{
m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::VERSION, SortType::DATE, SortType::PROVIDER };
}
diff --git a/launcher/minecraft/mod/ModFolderModel.h b/launcher/minecraft/mod/ModFolderModel.h
index 84e70db9..46f5087f 100644
--- a/launcher/minecraft/mod/ModFolderModel.h
+++ b/launcher/minecraft/mod/ModFolderModel.h
@@ -75,7 +75,7 @@ public:
Enable,
Toggle
};
- ModFolderModel(const QString &dir, bool is_indexed = false, bool create_dir = true);
+ ModFolderModel(const QString &dir, std::shared_ptr instance, bool is_indexed = false, bool create_dir = true);
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
diff --git a/launcher/minecraft/mod/ResourceFolderModel.cpp b/launcher/minecraft/mod/ResourceFolderModel.cpp
index 29a0c736..e1973468 100644
--- a/launcher/minecraft/mod/ResourceFolderModel.cpp
+++ b/launcher/minecraft/mod/ResourceFolderModel.cpp
@@ -16,7 +16,8 @@
#include "tasks/Task.h"
-ResourceFolderModel::ResourceFolderModel(QDir dir, QObject* parent, bool create_dir) : QAbstractListModel(parent), m_dir(dir), m_watcher(this)
+ResourceFolderModel::ResourceFolderModel(QDir dir, std::shared_ptr instance, QObject* parent, bool create_dir)
+ : QAbstractListModel(parent), m_dir(dir), m_instance(instance), m_watcher(this)
{
if (create_dir) {
FS::ensureFolderPathExists(m_dir.absolutePath());
@@ -26,7 +27,7 @@ ResourceFolderModel::ResourceFolderModel(QDir dir, QObject* parent, bool create_
m_dir.setSorting(QDir::Name | QDir::IgnoreCase | QDir::LocaleAware);
connect(&m_watcher, &QFileSystemWatcher::directoryChanged, this, &ResourceFolderModel::directoryChanged);
- connect(&m_helper_thread_task, &ConcurrentTask::finished, this, [this]{ m_helper_thread_task.clear(); });
+ connect(&m_helper_thread_task, &ConcurrentTask::finished, this, [this] { m_helper_thread_task.clear(); });
}
ResourceFolderModel::~ResourceFolderModel()
@@ -556,5 +557,5 @@ void ResourceFolderModel::enableInteraction(bool enabled)
}
QString ResourceFolderModel::instDirPath() const {
- return QFileInfo(m_dir.filePath("../..")).absoluteFilePath();
+ return QFileInfo(m_instance->instanceRoot()).absoluteFilePath();
}
diff --git a/launcher/minecraft/mod/ResourceFolderModel.h b/launcher/minecraft/mod/ResourceFolderModel.h
index f840b2de..fdf5f331 100644
--- a/launcher/minecraft/mod/ResourceFolderModel.h
+++ b/launcher/minecraft/mod/ResourceFolderModel.h
@@ -9,6 +9,8 @@
#include "Resource.h"
+#include "BaseInstance.h"
+
#include "tasks/Task.h"
#include "tasks/ConcurrentTask.h"
@@ -24,7 +26,7 @@ class QSortFilterProxyModel;
class ResourceFolderModel : public QAbstractListModel {
Q_OBJECT
public:
- ResourceFolderModel(QDir, QObject* parent = nullptr, bool create_dir = true);
+ ResourceFolderModel(QDir, std::shared_ptr, QObject* parent = nullptr, bool create_dir = true);
~ResourceFolderModel() override;
/** Starts watching the paths for changes.
@@ -189,6 +191,7 @@ class ResourceFolderModel : public QAbstractListModel {
bool m_can_interact = true;
QDir m_dir;
+ std::shared_ptr m_instance;
QFileSystemWatcher m_watcher;
bool m_is_watching = false;
diff --git a/launcher/minecraft/mod/ResourcePackFolderModel.cpp b/launcher/minecraft/mod/ResourcePackFolderModel.cpp
index 0480d8ba..6eba4e2e 100644
--- a/launcher/minecraft/mod/ResourcePackFolderModel.cpp
+++ b/launcher/minecraft/mod/ResourcePackFolderModel.cpp
@@ -45,7 +45,8 @@
#include "minecraft/mod/tasks/BasicFolderLoadTask.h"
#include "minecraft/mod/tasks/LocalResourcePackParseTask.h"
-ResourcePackFolderModel::ResourcePackFolderModel(const QString& dir) : ResourceFolderModel(QDir(dir))
+ResourcePackFolderModel::ResourcePackFolderModel(const QString& dir, std::shared_ptr instance)
+ : ResourceFolderModel(QDir(dir), instance)
{
m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::PACK_FORMAT, SortType::DATE };
}
diff --git a/launcher/minecraft/mod/ResourcePackFolderModel.h b/launcher/minecraft/mod/ResourcePackFolderModel.h
index cb620ce2..66d5a074 100644
--- a/launcher/minecraft/mod/ResourcePackFolderModel.h
+++ b/launcher/minecraft/mod/ResourcePackFolderModel.h
@@ -17,7 +17,7 @@ public:
NUM_COLUMNS
};
- explicit ResourcePackFolderModel(const QString &dir);
+ explicit ResourcePackFolderModel(const QString &dir, std::shared_ptr instance);
[[nodiscard]] QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
diff --git a/launcher/minecraft/mod/ShaderPackFolderModel.h b/launcher/minecraft/mod/ShaderPackFolderModel.h
index a3aa958f..6f3f2811 100644
--- a/launcher/minecraft/mod/ShaderPackFolderModel.h
+++ b/launcher/minecraft/mod/ShaderPackFolderModel.h
@@ -6,5 +6,7 @@ class ShaderPackFolderModel : public ResourceFolderModel {
Q_OBJECT
public:
- explicit ShaderPackFolderModel(const QString& dir) : ResourceFolderModel(QDir(dir)) {}
+ explicit ShaderPackFolderModel(const QString& dir, std::shared_ptr instance)
+ : ResourceFolderModel(QDir(dir), instance)
+ {}
};
diff --git a/launcher/minecraft/mod/TexturePackFolderModel.cpp b/launcher/minecraft/mod/TexturePackFolderModel.cpp
index 5a32cfaf..1e218537 100644
--- a/launcher/minecraft/mod/TexturePackFolderModel.cpp
+++ b/launcher/minecraft/mod/TexturePackFolderModel.cpp
@@ -39,7 +39,9 @@
#include "minecraft/mod/tasks/BasicFolderLoadTask.h"
#include "minecraft/mod/tasks/LocalTexturePackParseTask.h"
-TexturePackFolderModel::TexturePackFolderModel(const QString &dir) : ResourceFolderModel(QDir(dir)) {}
+TexturePackFolderModel::TexturePackFolderModel(const QString& dir, std::shared_ptr instance)
+ : ResourceFolderModel(QDir(dir), instance)
+{}
Task* TexturePackFolderModel::createUpdateTask()
{
diff --git a/launcher/minecraft/mod/TexturePackFolderModel.h b/launcher/minecraft/mod/TexturePackFolderModel.h
index 261f83b4..246997bd 100644
--- a/launcher/minecraft/mod/TexturePackFolderModel.h
+++ b/launcher/minecraft/mod/TexturePackFolderModel.h
@@ -43,7 +43,7 @@ class TexturePackFolderModel : public ResourceFolderModel
Q_OBJECT
public:
- explicit TexturePackFolderModel(const QString &dir);
+ explicit TexturePackFolderModel(const QString &dir, std::shared_ptr instance);
[[nodiscard]] Task* createUpdateTask() override;
[[nodiscard]] Task* createParseTask(Resource&) override;
};
diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp
index e20a7613..72b7db64 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 following instance(s) might reference files in this instance:\n\n"
"%1\n\n"
"Deleting it could break the other instance(s), \n\n"
- "Do you wish to proceed?").arg(linkedInstances.join("\n")),
+ "Do you wish to proceed?", nullptr, linkedInstances.count()).arg(linkedInstances.join("\n")),
QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No, QMessageBox::No
)->exec();
if (response != QMessageBox::Yes)
diff --git a/launcher/ui/dialogs/CopyInstanceDialog.cpp b/launcher/ui/dialogs/CopyInstanceDialog.cpp
index 347bd39f..d75bb5fe 100644
--- a/launcher/ui/dialogs/CopyInstanceDialog.cpp
+++ b/launcher/ui/dialogs/CopyInstanceDialog.cpp
@@ -108,7 +108,7 @@ 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 permission to create."));
+ "\n" + tr("On Windows, symbolic links may require admin permission to create."));
#endif
updateLinkOptions();
diff --git a/tests/ResourceFolderModel_test.cpp b/tests/ResourceFolderModel_test.cpp
index e38b8e93..054d81c4 100644
--- a/tests/ResourceFolderModel_test.cpp
+++ b/tests/ResourceFolderModel_test.cpp
@@ -36,6 +36,7 @@
#include
#include
#include
+#include "BaseInstance.h"
#include
@@ -89,7 +90,9 @@ slots:
QEventLoop loop;
- ModFolderModel m(tempDir.path(), true);
+ InstancePtr instance;
+
+ ModFolderModel m(tempDir.path(), instance, true);
connect(&m, &ModFolderModel::updateFinished, &loop, &QEventLoop::quit);
@@ -113,7 +116,8 @@ slots:
QString folder = source + '/';
QTemporaryDir tempDir;
QEventLoop loop;
- ModFolderModel m(tempDir.path(), true);
+ InstancePtr instance;
+ ModFolderModel m(tempDir.path(), instance, true);
connect(&m, &ModFolderModel::updateFinished, &loop, &QEventLoop::quit);
@@ -136,8 +140,8 @@ slots:
void test_addFromWatch()
{
QString source = QFINDTESTDATA("testdata/ResourceFolderModel");
-
- ModFolderModel model(source);
+ InstancePtr instance;
+ ModFolderModel model(source, instance);
QCOMPARE(model.size(), 0);
@@ -157,8 +161,9 @@ slots:
QString file_mod = QFINDTESTDATA("testdata/ResourceFolderModel/supercoolmod.jar");
QTemporaryDir tmp;
+ InstancePtr instance;
- ResourceFolderModel model(QDir(tmp.path()));
+ ResourceFolderModel model(QDir(tmp.path()), instance);
QCOMPARE(model.size(), 0);
@@ -209,7 +214,8 @@ slots:
QString file_mod = QFINDTESTDATA("testdata/ResourceFolderModel/supercoolmod.jar");
QTemporaryDir tmp;
- ResourceFolderModel model(tmp.path());
+ InstancePtr instance;
+ ResourceFolderModel model(tmp.path(), instance);
QCOMPARE(model.size(), 0);
From 5ec4cbf1cbf660d992ddd73c67d31de27c097f54 Mon Sep 17 00:00:00 2001
From: Rachel Powers <508861+Ryex@users.noreply.github.com>
Date: Wed, 3 May 2023 20:31:15 -0700
Subject: [PATCH 48/50] fix add an addtion lax file name match with ` `
replaced with `+`
Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com>
---
launcher/ui/dialogs/BlockedModsDialog.cpp | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/launcher/ui/dialogs/BlockedModsDialog.cpp b/launcher/ui/dialogs/BlockedModsDialog.cpp
index ba453df6..3e1137c2 100644
--- a/launcher/ui/dialogs/BlockedModsDialog.cpp
+++ b/launcher/ui/dialogs/BlockedModsDialog.cpp
@@ -305,6 +305,9 @@ bool BlockedModsDialog::checkValidPath(QString path)
QString laxFilename(filename);
laxFilename.replace('+', ' ');
+ QString laxFilename2(filename);
+ laxFilename.replace(' ', '+');
+
auto compare = [](QString fsfilename, QString metadataFilename) {
return metadataFilename.compare(fsfilename, Qt::CaseInsensitive) == 0;
};
@@ -314,7 +317,7 @@ bool BlockedModsDialog::checkValidPath(QString path)
qDebug() << "[Blocked Mods Dialog] Name match found:" << mod.name << "| From path:" << path;
return true;
}
- if (compare(laxFilename, mod.name)) {
+ if (compare(laxFilename, mod.name) || compare(laxFilename2, mod.name)) {
qDebug() << "[Blocked Mods Dialog] Lax name match found:" << mod.name << "| From path:" << path;
return true;
}
From e0635955df2d1bf79e1ba61eb02074801b60953f Mon Sep 17 00:00:00 2001
From: Rachel Powers <508861+Ryex@users.noreply.github.com>
Date: Thu, 4 May 2023 13:30:39 -0700
Subject: [PATCH 49/50] fix: super lax compare
Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com>
---
launcher/ui/dialogs/BlockedModsDialog.cpp | 40 +++++++++++++++++------
1 file changed, 30 insertions(+), 10 deletions(-)
diff --git a/launcher/ui/dialogs/BlockedModsDialog.cpp b/launcher/ui/dialogs/BlockedModsDialog.cpp
index 3e1137c2..fdfae597 100644
--- a/launcher/ui/dialogs/BlockedModsDialog.cpp
+++ b/launcher/ui/dialogs/BlockedModsDialog.cpp
@@ -195,7 +195,7 @@ void BlockedModsDialog::watchPath(QString path, bool watch_recursive)
auto to_watch = QFileInfo(path);
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!)
+ return; // don't watch the same path twice (no loops!)
qDebug() << "[Blocked Mods Dialog] Adding Watch Path:" << path;
m_watcher.addPath(to_watch_path);
@@ -203,10 +203,9 @@ void BlockedModsDialog::watchPath(QString path, bool watch_recursive)
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
+ QString watch_dir = QDir(it.next()).canonicalPath(); // resolve symlinks and relative paths
watchPath(watch_dir, watch_recursive);
}
}
@@ -302,14 +301,35 @@ bool BlockedModsDialog::checkValidPath(QString path)
{
const QFileInfo file = QFileInfo(path);
const QString filename = file.fileName();
- QString laxFilename(filename);
- laxFilename.replace('+', ' ');
- QString laxFilename2(filename);
- laxFilename.replace(' ', '+');
+ auto compare = [](QString fsFilename, QString metadataFilename) {
+ return metadataFilename.compare(fsFilename, Qt::CaseInsensitive) == 0;
+ };
- auto compare = [](QString fsfilename, QString metadataFilename) {
- return metadataFilename.compare(fsfilename, Qt::CaseInsensitive) == 0;
+ // super lax compare (but not fuzzy)
+ // convert to lowercase
+ // convert all speratores to whitespace
+ // simplify sequence of internal whitespace to a single space
+ // efectivly compare two strings ignoring all separators and case
+ auto laxCompare = [](QString fsfilename, QString metadataFilename) {
+ // allowed character seperators
+ QList allowedSeperators = { '-', '+', '.' , '_'};
+
+ // copy in lowercase
+ auto fsName = fsfilename.toLower();
+ auto metaName = metadataFilename.toLower();
+
+ // replace all potential allowed seperatores with whitespace
+ for (auto sep : allowedSeperators) {
+ fsName = fsName.replace(sep, ' ');
+ metaName = metaName.replace(sep, ' ');
+ }
+
+ // remove extraneous whitespace
+ fsName = fsName.simplified();
+ metaName = metaName.simplified();
+
+ return fsName.compare(metaName) == 0;
};
for (auto& mod : m_mods) {
@@ -317,7 +337,7 @@ bool BlockedModsDialog::checkValidPath(QString path)
qDebug() << "[Blocked Mods Dialog] Name match found:" << mod.name << "| From path:" << path;
return true;
}
- if (compare(laxFilename, mod.name) || compare(laxFilename2, mod.name)) {
+ if (laxCompare(filename, mod.name)) {
qDebug() << "[Blocked Mods Dialog] Lax name match found:" << mod.name << "| From path:" << path;
return true;
}
From d38696f411ea0889d4152f999864912f1685120b Mon Sep 17 00:00:00 2001
From: DioEgizio <83089242+DioEgizio@users.noreply.github.com>
Date: Sat, 6 May 2023 07:15:14 +0200
Subject: [PATCH 50/50] Update launcher/ui/pages/global/MinecraftPage.ui
Co-authored-by: Sefa Eyeoglu
Signed-off-by: DioEgizio <83089242+DioEgizio@users.noreply.github.com>
---
launcher/ui/pages/global/MinecraftPage.ui | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/launcher/ui/pages/global/MinecraftPage.ui b/launcher/ui/pages/global/MinecraftPage.ui
index cff071bf..103881b5 100644
--- a/launcher/ui/pages/global/MinecraftPage.ui
+++ b/launcher/ui/pages/global/MinecraftPage.ui
@@ -187,7 +187,7 @@
- System-related tweaks
+ Tweaks
-