From 8ba51c790098ec9ebe3d2ef686f823b61c8a3645 Mon Sep 17 00:00:00 2001
From: Rachel Powers <508861+Ryex@users.noreply.github.com>
Date: Wed, 8 Feb 2023 12:36:15 -0800
Subject: refactor: make complete list of links to make and send that.

Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com>
---
 launcher/FileSystem.cpp        | 221 +++++++++++++++++++++++++++--------------
 launcher/FileSystem.h          |  35 +++++--
 launcher/filelink/FileLink.cpp | 134 ++++++++++++++++++++-----
 launcher/filelink/FileLink.h   |   6 +-
 4 files changed, 285 insertions(+), 111 deletions(-)

(limited to 'launcher')

diff --git a/launcher/FileSystem.cpp b/launcher/FileSystem.cpp
index 9e51f932..c48a3bba 100644
--- a/launcher/FileSystem.cpp
+++ b/launcher/FileSystem.cpp
@@ -66,7 +66,6 @@
 #include <string>
 //for ShellExecute
 #include <shlobj.h>
-//#include <shlwapi.h>
 #include <objbase.h>
 #include <Shellapi.h>
 #else
@@ -228,94 +227,120 @@ bool copy::operator()(const QString& offset, bool dryRun)
 
 bool create_link::operator()(const QString& offset, bool dryRun) 
 {
+    m_linked = 0;  // reset counter
+    m_path_results.clear();
+    m_links_to_make.clear();
+
+    m_path_results.clear();
+    
+    make_link_list(offset);
+    
+    if (!dryRun)
+        return make_links();
 
-    for (auto pair : m_path_pairs) {
-        if (!make_link(pair.src, pair.dst, offset, dryRun)) {
-            return false;
-        }
-    }
     return true;
 }
 
 
 /**
- * @brief links a directory and it's contents from src to dest
+ * @brief make a list off all the  links ot make
  * @param offset subdirectory form src to link to dest
  * @return if there was an error during the attempt to link
  */
