1
0
Fork 0
mirror of synced 2024-06-02 02:34:40 +12:00
Rare/rare/components/dialogs/install_dialog.py

376 lines
16 KiB
Python
Raw Normal View History

2021-02-10 23:48:25 +13:00
import os
import platform as pf
2023-04-17 04:38:57 +12:00
import shutil
from typing import Tuple, List, Union, Optional
2021-02-10 23:48:25 +13:00
from PyQt5.QtCore import QThreadPool, QSettings
from PyQt5.QtCore import pyqtSignal, pyqtSlot
from PyQt5.QtGui import QShowEvent
from PyQt5.QtWidgets import QFileDialog, QCheckBox, QWidget, QFormLayout
DownloadsTab: Refactor downloads tab When updates are queued, they are removed from the update's list. An exceptions is made when the queued item comes from repairing (without updating), in which case the update is disabled for the runtime. A queued item can be either removed (if it is an update it will be added back to the updates groups) or forced to be updated now. If a queued item is forced, the currently running item will be added to the front of the queue. Downloads will be queued if there is no active download but there is a queue already. The download thread is now responsible for emitting the progress to `RareGame` InstallDialog: Pass `RareGame` and `InstallOptionsModel` only as arguments. The `update`, `repair` and `silent` arguments are already part of `InstallOptionsModel` `RareGame` is used to query information about the game. InstallInfoWorker: Pass only `InstallOptionsModel` as argument Emit `InstallQueueItemModel` as result, to re-use the worker when queuing stopped games RareGame: Query and store metadata property about entitlement grant date RareGame: Add `RareEosOverlay` class that imitates `RareGame` to handle the overlay LibraryWidgetController: Remove dead signal routing code, these signals are handled by `RareGame` Directly parent library widgets instead of reparenting them GameWidgets: Remove unused signals EOSGroup: Set install location based on preferences and use EOSOverlayApp from legendary GamesTab: Connect the `progress` signals of dlcs to the base game's signals GamesTab: Remove dead code GlobalSignals: Remove `ProgresSignals` RareCore: Mangle internal signleton's names Signed-off-by: loathingKernel <142770+loathingKernel@users.noreply.github.com>
2023-01-21 13:15:06 +13:00
from rare.models.game import RareGame
from rare.models.install import InstallDownloadModel, InstallQueueItemModel, InstallOptionsModel
from rare.shared.workers.install_info import InstallInfoWorker
from rare.ui.components.dialogs.install_dialog import Ui_InstallDialog
from rare.ui.components.dialogs.install_dialog_advanced import Ui_InstallDialogAdvanced
from rare.utils.misc import format_size, qta_icon
from rare.widgets.collapsible_widget import CollapsibleFrame
from rare.widgets.dialogs import ActionDialog, game_title
from rare.widgets.indicator_edit import PathEdit, IndicatorReasonsCommon
from rare.widgets.selective_widget import SelectiveWidget
class InstallDialogAdvanced(CollapsibleFrame):
def __init__(self, parent=None):
super(InstallDialogAdvanced, self).__init__(parent=parent)
title = self.tr("Advanced options")
self.setTitle(title)
self.widget = QWidget(parent=self)
self.ui = Ui_InstallDialogAdvanced()
self.ui.setupUi(self.widget)
self.setWidget(self.widget)
class InstallDialogSelective(CollapsibleFrame):
stateChanged: pyqtSignal = pyqtSignal()
def __init__(self, rgame: RareGame, parent=None):
super(InstallDialogSelective, self).__init__(parent=parent)
title = self.tr("Optional downloads")
self.setTitle(title)
self.setEnabled(bool(rgame.sdl_name))
self.widget: SelectiveWidget = None
self.rgame = rgame
def update_list(self, platform: str):
if self.widget is not None:
self.widget.deleteLater()
self.widget = SelectiveWidget(self.rgame, platform, parent=self)
self.widget.stateChanged.connect(self.stateChanged)
self.setWidget(self.widget)
def install_tags(self):
return self.widget.install_tags()
2021-02-10 23:48:25 +13:00
class InstallDialog(ActionDialog):
result_ready = pyqtSignal(InstallQueueItemModel)
2021-02-18 05:46:03 +13:00
def __init__(self, rgame: RareGame, options: InstallOptionsModel, parent=None):
super(InstallDialog, self).__init__(parent=parent)
header = self.tr("Install")
bicon = qta_icon("ri.install-line")
if options.repair_mode:
header = self.tr("Repair")
bicon = qta_icon("fa.wrench")
if options.repair_and_update:
header = self.tr("Repair and update")
elif options.update:
header = self.tr("Update")
elif options.reset_sdl:
header = self.tr("Modify")
bicon = qta_icon("fa.gear")
self.setWindowTitle(game_title(header, rgame.app_title))
self.setSubtitle(game_title(header, rgame.app_title))
install_widget = QWidget(self)
self.ui = Ui_InstallDialog()
self.ui.setupUi(install_widget)
self.core = rgame.core
DownloadsTab: Refactor downloads tab When updates are queued, they are removed from the update's list. An exceptions is made when the queued item comes from repairing (without updating), in which case the update is disabled for the runtime. A queued item can be either removed (if it is an update it will be added back to the updates groups) or forced to be updated now. If a queued item is forced, the currently running item will be added to the front of the queue. Downloads will be queued if there is no active download but there is a queue already. The download thread is now responsible for emitting the progress to `RareGame` InstallDialog: Pass `RareGame` and `InstallOptionsModel` only as arguments. The `update`, `repair` and `silent` arguments are already part of `InstallOptionsModel` `RareGame` is used to query information about the game. InstallInfoWorker: Pass only `InstallOptionsModel` as argument Emit `InstallQueueItemModel` as result, to re-use the worker when queuing stopped games RareGame: Query and store metadata property about entitlement grant date RareGame: Add `RareEosOverlay` class that imitates `RareGame` to handle the overlay LibraryWidgetController: Remove dead signal routing code, these signals are handled by `RareGame` Directly parent library widgets instead of reparenting them GameWidgets: Remove unused signals EOSGroup: Set install location based on preferences and use EOSOverlayApp from legendary GamesTab: Connect the `progress` signals of dlcs to the base game's signals GamesTab: Remove dead code GlobalSignals: Remove `ProgresSignals` RareCore: Mangle internal signleton's names Signed-off-by: loathingKernel <142770+loathingKernel@users.noreply.github.com>
2023-01-21 13:15:06 +13:00
self.rgame = rgame
self.__options: InstallOptionsModel = options
DownloadsTab: Refactor downloads tab When updates are queued, they are removed from the update's list. An exceptions is made when the queued item comes from repairing (without updating), in which case the update is disabled for the runtime. A queued item can be either removed (if it is an update it will be added back to the updates groups) or forced to be updated now. If a queued item is forced, the currently running item will be added to the front of the queue. Downloads will be queued if there is no active download but there is a queue already. The download thread is now responsible for emitting the progress to `RareGame` InstallDialog: Pass `RareGame` and `InstallOptionsModel` only as arguments. The `update`, `repair` and `silent` arguments are already part of `InstallOptionsModel` `RareGame` is used to query information about the game. InstallInfoWorker: Pass only `InstallOptionsModel` as argument Emit `InstallQueueItemModel` as result, to re-use the worker when queuing stopped games RareGame: Query and store metadata property about entitlement grant date RareGame: Add `RareEosOverlay` class that imitates `RareGame` to handle the overlay LibraryWidgetController: Remove dead signal routing code, these signals are handled by `RareGame` Directly parent library widgets instead of reparenting them GameWidgets: Remove unused signals EOSGroup: Set install location based on preferences and use EOSOverlayApp from legendary GamesTab: Connect the `progress` signals of dlcs to the base game's signals GamesTab: Remove dead code GlobalSignals: Remove `ProgresSignals` RareCore: Mangle internal signleton's names Signed-off-by: loathingKernel <142770+loathingKernel@users.noreply.github.com>
2023-01-21 13:15:06 +13:00
self.__download: Optional[InstallDownloadModel] = None
self.__queue_item: Optional[InstallQueueItemModel] = None
self.advanced = InstallDialogAdvanced(parent=self)
self.ui.advanced_layout.addWidget(self.advanced)
self.selectable = InstallDialogSelective(rgame, parent=self)
self.selectable.stateChanged.connect(self.option_changed)
self.ui.selectable_layout.addWidget(self.selectable)
self.options_changed = False
self.threadpool = QThreadPool(self)
self.threadpool.setMaxThreadCount(1)
if options.base_path:
base_path = options.base_path
elif rgame.is_installed:
base_path = rgame.install_path
else:
base_path = self.core.get_default_install_dir(rgame.default_platform)
2021-12-24 22:09:50 +13:00
self.install_dir_edit = PathEdit(
path=base_path,
file_mode=QFileDialog.DirectoryOnly,
edit_func=self.install_dir_edit_callback,
save_func=self.install_dir_save_callback,
2021-12-24 22:09:50 +13:00
parent=self,
)
self.ui.main_layout.setWidget(
self.ui.main_layout.getWidgetPosition(self.ui.install_dir_label)[0],
QFormLayout.FieldRole,
self.install_dir_edit,
)
self.install_dir_edit.setDisabled(rgame.is_installed)
self.ui.install_dir_label.setDisabled(rgame.is_installed)
self.ui.shortcut_label.setDisabled(rgame.is_installed)
self.ui.shortcut_check.setDisabled(rgame.is_installed)
self.ui.shortcut_check.setChecked(not rgame.is_installed and QSettings().value("create_shortcut", True, bool))
2021-02-10 23:48:25 +13:00
self.error_box()
self.ui.platform_combo.addItems(reversed(rgame.platforms))
combo_text = rgame.igame.platform if rgame.is_installed else rgame.default_platform
self.ui.platform_combo.setCurrentIndex(self.ui.platform_combo.findText(combo_text))
self.ui.platform_combo.currentIndexChanged.connect(self.option_changed)
self.ui.platform_combo.currentIndexChanged.connect(self.check_incompatible_platform)
self.ui.platform_combo.currentIndexChanged.connect(self.reset_install_dir)
self.ui.platform_combo.currentTextChanged.connect(self.selectable.update_list)
self.ui.platform_label.setDisabled(rgame.is_installed)
self.ui.platform_combo.setDisabled(rgame.is_installed)
2023-02-08 00:06:08 +13:00
# if we are repairing, disable the SDL selection and open the dialog frame to be visible
self.selectable.setDisabled(options.repair_mode and not options.repair_and_update)
if options.repair_mode and not options.repair_and_update:
self.selectable.click()
self.advanced.ui.max_workers_spin.setValue(self.core.lgd.config.getint("Legendary", "max_workers", fallback=0))
self.advanced.ui.max_workers_spin.valueChanged.connect(self.option_changed)
self.advanced.ui.max_memory_spin.setValue(self.core.lgd.config.getint("Legendary", "max_memory", fallback=0))
self.advanced.ui.max_memory_spin.valueChanged.connect(self.option_changed)
self.advanced.ui.dl_optimizations_check.stateChanged.connect(self.option_changed)
self.advanced.ui.force_download_check.stateChanged.connect(self.option_changed)
self.advanced.ui.ignore_space_check.stateChanged.connect(self.option_changed)
self.advanced.ui.download_only_check.stateChanged.connect(
lambda: self.non_reload_option_changed("download_only")
)
self.ui.shortcut_check.stateChanged.connect(
lambda: self.non_reload_option_changed("shortcut")
)
self.reset_install_dir(self.ui.platform_combo.currentIndex())
self.selectable.update_list(self.ui.platform_combo.currentText())
self.check_incompatible_platform(self.ui.platform_combo.currentIndex())
2021-05-22 02:48:15 +12:00
self.accept_button.setEnabled(False)
2021-02-10 23:48:25 +13:00
if self.__options.overlay:
DownloadsTab: Refactor downloads tab When updates are queued, they are removed from the update's list. An exceptions is made when the queued item comes from repairing (without updating), in which case the update is disabled for the runtime. A queued item can be either removed (if it is an update it will be added back to the updates groups) or forced to be updated now. If a queued item is forced, the currently running item will be added to the front of the queue. Downloads will be queued if there is no active download but there is a queue already. The download thread is now responsible for emitting the progress to `RareGame` InstallDialog: Pass `RareGame` and `InstallOptionsModel` only as arguments. The `update`, `repair` and `silent` arguments are already part of `InstallOptionsModel` `RareGame` is used to query information about the game. InstallInfoWorker: Pass only `InstallOptionsModel` as argument Emit `InstallQueueItemModel` as result, to re-use the worker when queuing stopped games RareGame: Query and store metadata property about entitlement grant date RareGame: Add `RareEosOverlay` class that imitates `RareGame` to handle the overlay LibraryWidgetController: Remove dead signal routing code, these signals are handled by `RareGame` Directly parent library widgets instead of reparenting them GameWidgets: Remove unused signals EOSGroup: Set install location based on preferences and use EOSOverlayApp from legendary GamesTab: Connect the `progress` signals of dlcs to the base game's signals GamesTab: Remove dead code GlobalSignals: Remove `ProgresSignals` RareCore: Mangle internal signleton's names Signed-off-by: loathingKernel <142770+loathingKernel@users.noreply.github.com>
2023-01-21 13:15:06 +13:00
self.ui.platform_label.setEnabled(False)
self.ui.platform_combo.setEnabled(False)
self.advanced.ui.ignore_space_label.setEnabled(False)
self.advanced.ui.ignore_space_check.setEnabled(False)
self.advanced.ui.download_only_label.setEnabled(False)
self.advanced.ui.download_only_check.setEnabled(False)
self.ui.shortcut_label.setEnabled(False)
self.ui.shortcut_check.setEnabled(False)
self.selectable.setEnabled(False)
if pf.system() == "Darwin":
self.ui.shortcut_label.setDisabled(True)
self.ui.shortcut_check.setDisabled(True)
self.ui.shortcut_check.setChecked(False)
self.ui.shortcut_check.setToolTip(self.tr("Creating a shortcut is not supported on macOS"))
self.advanced.ui.install_prereqs_label.setEnabled(False)
self.advanced.ui.install_prereqs_check.setEnabled(False)
self.advanced.ui.install_prereqs_check.stateChanged.connect(
lambda: self.non_reload_option_changed("install_prereqs")
)
self.non_reload_option_changed("shortcut")
self.advanced.ui.install_prereqs_check.setChecked(self.__options.install_prereqs)
# lk: set object names for CSS properties
self.accept_button.setText(header)
self.accept_button.setIcon(bicon)
self.accept_button.setObjectName("InstallButton")
self.action_button.setText(self.tr("Verify"))
self.action_button.setIcon(qta_icon("fa.check"))
2022-08-11 10:47:55 +12:00
self.setCentralWidget(install_widget)
def showEvent(self, a0: QShowEvent) -> None:
if a0.spontaneous():
return super().showEvent(a0)
self.install_dir_save_callback(self.install_dir_edit.text())
super().showEvent(a0)
def execute(self):
if self.__options.silent:
self.get_download_info()
else:
self.action_handler()
self.open()
@pyqtSlot(int)
def reset_install_dir(self, index: int):
if not self.rgame.is_installed:
platform = self.ui.platform_combo.itemText(index)
default_dir = self.core.get_default_install_dir(platform)
self.install_dir_edit.setText(default_dir)
@pyqtSlot(int)
def check_incompatible_platform(self, index: int):
platform = self.ui.platform_combo.itemText(index)
if platform == "Mac" and pf.system() != "Darwin":
self.error_box(
self.tr("Warning"),
self.tr("You will not be able to run the game if you select <b>{}</b> as platform").format(platform),
)
else:
self.error_box()
def get_options(self):
base_path = os.path.join(self.install_dir_edit.text(), ".overlay" if self.__options.overlay else "")
self.__options.base_path = "" if self.rgame.is_installed else base_path
self.__options.platform = self.ui.platform_combo.currentText()
self.__options.create_shortcut = self.ui.shortcut_check.isChecked()
self.__options.max_workers = self.advanced.ui.max_workers_spin.value()
self.__options.shared_memory = self.advanced.ui.max_memory_spin.value()
self.__options.order_opt = self.advanced.ui.dl_optimizations_check.isChecked()
self.__options.force = self.advanced.ui.force_download_check.isChecked()
self.__options.ignore_space = self.advanced.ui.ignore_space_check.isChecked()
self.__options.no_install = self.advanced.ui.download_only_check.isChecked()
self.__options.install_prereqs = self.advanced.ui.install_prereqs_check.isChecked()
self.__options.install_tag = self.selectable.install_tags()
self.__options.reset_sdl = True
def get_download_info(self):
DownloadsTab: Refactor downloads tab When updates are queued, they are removed from the update's list. An exceptions is made when the queued item comes from repairing (without updating), in which case the update is disabled for the runtime. A queued item can be either removed (if it is an update it will be added back to the updates groups) or forced to be updated now. If a queued item is forced, the currently running item will be added to the front of the queue. Downloads will be queued if there is no active download but there is a queue already. The download thread is now responsible for emitting the progress to `RareGame` InstallDialog: Pass `RareGame` and `InstallOptionsModel` only as arguments. The `update`, `repair` and `silent` arguments are already part of `InstallOptionsModel` `RareGame` is used to query information about the game. InstallInfoWorker: Pass only `InstallOptionsModel` as argument Emit `InstallQueueItemModel` as result, to re-use the worker when queuing stopped games RareGame: Query and store metadata property about entitlement grant date RareGame: Add `RareEosOverlay` class that imitates `RareGame` to handle the overlay LibraryWidgetController: Remove dead signal routing code, these signals are handled by `RareGame` Directly parent library widgets instead of reparenting them GameWidgets: Remove unused signals EOSGroup: Set install location based on preferences and use EOSOverlayApp from legendary GamesTab: Connect the `progress` signals of dlcs to the base game's signals GamesTab: Remove dead code GlobalSignals: Remove `ProgresSignals` RareCore: Mangle internal signleton's names Signed-off-by: loathingKernel <142770+loathingKernel@users.noreply.github.com>
2023-01-21 13:15:06 +13:00
self.__download = None
info_worker = InstallInfoWorker(self.core, self.__options)
info_worker.signals.result.connect(self.on_worker_result)
info_worker.signals.failed.connect(self.on_worker_failed)
self.threadpool.start(info_worker)
def action_handler(self):
self.error_box()
2021-05-22 02:48:15 +12:00
message = self.tr("Updating...")
self.ui.download_size_text.setText(message)
self.ui.download_size_text.setStyleSheet("font-style: italic; font-weight: normal")
self.ui.install_size_text.setText(message)
self.ui.install_size_text.setStyleSheet("font-style: italic; font-weight: normal")
self.setActive(True)
self.options_changed = False
self.get_options()
self.get_download_info()
@pyqtSlot()
def option_changed(self):
self.options_changed = True
self.accept_button.setEnabled(False)
self.action_button.setEnabled(not self.active())
def install_dir_edit_callback(self, path: str) -> Tuple[bool, str, int]:
self.option_changed()
return True, path, IndicatorReasonsCommon.VALID
def install_dir_save_callback(self, path: str):
2023-04-17 04:38:57 +12:00
if not os.path.exists(path):
return
_, _, free_space = shutil.disk_usage(path)
self.ui.avail_space.setText(format_size(free_space))
def non_reload_option_changed(self, option: str):
if option == "download_only":
self.__options.no_install = self.advanced.ui.download_only_check.isChecked()
elif option == "shortcut":
QSettings().setValue("create_shortcut", self.ui.shortcut_check.isChecked())
self.__options.create_shortcut = self.ui.shortcut_check.isChecked()
elif option == "install_prereqs":
self.__options.install_prereqs = self.advanced.ui.install_prereqs_check.isChecked()
2021-02-18 05:46:03 +13:00
@staticmethod
def same_platform(download: InstallDownloadModel) -> bool:
platform = download.igame.platform
if pf.system() == "Windows":
return platform in {"Windows", "Win32"}
elif pf.system() == "Darwin":
return platform == "Mac"
else:
return False
@pyqtSlot(InstallDownloadModel)
def on_worker_result(self, download: InstallDownloadModel):
self.setActive(False)
self.__download = download
download_size = download.analysis.dl_size
install_size = download.analysis.install_size
DownloadsTab: Refactor downloads tab When updates are queued, they are removed from the update's list. An exceptions is made when the queued item comes from repairing (without updating), in which case the update is disabled for the runtime. A queued item can be either removed (if it is an update it will be added back to the updates groups) or forced to be updated now. If a queued item is forced, the currently running item will be added to the front of the queue. Downloads will be queued if there is no active download but there is a queue already. The download thread is now responsible for emitting the progress to `RareGame` InstallDialog: Pass `RareGame` and `InstallOptionsModel` only as arguments. The `update`, `repair` and `silent` arguments are already part of `InstallOptionsModel` `RareGame` is used to query information about the game. InstallInfoWorker: Pass only `InstallOptionsModel` as argument Emit `InstallQueueItemModel` as result, to re-use the worker when queuing stopped games RareGame: Query and store metadata property about entitlement grant date RareGame: Add `RareEosOverlay` class that imitates `RareGame` to handle the overlay LibraryWidgetController: Remove dead signal routing code, these signals are handled by `RareGame` Directly parent library widgets instead of reparenting them GameWidgets: Remove unused signals EOSGroup: Set install location based on preferences and use EOSOverlayApp from legendary GamesTab: Connect the `progress` signals of dlcs to the base game's signals GamesTab: Remove dead code GlobalSignals: Remove `ProgresSignals` RareCore: Mangle internal signleton's names Signed-off-by: loathingKernel <142770+loathingKernel@users.noreply.github.com>
2023-01-21 13:15:06 +13:00
# install_size = self.dl_item.download.analysis.disk_space_delta
if download_size or (not download_size and (download.game.is_dlc or download.repair)):
self.ui.download_size_text.setText(format_size(download_size))
self.ui.download_size_text.setStyleSheet("font-style: normal; font-weight: bold")
self.accept_button.setEnabled(not self.options_changed)
else:
self.ui.install_size_text.setText(self.tr("Game already installed"))
self.ui.install_size_text.setStyleSheet("font-style: italics; font-weight: normal")
self.ui.install_size_text.setText(format_size(install_size))
self.ui.install_size_text.setStyleSheet("font-style: normal; font-weight: bold")
self.action_button.setEnabled(self.options_changed)
has_prereqs = bool(download.igame.prereq_info) and not download.igame.prereq_info.get("installed", False)
if has_prereqs:
prereq_name = download.igame.prereq_info.get("name", "")
prereq_path = os.path.split(download.igame.prereq_info.get("path", ""))[-1]
prereq_desc = prereq_name if prereq_name else prereq_path
self.advanced.ui.install_prereqs_check.setText(self.tr("Also install: {}").format(prereq_desc))
else:
self.advanced.ui.install_prereqs_check.setText("")
# Offer to install prerequisites only on same platforms
self.advanced.ui.install_prereqs_label.setEnabled(has_prereqs)
self.advanced.ui.install_prereqs_check.setEnabled(has_prereqs)
self.advanced.ui.install_prereqs_check.setChecked(has_prereqs and self.same_platform(download))
if self.__options.silent:
self.accept()
def on_worker_failed(self, message: str):
self.setActive(False)
error_text = self.tr("Error")
self.ui.download_size_text.setText(error_text)
self.ui.install_size_text.setText(error_text)
self.error_box(error_text, message)
self.action_button.setEnabled(self.options_changed)
self.accept_button.setEnabled(False)
if self.__options.silent:
self.open()
2021-02-18 05:46:03 +13:00
def error_box(self, label: str = "", message: str = ""):
self.ui.warning_label.setVisible(bool(label))
self.ui.warning_label.setText(label)
self.ui.warning_text.setVisible(bool(message))
self.ui.warning_text.setText(message)
def done_handler(self):
self.threadpool.clear()
self.threadpool.waitForDone()
self.result_ready.emit(self.__queue_item)
# lk: __download is already set at this point so just do nothing.
def accept_handler(self):
self.__queue_item = InstallQueueItemModel(options=self.__options, download=self.__download)
def reject_handler(self):
self.__queue_item = InstallQueueItemModel(options=self.__options, download=None)
2021-02-18 05:46:03 +13:00
class TagCheckBox(QCheckBox):
def __init__(self, text, desc, tags: List[str], parent=None):
super(TagCheckBox, self).__init__(parent)
2021-05-22 02:48:15 +12:00
self.setText(text)
self.setToolTip(desc)
self.tags = tags
2021-05-22 02:48:15 +12:00
def isChecked(self) -> Union[bool, List[str]]:
return self.tags if super(TagCheckBox, self).isChecked() else False