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:
parent
b02483eb80
commit
fc7e45a43a
12 changed files with 260 additions and 179 deletions
|
@ -10,7 +10,7 @@ from requests.exceptions import ConnectionError, HTTPError
|
||||||
from rare.components.dialogs.login import LoginDialog
|
from rare.components.dialogs.login import LoginDialog
|
||||||
from rare.models.apiresults import ApiResults
|
from rare.models.apiresults import ApiResults
|
||||||
from rare.shared import LegendaryCoreSingleton, ArgumentsSingleton, ApiResultsSingleton, ImageManagerSingleton
|
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.ui.components.dialogs.launch_dialog import Ui_LaunchDialog
|
||||||
from rare.utils.misc import CloudWorker
|
from rare.utils.misc import CloudWorker
|
||||||
from rare.widgets.elide_label import ElideLabel
|
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
|
# FIXME: incorporate installed game status checking here for now, still slow
|
||||||
for i, igame in enumerate(igame_list):
|
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(
|
self.signals.progress.emit(
|
||||||
int(i / len(igame_list) * 25) + 75,
|
int(i / len(igame_list) * 25) + 75,
|
||||||
self.tr("Validating install for <b>{}</b>").format(igame.title)
|
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)
|
uninstall_game(self.core, igame.app_name, keep_files=True)
|
||||||
logger.info(f"Uninstalled {igame.title}, because no game files exist")
|
logger.info(f"Uninstalled {igame.title}, because no game files exist")
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# lk: games that don't have an override and can't find their executable due to case sensitivity
|
# 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: will still erroneously require verification. This might need to be removed completely
|
||||||
# lk: or be decoupled from the verification requirement
|
# lk: or be decoupled from the verification requirement
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
from typing import Tuple
|
from typing import Tuple
|
||||||
|
|
||||||
from PyQt5.QtCore import Qt
|
from PyQt5.QtCore import Qt, pyqtSignal
|
||||||
|
from PyQt5.QtGui import QCloseEvent
|
||||||
from PyQt5.QtWidgets import (
|
from PyQt5.QtWidgets import (
|
||||||
QDialog,
|
QDialog,
|
||||||
QLabel,
|
QLabel,
|
||||||
|
@ -8,62 +9,70 @@ from PyQt5.QtWidgets import (
|
||||||
QCheckBox,
|
QCheckBox,
|
||||||
QHBoxLayout,
|
QHBoxLayout,
|
||||||
QPushButton,
|
QPushButton,
|
||||||
|
QApplication,
|
||||||
)
|
)
|
||||||
|
|
||||||
from legendary.models.game import Game
|
|
||||||
from legendary.utils.selective_dl import get_sdl_appname
|
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
|
from rare.utils.misc import icon
|
||||||
|
|
||||||
|
|
||||||
class UninstallDialog(QDialog):
|
class UninstallDialog(QDialog):
|
||||||
def __init__(self, game: Game):
|
result_ready = pyqtSignal(UninstallOptionsModel)
|
||||||
super(UninstallDialog, self).__init__()
|
|
||||||
self.setAttribute(Qt.WA_DeleteOnClose, True)
|
def __init__(self, rgame: RareGame, options: UninstallOptionsModel, parent=None):
|
||||||
self.setWindowTitle("Uninstall Game")
|
super(UninstallDialog, self).__init__(parent=parent)
|
||||||
layout = QVBoxLayout()
|
self.setAttribute(Qt.WA_DeleteOnClose, True)
|
||||||
self.info_text = QLabel(
|
self.setWindowFlags(Qt.Dialog | Qt.CustomizeWindowHint | Qt.WindowTitleHint)
|
||||||
self.tr("Do you really want to uninstall <b>{}</b>?").format(game.app_title)
|
self.setWindowTitle(f'{QApplication.instance().applicationName()} - Uninstall "{rgame.app_title}"')
|
||||||
)
|
self.info_text = QLabel(
|
||||||
layout.addWidget(self.info_text)
|
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_config = QCheckBox(self.tr("Keep game configuation."))
|
|
||||||
form_layout = QVBoxLayout()
|
self.keep_files = QCheckBox(self.tr("Keep game files."))
|
||||||
form_layout.setContentsMargins(6, 6, 0, 6)
|
self.keep_files.setChecked(bool(options.keep_files))
|
||||||
form_layout.addWidget(self.keep_files)
|
self.keep_config = QCheckBox(self.tr("Keep game configuation."))
|
||||||
form_layout.addWidget(self.keep_config)
|
self.keep_config.setChecked(bool(options.keep_config))
|
||||||
layout.addLayout(form_layout)
|
|
||||||
|
|
||||||
button_layout = QHBoxLayout()
|
|
||||||
self.ok_button = QPushButton(
|
self.ok_button = QPushButton(
|
||||||
icon("ei.remove-circle", color="red"), self.tr("Uninstall")
|
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 = 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.addWidget(self.ok_button)
|
||||||
button_layout.addStretch(1)
|
button_layout.addStretch(1)
|
||||||
button_layout.addWidget(self.cancel_button)
|
button_layout.addWidget(self.cancel_button)
|
||||||
|
|
||||||
|
layout = QVBoxLayout()
|
||||||
|
layout.addWidget(self.info_text)
|
||||||
|
layout.addLayout(form_layout)
|
||||||
layout.addLayout(button_layout)
|
layout.addLayout(button_layout)
|
||||||
|
|
||||||
self.setLayout(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.keep_config.setChecked(True)
|
||||||
|
|
||||||
self.options: Tuple[bool, bool, bool] = (
|
self.options: UninstallOptionsModel = options
|
||||||
False, self.keep_files.isChecked(), self.keep_config.isChecked()
|
|
||||||
)
|
|
||||||
|
|
||||||
def get_options(self) -> Tuple[bool, bool, bool]:
|
def closeEvent(self, a0: QCloseEvent) -> None:
|
||||||
self.exec_()
|
self.result_ready.emit(self.options)
|
||||||
return self.options
|
super(UninstallDialog, self).closeEvent(a0)
|
||||||
|
|
||||||
def ok(self):
|
def __on_uninstall(self):
|
||||||
self.options = (True, self.keep_files.isChecked(), self.keep_config.isChecked())
|
self.options.values = (True, self.keep_files.isChecked(), self.keep_config.isChecked())
|
||||||
self.close()
|
self.close()
|
||||||
|
|
||||||
def cancel(self):
|
def __on_cancel(self):
|
||||||
self.options = (False, False, False)
|
self.options.values = (None, None, None)
|
||||||
self.close()
|
self.close()
|
||||||
|
|
|
@ -164,7 +164,7 @@ class MainWindow(QMainWindow):
|
||||||
defaultButton=QMessageBox.No,
|
defaultButton=QMessageBox.No,
|
||||||
)
|
)
|
||||||
if reply == QMessageBox.Yes:
|
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:
|
else:
|
||||||
e.ignore()
|
e.ignore()
|
||||||
return
|
return
|
||||||
|
|
|
@ -10,10 +10,12 @@ from PyQt5.QtWidgets import (
|
||||||
)
|
)
|
||||||
|
|
||||||
from rare.components.dialogs.install_dialog import InstallDialog, InstallInfoWorker
|
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.lgndr.models.downloading import UIUpdate
|
||||||
from rare.models.game import RareGame
|
from rare.models.game import RareGame
|
||||||
from rare.models.install import InstallOptionsModel, InstallQueueItemModel
|
from rare.models.install import InstallOptionsModel, InstallQueueItemModel, UninstallOptionsModel
|
||||||
from rare.shared import RareCore, LegendaryCoreSingleton, GlobalSignalsSingleton, ArgumentsSingleton
|
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.ui.components.tabs.downloads.downloads_tab import Ui_DownloadsTab
|
||||||
from rare.utils.misc import get_size, create_desktop_link
|
from rare.utils.misc import get_size, create_desktop_link
|
||||||
from .groups import UpdateGroup, QueueGroup
|
from .groups import UpdateGroup, QueueGroup
|
||||||
|
@ -36,13 +38,12 @@ class DownloadsTab(QWidget):
|
||||||
self.ui = Ui_DownloadsTab()
|
self.ui = Ui_DownloadsTab()
|
||||||
self.ui.setupUi(self)
|
self.ui.setupUi(self)
|
||||||
self.rcore = RareCore.instance()
|
self.rcore = RareCore.instance()
|
||||||
self.core = LegendaryCoreSingleton()
|
self.core = self.rcore.core()
|
||||||
self.signals = GlobalSignalsSingleton()
|
self.signals = self.rcore.signals()
|
||||||
self.args = ArgumentsSingleton()
|
self.args = self.rcore.args()
|
||||||
|
|
||||||
self.thread: Optional[DlThread] = None
|
self.thread: Optional[DlThread] = None
|
||||||
self.threadpool = QThreadPool(self)
|
self.threadpool = QThreadPool.globalInstance()
|
||||||
self.threadpool.setMaxThreadCount(1)
|
|
||||||
|
|
||||||
self.ui.kill_button.clicked.connect(self.stop_download)
|
self.ui.kill_button.clicked.connect(self.stop_download)
|
||||||
|
|
||||||
|
@ -59,18 +60,21 @@ class DownloadsTab(QWidget):
|
||||||
self.__check_updates()
|
self.__check_updates()
|
||||||
|
|
||||||
self.signals.game.install.connect(self.__get_install_options)
|
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.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.__reset_download()
|
||||||
|
|
||||||
self.forced_item: Optional[InstallQueueItemModel] = None
|
self.__forced_item: Optional[InstallQueueItemModel] = None
|
||||||
self.on_exit = False
|
self.__omit_queue = False
|
||||||
|
|
||||||
def __check_updates(self):
|
def __check_updates(self):
|
||||||
for rgame in self.rcore.updates:
|
for rgame in self.rcore.updates:
|
||||||
self.__add_update(rgame)
|
self.__add_update(rgame)
|
||||||
|
|
||||||
|
@pyqtSlot(str)
|
||||||
|
@pyqtSlot(RareGame)
|
||||||
def __add_update(self, update: Union[str,RareGame]):
|
def __add_update(self, update: Union[str,RareGame]):
|
||||||
if isinstance(update, str):
|
if isinstance(update, str):
|
||||||
update = self.rcore.get_game(update)
|
update = self.rcore.get_game(update)
|
||||||
|
@ -80,6 +84,17 @@ class DownloadsTab(QWidget):
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
self.updates_group.append(update.game, update.igame)
|
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)
|
@pyqtSlot(str)
|
||||||
def __on_queue_removed(self, app_name: str):
|
def __on_queue_removed(self, app_name: str):
|
||||||
|
@ -100,32 +115,27 @@ class DownloadsTab(QWidget):
|
||||||
def __on_queue_force(self, item: InstallQueueItemModel):
|
def __on_queue_force(self, item: InstallQueueItemModel):
|
||||||
if self.thread:
|
if self.thread:
|
||||||
self.stop_download()
|
self.stop_download()
|
||||||
self.forced_item = item
|
self.__forced_item = item
|
||||||
else:
|
else:
|
||||||
self.__start_installation(item)
|
self.__start_installation(item)
|
||||||
|
|
||||||
@pyqtSlot(str)
|
def stop_download(self, omit_queue=False):
|
||||||
def __on_game_uninstalled(self, app_name):
|
"""
|
||||||
if self.thread and self.thread.item.options.app_name == app_name:
|
Stops the active download, by optionally skipping the queue
|
||||||
self.stop_download()
|
|
||||||
|
|
||||||
if self.queue_group.contains(app_name):
|
:param omit_queue: bool
|
||||||
self.queue_group.remove(app_name)
|
If `True`, the stopped download won't be added back to the queue.
|
||||||
|
Defaults to `False`
|
||||||
if self.updates_group.contains(app_name):
|
:return:
|
||||||
self.updates_group.remove(app_name)
|
"""
|
||||||
|
|
||||||
self.update_title.emit(self.queues_count())
|
|
||||||
|
|
||||||
def stop_download(self, on_exit=False):
|
|
||||||
self.thread.kill()
|
self.thread.kill()
|
||||||
self.ui.kill_button.setEnabled(False)
|
self.ui.kill_button.setEnabled(False)
|
||||||
# lk: if we are exitin Rare, waif for thread to finish
|
# lk: if we are exitin Rare, waif for thread to finish
|
||||||
# `self.on_exit` control whether we try to add the download
|
# `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
|
# 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
|
# to finish, we do not care about handling the result really
|
||||||
if on_exit:
|
self.__omit_queue = omit_queue
|
||||||
self.on_exit = on_exit
|
if omit_queue:
|
||||||
self.thread.wait()
|
self.thread.wait()
|
||||||
|
|
||||||
def __start_installation(self, item: InstallQueueItemModel):
|
def __start_installation(self, item: InstallQueueItemModel):
|
||||||
|
@ -141,6 +151,22 @@ class DownloadsTab(QWidget):
|
||||||
def queues_count(self) -> int:
|
def queues_count(self) -> int:
|
||||||
return self.updates_group.count() + self.queue_group.count()
|
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)
|
@pyqtSlot(InstallQueueItemModel)
|
||||||
def __on_info_worker_result(self, item: InstallQueueItemModel):
|
def __on_info_worker_result(self, item: InstallQueueItemModel):
|
||||||
rgame = self.rcore.get_game(item.options.app_name)
|
rgame = self.rcore.get_game(item.options.app_name)
|
||||||
|
@ -171,7 +197,7 @@ class DownloadsTab(QWidget):
|
||||||
if result.item.options.overlay:
|
if result.item.options.overlay:
|
||||||
self.signals.application.overlay_installed.emit()
|
self.signals.application.overlay_installed.emit()
|
||||||
else:
|
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):
|
if self.updates_group.contains(result.item.options.app_name):
|
||||||
self.updates_group.set_widget_enabled(result.item.options.app_name, True)
|
self.updates_group.set_widget_enabled(result.item.options.app_name, True)
|
||||||
|
@ -182,12 +208,12 @@ class DownloadsTab(QWidget):
|
||||||
|
|
||||||
elif result.code == DlResultCode.STOPPED:
|
elif result.code == DlResultCode.STOPPED:
|
||||||
logger.info(f"Download stopped: {result.item.download.game.app_title}")
|
logger.info(f"Download stopped: {result.item.download.game.app_title}")
|
||||||
if not self.on_exit:
|
if not self.__omit_queue:
|
||||||
info_worker = InstallInfoWorker(self.core, result.item.options)
|
worker = InstallInfoWorker(self.core, result.item.options)
|
||||||
info_worker.signals.result.connect(self.__on_info_worker_result)
|
worker.signals.result.connect(self.__on_info_worker_result)
|
||||||
info_worker.signals.failed.connect(self.__on_info_worker_failed)
|
worker.signals.failed.connect(self.__on_info_worker_failed)
|
||||||
info_worker.signals.finished.connect(self.__on_info_worker_finished)
|
worker.signals.finished.connect(self.__on_info_worker_finished)
|
||||||
self.threadpool.start(info_worker)
|
self.threadpool.start(worker)
|
||||||
else:
|
else:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
@ -199,9 +225,9 @@ class DownloadsTab(QWidget):
|
||||||
|
|
||||||
if result.code == DlResultCode.FINISHED and self.queue_group.count():
|
if result.code == DlResultCode.FINISHED and self.queue_group.count():
|
||||||
self.__start_installation(self.queue_group.pop_front())
|
self.__start_installation(self.queue_group.pop_front())
|
||||||
elif result.code == DlResultCode.STOPPED and self.forced_item:
|
elif result.code == DlResultCode.STOPPED and self.__forced_item:
|
||||||
self.__start_installation(self.forced_item)
|
self.__start_installation(self.__forced_item)
|
||||||
self.forced_item = None
|
self.__forced_item = None
|
||||||
else:
|
else:
|
||||||
self.__reset_download()
|
self.__reset_download()
|
||||||
|
|
||||||
|
@ -215,17 +241,15 @@ class DownloadsTab(QWidget):
|
||||||
self.ui.downloaded.setText("n/a")
|
self.ui.downloaded.setText("n/a")
|
||||||
self.thread = None
|
self.thread = None
|
||||||
|
|
||||||
@pyqtSlot(UIUpdate, c_ulonglong)
|
@pyqtSlot(InstallOptionsModel)
|
||||||
def __on_download_progress(self, ui_update: UIUpdate, dl_size: c_ulonglong):
|
def __get_install_options(self, options: InstallOptionsModel):
|
||||||
self.ui.progress_bar.setValue(int(ui_update.progress))
|
install_dialog = InstallDialog(
|
||||||
self.ui.dl_speed.setText(f"{get_size(ui_update.download_compressed_speed)}/s")
|
self.rcore.get_game(options.app_name),
|
||||||
self.ui.cache_used.setText(
|
options=options,
|
||||||
f"{get_size(ui_update.cache_usage) if ui_update.cache_usage > 1023 else '0KB'}"
|
parent=self,
|
||||||
)
|
)
|
||||||
self.ui.downloaded.setText(
|
install_dialog.result_ready.connect(self.__on_install_dialog_closed)
|
||||||
f"{get_size(ui_update.total_downloaded)} / {get_size(dl_size.value)}"
|
install_dialog.execute()
|
||||||
)
|
|
||||||
self.ui.time_left.setText(get_time(ui_update.estimated_time_left))
|
|
||||||
|
|
||||||
@pyqtSlot(InstallQueueItemModel)
|
@pyqtSlot(InstallQueueItemModel)
|
||||||
def __on_install_dialog_closed(self, item: 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):
|
if self.updates_group.contains(item.options.app_name):
|
||||||
self.updates_group.set_widget_enabled(item.options.app_name, True)
|
self.updates_group.set_widget_enabled(item.options.app_name, True)
|
||||||
|
|
||||||
|
@pyqtSlot(UninstallOptionsModel)
|
||||||
@pyqtSlot(InstallOptionsModel)
|
def __get_uninstall_options(self, options: UninstallOptionsModel):
|
||||||
def __get_install_options(self, options: InstallOptionsModel):
|
uninstall_dialog = UninstallDialog(
|
||||||
install_dialog = InstallDialog(
|
|
||||||
self.rcore.get_game(options.app_name),
|
self.rcore.get_game(options.app_name),
|
||||||
options=options,
|
options=options,
|
||||||
parent=self,
|
parent=self,
|
||||||
)
|
)
|
||||||
install_dialog.result_ready.connect(self.__on_install_dialog_closed)
|
uninstall_dialog.result_ready.connect(self.__on_uninstall_dialog_closed)
|
||||||
install_dialog.execute()
|
uninstall_dialog.exec()
|
||||||
|
|
||||||
@property
|
@pyqtSlot(UninstallOptionsModel)
|
||||||
def is_download_active(self):
|
def __on_uninstall_dialog_closed(self, options: UninstallOptionsModel):
|
||||||
return self.thread is not None
|
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)
|
||||||
|
|
|
@ -117,9 +117,9 @@ class GamesTab(QStackedWidget):
|
||||||
|
|
||||||
# signals
|
# signals
|
||||||
self.signals.game.installed.connect(self.update_count_games_label)
|
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.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))
|
# self.signals.game.uninstalled.connect(lambda x: self.setCurrentWidget(self.games))
|
||||||
|
|
||||||
start_t = time.time()
|
start_t = time.time()
|
||||||
|
|
|
@ -11,7 +11,6 @@ from PyQt5.QtCore import (
|
||||||
pyqtSlot,
|
pyqtSlot,
|
||||||
)
|
)
|
||||||
from PyQt5.QtWidgets import (
|
from PyQt5.QtWidgets import (
|
||||||
QHBoxLayout,
|
|
||||||
QMenu,
|
QMenu,
|
||||||
QPushButton,
|
QPushButton,
|
||||||
QWidget,
|
QWidget,
|
||||||
|
@ -19,7 +18,6 @@ from PyQt5.QtWidgets import (
|
||||||
QWidgetAction,
|
QWidgetAction,
|
||||||
)
|
)
|
||||||
|
|
||||||
from rare.components.dialogs.uninstall_dialog import UninstallDialog
|
|
||||||
from rare.models.game import RareGame
|
from rare.models.game import RareGame
|
||||||
from rare.shared import (
|
from rare.shared import (
|
||||||
RareCore,
|
RareCore,
|
||||||
|
@ -28,7 +26,6 @@ from rare.shared import (
|
||||||
ArgumentsSingleton,
|
ArgumentsSingleton,
|
||||||
ImageManagerSingleton,
|
ImageManagerSingleton,
|
||||||
)
|
)
|
||||||
from rare.shared.game_utils import uninstall_game
|
|
||||||
from rare.shared.image_manager import ImageSize
|
from rare.shared.image_manager import ImageSize
|
||||||
from rare.shared.workers.verify import VerifyWorker
|
from rare.shared.workers.verify import VerifyWorker
|
||||||
from rare.ui.components.tabs.games.game_info.game_info import Ui_GameInfo
|
from rare.ui.components.tabs.games.game_info.game_info import Ui_GameInfo
|
||||||
|
@ -107,35 +104,7 @@ class GameInfo(QWidget):
|
||||||
@pyqtSlot()
|
@pyqtSlot()
|
||||||
def __on_uninstall(self):
|
def __on_uninstall(self):
|
||||||
""" This function is to be called from the button only """
|
""" This function is to be called from the button only """
|
||||||
self.uninstall_game(self.rgame)
|
self.rgame.uninstall()
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
@pyqtSlot()
|
@pyqtSlot()
|
||||||
def __on_repair(self):
|
def __on_repair(self):
|
||||||
|
|
|
@ -11,7 +11,7 @@ from PyQt5.QtGui import QPixmap
|
||||||
from legendary.models.game import Game, InstalledGame, SaveGameFile
|
from legendary.models.game import Game, InstalledGame, SaveGameFile
|
||||||
|
|
||||||
from rare.lgndr.core import LegendaryCore
|
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.game_process import GameProcess
|
||||||
from rare.shared.image_manager import ImageManager
|
from rare.shared.image_manager import ImageManager
|
||||||
from rare.utils.misc import get_rare_executable
|
from rare.utils.misc import get_rare_executable
|
||||||
|
@ -28,6 +28,7 @@ class RareGame(QObject):
|
||||||
DOWNLOADING = 2
|
DOWNLOADING = 2
|
||||||
VERIFYING = 3
|
VERIFYING = 3
|
||||||
MOVING = 4
|
MOVING = 4
|
||||||
|
UNINSTALLING = 5
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class Metadata:
|
class Metadata:
|
||||||
|
@ -74,6 +75,7 @@ class RareGame(QObject):
|
||||||
class Game(QObject):
|
class Game(QObject):
|
||||||
install = pyqtSignal(InstallOptionsModel)
|
install = pyqtSignal(InstallOptionsModel)
|
||||||
installed = pyqtSignal(str)
|
installed = pyqtSignal(str)
|
||||||
|
uninstall = pyqtSignal(UninstallOptionsModel)
|
||||||
uninstalled = pyqtSignal(str)
|
uninstalled = pyqtSignal(str)
|
||||||
launched = pyqtSignal(str)
|
launched = pyqtSignal(str)
|
||||||
finished = 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(
|
def launch(
|
||||||
self,
|
self,
|
||||||
offline: bool = False,
|
offline: bool = False,
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import os
|
import os
|
||||||
import platform as pf
|
import platform as pf
|
||||||
from dataclasses import dataclass
|
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.downloading import AnalysisResult, ConditionCheckResult
|
||||||
from legendary.models.game import Game, InstalledGame
|
from legendary.models.game import Game, InstalledGame
|
||||||
|
@ -63,3 +63,29 @@ class InstallQueueItemModel:
|
||||||
|
|
||||||
def __bool__(self):
|
def __bool__(self):
|
||||||
return (self.download is not None) and (self.options is not None)
|
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]
|
|
@ -1,6 +1,6 @@
|
||||||
from PyQt5.QtCore import QObject, pyqtSignal
|
from PyQt5.QtCore import QObject, pyqtSignal
|
||||||
|
|
||||||
from .install import InstallOptionsModel
|
from .install import InstallOptionsModel, UninstallOptionsModel
|
||||||
|
|
||||||
|
|
||||||
class GlobalSignals:
|
class GlobalSignals:
|
||||||
|
@ -22,6 +22,7 @@ class GlobalSignals:
|
||||||
|
|
||||||
class GameSignals(QObject):
|
class GameSignals(QObject):
|
||||||
install = pyqtSignal(InstallOptionsModel)
|
install = pyqtSignal(InstallOptionsModel)
|
||||||
|
uninstall = pyqtSignal(UninstallOptionsModel)
|
||||||
# str: app_name
|
# str: app_name
|
||||||
installed = pyqtSignal(str)
|
installed = pyqtSignal(str)
|
||||||
# str: app_name
|
# str: app_name
|
||||||
|
|
|
@ -1,68 +1,16 @@
|
||||||
import os
|
|
||||||
import platform
|
|
||||||
from logging import getLogger
|
from logging import getLogger
|
||||||
|
|
||||||
from PyQt5.QtCore import QObject, pyqtSignal, QUrl, pyqtSlot
|
from PyQt5.QtCore import QObject, pyqtSignal, QUrl, pyqtSlot
|
||||||
from PyQt5.QtCore import QStandardPaths
|
|
||||||
from PyQt5.QtGui import QDesktopServices
|
from PyQt5.QtGui import QDesktopServices
|
||||||
from PyQt5.QtWidgets import QMessageBox, QPushButton
|
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.models.game import RareGame
|
||||||
from rare.shared import LegendaryCoreSingleton, GlobalSignalsSingleton, ArgumentsSingleton
|
from rare.shared import LegendaryCoreSingleton, GlobalSignalsSingleton, ArgumentsSingleton
|
||||||
from rare.utils import config_helper
|
|
||||||
from .cloud_save_utils import CloudSaveUtils
|
from .cloud_save_utils import CloudSaveUtils
|
||||||
|
|
||||||
logger = getLogger("GameUtils")
|
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):
|
class GameUtils(QObject):
|
||||||
finished = pyqtSignal(str, str) # app_name, error
|
finished = pyqtSignal(str, str) # app_name, error
|
||||||
cloud_save_finished = pyqtSignal(str)
|
cloud_save_finished = pyqtSignal(str)
|
||||||
|
|
|
@ -146,6 +146,7 @@ class RareCore(QObject):
|
||||||
|
|
||||||
def add_game(self, rgame: RareGame) -> None:
|
def add_game(self, rgame: RareGame) -> None:
|
||||||
rgame.signals.game.install.connect(self.__signals.game.install)
|
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.installed.connect(self.__signals.game.installed)
|
||||||
rgame.signals.game.uninstalled.connect(self.__signals.game.uninstalled)
|
rgame.signals.game.uninstalled.connect(self.__signals.game.uninstalled)
|
||||||
rgame.signals.game.finished.connect(self.__signals.application.update_tray)
|
rgame.signals.game.finished.connect(self.__signals.application.update_tray)
|
||||||
|
|
82
rare/shared/workers/uninstall.py
Normal file
82
rare/shared/workers/uninstall.py
Normal 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)
|
Loading…
Reference in a new issue