1
Fork 0
mirror of https://github.com/redstrate/Astra.git synced 2025-04-20 19:57:45 +00:00

Implement sync locking, and upload data when quitting the game

This commit is contained in:
Joshua Goins 2024-07-30 19:47:26 -04:00
parent 3d989d5790
commit a0da4d02b7
9 changed files with 92 additions and 18 deletions

View file

@ -21,8 +21,9 @@ public:
explicit CharacterSync(Account &account, LauncherCore &launcher, QObject *parent = nullptr);
/// Checks and synchronizes character files as necessary.
/// \param initialSync Whether this is the initial sync on game start.
/// \return False if the synchronization failed.
QCoro::Task<bool> sync();
QCoro::Task<bool> sync(bool initialSync = true);
private:
QCoro::Task<void> uploadCharacterData(const QDir &dir, const QString &id);

View file

@ -136,7 +136,7 @@ public:
Q_SIGNALS:
void loadingFinished();
void successfulLaunch();
void gameClosed();
void gameClosed(Profile *profile);
void loginError(QString message);
void dalamudError(QString message);
void miscError(QString message);
@ -158,6 +158,8 @@ private:
QCoro::Task<> fetchNews();
QCoro::Task<> handleGameExit(Profile *profile);
SteamAPI *m_steamApi = nullptr;
bool m_loadingFinished = false;

View file

@ -61,6 +61,15 @@ public:
/// Downloads character data
QCoro::Task<bool> downloadCharacterData(const QString &mxcUri, const QString &destPath);
/// Checks the lock on the sync
QCoro::Task<std::optional<QString>> checkLock();
/// Sets the sync lock to the device's hostname
QCoro::Task<> setLock();
/// Breaks the sync lock
QCoro::Task<> breakLock();
Q_SIGNALS:
void connectedChanged();
void userIdChanged();

View file

