From fc7e45a43aacea258848bcbf318c0be006f26ecb Mon Sep 17 00:00:00 2001 From: loathingKernel <142770+loathingKernel@users.noreply.github.com> Date: Wed, 25 Jan 2023 12:59:00 +0200 Subject: [PATCH] UninstallDialog: Implement it to work similarly to `InstallDialog` Similarly to the installation procedure, when an uninstall is requested, an `UninstallOptionsModel` is emitted by the `RareGame`. `DownloadsTab` handles the signal and spawns the `UninstallDialog`. After the `UninstallDialog` is closed, a worker thread handles uninstalling the application to avoid UI lock-ups when a large number of files is deleted. Allows for uninstall actions to be spawned from anything having access to the `RareGame` instance. LaunchDialog: Don't check health on DLCs, they always will require verification if they don't specify an executable. Signed-off-by: loathingKernel <142770+loathingKernel@users.noreply.github.com> --- rare/components/dialogs/launch_dialog.py | 7 +- rare/components/dialogs/uninstall_dialog.py | 75 +++++---- rare/components/main_window.py | 2 +- rare/components/tabs/downloads/__init__.py | 143 +++++++++++------- rare/components/tabs/games/__init__.py | 4 +- .../tabs/games/game_info/game_info.py | 33 +--- rare/models/game.py | 9 +- rare/models/install.py | 28 +++- rare/models/signals.py | 3 +- rare/shared/game_utils.py | 52 ------- rare/shared/rare_core.py | 1 + rare/shared/workers/uninstall.py | 82 ++++++++++ 12 files changed, 260 insertions(+), 179 deletions(-) create mode 100644 rare/shared/workers/uninstall.py diff --git a/rare/components/dialogs/launch_dialog.py b/rare/components/dialogs/launch_dialog.py index 32de3711..4d632954 100644 --- a/rare/components/dialogs/launch_dialog.py +++ b/rare/components/dialogs/launch_dialog.py @@ -10,7 +10,7 @@ from requests.exceptions import ConnectionError, HTTPError from rare.components.dialogs.login import LoginDialog from rare.models.apiresults import ApiResults from rare.shared import LegendaryCoreSingleton, ArgumentsSingleton, ApiResultsSingleton, ImageManagerSingleton -from rare.shared.game_utils import uninstall_game +from rare.shared.workers.uninstall import uninstall_game from rare.ui.components.dialogs.launch_dialog import Ui_LaunchDialog from rare.utils.misc import CloudWorker from rare.widgets.elide_label import ElideLabel @@ -92,6 +92,10 @@ class ImageWorker(LaunchWorker): # FIXME: incorporate installed game status checking here for now, still slow for i, igame in enumerate(igame_list): + # lk: do not check dlcs, they do not specify an executable + if igame.is_dlc: + continue + self.signals.progress.emit( int(i / len(igame_list) * 25) + 75, self.tr("Validating install for {}").format(igame.title) @@ -102,6 +106,7 @@ class ImageWorker(LaunchWorker): uninstall_game(self.core, igame.app_name, keep_files=True) logger.info(f"Uninstalled {igame.title}, because no game files exist") continue + # lk: games that don't have an override and can't find their executable due to case sensitivity # lk: will still erroneously require verification. This might need to be removed completely # lk: or be decoupled from the verification requirement diff --git a/rare/components/dialogs/uninstall_dialog.py b/rare/components/dialogs/uninstall_dialog.py index 0fcc5ca8..e866cfaa 100644 --- a/rare/components/dialogs/uninstall_dialog.py +++ b/rare/components/dialogs/uninstall_dialog.py @@ -1,6 +1,7 @@ from typing import Tuple -from PyQt5.QtCore import Qt +from PyQt5.QtCore import Qt, pyqtSignal +from PyQt5.QtGui import QCloseEvent from PyQt5.QtWidgets import ( QDialog, QLabel, @@ -8,62 +9,70 @@ from PyQt5.QtWidgets import ( QCheckBox, QHBoxLayout, QPushButton, + QApplication, ) - -from legendary.models.game import Game from legendary.utils.selective_dl import get_sdl_appname +from rare.models.game import RareGame +from rare.models.install import UninstallOptionsModel from rare.utils.misc import icon class UninstallDialog(QDialog): - def __init__(self, game: Game): - super(UninstallDialog, self).__init__() - self.setAttribute(Qt.WA_DeleteOnClose, True) - self.setWindowTitle("Uninstall Game") - layout = QVBoxLayout() - self.info_text = QLabel( - self.tr("Do you really want to uninstall {}?").format(game.app_title) - ) - layout.addWidget(self.info_text) - self.keep_files = QCheckBox(self.tr("Keep game files.")) - self.keep_config = QCheckBox(self.tr("Keep game configuation.")) - form_layout = QVBoxLayout() - form_layout.setContentsMargins(6, 6, 0, 6) - form_layout.addWidget(self.keep_files) - form_layout.addWidget(self.keep_config) - layout.addLayout(form_layout) + result_ready = pyqtSignal(UninstallOptionsModel) + + def __init__(self, rgame: RareGame, options: UninstallOptionsModel, parent=None): + super(UninstallDialog, self).__init__(parent=parent) + self.setAttribute(Qt.WA_DeleteOnClose, True) + self.setWindowFlags(Qt.Dialog | Qt.CustomizeWindowHint | Qt.WindowTitleHint) + self.setWindowTitle(f'{QApplication.instance().applicationName()} - Uninstall "{rgame.app_title}"') + self.info_text = QLabel( + self.tr("Do you really want to uninstall {}?").format(rgame.app_title) + ) + + self.keep_files = QCheckBox(self.tr("Keep game files.")) + self.keep_files.setChecked(bool(options.keep_files)) + self.keep_config = QCheckBox(self.tr("Keep game configuation.")) + self.keep_config.setChecked(bool(options.keep_config)) - button_layout = QHBoxLayout() self.ok_button = QPushButton( icon("ei.remove-circle", color="red"), self.tr("Uninstall") ) - self.ok_button.clicked.connect(self.ok) + self.ok_button.clicked.connect(self.__on_uninstall) self.cancel_button = QPushButton(self.tr("Cancel")) - self.cancel_button.clicked.connect(self.cancel) + self.cancel_button.clicked.connect(self.__on_cancel) + form_layout = QVBoxLayout() + form_layout.setContentsMargins(-1, -1, 0, -1) + form_layout.addWidget(self.keep_files) + form_layout.addWidget(self.keep_config) + + button_layout = QHBoxLayout() button_layout.addWidget(self.ok_button) button_layout.addStretch(1) button_layout.addWidget(self.cancel_button) + + layout = QVBoxLayout() + layout.addWidget(self.info_text) + layout.addLayout(form_layout) layout.addLayout(button_layout) + self.setLayout(layout) - if get_sdl_appname(game.app_name) is not None: + if get_sdl_appname(rgame.app_name) is not None: self.keep_config.setChecked(True) - self.options: Tuple[bool, bool, bool] = ( - False, self.keep_files.isChecked(), self.keep_config.isChecked() - ) + self.options: UninstallOptionsModel = options - def get_options(self) -> Tuple[bool, bool, bool]: - self.exec_() - return self.options + def closeEvent(self, a0: QCloseEvent) -> None: + self.result_ready.emit(self.options) + super(UninstallDialog, self).closeEvent(a0) - def ok(self): - self.options = (True, self.keep_files.isChecked(), self.keep_config.isChecked()) + def __on_uninstall(self): + self.options.values = (True, self.keep_files.isChecked(), self.keep_config.isChecked()) self.close() - def cancel(self): - self.options = (False, False, False) + def __on_cancel(self): + self.options.values = (None, None, None) self.close() diff --git a/rare/components/main_window.py b/rare/components/main_window.py index 241dc4f9..3e180618 100644 --- a/rare/components/main_window.py +++ b/rare/components/main_window.py @@ -164,7 +164,7 @@ class MainWindow(QMainWindow): defaultButton=QMessageBox.No, ) if reply == QMessageBox.Yes: - self.tab_widget.downloads_tab.stop_download(on_exit=True) + self.tab_widget.downloads_tab.stop_download(omit_queue=True) else: e.ignore() return diff --git a/rare/components/tabs/downloads/__init__.py b/rare/components/tabs/downloads/__init__.py index 33aae173..84609397 100644 --- a/rare/components/tabs/downloads/__init__.py +++ b/rare/components/tabs/downloads/__init__.py @@ -10,10 +10,12 @@ from PyQt5.QtWidgets import ( ) from rare.components.dialogs.install_dialog import InstallDialog, InstallInfoWorker +from rare.components.dialogs.uninstall_dialog import UninstallDialog from rare.lgndr.models.downloading import UIUpdate from rare.models.game import RareGame -from rare.models.install import InstallOptionsModel, InstallQueueItemModel -from rare.shared import RareCore, LegendaryCoreSingleton, GlobalSignalsSingleton, ArgumentsSingleton +from rare.models.install import InstallOptionsModel, InstallQueueItemModel, UninstallOptionsModel +from rare.shared import RareCore +from rare.shared.workers.uninstall import UninstallWorker from rare.ui.components.tabs.downloads.downloads_tab import Ui_DownloadsTab from rare.utils.misc import get_size, create_desktop_link from .groups import UpdateGroup, QueueGroup @@ -36,13 +38,12 @@ class DownloadsTab(QWidget): self.ui = Ui_DownloadsTab() self.ui.setupUi(self) self.rcore = RareCore.instance() - self.core = LegendaryCoreSingleton() - self.signals = GlobalSignalsSingleton() - self.args = ArgumentsSingleton() + self.core = self.rcore.core() + self.signals = self.rcore.signals() + self.args = self.rcore.args() self.thread: Optional[DlThread] = None - self.threadpool = QThreadPool(self) - self.threadpool.setMaxThreadCount(1) + self.threadpool = QThreadPool.globalInstance() self.ui.kill_button.clicked.connect(self.stop_download) @@ -59,18 +60,21 @@ class DownloadsTab(QWidget): self.__check_updates() self.signals.game.install.connect(self.__get_install_options) + self.signals.game.uninstall.connect(self.__get_uninstall_options) self.signals.download.enqueue.connect(self.__add_update) - self.signals.download.dequeue.connect(self.__on_game_uninstalled) + self.signals.download.dequeue.connect(self.__remove_update) self.__reset_download() - self.forced_item: Optional[InstallQueueItemModel] = None - self.on_exit = False + self.__forced_item: Optional[InstallQueueItemModel] = None + self.__omit_queue = False def __check_updates(self): for rgame in self.rcore.updates: self.__add_update(rgame) + @pyqtSlot(str) + @pyqtSlot(RareGame) def __add_update(self, update: Union[str,RareGame]): if isinstance(update, str): update = self.rcore.get_game(update) @@ -80,6 +84,17 @@ class DownloadsTab(QWidget): ) else: self.updates_group.append(update.game, update.igame) + self.update_title.emit(self.queues_count()) + + @pyqtSlot(str) + def __remove_update(self, app_name): + if self.thread and self.thread.item.options.app_name == app_name: + self.stop_download(omit_queue=True) + if self.queue_group.contains(app_name): + self.queue_group.remove(app_name) + if self.updates_group.contains(app_name): + self.updates_group.remove(app_name) + self.update_title.emit(self.queues_count()) @pyqtSlot(str) def __on_queue_removed(self, app_name: str): @@ -100,32 +115,27 @@ class DownloadsTab(QWidget): def __on_queue_force(self, item: InstallQueueItemModel): if self.thread: self.stop_download() - self.forced_item = item + self.__forced_item = item else: self.__start_installation(item) - @pyqtSlot(str) - def __on_game_uninstalled(self, app_name): - if self.thread and self.thread.item.options.app_name == app_name: - self.stop_download() + def stop_download(self, omit_queue=False): + """ + Stops the active download, by optionally skipping the queue - if self.queue_group.contains(app_name): - self.queue_group.remove(app_name) - - if self.updates_group.contains(app_name): - self.updates_group.remove(app_name) - - self.update_title.emit(self.queues_count()) - - def stop_download(self, on_exit=False): + :param omit_queue: bool + If `True`, the stopped download won't be added back to the queue. + Defaults to `False` + :return: + """ self.thread.kill() self.ui.kill_button.setEnabled(False) # lk: if we are exitin Rare, waif for thread to finish # `self.on_exit` control whether we try to add the download # back in the queue. If we are on exit we wait for the thread # to finish, we do not care about handling the result really - if on_exit: - self.on_exit = on_exit + self.__omit_queue = omit_queue + if omit_queue: self.thread.wait() def __start_installation(self, item: InstallQueueItemModel): @@ -141,6 +151,22 @@ class DownloadsTab(QWidget): def queues_count(self) -> int: return self.updates_group.count() + self.queue_group.count() + @property + def is_download_active(self): + return self.thread is not None + + @pyqtSlot(UIUpdate, c_ulonglong) + def __on_download_progress(self, ui_update: UIUpdate, dl_size: c_ulonglong): + self.ui.progress_bar.setValue(int(ui_update.progress)) + self.ui.dl_speed.setText(f"{get_size(ui_update.download_compressed_speed)}/s") + self.ui.cache_used.setText( + f"{get_size(ui_update.cache_usage) if ui_update.cache_usage > 1023 else '0KB'}" + ) + self.ui.downloaded.setText( + f"{get_size(ui_update.total_downloaded)} / {get_size(dl_size.value)}" + ) + self.ui.time_left.setText(get_time(ui_update.estimated_time_left)) + @pyqtSlot(InstallQueueItemModel) def __on_info_worker_result(self, item: InstallQueueItemModel): rgame = self.rcore.get_game(item.options.app_name) @@ -171,7 +197,7 @@ class DownloadsTab(QWidget): if result.item.options.overlay: self.signals.application.overlay_installed.emit() else: - self.signals.application.notify.emit(result.item.download.game.app_name) + self.signals.application.notify.emit(result.item.download.game.app_title) if self.updates_group.contains(result.item.options.app_name): self.updates_group.set_widget_enabled(result.item.options.app_name, True) @@ -182,12 +208,12 @@ class DownloadsTab(QWidget): elif result.code == DlResultCode.STOPPED: logger.info(f"Download stopped: {result.item.download.game.app_title}") - if not self.on_exit: - info_worker = InstallInfoWorker(self.core, result.item.options) - info_worker.signals.result.connect(self.__on_info_worker_result) - info_worker.signals.failed.connect(self.__on_info_worker_failed) - info_worker.signals.finished.connect(self.__on_info_worker_finished) - self.threadpool.start(info_worker) + if not self.__omit_queue: + worker = InstallInfoWorker(self.core, result.item.options) + worker.signals.result.connect(self.__on_info_worker_result) + worker.signals.failed.connect(self.__on_info_worker_failed) + worker.signals.finished.connect(self.__on_info_worker_finished) + self.threadpool.start(worker) else: return @@ -199,9 +225,9 @@ class DownloadsTab(QWidget): if result.code == DlResultCode.FINISHED and self.queue_group.count(): self.__start_installation(self.queue_group.pop_front()) - elif result.code == DlResultCode.STOPPED and self.forced_item: - self.__start_installation(self.forced_item) - self.forced_item = None + elif result.code == DlResultCode.STOPPED and self.__forced_item: + self.__start_installation(self.__forced_item) + self.__forced_item = None else: self.__reset_download() @@ -215,17 +241,15 @@ class DownloadsTab(QWidget): self.ui.downloaded.setText("n/a") self.thread = None - @pyqtSlot(UIUpdate, c_ulonglong) - def __on_download_progress(self, ui_update: UIUpdate, dl_size: c_ulonglong): - self.ui.progress_bar.setValue(int(ui_update.progress)) - self.ui.dl_speed.setText(f"{get_size(ui_update.download_compressed_speed)}/s") - self.ui.cache_used.setText( - f"{get_size(ui_update.cache_usage) if ui_update.cache_usage > 1023 else '0KB'}" + @pyqtSlot(InstallOptionsModel) + def __get_install_options(self, options: InstallOptionsModel): + install_dialog = InstallDialog( + self.rcore.get_game(options.app_name), + options=options, + parent=self, ) - self.ui.downloaded.setText( - f"{get_size(ui_update.total_downloaded)} / {get_size(dl_size.value)}" - ) - self.ui.time_left.setText(get_time(ui_update.estimated_time_left)) + install_dialog.result_ready.connect(self.__on_install_dialog_closed) + install_dialog.execute() @pyqtSlot(InstallQueueItemModel) def __on_install_dialog_closed(self, item: InstallQueueItemModel): @@ -249,17 +273,26 @@ class DownloadsTab(QWidget): if self.updates_group.contains(item.options.app_name): self.updates_group.set_widget_enabled(item.options.app_name, True) - - @pyqtSlot(InstallOptionsModel) - def __get_install_options(self, options: InstallOptionsModel): - install_dialog = InstallDialog( + @pyqtSlot(UninstallOptionsModel) + def __get_uninstall_options(self, options: UninstallOptionsModel): + uninstall_dialog = UninstallDialog( self.rcore.get_game(options.app_name), options=options, parent=self, ) - install_dialog.result_ready.connect(self.__on_install_dialog_closed) - install_dialog.execute() + uninstall_dialog.result_ready.connect(self.__on_uninstall_dialog_closed) + uninstall_dialog.exec() - @property - def is_download_active(self): - return self.thread is not None + @pyqtSlot(UninstallOptionsModel) + def __on_uninstall_dialog_closed(self, options: UninstallOptionsModel): + if options and options.uninstall: + self.__remove_update(options.app_name) + worker = UninstallWorker(self.core, self.rcore.get_game(options.app_name), options) + worker.signals.result.connect(self.__on_uninstall_worker_result) + self.threadpool.start(worker) + + @pyqtSlot(RareGame, bool, str) + def __on_uninstall_worker_result(self, rgame: RareGame, success: bool, message: str): + if not success: + QMessageBox.warning(None, self.tr("Uninstall - {}").format(rgame.title), message, QMessageBox.Close) + rgame.set_installed(False) diff --git a/rare/components/tabs/games/__init__.py b/rare/components/tabs/games/__init__.py index 02faf6ec..6917048d 100644 --- a/rare/components/tabs/games/__init__.py +++ b/rare/components/tabs/games/__init__.py @@ -117,9 +117,9 @@ class GamesTab(QStackedWidget): # signals self.signals.game.installed.connect(self.update_count_games_label) - # self.signals.game.installed.connect(self.library_controller.update_list) + self.signals.game.installed.connect(self.library_controller.update_list) self.signals.game.uninstalled.connect(self.update_count_games_label) - # self.signals.game.uninstalled.connect(self.library_controller.update_list) + self.signals.game.uninstalled.connect(self.library_controller.update_list) # self.signals.game.uninstalled.connect(lambda x: self.setCurrentWidget(self.games)) start_t = time.time() diff --git a/rare/components/tabs/games/game_info/game_info.py b/rare/components/tabs/games/game_info/game_info.py index 2b3e741b..c1c4efed 100644 --- a/rare/components/tabs/games/game_info/game_info.py +++ b/rare/components/tabs/games/game_info/game_info.py @@ -11,7 +11,6 @@ from PyQt5.QtCore import ( pyqtSlot, ) from PyQt5.QtWidgets import ( - QHBoxLayout, QMenu, QPushButton, QWidget, @@ -19,7 +18,6 @@ from PyQt5.QtWidgets import ( QWidgetAction, ) -from rare.components.dialogs.uninstall_dialog import UninstallDialog from rare.models.game import RareGame from rare.shared import ( RareCore, @@ -28,7 +26,6 @@ from rare.shared import ( ArgumentsSingleton, ImageManagerSingleton, ) -from rare.shared.game_utils import uninstall_game from rare.shared.image_manager import ImageSize from rare.shared.workers.verify import VerifyWorker from rare.ui.components.tabs.games.game_info.game_info import Ui_GameInfo @@ -107,35 +104,7 @@ class GameInfo(QWidget): @pyqtSlot() def __on_uninstall(self): """ This function is to be called from the button only """ - self.uninstall_game(self.rgame) - - def uninstall_game(self, rgame: RareGame) -> bool: - # returns if uninstalled - if not os.path.exists(rgame.igame.install_path): - if QMessageBox.Yes == QMessageBox.question( - None, - self.tr("Uninstall - {}").format(rgame.igame.title), - self.tr( - "Game files of {} do not exist. Remove it from installed games?" - ).format(rgame.igame.title), - QMessageBox.Yes | QMessageBox.No, - QMessageBox.Yes, - ): - self.core.lgd.remove_installed_game(rgame.app_name) - rgame.set_installed(False) - return True - else: - return False - - proceed, keep_files, keep_config = UninstallDialog(rgame.game).get_options() - if not proceed: - return False - success, message = uninstall_game(self.core, rgame.app_name, keep_files, keep_config) - if not success: - QMessageBox.warning(None, self.tr("Uninstall - {}").format(rgame.title), message, QMessageBox.Close) - rgame.set_installed(False) - self.signals.download.dequeue.emit(rgame.app_name) - return True + self.rgame.uninstall() @pyqtSlot() def __on_repair(self): diff --git a/rare/models/game.py b/rare/models/game.py index da2d7767..87c3f5d7 100644 --- a/rare/models/game.py +++ b/rare/models/game.py @@ -11,7 +11,7 @@ from PyQt5.QtGui import QPixmap from legendary.models.game import Game, InstalledGame, SaveGameFile from rare.lgndr.core import LegendaryCore -from rare.models.install import InstallOptionsModel +from rare.models.install import InstallOptionsModel, UninstallOptionsModel from rare.shared.game_process import GameProcess from rare.shared.image_manager import ImageManager from rare.utils.misc import get_rare_executable @@ -28,6 +28,7 @@ class RareGame(QObject): DOWNLOADING = 2 VERIFYING = 3 MOVING = 4 + UNINSTALLING = 5 @dataclass class Metadata: @@ -74,6 +75,7 @@ class RareGame(QObject): class Game(QObject): install = pyqtSignal(InstallOptionsModel) installed = pyqtSignal(str) + uninstall = pyqtSignal(UninstallOptionsModel) uninstalled = pyqtSignal(str) launched = pyqtSignal(str) finished = pyqtSignal(str) @@ -475,6 +477,11 @@ class RareGame(QObject): ) ) + def uninstall(self): + self.signals.game.uninstall.emit( + UninstallOptionsModel(app_name=self.app_name) + ) + def launch( self, offline: bool = False, diff --git a/rare/models/install.py b/rare/models/install.py index 33afa730..67e33bbb 100644 --- a/rare/models/install.py +++ b/rare/models/install.py @@ -1,7 +1,7 @@ import os import platform as pf from dataclasses import dataclass -from typing import List, Optional, Callable, Dict +from typing import List, Optional, Callable, Dict, Tuple from legendary.models.downloading import AnalysisResult, ConditionCheckResult from legendary.models.game import Game, InstalledGame @@ -63,3 +63,29 @@ class InstallQueueItemModel: def __bool__(self): return (self.download is not None) and (self.options is not None) + + +@dataclass +class UninstallOptionsModel: + app_name: str + uninstall: bool = None + keep_files: bool = None + keep_config: bool = None + + def __bool__(self): + return ( + bool(self.app_name) + and (self.uninstall is not None) + and (self.keep_files is not None) + and (self.keep_config is not None) + ) + + @property + def values(self) -> Tuple[bool, bool, bool]: + return self.uninstall, self.keep_config, self.keep_files + + @values.setter + def values(self, values: Tuple[bool, bool, bool]): + self.uninstall = values[0] + self.keep_files = values[1] + self.keep_config = values[2] \ No newline at end of file diff --git a/rare/models/signals.py b/rare/models/signals.py index e828756e..991cdf75 100644 --- a/rare/models/signals.py +++ b/rare/models/signals.py @@ -1,6 +1,6 @@ from PyQt5.QtCore import QObject, pyqtSignal -from .install import InstallOptionsModel +from .install import InstallOptionsModel, UninstallOptionsModel class GlobalSignals: @@ -22,6 +22,7 @@ class GlobalSignals: class GameSignals(QObject): install = pyqtSignal(InstallOptionsModel) + uninstall = pyqtSignal(UninstallOptionsModel) # str: app_name installed = pyqtSignal(str) # str: app_name diff --git a/rare/shared/game_utils.py b/rare/shared/game_utils.py index 3023057b..8a3d3e58 100644 --- a/rare/shared/game_utils.py +++ b/rare/shared/game_utils.py @@ -1,68 +1,16 @@ -import os -import platform from logging import getLogger from PyQt5.QtCore import QObject, pyqtSignal, QUrl, pyqtSlot -from PyQt5.QtCore import QStandardPaths from PyQt5.QtGui import QDesktopServices from PyQt5.QtWidgets import QMessageBox, QPushButton -from legendary.core import LegendaryCore -from rare.lgndr.cli import LegendaryCLI -from rare.lgndr.glue.arguments import LgndrUninstallGameArgs -from rare.lgndr.glue.monkeys import LgndrIndirectStatus from rare.models.game import RareGame from rare.shared import LegendaryCoreSingleton, GlobalSignalsSingleton, ArgumentsSingleton -from rare.utils import config_helper from .cloud_save_utils import CloudSaveUtils logger = getLogger("GameUtils") -def uninstall_game(core: LegendaryCore, app_name: str, keep_files=False, keep_config=False): - igame = core.get_installed_game(app_name) - - # remove shortcuts link - desktop = QStandardPaths.writableLocation(QStandardPaths.DesktopLocation) - applications = QStandardPaths.writableLocation(QStandardPaths.ApplicationsLocation) - if platform.system() == "Linux": - desktop_shortcut = os.path.join(desktop, f"{igame.title}.desktop") - if os.path.exists(desktop_shortcut): - os.remove(desktop_shortcut) - - applications_shortcut = os.path.join(applications, f"{igame.title}.desktop") - if os.path.exists(applications_shortcut): - os.remove(applications_shortcut) - - elif platform.system() == "Windows": - game_title = igame.title.split(":")[0] - desktop_shortcut = os.path.join(desktop, f"{game_title}.lnk") - if os.path.exists(desktop_shortcut): - os.remove(desktop_shortcut) - - start_menu_shortcut = os.path.join(applications, "..", f"{game_title}.lnk") - if os.path.exists(start_menu_shortcut): - os.remove(start_menu_shortcut) - - status = LgndrIndirectStatus() - LegendaryCLI(core).uninstall_game( - LgndrUninstallGameArgs( - app_name=app_name, - keep_files=keep_files, - indirect_status=status, - yes=True, - ) - ) - if not keep_config: - 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 status.success, status.message - - class GameUtils(QObject): finished = pyqtSignal(str, str) # app_name, error cloud_save_finished = pyqtSignal(str) diff --git a/rare/shared/rare_core.py b/rare/shared/rare_core.py index bce59aed..f739b8a7 100644 --- a/rare/shared/rare_core.py +++ b/rare/shared/rare_core.py @@ -146,6 +146,7 @@ class RareCore(QObject): def add_game(self, rgame: RareGame) -> None: rgame.signals.game.install.connect(self.__signals.game.install) + rgame.signals.game.uninstall.connect(self.__signals.game.uninstall) rgame.signals.game.installed.connect(self.__signals.game.installed) rgame.signals.game.uninstalled.connect(self.__signals.game.uninstalled) rgame.signals.game.finished.connect(self.__signals.application.update_tray) diff --git a/rare/shared/workers/uninstall.py b/rare/shared/workers/uninstall.py new file mode 100644 index 00000000..57d980b7 --- /dev/null +++ b/rare/shared/workers/uninstall.py @@ -0,0 +1,82 @@ +import os +import platform +import sys +from logging import getLogger + +from PyQt5.QtCore import QStandardPaths, QRunnable, QObject, pyqtSignal +from legendary.core import LegendaryCore + +from rare.lgndr.cli import LegendaryCLI +from rare.lgndr.glue.arguments import LgndrUninstallGameArgs +from rare.lgndr.glue.monkeys import LgndrIndirectStatus +from rare.models.game import RareGame +from rare.models.install import UninstallOptionsModel +from rare.utils import config_helper + +logger = getLogger("UninstallWorker") + + +def uninstall_game(core: LegendaryCore, app_name: str, keep_files=False, keep_config=False): + igame = core.get_installed_game(app_name) + + # remove shortcuts link + desktop = QStandardPaths.writableLocation(QStandardPaths.DesktopLocation) + applications = QStandardPaths.writableLocation(QStandardPaths.ApplicationsLocation) + if platform.system() == "Linux": + desktop_shortcut = os.path.join(desktop, f"{igame.title}.desktop") + if os.path.exists(desktop_shortcut): + os.remove(desktop_shortcut) + + applications_shortcut = os.path.join(applications, f"{igame.title}.desktop") + if os.path.exists(applications_shortcut): + os.remove(applications_shortcut) + + elif platform.system() == "Windows": + game_title = igame.title.split(":")[0] + desktop_shortcut = os.path.join(desktop, f"{game_title}.lnk") + if os.path.exists(desktop_shortcut): + os.remove(desktop_shortcut) + + start_menu_shortcut = os.path.join(applications, "..", f"{game_title}.lnk") + if os.path.exists(start_menu_shortcut): + os.remove(start_menu_shortcut) + + status = LgndrIndirectStatus() + LegendaryCLI(core).uninstall_game( + LgndrUninstallGameArgs( + app_name=app_name, + keep_files=keep_files, + indirect_status=status, + yes=True, + ) + ) + if not keep_config: + 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 status.success, status.message + + +class UninstallWorker(QRunnable): + class Signals(QObject): + result = pyqtSignal(RareGame, bool, str) + + def __init__(self, core: LegendaryCore, rgame: RareGame, options: UninstallOptionsModel): + sys.excepthook = sys.__excepthook__ + super(UninstallWorker, self).__init__() + self.signals = UninstallWorker.Signals() + self.setAutoDelete(True) + self.core = core + self.rgame = rgame + self.options = options + + def run(self) -> None: + self.rgame.state = RareGame.State.UNINSTALLING + success, message = uninstall_game( + self.core, self.rgame.app_name, self.options.keep_files, self.options.keep_config + ) + self.rgame.state = RareGame.State.IDLE + self.signals.result.emit(self.rgame, success, message)