[cli/core/models/utils] macOS cloud save support

This commit is contained in:
derrod 2021-12-02 15:24:01 +01:00
parent 4eaa608370
commit 1dfc5aabe7
4 changed files with 46 additions and 19 deletions

View file

@ -447,7 +447,7 @@ class LegendaryCLI:
continue
game = self.core.get_game(igame.app_name)
if not game or not game.supports_cloud_saves:
if not game or not (game.supports_cloud_saves or game.supports_mac_cloud_saves):
if igame.app_name in latest_save:
# this should never happen unless cloud save support was removed from a game
logger.warning(f'{igame.app_name} has remote save(s) but does not support cloud saves?!')
@ -462,7 +462,7 @@ class LegendaryCLI:
# if there is no saved save path, try to get one
if not igame.save_path:
save_path = self.core.get_save_path(igame.app_name)
save_path = self.core.get_save_path(igame.app_name, platform=igame.platform)
# ask user if path is correct if computing for the first time
logger.info(f'Computed save path: "{save_path}"')
@ -902,7 +902,7 @@ class LegendaryCLI:
end_t = time.time()
if not args.no_install:
# Allow setting savegame directory at install time so sync-saves will work immediately
if game.supports_cloud_saves and args.save_path:
if (game.supports_cloud_saves or game.supports_mac_cloud_saves) and args.save_path:
igame.save_path = args.save_path
postinstall = self.core.install_game(igame)
@ -930,7 +930,7 @@ class LegendaryCLI:
self.install_game(args)
args.yes, args.app_name = _yes, _app_name
if game.supports_cloud_saves and not game.is_dlc:
if (game.supports_cloud_saves or game.supports_mac_cloud_saves) and not game.is_dlc:
# todo option to automatically download saves after the installation
# args does not have the required attributes for sync_saves in here,
# not sure how to solve that elegantly.
@ -1399,13 +1399,20 @@ class LegendaryCLI:
game.app_version(args.platform)))
all_versions = {k: v.build_version for k,v in game.asset_infos.items()}
game_infos.append(InfoItem('All versions', 'platform_versions', all_versions, all_versions))
# Cloud save support for Mac and Windows
game_infos.append(InfoItem('Cloud saves supported', 'cloud_saves_supported',
game.supports_cloud_saves, game.supports_cloud_saves))
game.supports_cloud_saves or game.supports_mac_cloud_saves,
game.supports_cloud_saves or game.supports_mac_cloud_saves))
cs_dir = None
if game.supports_cloud_saves:
cs_dir = game.metadata['customAttributes']['CloudSaveFolder']['value']
else:
cs_dir = None
game_infos.append(InfoItem('Cloud save folder', 'cloud_save_folder', cs_dir, cs_dir))
game_infos.append(InfoItem('Cloud save folder (Windows)', 'cloud_save_folder', cs_dir, cs_dir))
cs_dir = None
if game.supports_mac_cloud_saves:
cs_dir = game.metadata['customAttributes']['CloudSaveFolder_MAC']['value']
game_infos.append(InfoItem('Cloud save folder (Mac)', 'cloud_save_folder_mac', cs_dir, cs_dir))
game_infos.append(InfoItem('Is DLC', 'is_dlc', game.is_dlc, game.is_dlc))
# Find custom launch options, if available
launch_options = []

View file

