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:
parent
e211c95e21
commit
21d305fd08
6 changed files with 100 additions and 114 deletions
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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."));
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue