1
0
Fork 0
mirror of synced 2024-06-30 04:00:48 +12:00

CloudSaveUtils: Transition to RareGame

Signed-off-by: loathingKernel <142770+loathingKernel@users.noreply.github.com>
This commit is contained in:
loathingKernel 2023-01-09 19:58:57 +02:00
parent 79bae4374d
commit c7a70a9a75
2 changed files with 91 additions and 95 deletions

View file

@ -6,9 +6,10 @@ from typing import Union, List, Dict
from PyQt5.QtCore import QObject, pyqtSignal, QRunnable, QThreadPool, Qt, QSettings
from PyQt5.QtWidgets import QDialog, QMessageBox, QSizePolicy, QLayout, QApplication
from legendary.core import LegendaryCore
from legendary.models.game import SaveGameStatus, InstalledGame, SaveGameFile
from rare.models.game import RareGame
from rare.shared import LegendaryCoreSingleton, ArgumentsSingleton, ApiResultsSingleton
from rare.ui.components.dialogs.sync_save_dialog import Ui_SyncSaveDialog
from rare.utils.misc import icon
@ -18,26 +19,26 @@ logger = getLogger("Cloud Saves")
@dataclass
class UploadModel:
app_name: str
rgame: RareGame
date_time: datetime.datetime
path: str
@dataclass
class DownloadModel:
app_name: str
rgame: RareGame
latest_save: SaveGameFile
path: str
class SaveSignals(QObject):
finished = pyqtSignal(str, str)
class SaveWorker(QRunnable):
class Signals(QObject):
finished = pyqtSignal(RareGame, str)
def __init__(self, model: Union[UploadModel, DownloadModel]):
super(SaveWorker, self).__init__()
self.signals = SaveSignals()
self.signals = SaveWorker.Signals()
self.setAutoDelete(True)
self.core = LegendaryCoreSingleton()
self.api_results = ApiResultsSingleton()
@ -47,29 +48,29 @@ class SaveWorker(QRunnable):
try:
if isinstance(self.model, DownloadModel):
self.core.download_saves(
self.model.app_name,
self.model.rgame.app_name,
self.model.latest_save.manifest_name,
self.model.path,
)
else:
self.core.upload_save(
self.model.app_name, self.model.path, self.model.date_time
self.model.rgame.app_name, self.model.path, self.model.date_time
)
except Exception as e:
self.signals.finished.emit(str(e), self.model.app_name)
self.signals.finished.emit(self.model.rgame, str(e))
logger.error(str(e))
return
try:
if isinstance(self.model, UploadModel):
logger.info("Updating cloud saves...")
result = self.core.get_save_games(self.model.app_name)
result = self.core.get_save_games(self.model.rgame.app_name)
self.api_results.saves = result
except Exception as e:
self.signals.finished.emit(str(e), self.model.app_name)
self.signals.finished.emit(self.model.rgame, str(e))
logger.error(str(e))
return
self.signals.finished.emit("", self.model.app_name)
self.signals.finished.emit(self.model.rgame, "")
class CloudSaveDialog(QDialog, Ui_SyncSaveDialog):
@ -123,7 +124,7 @@ class CloudSaveDialog(QDialog, Ui_SyncSaveDialog):
class CloudSaveUtils(QObject):
sync_finished = pyqtSignal(str)
sync_finished = pyqtSignal(RareGame)
def __init__(self):
super(CloudSaveUtils, self).__init__()
@ -152,24 +153,23 @@ class CloudSaveUtils(QObject):
latest_saves[s.app_name] = s
return latest_saves
def sync_before_launch_game(self, app_name, ignore_settings=False) -> int:
def sync_before_launch_game(self, rgame: RareGame, ignore_settings=False) -> int:
if not ignore_settings:
default = self.settings.value("auto_sync_cloud", True, bool)
if not self.settings.value(f"{app_name}/auto_sync_cloud", default, bool):
if not self.settings.value(f"{rgame.app_name}/auto_sync_cloud", default, bool):
return False
igame = self.core.get_installed_game(app_name)
if not igame.save_path:
if not rgame.igame.save_path:
try:
savepath = self.core.get_save_path(app_name)
savepath = self.core.get_save_path(rgame.app_name)
except Exception as e:
logger.error(e)
savepath = ""
if savepath:
igame.save_path = savepath
self.core.lgd.set_installed_game(app_name, igame)
logger.info(f"Set save path of {igame.title} to {savepath}")
rgame.igame.save_path = savepath
self.core.lgd.set_installed_game(rgame.app_name, rgame.igame)
logger.info(f"Set save path of {rgame.title} to {savepath}")
elif not ignore_settings: # sync on startup
if (
QMessageBox.question(
@ -177,7 +177,7 @@ class CloudSaveUtils(QObject):
"Warning",
self.tr(
"Could not compute cloud save path. Please set it in Game settings manually. \nDo you want to launch {} anyway?"
).format(igame.title),
).format(rgame.title),
QMessageBox.Yes | QMessageBox.No,
QMessageBox.No,
)
@ -197,7 +197,7 @@ class CloudSaveUtils(QObject):
return False
res, (dt_local, dt_remote) = self.core.check_savegame_state(
igame.save_path, self.latest_saves.get(app_name)
rgame.igame.save_path, self.latest_saves.get(rgame.app_name)
)
if res == SaveGameStatus.NO_SAVE:
@ -211,43 +211,41 @@ class CloudSaveUtils(QObject):
newer = "local"
if res == SaveGameStatus.REMOTE_NEWER and not dt_local:
self.download_saves(igame)
self.download_saves(rgame)
return True
elif res == SaveGameStatus.LOCAL_NEWER and not dt_remote:
self.upload_saves(igame, dt_local)
self.upload_saves(rgame, dt_local)
return True
result = CloudSaveDialog(igame, dt_local, dt_remote, newer).get_action()
result = CloudSaveDialog(rgame.igame, dt_local, dt_remote, newer).get_action()
if result == CloudSaveDialog.UPLOAD:
self.upload_saves(igame, dt_local)
self.upload_saves(rgame, dt_local)
elif result == CloudSaveDialog.DOWNLOAD:
self.download_saves(igame)
self.download_saves(rgame)
elif result == CloudSaveDialog.CANCEL:
raise AssertionError()
return True
return False
def game_finished(self, app_name, ignore_settings=False, always_ask: bool = False):
def game_finished(self, rgame: RareGame, ignore_settings=False, always_ask: bool = False):
if not ignore_settings:
default = self.settings.value("auto_sync_cloud", True, bool)
if not self.settings.value(f"{app_name}/auto_sync_cloud", default, bool):
self.sync_finished.emit(app_name)
if not self.settings.value(f"{rgame.app_name}/auto_sync_cloud", default, bool):
self.sync_finished.emit(rgame)
return
igame = self.core.get_installed_game(app_name)
if not igame.save_path:
if not rgame.igame.save_path:
try:
savepath = self.core.get_save_path(app_name)
savepath = self.core.get_save_path(rgame.app_name)
except Exception as e:
logger.error(e)
savepath = ""
if savepath:
igame.save_path = savepath
self.core.lgd.set_installed_game(app_name, igame)
logger.info(f"Set save path of {igame.title} to {savepath}")
rgame.igame.save_path = savepath
self.core.lgd.set_installed_game(rgame.app_name, rgame.igame)
logger.info(f"Set save path of {rgame.title} to {savepath}")
else:
QMessageBox.warning(
None, "Warning", self.tr("No savepath set. Skip syncing with cloud")
@ -255,11 +253,11 @@ class CloudSaveUtils(QObject):
return False
res, (dt_local, dt_remote) = self.core.check_savegame_state(
igame.save_path, self.latest_saves.get(app_name)
rgame.igame.save_path, self.latest_saves.get(rgame.app_name)
)
if res == SaveGameStatus.LOCAL_NEWER and not always_ask:
self.upload_saves(igame, dt_local)
self.upload_saves(rgame, dt_local)
return
elif res == SaveGameStatus.NO_SAVE and not always_ask:
@ -268,13 +266,13 @@ class CloudSaveUtils(QObject):
"No saves",
self.tr(
"There are no saves local and online. Maybe you have to change save path of {}"
).format(igame.title),
).format(rgame.title),
)
self.sync_finished.emit(app_name)
self.sync_finished.emit(rgame)
return
elif res == SaveGameStatus.SAME_AGE and not always_ask:
self.sync_finished.emit(app_name)
self.sync_finished.emit(rgame)
return
# Remote newer
@ -283,32 +281,32 @@ class CloudSaveUtils(QObject):
newer = "remote"
elif res == SaveGameStatus.LOCAL_NEWER:
newer = "local"
result = CloudSaveDialog(igame, dt_local, dt_remote, newer).get_action()
result = CloudSaveDialog(rgame.igame, dt_local, dt_remote, newer).get_action()
if result == CloudSaveDialog.UPLOAD:
self.upload_saves(igame, dt_local)
self.upload_saves(rgame, dt_local)
elif result == CloudSaveDialog.DOWNLOAD:
self.download_saves(igame)
self.download_saves(rgame)
def upload_saves(self, igame: InstalledGame, dt_local):
logger.info(f"Uploading saves for {igame.title}")
w = SaveWorker(UploadModel(igame.app_name, dt_local, igame.save_path))
def upload_saves(self, rgame: RareGame, dt_local):
logger.info(f"Uploading saves for {rgame.title}")
w = SaveWorker(UploadModel(rgame, dt_local, rgame.igame.save_path))
w.signals.finished.connect(self.worker_finished)
self.thread_pool.start(w)
def download_saves(self, igame):
logger.info(f"Downloading saves for {igame.title}")
def download_saves(self, rgame: RareGame):
logger.info(f"Downloading saves for {rgame.title}")
w = SaveWorker(
DownloadModel(
igame.app_name, self.latest_saves.get(igame.app_name), igame.save_path
rgame, self.latest_saves.get(rgame.app_name), rgame.igame.save_path
)
)
w.signals.finished.connect(self.worker_finished)
self.thread_pool.start(w)
def worker_finished(self, error_message: str, app_name: str):
def worker_finished(self, rgame: RareGame, error_message: str,):
if not error_message:
self.sync_finished.emit(app_name)
self.sync_finished.emit(rgame)
self.latest_saves = self.get_latest_saves(self.api_results.saves)
else:
QMessageBox.warning(
@ -316,7 +314,7 @@ class CloudSaveUtils(QObject):
"Warning",
self.tr("Syncing with cloud failed: \n {}").format(error_message)
)
self.sync_finished.emit(app_name)
self.sync_finished.emit(rgame)
def test_dialog():