@ -14,6 +14,7 @@ from multiprocessing import Queue
from platform import system
from requests import session
from requests.exceptions import HTTPError
from sys import platform as sys_platform
from uuid import uuid4
from urllib.parse import urlencode, parse_qsl
@ -398,7 +399,7 @@ class LegendaryCore:
if update_assets and (not game or force_refresh or (game and asset_updated)):
if game and asset_updated:
self.log.info(f'Updating meta for {game.app_name} due to build version mismatch')
# namespace/catalog item are the same for all platforms, so we can just use the first one
_ga = next(iter(app_assets.values()))
eg_meta = self.egs.get_game_info(_ga.namespace, _ga.catalog_item_id)
@ -673,9 +674,14 @@ class LegendaryCore:
return _saves
def get_save_path(self, app_name):
def get_save_path(self, app_name, platform='Windows'):
game = self.lgd.get_game_meta(app_name)
save_path = game.metadata['customAttributes'].get('CloudSaveFolder', {}).get('value')
if platform == 'Mac':
save_path = game.metadata['customAttributes'].get('CloudSaveFolder_MAC', {}).get('value')
else:
save_path = game.metadata['customAttributes'].get('CloudSaveFolder', {}).get('value')
if not save_path:
raise ValueError('Game does not support cloud saves')
@ -689,13 +695,19 @@ class LegendaryCore:
'{epicid}': self.lgd.userdata['account_id']
}
if os.name == 'nt':
if sys_platform == 'win32':
path_vars.update({
'{appdata}': os.path.expandvars('%LOCALAPPDATA%'),
'{userdir}': os.path.expandvars('%userprofile%/documents'),
# '{userprofile}': os.path.expandvars('%userprofile%'), # possibly wrong
'{userprofile}': os.path.expandvars('%userprofile%'),
'{usersavedgames}': os.path.expandvars('%userprofile%/Saved Games')
})
elif sys_platform == 'darwin' and platform == 'Mac':
path_vars.update({
'{appdata}': os.path.expandvars('~/Library/Application Support'),
'{userdir}': os.path.expandvars('~/Documents'),
'{userlibrary}': os.path.expandvars('~/Library')
})
else:
# attempt to get WINE prefix from config
wine_pfx = self.lgd.config.get(app_name, 'wine_prefix', fallback=None)
@ -724,8 +736,8 @@ class LegendaryCore:
# these paths should always use a forward slash
new_save_path = [path_vars.get(p.lower(), p) for p in save_path.split('/')]
absolute_path = os.path.realpath(os.path.join(*new_save_path))
# attempt to resolve as much as possible on case-insensitive file-systems
if os.name != 'nt':
# attempt to resolve as much as possible on case-sensitive file-systems
if os.name != 'nt' and platform != 'Mac':
absolute_path = case_insensitive_path_search(absolute_path)
return absolute_path
@ -765,6 +777,7 @@ class LegendaryCore:
game = self.lgd.get_game_meta(app_name)
custom_attr = game.metadata['customAttributes']
save_path = custom_attr.get('CloudSaveFolder', {}).get('value')
save_path_mac = custom_attr.get('CloudSaveFolder_MAC', {}).get('value')
include_f = exclude_f = None
if not disable_filtering:
@ -774,12 +787,12 @@ class LegendaryCore:
if (_exclude := custom_attr.get('CloudExcludeList', {}).get('value', None)) is not None:
exclude_f = _exclude.split(',')
if not save_path:
if not save_path and not save_path_mac:
raise ValueError('Game does not support cloud saves')
sgh = SaveGameHelper()
files = sgh.package_savegame(save_dir, app_name, self.egs.user.get('account_id'),
save_path, include_f, exclude_f, local_dt)
save_path, save_path_mac, include_f, exclude_f, local_dt)
if not files:
self.log.info('No files to upload. If you believe this is incorrect run command with "--disable-filters"')

View file

@ -75,6 +75,10 @@ class Game:
def supports_cloud_saves(self):
return self.metadata and (self.metadata.get('customAttributes', {}).get('CloudSaveFolder') is not None)
@property
def supports_mac_cloud_saves(self):
return self.metadata and (self.metadata.get('customAttributes', {}).get('CloudSaveFolder_MAC') is not None)
@property
def catalog_item_id(self):
if not self.metadata:

View file

@ -51,8 +51,8 @@ class SaveGameHelper:
_tmp_file.seek(0)
return ci
def package_savegame(self, input_folder: str, app_name: str = '',
epic_id: str = '', cloud_folder: str = '',
def package_savegame(self, input_folder: str, app_name: str = '', epic_id: str = '',
cloud_folder: str = '', cloud_folder_mac: str = '',
include_filter: list = None,
exclude_filter: list = None,
manifest_dt: datetime = None):
@ -61,6 +61,7 @@ class SaveGameHelper:
:param app_name: App name for savegame being stored
:param epic_id: Epic account ID
:param cloud_folder: Folder the savegame resides in (based on game metadata)
:param cloud_folder_mac: Folder the macOS savegame resides in (based on game metadata)
:param include_filter: list of patterns for files to include (excludes all others)
:param exclude_filter: list of patterns for files to exclude (includes all others)
:param manifest_dt: datetime for the manifest name (optional)
@ -77,6 +78,8 @@ class SaveGameHelper:
manifest_dt = datetime.utcnow()
m.meta.build_version = manifest_dt.strftime('%Y.%m.%d-%H.%M.%S')
m.custom_fields['CloudSaveFolder'] = cloud_folder
if cloud_folder_mac:
m.custom_fields['CloudSaveFolder_MAC'] = cloud_folder_mac
self.log.info(f'Packing savegame for "{app_name}", input folder: {input_folder}')
files = []