-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
from 0.20.35. Remove backports that are now merged in. Add backport for conflict resolution.
- Loading branch information
1 parent
b34455d
commit 92ef77f
Showing
5 changed files
with
137 additions
and
469 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,130 @@ | ||
diff --git a/legendary/cli.py b/legendary/cli.py | ||
index 165e7a1..b68b448 100644 | ||
--- a/legendary/cli.py | ||
+++ b/legendary/cli.py | ||
@@ -517,7 +517,7 @@ class LegendaryCLI: | ||
igame.save_path = save_path | ||
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: | ||
logger.info('No cloud or local savegame found.') | ||
@@ -527,6 +527,30 @@ class LegendaryCLI: | ||
logger.info(f'Save game for "{igame.title}" is up to date, skipping...') | ||
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 (remote - 1/local - 2/cancel - 3)', | ||
+ default=3, min_choice=1, max_choice=3) | ||
+ if result == 1: | ||
+ 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) | ||
+ elif result == 2: | ||
+ 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) | ||
+ 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: # only print this info if not forced | ||
logger.info(f'Cloud save for "{igame.title}" is newer:') | ||
@@ -548,6 +572,8 @@ class LegendaryCLI: | ||
logger.info('Downloading remote savegame...') | ||
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) | ||
elif res == SaveGameStatus.LOCAL_NEWER or args.force_upload: | ||
if res == SaveGameStatus.LOCAL_NEWER: | ||
logger.info(f'Local save for "{igame.title}" is newer') | ||
@@ -567,6 +593,8 @@ class LegendaryCLI: | ||
continue | ||
logger.info('Uploading local savegame...') | ||
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): | ||
app_name = self._resolve_aliases(args.app_name) | ||
diff --git a/legendary/core.py b/legendary/core.py | ||
index 614c597..58ee735 100644 | ||
--- a/legendary/core.py | ||
+++ b/legendary/core.py | ||
@@ -963,7 +963,7 @@ class LegendaryCore: | ||
|
||
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 | ||
for _dir, _, _files in os.walk(path): | ||
for _file in _files: | ||
@@ -973,8 +973,7 @@ class LegendaryCore: | ||
if not latest and not save: | ||
return SaveGameStatus.NO_SAVE, (None, None) | ||
|
||
- # timezones are fun! | ||
- dt_local = datetime.fromtimestamp(latest).replace(tzinfo=self.local_timezone).astimezone(timezone.utc) | ||
+ dt_local = datetime.fromtimestamp(latest, tz=timezone.utc) | ||
if not save: | ||
return SaveGameStatus.LOCAL_NEWER, (dt_local, None) | ||
|
||
@@ -982,7 +981,15 @@ class LegendaryCore: | ||
if not latest: | ||
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, | ||
# this is mostly a guess but should be accurate enough. | ||
diff --git a/legendary/models/game.py b/legendary/models/game.py | ||
index 5a7eec6..6f94daf 100644 | ||
--- a/legendary/models/game.py | ||
+++ b/legendary/models/game.py | ||
@@ -192,6 +192,7 @@ class InstalledGame: | ||
uninstaller: Optional[Dict] = None | ||
requires_ot: bool = False | ||
save_path: Optional[str] = None | ||
+ save_timestamp: Optional[float] = None | ||
|
||
@classmethod | ||
def from_json(cls, json): | ||
@@ -212,6 +213,7 @@ class InstalledGame: | ||
tmp.requires_ot = json.get('requires_ot', False) | ||
tmp.is_dlc = json.get('is_dlc', False) | ||
tmp.save_path = json.get('save_path', None) | ||
+ tmp.save_timestamp = json.get('save_timestamp', None) | ||
tmp.manifest_path = json.get('manifest_path', '') | ||
tmp.needs_verification = json.get('needs_verification', False) is True | ||
tmp.platform = json.get('platform', 'Windows') | ||
@@ -237,6 +239,7 @@ class SaveGameStatus(Enum): | ||
REMOTE_NEWER = 1 | ||
SAME_AGE = 2 | ||
NO_SAVE = 3 | ||
+ CONFLICT = 4 | ||
|
||
|
||
class VerifyResult(Enum): |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
Oops, something went wrong.