1
Fork 0
mirror of https://github.com/redstrate/Astra.git synced 2025-04-23 21:07:45 +00:00

Make patching asynchronous

This commit is contained in:
Joshua Goins 2023-09-16 20:12:42 -04:00
parent e211c95e21
commit 21d305fd08
6 changed files with 100 additions and 114 deletions

View file

@ -45,7 +45,8 @@ find_package(Qt6 ${QT_MIN_VERSION} REQUIRED COMPONENTS
Widgets Widgets
Network Network
QuickControls2 QuickControls2
WebView) WebView
Concurrent)
find_package(KF6 ${KF_MIN_VERSION} REQUIRED COMPONENTS Kirigami2 I18n Config CoreAddons) find_package(KF6 ${KF_MIN_VERSION} REQUIRED COMPONENTS Kirigami2 I18n Config CoreAddons)
find_package(PkgConfig REQUIRED) find_package(PkgConfig REQUIRED)
find_package(QCoro6 REQUIRED COMPONENTS Core Network Qml) find_package(QCoro6 REQUIRED COMPONENTS Core Network Qml)

View file

@ -82,6 +82,7 @@ target_link_libraries(astra PRIVATE
Qt6::Quick Qt6::Quick
Qt6::QuickControls2 Qt6::QuickControls2
Qt6::WebView Qt6::WebView
Qt6::Concurrent
KF6::Kirigami2 KF6::Kirigami2
KF6::I18n KF6::I18n
KF6::ConfigCore KF6::ConfigCore

View file

@ -7,6 +7,7 @@
#include <QNetworkAccessManager> #include <QNetworkAccessManager>
#include <QString> #include <QString>
#include <physis.hpp> #include <physis.hpp>
#include <qcorotask.h>
class LauncherCore; class LauncherCore;
@ -19,13 +20,9 @@ public:
Patcher(LauncherCore &launcher, QString baseDirectory, GameData *game_data, QObject *parent = nullptr); Patcher(LauncherCore &launcher, QString baseDirectory, GameData *game_data, QObject *parent = nullptr);
Patcher(LauncherCore &launcher, QString baseDirectory, BootData *game_data, QObject *parent = nullptr); Patcher(LauncherCore &launcher, QString baseDirectory, BootData *game_data, QObject *parent = nullptr);
void processPatchList(QNetworkAccessManager &mgr, const QString &patchList); QCoro::Task<> patch(QNetworkAccessManager &mgr, const QString &patchList);
signals:
void done();
private: private:
void checkIfDone();
void setupDirectories(); void setupDirectories();
[[nodiscard]] bool isBoot() const [[nodiscard]] bool isBoot() const

View file

@ -10,7 +10,10 @@
#include <QNetworkRequest> #include <QNetworkRequest>
#include <QRegExp> #include <QRegExp>
#include <QStandardPaths> #include <QStandardPaths>
#include <QtConcurrent>
#include <physis.hpp> #include <physis.hpp>
#include <qcorofuture.h>
#include <qcoronetworkreply.h>
#include <utility> #include <utility>
#include "launchercore.h" #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.")); 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()) { 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 { } else {
if (isBoot()) { Q_EMIT m_launcher.stageIndeterminate();
Q_EMIT m_launcher.stageIndeterminate(); Q_EMIT m_launcher.stageChanged(i18n("Checking the FINAL FANTASY XIV Game version."));
Q_EMIT m_launcher.stageChanged(i18n("Checking the FINAL FANTASY XIV Update/Launcher version.")); }
const QStringList parts = patchList.split("\r\n");
remainingPatches = parts.size() - 7;
patchQueue.resize(remainingPatches);
QFutureSynchronizer<void> 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 { } else {
Q_EMIT m_launcher.stageIndeterminate(); qDebug() << "Found existing patch: " << name;
Q_EMIT m_launcher.stageChanged(i18n("Checking the FINAL FANTASY XIV Game version."));
}
const QStringList parts = patchList.split("\r\n"); patchQueue[ourIndex] = {name, repository, version, patchPath, hashes, hashBlockSize, length};
remainingPatches = parts.size() - 7; synchronizer.addFuture({});
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();
}
} }
} }
}
void Patcher::checkIfDone() co_await QtConcurrent::run([&synchronizer] {
{ synchronizer.waitForFinished();
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."));
}
int i = 0; // This must happen synchronously
for (const auto &patch : patchQueue) { size_t i = 0;
Q_EMIT m_launcher.stageDeterminate(0, patchQueue.size(), i++); for (const auto &patch : patchQueue) {
processPatch(patch); Q_EMIT m_launcher.stageDeterminate(0, patchQueue.size(), i++);
} processPatch(patch);
patchQueue.clear();
emit done();
} }
co_return;
} }
void Patcher::processPatch(const QueuedPatch &patch) void Patcher::processPatch(const QueuedPatch &patch)

View file

@ -27,13 +27,6 @@ QCoro::Task<> SquareBoot::bootCheck(const LoginInformation &info)
Q_EMIT window.stageChanged(i18n("Checking for launcher updates...")); Q_EMIT window.stageChanged(i18n("Checking for launcher updates..."));
qDebug() << "Performing boot check..."; 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"))}}; const QUrlQuery query{{QStringLiteral("time"), QDateTime::currentDateTimeUtc().toString(QStringLiteral("yyyy-MM-dd-HH-mm"))}};
QUrl url; QUrl url;
@ -54,7 +47,13 @@ QCoro::Task<> SquareBoot::bootCheck(const LoginInformation &info)
const auto reply = window.mgr->get(request); const auto reply = window.mgr->get(request);
co_await reply; 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) QCoro::Task<> SquareBoot::checkGateStatus(LoginInformation *info)

View file

@ -170,7 +170,7 @@ QCoro::Task<> SquareLauncher::registerSession(const LoginInformation &info)
QString report = QStringLiteral("%1=%2").arg(info.profile->bootVersion, getBootHash(info)); QString report = QStringLiteral("%1=%2").arg(info.profile->bootVersion, getBootHash(info));
for (int i = 1; i < auth.maxExpansion + 1; i++) { for (int i = 1; i < auth.maxExpansion + 1; i++) {
if (i <= static_cast<int>(info.profile->repositories.repositories_count)) { if (i < static_cast<int>(info.profile->repositories.repositories_count)) {
report += QStringLiteral("\nex%1\t%2").arg(QString::number(i), info.profile->repositories.repositories[i].version); report += QStringLiteral("\nex%1\t%2").arg(QString::number(i), info.profile->repositories.repositories[i].version);
} else { } else {
report += QStringLiteral("\nex%1\t2012.01.01.0000.0000").arg(QString::number(i)); 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(); const QString body = reply->readAll();
patcher = new Patcher(window, info.profile->gamePath() + QStringLiteral("/game"), info.profile->gameData, this); patcher = new Patcher(window, info.profile->gamePath() + QStringLiteral("/game"), info.profile->gameData, this);
connect(patcher, &Patcher::done, [this, &info, reply] { co_await patcher->patch(*window.mgr, body);
info.profile->readGameVersion();
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 { } else {
Q_EMIT window.loginError(i18n("Fatal error, request was successful but X-Patch-Unique-Id was not recieved.")); Q_EMIT window.loginError(i18n("Fatal error, request was successful but X-Patch-Unique-Id was not recieved."));
} }