Skip to content

Commit 652b78b

Browse files
committed
Make reading from existing files optional with fallback
1 parent e744357 commit 652b78b

File tree

4 files changed

+45
-24
lines changed

4 files changed

+45
-24
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -459,6 +459,8 @@ optional arguments:
459459
--exclude <prefix> Exclude files starting with <prefix> (case
460460
insensitive)
461461
--install-tag <tag> Only download files with the specified install tag
462+
--read-files Read duplicated parts from already saved files, do not
463+
keep them in RAM
462464
--enable-reordering Enable reordering optimization to reduce RAM
463465
requirements during download (may have adverse results
464466
for some titles)

legendary/cli.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -971,6 +971,7 @@ def install_game(self, args):
971971
file_prefix_filter=args.file_prefix,
972972
file_exclude_filter=args.file_exclude_prefix,
973973
file_install_tag=args.install_tag,
974+
read_files=args.read_files,
974975
dl_optimizations=args.order_opt,
975976
dl_timeout=args.dl_timeout,
976977
repair=args.repair_mode,
@@ -2768,6 +2769,8 @@ def main():
27682769
type=str, help='Exclude files starting with <prefix> (case insensitive)')
27692770
install_parser.add_argument('--install-tag', dest='install_tag', action='append', metavar='<tag>',
27702771
type=str, help='Only download files with the specified install tag')
2772+
install_parser.add_argument('--read-files', dest='read_files', action='store_true',
2773+
help='Read duplicated parts from already saved files, do not keep them in memory')
27712774
install_parser.add_argument('--enable-reordering', dest='order_opt', action='store_true',
27722775
help='Enable reordering optimization to reduce RAM requirements '
27732776
'during download (may have adverse results for some titles)')

