Skip to content

Commit e1945f0

Browse files
committed
Provide option for disabling reading from already finished files
1 parent e744357 commit e1945f0

File tree

4 files changed

+40
-28
lines changed

4 files changed

+40
-28
lines changed

README.md

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ it has to be run from a terminal (e.g. PowerShell)
3535
- Linux, Windows (8.1+), or macOS (12.0+)
3636
+ 32-bit operating systems are not supported
3737
- python 3.9+ (64-bit)
38-
+ (Windows) `pythonnet` is not yet compatible with 3.10+, use 3.9 if you plan to install `pywebview`
38+
+ (Windows) `pythonnet` is not yet compatible with 3.10+, use 3.9 if you plan to install `pywebview`
3939
- PyPI packages:
4040
+ `requests`
4141
+ (optional) `pywebview` for webview-based login
@@ -71,7 +71,7 @@ Note that on Linux glibc >= 2.25 is required, so older distributions such as Ubu
7171

7272
#### Prerequisites
7373

74-
To prevent problems with permissions during installation, please upgrade your `pip` by running `python -m pip install -U pip --user`.
74+
To prevent problems with permissions during installation, please upgrade your `pip` by running `python -m pip install -U pip --user`.
7575

7676
> **Tip:** You may need to replace `python` in the above command with `python3` on Linux/macOS, or `py -3` on Windows.
7777
@@ -87,7 +87,7 @@ Optionally if logging in via an embedded web view is desired also run
8787
```bash
8888
pip install legendary-gl[webview]
8989
```
90-
On Linux this may also require installing a supported web engine and its python bindings.
90+
On Linux this may also require installing a supported web engine and its python bindings.
9191
Ubunutu example:
9292
```bash
9393
sudo apt install python3-gi-cairo
@@ -115,7 +115,7 @@ cd legendary
115115
pip install .
116116
````
117117

118-
If the `legendary` executable is not available after installation, you may need to configure your `PATH` correctly. You can do this by running the command:
118+
If the `legendary` executable is not available after installation, you may need to configure your `PATH` correctly. You can do this by running the command:
119119

120120
```bash
121121
echo 'export PATH=$PATH:~/.local/bin' >> ~/.profile && source ~/.profile
@@ -174,7 +174,7 @@ Importing a previously installed game
174174
````
175175
legendary import Anemone /mnt/games/Epic/WorldOfGoo
176176
````
177-
**Note:** Importing will require a full verification so Legendary can correctly update the game later.
177+
**Note:** Importing will require a full verification so Legendary can correctly update the game later.
178178
**Note 2:** In order to use an alias here you may have to put it into quotes if if contains more than one word, e.g. `legendary import-game "world of goo" /mnt/games/Epic/WorldOfGoo`.
179179

180180
Sync savegames with the Epic Cloud
@@ -445,6 +445,8 @@ optional arguments:
445445
--force Download all files / ignore existing (overwrite)
446446
--disable-patching Do not attempt to patch existing installation
447447
(download entire changed files)
448+
--disable-read-files Do not re-read duplicated parts from already saved
449+
files, keep them in memory
448450
--download-only, --no-install
449451
Do not install app and do not run prerequisite
450452
installers after download
@@ -677,7 +679,7 @@ locale = en-US
677679
; whether or not syncing with egl is enabled
678680
egl_sync = false
679681
; path to the "Manifests" folder in the EGL ProgramData directory
680-
egl_programdata = /home/user/Games/epic-games-store/drive_c/...
682+
egl_programdata = /home/user/Games/epic-games-store/drive_c/...
681683
; Set preferred CDN host (e.g. to improve download speed)
682684
preferred_cdn = epicgames-download1.akamaized.net
683685
; disable HTTPS for downloads (e.g. to use a LanCache)

legendary/cli.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -964,6 +964,7 @@ def install_game(self, args):
964964
force=args.force, max_shm=args.shared_memory,
965965
max_workers=args.max_workers, game_folder=args.game_folder,
966966
disable_patching=args.disable_patching,
967+
disable_read_files=args.disable_read_files,
967968
override_manifest=args.override_manifest,
968969
override_old_manifest=args.override_old_manifest,
969970
override_base_url=args.override_base_url,
@@ -1197,7 +1198,7 @@ def _handle_uninstaller(self, igame, yes=False):
11971198

