mirror of
https://github.com/derrod/legendary.git
synced 2024-12-22 17:55:27 +00:00
feat: add conflict resolution based on last sync time
This commit is contained in:
parent
3963382b3f
commit
bc3331b632
|
@ -516,7 +516,7 @@ class LegendaryCLI:
|
||||||
igame.save_path = save_path
|
igame.save_path = save_path
|
||||||
self.core.lgd.set_installed_game(igame.app_name, igame)
|
self.core.lgd.set_installed_game(igame.app_name, igame)
|
||||||
|
|
||||||
res, (dt_l, dt_r) = self.core.check_savegame_state(igame.save_path, latest_save.get(igame.app_name))
|
res, (dt_l, dt_r) = self.core.check_savegame_state(igame.save_path, igame.save_timestamp, latest_save.get(igame.app_name))
|
||||||
|
|
||||||
if res == SaveGameStatus.NO_SAVE:
|
if res == SaveGameStatus.NO_SAVE:
|
||||||
logger.info('No cloud or local savegame found.')
|
logger.info('No cloud or local savegame found.')
|
||||||
|
@ -526,6 +526,30 @@ class LegendaryCLI:
|
||||||
logger.info(f'Save game for "{igame.title}" is up to date, skipping...')
|
logger.info(f'Save game for "{igame.title}" is up to date, skipping...')
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
if res == SaveGameStatus.CONFLICT and not (args.force_upload or args.force_download):
|
||||||
|
logger.info(f'Cloud save for "{igame.title}" is in conflict:')
|
||||||
|
logger.info(f'- Cloud save date: {dt_r.strftime("%Y-%m-%d %H:%M:%S")}')
|
||||||
|
logger.info(f'- Local save date: {dt_l.strftime("%Y-%m-%d %H:%M:%S")}')
|
||||||
|
|
||||||
|
if args.yes:
|
||||||
|
logger.warning('Run the command again with appropriate force parameter to effectively pick the save')
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
result = get_int_choice('Which saves should be kept? Type the number corresponding to preferred action (local - 1/remote - 2/cancel - 3)',
|
||||||
|
default=3, min_choice=1, max_choice=3)
|
||||||
|
if result == 1:
|
||||||
|
self.core.upload_save(igame.app_name, igame.save_path, dt_l, args.disable_filters)
|
||||||
|
igame.save_timestamp = time.time()
|
||||||
|
self.core.lgd.set_installed_game(igame.app_name, igame)
|
||||||
|
elif result == 2:
|
||||||
|
self.core.download_saves(igame.app_name, save_dir=igame.save_path, clean_dir=True,
|
||||||
|
manifest_name=latest_save[igame.app_name].manifest_name)
|
||||||
|
igame.save_timestamp = time.time()
|
||||||
|
self.core.lgd.set_installed_game(igame.app_name, igame)
|
||||||
|
else:
|
||||||
|
logger.info(f'Skipping action for: "{igame.title}"...')
|
||||||
|
continue
|
||||||
|
|
||||||
if (res == SaveGameStatus.REMOTE_NEWER and not args.force_upload) or args.force_download:
|
if (res == SaveGameStatus.REMOTE_NEWER and not args.force_upload) or args.force_download:
|
||||||
if res == SaveGameStatus.REMOTE_NEWER: # only print this info if not forced
|
if res == SaveGameStatus.REMOTE_NEWER: # only print this info if not forced
|
||||||
logger.info(f'Cloud save for "{igame.title}" is newer:')
|
logger.info(f'Cloud save for "{igame.title}" is newer:')
|
||||||
|
@ -547,6 +571,8 @@ class LegendaryCLI:
|
||||||
logger.info('Downloading remote savegame...')
|
logger.info('Downloading remote savegame...')
|
||||||
self.core.download_saves(igame.app_name, save_dir=igame.save_path, clean_dir=True,
|
self.core.download_saves(igame.app_name, save_dir=igame.save_path, clean_dir=True,
|
||||||
manifest_name=latest_save[igame.app_name].manifest_name)
|
manifest_name=latest_save[igame.app_name].manifest_name)
|
||||||
|
igame.save_timestamp = time.time()
|
||||||
|
self.core.lgd.set_installed_game(igame.app_name, igame)
|
||||||
elif res == SaveGameStatus.LOCAL_NEWER or args.force_upload:
|
elif res == SaveGameStatus.LOCAL_NEWER or args.force_upload:
|
||||||
if res == SaveGameStatus.LOCAL_NEWER:
|
if res == SaveGameStatus.LOCAL_NEWER:
|
||||||
logger.info(f'Local save for "{igame.title}" is newer')
|
logger.info(f'Local save for "{igame.title}" is newer')
|
||||||
|
@ -566,6 +592,8 @@ class LegendaryCLI:
|
||||||
continue
|
continue
|
||||||
logger.info('Uploading local savegame...')
|
logger.info('Uploading local savegame...')
|
||||||
self.core.upload_save(igame.app_name, igame.save_path, dt_l, args.disable_filters)
|
self.core.upload_save(igame.app_name, igame.save_path, dt_l, args.disable_filters)
|
||||||
|
igame.save_timestamp = time.time()
|
||||||
|
self.core.lgd.set_installed_game(igame.app_name, igame)
|
||||||
|
|
||||||
def launch_game(self, args, extra):
|
def launch_game(self, args, extra):
|
||||||
app_name = self._resolve_aliases(args.app_name)
|
app_name = self._resolve_aliases(args.app_name)
|
||||||
|
|
|
@ -961,7 +961,7 @@ class LegendaryCore:
|
||||||
|
|
||||||
return absolute_path
|
return absolute_path
|
||||||
|
|
||||||
def check_savegame_state(self, path: str, save: SaveGameFile) -> (SaveGameStatus, (datetime, datetime)):
|
def check_savegame_state(self, path: str, sync_timestamp: Optional[float], save: SaveGameFile) -> tuple[SaveGameStatus, tuple[datetime, datetime]]:
|
||||||
latest = 0
|
latest = 0
|
||||||
for _dir, _, _files in os.walk(path):
|
for _dir, _, _files in os.walk(path):
|
||||||
for _file in _files:
|
for _file in _files:
|
||||||
|
@ -971,8 +971,7 @@ class LegendaryCore:
|
||||||
if not latest and not save:
|
if not latest and not save:
|
||||||
return SaveGameStatus.NO_SAVE, (None, None)
|
return SaveGameStatus.NO_SAVE, (None, None)
|
||||||
|
|
||||||
# timezones are fun!
|
dt_local = datetime.fromtimestamp(latest, tz=timezone.utc)
|
||||||
dt_local = datetime.fromtimestamp(latest).replace(tzinfo=self.local_timezone).astimezone(timezone.utc)
|
|
||||||
if not save:
|
if not save:
|
||||||
return SaveGameStatus.LOCAL_NEWER, (dt_local, None)
|
return SaveGameStatus.LOCAL_NEWER, (dt_local, None)
|
||||||
|
|
||||||
|
@ -980,7 +979,15 @@ class LegendaryCore:
|
||||||
if not latest:
|
if not latest:
|
||||||
return SaveGameStatus.REMOTE_NEWER, (None, dt_remote)
|
return SaveGameStatus.REMOTE_NEWER, (None, dt_remote)
|
||||||
|
|
||||||
self.log.debug(f'Local save date: {str(dt_local)}, Remote save date: {str(dt_remote)}')
|
dt_sync_time = datetime.fromtimestamp(sync_timestamp or 0, tz=timezone.utc)
|
||||||
|
self.log.debug(f'Local save date: {str(dt_local)}, Remote save date: {str(dt_remote)}, Last sync: {str(dt_sync_time)}')
|
||||||
|
|
||||||
|
# Pickup possible conflict
|
||||||
|
if sync_timestamp:
|
||||||
|
remote_updated = (dt_remote - dt_sync_time).total_seconds() > 60
|
||||||
|
local_updated = (dt_local - dt_sync_time).total_seconds() > 60
|
||||||
|
if remote_updated and local_updated:
|
||||||
|
return SaveGameStatus.CONFLICT, (dt_local, dt_remote)
|
||||||
|
|
||||||
# Ideally we check the files themselves based on manifest,
|
# Ideally we check the files themselves based on manifest,
|
||||||
# this is mostly a guess but should be accurate enough.
|
# this is mostly a guess but should be accurate enough.
|
||||||
|
|
|
@ -192,6 +192,7 @@ class InstalledGame:
|
||||||
uninstaller: Optional[Dict] = None
|
uninstaller: Optional[Dict] = None
|
||||||
requires_ot: bool = False
|
requires_ot: bool = False
|
||||||
save_path: Optional[str] = None
|
save_path: Optional[str] = None
|
||||||
|
save_timestamp: Optional[float] = None
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_json(cls, json):
|
def from_json(cls, json):
|
||||||
|
@ -212,6 +213,7 @@ class InstalledGame:
|
||||||
tmp.requires_ot = json.get('requires_ot', False)
|
tmp.requires_ot = json.get('requires_ot', False)
|
||||||
tmp.is_dlc = json.get('is_dlc', False)
|
tmp.is_dlc = json.get('is_dlc', False)
|
||||||
tmp.save_path = json.get('save_path', None)
|
tmp.save_path = json.get('save_path', None)
|
||||||
|
tmp.save_timestamp = json.get('save_timestamp', None)
|
||||||
tmp.manifest_path = json.get('manifest_path', '')
|
tmp.manifest_path = json.get('manifest_path', '')
|
||||||
tmp.needs_verification = json.get('needs_verification', False) is True
|
tmp.needs_verification = json.get('needs_verification', False) is True
|
||||||
tmp.platform = json.get('platform', 'Windows')
|
tmp.platform = json.get('platform', 'Windows')
|
||||||
|
@ -237,6 +239,7 @@ class SaveGameStatus(Enum):
|
||||||
REMOTE_NEWER = 1
|
REMOTE_NEWER = 1
|
||||||
SAME_AGE = 2
|
SAME_AGE = 2
|
||||||
NO_SAVE = 3
|
NO_SAVE = 3
|
||||||
|
CONFLICT = 4
|
||||||
|
|
||||||
|
|
||||||
class VerifyResult(Enum):
|
class VerifyResult(Enum):
|
||||||
|
|
Loading…
Reference in a new issue