1
0
Fork 0
mirror of synced 2024-06-23 08:40:45 +12:00

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:
loathingKernel 2022-07-15 21:16:12 +03:00
parent 8bbb42a045
commit bbaff5f42c
7 changed files with 111 additions and 70 deletions

View file

@ -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:

View file

@ -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()

View file

@ -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

View file

@ -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())

View file

@ -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)

View file

@ -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

View file

@ -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)