@ -16,14 +16,12 @@ CharacterSync::CharacterSync(Account &account, LauncherCore &launcher, QObject *
{
}
QCoro::Task<bool> CharacterSync::sync()
QCoro::Task<bool> CharacterSync::sync(const bool initialSync)
{
if (!launcher.settings()->enableSync()) {
co_return true;
}
qInfo() << "A";
auto syncManager = launcher.syncManager();
if (!syncManager->connected()) {
qInfo() << "B";
@ -32,8 +30,6 @@ QCoro::Task<bool> CharacterSync::sync()
co_return false;
}
qInfo() << "C";
if (!syncManager->isReady()) {
Q_EMIT launcher.stageChanged(i18n("Waiting for sync connection..."));
@ -43,6 +39,21 @@ QCoro::Task<bool> CharacterSync::sync()
Q_EMIT launcher.stageChanged(i18n("Synchronizing character data..."));
// On game boot, check if we need the lock. Otherwise break it when we clean up.
if (initialSync) {
if (const auto hostname = co_await syncManager->checkLock(); hostname.has_value()) {
// Don't warn about our own failures
if (hostname != QSysInfo::machineHostName()) {
Q_EMIT launcher.loginError(i18n("Device %1 has not yet uploaded it's character data. Astra will not continue until that device is re-synced."));
co_return false;
}
}
syncManager->setLock();
} else {
syncManager->breakLock();
}
// so first, we need to list the character folders
// we sync each one separately
QList<QFileInfo> characterDirs;
@ -63,15 +74,19 @@ QCoro::Task<bool> CharacterSync::sync()
for (const auto &dir : characterDirs) {
const QString id = dir.fileName(); // FFXIV_CHR0040000001000001 for example
const auto previousData = co_await syncManager->getUploadedCharacterData(id);
if (!previousData.has_value()) {
// TODO: make this a little bit smarter. We shouldn't waste time re-uploading data that's exactly the same.
if (!initialSync || !previousData.has_value()) {
// if we didn't upload character data yet, upload it now
co_await uploadCharacterData(dir.absoluteFilePath(), id);
} else {
// otherwise, download it
// but check first if it's our hostname
if (QSysInfo::machineHostName() == previousData->hostname) {
qCDebug(ASTRA_LOG) << "Skipping! We uploaded this data.";
const bool exists = QFile::exists(dir.absoluteFilePath() + QStringLiteral("/GEARSET.DAT"));
// but check first if it's our hostname. only skip if it exists
if (exists && QSysInfo::machineHostName() == previousData->hostname) {
qCDebug(ASTRA_LOG) << "Skipping" << id << "We uploaded this data.";
continue;
}

View file

@ -49,7 +49,7 @@ void GameRunner::beginVanillaGame(const QString &gameExecutablePath, Profile &pr
connect(gameProcess, &QProcess::finished, this, [this, &profile](const int exitCode) {
profile.setLoggedIn(false);
Q_UNUSED(exitCode)
Q_EMIT m_launcher.gameClosed();
Q_EMIT m_launcher.gameClosed(&profile);
});
auto args = getGameArgs(profile, auth);
@ -109,7 +109,7 @@ void GameRunner::beginDalamudGame(const QString &gameExecutablePath, Profile &pr
auto watcher = new ProcessWatcher(PID);
connect(watcher, &ProcessWatcher::finished, this, [this, &profile] {
profile.setLoggedIn(false);
Q_EMIT m_launcher.gameClosed();
Q_EMIT m_launcher.gameClosed(&profile);
});
return;
}
@ -117,7 +117,7 @@ void GameRunner::beginDalamudGame(const QString &gameExecutablePath, Profile &pr
// If Dalamud didn't give a valid PID, OK. Let's just do our previous status quo and inidcate we did log out.
profile.setLoggedIn(false);
Q_EMIT m_launcher.gameClosed();
Q_EMIT m_launcher.gameClosed(&profile);
});
QProcessEnvironment env = QProcessEnvironment::systemEnvironment();

View file

@ -40,6 +40,8 @@ LauncherCore::LauncherCore()
m_accountManager = new AccountManager(*this, this);
m_runner = new GameRunner(*this, this);
connect(this, &LauncherCore::gameClosed, this, &LauncherCore::handleGameExit);
#ifdef BUILD_SYNC
m_syncManager = new SyncManager(this);
#endif
@ -513,4 +515,19 @@ QCoro::Task<> LauncherCore::fetchNews()
Q_EMIT newsChanged();
}
QCoro::Task<> LauncherCore::handleGameExit(Profile *profile)
{
#ifdef BUILD_SYNC
qCDebug(ASTRA_LOG) << "Game closed! Uploading character data...";
const auto characterSync = new CharacterSync(*profile->account(), *this, this);
co_await characterSync->sync(false); // TODO: handle errors and especially interactive ones
#endif
// Otherwise, quit when everything is finished.
if (m_settings->closeWhenLaunched()) {
QCoreApplication::exit();
}
co_return;
}
#include "moc_launchercore.cpp"

View file

@ -21,6 +21,7 @@
const QString roomType = QStringLiteral("zone.xiv.astra-sync");
const QString syncEventType = QStringLiteral("zone.xiv.astra.sync");
const QString lockEventType = QStringLiteral("zone.xiv.astra.lock");
using namespace Quotient;
@ -250,4 +251,35 @@ QCoro::Task<bool> SyncManager::downloadCharacterData(const QString &mxcUri, cons
co_return true;
}
QCoro::Task<std::optional<QString>> SyncManager::checkLock()
{
const auto lockEvent = m_currentRoom->currentState().contentJson(syncEventType, QStringLiteral("latest"));
if (lockEvent.isEmpty()) {
co_return std::nullopt;
}
qCDebug(ASTRA_LOG) << "previous lock event:" << lockEvent;
const QString hostname = lockEvent[QStringLiteral("hostname")].toString();
if (hostname == QStringLiteral("none")) {
co_return std::nullopt;
}
co_return hostname;
}
QCoro::Task<> SyncManager::setLock()
{
auto lockSetState =
m_currentRoom->setState(syncEventType, QStringLiteral("latest"), QJsonObject{{QStringLiteral("hostname"), QSysInfo::machineHostName()}});
co_await qCoro(lockSetState, &BaseJob::finished);
co_return;
}
QCoro::Task<> SyncManager::breakLock()
{
auto lockSetState = m_currentRoom->setState(syncEventType, QStringLiteral("latest"), QJsonObject{{QStringLiteral("hostname"), QStringLiteral("none")}});
co_await qCoro(lockSetState, &BaseJob::finished);
co_return;
}
#include "moc_syncmanager.cpp"

View file

@ -113,9 +113,7 @@ Kirigami.ApplicationWindow {
}
function onGameClosed() {
if (LauncherCore.settings.closeWhenLaunched) {
Qt.callLater(Qt.quit);
} else {
if (!LauncherCore.settings.closeWhenLaunched) {
appWindow.checkSetup();
}
}

View file

@ -24,7 +24,7 @@ FormCard.FormCardPage {
FormCard.FormCheckDelegate {
id: closeAstraDelegate
text: i18n("Close Astra when game is launched")
text: i18n("Hide Astra when game is launched")
checked: LauncherCore.settings.closeWhenLaunched
onCheckedChanged: LauncherCore.settings.closeWhenLaunched = checked
}