mirror of
https://github.com/derrod/legendary.git
synced 2025-08-04 15:01:02 +00:00
fix: Download savegame chunks in parallel and fix savegame download when more than 1000 chunks in manifest
This commit is contained in:
parent
3963382b3f
commit
573db3b718
|
@ -2,11 +2,14 @@
|
||||||
# coding: utf-8
|
# coding: utf-8
|
||||||
|
|
||||||
import urllib.parse
|
import urllib.parse
|
||||||
|
from urllib3.util import Retry
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
import requests.adapters
|
import requests.adapters
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
from requests_futures.sessions import FuturesSession
|
||||||
|
|
||||||
from requests.auth import HTTPBasicAuth
|
from requests.auth import HTTPBasicAuth
|
||||||
|
|
||||||
from legendary.models.exceptions import InvalidCredentialsError
|
from legendary.models.exceptions import InvalidCredentialsError
|
||||||
|
@ -42,9 +45,22 @@ class EPCAPI:
|
||||||
# increase maximum pool size for multithreaded metadata requests
|
# increase maximum pool size for multithreaded metadata requests
|
||||||
self.session.mount('https://', requests.adapters.HTTPAdapter(pool_maxsize=16))
|
self.session.mount('https://', requests.adapters.HTTPAdapter(pool_maxsize=16))
|
||||||
|
|
||||||
|
retries = Retry(
|
||||||
|
total=3,
|
||||||
|
backoff_factor=0.1,
|
||||||
|
status_forcelist=[500, 501, 502, 503, 504],
|
||||||
|
allowed_methods={'GET'}
|
||||||
|
)
|
||||||
|
|
||||||
self.unauth_session = requests.session()
|
self.unauth_session = requests.session()
|
||||||
self.unauth_session.headers['User-Agent'] = self._user_agent
|
self.unauth_session.headers['User-Agent'] = self._user_agent
|
||||||
|
|
||||||
|
self.unauth_session.mount('https://', requests.adapters.HTTPAdapter(pool_connections=16, pool_maxsize=16, max_retries=retries))
|
||||||
|
|
||||||
|
self.unauth_future_session = FuturesSession(session=self.unauth_session,max_workers=16)
|
||||||
|
self.unauth_future_session.headers['User-Agent'] = self._user_agent
|
||||||
|
|
||||||
|
|
||||||
self._oauth_basic = HTTPBasicAuth(self._user_basic, self._pw_basic)
|
self._oauth_basic = HTTPBasicAuth(self._user_basic, self._pw_basic)
|
||||||
|
|
||||||
self.access_token = None
|
self.access_token = None
|
||||||
|
@ -67,6 +83,7 @@ class EPCAPI:
|
||||||
self._store_user_agent = f'EpicGamesLauncher/{version}'
|
self._store_user_agent = f'EpicGamesLauncher/{version}'
|
||||||
self.session.headers['User-Agent'] = self._user_agent
|
self.session.headers['User-Agent'] = self._user_agent
|
||||||
self.unauth_session.headers['User-Agent'] = self._user_agent
|
self.unauth_session.headers['User-Agent'] = self._user_agent
|
||||||
|
self.unauth_future_session.headers['User-Agent'] = self._user_agent
|
||||||
# update label
|
# update label
|
||||||
if label := egs_params['label']:
|
if label := egs_params['label']:
|
||||||
self._label = label
|
self._label = label
|
||||||
|
|
0
legendary/cli.py
Normal file → Executable file
0
legendary/cli.py
Normal file → Executable file
|
@ -1034,11 +1034,9 @@ class LegendaryCore:
|
||||||
os.makedirs(save_path)
|
os.makedirs(save_path)
|
||||||
|
|
||||||
_save_dir = save_dir
|
_save_dir = save_dir
|
||||||
savegames = self.egs.get_user_cloud_saves(app_name=app_name)
|
manifests = self.egs.get_user_cloud_saves(app_name=app_name,manifests=True)
|
||||||
files = savegames['files']
|
files = manifests["files"]
|
||||||
for fname, f in files.items():
|
for fname, f in files.items():
|
||||||
if '.manifest' not in fname:
|
|
||||||
continue
|
|
||||||
f_parts = fname.split('/')
|
f_parts = fname.split('/')
|
||||||
|
|
||||||
if manifest_name and f_parts[4] != manifest_name:
|
if manifest_name and f_parts[4] != manifest_name:
|
||||||
|
@ -1079,17 +1077,29 @@ class LegendaryCore:
|
||||||
m = self.load_manifest(r.content)
|
m = self.load_manifest(r.content)
|
||||||
|
|
||||||
# download chunks required for extraction
|
# download chunks required for extraction
|
||||||
chunks = dict()
|
chunkPaths = []
|
||||||
for chunk in m.chunk_data_list.elements:
|
for chunk in m.chunk_data_list.elements:
|
||||||
cpath_p = fname.split('/', 3)[:3]
|
chunkPaths.append(chunk.path)
|
||||||
cpath_p.append(chunk.path)
|
|
||||||
cpath = '/'.join(cpath_p)
|
chunkSaves = self.egs.get_user_cloud_saves(app_name=app_name, filenames=chunkPaths, manifests=False)
|
||||||
if cpath not in files:
|
chunkFiles = chunkSaves["files"]
|
||||||
self.log.warning(f'Chunk {cpath} not in file list, save data may be incomplete!')
|
|
||||||
|
if len(chunkFiles) != len(chunkPaths):
|
||||||
|
self.log.warning(f'Expected {len(chunkPaths)} chunks, but only found {len(chunkFiles)}! Save may be incomplete.')
|
||||||
continue
|
continue
|
||||||
|
|
||||||
self.log.debug(f'Downloading chunk "{cpath}"')
|
chunkLinks = [chunkFiles[c]['readLink'] for c in chunkFiles]
|
||||||
r = self.egs.unauth_session.get(files[cpath]['readLink'])
|
self.log.info(f'Downloading {len(chunkLinks)} chunks...')
|
||||||
|
|
||||||
|
def log_download(r, *args, **kwargs):
|
||||||
|
self.log.debug(f'Downloaded chunk {'/'.join(urlparse(r.url).path.split('/')[5:])} successfully.')
|
||||||
|
|
||||||
|
# map chunkLinks to self.egs.unauth_future_session.get
|
||||||
|
futures = [self.egs.unauth_future_session.get(link, hooks={'response': log_download}) for link in chunkLinks]
|
||||||
|
|
||||||
|
chunks = dict()
|
||||||
|
for future in futures:
|
||||||
|
r = future.result()
|
||||||
if r.status_code != 200:
|
if r.status_code != 200:
|
||||||
self.log.error(f'Download failed, status code: {r.status_code}')
|
self.log.error(f'Download failed, status code: {r.status_code}')
|
||||||
break
|
break
|
||||||
|
|
|
@ -1,2 +1,3 @@
|
||||||
requests<3.0
|
requests<3.0
|
||||||
|
requests_futures
|
||||||
filelock
|
filelock
|
||||||
|
|
Loading…
Reference in a new issue