1
0
Fork 0
mirror of synced 2024-06-26 10:11:19 +12:00

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>
This commit is contained in:
loathingKernel 2023-01-25 12:59:00 +02:00
parent b02483eb80
commit fc7e45a43a
12 changed files with 260 additions and 179 deletions

View file

@ -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 <b>{}</b>").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

View file

@ -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 <b>{}</b>?").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 <b>{}</b>?").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()

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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