Lgndr: Create argument models for consistent function arguments and cleaner implementation.
Lgndr: Prefix files that aren't part of legendary with 'api_' Lgndr: Return statistics from 'verify_game' instead of parsing the exception VerifyWorker: Add 'error' signal for exceptions.
This commit is contained in:
parent
d7aba41aa4
commit
3aae3887f6
|
@ -8,6 +8,7 @@ from PyQt5.QtCore import Qt, QObject, QRunnable, QThreadPool, pyqtSignal, pyqtSl
|
|||
from PyQt5.QtGui import QCloseEvent, QKeyEvent
|
||||
from PyQt5.QtWidgets import QDialog, QFileDialog, QCheckBox
|
||||
|
||||
from rare.lgndr.api_arguments import LgndrInstallGameArgs
|
||||
from rare.lgndr.cli import LegendaryCLI
|
||||
from rare.lgndr.core import LegendaryCore
|
||||
from legendary.models.downloading import ConditionCheckResult
|
||||
|
@ -311,7 +312,7 @@ class InstallInfoWorker(QRunnable):
|
|||
cli = LegendaryCLISingleton()
|
||||
download = InstallDownloadModel(
|
||||
# *self.core.prepare_download(
|
||||
*cli.prepare_install(
|
||||
*cli.prepare_install(LgndrInstallGameArgs(
|
||||
app_name=self.dl_item.options.app_name,
|
||||
base_path=self.dl_item.options.base_path,
|
||||
force=self.dl_item.options.force,
|
||||
|
@ -336,7 +337,7 @@ class InstallInfoWorker(QRunnable):
|
|||
# disable_delta=,
|
||||
# override_delta_manifest=,
|
||||
# reset_sdl=,
|
||||
sdl_prompt=lambda app_name, title: self.dl_item.options.sdl_list,
|
||||
sdl_prompt=lambda app_name, title: self.dl_item.options.sdl_list,)
|
||||
)
|
||||
)
|
||||
|
||||
|
|
|
@ -143,20 +143,27 @@ class GameInfo(QWidget, Ui_GameInfo):
|
|||
return
|
||||
self.verify_widget.setCurrentIndex(1)
|
||||
verify_worker = VerifyWorker(self.game.app_name)
|
||||
verify_worker.signals.status.connect(self.verify_statistics)
|
||||
verify_worker.signals.summary.connect(self.finish_verify)
|
||||
verify_worker.signals.status.connect(self.verify_status)
|
||||
verify_worker.signals.result.connect(self.verify_result)
|
||||
verify_worker.signals.error.connect(self.verify_error)
|
||||
self.verify_progress.setValue(0)
|
||||
self.verify_threads[self.game.app_name] = verify_worker
|
||||
self.verify_pool.start(verify_worker)
|
||||
self.move_button.setEnabled(False)
|
||||
|
||||
def verify_statistics(self, num, total, app_name):
|
||||
@pyqtSlot(str, str)
|
||||
def verify_error(self, app_name, message):
|
||||
pass
|
||||
|
||||
@pyqtSlot(str, int, int, float, float)
|
||||
def verify_status(self, app_name, num, total, percentage, speed):
|
||||
# checked, max, app_name
|
||||
if app_name == self.game.app_name:
|
||||
self.verify_progress.setValue(num * 100 // total)
|
||||
|
||||
def finish_verify(self, failed, missing, app_name):
|
||||
if failed == missing == 0:
|
||||
@pyqtSlot(str, bool, int, int)
|
||||
def verify_result(self, app_name, success, failed, missing):
|
||||
if success:
|
||||
QMessageBox.information(
|
||||
self,
|
||||
"Summary",
|
||||
|
|
|
@ -6,7 +6,7 @@ from typing import Tuple, Iterable, List
|
|||
from PyQt5.QtCore import Qt, QThreadPool, QRunnable, pyqtSlot, pyqtSignal
|
||||
from PyQt5.QtWidgets import QGroupBox, QListWidgetItem, QFileDialog, QMessageBox
|
||||
|
||||
from rare.lgndr.exception import LgndrException
|
||||
from rare.lgndr.api_exception import LgndrException
|
||||
from rare.shared import LegendaryCoreSingleton, GlobalSignalsSingleton
|
||||
from rare.ui.components.tabs.games.import_sync.egl_sync_group import Ui_EGLSyncGroup
|
||||
from rare.ui.components.tabs.games.import_sync.egl_sync_list_group import (
|
||||
|
|
|
@ -11,7 +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.exception import LgndrException
|
||||
from lgndr.api_arguments import LgndrImportGameArgs
|
||||
from rare.lgndr.api_exception import LgndrException
|
||||
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
|
||||
|
@ -105,23 +106,12 @@ class ImportWorker(QRunnable):
|
|||
# else:
|
||||
# return err
|
||||
|
||||
# TODO: This should be moved into RareCore and wrap import_game
|
||||
def import_game_args(self, app_path: str, app_name: str, platfrom: str = "Windows",
|
||||
disable_check: bool = False, skip_dlcs: bool = False, with_dlcs: bool = False, yes: bool = False):
|
||||
args = Namespace(
|
||||
app_path=app_path,
|
||||
app_name=app_name,
|
||||
platform=platfrom,
|
||||
disable_check=disable_check,
|
||||
skip_dlcs=skip_dlcs,
|
||||
with_dlcs=with_dlcs,
|
||||
yes=yes,
|
||||
)
|
||||
return args
|
||||
|
||||
def __import_game(self, app_name: str, path: Path):
|
||||
cli = LegendaryCLISingleton()
|
||||
args = self.import_game_args(str(path), app_name)
|
||||
args = LgndrImportGameArgs(
|
||||
app_path=str(path),
|
||||
app_name=app_name,
|
||||
)
|
||||
try:
|
||||
cli.import_game(args)
|
||||
return ""
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
"""
|
||||
Module that overloads and monkeypatches legendary's classes/methods to work with Rare
|
||||
|
||||
Files with the 'api_' prefix are not part of legendary's source, and contain facilities relating to Rare.
|
||||
"""
|
71
rare/lgndr/api_arguments.py
Normal file
71
rare/lgndr/api_arguments.py
Normal file
|
@ -0,0 +1,71 @@
|
|||
from dataclasses import dataclass
|
||||
from multiprocessing import Queue
|
||||
from typing import Callable, List
|
||||
|
||||
|
||||
@dataclass(kw_only=True)
|
||||
class LgndrCommonArgs:
|
||||
# keep this here for future reference
|
||||
# when we move to 3.10 we can use 'kw_only' to do dataclass inheritance
|
||||
app_name: str
|
||||
platform: str = "Windows"
|
||||
yes: bool = False
|
||||
|
||||
|
||||
@dataclass
|
||||
class LgndrImportGameArgs:
|
||||
app_path: str
|
||||
app_name: str
|
||||
platform: str = "Windows"
|
||||
disable_check: bool = False
|
||||
skip_dlcs: bool = False
|
||||
with_dlcs: bool = False
|
||||
yes: bool = False
|
||||
|
||||
|
||||
@dataclass
|
||||
class LgndrVerifyGameArgs:
|
||||
app_name: str
|
||||
# Rare's extra arguments
|
||||
verify_stdout: Callable[[int, int, float, float], None] = lambda a0, a1, a2, a3: print(
|
||||
f"Verification progress: {a0}/{a1} ({a2:.01f}%) [{a3:.1f} MiB/s]\t\r"
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class LgndrInstallGameArgs:
|
||||
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
|
||||
disable_https: bool = False
|
||||
yes: bool = True
|
||||
# Rare's extra arguments
|
||||
sdl_prompt: Callable[[str, str], List[str]] = lambda a0, a1: []
|
||||
verify_stdout: Callable[[int, int, float, float], None] = lambda a0, a1, a2, a3: print(
|
||||
f"Verification progress: {a0}/{a1} ({a2:.01f}%) [{a3:.1f} MiB/s]\t\r"
|
||||
)
|
|
@ -1,9 +1,7 @@
|
|||
import os
|
||||
import logging
|
||||
import time
|
||||
from argparse import Namespace
|
||||
from multiprocessing import Queue
|
||||
from typing import Optional, Callable, List
|
||||
from typing import Optional, Union
|
||||
|
||||
import legendary.cli
|
||||
from PyQt5.QtWidgets import QLabel, QMessageBox
|
||||
|
@ -14,8 +12,9 @@ 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
|
||||
from .api_arguments import LgndrInstallGameArgs, LgndrImportGameArgs, LgndrVerifyGameArgs
|
||||
from .api_exception import LgndrException, LgndrLogHandler
|
||||
|
||||
|
||||
def get_boolean_choice(message):
|
||||
|
@ -40,49 +39,7 @@ class LegendaryCLI(legendary.cli.LegendaryCLI):
|
|||
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
|
||||
)
|
||||
def prepare_install(self, args: LgndrInstallGameArgs) -> (DLManager, AnalysisResult, InstalledGame, Game, bool, Optional[str], ConditionCheckResult):
|
||||
old_choice = legendary.cli.get_boolean_choice
|
||||
legendary.cli.get_boolean_choice = get_boolean_choice
|
||||
try:
|
||||
|
@ -92,7 +49,7 @@ class LegendaryCLI(legendary.cli.LegendaryCLI):
|
|||
finally:
|
||||
legendary.cli.get_boolean_choice = old_choice
|
||||
|
||||
def install_game(self, args):
|
||||
def install_game(self, args: LgndrInstallGameArgs) -> (DLManager, AnalysisResult, InstalledGame, Game, bool, Optional[str], ConditionCheckResult):
|
||||
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)
|
||||
|
@ -267,10 +224,7 @@ class LegendaryCLI(legendary.cli.LegendaryCLI):
|
|||
def uninstall_game(self, args):
|
||||
super(LegendaryCLI, self).uninstall_game(args)
|
||||
|
||||
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
|
||||
|
||||
def verify_game(self, args: Union[LgndrVerifyGameArgs, LgndrInstallGameArgs], print_command=True, repair_mode=False, repair_online=False):
|
||||
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')
|
||||
|
@ -339,8 +293,8 @@ class LegendaryCLI(legendary.cli.LegendaryCLI):
|
|||
speed = (processed - last_processed) / 1024 / 1024 / delta
|
||||
last_processed = processed
|
||||
|
||||
if args.callback:
|
||||
args.callback(num, total, percentage, speed)
|
||||
if args.verify_stdout:
|
||||
args.verify_stdout(num, total, percentage, speed)
|
||||
|
||||
if result == VerifyResult.HASH_MATCH:
|
||||
repair_file.append(f'{result_hash}:{path}')
|
||||
|
@ -356,8 +310,8 @@ class LegendaryCLI(legendary.cli.LegendaryCLI):
|
|||
logger.info(f'Other failure (see log), treating file as missing: "{path}"')
|
||||
missing.append(path)
|
||||
|
||||
if args.callback:
|
||||
args.callback(num, total, percentage, speed)
|
||||
if args.verify_stdout:
|
||||
args.verify_stdout(num, total, percentage, speed)
|
||||
|
||||
# always write repair file, even if all match
|
||||
if repair_file:
|
||||
|
@ -368,12 +322,14 @@ class LegendaryCLI(legendary.cli.LegendaryCLI):
|
|||
|
||||
if not missing and not failed:
|
||||
logger.info('Verification finished successfully.')
|
||||
return True, 0, 0
|
||||
else:
|
||||
logger.error(f'Verification failed, {len(failed)} file(s) corrupted, {len(missing)} file(s) are missing.')
|
||||
logger.warning(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.')
|
||||
return False, len(failed), len(missing)
|
||||
|
||||
def import_game(self, args):
|
||||
def import_game(self, args: LgndrImportGameArgs):
|
||||
old_choice = legendary.cli.get_boolean_choice
|
||||
legendary.cli.get_boolean_choice = get_boolean_choice
|
||||
try:
|
||||
|
|
|
@ -5,7 +5,7 @@ from legendary.models.downloading import AnalysisResult
|
|||
from legendary.models.game import Game
|
||||
from legendary.models.manifest import ManifestMeta
|
||||
|
||||
from .exception import LgndrException, LgndrLogHandler
|
||||
from .api_exception import LgndrException, LgndrLogHandler
|
||||
from .manager import DLManager
|
||||
|
||||
|
||||
|
|
|
@ -1,17 +1,14 @@
|
|||
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.lgndr.api_arguments import LgndrVerifyGameArgs
|
||||
from rare.lgndr.api_exception import LgndrException
|
||||
from rare.shared import LegendaryCLISingleton, LegendaryCoreSingleton
|
||||
from rare.utils import config_helper
|
||||
from rare.lgndr.exception import LgndrException
|
||||
|
||||
logger = getLogger("Legendary Utils")
|
||||
|
||||
|
@ -88,8 +85,9 @@ def update_manifest(app_name: str, core: LegendaryCore):
|
|||
|
||||
class VerifyWorker(QRunnable):
|
||||
class Signals(QObject):
|
||||
status = pyqtSignal(int, int, str)
|
||||
summary = pyqtSignal(int, int, str)
|
||||
status = pyqtSignal(str, int, int, float, float)
|
||||
result = pyqtSignal(str, bool, int, int)
|
||||
error = pyqtSignal(str, str)
|
||||
|
||||
num: int = 0
|
||||
total: int = 1 # set default to 1 to avoid DivisionByZero before it is initialized
|
||||
|
@ -103,25 +101,22 @@ class VerifyWorker(QRunnable):
|
|||
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)
|
||||
self.signals.status.emit(self.app_name, num, total, percentage, speed)
|
||||
|
||||
def run(self):
|
||||
args = Namespace(app_name=self.app_name,
|
||||
callback=self.status_callback)
|
||||
args = LgndrVerifyGameArgs(app_name=self.app_name,
|
||||
verify_stdout=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)
|
||||
success, failed, missing = self.cli.verify_game(
|
||||
args, print_command=False, repair_mode=True, repair_online=True)
|
||||
# success, failed, missing = self.cli.verify_game(args, print_command=False)
|
||||
self.signals.result.emit(self.app_name, success, failed, missing)
|
||||
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:
|
||||
self.signals.summary.emit(r[0], r[1], self.app_name)
|
||||
self.signals.error.emit(self.app_name, ret.message)
|
||||
|
||||
|
||||
# FIXME: lk: ah ef me sideways, we can't even import this thing properly
|
||||
|
|
Loading…
Reference in a new issue