legendary/core.py

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1327,6 +1327,7 @@ def prepare_download(self, game: Game, base_game: Game = None, base_path: str =
13271327
override_old_manifest: str = '', override_base_url: str = '',
13281328
platform: str = 'Windows', file_prefix_filter: list = None,
13291329
file_exclude_filter: list = None, file_install_tag: list = None,
1330+
read_files: bool = False,
13301331
dl_optimizations: bool = False, dl_timeout: int = 10,
13311332
repair: bool = False, repair_use_latest: bool = False,
13321333
disable_delta: bool = False, override_delta_manifest: str = '',
@@ -1499,12 +1500,23 @@ def prepare_download(self, game: Game, base_game: Game = None, base_path: str =
14991500
dlm = DLManager(install_path, base_url, resume_file=resume_file, status_q=status_q,
15001501
max_shared_memory=max_shm * 1024 * 1024, max_workers=max_workers,
15011502
dl_timeout=dl_timeout, bind_ip=bind_ip)
1502-
anlres = dlm.run_analysis(manifest=new_manifest, old_manifest=old_manifest,
1503-
patch=not disable_patching, resume=not force,
1504-
file_prefix_filter=file_prefix_filter,
1505-
file_exclude_filter=file_exclude_filter,
1506-
file_install_tag=file_install_tag,
1507-
processing_optimization=process_opt)
1503+
1504+
analysis_kwargs = dict(
1505+
old_manifest=old_manifest,
1506+
patch=not disable_patching, resume=not force,
1507+
file_prefix_filter=file_prefix_filter,
1508+
file_exclude_filter=file_exclude_filter,
1509+
file_install_tag=file_install_tag,
1510+
processing_optimization=process_opt
1511+
)
1512+
1513+
try:
1514+
anlres = dlm.run_analysis(manifest=new_manifest, **analysis_kwargs, read_files=read_files)
1515+
except MemoryError:
1516+
if read_files:
1517+
raise
1518+
self.log.warning('Memory error encountered, retrying with file read enabled...')
1519+
anlres = dlm.run_analysis(manifest=new_manifest, **analysis_kwargs, read_files=True)
15081520

15091521
prereq = None
15101522
if new_manifest.meta.prereq_ids:

legendary/downloader/mp/manager.py

Lines changed: 22 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ def __init__(self, download_dir, base_url, cache_dir=None, status_q=None,
8282
def run_analysis(self, manifest: Manifest, old_manifest: Manifest = None,
8383
patch=True, resume=True, file_prefix_filter=None,
8484
file_exclude_filter=None, file_install_tag=None,
85+
read_files=False,
8586
processing_optimization=False) -> AnalysisResult:
8687
"""
8788
Run analysis on manifest and old manifest (if not None) and return a result
@@ -94,6 +95,7 @@ def run_analysis(self, manifest: Manifest, old_manifest: Manifest = None,
9495
:param file_prefix_filter: Only download files that start with this prefix
9596
:param file_exclude_filter: Exclude files with this prefix from download
9697
:param file_install_tag: Only install files with the specified tag
98+
:param read_files: Allow reading from already finished files
9799
:param processing_optimization: Attempt to optimize processing order and RAM usage
98100
:return: AnalysisResult
99101
"""
@@ -320,25 +322,27 @@ def run_analysis(self, manifest: Manifest, old_manifest: Manifest = None,
320322

321323
# determine whether a chunk part is currently in written files
322324
reusable_written = defaultdict(dict)
323-
cur_written_cps = defaultdict(list)
324-
for cur_file in fmlist:
325-
cur_file_cps = dict()
326-
cur_file_offset = 0
327-
for cp in cur_file.chunk_parts:
328-
key = (cp.guid_num, cp.offset, cp.size)
329-
for wr_file_name, wr_file_offset, wr_cp_offset, wr_cp_end_offset in cur_written_cps[cp.guid_num]:
330-
# check if new chunk part is wholly contained in a written chunk part
331-
cur_cp_end_offset = cp.offset + cp.size
332-
if wr_cp_offset <= cp.offset and wr_cp_end_offset >= cur_cp_end_offset:
333-
references[cp.guid_num] -= 1
334-
reuse_offset = wr_file_offset + (cp.offset - wr_cp_offset)
335-
reusable_written[cur_file.filename][key] = (wr_file_name, reuse_offset)
336-
break
337-
cur_file_cps[cp.guid_num] = (cur_file.filename, cur_file_offset, cp.offset, cp.offset + cp.size)
338-
cur_file_offset += cp.size
325+
if read_files:
326+
self.log.debug('Analyzing manifest for re-usable chunks in saved files...')
327+
cur_written_cps = defaultdict(list)
328+
for cur_file in fmlist:
329+
cur_file_cps = dict()
330+
cur_file_offset = 0
331+
for cp in cur_file.chunk_parts:
332+
key = (cp.guid_num, cp.offset, cp.size)
333+
for wr_file_name, wr_file_offset, wr_cp_offset, wr_cp_end_offset in cur_written_cps[cp.guid_num]:
334+
# check if new chunk part is wholly contained in a written chunk part
335+
cur_cp_end_offset = cp.offset + cp.size
336+
if wr_cp_offset <= cp.offset and wr_cp_end_offset >= cur_cp_end_offset:
337+
references[cp.guid_num] -= 1
338+
reuse_offset = wr_file_offset + (cp.offset - wr_cp_offset)
339+
reusable_written[cur_file.filename][key] = (wr_file_name, reuse_offset)
340+
break
341+
cur_file_cps[cp.guid_num] = (cur_file.filename, cur_file_offset, cp.offset, cp.offset + cp.size)
342+
cur_file_offset += cp.size
339343

340-
for guid, value in cur_file_cps.items():
341-
cur_written_cps[guid].append(value)
344+
for guid, value in cur_file_cps.items():
345+
cur_written_cps[guid].append(value)
342346

343347
last_cache_size = current_cache_size = 0
344348
# set to determine whether a file is currently cached or not

0 commit comments

Comments
 (0)