Lgndr: Add re-implemented uinstall_game
Lgndr: Change the exception level to CRITICAL for core LegendaryUtils: Use uninstall_game from our Lgndr UninstallDialog: Update to return a tuple of values App: Keep files if the install directory was lost App: Run legendary's exit procedures on exit
This commit is contained in:
parent
8bbb42a045
commit
bbaff5f42c
7 changed files with 111 additions and 70 deletions
|
@ -151,7 +151,9 @@ class App(RareApp):
|
|||
def start_app(self):
|
||||
for igame in self.core.get_installed_list():
|
||||
if not os.path.exists(igame.install_path):
|
||||
legendary_utils.uninstall(igame.app_name, self.core)
|
||||
# lk; since install_path is lost anyway, set keep_files to True
|
||||
# lk: to avoid spamming the log with "file not found" errors
|
||||
legendary_utils.uninstall_game(self.core, igame.app_name, keep_files=True)
|
||||
logger.info(f"Uninstalled {igame.title}, because no game files exist")
|
||||
continue
|
||||
if not os.path.exists(os.path.join(igame.install_path, igame.executable.replace("\\", "/").lstrip("/"))):
|
||||
|
@ -228,6 +230,7 @@ class App(RareApp):
|
|||
self.mainwindow.hide()
|
||||
threadpool = QThreadPool.globalInstance()
|
||||
threadpool.waitForDone()
|
||||
self.core.exit()
|
||||
if self.mainwindow is not None:
|
||||
self.mainwindow.close()
|
||||
if self.tray_icon is not None:
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
from enum import Enum, IntEnum
|
||||
from typing import Tuple
|
||||
|
||||
from PyQt5.QtCore import Qt
|
||||
from PyQt5.QtWidgets import (
|
||||
QDialog,
|
||||
|
@ -16,21 +19,20 @@ from rare.utils.utils import icon
|
|||
class UninstallDialog(QDialog):
|
||||
def __init__(self, game: Game):
|
||||
super(UninstallDialog, self).__init__()
|
||||
self.setWindowTitle("Uninstall Game")
|
||||
self.info = 0
|
||||
self.setAttribute(Qt.WA_DeleteOnClose, True)
|
||||
self.layout = QVBoxLayout()
|
||||
self.setWindowTitle("Uninstall Game")
|
||||
layout = QVBoxLayout()
|
||||
self.info_text = QLabel(
|
||||
self.tr("Do you really want to uninstall {}").format(game.app_title)
|
||||
)
|
||||
self.layout.addWidget(self.info_text)
|
||||
layout.addWidget(self.info_text)
|
||||
self.keep_files = QCheckBox(self.tr("Keep Files"))
|
||||
self.form = QFormLayout()
|
||||
self.form.setContentsMargins(0, 10, 0, 10)
|
||||
self.form.addRow(QLabel(self.tr("Do you want to keep files?")), self.keep_files)
|
||||
self.layout.addLayout(self.form)
|
||||
form_layout = QFormLayout()
|
||||
form_layout.setContentsMargins(0, 10, 0, 10)
|
||||
form_layout.addRow(QLabel(self.tr("Do you want to keep files?")), self.keep_files)
|
||||
layout.addLayout(form_layout)
|
||||
|
||||
self.button_layout = QHBoxLayout()
|
||||
button_layout = QHBoxLayout()
|
||||
self.ok_button = QPushButton(
|
||||
icon("ei.remove-circle", color="red"), self.tr("Uninstall")
|
||||
)
|
||||
|
@ -39,20 +41,22 @@ class UninstallDialog(QDialog):
|
|||
self.cancel_button = QPushButton(self.tr("Cancel"))
|
||||
self.cancel_button.clicked.connect(self.cancel)
|
||||
|
||||
self.button_layout.addStretch(1)
|
||||
self.button_layout.addWidget(self.ok_button)
|
||||
self.button_layout.addWidget(self.cancel_button)
|
||||
self.layout.addLayout(self.button_layout)
|
||||
self.setLayout(self.layout)
|
||||
button_layout.addStretch(1)
|
||||
button_layout.addWidget(self.ok_button)
|
||||
button_layout.addWidget(self.cancel_button)
|
||||
layout.addLayout(button_layout)
|
||||
self.setLayout(layout)
|
||||
|
||||
def get_information(self):
|
||||
self.options: Tuple[bool, bool] = (False, False)
|
||||
|
||||
def get_options(self) -> Tuple[bool, bool]:
|
||||
self.exec_()
|
||||
return self.info
|
||||
return self.options
|
||||
|
||||
def ok(self):
|
||||
self.info = {"keep_files": self.keep_files.isChecked()}
|
||||
self.options = (True, self.keep_files.isChecked())
|
||||
self.close()
|
||||
|
||||
def cancel(self):
|
||||
self.info = 0
|
||||
self.options = (False, False)
|
||||
self.close()
|
||||
|
|
|
@ -164,10 +164,10 @@ class GameUtils(QObject):
|
|||
else:
|
||||
return False
|
||||
|
||||
infos = UninstallDialog(game).get_information()
|
||||
if infos == 0:
|
||||
proceed, keep_files = UninstallDialog(game).get_options()
|
||||
if not proceed:
|
||||
return False
|
||||
legendary_utils.uninstall(game.app_name, self.core, infos)
|
||||
legendary_utils.uninstall_game(self.core, game.app_name, keep_files)
|
||||
self.signals.game_uninstalled.emit(app_name)
|
||||
return True
|
||||
|
||||
|
|
|
@ -14,10 +14,19 @@ class LgndrWarning(RuntimeWarning):
|
|||
super(LgndrWarning, self).__init__(self.message)
|
||||
|
||||
|
||||
class LgndrLogHandler(logging.Handler):
|
||||
class LgndrCLILogHandler(logging.Handler):
|
||||
def emit(self, record: logging.LogRecord) -> None:
|
||||
# lk: FATAL is the same as CRITICAL
|
||||
if record.levelno == logging.ERROR or record.levelno == logging.CRITICAL:
|
||||
raise LgndrException(record.getMessage())
|
||||
# if record.levelno == logging.INFO or record.levelno == logging.WARNING:
|
||||
# if record.levelno < logging.ERROR or record.levelno == logging.WARNING:
|
||||
# warnings.warn(record.getMessage())
|
||||
|
||||
|
||||
class LgndrCoreLogHandler(logging.Handler):
|
||||
def emit(self, record: logging.LogRecord) -> None:
|
||||
# lk: FATAL is the same as CRITICAL
|
||||
if record.levelno == logging.CRITICAL:
|
||||
raise LgndrException(record.getMessage())
|
||||
# if record.levelno < logging.CRITICAL:
|
||||
# warnings.warn(record.getMessage())
|
||||
|
|
|
@ -13,24 +13,17 @@ from legendary.utils.selective_dl import get_sdl_appname
|
|||
|
||||
from .core import LegendaryCore
|
||||
from .manager import DLManager
|
||||
from .api_arguments import LgndrInstallGameArgs, LgndrImportGameArgs, LgndrVerifyGameArgs
|
||||
from .api_exception import LgndrException, LgndrLogHandler
|
||||
from .api_arguments import LgndrInstallGameArgs, LgndrImportGameArgs, LgndrVerifyGameArgs, LgndrUninstallGameArgs
|
||||
from .api_exception import LgndrException, LgndrCLILogHandler
|
||||
from .api_monkeys import return_exit as exit, get_boolean_choice
|
||||
|
||||
legendary.cli.exit = exit
|
||||
|
||||
|
||||
class LegendaryCLI(legendary.cli.LegendaryCLI):
|
||||
def __init__(self, override_config=None, api_timeout=None):
|
||||
self.core = LegendaryCore(override_config, timeout=api_timeout)
|
||||
self.logger = logging.getLogger('cli')
|
||||
self.logging_queue = None
|
||||
self.handler = LgndrLogHandler()
|
||||
self.logger.addHandler(self.handler)
|
||||
|
||||
def resolve_aliases(self, name):
|
||||
return super(LegendaryCLI, self)._resolve_aliases(name)
|
||||
|
||||
@staticmethod
|
||||
def wrapped(func):
|
||||
def apply_wrap(func):
|
||||
|
||||
@functools.wraps(func)
|
||||
def inner(self, args, *oargs, **kwargs):
|
||||
|
@ -39,6 +32,7 @@ class LegendaryCLI(legendary.cli.LegendaryCLI):
|
|||
|
||||
old_choice = legendary.cli.get_boolean_choice
|
||||
if hasattr(args, 'get_boolean_choice') and args.get_boolean_choice is not None:
|
||||
# g['get_boolean_choice'] = args.get_boolean_choice
|
||||
legendary.cli.get_boolean_choice = args.get_boolean_choice
|
||||
|
||||
try:
|
||||
|
@ -46,12 +40,22 @@ class LegendaryCLI(legendary.cli.LegendaryCLI):
|
|||
except LgndrException as ret:
|
||||
raise ret
|
||||
finally:
|
||||
# g['get_boolean_choice'] = old_choice
|
||||
legendary.cli.get_boolean_choice = old_choice
|
||||
legendary.cli.exit = old_exit
|
||||
|
||||
return inner
|
||||
|
||||
@wrapped
|
||||
def __init__(self, override_config=None, api_timeout=None):
|
||||
self.core = LegendaryCore(override_config, timeout=api_timeout)
|
||||
self.logger = logging.getLogger('cli')
|
||||
self.logging_queue = None
|
||||
self.handler = LgndrCLILogHandler()
|
||||
self.logger.addHandler(self.handler)
|
||||
|
||||
def resolve_aliases(self, name):
|
||||
return super(LegendaryCLI, self)._resolve_aliases(name)
|
||||
|
||||
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):
|
||||
|
@ -222,15 +226,39 @@ class LegendaryCLI(legendary.cli.LegendaryCLI):
|
|||
self.core.uninstall_tag(old_igame)
|
||||
self.core.install_game(old_igame)
|
||||
|
||||
@wrapped
|
||||
@apply_wrap
|
||||
def handle_postinstall(self, postinstall, igame, yes=False):
|
||||
super(LegendaryCLI, self)._handle_postinstall(postinstall, igame, yes)
|
||||
|
||||
@wrapped
|
||||
def uninstall_game(self, args):
|
||||
super(LegendaryCLI, self).uninstall_game(args)
|
||||
def uninstall_game(self, args: LgndrUninstallGameArgs):
|
||||
args.app_name = self._resolve_aliases(args.app_name)
|
||||
igame = self.core.get_installed_game(args.app_name)
|
||||
if not igame:
|
||||
logger.error(f'Game {args.app_name} not installed, cannot uninstall!')
|
||||
exit(0)
|
||||
|
||||
if not args.yes:
|
||||
if not get_boolean_choice(f'Do you wish to uninstall "{igame.title}"?', default=False):
|
||||
return False
|
||||
|
||||
try:
|
||||
if not igame.is_dlc:
|
||||
# Remove DLC first so directory is empty when game uninstall runs
|
||||
dlcs = self.core.get_dlc_for_game(igame.app_name)
|
||||
for dlc in dlcs:
|
||||
if (idlc := self.core.get_installed_game(dlc.app_name)) is not None:
|
||||
logger.info(f'Uninstalling DLC "{dlc.app_name}"...')
|
||||
self.core.uninstall_game(idlc, delete_files=not args.keep_files)
|
||||
|
||||
logger.info(f'Removing "{igame.title}" from "{igame.install_path}"...')
|
||||
self.core.uninstall_game(igame, delete_files=not args.keep_files,
|
||||
delete_root_directory=not igame.is_dlc)
|
||||
logger.info('Game has been uninstalled.')
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.warning(f'Removing game failed: {e!r}, please remove {igame.install_path} manually.')
|
||||
return False
|
||||
|
||||
@wrapped
|
||||
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):
|
||||
|
@ -340,6 +368,6 @@ class LegendaryCLI(legendary.cli.LegendaryCLI):
|
|||
logger.info(f'Run "legendary repair {args.app_name}" to repair your game installation.')
|
||||
return False, len(failed), len(missing)
|
||||
|
||||
@wrapped
|
||||
@apply_wrap
|
||||
def import_game(self, args: LgndrImportGameArgs):
|
||||
super(LegendaryCLI, self).import_game(args)
|
||||
|
|
|
@ -2,10 +2,10 @@ from multiprocessing import Queue
|
|||
|
||||
import legendary.core
|
||||
from legendary.models.downloading import AnalysisResult
|
||||
from legendary.models.game import Game
|
||||
from legendary.models.game import Game, InstalledGame
|
||||
from legendary.models.manifest import ManifestMeta
|
||||
|
||||
from .api_exception import LgndrException, LgndrLogHandler
|
||||
from .api_exception import LgndrException, LgndrCoreLogHandler
|
||||
from .manager import DLManager
|
||||
|
||||
# lk: Monkeypatch the modified DLManager into LegendaryCore
|
||||
|
@ -16,7 +16,7 @@ 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.handler = LgndrCoreLogHandler()
|
||||
self.log.addHandler(self.handler)
|
||||
|
||||
def prepare_download(self, game: Game, base_game: Game = None, base_path: str = '',
|
||||
|
@ -31,7 +31,7 @@ class LegendaryCore(legendary.core.LegendaryCore):
|
|||
disable_delta: bool = False, override_delta_manifest: str = '',
|
||||
egl_guid: str = '', preferred_cdn: str = None,
|
||||
disable_https: bool = False) -> (DLManager, AnalysisResult, ManifestMeta):
|
||||
dlm, analysis, igame = super(LegendaryCore, self).prepare_download(
|
||||
return 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,
|
||||
|
@ -45,7 +45,14 @@ class LegendaryCore(legendary.core.LegendaryCore):
|
|||
egl_guid=egl_guid, preferred_cdn=preferred_cdn,
|
||||
disable_https=disable_https
|
||||
)
|
||||
return dlm, analysis, igame
|
||||
|
||||
def uninstall_game(self, installed_game: InstalledGame, delete_files=True, delete_root_directory=False):
|
||||
try:
|
||||
super(LegendaryCore, self).uninstall_game(installed_game, delete_files, delete_root_directory)
|
||||
except Exception as e:
|
||||
raise e
|
||||
finally:
|
||||
pass
|
||||
|
||||
def egl_import(self, app_name):
|
||||
try:
|
||||
|
@ -68,3 +75,4 @@ class LegendaryCore(legendary.core.LegendaryCore):
|
|||
# lk: monkeypatch status_q (the queue for download stats)
|
||||
dlm.status_queue = status_q
|
||||
return dlm, analysis_result, igame
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@ from logging import getLogger
|
|||
from PyQt5.QtCore import pyqtSignal, QCoreApplication, QObject, QRunnable, QStandardPaths
|
||||
from legendary.core import LegendaryCore
|
||||
|
||||
from rare.lgndr.api_arguments import LgndrVerifyGameArgs
|
||||
from rare.lgndr.api_arguments import LgndrVerifyGameArgs, LgndrUninstallGameArgs
|
||||
from rare.lgndr.api_exception import LgndrException
|
||||
from rare.shared import LegendaryCLISingleton, LegendaryCoreSingleton
|
||||
from rare.utils import config_helper
|
||||
|
@ -13,9 +13,7 @@ from rare.utils import config_helper
|
|||
logger = getLogger("Legendary Utils")
|
||||
|
||||
|
||||
def uninstall(app_name: str, core: LegendaryCore, options=None):
|
||||
if not options:
|
||||
options = {"keep_files": False}
|
||||
def uninstall_game(core: LegendaryCore, app_name: str, keep_files=False):
|
||||
igame = core.get_installed_game(app_name)
|
||||
|
||||
# remove shortcuts link
|
||||
|
@ -40,31 +38,22 @@ def uninstall(app_name: str, core: LegendaryCore, options=None):
|
|||
if os.path.exists(start_menu_shortcut):
|
||||
os.remove(start_menu_shortcut)
|
||||
|
||||
try:
|
||||
# Remove DLC first so directory is empty when game uninstall runs
|
||||
dlcs = core.get_dlc_for_game(app_name)
|
||||
for dlc in dlcs:
|
||||
if (idlc := core.get_installed_game(dlc.app_name)) is not None:
|
||||
logger.info(f'Uninstalling DLC "{dlc.app_name}"...')
|
||||
core.uninstall_game(idlc, delete_files=not options["keep_files"])
|
||||
|
||||
logger.info(f'Removing "{igame.title}" from "{igame.install_path}"...')
|
||||
core.uninstall_game(
|
||||
igame, delete_files=not options["keep_files"], delete_root_directory=True
|
||||
result = LegendaryCLISingleton().uninstall_game(
|
||||
LgndrUninstallGameArgs(
|
||||
app_name=app_name,
|
||||
keep_files=keep_files,
|
||||
yes=True,
|
||||
)
|
||||
logger.info("Game has been uninstalled.")
|
||||
|
||||
except Exception as e:
|
||||
logger.warning(
|
||||
f"Removing game failed: {e!r}, please remove {igame.install_path} manually."
|
||||
)
|
||||
if not options["keep_files"]:
|
||||
)
|
||||
if not keep_files:
|
||||
logger.info("Removing sections in config file")
|
||||
config_helper.remove_section(app_name)
|
||||
config_helper.remove_section(f"{app_name}.env")
|
||||
|
||||
config_helper.save_config()
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def update_manifest(app_name: str, core: LegendaryCore):
|
||||
game = core.get_game(app_name)
|
||||
|
|
Loading…
Reference in a new issue