diff --git a/legendary/cli.py b/legendary/cli.py index c4d8185..3e4b539 100644 --- a/legendary/cli.py +++ b/legendary/cli.py @@ -272,7 +272,8 @@ class LegendaryCLI: override_old_manifest=args.override_old_manifest, override_base_url=args.override_base_url, platform_override=args.platform_override, - file_prefix_filter=args.file_prefix) + file_prefix_filter=args.file_prefix, + file_exclude_filter=args.file_exclude_prefix) # game is either up to date or hasn't changed, so we have nothing to do if not analysis.dl_size: @@ -451,6 +452,8 @@ def main(): type=str, help='Platform override for download (disables install)') install_parser.add_argument('--prefix', dest='file_prefix', action='store', metavar='', type=str, help='Only fetch files whose path starts with (case insensitive)') + install_parser.add_argument('--exclude', dest='file_exclude_prefix', action='store', metavar='', + type=str, help='Exclude files starting with (case insensitive)') launch_parser.add_argument('--offline', dest='offline', action='store_true', default=False, help='Skip login and launch game without online authentication') diff --git a/legendary/core.py b/legendary/core.py index dbb9fd0..9b432c8 100644 --- a/legendary/core.py +++ b/legendary/core.py @@ -355,9 +355,8 @@ class LegendaryCore: force: bool = False, disable_patching: bool = False, game_folder: str = '', override_manifest: str = '', override_old_manifest: str = '', override_base_url: str = '', - platform_override: str = '', file_prefix_filter: str = '' - ) -> (DLManager, AnalysisResult, ManifestMeta): - + platform_override: str = '', file_prefix_filter: str = '', + file_exclude_filter: str = '' ) -> (DLManager, AnalysisResult, ManifestMeta): # load old manifest old_manifest = None @@ -430,7 +429,8 @@ class LegendaryCore: max_shared_memory=max_shm * 1024 * 1024, max_workers=max_workers) anlres = dlm.run_analysis(manifest=new_manifest, old_manifest=old_manifest, patch=not disable_patching, resume=not force, - file_prefix_filter=file_prefix_filter) + file_prefix_filter=file_prefix_filter, + file_exclude_filter=file_exclude_filter) prereq = None if new_manifest.meta.prereq_ids: diff --git a/legendary/downloader/manager.py b/legendary/downloader/manager.py index 09c54cc..b2617b4 100644 --- a/legendary/downloader/manager.py +++ b/legendary/downloader/manager.py @@ -241,7 +241,8 @@ class DLManager(Process): self.log.info('Writer result handler quitting...') def run_analysis(self, manifest: Manifest, old_manifest: Manifest = None, - patch=True, resume=True, file_prefix_filter=None) -> AnalysisResult: + patch=True, resume=True, file_prefix_filter=None, + file_exclude_filter=None) -> AnalysisResult: """ Run analysis on manifest and old manifest (if not None) and return a result with a summary resources required in order to install the provided manifest. @@ -251,12 +252,10 @@ class DLManager(Process): :param patch: Patch instead of redownloading the entire file :param resume: Continue based on resume file if it exists :param file_prefix_filter: Only download files that start with this prefix + :param file_exclude_filter: Exclude files with this prefix from download :return: AnalysisResult """ - if file_prefix_filter: - file_prefix_filter = file_prefix_filter.lower() - analysis_res = AnalysisResult() analysis_res.install_size = sum(fm.file_size for fm in manifest.file_manifest_list.elements) analysis_res.biggest_chunk = max(c.window_size for c in manifest.chunk_data_list.elements) @@ -279,14 +278,29 @@ class DLManager(Process): except Exception as e: self.log.warning(f'Reading resume file failed: {e!r}, continuing as normal...') - # if prefix has been set: mark all files that are not to be downloaded as unchanged (not removed) - if file_prefix_filter: - files_to_skip = set(i for i in mc.added | mc.changed if not i.lower().startswith(file_prefix_filter)) - self.log.info(f'Found {len(files_to_skip)} files to skip based on prefix.') + # if include/exclude prefix has been set: mark all files that are not to be downloaded as unchanged + if file_exclude_filter: + file_exclude_filter = file_exclude_filter.lower() + files_to_skip = set(i for i in mc.added | mc.changed if i.lower().startswith(file_exclude_filter)) + self.log.info(f'Found {len(files_to_skip)} files to skip based on exclude prefix.') mc.added -= files_to_skip mc.changed -= files_to_skip mc.unchanged |= files_to_skip + if file_prefix_filter: + file_prefix_filter = file_prefix_filter.lower() + files_to_skip = set(i for i in mc.added | mc.changed if not i.lower().startswith(file_prefix_filter)) + self.log.info(f'Found {len(files_to_skip)} files to skip based on include prefix.') + mc.added -= files_to_skip + mc.changed -= files_to_skip + mc.unchanged |= files_to_skip + + if file_prefix_filter or file_exclude_filter: + self.log.info(f'Remaining files after filtering: {len(mc.added) + len(mc.changed)}') + # correct install size after filtering + analysis_res.install_size = sum(fm.file_size for fm in manifest.file_manifest_list.elements + if fm.filename in mc.added) + if mc.removed: analysis_res.removed = len(mc.removed) self.log.debug(f'{analysis_res.removed} removed files')