11981199
print('\nThis game provides the following uninstaller:')
11991200
print(f'- {uninstaller["path"]} {uninstaller["args"]}\n')
1200-
1201+
12011202
if yes or get_boolean_choice('Do you wish to run the uninstaller?', default=True):
12021203
logger.info('Running uninstaller...')
12031204
req_path, req_exec = os.path.split(uninstaller['path'])
@@ -2470,7 +2471,7 @@ def crossover_setup(self, args):
24702471
default_choice = None
24712472
for i, bottle in enumerate(usable_bottles, start=1):
24722473
extra = []
2473-
2474+
24742475
if cx_version in bottle['cx_versions']:
24752476
if app_name in bottle['compatible_apps']:
24762477
extra.append('recommended')
@@ -2754,6 +2755,8 @@ def main():
27542755
help='Download all files / ignore existing (overwrite)')
27552756
install_parser.add_argument('--disable-patching', dest='disable_patching', action='store_true',
27562757
help='Do not attempt to patch existing installation (download entire changed files)')
2758+
install_parser.add_argument('--disable-read-files', dest='disable_read_files', action='store_true',
2759+
help='Do not re-read duplicated parts from already saved files, keep them in memory')
27572760
install_parser.add_argument('--download-only', '--no-install', dest='no_install', action='store_true',
27582761
help='Do not install app and do not run prerequisite installers after download')
27592762
install_parser.add_argument('--update-only', dest='update_only', action='store_true',

legendary/core.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1323,6 +1323,7 @@ def get_delta_manifest(self, base_url, old_build_id, new_build_id):
13231323
def prepare_download(self, game: Game, base_game: Game = None, base_path: str = '',
13241324
status_q: Queue = None, max_shm: int = 0, max_workers: int = 0,
13251325
force: bool = False, disable_patching: bool = False,
1326+
disable_read_files: bool = False,
13261327
game_folder: str = '', override_manifest: str = '',
13271328
override_old_manifest: str = '', override_base_url: str = '',
13281329
platform: str = 'Windows', file_prefix_filter: list = None,
@@ -1501,6 +1502,7 @@ def prepare_download(self, game: Game, base_game: Game = None, base_path: str =
15011502
dl_timeout=dl_timeout, bind_ip=bind_ip)
15021503
anlres = dlm.run_analysis(manifest=new_manifest, old_manifest=old_manifest,
15031504
patch=not disable_patching, resume=not force,
1505+
read_files=not disable_read_files,
15041506
file_prefix_filter=file_prefix_filter,
15051507
file_exclude_filter=file_exclude_filter,
15061508
file_install_tag=file_install_tag,

legendary/downloader/mp/manager.py

Lines changed: 25 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -80,8 +80,10 @@ def __init__(self, download_dir, base_url, cache_dir=None, status_q=None,
8080
self.num_tasks_processed_since_last = 0
8181

8282
def run_analysis(self, manifest: Manifest, old_manifest: Manifest = None,
83-
patch=True, resume=True, file_prefix_filter=None,
84-
file_exclude_filter=None, file_install_tag=None,
83+
patch=True, resume=True, read_files=True,
84+
file_prefix_filter=None,
85+
file_exclude_filter=None,
86+
file_install_tag=None,
8587
processing_optimization=False) -> AnalysisResult:
8688
"""
8789
Run analysis on manifest and old manifest (if not None) and return a result
@@ -90,6 +92,7 @@ def run_analysis(self, manifest: Manifest, old_manifest: Manifest = None,
9092
:param manifest: Manifest to install
9193
:param old_manifest: Old manifest to patch from (if applicable)
9294
:param patch: Patch instead of redownloading the entire file
95+
:param read_files: Allow reading from already finished files
9396
:param resume: Continue based on resume file if it exists
9497
:param file_prefix_filter: Only download files that start with this prefix
9598
:param file_exclude_filter: Exclude files with this prefix from download
@@ -320,25 +323,27 @@ def run_analysis(self, manifest: Manifest, old_manifest: Manifest = None,
320323

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

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

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

0 commit comments

Comments
 (0)