mirror of
https://github.com/derrod/legendary.git
synced 2024-12-22 01:45:28 +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()}"')
|
||||
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):
|
||||
if not self.core.login():
|
||||
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_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')
|
||||
clean_saves_parser = subparsers.add_parser('clean-saves', help='Clean 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')
|
||||
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)')
|
||||
download_saves_parser.add_argument('app_name', nargs='?', metavar='<App Name>', default='',
|
||||
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='',
|
||||
help='Name of the app (optional)')
|
||||
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',
|
||||
'launch', 'download', 'uninstall', 'install', 'update',
|
||||
'repair', 'list-saves', 'download-saves', 'sync-saves',
|
||||
'verify-game', 'import-game', 'egl-sync', 'status',
|
||||
'info', 'alias', 'cleanup'):
|
||||
'clean-saves', 'verify-game', 'import-game', 'egl-sync',
|
||||
'status', 'info', 'alias', 'cleanup'):
|
||||
print(parser.format_help())
|
||||
|
||||
# Print the main help *and* the help for all of the subcommands. Thanks stackoverflow!
|
||||
|
@ -2031,6 +2041,8 @@ def main():
|
|||
cli.download_saves(args)
|
||||
elif args.subparser_name == 'sync-saves':
|
||||
cli.sync_saves(args)
|
||||
elif args.subparser_name == 'clean-saves':
|
||||
cli.clean_saves(args)
|
||||
elif args.subparser_name == 'verify-game':
|
||||
cli.verify_game(args)
|
||||
elif args.subparser_name == 'import-game':
|
||||
|
|
|
@ -849,6 +849,71 @@ class LegendaryCore:
|
|||
|
||||
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:
|
||||
return self.lgd.config.getboolean(app_name, 'offline', fallback=False)
|
||||
|
||||
|
|
Loading…
Reference in a new issue