From 21d305fd0842873e3b7ac795089695376bd86707 Mon Sep 17 00:00:00 2001 From: Joshua Goins Date: Sat, 16 Sep 2023 20:12:42 -0400 Subject: [PATCH] Make patching asynchronous --- CMakeLists.txt | 3 +- launcher/CMakeLists.txt | 1 + launcher/include/patcher.h | 7 +- launcher/src/patcher.cpp | 176 +++++++++++++++----------------- launcher/src/squareboot.cpp | 15 ++- launcher/src/squarelauncher.cpp | 12 +-- 6 files changed, 100 insertions(+), 114 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index ce6b471..4a269c2 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -45,7 +45,8 @@ find_package(Qt6 ${QT_MIN_VERSION} REQUIRED COMPONENTS Widgets Network QuickControls2 - WebView) + WebView + Concurrent) find_package(KF6 ${KF_MIN_VERSION} REQUIRED COMPONENTS Kirigami2 I18n Config CoreAddons) find_package(PkgConfig REQUIRED) find_package(QCoro6 REQUIRED COMPONENTS Core Network Qml) diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index 325a60d..edae1ab 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -82,6 +82,7 @@ target_link_libraries(astra PRIVATE Qt6::Quick Qt6::QuickControls2 Qt6::WebView + Qt6::Concurrent KF6::Kirigami2 KF6::I18n KF6::ConfigCore diff --git a/launcher/include/patcher.h b/launcher/include/patcher.h index b1a62b8..6a054bd 100644 --- a/launcher/include/patcher.h +++ b/launcher/include/patcher.h @@ -7,6 +7,7 @@ #include #include #include +#include class LauncherCore; @@ -19,13 +20,9 @@ public: Patcher(LauncherCore &launcher, QString baseDirectory, GameData *game_data, QObject *parent = nullptr); Patcher(LauncherCore &launcher, QString baseDirectory, BootData *game_data, QObject *parent = nullptr); - void processPatchList(QNetworkAccessManager &mgr, const QString &patchList); - -signals: - void done(); + QCoro::Task<> patch(QNetworkAccessManager &mgr, const QString &patchList); private: - void checkIfDone(); void setupDirectories(); [[nodiscard]] bool isBoot() const diff --git a/launcher/src/patcher.cpp b/launcher/src/patcher.cpp index 70331df..77bad7b 100644 --- a/launcher/src/patcher.cpp +++ b/launcher/src/patcher.cpp @@ -10,7 +10,10 @@ #include #include #include +#include #include +#include +#include #include #include "launchercore.h" @@ -37,110 +40,97 @@ Patcher::Patcher(LauncherCore &launcher, QString baseDirectory, GameData *game_d Q_EMIT m_launcher.stageChanged(i18n("Checking the FINAL FANTASY XIV Game version.")); } -void Patcher::processPatchList(QNetworkAccessManager &mgr, const QString &patchList) +QCoro::Task<> Patcher::patch(QNetworkAccessManager &mgr, const QString &patchList) { if (patchList.isEmpty()) { - emit done(); + co_return; + } + + if (isBoot()) { + Q_EMIT m_launcher.stageIndeterminate(); + Q_EMIT m_launcher.stageChanged(i18n("Checking the FINAL FANTASY XIV Update/Launcher version.")); } else { - if (isBoot()) { - Q_EMIT m_launcher.stageIndeterminate(); - Q_EMIT m_launcher.stageChanged(i18n("Checking the FINAL FANTASY XIV Update/Launcher version.")); + Q_EMIT m_launcher.stageIndeterminate(); + Q_EMIT m_launcher.stageChanged(i18n("Checking the FINAL FANTASY XIV Game version.")); + } + + const QStringList parts = patchList.split("\r\n"); + + remainingPatches = parts.size() - 7; + patchQueue.resize(remainingPatches); + + QFutureSynchronizer synchronizer; + + int patchIndex = 0; + + for (int i = 5; i < parts.size() - 2; i++) { + const QStringList patchParts = parts[i].split("\t"); + + const int length = patchParts[0].toInt(); + int ourIndex = patchIndex++; + + const QString version = patchParts[4]; + const long hashBlockSize = patchParts.size() == 9 ? patchParts[6].toLong() : 0; + + const QString name = version; + const QStringList hashes = patchParts.size() == 9 ? (patchParts[7].split(',')) : QStringList(); + const QString url = patchParts[patchParts.size() == 9 ? 8 : 5]; + const QString filename = QStringLiteral("%1.patch").arg(name); + + auto url_parts = url.split('/'); + const QString repository = url_parts[url_parts.size() - 3]; + + const QDir repositoryDir = patchesDir.absoluteFilePath(repository); + + if (!QDir().exists(repositoryDir.absolutePath())) + QDir().mkpath(repositoryDir.absolutePath()); + + const QString patchPath = repositoryDir.absoluteFilePath(filename); + if (!QFile::exists(patchPath)) { + auto patchReply = mgr.get(QNetworkRequest(url)); + + connect(patchReply, &QNetworkReply::downloadProgress, [this, repository, version, length](int recieved, int total) { + Q_UNUSED(total) + + if (isBoot()) { + Q_EMIT m_launcher.stageChanged(i18n("Updating the FINAL FANTASY XIV Updater/Launcher version.\nDownloading ffxivboot - %1", version)); + } else { + Q_EMIT m_launcher.stageChanged(i18n("Updating the FINAL FANTASY XIV Game version.\nDownloading %1 - %2", repository, version)); + } + + Q_EMIT m_launcher.stageDeterminate(0, length, recieved); + }); + + synchronizer.addFuture(QtFuture::connect(patchReply, &QNetworkReply::finished) + .then([this, patchPath, patchReply, ourIndex, name, repository, version, hashes, hashBlockSize, length] { + QFile file(patchPath); + file.open(QIODevice::WriteOnly); + file.write(patchReply->readAll()); + file.close(); + + patchQueue[ourIndex] = {name, repository, version, patchPath, hashes, hashBlockSize, length}; + })); } else { - Q_EMIT m_launcher.stageIndeterminate(); - Q_EMIT m_launcher.stageChanged(i18n("Checking the FINAL FANTASY XIV Game version.")); - } + qDebug() << "Found existing patch: " << name; - const QStringList parts = patchList.split("\r\n"); + patchQueue[ourIndex] = {name, repository, version, patchPath, hashes, hashBlockSize, length}; - remainingPatches = parts.size() - 7; - patchQueue.resize(remainingPatches); - - int patchIndex = 0; - - for (int i = 5; i < parts.size() - 2; i++) { - const QStringList patchParts = parts[i].split("\t"); - - const int length = patchParts[0].toInt(); - int ourIndex = patchIndex++; - - const QString version = patchParts[4]; - const long hashBlockSize = patchParts.size() == 9 ? patchParts[6].toLong() : 0; - - const QString name = version; - const QStringList hashes = patchParts.size() == 9 ? (patchParts[7].split(',')) : QStringList(); - const QString url = patchParts[patchParts.size() == 9 ? 8 : 5]; - const QString filename = QStringLiteral("%1.patch").arg(name); - - auto url_parts = url.split('/'); - const QString repository = url_parts[url_parts.size() - 3]; - - const QDir repositoryDir = patchesDir.absoluteFilePath(repository); - - if (!QDir().exists(repositoryDir.absolutePath())) - QDir().mkpath(repositoryDir.absolutePath()); - - const QString patchPath = repositoryDir.absoluteFilePath(filename); - if (!QFile::exists(patchPath)) { - qDebug() << "Need to download " + name; - - QNetworkRequest patchRequest(url); - auto patchReply = mgr.get(patchRequest); - connect(patchReply, &QNetworkReply::downloadProgress, [this, repository, version, length](int recieved, int total) { - Q_UNUSED(total) - - if (isBoot()) { - Q_EMIT m_launcher.stageChanged(i18n("Updating the FINAL FANTASY XIV Updater/Launcher version.\nDownloading ffxivboot - %1", version)); - } else { - Q_EMIT m_launcher.stageChanged(i18n("Updating the FINAL FANTASY XIV Game version.\nDownloading %1 - %2", repository, version)); - } - - Q_EMIT m_launcher.stageDeterminate(0, length, recieved); - }); - - connect(patchReply, - &QNetworkReply::finished, - [this, ourIndex, patchPath, name, patchReply, repository, version, hashes, hashBlockSize, length] { - QFile file(patchPath); - file.open(QIODevice::WriteOnly); - file.write(patchReply->readAll()); - file.close(); - - patchQueue[ourIndex] = {name, repository, version, patchPath, hashes, hashBlockSize, length}; - - remainingPatches--; - checkIfDone(); - }); - } else { - qDebug() << "Found existing patch: " << name; - - patchQueue[ourIndex] = {name, repository, version, patchPath, hashes, hashBlockSize, length}; - - remainingPatches--; - checkIfDone(); - } + synchronizer.addFuture({}); } } -} -void Patcher::checkIfDone() -{ - if (remainingPatches <= 0) { - if (isBoot()) { - Q_EMIT m_launcher.stageChanged(i18n("Applying updates to the FINAL FANTASY XIV Updater/Launcher.")); - } else { - Q_EMIT m_launcher.stageChanged(i18n("Applying updates to the FINAL FANTASY XIV Game.")); - } + co_await QtConcurrent::run([&synchronizer] { + synchronizer.waitForFinished(); + }); - int i = 0; - for (const auto &patch : patchQueue) { - Q_EMIT m_launcher.stageDeterminate(0, patchQueue.size(), i++); - processPatch(patch); - } - - patchQueue.clear(); - - emit done(); + // This must happen synchronously + size_t i = 0; + for (const auto &patch : patchQueue) { + Q_EMIT m_launcher.stageDeterminate(0, patchQueue.size(), i++); + processPatch(patch); } + + co_return; } void Patcher::processPatch(const QueuedPatch &patch) diff --git a/launcher/src/squareboot.cpp b/launcher/src/squareboot.cpp index cc3930e..28db402 100644 --- a/launcher/src/squareboot.cpp +++ b/launcher/src/squareboot.cpp @@ -27,13 +27,6 @@ QCoro::Task<> SquareBoot::bootCheck(const LoginInformation &info) Q_EMIT window.stageChanged(i18n("Checking for launcher updates...")); qDebug() << "Performing boot check..."; - patcher = new Patcher(window, info.profile->gamePath() + QStringLiteral("/boot"), info.profile->bootData, this); - connect(patcher, &Patcher::done, [this, &info] { - info.profile->readGameVersion(); - - launcher.login(info); - }); - const QUrlQuery query{{QStringLiteral("time"), QDateTime::currentDateTimeUtc().toString(QStringLiteral("yyyy-MM-dd-HH-mm"))}}; QUrl url; @@ -54,7 +47,13 @@ QCoro::Task<> SquareBoot::bootCheck(const LoginInformation &info) const auto reply = window.mgr->get(request); co_await reply; - patcher->processPatchList(*window.mgr, reply->readAll()); + patcher = new Patcher(window, info.profile->gamePath() + QStringLiteral("/boot"), info.profile->bootData, this); + co_await patcher->patch(*window.mgr, reply->readAll()); + + // update game version information + info.profile->readGameVersion(); + + launcher.login(info); } QCoro::Task<> SquareBoot::checkGateStatus(LoginInformation *info) diff --git a/launcher/src/squarelauncher.cpp b/launcher/src/squarelauncher.cpp index f5aa571..d52b53f 100644 --- a/launcher/src/squarelauncher.cpp +++ b/launcher/src/squarelauncher.cpp @@ -170,7 +170,7 @@ QCoro::Task<> SquareLauncher::registerSession(const LoginInformation &info) QString report = QStringLiteral("%1=%2").arg(info.profile->bootVersion, getBootHash(info)); for (int i = 1; i < auth.maxExpansion + 1; i++) { - if (i <= static_cast(info.profile->repositories.repositories_count)) { + if (i < static_cast(info.profile->repositories.repositories_count)) { report += QStringLiteral("\nex%1\t%2").arg(QString::number(i), info.profile->repositories.repositories[i].version); } else { report += QStringLiteral("\nex%1\t2012.01.01.0000.0000").arg(QString::number(i)); @@ -185,15 +185,13 @@ QCoro::Task<> SquareLauncher::registerSession(const LoginInformation &info) const QString body = reply->readAll(); patcher = new Patcher(window, info.profile->gamePath() + QStringLiteral("/game"), info.profile->gameData, this); - connect(patcher, &Patcher::done, [this, &info, reply] { - info.profile->readGameVersion(); + co_await patcher->patch(*window.mgr, body); - auth.SID = reply->rawHeader(QByteArrayLiteral("X-Patch-Unique-Id")); + info.profile->readGameVersion(); - window.launchGame(*info.profile, auth); - }); + auth.SID = reply->rawHeader(QByteArrayLiteral("X-Patch-Unique-Id")); - patcher->processPatchList(*window.mgr, body); + window.launchGame(*info.profile, auth); } else { Q_EMIT window.loginError(i18n("Fatal error, request was successful but X-Patch-Unique-Id was not recieved.")); }