mirror of
https://github.com/derrod/legendary.git
synced 2024-12-22 17:55:27 +00:00
[cli/core] Add "clean-saves" command to remove obsolete/broken cloud save data
This commit is contained in:
parent
85a275950d
commit
355b1107e6
|
@ -403,6 +403,13 @@ class LegendaryCLI:
|
||||||
logger.info(f'Downloading saves to "{self.core.get_default_install_dir()}"')
|
logger.info(f'Downloading saves to "{self.core.get_default_install_dir()}"')
|
||||||
self.core.download_saves(self._resolve_aliases(args.app_name))
|
self.core.download_saves(self._resolve_aliases(args.app_name))
|
||||||
|
|
||||||
|
def clean_saves(self, args):
|
||||||
|
if not self.core.login():
|
||||||
|
logger.error('Login failed! Cannot continue with download process.')
|
||||||
|
exit(1)
|
||||||
|
logger.info(f'Cleaning saves...')
|
||||||
|
self.core.clean_saves(self._resolve_aliases(args.app_name))
|
||||||
|
|
||||||
def sync_saves(self, args):
|
def sync_saves(self, args):
|
||||||
if not self.core.login():
|
if not self.core.login():
|
||||||
logger.error('Login failed! Cannot continue with download process.')
|
logger.error('Login failed! Cannot continue with download process.')
|
||||||
|
@ -1727,6 +1734,7 @@ def main():
|
||||||
list_files_parser = subparsers.add_parser('list-files', help='List files in manifest')
|
list_files_parser = subparsers.add_parser('list-files', help='List files in manifest')
|
||||||
list_saves_parser = subparsers.add_parser('list-saves', help='List available cloud saves')
|
list_saves_parser = subparsers.add_parser('list-saves', help='List available cloud saves')
|
||||||
download_saves_parser = subparsers.add_parser('download-saves', help='Download all cloud saves')
|
download_saves_parser = subparsers.add_parser('download-saves', help='Download all cloud saves')
|
||||||
|
clean_saves_parser = subparsers.add_parser('clean-saves', help='Clean cloud saves')
|
||||||
sync_saves_parser = subparsers.add_parser('sync-saves', help='Sync cloud saves')
|
sync_saves_parser = subparsers.add_parser('sync-saves', help='Sync cloud saves')
|
||||||
verify_parser = subparsers.add_parser('verify-game', help='Verify a game\'s local files')
|
verify_parser = subparsers.add_parser('verify-game', help='Verify a game\'s local files')
|
||||||
import_parser = subparsers.add_parser('import-game', help='Import an already installed game')
|
import_parser = subparsers.add_parser('import-game', help='Import an already installed game')
|
||||||
|
@ -1745,6 +1753,8 @@ def main():
|
||||||
help='Name of the app (optional)')
|
help='Name of the app (optional)')
|
||||||
download_saves_parser.add_argument('app_name', nargs='?', metavar='<App Name>', default='',
|
download_saves_parser.add_argument('app_name', nargs='?', metavar='<App Name>', default='',
|
||||||
help='Name of the app (optional)')
|
help='Name of the app (optional)')
|
||||||
|
clean_saves_parser.add_argument('app_name', nargs='?', metavar='<App Name>', default='',
|
||||||
|
help='Name of the app (optional)')
|
||||||
sync_saves_parser.add_argument('app_name', nargs='?', metavar='<App Name>', default='',
|
sync_saves_parser.add_argument('app_name', nargs='?', metavar='<App Name>', default='',
|
||||||
help='Name of the app (optional)')
|
help='Name of the app (optional)')
|
||||||
verify_parser.add_argument('app_name', help='Name of the app', metavar='<App Name>')
|
verify_parser.add_argument('app_name', help='Name of the app', metavar='<App Name>')
|
||||||
|
@ -1979,8 +1989,8 @@ def main():
|
||||||
if args.subparser_name not in ('auth', 'list-games', 'list-installed', 'list-files',
|
if args.subparser_name not in ('auth', 'list-games', 'list-installed', 'list-files',
|
||||||
'launch', 'download', 'uninstall', 'install', 'update',
|
'launch', 'download', 'uninstall', 'install', 'update',
|
||||||
'repair', 'list-saves', 'download-saves', 'sync-saves',
|
'repair', 'list-saves', 'download-saves', 'sync-saves',
|
||||||
'verify-game', 'import-game', 'egl-sync', 'status',
|
'clean-saves', 'verify-game', 'import-game', 'egl-sync',
|
||||||
'info', 'alias', 'cleanup'):
|
'status', 'info', 'alias', 'cleanup'):
|
||||||
print(parser.format_help())
|
print(parser.format_help())
|
||||||
|
|
||||||
# Print the main help *and* the help for all of the subcommands. Thanks stackoverflow!
|
# Print the main help *and* the help for all of the subcommands. Thanks stackoverflow!
|
||||||
|
@ -2031,6 +2041,8 @@ def main():
|
||||||
cli.download_saves(args)
|
cli.download_saves(args)
|
||||||
elif args.subparser_name == 'sync-saves':
|
elif args.subparser_name == 'sync-saves':
|
||||||
cli.sync_saves(args)
|
cli.sync_saves(args)
|
||||||
|
elif args.subparser_name == 'clean-saves':
|
||||||
|
cli.clean_saves(args)
|
||||||
elif args.subparser_name == 'verify-game':
|
elif args.subparser_name == 'verify-game':
|
||||||
cli.verify_game(args)
|
cli.verify_game(args)
|
||||||
elif args.subparser_name == 'import-game':
|
elif args.subparser_name == 'import-game':
|
||||||
|
|
|
@ -849,6 +849,71 @@ class LegendaryCore:
|
||||||
|
|
||||||
self.log.info('Successfully completed savegame download.')
|
self.log.info('Successfully completed savegame download.')
|
||||||
|
|
||||||
|
def clean_saves(self, app_name=''):
|
||||||
|
savegames = self.egs.get_user_cloud_saves(app_name=app_name)
|
||||||
|
files = savegames['files']
|
||||||
|
deletion_list = []
|
||||||
|
used_chunks = set()
|
||||||
|
do_not_delete = set()
|
||||||
|
|
||||||
|
# check if all chunks for manifests are there
|
||||||
|
for fname, f in files.items():
|
||||||
|
if '.manifest' not in fname:
|
||||||
|
continue
|
||||||
|
|
||||||
|
app_name = fname.split('/', 3)[2]
|
||||||
|
|
||||||
|
self.log.info(f'Checking {app_name} "{fname.split("/", 2)[2]}"...')
|
||||||
|
# download manifest
|
||||||
|
r = self.egs.unauth_session.get(f['readLink'])
|
||||||
|
|
||||||
|
if r.status_code == 404:
|
||||||
|
self.log.error('Manifest is missing! Marking for deletion.')
|
||||||
|
deletion_list.append(fname)
|
||||||
|
continue
|
||||||
|
elif r.status_code != 200:
|
||||||
|
self.log.warning(f'Download failed, status code: {r.status_code}. Skipping...')
|
||||||
|
do_not_delete.add(app_name)
|
||||||
|
continue
|
||||||
|
|
||||||
|
if not r.content:
|
||||||
|
self.log.error('Manifest is empty! Marking for deletion.')
|
||||||
|
deletion_list.append(fname)
|
||||||
|
continue
|
||||||
|
|
||||||
|
m = self.load_manifest(r.content)
|
||||||
|
# check if all required chunks are present
|
||||||
|
chunk_fnames = set()
|
||||||
|
for chunk in m.chunk_data_list.elements:
|
||||||
|
cpath_p = fname.split('/', 3)[:3]
|
||||||
|
cpath_p.append(chunk.path)
|
||||||
|
cpath = '/'.join(cpath_p)
|
||||||
|
if cpath not in files:
|
||||||
|
self.log.error(f'Chunk missing, marking manifest for deletion.')
|
||||||
|
deletion_list.append(fname)
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
chunk_fnames.add(cpath)
|
||||||
|
else:
|
||||||
|
used_chunks |= chunk_fnames
|
||||||
|
|
||||||
|
# check for orphaned chunks (not used in any manifests)
|
||||||
|
for fname, f in files.items():
|
||||||
|
if fname in used_chunks or '.manifest' in fname:
|
||||||
|
continue
|
||||||
|
# skip chunks where orphan status could not be reliably determined
|
||||||
|
if fname.split('/', 3)[2] in do_not_delete:
|
||||||
|
continue
|
||||||
|
self.log.debug(f'Marking orphaned chunk {fname} for deletion.')
|
||||||
|
deletion_list.append(fname)
|
||||||
|
|
||||||
|
self.log.info('Deleting unused/broken files...')
|
||||||
|
for fname in deletion_list:
|
||||||
|
self.log.debug(f'Deleting {fname}')
|
||||||
|
self.egs.delete_game_cloud_save_file(fname)
|
||||||
|
|
||||||
|
self.log.info('Successfully completed savegame cleanup.')
|
||||||
|
|
||||||
def is_offline_game(self, app_name: str) -> bool:
|
def is_offline_game(self, app_name: str) -> bool:
|
||||||
return self.lgd.config.getboolean(app_name, 'offline', fallback=False)
|
return self.lgd.config.getboolean(app_name, 'offline', fallback=False)
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue