1
0
Fork 0
mirror of synced 2024-06-02 18:54:41 +12:00

RareCore: Fetch **ALL** saves during launch

To save time and requests, bulk get saves for all games and
load them into each respective RareGame.

Co-authored-by: Dummerle <44114474+dummerle@users.noreply.github.com>
This commit is contained in:
loathingKernel 2023-03-10 16:55:31 +02:00
parent f6189772d0
commit 81ec9bf772
No known key found for this signature in database
GPG key ID: CE0C72D0B53821FD
5 changed files with 71 additions and 40 deletions

View file

@ -1,4 +1,5 @@
from abc import abstractmethod
from dataclasses import dataclass
from datetime import datetime
from enum import IntEnum
from logging import getLogger
@ -13,6 +14,14 @@ from rare.models.install import UninstallOptionsModel, InstallOptionsModel
logger = getLogger("RareGameBase")
@dataclass
class RareSaveGame:
file: SaveGameFile
status: SaveGameStatus = SaveGameStatus.NO_SAVE
dt_local: Optional[datetime] = None
dt_remote: datetime = None
class RareGameBase(QObject):
class State(IntEnum):
@ -122,7 +131,7 @@ class RareGameSlim(RareGameBase):
super(RareGameSlim, self).__init__(legendary_core, game)
# None if origin or not installed
self.igame: Optional[InstalledGame] = self.core.get_installed_game(game.app_name)
self.saves: List[SaveGameFile] = []
self.saves: List[RareSaveGame] = []
@property
def is_installed(self) -> bool:
@ -162,16 +171,18 @@ class RareGameSlim(RareGameBase):
return None
@property
def latest_save(self) -> Optional[SaveGameFile]:
def latest_save(self) -> Optional[RareSaveGame]:
if self.saves:
self.saves.sort(key=lambda s: s.datetime, reverse=True)
return self.saves[0]
saves = sorted(self.saves, key=lambda s: s.file.datetime, reverse=True)
return saves[0]
return None
@property
def save_game_state(self) -> (SaveGameStatus, (datetime, datetime)):
if self.saves and self.save_path :
return self.core.check_savegame_state(self.save_path, self.latest_save)
if self.saves and self.save_path:
latest = self.latest_save
return latest.status, (latest.dt_local, latest.dt_remote)
# return self.core.check_savegame_state(self.save_path, self.latest_save.save)
return SaveGameStatus.NO_SAVE, (None, None)
def upload_saves(self, thread=True):
@ -203,7 +214,7 @@ class RareGameSlim(RareGameBase):
def _download():
logger.info(f"Downloading save for {self.title}")
self.state = RareGameSlim.State.SYNCING
self.core.download_saves(self.app_name, self.latest_save.manifest_name, self.save_path)
self.core.download_saves(self.app_name, self.latest_save.file.manifest_name, self.save_path)
self.state = RareGameSlim.State.IDLE
self.update_saves()
@ -222,8 +233,21 @@ class RareGameSlim(RareGameBase):
else:
_download()
def load_saves(self, saves: List[SaveGameFile]):
""" Use only in a thread """
self.saves.clear()
for save in saves:
if self.save_path:
status, (dt_local, dt_remote) = self.core.check_savegame_state(self.save_path, save)
rsave = RareSaveGame(save, status, dt_local, dt_remote)
else:
rsave = RareSaveGame(save, SaveGameStatus.SAME_AGE, dt_local=None, dt_remote=save.datetime)
self.saves.append(rsave)
def update_saves(self):
self.saves = self.core.get_save_games(self.app_name)
""" Use only in a thread """
saves = self.core.get_save_games(self.app_name)
self.load_saves(saves)
self.signals.widget.update.emit()
@property
@ -231,3 +255,15 @@ class RareGameSlim(RareGameBase):
status, (_, _) = self.save_game_state
return (status == SaveGameStatus.SAME_AGE) \
or (status == SaveGameStatus.NO_SAVE)
@property
def raw_save_path(self) -> str:
if self.game.supports_cloud_saves:
return self.game.metadata.get("customAttributes", {}).get("CloudSaveFolder", {}).get("value")
return ""
@property
def raw_save_path_mac(self) -> str:
if self.game.supports_mac_cloud_saves:
return self.game.metadata.get("customAttributes", {}).get('CloudSaveFolder_MAC', {}).get('value')
return ""

View file

@ -401,18 +401,6 @@ class RareGame(RareGameSlim):
def folder_name(self) -> str:
return self.game.metadata.get("customAttributes", {}).get("FolderName", {}).get("value")
@property
def raw_save_path(self) -> str:
if self.game.supports_cloud_saves:
return self.game.metadata.get("customAttributes", {}).get("CloudSaveFolder", {}).get("value")
return ""
@property
def raw_save_path_mac(self) -> str:
if self.game.supports_mac_cloud_saves:
return self.game.metadata.get("customAttributes", {}).get('CloudSaveFolder_MAC', {}).get('value')
return ""
@property
def save_path(self) -> Optional[str]:
return super(RareGame, self).save_path

View file

