we want to make links!

Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com>
This commit is contained in:
Rachel Powers 2023-02-06 23:05:06 -08:00
parent 6dcf34acdc
commit f794e49bb6
14 changed files with 918 additions and 71 deletions

2
.gitignore vendored
View File

@ -12,6 +12,8 @@ html/
CMakeLists.txt.user CMakeLists.txt.user
CMakeLists.txt.user.* CMakeLists.txt.user.*
CMakeSettings.json CMakeSettings.json
/CMakeFiles
CMakeCache.txt
/.project /.project
/.settings /.settings
/.idea /.idea

View File

@ -559,6 +559,11 @@ set(ATLAUNCHER_SOURCES
modplatform/atlauncher/ATLShareCode.h modplatform/atlauncher/ATLShareCode.h
) )
set(LINKDAEMON_SOURCES
filelink/FileLink.h
filelink/FileLink.cpp
)
######## Logging categories ######## ######## Logging categories ########
ecm_qt_declare_logging_category(CORE_SOURCES ecm_qt_declare_logging_category(CORE_SOURCES
@ -1107,6 +1112,53 @@ install(TARGETS ${Launcher_Name}
FRAMEWORK DESTINATION ${FRAMEWORK_DEST_DIR} COMPONENT Runtime 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) if (UNIX AND APPLE)
# Add Sparkle updater # Add Sparkle updater
# It has to be copied here instead of just allowing fixup_bundle to install it, otherwise essential parts of # It has to be copied here instead of just allowing fixup_bundle to install it, otherwise essential parts of

View File

@ -152,9 +152,11 @@ bool ensureFolderPathExists(QString foldernamepath)
return success; return success;
} }
/// @brief Copies a directory and it's contents from src to dest /**
/// @param offset subdirectory form src to copy to dest * @brief Copies a directory and it's contents from src to dest
/// @return if there was an error during the filecopy * @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) bool copy::operator()(const QString& offset, bool dryRun)
{ {
using copy_opts = fs::copy_options; using copy_opts = fs::copy_options;
@ -215,6 +217,85 @@ bool copy::operator()(const QString& offset, bool dryRun)
return err.value() == 0; 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) bool move(const QString& source, const QString& dest)
{ {
std::error_code err; std::error_code err;

View File

@ -77,7 +77,9 @@ bool ensureFilePathExists(QString filenamepath);
*/ */
bool ensureFolderPathExists(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 { class copy : public QObject {
Q_OBJECT Q_OBJECT
public: public:
@ -122,6 +124,70 @@ class copy : public QObject {
int m_copied; 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 * @brief moves a file by renaming it
* @param source source file path * @param source source file path

View File

@ -93,6 +93,21 @@ bool InstanceCopyPrefs::isCopyScreenshotsEnabled() const
return copyScreenshots; return copyScreenshots;
} }
bool InstanceCopyPrefs::isLinkFilesEnabled() const
{
return linkFiles;
}
bool InstanceCopyPrefs::isUseHardLinksEnabled() const
{
return useHardLinks;
}
bool InstanceCopyPrefs::isLinkWorldsEnabled() const
{
return linkWorlds;
}
// ======= Setters ======= // ======= Setters =======
void InstanceCopyPrefs::enableCopySaves(bool b) void InstanceCopyPrefs::enableCopySaves(bool b)
{ {
@ -133,3 +148,18 @@ void InstanceCopyPrefs::enableCopyScreenshots(bool b)
{ {
copyScreenshots = b; copyScreenshots = b;
} }
void InstanceCopyPrefs::enableLinkFiles(bool b)
{
linkFiles = b;
}
void InstanceCopyPrefs::enableUseHardLinks(bool b)
{
useHardLinks = b;
}
void InstanceCopyPrefs::enableLinkWorlds(bool b)
{
linkWorlds = b;
}

View File

@ -19,6 +19,9 @@ struct InstanceCopyPrefs {
[[nodiscard]] bool isCopyServersEnabled() const; [[nodiscard]] bool isCopyServersEnabled() const;
[[nodiscard]] bool isCopyModsEnabled() const; [[nodiscard]] bool isCopyModsEnabled() const;
[[nodiscard]] bool isCopyScreenshotsEnabled() const; [[nodiscard]] bool isCopyScreenshotsEnabled() const;
[[nodiscard]] bool isLinkFilesEnabled() const;
[[nodiscard]] bool isUseHardLinksEnabled() const;
[[nodiscard]] bool isLinkWorldsEnabled() const;
// Setters // Setters
void enableCopySaves(bool b); void enableCopySaves(bool b);
void enableKeepPlaytime(bool b); void enableKeepPlaytime(bool b);
@ -28,6 +31,9 @@ struct InstanceCopyPrefs {
void enableCopyServers(bool b); void enableCopyServers(bool b);
void enableCopyMods(bool b); void enableCopyMods(bool b);
void enableCopyScreenshots(bool b); void enableCopyScreenshots(bool b);
void enableLinkFiles(bool b);
void enableUseHardLinks(bool b);
void enableLinkWorlds(bool b);
protected: // data protected: // data
bool copySaves = true; bool copySaves = true;
@ -38,4 +44,7 @@ struct InstanceCopyPrefs {
bool copyServers = true; bool copyServers = true;
bool copyMods = true; bool copyMods = true;
bool copyScreenshots = true; bool copyScreenshots = true;
bool linkFiles = false;
bool useHardLinks = false;
bool linkWorlds = true;
}; };

View File

@ -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 <https://www.gnu.org/licenses/>.
*
*/
#include "FileLink.h"
#include "BuildConfig.h"
#include <iostream>
#include <QAccessible>
#include <QCommandLineParser>
#include <QDebug>
#include <FileSystem.h>
#include <DesktopServices.h>
#include <sys.h>
#if defined Q_OS_WIN32
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#include <windows.h>
#include <stdio.h>
#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
}

View File

@ -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 <https://www.gnu.org/licenses/>.
*
*/
#pragma once
#include <QtCore>
#include <QApplication>
#include <memory>
#include <QDebug>
#include <QFlag>
#include <QIcon>
#include <QDateTime>
#include <QUrl>
#include <QDateTime>
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
};

View File

@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<application xmlns="urn:schemas-microsoft-com:asm.v3">
<windowsSettings>
</windowsSettings>
</application>
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<!-- Windows 10, Windows 11 -->
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
<!-- Windows 8.1 -->
<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/>
<!-- Windows 8 -->
<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/>
<!-- Windows 7 -->
<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>
</application>
</compatibility>
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
<security>
<requestedPrivileges>
<requestedExecutionLevel
level="requireAdministrator"
uiAccess="false"/>
</requestedPrivileges>
</security>
</trustInfo>
</assembly>

View File

@ -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 <https://www.gnu.org/licenses/>.
*
*/
#include "FileLink.h"
int main(int argc, char *argv[])
{
FileLinkApp ldh(argc, argv);
return ldh.exec();
}

View File

@ -85,6 +85,10 @@ CopyInstanceDialog::CopyInstanceDialog(InstancePtr original, QWidget *parent)
ui->copyServersCheckbox->setChecked(m_selectedOptions.isCopyServersEnabled()); ui->copyServersCheckbox->setChecked(m_selectedOptions.isCopyServersEnabled());
ui->copyModsCheckbox->setChecked(m_selectedOptions.isCopyModsEnabled()); ui->copyModsCheckbox->setChecked(m_selectedOptions.isCopyModsEnabled());
ui->copyScreenshotsCheckbox->setChecked(m_selectedOptions.isCopyScreenshotsEnabled()); 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() CopyInstanceDialog::~CopyInstanceDialog()
@ -220,3 +224,18 @@ void CopyInstanceDialog::on_copyScreenshotsCheckbox_stateChanged(int state)
m_selectedOptions.enableCopyScreenshots(state == Qt::Checked); m_selectedOptions.enableCopyScreenshots(state == Qt::Checked);
updateSelectAllCheckbox(); 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);
}

View File

@ -55,6 +55,9 @@ slots:
void on_copyServersCheckbox_stateChanged(int state); void on_copyServersCheckbox_stateChanged(int state);
void on_copyModsCheckbox_stateChanged(int state); void on_copyModsCheckbox_stateChanged(int state);
void on_copyScreenshotsCheckbox_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: private:
void checkAllCheckboxes(const bool& b); void checkAllCheckboxes(const bool& b);

View File

@ -9,8 +9,8 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>341</width> <width>525</width>
<height>399</height> <height>581</height>
</rect> </rect>
</property> </property>
<property name="windowTitle"> <property name="windowTitle">
@ -136,70 +136,126 @@
</layout> </layout>
</item> </item>
<item> <item>
<layout class="QGridLayout" name="copyOptionsLayout"> <widget class="QGroupBox" name="copyOptionsGroup">
<item row="6" column="1"> <property name="title">
<widget class="QCheckBox" name="copyModsCheckbox"> <string>Instance copy options</string>
</property>
<layout class="QGridLayout" name="copyOptionsLayout">
<item row="6" column="1">
<widget class="QCheckBox" name="copyModsCheckbox">
<property name="toolTip">
<string>Disabling this will still keep the mod loader (ex: Fabric, Quilt, etc.) but erase the mods folder and their configs.</string>
</property>
<property name="text">
<string>Copy mods</string>
</property>
</widget>
</item>
<item row="5" column="0">
<widget class="QCheckBox" name="copyGameOptionsCheckbox">
<property name="toolTip">
<string>Copy the in-game options like FOV, max framerate, etc.</string>
</property>
<property name="text">
<string>Copy game options</string>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QCheckBox" name="copySavesCheckbox">
<property name="text">
<string>Copy saves</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QCheckBox" name="copyShaderPacksCheckbox">
<property name="text">
<string>Copy shader packs</string>
</property>
</widget>
</item>
<item row="5" column="1">
<widget class="QCheckBox" name="copyServersCheckbox">
<property name="text">
<string>Copy servers</string>
</property>
</widget>
</item>
<item row="6" column="0">
<widget class="QCheckBox" name="copyResPacksCheckbox">
<property name="enabled">
<bool>true</bool>
</property>
<property name="text">
<string>Copy resource packs</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QCheckBox" name="keepPlaytimeCheckbox">
<property name="text">
<string>Keep play time</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QCheckBox" name="copyScreenshotsCheckbox">
<property name="text">
<string>Copy screenshots</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<layout class="QVBoxLayout" name="copyModeLayout">
<item>
<widget class="QGroupBox" name="linkFilesGroup">
<property name="toolTip"> <property name="toolTip">
<string>Disabling this will still keep the mod loader (ex: Fabric, Quilt, etc.) but erase the mods folder and their configs.</string> <string>Use symbolic links instead of copying files.</string>
</property> </property>
<property name="text"> <property name="title">
<string>Copy mods</string> <string>Link files instead of copying them</string>
</property> </property>
</widget> <property name="flat">
</item> <bool>false</bool>
<item row="5" column="0">
<widget class="QCheckBox" name="copyGameOptionsCheckbox">
<property name="toolTip">
<string>Copy the in-game options like FOV, max framerate, etc.</string>
</property> </property>
<property name="text"> <property name="checkable">
<string>Copy game options</string>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QCheckBox" name="copySavesCheckbox">
<property name="text">
<string>Copy saves</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QCheckBox" name="copyShaderPacksCheckbox">
<property name="text">
<string>Copy shader packs</string>
</property>
</widget>
</item>
<item row="5" column="1">
<widget class="QCheckBox" name="copyServersCheckbox">
<property name="text">
<string>Copy servers</string>
</property>
</widget>
</item>
<item row="6" column="0">
<widget class="QCheckBox" name="copyResPacksCheckbox">
<property name="enabled">
<bool>true</bool> <bool>true</bool>
</property> </property>
<property name="text"> <property name="checked">
<string>Copy resource packs</string> <bool>false</bool>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QCheckBox" name="keepPlaytimeCheckbox">
<property name="text">
<string>Keep play time</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QCheckBox" name="copyScreenshotsCheckbox">
<property name="text">
<string>Copy screenshots</string>
</property> </property>
<layout class="QVBoxLayout" name="linkOptionsLayout">
<item>
<widget class="QCheckBox" name="hardLinksCheckbox">
<property name="toolTip">
<string>Use hard links instead of symbolic links</string>
</property>
<property name="text">
<string>Use hard links</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="linkWorldsCheckbox">
<property name="toolTip">
<string>World save data will be linked and thus shared between instances.</string>
</property>
<property name="text">
<string>Link worlds</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
<property name="tristate">
<bool>false</bool>
</property>
</widget>
</item>
</layout>
</widget> </widget>
</item> </item>
</layout> </layout>
@ -210,7 +266,7 @@
<enum>Qt::Horizontal</enum> <enum>Qt::Horizontal</enum>
</property> </property>
<property name="standardButtons"> <property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> <set>QDialogButtonBox::Cancel|QDialogButtonBox::Help|QDialogButtonBox::Ok</set>
</property> </property>
</widget> </widget>
</item> </item>
@ -220,10 +276,20 @@
<tabstop>iconButton</tabstop> <tabstop>iconButton</tabstop>
<tabstop>instNameTextBox</tabstop> <tabstop>instNameTextBox</tabstop>
<tabstop>groupBox</tabstop> <tabstop>groupBox</tabstop>
<tabstop>selectAllCheckbox</tabstop>
<tabstop>keepPlaytimeCheckbox</tabstop>
<tabstop>copyScreenshotsCheckbox</tabstop>
<tabstop>copySavesCheckbox</tabstop>
<tabstop>copyShaderPacksCheckbox</tabstop>
<tabstop>copyGameOptionsCheckbox</tabstop>
<tabstop>copyServersCheckbox</tabstop>
<tabstop>copyResPacksCheckbox</tabstop>
<tabstop>copyModsCheckbox</tabstop>
<tabstop>linkFilesGroup</tabstop>
<tabstop>hardLinksCheckbox</tabstop>
<tabstop>linkWorldsCheckbox</tabstop>
</tabstops> </tabstops>
<resources> <resources/>
<include location="../../graphics.qrc"/>
</resources>
<connections> <connections>
<connection> <connection>
<sender>buttonBox</sender> <sender>buttonBox</sender>
@ -232,8 +298,8 @@
<slot>accept()</slot> <slot>accept()</slot>
<hints> <hints>
<hint type="sourcelabel"> <hint type="sourcelabel">
<x>254</x> <x>263</x>
<y>316</y> <y>571</y>
</hint> </hint>
<hint type="destinationlabel"> <hint type="destinationlabel">
<x>157</x> <x>157</x>
@ -248,8 +314,8 @@
<slot>reject()</slot> <slot>reject()</slot>
<hints> <hints>
<hint type="sourcelabel"> <hint type="sourcelabel">
<x>322</x> <x>331</x>
<y>316</y> <y>571</y>
</hint> </hint>
<hint type="destinationlabel"> <hint type="destinationlabel">
<x>286</x> <x>286</x>

View File

@ -248,6 +248,295 @@ slots:
{ {
QCOMPARE(FS::getDesktopDir(), QStandardPaths::writableLocation(QStandardPaths::DesktopLocation)); 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) QTEST_GUILESS_MAIN(FileSystemTest)