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)