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
|
||||||
CMakeLists.txt.user.*
|
CMakeLists.txt.user.*
|
||||||
CMakeSettings.json
|
CMakeSettings.json
|
||||||
|
/CMakeFiles
|
||||||
|
CMakeCache.txt
|
||||||
/.project
|
/.project
|
||||||
/.settings
|
/.settings
|
||||||
/.idea
|
/.idea
|
||||||
|
@ -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
|
||||||
|
@ -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;
|
||||||
|
@ -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
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
|
@ -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;
|
||||||
};
|
};
|
||||||
|
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->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);
|
||||||
|
}
|
||||||
|
@ -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);
|
||||||
|
@ -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>
|
||||||
|
@ -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)
|
||||||
|
Loading…
Reference in New Issue
Block a user