Mirror Legendary classes structure in the shim.
Lgndr: Move code segments copied from `prepare_download` back to their original location in `install_game` Lgndr: Add the LgndrLogHandler at initialization instead of every function. Lgndr: Move `verify_game` to its original place in `LegendaryCLI` Lgndr: Change the way DLManager is patched into LegendaryCore proper Shared: Add singleton for LegendaryCLI, LegendaryCoreSignleton returns core from LegendaryCLI VerifyWorker: Update to use `verify_game` from `LegendaryCLI` directly PreLaunchThread: Initialize LegendaryCLI to get LegendaryCore from it InstallDialog: Update `prepare_install` argument names
This commit is contained in:
parent
3892f4a594
commit
883bd268ff
|
@ -20,7 +20,7 @@ import rare
|
|||
from rare.components.dialogs.launch_dialog import LaunchDialog
|
||||
from rare.components.main_window import MainWindow
|
||||
from rare.components.tray_icon import TrayIcon
|
||||
from rare.shared import LegendaryCoreSingleton, GlobalSignalsSingleton, ArgumentsSingleton
|
||||
from rare.shared import LegendaryCLISingleton, LegendaryCoreSingleton, GlobalSignalsSingleton, ArgumentsSingleton
|
||||
from rare.shared.image_manager import ImageManagerSingleton
|
||||
from rare.utils import legendary_utils, config_helper
|
||||
from rare.utils.paths import cache_dir, tmp_dir
|
||||
|
@ -64,7 +64,8 @@ class App(RareApp):
|
|||
|
||||
# init Legendary
|
||||
try:
|
||||
self.core = LegendaryCoreSingleton(init=True)
|
||||
LegendaryCLISingleton(init=True)
|
||||
self.core = LegendaryCoreSingleton()
|
||||
except configparser.MissingSectionHeaderError as e:
|
||||
logger.warning(f"Config is corrupt: {e}")
|
||||
if config_path := os.environ.get("XDG_CONFIG_HOME"):
|
||||
|
@ -73,7 +74,8 @@ class App(RareApp):
|
|||
path = os.path.expanduser("~/.config/legendary")
|
||||
with open(os.path.join(path, "config.ini"), "w") as config_file:
|
||||
config_file.write("[Legendary]")
|
||||
self.core = LegendaryCoreSingleton(init=True)
|
||||
LegendaryCLISingleton(init=True)
|
||||
self.core = LegendaryCoreSingleton()
|
||||
if "Legendary" not in self.core.lgd.config.sections():
|
||||
self.core.lgd.config.add_section("Legendary")
|
||||
self.core.lgd.save_config()
|
||||
|
|
|
@ -7,12 +7,14 @@ from typing import Tuple
|
|||
from PyQt5.QtCore import Qt, QObject, QRunnable, QThreadPool, pyqtSignal, pyqtSlot
|
||||
from PyQt5.QtGui import QCloseEvent, QKeyEvent
|
||||
from PyQt5.QtWidgets import QDialog, QFileDialog, QCheckBox
|
||||
|
||||
from rare.lgndr.cli import LegendaryCLI
|
||||
from rare.lgndr.core import LegendaryCore
|
||||
from legendary.models.downloading import ConditionCheckResult
|
||||
from legendary.models.game import Game
|
||||
from legendary.utils.selective_dl import games
|
||||
|
||||
from rare.shared import LegendaryCoreSingleton, ApiResultsSingleton, ArgumentsSingleton
|
||||
from rare.shared import LegendaryCLISingleton, LegendaryCoreSingleton, ApiResultsSingleton, ArgumentsSingleton
|
||||
from rare.ui.components.dialogs.install_dialog import Ui_InstallDialog
|
||||
from rare.utils.extra_widgets import PathEdit
|
||||
from rare.utils.models import InstallDownloadModel, InstallQueueItemModel
|
||||
|
@ -306,14 +308,16 @@ class InstallInfoWorker(QRunnable):
|
|||
def run(self):
|
||||
try:
|
||||
if not self.is_overlay_install:
|
||||
cli = LegendaryCLISingleton()
|
||||
download = InstallDownloadModel(
|
||||
*self.core.prepare_download(
|
||||
# *self.core.prepare_download(
|
||||
*cli.prepare_install(
|
||||
app_name=self.dl_item.options.app_name,
|
||||
base_path=self.dl_item.options.base_path,
|
||||
force=self.dl_item.options.force,
|
||||
no_install=self.dl_item.options.no_install,
|
||||
status_q=self.dl_item.status_q,
|
||||
max_shm=self.dl_item.options.max_shm,
|
||||
shared_memory=self.dl_item.options.max_shm,
|
||||
max_workers=self.dl_item.options.max_workers,
|
||||
# game_folder=,
|
||||
# disable_patching=,
|
||||
|
@ -324,11 +328,11 @@ class InstallInfoWorker(QRunnable):
|
|||
# file_prefix_filter=,
|
||||
# file_exclude_filter=,
|
||||
# file_install_tag=,
|
||||
dl_optimizations=self.dl_item.options.dl_optimizations,
|
||||
order_opt=self.dl_item.options.dl_optimizations,
|
||||
# dl_timeout=,
|
||||
repair=self.dl_item.options.repair,
|
||||
# repair_use_latest=,
|
||||
ignore_space_req=self.dl_item.options.ignore_space_req,
|
||||
repair_mode=self.dl_item.options.repair,
|
||||
# repair_and_update=True,
|
||||
ignore_space=self.dl_item.options.ignore_space_req,
|
||||
# disable_delta=,
|
||||
# override_delta_manifest=,
|
||||
# reset_sdl=,
|
||||
|
|
|
@ -11,9 +11,8 @@ from PyQt5.QtCore import Qt, QModelIndex, pyqtSignal, QRunnable, QObject, QThrea
|
|||
from PyQt5.QtGui import QStandardItemModel
|
||||
from PyQt5.QtWidgets import QFileDialog, QGroupBox, QCompleter, QTreeView, QHeaderView, qApp, QMessageBox
|
||||
|
||||
from rare.lgndr.cli import LegendaryCLI
|
||||
from rare.lgndr.exception import LgndrException
|
||||
from rare.shared import LegendaryCoreSingleton, GlobalSignalsSingleton, ApiResultsSingleton
|
||||
from rare.shared import LegendaryCLISingleton, LegendaryCoreSingleton, GlobalSignalsSingleton, ApiResultsSingleton
|
||||
from rare.ui.components.tabs.games.import_sync.import_group import Ui_ImportGroup
|
||||
from rare.utils import legendary_utils
|
||||
from rare.utils.extra_widgets import IndicatorLineEdit, PathEdit
|
||||
|
@ -121,8 +120,7 @@ class ImportWorker(QRunnable):
|
|||
return args
|
||||
|
||||
def __import_game(self, app_name: str, path: Path):
|
||||
cli = LegendaryCLI()
|
||||
cli.core = LegendaryCoreSingleton()
|
||||
cli = LegendaryCLISingleton()
|
||||
args = self.import_game_args(str(path), app_name)
|
||||
try:
|
||||
cli.import_game(args)
|
||||
|
|
|
@ -14,7 +14,7 @@ from PyQt5.QtNetwork import QLocalServer, QLocalSocket
|
|||
from .console import Console
|
||||
from .lgd_helper import get_launch_args, InitArgs, get_configured_process, LaunchArgs, GameArgsError
|
||||
from .message_models import ErrorModel, Actions, FinishedModel, BaseModel, StateChangedModel
|
||||
from ..shared import LegendaryCoreSingleton
|
||||
from ..shared import LegendaryCLISingleton, LegendaryCoreSingleton
|
||||
from ..widgets.rare_app import RareApp
|
||||
|
||||
|
||||
|
@ -69,7 +69,8 @@ class GameProcessApp(RareApp):
|
|||
self.game_process = QProcess()
|
||||
self.app_name = app_name
|
||||
self.logger = getLogger(self.app_name)
|
||||
self.core = LegendaryCoreSingleton(True)
|
||||
LegendaryCLISingleton(init=True)
|
||||
self.core = LegendaryCoreSingleton()
|
||||
|
||||
lang = self.settings.value("language", self.core.language_code, type=str)
|
||||
self.load_translator(lang)
|
||||
|
|
|
@ -1,11 +1,21 @@
|
|||
import os
|
||||
import logging
|
||||
import time
|
||||
from argparse import Namespace
|
||||
from multiprocessing import Queue
|
||||
from typing import Optional, Callable, List
|
||||
|
||||
import legendary.cli
|
||||
from PyQt5.QtWidgets import QLabel, QMessageBox
|
||||
from legendary.cli import LegendaryCLI as LegendaryCLIReal, logger
|
||||
from legendary.cli import logger
|
||||
from legendary.models.downloading import AnalysisResult, ConditionCheckResult
|
||||
from legendary.models.game import Game, InstalledGame, VerifyResult
|
||||
from legendary.utils.lfs import validate_files
|
||||
from legendary.utils.selective_dl import get_sdl_appname
|
||||
|
||||
from .core import LegendaryCore
|
||||
from .exception import LgndrException, LgndrLogHandler
|
||||
from .manager import DLManager
|
||||
|
||||
|
||||
def get_boolean_choice(message):
|
||||
|
@ -22,22 +32,347 @@ class UILogHandler(logging.Handler):
|
|||
self.widget.setText(record.getMessage())
|
||||
|
||||
|
||||
class LegendaryCLI(LegendaryCLIReal):
|
||||
|
||||
def __init__(self):
|
||||
self.core = None
|
||||
class LegendaryCLI(legendary.cli.LegendaryCLI):
|
||||
def __init__(self, override_config=None, api_timeout=None):
|
||||
self.core = LegendaryCore(override_config)
|
||||
self.logger = logging.getLogger('cli')
|
||||
self.logging_queue = None
|
||||
self.handler = LgndrLogHandler()
|
||||
self.logger.addHandler(self.handler)
|
||||
|
||||
# def __init__(self, core: LegendaryCore):
|
||||
# self.core = core
|
||||
# self.logger = logging.getLogger('cli')
|
||||
# self.logging_queue = None
|
||||
# self.handler = LgndrLogHandler()
|
||||
# self.logger.addHandler(self.handler)
|
||||
|
||||
# app_name, repair, repair_mode, no_install, repair_and_update, file_prefix, file_exclude_prefix, platform
|
||||
# sdl_prompt, status_q
|
||||
def prepare_install(self, app_name: str, base_path: str = '',
|
||||
status_q: Queue = None, shared_memory: int = 0, max_workers: int = 0,
|
||||
force: bool = False, disable_patching: bool = False,
|
||||
game_folder: str = '', override_manifest: str = '',
|
||||
override_old_manifest: str = '', override_base_url: str = '',
|
||||
platform: str = 'Windows', file_prefix: list = None,
|
||||
file_exclude_prefix: list = None, install_tag: list = None,
|
||||
order_opt: bool = False, dl_timeout: int = 10,
|
||||
repair_mode: bool = False, repair_and_update: bool = False,
|
||||
disable_delta: bool = False, override_delta_manifest: str = '',
|
||||
egl_guid: str = '', preferred_cdn: str = None,
|
||||
no_install: bool = False, ignore_space: bool = False,
|
||||
disable_sdl: bool = False, reset_sdl: bool = False, skip_sdl: bool = False,
|
||||
sdl_prompt: Callable[[str, str], List[str]] = list,
|
||||
yes: bool = True, verify_callback: Callable[[int, int, float, float], None] = None,
|
||||
disable_https: bool = False) -> (DLManager, AnalysisResult, InstalledGame, Game, bool, Optional[str], ConditionCheckResult):
|
||||
args = Namespace(
|
||||
app_name=app_name, base_path=base_path,
|
||||
status_q=status_q, shared_memory=shared_memory, max_workers=max_workers,
|
||||
force=force, disable_patching=disable_patching,
|
||||
game_folder=game_folder, override_manifest=override_manifest,
|
||||
override_old_manifest=override_old_manifest, override_base_url=override_base_url,
|
||||
platform=platform, file_prefix=file_prefix,
|
||||
file_exclude_prefix=file_exclude_prefix, install_tag=install_tag,
|
||||
order_opt=order_opt, dl_timeout=dl_timeout,
|
||||
repair_mode=repair_mode, repair_and_update=repair_and_update,
|
||||
disable_delta=disable_delta, override_delta_manifest=override_delta_manifest,
|
||||
preferred_cdn=preferred_cdn,
|
||||
no_install=no_install, ignore_space=ignore_space,
|
||||
disable_sdl=disable_sdl, reset_sdl=reset_sdl, skip_sdl=skip_sdl,
|
||||
sdl_prompt=sdl_prompt,
|
||||
yes=yes, callback=verify_callback,
|
||||
disable_https=disable_https
|
||||
)
|
||||
old_choice = legendary.cli.get_boolean_choice
|
||||
legendary.cli.get_boolean_choice = get_boolean_choice
|
||||
try:
|
||||
return self.install_game(args)
|
||||
except LgndrException as ret:
|
||||
raise ret
|
||||
finally:
|
||||
legendary.cli.get_boolean_choice = old_choice
|
||||
|
||||
def install_game(self, args):
|
||||
args.app_name = self._resolve_aliases(args.app_name)
|
||||
if self.core.is_installed(args.app_name):
|
||||
igame = self.core.get_installed_game(args.app_name)
|
||||
args.platform = igame.platform
|
||||
if igame.needs_verification and not args.repair_mode:
|
||||
logger.info('Game needs to be verified before updating, switching to repair mode...')
|
||||
args.repair_mode = True
|
||||
|
||||
repair_file = None
|
||||
if args.repair_mode:
|
||||
args.repair_mode = True
|
||||
args.no_install = args.repair_and_update is False
|
||||
repair_file = os.path.join(self.core.lgd.get_tmp_path(), f'{args.app_name}.repair')
|
||||
|
||||
if args.file_prefix or args.file_exclude_prefix:
|
||||
args.no_install = True
|
||||
|
||||
game = self.core.get_game(args.app_name, update_meta=True, platform=args.platform)
|
||||
|
||||
if not game:
|
||||
logger.error(f'Could not find "{args.app_name}" in list of available games, '
|
||||
f'did you type the name correctly?')
|
||||
|
||||
if args.platform not in game.asset_infos:
|
||||
if not args.no_install:
|
||||
if self.core.lgd.config.getboolean('Legendary', 'install_platform_fallback', fallback=True):
|
||||
logger.warning(f'App has no asset for platform "{args.platform}", falling back to "Windows".')
|
||||
args.platform = 'Windows'
|
||||
else:
|
||||
logger.error(f'No app asset found for platform "{args.platform}", run '
|
||||
f'"legendary info --platform {args.platform}" and make '
|
||||
f'sure the app is available for the specified platform.')
|
||||
return
|
||||
else:
|
||||
logger.warning(f'No asset found for platform "{args.platform}", '
|
||||
f'trying anyway since --no-install is set.')
|
||||
|
||||
if game.is_dlc:
|
||||
logger.info('Install candidate is DLC')
|
||||
app_name = game.metadata['mainGameItem']['releaseInfo'][0]['appId']
|
||||
base_game = self.core.get_game(app_name)
|
||||
# check if base_game is actually installed
|
||||
if not self.core.is_installed(app_name):
|
||||
# download mode doesn't care about whether something's installed
|
||||
if not args.no_install:
|
||||
logger.fatal(f'Base game "{app_name}" is not installed!')
|
||||
else:
|
||||
base_game = None
|
||||
|
||||
if args.repair_mode:
|
||||
if not self.core.is_installed(game.app_name):
|
||||
logger.error(f'Game "{game.app_title}" ({game.app_name}) is not installed!')
|
||||
|
||||
if not os.path.exists(repair_file):
|
||||
logger.info('Game has not been verified yet.')
|
||||
if not args.yes:
|
||||
if not get_boolean_choice(f'Verify "{game.app_name}" now ("no" will abort repair)?'):
|
||||
return
|
||||
try:
|
||||
self.verify_game(args, print_command=False, repair_mode=True, repair_online=args.repair_and_update)
|
||||
except ValueError:
|
||||
logger.error('To repair a game with a missing manifest you must run the command with '
|
||||
'"--repair-and-update". However this will redownload any file that does '
|
||||
'not match the current hash in its entirety.')
|
||||
return
|
||||
else:
|
||||
logger.info(f'Using existing repair file: {repair_file}')
|
||||
|
||||
# check if SDL should be disabled
|
||||
sdl_enabled = not args.install_tag and not game.is_dlc
|
||||
config_tags = self.core.lgd.config.get(game.app_name, 'install_tags', fallback=None)
|
||||
config_disable_sdl = self.core.lgd.config.getboolean(game.app_name, 'disable_sdl', fallback=False)
|
||||
# remove config flag if SDL is reset
|
||||
if config_disable_sdl and args.reset_sdl and not args.disable_sdl:
|
||||
self.core.lgd.config.remove_option(game.app_name, 'disable_sdl')
|
||||
# if config flag is not yet set, set it and remove previous install tags
|
||||
elif not config_disable_sdl and args.disable_sdl:
|
||||
logger.info('Clearing install tags from config and disabling SDL for title.')
|
||||
if config_tags:
|
||||
self.core.lgd.config.remove_option(game.app_name, 'install_tags')
|
||||
config_tags = None
|
||||
self.core.lgd.config.set(game.app_name, 'disable_sdl', True)
|
||||
sdl_enabled = False
|
||||
# just disable SDL, but keep config tags that have been manually specified
|
||||
elif config_disable_sdl or args.disable_sdl:
|
||||
sdl_enabled = False
|
||||
|
||||
if sdl_enabled and ((sdl_name := get_sdl_appname(game.app_name)) is not None):
|
||||
if not self.core.is_installed(game.app_name) or config_tags is None or args.reset_sdl:
|
||||
sdl_data = self.core.get_sdl_data(sdl_name, platform=args.platform)
|
||||
if sdl_data:
|
||||
if args.skip_sdl:
|
||||
args.install_tag = ['']
|
||||
if '__required' in sdl_data:
|
||||
args.install_tag.extend(sdl_data['__required']['tags'])
|
||||
else:
|
||||
args.install_tag = args.sdl_prompt(sdl_data, game.app_title)
|
||||
self.core.lgd.config.set(game.app_name, 'install_tags', ','.join(args.install_tag))
|
||||
else:
|
||||
logger.error(f'Unable to get SDL data for {sdl_name}')
|
||||
else:
|
||||
args.install_tag = config_tags.split(',')
|
||||
elif args.install_tag and not game.is_dlc and not args.no_install:
|
||||
config_tags = ','.join(args.install_tag)
|
||||
logger.info(f'Saving install tags for "{game.app_name}" to config: {config_tags}')
|
||||
self.core.lgd.config.set(game.app_name, 'install_tags', config_tags)
|
||||
elif not game.is_dlc:
|
||||
if config_tags and args.reset_sdl:
|
||||
logger.info('Clearing install tags from config.')
|
||||
self.core.lgd.config.remove_option(game.app_name, 'install_tags')
|
||||
elif config_tags:
|
||||
logger.info(f'Using install tags from config: {config_tags}')
|
||||
args.install_tag = config_tags.split(',')
|
||||
|
||||
logger.info(f'Preparing download for "{game.app_title}" ({game.app_name})...')
|
||||
# todo use status queue to print progress from CLI
|
||||
# This has become a little ridiculous hasn't it?
|
||||
dlm, analysis, igame = self.core.prepare_download(game=game, base_game=base_game, base_path=args.base_path,
|
||||
status_q=args.status_q,
|
||||
force=args.force, max_shm=args.shared_memory,
|
||||
max_workers=args.max_workers, game_folder=args.game_folder,
|
||||
disable_patching=args.disable_patching,
|
||||
override_manifest=args.override_manifest,
|
||||
override_old_manifest=args.override_old_manifest,
|
||||
override_base_url=args.override_base_url,
|
||||
platform=args.platform,
|
||||
file_prefix_filter=args.file_prefix,
|
||||
file_exclude_filter=args.file_exclude_prefix,
|
||||
file_install_tag=args.install_tag,
|
||||
dl_optimizations=args.order_opt,
|
||||
dl_timeout=args.dl_timeout,
|
||||
repair=args.repair_mode,
|
||||
repair_use_latest=args.repair_and_update,
|
||||
disable_delta=args.disable_delta,
|
||||
override_delta_manifest=args.override_delta_manifest,
|
||||
preferred_cdn=args.preferred_cdn,
|
||||
disable_https=args.disable_https)
|
||||
|
||||
# game is either up-to-date or hasn't changed, so we have nothing to do
|
||||
if not analysis.dl_size:
|
||||
logger.info('Download size is 0, the game is either already up to date or has not changed. Exiting...')
|
||||
self.clean_post_install(game, igame, args.repair_mode, repair_file)
|
||||
|
||||
logger.error('Nothing to do.')
|
||||
|
||||
res = self.core.check_installation_conditions(analysis=analysis, install=igame, game=game,
|
||||
updating=self.core.is_installed(args.app_name),
|
||||
ignore_space_req=args.ignore_space)
|
||||
|
||||
return dlm, analysis, igame, game, args.repair_mode, repair_file, res
|
||||
|
||||
def clean_post_install(self, game: Game, igame: InstalledGame, repair: bool = False, repair_file: str = ''):
|
||||
old_igame = self.core.get_installed_game(game.app_name)
|
||||
if old_igame and repair and os.path.exists(repair_file):
|
||||
if old_igame.needs_verification:
|
||||
old_igame.needs_verification = False
|
||||
self.core.install_game(old_igame)
|
||||
|
||||
logger.debug('Removing repair file.')
|
||||
os.remove(repair_file)
|
||||
|
||||
# check if install tags have changed, if they did; try deleting files that are no longer required.
|
||||
if old_igame and old_igame.install_tags != igame.install_tags:
|
||||
old_igame.install_tags = igame.install_tags
|
||||
logger.info('Deleting now untagged files.')
|
||||
self.core.uninstall_tag(old_igame)
|
||||
self.core.install_game(old_igame)
|
||||
|
||||
def verify_game(self, args, print_command=True, repair_mode=False, repair_online=False):
|
||||
if not hasattr(args, 'callback') or args.callback is None:
|
||||
args.callback = print
|
||||
|
||||
args.app_name = self._resolve_aliases(args.app_name)
|
||||
if not self.core.is_installed(args.app_name):
|
||||
logger.error(f'Game "{args.app_name}" is not installed')
|
||||
return
|
||||
|
||||
logger.info(f'Loading installed manifest for "{args.app_name}"')
|
||||
igame = self.core.get_installed_game(args.app_name)
|
||||
if not os.path.exists(igame.install_path):
|
||||
logger.error(f'Install path "{igame.install_path}" does not exist, make sure all necessary mounts '
|
||||
f'are available. If you previously deleted the game folder without uninstalling, run '
|
||||
f'"legendary uninstall -y {igame.app_name}" and reinstall from scratch.')
|
||||
return
|
||||
|
||||
manifest_data, _ = self.core.get_installed_manifest(args.app_name)
|
||||
if manifest_data is None:
|
||||
if repair_mode:
|
||||
if not repair_online:
|
||||
logger.critical('No manifest could be loaded, the manifest file may be missing!')
|
||||
raise ValueError('Local manifest is missing')
|
||||
|
||||
logger.warning('No manifest could be loaded, the file may be missing. Downloading the latest manifest.')
|
||||
game = self.core.get_game(args.app_name, platform=igame.platform)
|
||||
manifest_data, _ = self.core.get_cdn_manifest(game, igame.platform)
|
||||
else:
|
||||
logger.critical(f'Manifest appears to be missing! To repair, run "legendary repair '
|
||||
f'{args.app_name} --repair-and-update", this will however redownload all files '
|
||||
f'that do not match the latest manifest in their entirety.')
|
||||
return
|
||||
|
||||
manifest = self.core.load_manifest(manifest_data)
|
||||
|
||||
files = sorted(manifest.file_manifest_list.elements,
|
||||
key=lambda a: a.filename.lower())
|
||||
|
||||
# build list of hashes
|
||||
if config_tags := self.core.lgd.config.get(args.app_name, 'install_tags', fallback=None):
|
||||
install_tags = set(i.strip() for i in config_tags.split(','))
|
||||
file_list = [
|
||||
(f.filename, f.sha_hash.hex())
|
||||
for f in files
|
||||
if any(it in install_tags for it in f.install_tags) or not f.install_tags
|
||||
]
|
||||
else:
|
||||
file_list = [(f.filename, f.sha_hash.hex()) for f in files]
|
||||
|
||||
total = len(file_list)
|
||||
total_size = sum(manifest.file_manifest_list.get_file_by_path(fm[0]).file_size
|
||||
for fm in file_list)
|
||||
num = processed = last_processed = 0
|
||||
speed = 0.0
|
||||
percentage = 0.0
|
||||
failed = []
|
||||
missing = []
|
||||
|
||||
last_update = time.time()
|
||||
|
||||
logger.info(f'Verifying "{igame.title}" version "{manifest.meta.build_version}"')
|
||||
repair_file = []
|
||||
for result, path, result_hash, bytes_read in validate_files(igame.install_path, file_list):
|
||||
processed += bytes_read
|
||||
percentage = (processed / total_size) * 100.0
|
||||
num += 1
|
||||
|
||||
if (delta := ((current_time := time.time()) - last_update)) > 1 or (not last_processed and delta > 1):
|
||||
last_update = current_time
|
||||
speed = (processed - last_processed) / 1024 / 1024 / delta
|
||||
last_processed = processed
|
||||
|
||||
if args.callback:
|
||||
args.callback(num, total, percentage, speed)
|
||||
|
||||
if result == VerifyResult.HASH_MATCH:
|
||||
repair_file.append(f'{result_hash}:{path}')
|
||||
continue
|
||||
elif result == VerifyResult.HASH_MISMATCH:
|
||||
logger.info(f'File does not match hash: "{path}"')
|
||||
repair_file.append(f'{result_hash}:{path}')
|
||||
failed.append(path)
|
||||
elif result == VerifyResult.FILE_MISSING:
|
||||
logger.info(f'File is missing: "{path}"')
|
||||
missing.append(path)
|
||||
else:
|
||||
logger.info(f'Other failure (see log), treating file as missing: "{path}"')
|
||||
missing.append(path)
|
||||
|
||||
if args.callback:
|
||||
args.callback(num, total, percentage, speed)
|
||||
|
||||
# always write repair file, even if all match
|
||||
if repair_file:
|
||||
repair_filename = os.path.join(self.core.lgd.get_tmp_path(), f'{args.app_name}.repair')
|
||||
with open(repair_filename, 'w', encoding='utf-8') as f:
|
||||
f.write('\n'.join(repair_file))
|
||||
logger.debug(f'Written repair file to "{repair_filename}"')
|
||||
|
||||
if not missing and not failed:
|
||||
logger.info('Verification finished successfully.')
|
||||
else:
|
||||
logger.error(f'Verification failed, {len(failed)} file(s) corrupted, {len(missing)} file(s) are missing.')
|
||||
if print_command:
|
||||
logger.info(f'Run "legendary repair {args.app_name}" to repair your game installation.')
|
||||
|
||||
def import_game(self, args):
|
||||
handler = LgndrLogHandler()
|
||||
logger.addHandler(handler)
|
||||
old_choice = legendary.cli.get_boolean_choice
|
||||
legendary.cli.get_boolean_choice = get_boolean_choice
|
||||
try:
|
||||
super(LegendaryCLI, self).import_game(args)
|
||||
except LgndrException as e:
|
||||
raise e
|
||||
except LgndrException as ret:
|
||||
raise ret
|
||||
finally:
|
||||
legendary.cli.get_boolean_choice = old_choice
|
||||
logger.removeHandler(handler)
|
||||
|
|
|
@ -1,24 +1,22 @@
|
|||
import logging
|
||||
import os
|
||||
import sys
|
||||
|
||||
from multiprocessing import Queue
|
||||
from typing import Callable
|
||||
|
||||
from legendary.utils.lfs import validate_files
|
||||
from legendary.models.downloading import AnalysisResult, ConditionCheckResult
|
||||
from legendary.models.game import *
|
||||
from legendary.utils.selective_dl import get_sdl_appname
|
||||
import legendary.core
|
||||
from legendary.models.downloading import AnalysisResult
|
||||
from legendary.models.game import Game
|
||||
from legendary.models.manifest import ManifestMeta
|
||||
|
||||
from legendary.core import LegendaryCore as LegendaryCoreReal
|
||||
|
||||
from .manager import DLManager
|
||||
from .exception import LgndrException, LgndrLogHandler
|
||||
from .manager import DLManager
|
||||
|
||||
|
||||
class LegendaryCore(LegendaryCoreReal):
|
||||
# fmt: off
|
||||
def prepare_download(self, app_name: str, base_path: str = '',
|
||||
class LegendaryCore(legendary.core.LegendaryCore):
|
||||
|
||||
def __init__(self, override_config=None, timeout=10.0):
|
||||
super(LegendaryCore, self).__init__(override_config=override_config, timeout=timeout)
|
||||
self.handler = LgndrLogHandler()
|
||||
self.log.addHandler(self.handler)
|
||||
|
||||
def prepare_download(self, game: Game, base_game: Game = None, base_path: str = '',
|
||||
status_q: Queue = None, max_shm: int = 0, max_workers: int = 0,
|
||||
force: bool = False, disable_patching: bool = False,
|
||||
game_folder: str = '', override_manifest: str = '',
|
||||
|
@ -29,232 +27,59 @@ class LegendaryCore(LegendaryCoreReal):
|
|||
repair: bool = False, repair_use_latest: bool = False,
|
||||
disable_delta: bool = False, override_delta_manifest: str = '',
|
||||
egl_guid: str = '', preferred_cdn: str = None,
|
||||
no_install: bool = False, ignore_space_req: bool = False,
|
||||
disable_sdl: bool = False, reset_sdl: bool = False, skip_sdl: bool = False,
|
||||
sdl_prompt: Callable[[str, str], List[str]] = list,
|
||||
disable_https: bool = False) -> (DLManager, AnalysisResult, InstalledGame, Game, bool, Optional[str], ConditionCheckResult):
|
||||
if self.is_installed(app_name):
|
||||
igame = self.get_installed_game(app_name)
|
||||
platform = igame.platform
|
||||
if igame.needs_verification and not repair:
|
||||
self.log.info('Game needs to be verified before updating, switching to repair mode...')
|
||||
repair = True
|
||||
|
||||
repair_file = None
|
||||
if repair:
|
||||
repair = True
|
||||
no_install = repair_use_latest is False
|
||||
repair_file = os.path.join(self.lgd.get_tmp_path(), f'{app_name}.repair')
|
||||
|
||||
if file_prefix_filter or file_exclude_filter or file_install_tag:
|
||||
no_install = True
|
||||
|
||||
game = self.get_game(app_name, update_meta=True)
|
||||
|
||||
if not game:
|
||||
raise RuntimeError(f'Could not find "{app_name}" in list of available games,'
|
||||
f'did you type the name correctly?')
|
||||
|
||||
if game.is_dlc:
|
||||
self.log.info('Install candidate is DLC')
|
||||
app_name = game.metadata['mainGameItem']['releaseInfo'][0]['appId']
|
||||
base_game = self.get_game(app_name)
|
||||
# check if base_game is actually installed
|
||||
if not self.is_installed(app_name):
|
||||
# download mode doesn't care about whether or not something's installed
|
||||
if not no_install:
|
||||
raise RuntimeError(f'Base game "{app_name}" is not installed!')
|
||||
else:
|
||||
base_game = None
|
||||
|
||||
if repair:
|
||||
if not self.is_installed(game.app_name):
|
||||
raise RuntimeError(f'Game "{game.app_title}" ({game.app_name}) is not installed!')
|
||||
|
||||
if not os.path.exists(repair_file):
|
||||
self.log.info('Verifing game...')
|
||||
self.verify_game(app_name)
|
||||
else:
|
||||
self.log.info(f'Using existing repair file: {repair_file}')
|
||||
|
||||
# check if SDL should be disabled
|
||||
sdl_enabled = not file_install_tag and not game.is_dlc
|
||||
config_tags = self.lgd.config.get(game.app_name, 'install_tags', fallback=None)
|
||||
config_disable_sdl = self.lgd.config.getboolean(game.app_name, 'disable_sdl', fallback=False)
|
||||
# remove config flag if SDL is reset
|
||||
if config_disable_sdl and reset_sdl and not disable_sdl:
|
||||
self.lgd.config.remove_option(game.app_name, 'disable_sdl')
|
||||
# if config flag is not yet set, set it and remove previous install tags
|
||||
elif not config_disable_sdl and disable_sdl:
|
||||
self.log.info('Clearing install tags from config and disabling SDL for title.')
|
||||
if config_tags:
|
||||
self.lgd.config.remove_option(game.app_name, 'install_tags')
|
||||
config_tags = None
|
||||
self.lgd.config.set(game.app_name, 'disable_sdl', True)
|
||||
sdl_enabled = False
|
||||
# just disable SDL, but keep config tags that have been manually specified
|
||||
elif config_disable_sdl or disable_sdl:
|
||||
sdl_enabled = False
|
||||
|
||||
if sdl_enabled and ((sdl_name := get_sdl_appname(game.app_name)) is not None):
|
||||
if not self.is_installed(game.app_name) or config_tags is None or reset_sdl:
|
||||
sdl_data = self.get_sdl_data(sdl_name, platform=platform)
|
||||
if sdl_data:
|
||||
if skip_sdl:
|
||||
file_install_tag = ['']
|
||||
if '__required' in sdl_data:
|
||||
file_install_tag.extend(sdl_data['__required']['tags'])
|
||||
else:
|
||||
file_install_tag = sdl_prompt(sdl_data, game.app_title)
|
||||
self.lgd.config.set(game.app_name, 'install_tags', ','.join(file_install_tag))
|
||||
else:
|
||||
self.log.error(f'Unable to get SDL data for {sdl_name}')
|
||||
else:
|
||||
file_install_tag = config_tags.split(',')
|
||||
elif file_install_tag and not game.is_dlc and not no_install:
|
||||
config_tags = ','.join(file_install_tag)
|
||||
self.log.info(f'Saving install tags for "{game.app_name}" to config: {config_tags}')
|
||||
self.lgd.config.set(game.app_name, 'install_tags', config_tags)
|
||||
elif not game.is_dlc:
|
||||
if config_tags and reset_sdl:
|
||||
self.log.info('Clearing install tags from config.')
|
||||
self.lgd.config.remove_option(game.app_name, 'install_tags')
|
||||
elif config_tags:
|
||||
self.log.info(f'Using install tags from config: {config_tags}')
|
||||
file_install_tag = config_tags.split(',')
|
||||
|
||||
dlm, analysis, igame = super(LegendaryCore, self).prepare_download(game=game, base_game=base_game, base_path=base_path,
|
||||
force=force, max_shm=max_shm,
|
||||
max_workers=max_workers, game_folder=game_folder,
|
||||
disable_patching=disable_patching,
|
||||
override_manifest=override_manifest,
|
||||
override_old_manifest=override_old_manifest,
|
||||
override_base_url=override_base_url,
|
||||
platform=platform,
|
||||
file_prefix_filter=file_prefix_filter,
|
||||
file_exclude_filter=file_exclude_filter,
|
||||
file_install_tag=file_install_tag,
|
||||
dl_optimizations=dl_optimizations,
|
||||
dl_timeout=dl_timeout,
|
||||
repair=repair,
|
||||
repair_use_latest=repair_use_latest,
|
||||
disable_delta=disable_delta,
|
||||
override_delta_manifest=override_delta_manifest,
|
||||
preferred_cdn=preferred_cdn,
|
||||
status_q=status_q,
|
||||
disable_https=disable_https)
|
||||
# lk: monkeypatch run_real (the method that emits the stats) into DLManager
|
||||
dlm.run_real = DLManager.run_real.__get__(dlm, DLManager)
|
||||
|
||||
# game is either up to date or hasn't changed, so we have nothing to do
|
||||
if not analysis.dl_size:
|
||||
self.log.info('Download size is 0, the game is either already up to date or has not changed. Exiting...')
|
||||
self.clean_post_install(game, igame, repair, repair_file)
|
||||
|
||||
raise RuntimeError('Nothing to do.')
|
||||
|
||||
res = self.check_installation_conditions(analysis=analysis, install=igame, game=game,
|
||||
updating=self.is_installed(app_name),
|
||||
ignore_space_req=ignore_space_req)
|
||||
|
||||
return dlm, analysis, igame, game, repair, repair_file, res
|
||||
|
||||
def verify_game(self, app_name: str, callback: Callable[[int, int], None] = print):
|
||||
if not self.is_installed(app_name):
|
||||
self.log.error(f'Game "{app_name}" is not installed')
|
||||
return
|
||||
|
||||
self.log.info(f'Loading installed manifest for "{app_name}"')
|
||||
igame = self.get_installed_game(app_name)
|
||||
manifest_data, _ = self.get_installed_manifest(app_name)
|
||||
manifest = self.load_manifest(manifest_data)
|
||||
|
||||
files = sorted(manifest.file_manifest_list.elements,
|
||||
key=lambda a: a.filename.lower())
|
||||
|
||||
# build list of hashes
|
||||
file_list = [(f.filename, f.sha_hash.hex()) for f in files]
|
||||
total = len(file_list)
|
||||
num = 0
|
||||
failed = []
|
||||
missing = []
|
||||
|
||||
self.log.info(f'Verifying "{igame.title}" version "{manifest.meta.build_version}"')
|
||||
repair_file = []
|
||||
for result, path, result_hash in validate_files(igame.install_path, file_list):
|
||||
if callback:
|
||||
num += 1
|
||||
callback(num, total)
|
||||
|
||||
if result == VerifyResult.HASH_MATCH:
|
||||
repair_file.append(f'{result_hash}:{path}')
|
||||
continue
|
||||
elif result == VerifyResult.HASH_MISMATCH:
|
||||
self.log.error(f'File does not match hash: "{path}"')
|
||||
repair_file.append(f'{result_hash}:{path}')
|
||||
failed.append(path)
|
||||
elif result == VerifyResult.FILE_MISSING:
|
||||
self.log.error(f'File is missing: "{path}"')
|
||||
missing.append(path)
|
||||
else:
|
||||
self.log.error(f'Other failure (see log), treating file as missing: "{path}"')
|
||||
missing.append(path)
|
||||
|
||||
# always write repair file, even if all match
|
||||
if repair_file:
|
||||
repair_filename = os.path.join(self.lgd.get_tmp_path(), f'{app_name}.repair')
|
||||
with open(repair_filename, 'w') as f:
|
||||
f.write('\n'.join(repair_file))
|
||||
self.log.debug(f'Written repair file to "{repair_filename}"')
|
||||
|
||||
if not missing and not failed:
|
||||
self.log.info('Verification finished successfully.')
|
||||
else:
|
||||
raise RuntimeError(
|
||||
f'Verification failed, {len(failed)} file(s) corrupted, {len(missing)} file(s) are missing.')
|
||||
|
||||
def clean_post_install(self, game: Game, igame: InstalledGame, repair: bool = False, repair_file: str = ''):
|
||||
old_igame = self.get_installed_game(game.app_name)
|
||||
if old_igame and repair and os.path.exists(repair_file):
|
||||
if old_igame.needs_verification:
|
||||
old_igame.needs_verification = False
|
||||
self.install_game(old_igame)
|
||||
|
||||
self.log.debug('Removing repair file.')
|
||||
os.remove(repair_file)
|
||||
|
||||
# check if install tags have changed, if they did; try deleting files that are no longer required.
|
||||
if old_igame and old_igame.install_tags != igame.install_tags:
|
||||
old_igame.install_tags = igame.install_tags
|
||||
self.log.info('Deleting now untagged files.')
|
||||
self.uninstall_tag(old_igame)
|
||||
self.install_game(old_igame)
|
||||
# fmt: on
|
||||
disable_https: bool = False) -> (DLManager, AnalysisResult, ManifestMeta):
|
||||
_dlmanager = legendary.core.DLManager
|
||||
legendary.core.DLManager = DLManager
|
||||
try:
|
||||
dlm, analysis, igame = super(LegendaryCore, self).prepare_download(
|
||||
game=game, base_game=base_game, base_path=base_path,
|
||||
status_q=status_q, max_shm=max_shm, max_workers=max_workers,
|
||||
force=force, disable_patching=disable_patching,
|
||||
game_folder=game_folder, override_manifest=override_manifest,
|
||||
override_old_manifest=override_old_manifest, override_base_url=override_base_url,
|
||||
platform=platform, file_prefix_filter=file_prefix_filter,
|
||||
file_exclude_filter=file_exclude_filter, file_install_tag=file_install_tag,
|
||||
dl_optimizations=dl_optimizations, dl_timeout=dl_timeout,
|
||||
repair=repair, repair_use_latest=repair_use_latest,
|
||||
disable_delta=disable_delta, override_delta_manifest=override_delta_manifest,
|
||||
egl_guid=egl_guid, preferred_cdn=preferred_cdn,
|
||||
disable_https=disable_https
|
||||
)
|
||||
# lk: monkeypatch run_real (the method that emits the stats) into DLManager
|
||||
# dlm.run_real = DLManager.run_real.__get__(dlm, DLManager)
|
||||
return dlm, analysis, igame
|
||||
except LgndrException as ret:
|
||||
raise ret
|
||||
finally:
|
||||
legendary.core.DLManager = _dlmanager
|
||||
|
||||
def egl_import(self, app_name):
|
||||
handler = LgndrLogHandler()
|
||||
self.log.addHandler(handler)
|
||||
try:
|
||||
super(LegendaryCore, self).egl_import(app_name)
|
||||
except LgndrException as ret:
|
||||
raise ret
|
||||
finally:
|
||||
self.log.removeHandler(handler)
|
||||
pass
|
||||
|
||||
def egl_export(self, app_name):
|
||||
handler = LgndrLogHandler()
|
||||
self.log.addHandler(handler)
|
||||
try:
|
||||
super(LegendaryCore, self).egl_export(app_name)
|
||||
except LgndrException as ret:
|
||||
raise ret
|
||||
finally:
|
||||
self.log.removeHandler(handler)
|
||||
pass
|
||||
|
||||
def prepare_overlay_install(self, path=None, status_q: Queue = None):
|
||||
dlm, analysis_result, igame = super(LegendaryCore, self).prepare_overlay_install(path)
|
||||
# lk: monkeypatch status_q (the queue for download stats)
|
||||
# lk: and run_real (the method that emits the stats) into DLManager
|
||||
dlm.status_queue = status_q
|
||||
dlm.run_real = DLManager.run_real.__get__(dlm, DLManager)
|
||||
return dlm, analysis_result, igame
|
||||
_dlmanager = legendary.core.DLManager
|
||||
legendary.core.DLManager = DLManager
|
||||
try:
|
||||
dlm, analysis_result, igame = super(LegendaryCore, self).prepare_overlay_install(path)
|
||||
# lk: monkeypatch status_q (the queue for download stats)
|
||||
# lk: and run_real (the method that emits the stats) into DLManager
|
||||
dlm.status_queue = status_q
|
||||
# dlm.run_real = DLManager.run_real.__get__(dlm, DLManager)
|
||||
return dlm, analysis_result, igame
|
||||
except LgndrException as ret:
|
||||
raise ret
|
||||
finally:
|
||||
legendary.core.DLManager = _dlmanager
|
||||
|
|
|
@ -10,13 +10,12 @@ from threading import Condition, Thread
|
|||
from legendary.downloader.mp.workers import DLWorker, FileWorker
|
||||
from legendary.models.downloading import ChunkTask, SharedMemorySegment, TerminateWorkerTask
|
||||
|
||||
from legendary.downloader.mp.manager import DLManager as DLManagerReal
|
||||
import legendary.downloader.mp.manager
|
||||
from .downloading import UIUpdate
|
||||
|
||||
|
||||
class DLManager(DLManagerReal):
|
||||
class DLManager(legendary.downloader.mp.manager.DLManager):
|
||||
# fmt: off
|
||||
@staticmethod
|
||||
def run_real(self):
|
||||
self.shared_memory = SharedMemory(create=True, size=self.max_shared_memory)
|
||||
self.log.debug(f'Created shared memory of size: {self.shared_memory.size / 1024 / 1024:.02f} MiB')
|
||||
|
|
|
@ -8,24 +8,35 @@ and only ONCE!
|
|||
from argparse import Namespace
|
||||
from typing import Optional
|
||||
|
||||
from rare.lgndr.cli import LegendaryCLI
|
||||
from rare.lgndr.core import LegendaryCore
|
||||
|
||||
from rare.models.apiresults import ApiResults
|
||||
from rare.models.signals import GlobalSignals
|
||||
|
||||
_legendary_cli_signleton: Optional[LegendaryCLI] = None
|
||||
_legendary_core_singleton: Optional[LegendaryCore] = None
|
||||
_global_signals_singleton: Optional[GlobalSignals] = None
|
||||
_arguments_singleton: Optional[Namespace] = None
|
||||
_api_results_singleton: Optional[ApiResults] = None
|
||||
|
||||
|
||||
def LegendaryCLISingleton(init: bool = False) -> LegendaryCLI:
|
||||
global _legendary_cli_signleton
|
||||
if _legendary_cli_signleton is None and not init:
|
||||
raise RuntimeError("Uninitialized use of LegendaryCLISingleton")
|
||||
if _legendary_cli_signleton is None:
|
||||
_legendary_cli_signleton = LegendaryCLI()
|
||||
return _legendary_cli_signleton
|
||||
|
||||
|
||||
def LegendaryCoreSingleton(init: bool = False) -> LegendaryCore:
|
||||
global _legendary_core_singleton
|
||||
if _legendary_core_singleton is None and not init:
|
||||
raise RuntimeError("Uninitialized use of LegendaryCoreSingleton")
|
||||
if _legendary_core_singleton is None:
|
||||
_legendary_core_singleton = LegendaryCore()
|
||||
return _legendary_core_singleton
|
||||
global _legendary_cli_signleton
|
||||
if _legendary_cli_signleton is None:
|
||||
raise RuntimeError("LegendaryCLI is not initialized yet")
|
||||
# if _legendary_cli_signleton is None:
|
||||
# _legendary_cli_signleton = LegendaryCLISingleton(init)
|
||||
return _legendary_cli_signleton.core
|
||||
|
||||
|
||||
def GlobalSignalsSingleton(init: bool = False) -> GlobalSignals:
|
||||
|
|
|
@ -1,14 +1,17 @@
|
|||
import os
|
||||
import platform
|
||||
from argparse import Namespace
|
||||
from logging import getLogger
|
||||
|
||||
from PyQt5.QtCore import pyqtSignal, QCoreApplication, QObject, QRunnable, QStandardPaths
|
||||
from legendary.core import LegendaryCore
|
||||
from legendary.models.game import VerifyResult
|
||||
from legendary.utils.lfs import validate_files
|
||||
from parse import parse
|
||||
|
||||
from rare.shared import LegendaryCoreSingleton
|
||||
from rare.shared import LegendaryCLISingleton, LegendaryCoreSingleton
|
||||
from rare.utils import config_helper
|
||||
from rare.lgndr.exception import LgndrException
|
||||
|
||||
logger = getLogger("Legendary Utils")
|
||||
|
||||
|
@ -83,75 +86,42 @@ def update_manifest(app_name: str, core: LegendaryCore):
|
|||
)
|
||||
|
||||
|
||||
class VerifySignals(QObject):
|
||||
status = pyqtSignal(int, int, str)
|
||||
summary = pyqtSignal(int, int, str)
|
||||
|
||||
|
||||
class VerifyWorker(QRunnable):
|
||||
class Signals(QObject):
|
||||
status = pyqtSignal(int, int, str)
|
||||
summary = pyqtSignal(int, int, str)
|
||||
|
||||
num: int = 0
|
||||
total: int = 1 # set default to 1 to avoid DivisionByZero before it is initialized
|
||||
|
||||
def __init__(self, app_name):
|
||||
super(VerifyWorker, self).__init__()
|
||||
self.signals = VerifySignals()
|
||||
self.signals = VerifyWorker.Signals()
|
||||
self.setAutoDelete(True)
|
||||
self.cli = LegendaryCLISingleton()
|
||||
self.core = LegendaryCoreSingleton()
|
||||
self.app_name = app_name
|
||||
|
||||
def status_callback(self, num: int, total: int, percentage: float, speed: float):
|
||||
self.signals.status.emit(num, total, self.app_name)
|
||||
|
||||
def run(self):
|
||||
if not self.core.is_installed(self.app_name):
|
||||
logger.error(f'Game "{self.app_name}" is not installed')
|
||||
return
|
||||
|
||||
logger.info(f'Loading installed manifest for "{self.app_name}"')
|
||||
igame = self.core.get_installed_game(self.app_name)
|
||||
manifest_data, _ = self.core.get_installed_manifest(self.app_name)
|
||||
manifest = self.core.load_manifest(manifest_data)
|
||||
|
||||
files = sorted(manifest.file_manifest_list.elements,
|
||||
key=lambda a: a.filename.lower())
|
||||
|
||||
# build list of hashes
|
||||
file_list = [(f.filename, f.sha_hash.hex()) for f in files]
|
||||
total = len(file_list)
|
||||
num = 0
|
||||
failed = []
|
||||
missing = []
|
||||
|
||||
logger.info(f'Verifying "{igame.title}" version "{manifest.meta.build_version}"')
|
||||
repair_file = []
|
||||
for result, path, result_hash, _ in validate_files(igame.install_path, file_list):
|
||||
num += 1
|
||||
self.signals.status.emit(num, total, self.app_name)
|
||||
|
||||
if result == VerifyResult.HASH_MATCH:
|
||||
repair_file.append(f"{result_hash}:{path}")
|
||||
continue
|
||||
elif result == VerifyResult.HASH_MISMATCH:
|
||||
logger.error(f'File does not match hash: "{path}"')
|
||||
repair_file.append(f"{result_hash}:{path}")
|
||||
failed.append(path)
|
||||
elif result == VerifyResult.FILE_MISSING:
|
||||
logger.error(f'File is missing: "{path}"')
|
||||
missing.append(path)
|
||||
args = Namespace(app_name=self.app_name,
|
||||
callback=self.status_callback)
|
||||
try:
|
||||
# TODO: offer this as an alternative when manifest doesn't exist
|
||||
# TODO: requires the client to be online. To do it this way, we need to
|
||||
# TODO: somehow detect the error and offer a dialog in which case `verify_games` is
|
||||
# TODO: re-run with `repair_mode` and `repair_online`
|
||||
self.cli.verify_game(args, print_command=False, repair_mode=True, repair_online=True)
|
||||
# self.cli.verify_game(args, print_command=False)
|
||||
self.signals.summary.emit(0, 0, self.app_name)
|
||||
except LgndrException as ret:
|
||||
r = parse('Verification failed, {:d} file(s) corrupted, {:d} file(s) are missing.', ret.message)
|
||||
if r is None:
|
||||
raise ret
|
||||
else:
|
||||
logger.error(f'Other failure (see log), treating file as missing: "{path}"')
|
||||
missing.append(path)
|
||||
|
||||
# always write repair file, even if all match
|
||||
if repair_file:
|
||||
repair_filename = os.path.join(self.core.lgd.get_tmp_path(), f'{self.app_name}.repair')
|
||||
with open(repair_filename, 'w') as f:
|
||||
f.write('\n'.join(repair_file))
|
||||
logger.debug(f'Written repair file to "{repair_filename}"')
|
||||
|
||||
if not missing and not failed:
|
||||
logger.info('Verification finished successfully.')
|
||||
else:
|
||||
logger.warning(
|
||||
f'Verification failed, {len(failed)} file(s) corrupted, {len(missing)} file(s) are missing.')
|
||||
self.signals.summary.emit(len(failed), len(missing), self.app_name)
|
||||
self.signals.summary.emit(r[0], r[1], self.app_name)
|
||||
|
||||
|
||||
# FIXME: lk: ah ef me sideways, we can't even import this thing properly
|
||||
|
|
Loading…
Reference in a new issue