@ -6,7 +6,7 @@ from itertools import chain
from logging import getLogger
from typing import Dict, Iterator, Callable, Tuple, Optional, List, Union
from PyQt5.QtCore import QObject, pyqtSignal, QSettings, pyqtSlot, QThreadPool
from PyQt5.QtCore import QObject, pyqtSignal, QSettings, pyqtSlot, QThreadPool, QRunnable
from legendary.lfs.eos import EOSOverlayApp
from legendary.models.game import Game, SaveGameFile
@ -27,7 +27,7 @@ from .workers import (
)
from .workers.uninstall import uninstall_game
from .workers.worker import QueueWorkerInfo, QueueWorkerState
from rare.models.base_game import RareGameBase
from rare.models.base_game import RareSaveGame
logger = getLogger("RareCore")
@ -272,11 +272,14 @@ class RareCore(QObject):
if res_type == FetchWorker.Result.GAMES:
games, dlc_dict = result
self.__add_games_and_dlcs(games, dlc_dict)
self.fetch_extra()
self.__games_fetched = True
status = "Loaded games for Windows"
if res_type == FetchWorker.Result.NON_ASSET:
games, dlc_dict = result
self.__add_games_and_dlcs(games, dlc_dict)
self.fetch_saves()
self.resolve_origin()
self.__non_asset_fetched = True
status = "Loaded games without assets"
if res_type == FetchWorker.Result.ORIGIN:
@ -284,8 +287,8 @@ class RareCore(QObject):
status = "Resolved Origin installation status"
if res_type == FetchWorker.Result.SAVES:
saves, _ = result
for save in saves:
self.__games[save.app_name].saves.append(save)
for app_name, saves in saves.items():
self.__games[app_name].load_saves(saves)
self.__saves_fetched = True
status = "Loaded save games"
if res_type == FetchWorker.Result.ENTITLEMENTS:
@ -321,8 +324,7 @@ class RareCore(QObject):
self.start_time = time.time()
games_worker = GamesWorker(self.__core, self.__args)
games_worker.signals.result.connect(self.handle_result)
games_worker.signals.finished.connect(self.fetch_saves)
games_worker.signals.finished.connect(self.fetch_extra)
QThreadPool.globalInstance().start(games_worker)
def fetch_saves(self):
@ -341,12 +343,14 @@ class RareCore(QObject):
def fetch_extra(self):
non_asset_worker = NonAssetWorker(self.__core, self.__args)
non_asset_worker.signals.result.connect(self.handle_result)
non_asset_worker.signals.finished.connect(self.resolve_origin)
QThreadPool.globalInstance().start(non_asset_worker)
entitlements_worker = EntitlementsWorker(self.__core, self.__args)
entitlements_worker.signals.result.connect(self.handle_result)
QThreadPool.globalInstance().start(entitlements_worker)
if not self.__args.offline:
entitlements_worker = EntitlementsWorker(self.__core, self.__args)
entitlements_worker.signals.result.connect(self.handle_result)
QThreadPool.globalInstance().start(entitlements_worker)
else:
self.__entitlements_fetched = True
def load_pixmaps(self) -> None:
"""
@ -428,7 +432,7 @@ class RareCore(QObject):
return self.__filter_games(lambda game: game.has_update)
@property
def saves(self) -> Iterator[SaveGameFile]:
def saves(self) -> Iterator[RareSaveGame]:
"""!
SaveGameFiles across games
"""

View file

@ -2,8 +2,10 @@ import time
from argparse import Namespace
from enum import IntEnum
from logging import getLogger
from typing import Dict, List
from PyQt5.QtCore import QObject, pyqtSignal
from legendary.models.game import SaveGameFile
from requests.exceptions import ConnectionError, HTTPError
from rare.lgndr.core import LegendaryCore
@ -22,7 +24,6 @@ class FetchWorker(Worker):
class Signals(QObject):
result = pyqtSignal(object, int)
finished = pyqtSignal()
def __init__(self, core: LegendaryCore, args: Namespace):
super(Worker, self).__init__()
@ -38,7 +39,6 @@ class GamesWorker(FetchWorker):
self.signals.result.emit(result, FetchWorker.Result.GAMES)
logger.debug(f"Games: {len(result[0])}, DLCs {len(result[1])}")
logger.debug(f"Request Games: {time.time() - start_time} seconds")
self.signals.finished.emit()
class NonAssetWorker(FetchWorker):
@ -52,7 +52,6 @@ class NonAssetWorker(FetchWorker):
self.signals.result.emit(result, FetchWorker.Result.NON_ASSET)
logger.debug(f"Non asset: {len(result[0])}, DLCs {len(result[1])}")
logger.debug(f"Request Non Asset: {time.time() - start_time} seconds")
self.signals.finished.emit()
class EntitlementsWorker(FetchWorker):
@ -66,18 +65,22 @@ class EntitlementsWorker(FetchWorker):
self.signals.result.emit(entitlements, FetchWorker.Result.ENTITLEMENTS)
logger.debug(f"Entitlements: {len(list(entitlements))}")
logger.debug(f"Request Entitlements: {time.time() - start_time} seconds")
self.signals.finished.emit()
class SavesWorker(FetchWorker):
def run_real(self):
start_time = time.time()
result: Dict[str, List[SaveGameFile]] = {}
try:
result = self.core.get_save_games()
saves = self.core.get_save_games()
for save in saves:
if not save.app_name in result.keys():
result[save.app_name] = [save]
else:
result[save.app_name].append(save)
except (HTTPError, ConnectionError) as e:
logger.warning(f"Exception while fetching saves fromt EGS: {e}")
result = list()
logger.warning(f"Exception while fetching saves from EGS: {e}")
result = {}
self.signals.result.emit((result, {}), FetchWorker.Result.SAVES)
logger.debug(f"Saves: {len(result)}")
logger.debug(f"Request saves: {time.time() - start_time} seconds")
self.signals.finished.emit()

View file

@ -160,7 +160,7 @@ def path_size(path: Union[str, os.PathLike]) -> int:
return sum(
os.stat(os.path.join(dp, f)).st_size
for dp, dn, filenames in os.walk(path)
for f in filenames if os.path.isfile(f)
for f in filenames if os.path.isfile(os.path.join(dp,f ))
)