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
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)

View file

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

View file

@ -7,6 +7,7 @@
#include <QNetworkAccessManager>
#include <QString>
#include <physis.hpp>
#include <qcorotask.h>
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

View file

@ -10,7 +10,10 @@
#include <QNetworkRequest>
#include <QRegExp>
#include <QStandardPaths>
#include <QtConcurrent>
#include <physis.hpp>
#include <qcorofuture.h>
#include <qcoronetworkreply.h>
#include <utility>
#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<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 {
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)

View file

@ -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)

View file

@ -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<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);
} 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."));
}