-bool create_link::make_link(const QString& srcPath, const QString& dstPath, const QString& offset, bool dryRun)
+void create_link::make_link_list( const QString& offset)
 {
-    m_linked = 0;  // reset counter
+    for (auto pair : m_path_pairs) {
+        const QString& srcPath = pair.src;
+        const QString& dstPath = pair.dst;
 
-    auto src = PathCombine(QDir(srcPath).absolutePath(), offset);
-    auto dst = PathCombine(QDir(dstPath).absolutePath(), offset);
+        auto src = PathCombine(QDir(srcPath).absolutePath(), offset);
+        auto dst = PathCombine(QDir(dstPath).absolutePath(), offset);
 
-    // you can't hard link a directory so make sure if we deal with a directory we do so recursively
-    if (m_useHardLinks)
-        m_recursive = true;
+        // you can't hard link a directory so make sure if we deal with a directory we do so recursively
+        if (m_useHardLinks)
+            m_recursive = true;
 
-    // Function that'll do the actual linking
-    auto link_file = [&](QString src_path, QString relative_dst_path) {
-        if (m_matcher && (m_matcher->matches(relative_dst_path) != m_whitelist))
-            return;
+        // Function that'll do the actual linking
+        auto link_file = [&](QString src_path, QString relative_dst_path) {
+            if (m_matcher && (m_matcher->matches(relative_dst_path) != m_whitelist)) {
+                qDebug() << "path" << relative_dst_path << "in black list or not in whitelist";
+                return;
+            }
+                
 
-        auto dst_path = PathCombine(dst, relative_dst_path);
-        if (!dryRun) {
-            
-            ensureFilePathExists(dst_path);
-            if (m_useHardLinks) {
-                if (m_debug)
-                    qDebug() << "making hard link:" << src_path << "to" << dst_path;
-                fs::create_hard_link(StringUtils::toStdString(src_path), StringUtils::toStdString(dst_path), m_os_err);
-            } else if (fs::is_directory(StringUtils::toStdString(src_path))) {
-                if (m_debug)
-                    qDebug() << "making directory_symlink:" << src_path << "to" << dst_path;
-                fs::create_directory_symlink(StringUtils::toStdString(src_path), StringUtils::toStdString(dst_path), m_os_err);
-            } else {
-                if (m_debug)
-                    qDebug() << "making symlink:" << src_path << "to" << dst_path;
-                fs::create_symlink(StringUtils::toStdString(src_path), StringUtils::toStdString(dst_path), m_os_err);
+            auto dst_path = PathCombine(dst, relative_dst_path);
+            LinkPair link = {src_path, dst_path};
+            m_links_to_make.append(link); 
+        };
+        
+        if ((!m_recursive) || !fs::is_directory(StringUtils::toStdString(src))) {
+            if (m_debug)
+                qDebug() << "linking single file or dir:" << src << "to" << dst;
+            link_file(src, "");
+        } else {
+            if (m_debug)
+                qDebug() << "linking recursivly:" << src << "to" << dst;
+            QDir src_dir(src);
+            QDirIterator source_it(src, QDir::Filter::Files | QDir::Filter::Hidden, QDirIterator::Subdirectories);
+
+            while (source_it.hasNext()) {
+                auto src_path = source_it.next();
+                auto relative_path = src_dir.relativeFilePath(src_path);
+
+                link_file(src_path, relative_path);
             }
-           
         }
+    }  
+}
+
+bool create_link::make_links()
+{
+    for (auto link : m_links_to_make) {
+
+        QString src_path = link.src;
+        QString dst_path = link.dst;
+
+        ensureFilePathExists(dst_path);
+        if (m_useHardLinks) {
+            if (m_debug)
+                qDebug() << "making hard link:" << src_path << "to" << dst_path;
+            fs::create_hard_link(StringUtils::toStdString(src_path), StringUtils::toStdString(dst_path), m_os_err);
+        } else if (fs::is_directory(StringUtils::toStdString(src_path))) {
+            if (m_debug)
+                qDebug() << "making directory_symlink:" << src_path << "to" << dst_path;
+            fs::create_directory_symlink(StringUtils::toStdString(src_path), StringUtils::toStdString(dst_path), m_os_err);
+        } else {
+            if (m_debug)
+                qDebug() << "making symlink:" << src_path << "to" << dst_path;
+            fs::create_symlink(StringUtils::toStdString(src_path), StringUtils::toStdString(dst_path), m_os_err);
+        }
+           
+
         if (m_os_err) {
             qWarning() << "Failed to link files:" << QString::fromStdString(m_os_err.message());
             qDebug() << "Source file:" << src_path;
             qDebug() << "Destination file:" << dst_path;
             qDebug() << "Error catagory:" << m_os_err.category().name();
             qDebug() << "Error code:" << m_os_err.value();
-            emit linkFailed(src_path, dst_path,  m_os_err);
+            emit linkFailed(src_path, dst_path, QString::fromStdString(m_os_err.message()), m_os_err.value());
         } else {
             m_linked++;
-            emit fileLinked(relative_dst_path);
-        }
-        
-    };
-    
-    if ((!m_recursive) || !fs::is_directory(StringUtils::toStdString(src))) {
-        if (m_debug)
-            qDebug() << "linking single file or dir:" << src << "to" << dst;
-        link_file(src, "");
-    } else {
-        if (m_debug)
-            qDebug() << "linking recursivly:" << src << "to" << dst;
-        QDir src_dir(src);
-        QDirIterator source_it(src, QDir::Filter::Files | QDir::Filter::Hidden, QDirIterator::Subdirectories);
-
-        while (source_it.hasNext()) {
-            auto src_path = source_it.next();
-            auto relative_path = src_dir.relativeFilePath(src_path);
-
-            link_file(src_path, relative_path);
-            if (m_os_err) return false;
+            emit fileLinked(src_path, dst_path);
         }
+        if (m_os_err) return false;
     }
-
-    return m_os_err.value() == 0;
+    return true;
 }
 
-bool create_link::runPrivlaged(const QString& offset)
+void create_link::runPrivlaged(const QString& offset)
 {
+    m_linked = 0;  // reset counter
+    m_path_results.clear();
+    m_links_to_make.clear();
+
+    bool gotResults = false;
+
+    make_link_list(offset);
 
     QString serverName = BuildConfig.LAUNCHER_APP_BINARY_NAME + "_filelink_server" + StringUtils::getRandomAlphaNumeric(8);
 
@@ -325,25 +350,72 @@ bool create_link::runPrivlaged(const QString& offset)
         // construct block of data to send
         QByteArray block;
         QDataStream out(&block, QIODevice::WriteOnly);
-        out.setVersion(QDataStream::Qt_5_15); // choose correct version better?
+        out.setVersion(QDataStream::Qt_5_0); // choose correct version better?
 
         qint32 blocksize = quint32(sizeof(quint32));
-        for (auto pair : m_path_pairs) {
-            blocksize += quint32(pair.src.size()); 
-            blocksize += quint32(pair.dst.size());
+        for (auto link : m_links_to_make) {
+            blocksize += quint32(link.src.size()); 
+            blocksize += quint32(link.dst.size());
         }
         qDebug() << "About to write block of size:" << blocksize;
         out << blocksize;
 
-        out << quint32(m_path_pairs.length());
-        for (auto pair : m_path_pairs) {
-            out << pair.src;   
-            out << pair.dst;
+        out << quint32(m_links_to_make.length());
+        for (auto link : m_links_to_make) {
+            out << link.src;   
+            out << link.dst;
         }
 
         QLocalSocket *clientConnection = m_linkServer.nextPendingConnection();
         connect(clientConnection, &QLocalSocket::disconnected,
                 clientConnection, &QLocalSocket::deleteLater);
+        
+        connect(clientConnection, &QLocalSocket::readyRead, this, [&, clientConnection](){
+            QDataStream in;
+            quint32 blockSize = 0;
+            in.setDevice(clientConnection);
+            in.setVersion(QDataStream::Qt_5_0);
+            qDebug() << "Reading path results from client";
+            qDebug() << "bytes avalible" << clientConnection->bytesAvailable();
+
+            // Relies on the fact that QDataStream serializes a quint32 into
+            // sizeof(quint32) bytes
+            if (clientConnection->bytesAvailable() < (int)sizeof(quint32))
+                return;
+            qDebug() << "reading block size";
+            in >> blockSize;
+
+            qDebug() << "blocksize is" << blockSize;
+            qDebug() << "bytes avalible" << clientConnection->bytesAvailable();
+            if (clientConnection->bytesAvailable() < blockSize || in.atEnd())
+                return;
+            
+            quint32 numResults;
+            in >> numResults;
+            qDebug() << "numResults" << numResults;
+
+            for(int i = 0; i < numResults; i++) {
+                FS::LinkResult result;
+                in >> result.src;
+                in >> result.dst;
+                in >> result.err_msg;
+                qint32 err_value;
+                in >>  err_value;
+                result.err_value = err_value;
+                if (result.err_value) {
+                    qDebug() << "privlaged link fail" << result.src << "to" << result.dst << "code" << result.err_value << result.err_msg;
+                    emit linkFailed(result.src, result.dst, result.err_msg, result.err_value);
+                } else {
+                    qDebug() << "privlaged link success" << result.src << "to" << result.dst;
+                    m_linked++;
+                    emit fileLinked(result.src, result.dst);
+                }
+                m_path_results.append(result);
+            }
+            gotResults = true;
+            qDebug() << "results recieved, closing connection";
+            clientConnection->close();
+        });
 
         qint64 byteswritten = clientConnection->write(block);
         bool bytesflushed = clientConnection->flush();
@@ -354,20 +426,15 @@ bool create_link::runPrivlaged(const QString& offset)
     qDebug() << "Listening on pipe" << serverName;
     if (!m_linkServer.listen(serverName)) {
         qDebug() << "Unable to start local pipe server on" << serverName << ":" << m_linkServer.errorString();
-        return false;
+        return;
     }
 
-    ExternalLinkFileProcess *linkFileProcess = new ExternalLinkFileProcess(serverName, this);
-    connect(linkFileProcess, &ExternalLinkFileProcess::processExited, this, [&](){
-        emit finishedPrivlaged();
-    });
+    ExternalLinkFileProcess* linkFileProcess = new ExternalLinkFileProcess(serverName, m_useHardLinks, this);
+    connect(linkFileProcess, &ExternalLinkFileProcess::processExited, this, [&]() { emit finishedPrivlaged(gotResults); });
     connect(linkFileProcess, &ExternalLinkFileProcess::finished, linkFileProcess, &QObject::deleteLater);
 
     linkFileProcess->start();
 
-    // linkFileProcess->wait();
-
-    return true;
 }
 
 
@@ -375,6 +442,8 @@ void ExternalLinkFileProcess::runLinkFile() {
     QString fileLinkExe = PathCombine(QCoreApplication::instance()->applicationDirPath(),  BuildConfig.LAUNCHER_APP_BINARY_NAME + "_filelink");
     QString params = "-s " + m_server;
 
+    params += " -H " + QVariant(m_useHardLinks).toString();
+
 #if defined Q_OS_WIN32
     SHELLEXECUTEINFO ShExecInfo;
     HRESULT hr;
diff --git a/launcher/FileSystem.h b/launcher/FileSystem.h
index b15d1685..2e739298 100644
--- a/launcher/FileSystem.h
+++ b/launcher/FileSystem.h
@@ -133,13 +133,22 @@ struct LinkPair {
     QString dst;
 };
 
-class ExternalLinkFileProcess : public QThread
-{
+struct LinkResult {
+    QString src;
+    QString dst;
+    QString err_msg;
+    int err_value;
+};
+
+class ExternalLinkFileProcess : public QThread {
     Q_OBJECT
    public:
-    ExternalLinkFileProcess(QString server, QObject* parent = nullptr) : QThread(parent), m_server(server) {}
+    ExternalLinkFileProcess(QString server, bool useHardLinks, QObject* parent = nullptr)
+        : QThread(parent), m_server(server), m_useHardLinks(useHardLinks)
+    {}
 
-    void run() override {
+    void run() override
+    {
         runLinkFile();
         emit processExited();
     }
@@ -150,6 +159,8 @@ class ExternalLinkFileProcess : public QThread
    private:
     void runLinkFile();
 
+    bool m_useHardLinks = false;
+
     QString m_server;
 };
 
@@ -200,19 +211,21 @@ class create_link : public QObject {
 
     bool operator()(bool dryRun = false) { return operator()(QString(), dryRun); }
 
-    bool runPrivlaged() { return runPrivlaged(QString()); }
-    bool runPrivlaged(const QString& offset);
+    void runPrivlaged() { runPrivlaged(QString()); }
+    void runPrivlaged(const QString& offset);
 
     int totalLinked() { return m_linked; }
 
    signals:
-    void fileLinked(const QString& relativeName);
-    void linkFailed(const QString& srcName, const QString& dstName, std::error_code err);
-    void finishedPrivlaged();
+    void fileLinked(const QString& srcName, const QString& dstName);
+    void linkFailed(const QString& srcName, const QString& dstName, const QString& err_msg, int err_value);
+    void finishedPrivlaged(bool gotResults);
+    void finished();
 
    private:
     bool operator()(const QString& offset, bool dryRun = false);
-    bool make_link(const QString& src_path, const QString& dst_path, const QString& offset, bool dryRun);
+    void make_link_list(const QString& offset);
+    bool make_links();
 
    private:
     bool m_useHardLinks = false;
@@ -221,6 +234,8 @@ class create_link : public QObject {
     bool m_recursive = true;
 
     QList<LinkPair> m_path_pairs;
+    QList<LinkResult> m_path_results;
+    QList<LinkPair> m_links_to_make;
 
     int m_linked;
     bool m_debug = false;
diff --git a/launcher/filelink/FileLink.cpp b/launcher/filelink/FileLink.cpp
index 78486507..a731ecdb 100644
--- a/launcher/filelink/FileLink.cpp
+++ b/launcher/filelink/FileLink.cpp
@@ -23,6 +23,8 @@
 #include "FileLink.h"
 #include "BuildConfig.h"
 
+#include "StringUtils.h"
+
 
 #include <iostream>
 
@@ -43,6 +45,24 @@
 #include <stdio.h>
 #endif
 
+// Snippet from https://github.com/gulrak/filesystem#using-it-as-single-file-header
+
+#ifdef __APPLE__
+#include <Availability.h> // 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(<filesystem>) && (!defined(__MAC_OS_X_VERSION_MIN_REQUIRED) || __MAC_OS_X_VERSION_MIN_REQUIRED >= 101500)
+#define GHC_USE_STD_FS
+#include <filesystem>
+namespace fs = std::filesystem;
+#endif // MacOS min version check
+#endif // Other OSes version check
+
+#ifndef GHC_USE_STD_FS
+#include <ghc/filesystem.hpp>
+namespace fs = ghc::filesystem;
+#endif
 
 
 
@@ -82,7 +102,8 @@ FileLinkApp::FileLinkApp(int &argc, char **argv) : QCoreApplication(argc, argv),
     parser.setApplicationDescription(QObject::tr("a batch MKLINK program for windows to be used with prismlauncher"));
 
     parser.addOptions({
-        {{"s", "server"}, "Join the specified server on launch", "pipe name"}
+        {{"s", "server"}, "Join the specified server on launch", "pipe name"},
+        {{"H", "hard"}, "use hard links insted of symbolic", "true/false"}
     });
     parser.addHelpOption();
     parser.addVersionOption();
@@ -90,6 +111,7 @@ FileLinkApp::FileLinkApp(int &argc, char **argv) : QCoreApplication(argc, argv),
     parser.process(arguments());
 
     QString serverToJoin = parser.value("server");
+    m_useHardLinks = QVariant(parser.value("hard")).toBool();
 
     qDebug() << "link program launched";
 
@@ -109,7 +131,7 @@ void FileLinkApp::joinServer(QString server)
     blockSize = 0; 
 
     in.setDevice(&socket);
-    in.setVersion(QDataStream::Qt_5_15);
+    in.setVersion(QDataStream::Qt_5_0);
 
     connect(&socket, &QLocalSocket::connected, this, [&](){
         qDebug() << "connected to server";
@@ -120,25 +142,27 @@ void FileLinkApp::joinServer(QString server)
     connect(&socket, &QLocalSocket::errorOccurred, this, [&](QLocalSocket::LocalSocketError socketError){
         switch (socketError) {
         case QLocalSocket::ServerNotFoundError:
-            qDebug() << tr("The host was not found. Please make sure "
-                            "that the server is running and that the "
-                            "server name is correct.");
+            qDebug() << ("The host was not found. Please make sure "
+                        "that the server is running and that the "
+                        "server name is correct.");
             break;
         case QLocalSocket::ConnectionRefusedError:
-            qDebug() << tr("The connection was refused by the peer. "
-                            "Make sure the server is running, "
-                            "and check that the server name "
-                            "is correct.");
+            qDebug() << ("The connection was refused by the peer. "
+                        "Make sure the server is running, "
+                        "and check that the server name "
+                        "is correct.");
             break;
         case QLocalSocket::PeerClosedError:
+            qDebug() << ("The connection was closed by the peer. ");
             break;
         default:
-            qDebug() << tr("The following error occurred: %1.").arg(socket.errorString());
+            qDebug() << "The following error occurred: " << socket.errorString();
         }
     });
 
     connect(&socket, &QLocalSocket::disconnected, this, [&](){
-        qDebug() << "dissconnected from server";
+        qDebug() << "dissconnected from server, should exit";
+        exit();
     });
 
     socket.connectToServer(server);
@@ -147,20 +171,82 @@ void FileLinkApp::joinServer(QString server)
 }
 
 void FileLinkApp::runLink()
+{   
+
+    std::error_code os_err;
+
+    qDebug() << "creating links";
+
+    for (auto link : m_links_to_make) {
+
+        QString src_path = link.src;
+        QString dst_path = link.dst;
+
+        FS::ensureFilePathExists(dst_path);
+        if (m_useHardLinks) {
+            qDebug() << "making hard link:" << src_path << "to" << dst_path;
+            fs::create_hard_link(StringUtils::toStdString(src_path), StringUtils::toStdString(dst_path), os_err);
+        } else if (fs::is_directory(StringUtils::toStdString(src_path))) {
+            qDebug() << "making directory_symlink:" << src_path << "to" << dst_path;
+            fs::create_directory_symlink(StringUtils::toStdString(src_path), StringUtils::toStdString(dst_path), os_err);
+        } else {
+            qDebug() << "making symlink:" << src_path << "to" << dst_path;
+            fs::create_symlink(StringUtils::toStdString(src_path), StringUtils::toStdString(dst_path), os_err);
+        }    
+
+        if (os_err) {
+            qWarning() << "Failed to link files:" << QString::fromStdString(os_err.message());
+            qDebug() << "Source file:" << src_path;
+            qDebug() << "Destination file:" << dst_path;
+            qDebug() << "Error catagory:" << os_err.category().name();
+            qDebug() << "Error code:" << os_err.value();
+
+            FS::LinkResult result = {src_path, dst_path, QString::fromStdString(os_err.message()), os_err.value()};
+            m_path_results.append(result);
+        } else {
+            FS::LinkResult result = {src_path, dst_path};
+            m_path_results.append(result);
+        }
+    }
+
+    sendResults();
+    qDebug() << "done, should exit soon";
+    
+}
+
+void FileLinkApp::sendResults()
 {
-    qDebug() << "creating link";
-    FS::create_link lnk(m_path_pairs);
-    lnk.debug(true);
-    if (!lnk()) {
-        qDebug() << "Link Failed!" << lnk.getOSError().value() << lnk.getOSError().message().c_str();
+    // construct block of data to send
+    QByteArray block;
+    QDataStream out(&block, QIODevice::WriteOnly);
+    out.setVersion(QDataStream::Qt_5_0);
+
+    qint32 blocksize = quint32(sizeof(quint32));
+    for (auto result : m_path_results) {
+        blocksize += quint32(result.src.size());
+        blocksize += quint32(result.dst.size());
+        blocksize += quint32(result.err_msg.size());
+        blocksize += quint32(sizeof(quint32));
     }
-    //exit();
-    qDebug() << "done, should exit";
+    qDebug() << "About to write block of size:" << blocksize;
+    out << blocksize;
+
+    out << quint32(m_path_results.length());
+    for (auto result : m_path_results) {
+        out << result.src;
+        out << result.dst;
+        out << result.err_msg;
+        out << quint32(result.err_value);
+    }
+
+    qint64 byteswritten = socket.write(block);
+    bool bytesflushed = socket.flush();
+    qDebug() << "block flushed" << byteswritten << bytesflushed;
 }
 
 void FileLinkApp::readPathPairs()
 {   
-    m_path_pairs.clear();
+    m_links_to_make.clear();
     qDebug() << "Reading path pairs from server";
     qDebug() << "bytes avalible" << socket.bytesAvailable();
     if (blockSize == 0) {
@@ -176,16 +262,16 @@ void FileLinkApp::readPathPairs()
     if (socket.bytesAvailable() < blockSize || in.atEnd())
         return;
     
-    quint32 numPairs;
-    in >> numPairs;
-    qDebug() << "numPairs" << numPairs;
+    quint32 numLinks;
+    in >> numLinks;
+    qDebug() << "numLinks" << numLinks;
 
-    for(int i = 0; i < numPairs; i++) {
+    for(int i = 0; i < numLinks; i++) {
         FS::LinkPair pair;
         in >> pair.src;
         in >> pair.dst;
         qDebug() << "link" << pair.src << "to" << pair.dst;
-        m_path_pairs.append(pair);
+        m_links_to_make.append(pair);
     }
 
     runLink();
diff --git a/launcher/filelink/FileLink.h b/launcher/filelink/FileLink.h
index 5d0ba123..d146b8d9 100644
--- a/launcher/filelink/FileLink.h
+++ b/launcher/filelink/FileLink.h
@@ -52,13 +52,17 @@ private:
     void joinServer(QString server);
     void readPathPairs();
     void runLink();
+    void sendResults();
+
+    bool m_useHardLinks = false;
 
     QDateTime m_startTime;
     QLocalSocket socket;
     QDataStream in;
     quint32 blockSize;
 
-    QList<FS::LinkPair> m_path_pairs;
+    QList<FS::LinkPair> m_links_to_make;
+    QList<FS::LinkResult> m_path_results;
 
 #if defined Q_OS_WIN32
     // used on Windows to attach the standard IO streams
-- 
cgit