View file

@ -4,7 +4,7 @@ import os
import platform
from logging import getLogger
from PyQt5.QtCore import QObject, QProcess, pyqtSignal, QUrl, QTimer
from PyQt5.QtCore import QObject, QProcess, pyqtSignal, QUrl, QTimer, pyqtSlot
from PyQt5.QtCore import QStandardPaths
from PyQt5.QtGui import QDesktopServices
from PyQt5.QtNetwork import QLocalSocket
@ -26,9 +26,9 @@ logger = getLogger("GameUtils")
class GameProcess(QObject):
# str: app_name, int: exit_code
game_finished = pyqtSignal(str, int)
game_finished = pyqtSignal(RareGame, int)
# str: app_name
game_launched = pyqtSignal(str)
game_launched = pyqtSignal(RareGame)
def __init__(self, rgame: RareGame, on_startup=False, always_ask_sync: bool = False):
super(GameProcess, self).__init__()
@ -68,7 +68,7 @@ class GameProcess(QObject):
if self.tried_connections > 50: # 10 seconds
QMessageBox.warning(None, "Error", self.tr("Connection to game process failed (Timeout)"))
self.timer.stop()
self.game_finished.emit(self.rgame.app_name, 1)
self.game_finished.emit(self.rgame, 1)
def _message_available(self):
message = self.socket.readAll().data()
@ -103,7 +103,7 @@ class GameProcess(QObject):
model = message_models.StateChangedModel.from_json(data)
if model.new_state == message_models.StateChangedModel.States.started:
logger.info("Launched Game")
self.game_launched.emit(self.rgame.app_name)
self.game_launched.emit(self.rgame)
meta_data = self.rgame.metadata
meta_data.last_played = datetime.datetime.now()
self.rgame.save_metadata()
@ -116,7 +116,7 @@ class GameProcess(QObject):
logger.info(f"Found {self.rgame.app_name} ({self.rgame.app_title}) running at startup")
# FIXME run this after startup, widgets do not exist at this time
QTimer.singleShot(1000, lambda: self.game_launched.emit(self.rgame.app_name))
QTimer.singleShot(1000, lambda: self.game_launched.emit(self.rgame))
def _error_occurred(self, _):
if self.on_startup:
@ -126,7 +126,7 @@ class GameProcess(QObject):
logger.error(f"{self.rgame.app_name} ({self.rgame.app_title}): {self.socket.errorString()}")
def _game_finished(self, exit_code: int):
self.game_finished.emit(self.rgame.app_name, exit_code)
self.game_finished.emit(self.rgame, exit_code)
def uninstall_game(core: LegendaryCore, app_name: str, keep_files=False, keep_config=False):
@ -198,32 +198,30 @@ class GameUtils(QObject):
game_process.game_launched.connect(self.game_launched.emit)
self.running_games[rgame.app_name] = game_process
def uninstall_game(self, app_name) -> bool:
def uninstall_game(self, rgame: RareGame) -> bool:
# returns if uninstalled
game = self.core.get_game(app_name)
igame = self.core.get_installed_game(app_name)
if not os.path.exists(igame.install_path):
if not os.path.exists(rgame.igame.install_path):
if QMessageBox.Yes == QMessageBox.question(
None,
self.tr("Uninstall - {}").format(igame.title),
self.tr("Uninstall - {}").format(rgame.igame.title),
self.tr(
"Game files of {} do not exist. Remove it from installed games?"
).format(igame.title),
).format(rgame.igame.title),
QMessageBox.Yes | QMessageBox.No,
QMessageBox.Yes,
):
self.core.lgd.remove_installed_game(app_name)
self.core.lgd.remove_installed_game(rgame.app_name)
return True
else:
return False
proceed, keep_files, keep_config = UninstallDialog(game).get_options()
proceed, keep_files, keep_config = UninstallDialog(rgame.game).get_options()
if not proceed:
return False
success, message = uninstall_game(self.core, game.app_name, keep_files, keep_config)
success, message = uninstall_game(self.core, rgame.app_name, keep_files, keep_config)
if not success:
QMessageBox.warning(None, self.tr("Uninstall - {}").format(igame.title), message, QMessageBox.Close)
self.signals.game.uninstalled.emit(app_name)
QMessageBox.warning(None, self.tr("Uninstall - {}").format(rgame.title), message, QMessageBox.Close)
self.signals.game.uninstalled.emit(rgame.app_name)
return True
def prepare_launch(
@ -234,18 +232,18 @@ class GameUtils(QObject):
# TODO move this to helper
if rgame.game.supports_cloud_saves and not offline:
try:
sync = self.cloud_save_utils.sync_before_launch_game(rgame.app_name)
sync = self.cloud_save_utils.sync_before_launch_game(rgame)
except ValueError:
logger.info("Cancel startup")
self.sync_finished(rgame.app_name)
self.sync_finished(rgame)
return
except AssertionError:
dont_sync_after_finish = True
else:
if sync:
self.launch_queue[rgame.app_name] = (rgame.app_name, skip_update_check, offline)
self.launch_queue[rgame.app_name] = (rgame, skip_update_check, offline)
return
self.sync_finished(rgame.app_name)
self.sync_finished(rgame)
self.launch_game(
rgame, offline, skip_update_check, ask_always_sync=dont_sync_after_finish
@ -284,18 +282,17 @@ class GameUtils(QObject):
game_process.game_launched.connect(self.game_launched.emit)
self.running_games[rgame.app_name] = game_process
def game_finished(self, app_name, exit_code):
if self.running_games.get(app_name):
self.running_games.pop(app_name)
def game_finished(self, rgame: RareGame, exit_code):
if self.running_games.get(rgame.app_name):
self.running_games.pop(rgame.app_name)
if exit_code == -1234:
return
self.finished.emit(app_name, "")
self.finished.emit(rgame.app_name, "")
logger.info(f"Game exited with exit code: {exit_code}")
self.signals.discord_rpc.set_title.emit("")
is_origin = self.core.get_game(app_name).third_party_store == "Origin"
if exit_code == 1 and is_origin:
if exit_code == 1 and rgame.is_origin:
msg_box = QMessageBox()
msg_box.setText(
self.tr(
@ -322,10 +319,10 @@ class GameUtils(QObject):
)
"""
if app_name in self.running_games.keys():
self.running_games.pop(app_name)
if rgame.app_name in self.running_games.keys():
self.running_games.pop(rgame.app_name)
if self.core.get_game(app_name).supports_cloud_saves:
if rgame.game.supports_cloud_saves:
if exit_code != 0:
r = QMessageBox.question(
None,
@ -341,13 +338,14 @@ class GameUtils(QObject):
return
# TODO move this to helper
self.cloud_save_utils.game_finished(app_name, always_ask=False)
self.cloud_save_utils.game_finished(rgame, always_ask=False)
def sync_finished(self, app_name):
if app_name in self.launch_queue.keys():
self.cloud_save_finished.emit(app_name)
params = self.launch_queue[app_name]
self.launch_queue.pop(app_name)
@pyqtSlot(RareGame)
def sync_finished(self, rgame: RareGame):
if rgame.app_name in self.launch_queue.keys():
self.cloud_save_finished.emit(rgame.app_name)
params = self.launch_queue[rgame.app_name]
self.launch_queue.pop(rgame.app_name)
self.launch_game(*params)
else:
self.cloud_save_finished.emit(app_name)
self.cloud_save_finished.emit(rgame.app_name)