// SPDX-FileCopyrightText: 2022 Rachel Powers <508861+Ryex@users.noreply.github.com>
//
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (C) 2022 Rachel Powers <508861+Ryex@users.noreply.github.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*
*/
#include "FileLink.h"
#include "BuildConfig.h"
#include "StringUtils.h"
#include
#include
#include
#include
#include
#include
#if defined Q_OS_WIN32
#include "WindowsConsole.h"
#endif
// Snippet from https://github.com/gulrak/filesystem#using-it-as-single-file-header
#ifdef __APPLE__
#include // for deployment target to support pre-catalina targets without std::fs
#endif // __APPLE__
#if ((defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) || (defined(__cplusplus) && __cplusplus >= 201703L)) && defined(__has_include)
#if __has_include() && (!defined(__MAC_OS_X_VERSION_MIN_REQUIRED) || __MAC_OS_X_VERSION_MIN_REQUIRED >= 101500)
#define GHC_USE_STD_FS
#include
namespace fs = std::filesystem;
#endif // MacOS min version check
#endif // Other OSes version check
#ifndef GHC_USE_STD_FS
#include
namespace fs = ghc::filesystem;
#endif
FileLinkApp::FileLinkApp(int& argc, char** argv) : QCoreApplication(argc, argv), socket(new QLocalSocket(this))
{
#if defined Q_OS_WIN32
// attach the parent console
if (AttachWindowsConsole()) {
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 used with prismlauncher"));
parser.addOptions({ { { "s", "server" }, "Join the specified server on launch", "pipe name" },
{ { "H", "hard" }, "use hard links instead of symbolic", "true/false" } });
parser.addHelpOption();
parser.addVersionOption();
parser.process(arguments());
QString serverToJoin = parser.value("server");
m_useHardLinks = QVariant(parser.value("hard")).toBool();
qDebug() << "link program launched";
if (!serverToJoin.isEmpty()) {
qDebug() << "joining server" << serverToJoin;
joinServer(serverToJoin);
} else {
qDebug() << "no server to join";
m_status = Failed;
exit();
}
}
void FileLinkApp::joinServer(QString server)
{
blockSize = 0;
in.setDevice(&socket);
connect(&socket, &QLocalSocket::connected, this, [&]() { qDebug() << "connected to server"; });
connect(&socket, &QLocalSocket::readyRead, this, &FileLinkApp::readPathPairs);
connect(&socket, &QLocalSocket::errorOccurred, this, [&](QLocalSocket::LocalSocketError socketError) {
m_status = Failed;
switch (socketError) {
case QLocalSocket::ServerNotFoundError:
qDebug()
<< ("The host was not found. Please make sure "
"that the server is running and that the "
"server name is correct.");
break;
case QLocalSocket::ConnectionRefusedError:
qDebug()
<< ("The connection was refused by the peer. "
"Make sure the server is running, "
"and check that the server name "
"is correct.");
break;
case QLocalSocket::PeerClosedError:
qDebug() << ("The connection was closed by the peer. ");
break;
default:
qDebug() << "The following error occurred: " << socket.errorString();
}
});
connect(&socket, &QLocalSocket::disconnected, this, [&]() {
qDebug() << "disconnected from server, should exit";
m_status = Succeeded;
exit();
});
socket.connectToServer(server);
}
void FileLinkApp::runLink()
{
std::error_code os_err;
qDebug() << "creating links";
for (auto link : m_links_to_make) {
QString src_path = link.src;
QString dst_path = link.dst;
FS::ensureFilePathExists(dst_path);
if (m_useHardLinks) {
qDebug() << "making hard link:" << src_path << "to" << dst_path;
fs::create_hard_link(StringUtils::toStdString(src_path), StringUtils::toStdString(dst_path), os_err);
} else if (fs::is_directory(StringUtils::toStdString(src_path))) {
qDebug() << "making directory_symlink:" << src_path << "to" << dst_path;
fs::create_directory_symlink(StringUtils::toStdString(src_path), StringUtils::toStdString(dst_path), os_err);
} else {
qDebug() << "making symlink:" << src_path << "to" << dst_path;
fs::create_symlink(StringUtils::toStdString(src_path), StringUtils::toStdString(dst_path), os_err);
}
if (os_err) {
qWarning() << "Failed to link files:" << QString::fromStdString(os_err.message());
qDebug() << "Source file:" << src_path;
qDebug() << "Destination file:" << dst_path;
qDebug() << "Error category:" << os_err.category().name();
qDebug() << "Error code:" << os_err.value();
FS::LinkResult result = { src_path, dst_path, QString::fromStdString(os_err.message()), os_err.value() };
m_path_results.append(result);
} else {
FS::LinkResult result = { src_path, dst_path, "", 0 };
m_path_results.append(result);
}
}
sendResults();
qDebug() << "done, should exit soon";
}
void FileLinkApp::sendResults()
{
// construct block of data to send
QByteArray block;
QDataStream out(&block, QIODevice::WriteOnly);
qint32 blocksize = quint32(sizeof(quint32));
for (auto result : m_path_results) {
blocksize += quint32(result.src.size());
blocksize += quint32(result.dst.size());
blocksize += quint32(result.err_msg.size());
blocksize += quint32(sizeof(quint32));
}
qDebug() << "About to write block of size:" << blocksize;
out << blocksize;
out << quint32(m_path_results.length());
for (auto result : m_path_results) {
out << result.src;
out << result.dst;
out << result.err_msg;
out << quint32(result.err_value);
}
qint64 byteswritten = socket.write(block);
bool bytesflushed = socket.flush();
qDebug() << "block flushed" << byteswritten << bytesflushed;
}
void FileLinkApp::readPathPairs()
{
m_links_to_make.clear();
qDebug() << "Reading path pairs from server";
qDebug() << "bytes available" << socket.bytesAvailable();
if (blockSize == 0) {
// Relies on the fact that QDataStream serializes a quint32 into
// sizeof(quint32) bytes
if (socket.bytesAvailable() < (int)sizeof(quint32))
return;
qDebug() << "reading block size";
in >> blockSize;
}
qDebug() << "blocksize is" << blockSize;
qDebug() << "bytes available" << socket.bytesAvailable();
if (socket.bytesAvailable() < blockSize || in.atEnd())
return;
quint32 numLinks;
in >> numLinks;
qDebug() << "numLinks" << numLinks;
for (quint32 i = 0; i < numLinks; i++) {
FS::LinkPair pair;
in >> pair.src;
in >> pair.dst;
qDebug() << "link" << pair.src << "to" << pair.dst;
m_links_to_make.append(pair);
}
runLink();
}
FileLinkApp::~FileLinkApp()
{
qDebug() << "link program shutting down";
// Shut down logger by setting the logger function to nothing
qInstallMessageHandler(nullptr);
#if defined Q_OS_WIN32
// Detach from Windows console
if (consoleAttached) {
fclose(stdout);
fclose(stdin);
fclose(stderr);
}
#endif
}