diff --git a/launcher/include/launchercore.h b/launcher/include/launchercore.h index b04de90..03dd768 100755 --- a/launcher/include/launchercore.h +++ b/launcher/include/launchercore.h @@ -105,6 +105,7 @@ public: [[nodiscard]] bool isLoadingFinished() const; [[nodiscard]] bool isSteam() const; [[nodiscard]] bool isSteamDeck() const; + [[nodiscard]] Q_INVOKABLE bool isPatching() const; [[nodiscard]] QNetworkAccessManager *mgr(); [[nodiscard]] LauncherSettings *settings(); @@ -112,19 +113,24 @@ public: [[nodiscard]] AccountManager *accountManager(); [[nodiscard]] Headline *headline() const; -signals: +Q_SIGNALS: void loadingFinished(); void successfulLaunch(); void gameClosed(); void loginError(QString message); void dalamudError(QString message); - void stageChanged(QString message); + void stageChanged(QString message, QString explanation = {}); void stageIndeterminate(); void stageDeterminate(int min, int max, int value); void newsChanged(); void currentProfileChanged(); void autoLoginProfileChanged(); +protected: + friend class Patcher; + + bool m_isPatching = false; + private: QCoro::Task<> beginLogin(LoginInformation &info); diff --git a/launcher/include/patcher.h b/launcher/include/patcher.h index 22a5caa..d44689d 100644 --- a/launcher/include/patcher.h +++ b/launcher/include/patcher.h @@ -5,6 +5,7 @@ #include "patchlist.h" #include +#include #include #include #include @@ -21,6 +22,7 @@ class Patcher : public QObject public: Patcher(LauncherCore &launcher, const QString &baseDirectory, GameData &gameData, QObject *parent = nullptr); Patcher(LauncherCore &launcher, const QString &baseDirectory, BootData &bootData, QObject *parent = nullptr); + ~Patcher() override; QCoro::Task patch(const PatchList &patchList); @@ -40,6 +42,9 @@ private: long length; bool isBoot; + long bytesDownloaded; + bool downloaded = false; + [[nodiscard]] QString getVersion() const { if (isBoot) { @@ -62,4 +67,10 @@ private: int m_remainingPatches = -1; LauncherCore &m_launcher; + + QMutex m_finishedPatchesMutex; + int m_finishedPatches = 0; + + void updateDownloadProgress(int index, int received); + void updateMessage(); }; diff --git a/launcher/src/launchercore.cpp b/launcher/src/launchercore.cpp index 4a55d65..48fd365 100755 --- a/launcher/src/launchercore.cpp +++ b/launcher/src/launchercore.cpp @@ -224,6 +224,11 @@ bool LauncherCore::isSteamDeck() const } } +bool LauncherCore::isPatching() const +{ + return m_isPatching; +} + QNetworkAccessManager *LauncherCore::mgr() { return m_mgr; diff --git a/launcher/src/patcher.cpp b/launcher/src/patcher.cpp index 972e857..880b34c 100644 --- a/launcher/src/patcher.cpp +++ b/launcher/src/patcher.cpp @@ -24,9 +24,11 @@ Patcher::Patcher(LauncherCore &launcher, const QString &baseDirectory, BootData , m_bootData(&bootData) , m_launcher(launcher) { + m_launcher.m_isPatching = true; + setupDirectories(); - Q_EMIT m_launcher.stageChanged(i18n("Checking %1 version.", getBaseString())); + Q_EMIT m_launcher.stageChanged(i18n("Checking %1 version", getBaseString())); } Patcher::Patcher(LauncherCore &launcher, const QString &baseDirectory, GameData &gameData, QObject *parent) @@ -35,9 +37,16 @@ Patcher::Patcher(LauncherCore &launcher, const QString &baseDirectory, GameData , m_gameData(&gameData) , m_launcher(launcher) { + m_launcher.m_isPatching = true; + setupDirectories(); - Q_EMIT m_launcher.stageChanged(i18n("Checking %1 version.", getBaseString())); + Q_EMIT m_launcher.stageChanged(i18n("Checking %1 version", getBaseString())); +} + +Patcher::~Patcher() +{ + m_launcher.m_isPatching = false; } QCoro::Task Patcher::patch(const PatchList &patchList) @@ -47,7 +56,7 @@ QCoro::Task Patcher::patch(const PatchList &patchList) } Q_EMIT m_launcher.stageIndeterminate(); - Q_EMIT m_launcher.stageChanged(i18n("Checking %1 version.", getBaseString())); + Q_EMIT m_launcher.stageChanged(i18n("Updating %1", getBaseString())); m_remainingPatches = patchList.patches().size(); m_patchQueue.resize(m_remainingPatches); @@ -86,18 +95,27 @@ QCoro::Task Patcher::patch(const PatchList &patchList) auto patchReply = m_launcher.mgr()->get(patchRequest); - connect(patchReply, &QNetworkReply::downloadProgress, this, [this, queuedPatch](int received, int total) { - Q_EMIT m_launcher.stageChanged(i18n("Updating %1.\nDownloading %2", getBaseString(), queuedPatch.getVersion())); - Q_EMIT m_launcher.stageDeterminate(0, total, received); + connect(patchReply, &QNetworkReply::downloadProgress, this, [this, ourIndex, queuedPatch](int received, int total) { + updateDownloadProgress(ourIndex, received); }); - synchronizer.addFuture(QtFuture::connect(patchReply, &QNetworkReply::finished).then([patchPath, patchReply] { + synchronizer.addFuture(QtFuture::connect(patchReply, &QNetworkReply::finished).then([this, ourIndex, patchPath, patchReply] { + qDebug(ASTRA_PATCHER) << "Downloaded to" << patchPath; + QFile file(patchPath); file.open(QIODevice::WriteOnly); file.write(patchReply->readAll()); file.close(); + + QMutexLocker locker(&m_finishedPatchesMutex); + m_finishedPatches++; + m_patchQueue[ourIndex].downloaded = true; + + updateMessage(); })); } else { + m_patchQueue[ourIndex].downloaded = true; + m_finishedPatches++; qDebug(ASTRA_PATCHER) << "Found existing patch: " << patch.name; } } @@ -109,10 +127,17 @@ QCoro::Task Patcher::patch(const PatchList &patchList) // This must happen synchronously size_t i = 0; for (const auto &patch : m_patchQueue) { - Q_EMIT m_launcher.stageChanged(i18n("Updating %1.\nInstalling %2", getBaseString(), patch.getVersion())); + QString repositoryName = patch.repository; + if (repositoryName == QStringLiteral("game")) { + repositoryName = QStringLiteral("ffxiv"); + } + + Q_EMIT m_launcher.stageChanged(i18n("Installing %1 - %2 [%3/%4]", repositoryName, patch.version, i++, m_remainingPatches)); Q_EMIT m_launcher.stageDeterminate(0, m_patchQueue.size(), i++); - processPatch(patch); + co_await QtConcurrent::run([this, patch] { + processPatch(patch); + }); } co_return true; @@ -191,3 +216,33 @@ QString Patcher::getBaseString() const return i18n("FINAL FANTASY XIV Game"); } } + +void Patcher::updateDownloadProgress(const int index, int received) +{ + QMutexLocker locker(&m_finishedPatchesMutex); + + m_patchQueue[index].bytesDownloaded = received; + + updateMessage(); +} + +void Patcher::updateMessage() +{ + // Find first not-downloaded patch + for (const auto &patch : m_patchQueue) { + if (!patch.downloaded) { + QString repositoryName = patch.repository; + if (repositoryName == QStringLiteral("game")) { + repositoryName = QStringLiteral("ffxiv"); + } + + const float progress = ((float)patch.bytesDownloaded / (float)patch.length) * 100.0f; + const QString progressStr = QStringLiteral("%1").arg(progress, 1, 'f', 1, '0'); + + Q_EMIT m_launcher.stageChanged(i18n("Downloading %1 - %2 [%3/%4]", repositoryName, patch.version, m_finishedPatches, m_remainingPatches), + i18n("%1%", progressStr)); + Q_EMIT m_launcher.stageDeterminate(0, patch.length, patch.bytesDownloaded); + return; + } + } +} diff --git a/launcher/src/squareenixlogin.cpp b/launcher/src/squareenixlogin.cpp index 8f8b739..db793e8 100644 --- a/launcher/src/squareenixlogin.cpp +++ b/launcher/src/squareenixlogin.cpp @@ -86,12 +86,15 @@ QCoro::Task<> SquareEnixLogin::checkBootUpdates() Q_EMIT m_launcher.stageChanged(i18n("Checking for launcher updates...")); qInfo(ASTRA_LOG) << "Checking for updates to boot components..."; - const QUrlQuery query{{QStringLiteral("time"), QDateTime::currentDateTimeUtc().toString(QStringLiteral("yyyy-MM-dd-HH-mm"))}}; + QString formattedDate = QDateTime::currentDateTimeUtc().toString(QStringLiteral("yyyy-MM-dd-HH-mm")); + formattedDate[15] = '0'; + + const QUrlQuery query{{QStringLiteral("time"), formattedDate}}; QUrl url; url.setScheme(QStringLiteral("http")); url.setHost(QStringLiteral("patch-bootver.%1").arg(m_launcher.settings()->squareEnixServer())); - url.setPath(QStringLiteral("/http/win32/ffxivneo_release_boot/%1").arg(m_info->profile->bootVersion())); + url.setPath(QStringLiteral("/http/win32/ffxivneo_release_boot/%1/").arg(m_info->profile->bootVersion())); url.setQuery(query); auto request = QNetworkRequest(url); diff --git a/launcher/ui/Main.qml b/launcher/ui/Main.qml index 0ab56ec..dd79248 100644 --- a/launcher/ui/Main.qml +++ b/launcher/ui/Main.qml @@ -29,6 +29,13 @@ Kirigami.ApplicationWindow { } } + onClosing: (close) => { + if (LauncherCore.isPatching()) { + applicationWindow().showPassiveNotification(i18n("Please do not quit while patching!")); + } + close.accepted = !LauncherCore.isPatching(); + } + function checkSetup() { if (!LauncherCore.loadingFinished) { return diff --git a/launcher/ui/Pages/StatusPage.qml b/launcher/ui/Pages/StatusPage.qml index a18a6e5..b93c42b 100644 --- a/launcher/ui/Pages/StatusPage.qml +++ b/launcher/ui/Pages/StatusPage.qml @@ -12,6 +12,14 @@ Kirigami.Page { title: i18n("Logging in...") + onBackRequested: (event) => { + if (LauncherCore.isPatching()) { + // Prevent going back + applicationWindow().showPassiveNotification(i18n("Please do not quit while patching!")); + event.accepted = true; + } + } + Kirigami.LoadingPlaceholder { id: placeholder @@ -48,8 +56,9 @@ Kirigami.Page { Connections { target: LauncherCore - function onStageChanged(message) { + function onStageChanged(message, explanation) { placeholder.text = message + placeholder.explanation = explanation } function onStageIndeterminate() {