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:
parent
3d989d5790
commit
a0da4d02b7
9 changed files with 92 additions and 18 deletions
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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"
|
|
@ -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"
|
||||
|
|
|
@ -113,9 +113,7 @@ Kirigami.ApplicationWindow {
|
|||
}
|
||||
|
||||
function onGameClosed() {
|
||||
if (LauncherCore.settings.closeWhenLaunched) {
|
||||
Qt.callLater(Qt.quit);
|
||||
} else {
|
||||
if (!LauncherCore.settings.closeWhenLaunched) {
|
||||
appWindow.checkSetup();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue