we want to make links!
Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com>
This commit is contained in:
parent
6dcf34acdc
commit
f794e49bb6
2
.gitignore
vendored
2
.gitignore
vendored
@ -12,6 +12,8 @@ html/
|
||||
CMakeLists.txt.user
|
||||
CMakeLists.txt.user.*
|
||||
CMakeSettings.json
|
||||
/CMakeFiles
|
||||
CMakeCache.txt
|
||||
/.project
|
||||
/.settings
|
||||
/.idea
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
};
|
||||
|
119
launcher/filelink/FileLink.cpp
Normal file
119
launcher/filelink/FileLink.cpp
Normal 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
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
52
launcher/filelink/FileLink.h
Normal file
52
launcher/filelink/FileLink.h
Normal 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
|
||||
};
|
28
launcher/filelink/filelink.exe.manifest
Normal file
28
launcher/filelink/filelink.exe.manifest
Normal 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>
|
31
launcher/filelink/main.cpp
Normal file
31
launcher/filelink/main.cpp
Normal 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();
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -9,8 +9,8 @@
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>341</width>
|
||||
<height>399</height>
|
||||
<width>525</width>
|
||||
<height>581</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
@ -136,6 +136,10 @@
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="copyOptionsGroup">
|
||||
<property name="title">
|
||||
<string>Instance copy options</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="copyOptionsLayout">
|
||||
<item row="6" column="1">
|
||||
<widget class="QCheckBox" name="copyModsCheckbox">
|
||||
@ -203,6 +207,58 @@
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="copyModeLayout">
|
||||
<item>
|
||||
<widget class="QGroupBox" name="linkFilesGroup">
|
||||
<property name="toolTip">
|
||||
<string>Use symbolic links instead of copying files.</string>
|
||||
</property>
|
||||
<property name="title">
|
||||
<string>Link files instead of copying them</string>
|
||||
</property>
|
||||
<property name="flat">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="checkable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>false</bool>
|
||||
</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>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
@ -210,7 +266,7 @@
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
|
||||
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Help|QDialogButtonBox::Ok</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
@ -220,10 +276,20 @@
|
||||
<tabstop>iconButton</tabstop>
|
||||
<tabstop>instNameTextBox</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>
|
||||
<resources>
|
||||
<include location="../../graphics.qrc"/>
|
||||
</resources>
|
||||
<resources/>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
@ -232,8 +298,8 @@
|
||||
<slot>accept()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>254</x>
|
||||
<y>316</y>
|
||||
<x>263</x>
|
||||
<y>571</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>157</x>
|
||||
@ -248,8 +314,8 @@
|
||||
<slot>reject()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>322</x>
|
||||
<y>316</y>
|
||||
<x>331</x>
|
||||
<y>571</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>286</x>
|
||||
|
@ -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)
|
||||
|
Loading…
Reference in New Issue
Block a user