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>
This commit is contained in:
parent
1470aa9eb3
commit
4063195b4d
28 changed files with 954 additions and 771 deletions
|
@ -3,11 +3,12 @@ import platform as pf
|
||||||
import sys
|
import sys
|
||||||
from typing import Tuple, List, Union, Optional
|
from typing import Tuple, List, Union, Optional
|
||||||
|
|
||||||
from PyQt5.QtCore import Qt, QObject, QRunnable, QThreadPool, pyqtSignal, pyqtSlot, QSettings
|
from PyQt5.QtCore import QObject, QRunnable, pyqtSignal, pyqtSlot
|
||||||
|
from PyQt5.QtCore import Qt, QThreadPool, QSettings
|
||||||
from PyQt5.QtGui import QCloseEvent, QKeyEvent
|
from PyQt5.QtGui import QCloseEvent, QKeyEvent
|
||||||
from PyQt5.QtWidgets import QDialog, QFileDialog, QCheckBox, QLayout, QWidget, QVBoxLayout, QApplication
|
from PyQt5.QtWidgets import QDialog, QFileDialog, QCheckBox, QLayout, QWidget, QVBoxLayout, QApplication
|
||||||
|
from legendary.lfs.eos import EOSOverlayApp
|
||||||
from legendary.models.downloading import ConditionCheckResult
|
from legendary.models.downloading import ConditionCheckResult
|
||||||
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.lgndr.cli import LegendaryCLI
|
from rare.lgndr.cli import LegendaryCLI
|
||||||
|
@ -15,8 +16,9 @@ from rare.lgndr.core import LegendaryCore
|
||||||
from rare.lgndr.glue.arguments import LgndrInstallGameArgs
|
from rare.lgndr.glue.arguments import LgndrInstallGameArgs
|
||||||
from rare.lgndr.glue.exception import LgndrException
|
from rare.lgndr.glue.exception import LgndrException
|
||||||
from rare.lgndr.glue.monkeys import LgndrIndirectStatus
|
from rare.lgndr.glue.monkeys import LgndrIndirectStatus
|
||||||
from rare.models.install import InstallDownloadModel, InstallQueueItemModel
|
from rare.models.game import RareGame
|
||||||
from rare.shared import LegendaryCoreSingleton, ApiResultsSingleton, ArgumentsSingleton
|
from rare.models.install import InstallDownloadModel, InstallQueueItemModel, InstallOptionsModel
|
||||||
|
from rare.shared import LegendaryCoreSingleton, ArgumentsSingleton
|
||||||
from rare.ui.components.dialogs.install_dialog import Ui_InstallDialog
|
from rare.ui.components.dialogs.install_dialog import Ui_InstallDialog
|
||||||
from rare.ui.components.dialogs.install_dialog_advanced import Ui_InstallDialogAdvanced
|
from rare.ui.components.dialogs.install_dialog_advanced import Ui_InstallDialogAdvanced
|
||||||
from rare.utils import config_helper
|
from rare.utils import config_helper
|
||||||
|
@ -37,7 +39,7 @@ class InstallDialogAdvanced(CollapsibleFrame):
|
||||||
class InstallDialog(QDialog):
|
class InstallDialog(QDialog):
|
||||||
result_ready = pyqtSignal(InstallQueueItemModel)
|
result_ready = pyqtSignal(InstallQueueItemModel)
|
||||||
|
|
||||||
def __init__(self, dl_item: InstallQueueItemModel, update=False, repair=False, silent=False, parent=None):
|
def __init__(self, rgame: RareGame, options: InstallOptionsModel, parent=None,):
|
||||||
super(InstallDialog, self).__init__(parent)
|
super(InstallDialog, self).__init__(parent)
|
||||||
self.setAttribute(Qt.WA_DeleteOnClose, True)
|
self.setAttribute(Qt.WA_DeleteOnClose, True)
|
||||||
self.setWindowFlags(Qt.Dialog | Qt.CustomizeWindowHint | Qt.WindowTitleHint)
|
self.setWindowFlags(Qt.Dialog | Qt.CustomizeWindowHint | Qt.WindowTitleHint)
|
||||||
|
@ -45,14 +47,9 @@ class InstallDialog(QDialog):
|
||||||
self.ui.setupUi(self)
|
self.ui.setupUi(self)
|
||||||
|
|
||||||
self.core = LegendaryCoreSingleton()
|
self.core = LegendaryCoreSingleton()
|
||||||
self.api_results = ApiResultsSingleton()
|
self.rgame = rgame
|
||||||
self.dl_item = dl_item
|
self.options = options
|
||||||
self.app_name = self.dl_item.options.app_name
|
self.__download: Optional[InstallDownloadModel] = None
|
||||||
self.game = (
|
|
||||||
self.core.get_game(self.app_name)
|
|
||||||
if not self.dl_item.options.overlay
|
|
||||||
else Game(app_name=self.app_name, app_title="Epic Overlay")
|
|
||||||
)
|
|
||||||
|
|
||||||
self.advanced = InstallDialogAdvanced(parent=self)
|
self.advanced = InstallDialogAdvanced(parent=self)
|
||||||
self.ui.advanced_layout.addWidget(self.advanced)
|
self.ui.advanced_layout.addWidget(self.advanced)
|
||||||
|
@ -60,12 +57,6 @@ class InstallDialog(QDialog):
|
||||||
self.selectable = CollapsibleFrame(widget=None, title=self.tr("Optional downloads"), parent=self)
|
self.selectable = CollapsibleFrame(widget=None, title=self.tr("Optional downloads"), parent=self)
|
||||||
self.ui.selectable_layout.addWidget(self.selectable)
|
self.ui.selectable_layout.addWidget(self.selectable)
|
||||||
|
|
||||||
self.game_path = self.game.metadata.get("customAttributes", {}).get("FolderName", {}).get("value", "")
|
|
||||||
|
|
||||||
self.update = update
|
|
||||||
self.repair = repair
|
|
||||||
self.silent = silent
|
|
||||||
|
|
||||||
self.options_changed = False
|
self.options_changed = False
|
||||||
self.worker_running = False
|
self.worker_running = False
|
||||||
self.reject_close = True
|
self.reject_close = True
|
||||||
|
@ -73,24 +64,31 @@ class InstallDialog(QDialog):
|
||||||
self.threadpool = QThreadPool(self)
|
self.threadpool = QThreadPool(self)
|
||||||
self.threadpool.setMaxThreadCount(1)
|
self.threadpool.setMaxThreadCount(1)
|
||||||
|
|
||||||
header = self.tr("Update") if update else self.tr("Install")
|
if options.repair_mode:
|
||||||
self.ui.install_dialog_label.setText(f'<h3>{header} "{self.game.app_title}"</h3>')
|
header = self.tr("Repair")
|
||||||
self.setWindowTitle(f'{QApplication.instance().applicationName()} - {header} "{self.game.app_title}"')
|
if options.repair_and_update:
|
||||||
|
header = self.tr("Repair and update")
|
||||||
|
elif options.update:
|
||||||
|
header = self.tr("Update")
|
||||||
|
else:
|
||||||
|
header = self.tr("Install")
|
||||||
|
self.ui.install_dialog_label.setText(f'<h3>{header} "{self.rgame.app_title}"</h3>')
|
||||||
|
self.setWindowTitle(f'{QApplication.instance().applicationName()} - {header} "{self.rgame.app_title}"')
|
||||||
|
|
||||||
if not self.dl_item.options.base_path:
|
if not self.options.base_path:
|
||||||
self.dl_item.options.base_path = self.core.lgd.config.get(
|
self.options.base_path = self.core.lgd.config.get(
|
||||||
"Legendary", "install_dir", fallback=os.path.expanduser("~/legendary")
|
"Legendary", "install_dir", fallback=os.path.expanduser("~/legendary")
|
||||||
)
|
)
|
||||||
|
|
||||||
self.install_dir_edit = PathEdit(
|
self.install_dir_edit = PathEdit(
|
||||||
path=self.dl_item.options.base_path,
|
path=self.options.base_path,
|
||||||
file_type=QFileDialog.DirectoryOnly,
|
file_type=QFileDialog.DirectoryOnly,
|
||||||
edit_func=self.option_changed,
|
edit_func=self.option_changed,
|
||||||
parent=self,
|
parent=self,
|
||||||
)
|
)
|
||||||
self.ui.install_dir_layout.addWidget(self.install_dir_edit)
|
self.ui.install_dir_layout.addWidget(self.install_dir_edit)
|
||||||
|
|
||||||
if self.update:
|
if self.options.update:
|
||||||
self.ui.install_dir_label.setEnabled(False)
|
self.ui.install_dir_label.setEnabled(False)
|
||||||
self.install_dir_edit.setEnabled(False)
|
self.install_dir_edit.setEnabled(False)
|
||||||
self.ui.shortcut_label.setEnabled(False)
|
self.ui.shortcut_label.setEnabled(False)
|
||||||
|
@ -101,9 +99,9 @@ class InstallDialog(QDialog):
|
||||||
self.error_box()
|
self.error_box()
|
||||||
|
|
||||||
platforms = ["Windows"]
|
platforms = ["Windows"]
|
||||||
if dl_item.options.app_name in self.api_results.bit32_games:
|
if self.rgame.is_win32:
|
||||||
platforms.append("Win32")
|
platforms.append("Win32")
|
||||||
if dl_item.options.app_name in self.api_results.mac_games:
|
if self.rgame.is_mac:
|
||||||
platforms.append("Mac")
|
platforms.append("Mac")
|
||||||
self.ui.platform_combo.addItems(platforms)
|
self.ui.platform_combo.addItems(platforms)
|
||||||
self.ui.platform_combo.currentIndexChanged.connect(lambda: self.option_changed(None))
|
self.ui.platform_combo.currentIndexChanged.connect(lambda: self.option_changed(None))
|
||||||
|
@ -145,16 +143,16 @@ class InstallDialog(QDialog):
|
||||||
|
|
||||||
self.ui.install_button.setEnabled(False)
|
self.ui.install_button.setEnabled(False)
|
||||||
|
|
||||||
if self.dl_item.options.overlay:
|
if self.options.overlay:
|
||||||
self.ui.platform_label.setVisible(False)
|
self.ui.platform_label.setEnabled(False)
|
||||||
self.ui.platform_combo.setVisible(False)
|
self.ui.platform_combo.setEnabled(False)
|
||||||
self.advanced.ui.ignore_space_label.setVisible(False)
|
self.advanced.ui.ignore_space_label.setEnabled(False)
|
||||||
self.advanced.ui.ignore_space_check.setVisible(False)
|
self.advanced.ui.ignore_space_check.setEnabled(False)
|
||||||
self.advanced.ui.download_only_label.setVisible(False)
|
self.advanced.ui.download_only_label.setEnabled(False)
|
||||||
self.advanced.ui.download_only_check.setVisible(False)
|
self.advanced.ui.download_only_check.setEnabled(False)
|
||||||
self.ui.shortcut_label.setVisible(False)
|
self.ui.shortcut_label.setEnabled(False)
|
||||||
self.ui.shortcut_check.setVisible(False)
|
self.ui.shortcut_check.setEnabled(False)
|
||||||
self.selectable.setVisible(False)
|
self.selectable.setEnabled(False)
|
||||||
|
|
||||||
if pf.system() == "Darwin":
|
if pf.system() == "Darwin":
|
||||||
self.ui.shortcut_check.setDisabled(True)
|
self.ui.shortcut_check.setDisabled(True)
|
||||||
|
@ -173,12 +171,12 @@ class InstallDialog(QDialog):
|
||||||
self.ui.verify_button.clicked.connect(self.verify_clicked)
|
self.ui.verify_button.clicked.connect(self.verify_clicked)
|
||||||
self.ui.install_button.clicked.connect(self.install_clicked)
|
self.ui.install_button.clicked.connect(self.install_clicked)
|
||||||
|
|
||||||
self.advanced.ui.install_prereqs_check.setChecked(self.dl_item.options.install_prereqs)
|
self.advanced.ui.install_prereqs_check.setChecked(self.options.install_prereqs)
|
||||||
|
|
||||||
self.ui.install_dialog_layout.setSizeConstraint(QLayout.SetFixedSize)
|
self.ui.install_dialog_layout.setSizeConstraint(QLayout.SetFixedSize)
|
||||||
|
|
||||||
def execute(self):
|
def execute(self):
|
||||||
if self.silent:
|
if self.options.silent:
|
||||||
self.reject_close = False
|
self.reject_close = False
|
||||||
self.get_download_info()
|
self.get_download_info()
|
||||||
else:
|
else:
|
||||||
|
@ -192,10 +190,10 @@ class InstallDialog(QDialog):
|
||||||
cb.deleteLater()
|
cb.deleteLater()
|
||||||
self.selectable_checks.clear()
|
self.selectable_checks.clear()
|
||||||
|
|
||||||
if config_tags := self.core.lgd.config.get(self.game.app_name, 'install_tags', fallback=None):
|
if config_tags := self.core.lgd.config.get(self.rgame.app_name, 'install_tags', fallback=None):
|
||||||
self.config_tags = config_tags.split(",")
|
self.config_tags = config_tags.split(",")
|
||||||
config_disable_sdl = self.core.lgd.config.getboolean(self.game.app_name, 'disable_sdl', fallback=False)
|
config_disable_sdl = self.core.lgd.config.getboolean(self.rgame.app_name, 'disable_sdl', fallback=False)
|
||||||
sdl_name = get_sdl_appname(self.game.app_name)
|
sdl_name = get_sdl_appname(self.rgame.app_name)
|
||||||
if not config_disable_sdl and sdl_name is not None:
|
if not config_disable_sdl and sdl_name is not None:
|
||||||
# FIXME: this should be updated whenever platform changes
|
# FIXME: this should be updated whenever platform changes
|
||||||
sdl_data = self.core.get_sdl_data(sdl_name, platform=platform)
|
sdl_data = self.core.get_sdl_data(sdl_name, platform=platform)
|
||||||
|
@ -220,28 +218,26 @@ class InstallDialog(QDialog):
|
||||||
self.selectable.setDisabled(True)
|
self.selectable.setDisabled(True)
|
||||||
|
|
||||||
def get_options(self):
|
def get_options(self):
|
||||||
self.dl_item.options.base_path = self.install_dir_edit.text() if not self.update else None
|
self.options.base_path = self.install_dir_edit.text() if not self.options.update else None
|
||||||
|
self.options.max_workers = self.advanced.ui.max_workers_spin.value()
|
||||||
self.dl_item.options.max_workers = self.advanced.ui.max_workers_spin.value()
|
self.options.shared_memory = self.advanced.ui.max_memory_spin.value()
|
||||||
self.dl_item.options.shared_memory = self.advanced.ui.max_memory_spin.value()
|
self.options.order_opt = self.advanced.ui.dl_optimizations_check.isChecked()
|
||||||
self.dl_item.options.order_opt = self.advanced.ui.dl_optimizations_check.isChecked()
|
self.options.force = self.advanced.ui.force_download_check.isChecked()
|
||||||
self.dl_item.options.force = self.advanced.ui.force_download_check.isChecked()
|
self.options.ignore_space = self.advanced.ui.ignore_space_check.isChecked()
|
||||||
self.dl_item.options.ignore_space = self.advanced.ui.ignore_space_check.isChecked()
|
self.options.no_install = self.advanced.ui.download_only_check.isChecked()
|
||||||
self.dl_item.options.no_install = self.advanced.ui.download_only_check.isChecked()
|
self.options.platform = self.ui.platform_combo.currentText()
|
||||||
self.dl_item.options.platform = self.ui.platform_combo.currentText()
|
self.options.install_prereqs = self.advanced.ui.install_prereqs_check.isChecked()
|
||||||
self.dl_item.options.install_prereqs = self.advanced.ui.install_prereqs_check.isChecked()
|
self.options.create_shortcut = self.ui.shortcut_check.isChecked()
|
||||||
self.dl_item.options.create_shortcut = self.ui.shortcut_check.isChecked()
|
|
||||||
if self.selectable_checks:
|
if self.selectable_checks:
|
||||||
self.dl_item.options.install_tag = [""]
|
self.options.install_tag = [""]
|
||||||
for cb in self.selectable_checks:
|
for cb in self.selectable_checks:
|
||||||
if data := cb.isChecked():
|
if data := cb.isChecked():
|
||||||
# noinspection PyTypeChecker
|
# noinspection PyTypeChecker
|
||||||
self.dl_item.options.install_tag.extend(data)
|
self.options.install_tag.extend(data)
|
||||||
|
|
||||||
def get_download_info(self):
|
def get_download_info(self):
|
||||||
self.dl_item.download = None
|
self.__download = None
|
||||||
info_worker = InstallInfoWorker(self.core, self.dl_item, self.game)
|
info_worker = InstallInfoWorker(self.core, self.options)
|
||||||
info_worker.setAutoDelete(True)
|
|
||||||
info_worker.signals.result.connect(self.on_worker_result)
|
info_worker.signals.result.connect(self.on_worker_result)
|
||||||
info_worker.signals.failed.connect(self.on_worker_failed)
|
info_worker.signals.failed.connect(self.on_worker_failed)
|
||||||
info_worker.signals.finished.connect(self.on_worker_finished)
|
info_worker.signals.finished.connect(self.on_worker_finished)
|
||||||
|
@ -270,21 +266,21 @@ class InstallDialog(QDialog):
|
||||||
|
|
||||||
def non_reload_option_changed(self, option: str):
|
def non_reload_option_changed(self, option: str):
|
||||||
if option == "download_only":
|
if option == "download_only":
|
||||||
self.dl_item.options.no_install = self.advanced.ui.download_only_check.isChecked()
|
self.options.no_install = self.advanced.ui.download_only_check.isChecked()
|
||||||
elif option == "shortcut":
|
elif option == "shortcut":
|
||||||
QSettings().setValue("create_shortcut", self.ui.shortcut_check.isChecked())
|
QSettings().setValue("create_shortcut", self.ui.shortcut_check.isChecked())
|
||||||
self.dl_item.options.create_shortcut = self.ui.shortcut_check.isChecked()
|
self.options.create_shortcut = self.ui.shortcut_check.isChecked()
|
||||||
elif option == "install_prereqs":
|
elif option == "install_prereqs":
|
||||||
self.dl_item.options.install_prereqs = self.advanced.ui.install_prereqs_check.isChecked()
|
self.options.install_prereqs = self.advanced.ui.install_prereqs_check.isChecked()
|
||||||
|
|
||||||
def cancel_clicked(self):
|
def cancel_clicked(self):
|
||||||
if self.config_tags:
|
if self.config_tags:
|
||||||
config_helper.add_option(self.game.app_name, 'install_tags', ','.join(self.config_tags))
|
config_helper.add_option(self.rgame.app_name, 'install_tags', ','.join(self.config_tags))
|
||||||
else:
|
else:
|
||||||
# lk: this is purely for cleaning any install tags we might have added erroneously to the config
|
# lk: this is purely for cleaning any install tags we might have added erroneously to the config
|
||||||
config_helper.remove_option(self.game.app_name, 'install_tags')
|
config_helper.remove_option(self.rgame.app_name, 'install_tags')
|
||||||
|
|
||||||
self.dl_item.download = None
|
self.__download = None
|
||||||
self.reject_close = False
|
self.reject_close = False
|
||||||
self.close()
|
self.close()
|
||||||
|
|
||||||
|
@ -292,33 +288,35 @@ class InstallDialog(QDialog):
|
||||||
self.reject_close = False
|
self.reject_close = False
|
||||||
self.close()
|
self.close()
|
||||||
|
|
||||||
def on_worker_result(self, dl_item: InstallDownloadModel):
|
@pyqtSlot(InstallQueueItemModel)
|
||||||
self.dl_item.download = dl_item
|
def on_worker_result(self, dl_item: InstallQueueItemModel):
|
||||||
download_size = self.dl_item.download.analysis.dl_size
|
self.__download = dl_item.download
|
||||||
install_size = self.dl_item.download.analysis.install_size
|
download_size = dl_item.download.analysis.dl_size
|
||||||
|
install_size = dl_item.download.analysis.install_size
|
||||||
|
# install_size = self.dl_item.download.analysis.disk_space_delta
|
||||||
if download_size:
|
if download_size:
|
||||||
self.ui.download_size_text.setText("{}".format(get_size(download_size)))
|
self.ui.download_size_text.setText(get_size(download_size))
|
||||||
self.ui.download_size_text.setStyleSheet("font-style: normal; font-weight: bold")
|
self.ui.download_size_text.setStyleSheet("font-style: normal; font-weight: bold")
|
||||||
self.ui.install_button.setEnabled(not self.options_changed)
|
self.ui.install_button.setEnabled(not self.options_changed)
|
||||||
else:
|
else:
|
||||||
self.ui.install_size_text.setText(self.tr("Game already installed"))
|
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.setStyleSheet("font-style: italics; font-weight: normal")
|
||||||
self.ui.install_size_text.setText("{}".format(get_size(install_size)))
|
self.ui.install_size_text.setText(get_size(install_size))
|
||||||
self.ui.install_size_text.setStyleSheet("font-style: normal; font-weight: bold")
|
self.ui.install_size_text.setStyleSheet("font-style: normal; font-weight: bold")
|
||||||
self.ui.verify_button.setEnabled(self.options_changed)
|
self.ui.verify_button.setEnabled(self.options_changed)
|
||||||
self.ui.cancel_button.setEnabled(True)
|
self.ui.cancel_button.setEnabled(True)
|
||||||
if pf.system() == "Windows" or ArgumentsSingleton().debug:
|
if pf.system() == "Windows" or ArgumentsSingleton().debug:
|
||||||
if dl_item.igame.prereq_info and not dl_item.igame.prereq_info.get("installed", False):
|
if dl_item.download.igame.prereq_info and not dl_item.download.igame.prereq_info.get("installed", False):
|
||||||
self.advanced.ui.install_prereqs_check.setEnabled(True)
|
self.advanced.ui.install_prereqs_check.setEnabled(True)
|
||||||
self.advanced.ui.install_prereqs_label.setEnabled(True)
|
self.advanced.ui.install_prereqs_label.setEnabled(True)
|
||||||
self.advanced.ui.install_prereqs_check.setChecked(True)
|
self.advanced.ui.install_prereqs_check.setChecked(True)
|
||||||
prereq_name = dl_item.igame.prereq_info.get("name", "")
|
prereq_name = dl_item.download.igame.prereq_info.get("name", "")
|
||||||
prereq_path = os.path.split(dl_item.igame.prereq_info.get("path", ""))[-1]
|
prereq_path = os.path.split(dl_item.download.igame.prereq_info.get("path", ""))[-1]
|
||||||
prereq_desc = prereq_name if prereq_name else prereq_path
|
prereq_desc = prereq_name if prereq_name else prereq_path
|
||||||
self.advanced.ui.install_prereqs_check.setText(
|
self.advanced.ui.install_prereqs_check.setText(
|
||||||
self.tr("Also install: {}").format(prereq_desc)
|
self.tr("Also install: {}").format(prereq_desc)
|
||||||
)
|
)
|
||||||
if self.silent:
|
if self.options.silent:
|
||||||
self.close()
|
self.close()
|
||||||
|
|
||||||
def on_worker_failed(self, message: str):
|
def on_worker_failed(self, message: str):
|
||||||
|
@ -328,7 +326,7 @@ class InstallDialog(QDialog):
|
||||||
self.error_box(error_text, message)
|
self.error_box(error_text, message)
|
||||||
self.ui.verify_button.setEnabled(self.options_changed)
|
self.ui.verify_button.setEnabled(self.options_changed)
|
||||||
self.ui.cancel_button.setEnabled(True)
|
self.ui.cancel_button.setEnabled(True)
|
||||||
if self.silent:
|
if self.options.silent:
|
||||||
self.show()
|
self.show()
|
||||||
|
|
||||||
def error_box(self, label: str = "", message: str = ""):
|
def error_box(self, label: str = "", message: str = ""):
|
||||||
|
@ -349,7 +347,7 @@ class InstallDialog(QDialog):
|
||||||
else:
|
else:
|
||||||
self.threadpool.clear()
|
self.threadpool.clear()
|
||||||
self.threadpool.waitForDone()
|
self.threadpool.waitForDone()
|
||||||
self.result_ready.emit(self.dl_item)
|
self.result_ready.emit(InstallQueueItemModel(options=self.options, download=self.__download))
|
||||||
super(InstallDialog, self).closeEvent(a0)
|
super(InstallDialog, self).closeEvent(a0)
|
||||||
|
|
||||||
def keyPressEvent(self, e: QKeyEvent) -> None:
|
def keyPressEvent(self, e: QKeyEvent) -> None:
|
||||||
|
@ -359,51 +357,51 @@ class InstallDialog(QDialog):
|
||||||
|
|
||||||
class InstallInfoWorker(QRunnable):
|
class InstallInfoWorker(QRunnable):
|
||||||
class Signals(QObject):
|
class Signals(QObject):
|
||||||
result = pyqtSignal(InstallDownloadModel)
|
result = pyqtSignal(InstallQueueItemModel)
|
||||||
failed = pyqtSignal(str)
|
failed = pyqtSignal(str)
|
||||||
finished = pyqtSignal()
|
finished = pyqtSignal()
|
||||||
|
|
||||||
def __init__(self, core: LegendaryCore, dl_item: InstallQueueItemModel, game: Game = None):
|
def __init__(self, core: LegendaryCore, options: InstallOptionsModel):
|
||||||
sys.excepthook = sys.__excepthook__
|
sys.excepthook = sys.__excepthook__
|
||||||
super(InstallInfoWorker, self).__init__()
|
super(InstallInfoWorker, self).__init__()
|
||||||
|
self.setAutoDelete(True)
|
||||||
self.signals = InstallInfoWorker.Signals()
|
self.signals = InstallInfoWorker.Signals()
|
||||||
self.core = core
|
self.core = core
|
||||||
self.dl_item = dl_item
|
self.options = options
|
||||||
self.game = game
|
|
||||||
|
|
||||||
@pyqtSlot()
|
@pyqtSlot()
|
||||||
def run(self):
|
def run(self):
|
||||||
try:
|
try:
|
||||||
if not self.dl_item.options.overlay:
|
if not self.options.overlay:
|
||||||
cli = LegendaryCLI(self.core)
|
cli = LegendaryCLI(self.core)
|
||||||
status = LgndrIndirectStatus()
|
status = LgndrIndirectStatus()
|
||||||
result = cli.install_game(
|
result = cli.install_game(
|
||||||
LgndrInstallGameArgs(**self.dl_item.options.as_install_kwargs(), indirect_status=status)
|
LgndrInstallGameArgs(**self.options.as_install_kwargs(), indirect_status=status)
|
||||||
)
|
)
|
||||||
if result:
|
if result:
|
||||||
download = InstallDownloadModel(*result)
|
download = InstallDownloadModel(*result)
|
||||||
else:
|
else:
|
||||||
raise LgndrException(status.message)
|
raise LgndrException(status.message)
|
||||||
else:
|
else:
|
||||||
if not os.path.exists(path := self.dl_item.options.base_path):
|
if not os.path.exists(path := self.options.base_path):
|
||||||
os.makedirs(path)
|
os.makedirs(path)
|
||||||
|
|
||||||
dlm, analysis, igame = self.core.prepare_overlay_install(
|
dlm, analysis, igame = self.core.prepare_overlay_install(
|
||||||
path=self.dl_item.options.base_path
|
path=self.options.base_path
|
||||||
)
|
)
|
||||||
|
|
||||||
download = InstallDownloadModel(
|
download = InstallDownloadModel(
|
||||||
dlm=dlm,
|
dlm=dlm,
|
||||||
analysis=analysis,
|
analysis=analysis,
|
||||||
igame=igame,
|
igame=igame,
|
||||||
game=self.game,
|
game=EOSOverlayApp,
|
||||||
repair=False,
|
repair=False,
|
||||||
repair_file="",
|
repair_file="",
|
||||||
res=ConditionCheckResult(), # empty
|
res=ConditionCheckResult(), # empty
|
||||||
)
|
)
|
||||||
|
|
||||||
if not download.res or not download.res.failures:
|
if not download.res or not download.res.failures:
|
||||||
self.signals.result.emit(download)
|
self.signals.result.emit(InstallQueueItemModel(options=self.options, download=download))
|
||||||
else:
|
else:
|
||||||
self.signals.failed.emit("\n".join(str(i) for i in download.res.failures))
|
self.signals.failed.emit("\n".join(str(i) for i in download.res.failures))
|
||||||
except LgndrException as ret:
|
except LgndrException as ret:
|
||||||
|
@ -421,3 +419,6 @@ class TagCheckBox(QCheckBox):
|
||||||
|
|
||||||
def isChecked(self) -> Union[bool, List[str]]:
|
def isChecked(self) -> Union[bool, List[str]]:
|
||||||
return self.tags if super(TagCheckBox, self).isChecked() else False
|
return self.tags if super(TagCheckBox, self).isChecked() else False
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -282,6 +282,8 @@ class LaunchDialog(QDialog):
|
||||||
if self.completed >= 2:
|
if self.completed >= 2:
|
||||||
logger.info("App starting")
|
logger.info("App starting")
|
||||||
ApiResultsSingleton(self.api_results)
|
ApiResultsSingleton(self.api_results)
|
||||||
|
# FIXME: Add this to RareCore
|
||||||
|
self.core.lgd.entitlements = self.core.egs.get_user_entitlements()
|
||||||
self.completed += 1
|
self.completed += 1
|
||||||
self.start_app.emit()
|
self.start_app.emit()
|
||||||
|
|
||||||
|
|
|
@ -164,9 +164,7 @@ class MainWindow(QMainWindow):
|
||||||
defaultButton=QMessageBox.No,
|
defaultButton=QMessageBox.No,
|
||||||
)
|
)
|
||||||
if reply == QMessageBox.Yes:
|
if reply == QMessageBox.Yes:
|
||||||
# clear queue
|
self.tab_widget.downloads_tab.stop_download(on_exit=True)
|
||||||
self.tab_widget.downloads_tab.queue_widget.update_queue([])
|
|
||||||
self.tab_widget.downloads_tab.stop_download()
|
|
||||||
else:
|
else:
|
||||||
e.ignore()
|
e.ignore()
|
||||||
return
|
return
|
||||||
|
|
|
@ -32,11 +32,10 @@ class TabWidget(QTabWidget):
|
||||||
# Downloads Tab after Games Tab to use populated RareCore games list
|
# Downloads Tab after Games Tab to use populated RareCore games list
|
||||||
if not self.args.offline:
|
if not self.args.offline:
|
||||||
self.downloads_tab = DownloadsTab(self)
|
self.downloads_tab = DownloadsTab(self)
|
||||||
updates = list(self.rcore.updates)
|
# update dl tab text
|
||||||
self.addTab(
|
self.addTab(self.downloads_tab, "")
|
||||||
self.downloads_tab,
|
self.__on_downloads_update_title(self.downloads_tab.queues_count())
|
||||||
self.tr("Downloads {}").format(f"({len(updates) if updates else 0})"),
|
self.downloads_tab.update_title.connect(self.__on_downloads_update_title)
|
||||||
)
|
|
||||||
self.store = Shop(self.core)
|
self.store = Shop(self.core)
|
||||||
self.addTab(self.store, self.tr("Store (Beta)"))
|
self.addTab(self.store, self.tr("Store (Beta)"))
|
||||||
|
|
||||||
|
@ -71,12 +70,9 @@ class TabWidget(QTabWidget):
|
||||||
# set current index
|
# set current index
|
||||||
# self.signals.set_main_tab_index.connect(self.setCurrentIndex)
|
# self.signals.set_main_tab_index.connect(self.setCurrentIndex)
|
||||||
|
|
||||||
# update dl tab text
|
|
||||||
self.signals.download.update_tab.connect(self.update_dl_tab_text)
|
|
||||||
|
|
||||||
# Open game list on click on Games tab button
|
# Open game list on click on Games tab button
|
||||||
self.tabBarClicked.connect(self.mouse_clicked)
|
self.tabBarClicked.connect(self.mouse_clicked)
|
||||||
self.setIconSize(QSize(25, 25))
|
self.setIconSize(QSize(24, 24))
|
||||||
|
|
||||||
# shortcuts
|
# shortcuts
|
||||||
QShortcut("Alt+1", self).activated.connect(lambda: self.setCurrentIndex(0))
|
QShortcut("Alt+1", self).activated.connect(lambda: self.setCurrentIndex(0))
|
||||||
|
@ -84,18 +80,9 @@ class TabWidget(QTabWidget):
|
||||||
QShortcut("Alt+3", self).activated.connect(lambda: self.setCurrentIndex(2))
|
QShortcut("Alt+3", self).activated.connect(lambda: self.setCurrentIndex(2))
|
||||||
QShortcut("Alt+4", self).activated.connect(lambda: self.setCurrentIndex(5))
|
QShortcut("Alt+4", self).activated.connect(lambda: self.setCurrentIndex(5))
|
||||||
|
|
||||||
def update_dl_tab_text(self):
|
@pyqtSlot(int)
|
||||||
num_downloads = len(
|
def __on_downloads_update_title(self, num_downloads: int):
|
||||||
set(
|
self.setTabText(self.indexOf(self.downloads_tab), self.tr("Downloads ({})").format(num_downloads))
|
||||||
[i.options.app_name for i in self.downloads_tab.dl_queue]
|
|
||||||
+ [i for i in self.downloads_tab.update_widgets.keys()]
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
if num_downloads != 0:
|
|
||||||
self.setTabText(1, f"Downloads ({num_downloads})")
|
|
||||||
else:
|
|
||||||
self.setTabText(1, "Downloads")
|
|
||||||
|
|
||||||
def mouse_clicked(self, tab_num):
|
def mouse_clicked(self, tab_num):
|
||||||
if tab_num == 0:
|
if tab_num == 0:
|
||||||
|
|
|
@ -1,303 +1,265 @@
|
||||||
import datetime
|
import datetime
|
||||||
|
from ctypes import c_ulonglong
|
||||||
from logging import getLogger
|
from logging import getLogger
|
||||||
from typing import List, Dict, Union, Set, Optional
|
from typing import Union, Optional
|
||||||
|
|
||||||
from PyQt5.QtCore import QThread, pyqtSignal, QSettings, pyqtSlot
|
from PyQt5.QtCore import pyqtSignal, QSettings, pyqtSlot, QThreadPool
|
||||||
from PyQt5.QtWidgets import (
|
from PyQt5.QtWidgets import (
|
||||||
QWidget,
|
QWidget,
|
||||||
QMessageBox,
|
QMessageBox,
|
||||||
QVBoxLayout,
|
|
||||||
QLabel,
|
|
||||||
QPushButton,
|
|
||||||
QGroupBox,
|
|
||||||
)
|
)
|
||||||
from legendary.core import LegendaryCore
|
|
||||||
from legendary.models.game import Game, InstalledGame
|
|
||||||
|
|
||||||
from rare.components.dialogs.install_dialog import InstallDialog
|
from rare.components.dialogs.install_dialog import InstallDialog, InstallInfoWorker
|
||||||
from rare.components.tabs.downloads.dl_queue_widget import DlQueueWidget, DlWidget
|
|
||||||
from rare.components.tabs.downloads.download_thread import DownloadThread
|
|
||||||
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
|
||||||
from rare.shared import RareCore, LegendaryCoreSingleton, GlobalSignalsSingleton
|
from rare.shared import RareCore, LegendaryCoreSingleton, GlobalSignalsSingleton, ArgumentsSingleton
|
||||||
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 .thread import DlThread, DlResultModel, DlResultCode
|
||||||
|
from .widgets import UpdateWidget, QueueWidget
|
||||||
|
|
||||||
logger = getLogger("Download")
|
logger = getLogger("Download")
|
||||||
|
|
||||||
|
|
||||||
class DownloadsTab(QWidget, Ui_DownloadsTab):
|
def get_time(seconds: Union[int, float]) -> str:
|
||||||
thread: QThread
|
return str(datetime.timedelta(seconds=seconds))
|
||||||
dl_queue: List[InstallQueueItemModel] = []
|
|
||||||
dl_status = pyqtSignal(int)
|
|
||||||
|
class DownloadsTab(QWidget):
|
||||||
|
# int: number of updates
|
||||||
|
update_title = pyqtSignal(int)
|
||||||
|
|
||||||
def __init__(self, parent=None):
|
def __init__(self, parent=None):
|
||||||
super(DownloadsTab, self).__init__(parent=parent)
|
super(DownloadsTab, self).__init__(parent=parent)
|
||||||
self.setupUi(self)
|
self.ui = Ui_DownloadsTab()
|
||||||
|
self.ui.setupUi(self)
|
||||||
self.rcore = RareCore.instance()
|
self.rcore = RareCore.instance()
|
||||||
self.core = LegendaryCoreSingleton()
|
self.core = LegendaryCoreSingleton()
|
||||||
self.signals = GlobalSignalsSingleton()
|
self.signals = GlobalSignalsSingleton()
|
||||||
|
self.args = ArgumentsSingleton()
|
||||||
|
|
||||||
self.active_game: Optional[Game] = None
|
self.thread: Optional[DlThread] = None
|
||||||
self.analysis = None
|
self.threadpool = QThreadPool(self)
|
||||||
|
self.threadpool.setMaxThreadCount(1)
|
||||||
|
|
||||||
self.kill_button.clicked.connect(self.stop_download)
|
self.ui.kill_button.clicked.connect(self.stop_download)
|
||||||
|
|
||||||
self.queue_widget = DlQueueWidget()
|
self.queue_group = QueueGroup(self)
|
||||||
self.queue_widget.update_list.connect(self.update_dl_queue)
|
# lk: todo recreate update widget
|
||||||
self.queue_scroll_contents_layout.addWidget(self.queue_widget)
|
self.queue_group.removed.connect(self.__on_queue_removed)
|
||||||
|
self.queue_group.force.connect(self.__on_queue_force)
|
||||||
|
self.ui.queue_scroll_contents_layout.addWidget(self.queue_group)
|
||||||
|
|
||||||
self.updates = QGroupBox(self.tr("Updates"))
|
self.updates_group = UpdateGroup(self)
|
||||||
self.updates.setObjectName("updates_group")
|
self.updates_group.enqueue.connect(self.__get_install_options)
|
||||||
self.update_layout = QVBoxLayout(self.updates)
|
self.ui.queue_scroll_contents_layout.addWidget(self.updates_group)
|
||||||
self.queue_scroll_contents_layout.addWidget(self.updates)
|
|
||||||
|
|
||||||
self.update_widgets: Dict[str, UpdateWidget] = {}
|
self.__check_updates()
|
||||||
|
|
||||||
self.update_text = QLabel(self.tr("No updates available"))
|
self.signals.game.install.connect(self.__get_install_options)
|
||||||
self.update_layout.addWidget(self.update_text)
|
self.signals.download.enqueue.connect(self.__add_update)
|
||||||
|
self.signals.download.dequeue.connect(self.__on_game_uninstalled)
|
||||||
|
|
||||||
has_updates = False
|
self.__reset_download()
|
||||||
|
|
||||||
|
self.forced_item: Optional[InstallQueueItemModel] = None
|
||||||
|
self.on_exit = False
|
||||||
|
|
||||||
|
def __check_updates(self):
|
||||||
for rgame in self.rcore.updates:
|
for rgame in self.rcore.updates:
|
||||||
has_updates = True
|
self.__add_update(rgame)
|
||||||
self.add_update(rgame)
|
|
||||||
|
|
||||||
self.queue_widget.item_removed.connect(self.queue_item_removed)
|
def __add_update(self, update: Union[str,RareGame]):
|
||||||
|
if isinstance(update, str):
|
||||||
self.signals.game.install.connect(self.get_install_options)
|
update = self.rcore.get_game(update)
|
||||||
self.signals.game.uninstalled.connect(self.queue_item_removed)
|
if update.metadata.auto_update or QSettings().value("auto_update", False, bool):
|
||||||
self.signals.game.uninstalled.connect(self.remove_update)
|
self.__get_install_options(
|
||||||
|
InstallOptionsModel(app_name=update.app_name, update=True, silent=True)
|
||||||
self.signals.download.enqueue_game.connect(
|
|
||||||
lambda app_name: self.add_update(app_name)
|
|
||||||
)
|
)
|
||||||
self.signals.game.uninstalled.connect(self.game_uninstalled)
|
else:
|
||||||
|
self.updates_group.append(update.game, update.igame)
|
||||||
|
|
||||||
self.reset_infos()
|
@pyqtSlot(str)
|
||||||
|
def __on_queue_removed(self, app_name: str):
|
||||||
|
"""
|
||||||
|
Handle removing a queued item.
|
||||||
|
If the item exists in the updates (it means a repair was removed), re-enable the buttons.
|
||||||
|
If it doesn't exist in the updates, recreate the widget.
|
||||||
|
:param app_name:
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
if self.updates_group.contains(app_name):
|
||||||
|
self.updates_group.set_widget_enabled(app_name, True)
|
||||||
|
else:
|
||||||
|
if self.rcore.get_game(app_name).is_installed:
|
||||||
|
self.__add_update(app_name)
|
||||||
|
|
||||||
def queue_item_removed(self, app_name):
|
@pyqtSlot(InstallQueueItemModel)
|
||||||
if w := self.update_widgets.get(app_name):
|
def __on_queue_force(self, item: InstallQueueItemModel):
|
||||||
w.update_button.setDisabled(False)
|
if self.thread:
|
||||||
w.update_with_settings.setDisabled(False)
|
self.stop_download()
|
||||||
|
self.forced_item = item
|
||||||
|
else:
|
||||||
|
self.__start_installation(item)
|
||||||
|
|
||||||
def add_update(self, rgame: RareGame):
|
@pyqtSlot(str)
|
||||||
if old_widget := self.update_widgets.get(rgame.app_name, False):
|
def __on_game_uninstalled(self, app_name):
|
||||||
old_widget.deleteLater()
|
if self.thread and self.thread.item.options.app_name == app_name:
|
||||||
self.update_widgets.pop(rgame.app_name)
|
|
||||||
widget = UpdateWidget(self.core, rgame, self)
|
|
||||||
self.update_layout.addWidget(widget)
|
|
||||||
self.update_widgets[rgame.app_name] = widget
|
|
||||||
widget.update_signal.connect(self.get_install_options)
|
|
||||||
if QSettings().value("auto_update", False, bool):
|
|
||||||
self.get_install_options(
|
|
||||||
InstallOptionsModel(app_name=rgame.app_name, update=True, silent=True)
|
|
||||||
)
|
|
||||||
widget.update_button.setDisabled(True)
|
|
||||||
self.update_text.setVisible(False)
|
|
||||||
|
|
||||||
def game_uninstalled(self, app_name):
|
|
||||||
# game in dl_queue
|
|
||||||
for i, item in enumerate(self.dl_queue):
|
|
||||||
if item.options.app_name == app_name:
|
|
||||||
self.dl_queue.pop(i)
|
|
||||||
self.queue_widget.update_queue(self.dl_queue)
|
|
||||||
break
|
|
||||||
|
|
||||||
# if game is updating
|
|
||||||
if self.active_game and self.active_game.app_name == app_name:
|
|
||||||
self.stop_download()
|
self.stop_download()
|
||||||
|
|
||||||
# game has available update
|
if self.queue_group.contains(app_name):
|
||||||
if app_name in self.update_widgets.keys():
|
self.queue_group.remove(app_name)
|
||||||
self.remove_update(app_name)
|
|
||||||
|
|
||||||
def remove_update(self, app_name):
|
if self.updates_group.contains(app_name):
|
||||||
if w := self.update_widgets.get(app_name):
|
self.updates_group.remove(app_name)
|
||||||
w.deleteLater()
|
|
||||||
self.update_widgets.pop(app_name)
|
|
||||||
|
|
||||||
if len(self.update_widgets) == 0:
|
self.update_title.emit(self.queues_count())
|
||||||
self.update_text.setVisible(True)
|
|
||||||
|
|
||||||
self.signals.download.update_tab.emit()
|
def stop_download(self, on_exit=False):
|
||||||
|
|
||||||
def update_dl_queue(self, dl_queue):
|
|
||||||
self.dl_queue = dl_queue
|
|
||||||
|
|
||||||
def stop_download(self):
|
|
||||||
self.thread.kill()
|
self.thread.kill()
|
||||||
self.kill_button.setEnabled(False)
|
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.thread.wait()
|
||||||
|
|
||||||
def install_game(self, queue_item: InstallQueueItemModel):
|
def __start_installation(self, item: InstallQueueItemModel):
|
||||||
if self.active_game is None:
|
thread = DlThread(item, self.rcore.get_game(item.options.app_name), self.core, self.args.debug)
|
||||||
self.start_installation(queue_item)
|
thread.result.connect(self.__on_download_result)
|
||||||
else:
|
thread.progress.connect(self.__on_download_progress)
|
||||||
self.dl_queue.append(queue_item)
|
thread.finished.connect(thread.deleteLater)
|
||||||
self.queue_widget.update_queue(self.dl_queue)
|
thread.start()
|
||||||
|
self.thread = thread
|
||||||
|
self.ui.kill_button.setDisabled(False)
|
||||||
|
self.ui.dl_name.setText(item.download.game.app_title)
|
||||||
|
|
||||||
def start_installation(self, queue_item: InstallQueueItemModel):
|
def queues_count(self) -> int:
|
||||||
if self.dl_queue:
|
return self.updates_group.count() + self.queue_group.count()
|
||||||
self.dl_queue.pop(0)
|
|
||||||
self.queue_widget.update_queue(self.dl_queue)
|
|
||||||
self.active_game = queue_item.download.game
|
|
||||||
self.thread = DownloadThread(self.core, queue_item)
|
|
||||||
self.thread.ret_status.connect(self.status)
|
|
||||||
self.thread.ui_update.connect(self.progress_update)
|
|
||||||
self.thread.start()
|
|
||||||
self.kill_button.setDisabled(False)
|
|
||||||
self.analysis = queue_item.download.analysis
|
|
||||||
self.dl_name.setText(self.active_game.app_title)
|
|
||||||
|
|
||||||
self.signals.progress.started.emit(self.active_game.app_name)
|
@pyqtSlot(InstallQueueItemModel)
|
||||||
|
def __on_info_worker_result(self, item: InstallQueueItemModel):
|
||||||
|
rgame = self.rcore.get_game(item.options.app_name)
|
||||||
|
self.queue_group.push_front(item, rgame.igame)
|
||||||
|
logger.info(f"Re-queued download for {item.download.game.app_name} ({item.download.game.app_title})")
|
||||||
|
|
||||||
@pyqtSlot(DownloadThread.ReturnStatus)
|
@pyqtSlot(str)
|
||||||
def status(self, result: DownloadThread.ReturnStatus):
|
def __on_info_worker_failed(self, message: str):
|
||||||
if result.ret_code == result.ReturnCode.FINISHED:
|
logger.error(f"Failed to re-queue stopped download with error: {message}")
|
||||||
|
|
||||||
|
@pyqtSlot()
|
||||||
|
def __on_info_worker_finished(self):
|
||||||
|
logger.info("Download re-queue worker finished")
|
||||||
|
|
||||||
|
@pyqtSlot(DlResultModel)
|
||||||
|
def __on_download_result(self, result: DlResultModel):
|
||||||
|
if result.code == DlResultCode.FINISHED:
|
||||||
if result.shortcuts:
|
if result.shortcuts:
|
||||||
if not create_desktop_link(result.app_name, self.core, "desktop"):
|
if not create_desktop_link(result.item.options.app_name, self.core, "desktop"):
|
||||||
# maybe add it to download summary, to show in finished downloads
|
# maybe add it to download summary, to show in finished downloads
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
logger.info("Desktop shortcut written")
|
logger.info("Desktop shortcut written")
|
||||||
|
logger.info(
|
||||||
self.dl_name.setText(self.tr("Download finished. Reload library"))
|
f"Download finished: {result.item.download.game.app_name} ({result.item.download.game.app_title})"
|
||||||
logger.info(f"Download finished: {self.active_game.app_title}")
|
|
||||||
|
|
||||||
game = self.active_game
|
|
||||||
self.active_game = None
|
|
||||||
|
|
||||||
if self.dl_queue:
|
|
||||||
if self.dl_queue[0].download.game.app_name == game.app_name:
|
|
||||||
self.dl_queue.pop(0)
|
|
||||||
self.queue_widget.update_queue(self.dl_queue)
|
|
||||||
|
|
||||||
if game.app_name in self.update_widgets.keys():
|
|
||||||
igame = self.core.get_installed_game(game.app_name)
|
|
||||||
if (
|
|
||||||
self.core.get_asset(
|
|
||||||
game.app_name, igame.platform, False
|
|
||||||
).build_version
|
|
||||||
== igame.version
|
|
||||||
):
|
|
||||||
self.remove_update(game.app_name)
|
|
||||||
|
|
||||||
self.signals.application.notify.emit(game.app_title)
|
|
||||||
self.signals.game.installed.emit([game.app_name])
|
|
||||||
self.signals.download.update_tab.emit()
|
|
||||||
|
|
||||||
self.signals.progress.finished.emit(game.app_name, True)
|
|
||||||
|
|
||||||
self.reset_infos()
|
|
||||||
|
|
||||||
if len(self.dl_queue) != 0:
|
|
||||||
self.start_installation(self.dl_queue[0])
|
|
||||||
else:
|
|
||||||
self.queue_widget.update_queue(self.dl_queue)
|
|
||||||
|
|
||||||
elif result.ret_code == result.ReturnCode.ERROR:
|
|
||||||
QMessageBox.warning(self, self.tr("Error"), f"Download error: {result.message}")
|
|
||||||
|
|
||||||
elif result.ret_code == result.ReturnCode.STOPPED:
|
|
||||||
self.reset_infos()
|
|
||||||
if w := self.update_widgets.get(self.active_game.app_name):
|
|
||||||
w.update_button.setDisabled(False)
|
|
||||||
w.update_with_settings.setDisabled(False)
|
|
||||||
self.signals.progress.finished.emit(self.active_game.app_name, False)
|
|
||||||
self.active_game = None
|
|
||||||
if self.dl_queue:
|
|
||||||
self.start_installation(self.dl_queue[0])
|
|
||||||
|
|
||||||
def reset_infos(self):
|
|
||||||
self.kill_button.setDisabled(True)
|
|
||||||
self.dl_name.setText(self.tr("No active download"))
|
|
||||||
self.progress_bar.setValue(0)
|
|
||||||
self.dl_speed.setText("n/a")
|
|
||||||
self.time_left.setText("n/a")
|
|
||||||
self.cache_used.setText("n/a")
|
|
||||||
self.downloaded.setText("n/a")
|
|
||||||
self.analysis = None
|
|
||||||
|
|
||||||
@pyqtSlot(str, UIUpdate)
|
|
||||||
def progress_update(self, app_name: str, ui_update: UIUpdate):
|
|
||||||
self.progress_bar.setValue(
|
|
||||||
100 * ui_update.total_downloaded // self.analysis.dl_size
|
|
||||||
)
|
)
|
||||||
self.dl_speed.setText(f"{get_size(ui_update.download_compressed_speed)}/s")
|
|
||||||
self.cache_used.setText(
|
if result.item.options.overlay:
|
||||||
|
self.signals.application.overlay_installed.emit()
|
||||||
|
else:
|
||||||
|
self.signals.application.notify.emit(result.item.download.game.app_name)
|
||||||
|
|
||||||
|
if self.updates_group.contains(result.item.options.app_name):
|
||||||
|
self.updates_group.set_widget_enabled(result.item.options.app_name, True)
|
||||||
|
|
||||||
|
elif result.code == DlResultCode.ERROR:
|
||||||
|
QMessageBox.warning(self, self.tr("Error"), self.tr("Download error: {}").format(result.message))
|
||||||
|
logger.error(f"Download error: {result.message}")
|
||||||
|
|
||||||
|
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)
|
||||||
|
else:
|
||||||
|
return
|
||||||
|
|
||||||
|
self.update_title.emit(self.queues_count())
|
||||||
|
|
||||||
|
# lk: if we finished a repair and we have a disabled update, re-enable it
|
||||||
|
if self.updates_group.contains(result.item.options.app_name):
|
||||||
|
self.updates_group.set_widget_enabled(result.item.options.app_name, True)
|
||||||
|
|
||||||
|
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
|
||||||
|
else:
|
||||||
|
self.__reset_download()
|
||||||
|
|
||||||
|
def __reset_download(self):
|
||||||
|
self.ui.kill_button.setDisabled(True)
|
||||||
|
self.ui.dl_name.setText(self.tr("No active download"))
|
||||||
|
self.ui.progress_bar.setValue(0)
|
||||||
|
self.ui.dl_speed.setText("n/a")
|
||||||
|
self.ui.time_left.setText("n/a")
|
||||||
|
self.ui.cache_used.setText("n/a")
|
||||||
|
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'}"
|
f"{get_size(ui_update.cache_usage) if ui_update.cache_usage > 1023 else '0KB'}"
|
||||||
)
|
)
|
||||||
self.downloaded.setText(
|
self.ui.downloaded.setText(
|
||||||
f"{get_size(ui_update.total_downloaded)} / {get_size(self.analysis.dl_size)}"
|
f"{get_size(ui_update.total_downloaded)} / {get_size(dl_size.value)}"
|
||||||
)
|
|
||||||
self.time_left.setText(self.get_time(ui_update.estimated_time_left))
|
|
||||||
self.signals.progress.value.emit(
|
|
||||||
app_name,
|
|
||||||
100 * ui_update.total_downloaded // self.analysis.dl_size
|
|
||||||
)
|
)
|
||||||
|
self.ui.time_left.setText(get_time(ui_update.estimated_time_left))
|
||||||
|
|
||||||
def get_time(self, seconds: Union[int, float]) -> str:
|
@pyqtSlot(InstallQueueItemModel)
|
||||||
return str(datetime.timedelta(seconds=seconds))
|
def __on_install_dialog_closed(self, item: InstallQueueItemModel):
|
||||||
|
if item:
|
||||||
def on_install_dialog_closed(self, download_item: InstallQueueItemModel):
|
# lk: start update only if there is no other active thread and there is no queue
|
||||||
if download_item:
|
if self.thread is None and not self.queue_group.count():
|
||||||
self.install_game(download_item)
|
self.__start_installation(item)
|
||||||
# lk: In case the download in comming from game verification/repair
|
|
||||||
if w := self.update_widgets.get(download_item.options.app_name):
|
|
||||||
w.update_button.setDisabled(True)
|
|
||||||
w.update_with_settings.setDisabled(True)
|
|
||||||
# self.signals.set_main_tab_index.emit(1)
|
|
||||||
else:
|
else:
|
||||||
if w := self.update_widgets.get(download_item.options.app_name):
|
rgame = self.rcore.get_game(item.options.app_name)
|
||||||
w.update_button.setDisabled(False)
|
self.queue_group.push_back(item, rgame.igame)
|
||||||
w.update_with_settings.setDisabled(False)
|
# lk: Handle repairing into the current version
|
||||||
|
# When we add something to the queue from repair, we might select to update or not
|
||||||
|
# if we do select to update with repair, we can remove the widget from the updates groups
|
||||||
|
# otherwise we disable it and keep it in the updates
|
||||||
|
if self.updates_group.contains(item.options.app_name):
|
||||||
|
if item.download.igame.version == self.updates_group.get_update_version(item.options.app_name):
|
||||||
|
self.updates_group.remove(item.options.app_name)
|
||||||
|
else:
|
||||||
|
self.updates_group.set_widget_enabled(item.options.app_name, False)
|
||||||
|
else:
|
||||||
|
if self.updates_group.contains(item.options.app_name):
|
||||||
|
self.updates_group.set_widget_enabled(item.options.app_name, True)
|
||||||
|
|
||||||
def get_install_options(self, options: InstallOptionsModel):
|
|
||||||
|
|
||||||
|
@pyqtSlot(InstallOptionsModel)
|
||||||
|
def __get_install_options(self, options: InstallOptionsModel):
|
||||||
install_dialog = InstallDialog(
|
install_dialog = InstallDialog(
|
||||||
InstallQueueItemModel(options=options),
|
self.rcore.get_game(options.app_name),
|
||||||
update=options.update,
|
options=options,
|
||||||
silent=options.silent,
|
|
||||||
parent=self,
|
parent=self,
|
||||||
)
|
)
|
||||||
install_dialog.result_ready.connect(self.on_install_dialog_closed)
|
install_dialog.result_ready.connect(self.__on_install_dialog_closed)
|
||||||
install_dialog.execute()
|
install_dialog.execute()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_download_active(self):
|
def is_download_active(self):
|
||||||
return self.active_game is not None
|
return self.thread is not None
|
||||||
|
|
||||||
|
|
||||||
class UpdateWidget(QWidget):
|
|
||||||
update_signal = pyqtSignal(InstallOptionsModel)
|
|
||||||
|
|
||||||
def __init__(self, core: LegendaryCore, rgame: RareGame, parent):
|
|
||||||
super(UpdateWidget, self).__init__(parent=parent)
|
|
||||||
self.core = core
|
|
||||||
self.rgame = rgame
|
|
||||||
|
|
||||||
layout = QVBoxLayout()
|
|
||||||
self.title = QLabel(self.rgame.app_title)
|
|
||||||
layout.addWidget(self.title)
|
|
||||||
|
|
||||||
self.update_button = QPushButton(self.tr("Update Game"))
|
|
||||||
self.update_button.clicked.connect(lambda: self.update_game(True))
|
|
||||||
self.update_with_settings = QPushButton("Update with settings")
|
|
||||||
self.update_with_settings.clicked.connect(lambda: self.update_game(False))
|
|
||||||
layout.addWidget(self.update_button)
|
|
||||||
layout.addWidget(self.update_with_settings)
|
|
||||||
layout.addWidget(
|
|
||||||
QLabel(
|
|
||||||
self.tr("Version: <b>{}</b> >> <b>{}</b>")
|
|
||||||
.format(self.rgame.version, self.rgame.remote_version)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
self.setLayout(layout)
|
|
||||||
|
|
||||||
def update_game(self, auto: bool):
|
|
||||||
self.update_button.setDisabled(True)
|
|
||||||
self.update_with_settings.setDisabled(True)
|
|
||||||
self.update_signal.emit(
|
|
||||||
InstallOptionsModel(app_name=self.rgame.app_name, update=True, silent=auto)
|
|
||||||
) # True if settings
|
|
||||||
|
|
|
@ -1,151 +0,0 @@
|
||||||
from logging import getLogger
|
|
||||||
|
|
||||||
from PyQt5.QtCore import pyqtSignal
|
|
||||||
from PyQt5.QtWidgets import (
|
|
||||||
QGroupBox,
|
|
||||||
QVBoxLayout,
|
|
||||||
QLabel,
|
|
||||||
QHBoxLayout,
|
|
||||||
QPushButton,
|
|
||||||
QWidget,
|
|
||||||
)
|
|
||||||
|
|
||||||
from rare.models.install import InstallQueueItemModel
|
|
||||||
from rare.utils.misc import icon
|
|
||||||
|
|
||||||
logger = getLogger("QueueWidget")
|
|
||||||
|
|
||||||
|
|
||||||
class DlWidget(QWidget):
|
|
||||||
move_up = pyqtSignal(str) # app_name
|
|
||||||
move_down = pyqtSignal(str) # app_name
|
|
||||||
remove = pyqtSignal(str) # app_name
|
|
||||||
|
|
||||||
def __init__(self, index, queue_item: InstallQueueItemModel):
|
|
||||||
super(DlWidget, self).__init__()
|
|
||||||
self.app_name = queue_item.download.game.app_name
|
|
||||||
self.layout = QHBoxLayout()
|
|
||||||
|
|
||||||
self.left_layout = QVBoxLayout()
|
|
||||||
self.move_up_button = QPushButton(icon("fa.arrow-up"), "")
|
|
||||||
if index == 0:
|
|
||||||
self.move_up_button.setDisabled(True)
|
|
||||||
self.move_up_button.clicked.connect(lambda: self.move_up.emit(self.app_name))
|
|
||||||
self.move_up_button.setFixedWidth(20)
|
|
||||||
self.left_layout.addWidget(self.move_up_button)
|
|
||||||
|
|
||||||
self.move_down_buttton = QPushButton(icon("fa.arrow-down"), "")
|
|
||||||
self.move_down_buttton.clicked.connect(
|
|
||||||
lambda: self.move_down.emit(self.app_name)
|
|
||||||
)
|
|
||||||
self.left_layout.addWidget(self.move_down_buttton)
|
|
||||||
self.move_down_buttton.setFixedWidth(20)
|
|
||||||
|
|
||||||
self.layout.addLayout(self.left_layout)
|
|
||||||
|
|
||||||
self.right_layout = QVBoxLayout()
|
|
||||||
self.title = QLabel(queue_item.download.game.app_title)
|
|
||||||
self.right_layout.addWidget(self.title)
|
|
||||||
|
|
||||||
dl_size = queue_item.download.analysis.dl_size
|
|
||||||
install_size = queue_item.download.analysis.install_size
|
|
||||||
|
|
||||||
self.size = QHBoxLayout()
|
|
||||||
|
|
||||||
self.size.addWidget(
|
|
||||||
QLabel(
|
|
||||||
self.tr("Download size: {} GB").format(round(dl_size / 1024 ** 3, 2))
|
|
||||||
)
|
|
||||||
)
|
|
||||||
self.size.addWidget(
|
|
||||||
QLabel(
|
|
||||||
self.tr("Install size: {} GB").format(
|
|
||||||
round(install_size / 1024 ** 3, 2)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
self.right_layout.addLayout(self.size)
|
|
||||||
|
|
||||||
self.delete = QPushButton(self.tr("Remove Download"))
|
|
||||||
self.delete.clicked.connect(lambda: self.remove.emit(self.app_name))
|
|
||||||
self.right_layout.addWidget(self.delete)
|
|
||||||
|
|
||||||
self.layout.addLayout(self.right_layout)
|
|
||||||
self.setLayout(self.layout)
|
|
||||||
|
|
||||||
|
|
||||||
class DlQueueWidget(QGroupBox):
|
|
||||||
update_list = pyqtSignal(list)
|
|
||||||
item_removed = pyqtSignal(str)
|
|
||||||
dl_queue = []
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
super(DlQueueWidget, self).__init__()
|
|
||||||
self.setTitle(self.tr("Download Queue"))
|
|
||||||
self.setLayout(QVBoxLayout())
|
|
||||||
self.setObjectName("group")
|
|
||||||
self.text = QLabel(self.tr("No downloads in queue"))
|
|
||||||
self.layout().addWidget(self.text)
|
|
||||||
|
|
||||||
def update_queue(self, dl_queue: list):
|
|
||||||
logger.debug(
|
|
||||||
"Update Queue " + ", ".join(i.download.game.app_title for i in dl_queue)
|
|
||||||
)
|
|
||||||
self.dl_queue = dl_queue
|
|
||||||
|
|
||||||
for item in (self.layout().itemAt(i) for i in range(self.layout().count())):
|
|
||||||
item.widget().deleteLater()
|
|
||||||
|
|
||||||
if len(dl_queue) == 0:
|
|
||||||
self.layout().addWidget(QLabel(self.tr("No downloads in queue")))
|
|
||||||
self.setLayout(self.layout())
|
|
||||||
return
|
|
||||||
|
|
||||||
for index, item in enumerate(dl_queue):
|
|
||||||
widget = DlWidget(index, item)
|
|
||||||
widget.remove.connect(self.remove)
|
|
||||||
widget.move_up.connect(self.move_up)
|
|
||||||
widget.move_down.connect(self.move_down)
|
|
||||||
self.layout().addWidget(widget)
|
|
||||||
if index + 1 == len(dl_queue):
|
|
||||||
widget.move_down_buttton.setDisabled(True)
|
|
||||||
|
|
||||||
def remove(self, app_name):
|
|
||||||
for index, i in enumerate(self.dl_queue):
|
|
||||||
if i.download.game.app_name == app_name:
|
|
||||||
self.dl_queue.pop(index)
|
|
||||||
self.item_removed.emit(app_name)
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
logger.warning(f"BUG! {app_name}")
|
|
||||||
return
|
|
||||||
self.update_list.emit(self.dl_queue)
|
|
||||||
self.update_queue(self.dl_queue)
|
|
||||||
|
|
||||||
def move_up(self, app_name):
|
|
||||||
index: int
|
|
||||||
|
|
||||||
for i, item in enumerate(self.dl_queue):
|
|
||||||
if item.download.game.app_name == app_name:
|
|
||||||
index = i
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
logger.warning(f"Could not find appname {app_name}")
|
|
||||||
return
|
|
||||||
self.dl_queue.insert(index - 1, self.dl_queue.pop(index))
|
|
||||||
self.update_list.emit(self.dl_queue)
|
|
||||||
self.update_queue(self.dl_queue)
|
|
||||||
|
|
||||||
def move_down(self, app_name):
|
|
||||||
index: int
|
|
||||||
|
|
||||||
for i, item in enumerate(self.dl_queue):
|
|
||||||
if item.download.game.app_name == app_name:
|
|
||||||
index = i
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
logger.warning(f"Info: Could not find appname {app_name}")
|
|
||||||
return
|
|
||||||
self.dl_queue.insert(index + 1, self.dl_queue.pop(index))
|
|
||||||
self.update_list.emit(self.dl_queue)
|
|
||||||
self.update_queue(self.dl_queue)
|
|
242
rare/components/tabs/downloads/groups.py
Normal file
242
rare/components/tabs/downloads/groups.py
Normal file
|
@ -0,0 +1,242 @@
|
||||||
|
from argparse import Namespace
|
||||||
|
from collections import deque
|
||||||
|
from enum import IntEnum
|
||||||
|
from logging import getLogger
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from PyQt5.QtCore import pyqtSignal, pyqtSlot, Qt
|
||||||
|
from PyQt5.QtWidgets import (
|
||||||
|
QGroupBox,
|
||||||
|
QVBoxLayout,
|
||||||
|
QLabel, QSizePolicy, QWidget,
|
||||||
|
)
|
||||||
|
from legendary.models.game import Game, InstalledGame
|
||||||
|
|
||||||
|
from rare.components.tabs.downloads.widgets import QueueWidget, UpdateWidget
|
||||||
|
from rare.models.install import InstallOptionsModel, InstallQueueItemModel
|
||||||
|
|
||||||
|
logger = getLogger("QueueGroup")
|
||||||
|
|
||||||
|
|
||||||
|
class UpdateGroup(QGroupBox):
|
||||||
|
enqueue = pyqtSignal(InstallOptionsModel)
|
||||||
|
|
||||||
|
def __init__(self, parent=None):
|
||||||
|
super(UpdateGroup, self).__init__(parent=parent)
|
||||||
|
self.setObjectName(type(self).__name__)
|
||||||
|
self.setTitle(self.tr("Updates"))
|
||||||
|
self.text = QLabel(self.tr("No updates available"))
|
||||||
|
self.text.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
|
||||||
|
|
||||||
|
# lk: For findChildren to work, the upates's layout has to be in a widget
|
||||||
|
self.container = QWidget(self)
|
||||||
|
self.container.setLayout(QVBoxLayout())
|
||||||
|
self.container.layout().setContentsMargins(0, 0, 0, 0)
|
||||||
|
|
||||||
|
self.setLayout(QVBoxLayout())
|
||||||
|
self.layout().addWidget(self.text)
|
||||||
|
self.layout().addWidget(self.container)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def __widget_name(app_name: str) -> str:
|
||||||
|
return f"UpdateWidget_{app_name}"
|
||||||
|
|
||||||
|
def __find_widget(self, app_name: str) -> Optional[UpdateWidget]:
|
||||||
|
return self.container.findChild(UpdateWidget, name=self.__widget_name(app_name))
|
||||||
|
|
||||||
|
def count(self) -> int:
|
||||||
|
return len(self.container.findChildren(UpdateWidget, options=Qt.FindDirectChildrenOnly))
|
||||||
|
|
||||||
|
def contains(self, app_name: str) -> bool:
|
||||||
|
return self.__find_widget(app_name) is not None
|
||||||
|
|
||||||
|
def append(self, game: Game, igame: InstalledGame):
|
||||||
|
self.text.setVisible(False)
|
||||||
|
self.container.setVisible(True)
|
||||||
|
widget: UpdateWidget = self.__find_widget(game.app_name)
|
||||||
|
if widget is not None:
|
||||||
|
self.container.layout().removeWidget(widget)
|
||||||
|
widget.deleteLater()
|
||||||
|
widget = UpdateWidget(game, igame, parent=self.container)
|
||||||
|
widget.enqueue.connect(self.enqueue)
|
||||||
|
self.container.layout().addWidget(widget)
|
||||||
|
|
||||||
|
def remove(self, app_name: str):
|
||||||
|
widget: UpdateWidget = self.__find_widget(app_name)
|
||||||
|
self.container.layout().removeWidget(widget)
|
||||||
|
widget.deleteLater()
|
||||||
|
self.text.setVisible(not bool(self.count()))
|
||||||
|
self.container.setVisible(bool(self.count()))
|
||||||
|
|
||||||
|
def set_widget_enabled(self, app_name: str, enabled: bool):
|
||||||
|
widget: UpdateWidget = self.__find_widget(app_name)
|
||||||
|
widget.set_enabled(enabled)
|
||||||
|
|
||||||
|
def get_update_version(self, app_name: str) -> str:
|
||||||
|
widget: UpdateWidget = self.__find_widget(app_name)
|
||||||
|
return widget.version()
|
||||||
|
|
||||||
|
|
||||||
|
class QueueGroup(QGroupBox):
|
||||||
|
removed = pyqtSignal(str)
|
||||||
|
force = pyqtSignal(InstallQueueItemModel)
|
||||||
|
|
||||||
|
def __init__(self, parent=None):
|
||||||
|
super(QueueGroup, self).__init__(parent=parent)
|
||||||
|
self.setObjectName(type(self).__name__)
|
||||||
|
self.setTitle(self.tr("Queue"))
|
||||||
|
self.text = QLabel(self.tr("No downloads in queue"), self)
|
||||||
|
self.text.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
|
||||||
|
|
||||||
|
# lk: For findChildren to work, the queue's layout has to be in a widget
|
||||||
|
self.container = QWidget(self)
|
||||||
|
self.container.setLayout(QVBoxLayout())
|
||||||
|
self.container.layout().setContentsMargins(0, 0, 0, 0)
|
||||||
|
self.container.setVisible(False)
|
||||||
|
|
||||||
|
self.setLayout(QVBoxLayout())
|
||||||
|
self.layout().addWidget(self.text)
|
||||||
|
self.layout().addWidget(self.container)
|
||||||
|
|
||||||
|
self.queue = deque()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def __widget_name(app_name:str) -> str:
|
||||||
|
return f"QueueWidget_{app_name}"
|
||||||
|
|
||||||
|
def __find_widget(self, app_name: str) -> Optional[QueueWidget]:
|
||||||
|
return self.container.findChild(QueueWidget, name=self.__widget_name(app_name))
|
||||||
|
|
||||||
|
def contains(self, app_name: str) -> bool:
|
||||||
|
return self.__find_widget(app_name) is not None
|
||||||
|
|
||||||
|
def count(self) -> int:
|
||||||
|
return len(self.queue)
|
||||||
|
|
||||||
|
def __create_widget(self, item: InstallQueueItemModel, old_igame: InstalledGame) -> QueueWidget:
|
||||||
|
widget: QueueWidget = QueueWidget(item, old_igame, parent=self.container)
|
||||||
|
widget.toggle_arrows(self.queue.index(item.download.game.app_name), len(self.queue))
|
||||||
|
widget.remove.connect(self.remove)
|
||||||
|
widget.force.connect(self.__on_force)
|
||||||
|
widget.move_up.connect(self.__on_move_up)
|
||||||
|
widget.move_down.connect(self.__on_move_down)
|
||||||
|
return widget
|
||||||
|
|
||||||
|
def push_front(self, item: InstallQueueItemModel, old_igame: InstalledGame):
|
||||||
|
self.text.setVisible(False)
|
||||||
|
self.container.setVisible(True)
|
||||||
|
self.queue.appendleft(item.download.game.app_name)
|
||||||
|
widget = self.__create_widget(item, old_igame)
|
||||||
|
self.container.layout().insertWidget(0, widget)
|
||||||
|
if self.count() > 1:
|
||||||
|
app_name = self.queue[1]
|
||||||
|
other: QueueWidget = self.__find_widget(app_name)
|
||||||
|
other.toggle_arrows(1, len(self.queue))
|
||||||
|
|
||||||
|
def push_back(self, item: InstallQueueItemModel, old_igame: InstalledGame):
|
||||||
|
self.text.setVisible(False)
|
||||||
|
self.container.setVisible(True)
|
||||||
|
self.queue.append(item.download.game.app_name)
|
||||||
|
widget = self.__create_widget(item, old_igame)
|
||||||
|
self.container.layout().addWidget(widget)
|
||||||
|
if self.count() > 1:
|
||||||
|
app_name = self.queue[-2]
|
||||||
|
other: QueueWidget = self.__find_widget(app_name)
|
||||||
|
other.toggle_arrows(len(self.queue) - 2, len(self.queue))
|
||||||
|
|
||||||
|
def pop_front(self) -> InstallQueueItemModel:
|
||||||
|
app_name = self.queue.popleft()
|
||||||
|
widget: QueueWidget = self.__find_widget(app_name)
|
||||||
|
item = widget.item
|
||||||
|
widget.deleteLater()
|
||||||
|
self.text.setVisible(not bool(self.count()))
|
||||||
|
self.container.setVisible(bool(self.count()))
|
||||||
|
return item
|
||||||
|
|
||||||
|
def __update_queue(self):
|
||||||
|
"""
|
||||||
|
check the first, second, last and second to last widgets in the list
|
||||||
|
and update their arrows
|
||||||
|
:return: None
|
||||||
|
"""
|
||||||
|
for idx in [0, 1]:
|
||||||
|
if self.count() > idx:
|
||||||
|
app_name = self.queue[idx]
|
||||||
|
widget: QueueWidget = self.__find_widget(app_name)
|
||||||
|
widget.toggle_arrows(idx, len(self.queue))
|
||||||
|
for idx in [1, 2]:
|
||||||
|
if self.count() > idx:
|
||||||
|
app_name = self.queue[-idx]
|
||||||
|
widget: QueueWidget = self.__find_widget(app_name)
|
||||||
|
widget.toggle_arrows(len(self.queue) - idx, len(self.queue))
|
||||||
|
|
||||||
|
def __remove(self, app_name: str):
|
||||||
|
self.queue.remove(app_name)
|
||||||
|
widget: QueueWidget = self.__find_widget(app_name)
|
||||||
|
self.container.layout().removeWidget(widget)
|
||||||
|
widget.deleteLater()
|
||||||
|
self.__update_queue()
|
||||||
|
self.text.setVisible(not bool(self.count()))
|
||||||
|
self.container.setVisible(bool(self.count()))
|
||||||
|
|
||||||
|
@pyqtSlot(str)
|
||||||
|
def remove(self, app_name: str):
|
||||||
|
self.__remove(app_name)
|
||||||
|
self.removed.emit(app_name)
|
||||||
|
|
||||||
|
@pyqtSlot(InstallQueueItemModel)
|
||||||
|
def __on_force(self, item: InstallQueueItemModel):
|
||||||
|
self.__remove(item.options.app_name)
|
||||||
|
self.force.emit(item)
|
||||||
|
|
||||||
|
class MoveDirection(IntEnum):
|
||||||
|
UP = -1
|
||||||
|
DOWN = 1
|
||||||
|
|
||||||
|
def __move(self, app_name: str, direction: MoveDirection):
|
||||||
|
"""
|
||||||
|
Moved the widget for `app_name` up or down in the queue and the container
|
||||||
|
:param app_name: The app_name associated with the widget
|
||||||
|
:param direction: -1 to move up, +1 to move down
|
||||||
|
:return: None
|
||||||
|
"""
|
||||||
|
index = self.queue.index(app_name)
|
||||||
|
self.queue.remove(app_name)
|
||||||
|
self.queue.insert(index + int(direction), app_name)
|
||||||
|
widget: QueueWidget = self.__find_widget(app_name)
|
||||||
|
self.container.layout().insertWidget(index + int(direction), widget)
|
||||||
|
self.__update_queue()
|
||||||
|
|
||||||
|
@pyqtSlot(str)
|
||||||
|
def __on_move_up(self, app_name: str):
|
||||||
|
self.__move(app_name, QueueGroup.MoveDirection.UP)
|
||||||
|
|
||||||
|
@pyqtSlot(str)
|
||||||
|
def __on_move_down(self, app_name: str):
|
||||||
|
self.__move(app_name, QueueGroup.MoveDirection.DOWN)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
import sys
|
||||||
|
from PyQt5.QtWidgets import QApplication, QDialog
|
||||||
|
from rare.utils.misc import set_style_sheet
|
||||||
|
|
||||||
|
app = QApplication(sys.argv)
|
||||||
|
|
||||||
|
set_style_sheet("RareStyle")
|
||||||
|
|
||||||
|
queue_group = QueueGroup()
|
||||||
|
|
||||||
|
dialog = QDialog()
|
||||||
|
dialog.setLayout(QVBoxLayout())
|
||||||
|
dialog.layout().addWidget(queue_group)
|
||||||
|
for i in range(5):
|
||||||
|
rgame = Namespace(app_name=i, title=f"{i}", remote_version=f"{i}", version=f"{i}")
|
||||||
|
analysis = Namespace(dl_size=i*1024, install_size=i*2048)
|
||||||
|
game = Namespace(app_name=f"{i}")
|
||||||
|
download = Namespace(analysis=analysis, game=game)
|
||||||
|
model = InstallQueueItemModel(rgame=rgame, download=download, options=True)
|
||||||
|
queue_group.push_back(model)
|
||||||
|
dialog.layout().setSizeConstraint(QVBoxLayout.SetFixedSize)
|
||||||
|
dialog.show()
|
||||||
|
sys.exit(app.exec_())
|
|
@ -2,62 +2,75 @@ import os
|
||||||
import platform
|
import platform
|
||||||
import queue
|
import queue
|
||||||
import time
|
import time
|
||||||
|
from ctypes import c_ulonglong
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from enum import IntEnum
|
from enum import IntEnum
|
||||||
from logging import getLogger
|
from logging import getLogger
|
||||||
from typing import List, Optional, Dict
|
from typing import List, Optional, Dict
|
||||||
|
|
||||||
from PyQt5.QtCore import QThread, pyqtSignal, QProcess
|
from PyQt5.QtCore import QThread, pyqtSignal, QProcess
|
||||||
from legendary.core import LegendaryCore
|
|
||||||
|
|
||||||
from rare.lgndr.cli import LegendaryCLI
|
from rare.lgndr.cli import LegendaryCLI
|
||||||
|
from rare.lgndr.core import LegendaryCore
|
||||||
from rare.lgndr.glue.monkeys import DLManagerSignals
|
from rare.lgndr.glue.monkeys import DLManagerSignals
|
||||||
from rare.lgndr.models.downloading import UIUpdate
|
from rare.lgndr.models.downloading import UIUpdate
|
||||||
|
from rare.models.game import RareGame
|
||||||
from rare.models.install import InstallQueueItemModel
|
from rare.models.install import InstallQueueItemModel
|
||||||
from rare.shared import GlobalSignalsSingleton, ArgumentsSingleton
|
|
||||||
|
|
||||||
logger = getLogger("DownloadThread")
|
logger = getLogger("DownloadThread")
|
||||||
|
|
||||||
|
|
||||||
class DownloadThread(QThread):
|
class DlResultCode(IntEnum):
|
||||||
@dataclass
|
|
||||||
class ReturnStatus:
|
|
||||||
class ReturnCode(IntEnum):
|
|
||||||
ERROR = 1
|
ERROR = 1
|
||||||
STOPPED = 2
|
STOPPED = 2
|
||||||
FINISHED = 3
|
FINISHED = 3
|
||||||
|
|
||||||
app_name: str
|
@dataclass
|
||||||
ret_code: ReturnCode = ReturnCode.ERROR
|
class DlResultModel:
|
||||||
|
item: InstallQueueItemModel
|
||||||
|
code: DlResultCode = DlResultCode.ERROR
|
||||||
message: str = ""
|
message: str = ""
|
||||||
dlcs: Optional[List[Dict]] = None
|
dlcs: Optional[List[Dict]] = None
|
||||||
sync_saves: bool = False
|
sync_saves: bool = False
|
||||||
tip_url: str = ""
|
tip_url: str = ""
|
||||||
shortcuts: bool = False
|
shortcuts: bool = False
|
||||||
|
|
||||||
ret_status = pyqtSignal(ReturnStatus)
|
class DlThread(QThread):
|
||||||
ui_update = pyqtSignal(str, UIUpdate)
|
result = pyqtSignal(DlResultModel)
|
||||||
|
progress = pyqtSignal(UIUpdate, c_ulonglong)
|
||||||
|
|
||||||
def __init__(self, core: LegendaryCore, item: InstallQueueItemModel):
|
def __init__(self, item: InstallQueueItemModel, rgame: RareGame, core: LegendaryCore, debug: bool = False):
|
||||||
super(DownloadThread, self).__init__()
|
super(DlThread, self).__init__()
|
||||||
self.signals = GlobalSignalsSingleton()
|
self.dlm_signals: DLManagerSignals = DLManagerSignals()
|
||||||
self.core: LegendaryCore = core
|
self.core: LegendaryCore = core
|
||||||
self.item: InstallQueueItemModel = item
|
self.item: InstallQueueItemModel = item
|
||||||
self.dlm_signals: DLManagerSignals = DLManagerSignals()
|
self.dl_size = c_ulonglong(item.download.analysis.dl_size)
|
||||||
|
self.rgame = rgame
|
||||||
|
self.debug = debug
|
||||||
|
|
||||||
|
def __result_emit(self, result):
|
||||||
|
if result.code == DlResultCode.FINISHED:
|
||||||
|
self.rgame.set_installed(True)
|
||||||
|
self.rgame.state = RareGame.State.IDLE
|
||||||
|
self.rgame.signals.progress.finish.emit(not result.code == DlResultCode.FINISHED)
|
||||||
|
self.result.emit(result)
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
cli = LegendaryCLI(self.core)
|
cli = LegendaryCLI(self.core)
|
||||||
self.item.download.dlm.logging_queue = cli.logging_queue
|
self.item.download.dlm.logging_queue = cli.logging_queue
|
||||||
self.item.download.dlm.proc_debug = ArgumentsSingleton().debug
|
self.item.download.dlm.proc_debug = self.debug
|
||||||
ret = DownloadThread.ReturnStatus(self.item.download.game.app_name)
|
result = DlResultModel(self.item)
|
||||||
start_t = time.time()
|
start_t = time.time()
|
||||||
try:
|
try:
|
||||||
self.item.download.dlm.start()
|
self.item.download.dlm.start()
|
||||||
|
self.rgame.state = RareGame.State.DOWNLOADING
|
||||||
|
self.rgame.signals.progress.start.emit()
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
while self.item.download.dlm.is_alive():
|
while self.item.download.dlm.is_alive():
|
||||||
try:
|
try:
|
||||||
self.ui_update.emit(self.item.download.game.app_name,
|
status = self.item.download.dlm.status_queue.get(timeout=1.0)
|
||||||
self.item.download.dlm.status_queue.get(timeout=1.0))
|
self.rgame.signals.progress.update.emit(int(status.progress))
|
||||||
|
self.progress.emit(status, self.dl_size)
|
||||||
except queue.Empty:
|
except queue.Empty:
|
||||||
pass
|
pass
|
||||||
if self.dlm_signals.update:
|
if self.dlm_signals.update:
|
||||||
|
@ -68,28 +81,29 @@ class DownloadThread(QThread):
|
||||||
time.sleep(self.item.download.dlm.update_interval / 10)
|
time.sleep(self.item.download.dlm.update_interval / 10)
|
||||||
self.item.download.dlm.join()
|
self.item.download.dlm.join()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
self.kill()
|
||||||
|
self.item.download.dlm.join()
|
||||||
end_t = time.time()
|
end_t = time.time()
|
||||||
logger.error(f"Installation failed after {end_t - start_t:.02f} seconds.")
|
logger.error(f"Installation failed after {end_t - start_t:.02f} seconds.")
|
||||||
logger.warning(f"The following exception occurred while waiting for the downloader to finish: {e!r}.")
|
logger.warning(f"The following exception occurred while waiting for the downloader to finish: {e!r}.")
|
||||||
ret.ret_code = ret.ReturnCode.ERROR
|
result.code = DlResultCode.ERROR
|
||||||
ret.message = f"{e!r}"
|
result.message = f"{e!r}"
|
||||||
self.ret_status.emit(ret)
|
self.__result_emit(result)
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
end_t = time.time()
|
end_t = time.time()
|
||||||
if self.dlm_signals.kill is True:
|
if self.dlm_signals.kill is True:
|
||||||
logger.info(f"Download stopped after {end_t - start_t:.02f} seconds.")
|
logger.info(f"Download stopped after {end_t - start_t:.02f} seconds.")
|
||||||
ret.ret_code = ret.ReturnCode.STOPPED
|
result.code = DlResultCode.STOPPED
|
||||||
self.ret_status.emit(ret)
|
self.__result_emit(result)
|
||||||
return
|
return
|
||||||
logger.info(f"Download finished in {end_t - start_t:.02f} seconds.")
|
logger.info(f"Download finished in {end_t - start_t:.02f} seconds.")
|
||||||
|
|
||||||
ret.ret_code = ret.ReturnCode.FINISHED
|
result.code = DlResultCode.FINISHED
|
||||||
|
|
||||||
if self.item.options.overlay:
|
if self.item.options.overlay:
|
||||||
self.signals.application.overlay_installed.emit()
|
|
||||||
self.core.finish_overlay_install(self.item.download.igame)
|
self.core.finish_overlay_install(self.item.download.igame)
|
||||||
self.ret_status.emit(ret)
|
self.__result_emit(result)
|
||||||
return
|
return
|
||||||
|
|
||||||
if not self.item.options.no_install:
|
if not self.item.options.no_install:
|
||||||
|
@ -105,9 +119,9 @@ class DownloadThread(QThread):
|
||||||
|
|
||||||
dlcs = self.core.get_dlc_for_game(self.item.download.igame.app_name)
|
dlcs = self.core.get_dlc_for_game(self.item.download.igame.app_name)
|
||||||
if dlcs and not self.item.options.skip_dlcs:
|
if dlcs and not self.item.options.skip_dlcs:
|
||||||
ret.dlcs = []
|
result.dlcs = []
|
||||||
for dlc in dlcs:
|
for dlc in dlcs:
|
||||||
ret.dlcs.append(
|
result.dlcs.append(
|
||||||
{
|
{
|
||||||
"app_name": dlc.app_name,
|
"app_name": dlc.app_name,
|
||||||
"app_title": dlc.app_title,
|
"app_title": dlc.app_title,
|
||||||
|
@ -119,11 +133,11 @@ class DownloadThread(QThread):
|
||||||
self.item.download.game.supports_cloud_saves
|
self.item.download.game.supports_cloud_saves
|
||||||
or self.item.download.game.supports_mac_cloud_saves
|
or self.item.download.game.supports_mac_cloud_saves
|
||||||
) and not self.item.download.game.is_dlc:
|
) and not self.item.download.game.is_dlc:
|
||||||
ret.sync_saves = True
|
result.sync_saves = True
|
||||||
|
|
||||||
# show tip again after installation finishes so users hopefully actually see it
|
# show tip again after installation finishes so users hopefully actually see it
|
||||||
if tip_url := self.core.get_game_tip(self.item.download.igame.app_name):
|
if tip_url := self.core.get_game_tip(self.item.download.igame.app_name):
|
||||||
ret.tip_url = tip_url
|
result.tip_url = tip_url
|
||||||
|
|
||||||
LegendaryCLI(self.core).install_game_cleanup(
|
LegendaryCLI(self.core).install_game_cleanup(
|
||||||
self.item.download.game,
|
self.item.download.game,
|
||||||
|
@ -133,9 +147,9 @@ class DownloadThread(QThread):
|
||||||
)
|
)
|
||||||
|
|
||||||
if not self.item.options.update and self.item.options.create_shortcut:
|
if not self.item.options.update and self.item.options.create_shortcut:
|
||||||
ret.shortcuts = True
|
result.shortcuts = True
|
||||||
|
|
||||||
self.ret_status.emit(ret)
|
self.__result_emit(result)
|
||||||
|
|
||||||
def _handle_postinstall(self, postinstall, igame):
|
def _handle_postinstall(self, postinstall, igame):
|
||||||
logger.info("This game lists the following prerequisites to be installed:")
|
logger.info("This game lists the following prerequisites to be installed:")
|
123
rare/components/tabs/downloads/widgets.py
Normal file
123
rare/components/tabs/downloads/widgets.py
Normal file
|
@ -0,0 +1,123 @@
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from PyQt5.QtCore import pyqtSignal, Qt
|
||||||
|
from PyQt5.QtWidgets import QWidget, QFrame
|
||||||
|
from legendary.models.downloading import AnalysisResult
|
||||||
|
from legendary.models.game import Game, InstalledGame
|
||||||
|
from qtawesome import icon
|
||||||
|
|
||||||
|
from rare.models.install import InstallQueueItemModel, InstallOptionsModel
|
||||||
|
from rare.shared import ImageManagerSingleton
|
||||||
|
from rare.ui.components.tabs.downloads.download_widget import Ui_DownloadWidget
|
||||||
|
from rare.ui.components.tabs.downloads.info_widget import Ui_InfoWidget
|
||||||
|
from rare.utils.misc import get_size, widget_object_name
|
||||||
|
from rare.widgets.image_widget import ImageWidget, ImageSize
|
||||||
|
|
||||||
|
|
||||||
|
class InfoWidget(QWidget):
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
game: Game,
|
||||||
|
igame: InstalledGame,
|
||||||
|
analysis: Optional[AnalysisResult] = None,
|
||||||
|
old_igame: Optional[InstalledGame] = None,
|
||||||
|
parent=None,
|
||||||
|
):
|
||||||
|
super(InfoWidget, self).__init__(parent=parent)
|
||||||
|
self.ui = Ui_InfoWidget()
|
||||||
|
self.ui.setupUi(self)
|
||||||
|
|
||||||
|
self.image_manager = ImageManagerSingleton()
|
||||||
|
|
||||||
|
self.ui.title.setText(game.app_title)
|
||||||
|
self.ui.remote_version.setText(old_igame.version if old_igame else game.app_version(igame.platform))
|
||||||
|
self.ui.local_version.setText(igame.version)
|
||||||
|
self.ui.dl_size.setText(get_size(analysis.dl_size) if analysis else "")
|
||||||
|
self.ui.install_size.setText(get_size(analysis.install_size) if analysis else "")
|
||||||
|
|
||||||
|
self.image = ImageWidget(self)
|
||||||
|
self.image.setFixedSize(ImageSize.Icon)
|
||||||
|
self.image.setPixmap(self.image_manager.get_pixmap(game.app_name, color=True))
|
||||||
|
self.ui.image_layout.addWidget(self.image)
|
||||||
|
|
||||||
|
self.ui.info_widget_layout.setAlignment(Qt.AlignTop)
|
||||||
|
|
||||||
|
|
||||||
|
class UpdateWidget(QFrame):
|
||||||
|
enqueue = pyqtSignal(InstallOptionsModel)
|
||||||
|
|
||||||
|
def __init__(self, game: Game, igame: InstalledGame, parent=None):
|
||||||
|
super(UpdateWidget, self).__init__(parent=parent)
|
||||||
|
self.ui = Ui_DownloadWidget()
|
||||||
|
self.ui.setupUi(self)
|
||||||
|
# lk: setObjectName has to be after `setupUi` because it is also set in that function
|
||||||
|
self.setObjectName(widget_object_name(self, game.app_name))
|
||||||
|
|
||||||
|
self.game = game
|
||||||
|
self.igame = igame
|
||||||
|
|
||||||
|
self.ui.queue_buttons.setVisible(False)
|
||||||
|
self.ui.move_buttons.setVisible(False)
|
||||||
|
|
||||||
|
self.info_widget = InfoWidget(game, igame, parent=self)
|
||||||
|
self.ui.info_layout.addWidget(self.info_widget)
|
||||||
|
|
||||||
|
self.ui.update_button.clicked.connect(lambda: self.update_game(True))
|
||||||
|
self.ui.settings_button.clicked.connect(lambda: self.update_game(False))
|
||||||
|
|
||||||
|
def update_game(self, auto: bool):
|
||||||
|
self.ui.update_button.setDisabled(True)
|
||||||
|
self.ui.settings_button.setDisabled(True)
|
||||||
|
self.enqueue.emit(InstallOptionsModel(app_name=self.game.app_name, update=True, silent=auto)) # True if settings
|
||||||
|
|
||||||
|
def set_enabled(self, enabled: bool):
|
||||||
|
self.ui.update_button.setEnabled(enabled)
|
||||||
|
self.ui.settings_button.setEnabled(enabled)
|
||||||
|
|
||||||
|
def version(self) -> str:
|
||||||
|
return self.game.app_version(self.igame.platform)
|
||||||
|
|
||||||
|
|
||||||
|
class QueueWidget(QFrame):
|
||||||
|
# str: app_name
|
||||||
|
move_up = pyqtSignal(str)
|
||||||
|
# str: app_name
|
||||||
|
move_down = pyqtSignal(str)
|
||||||
|
# str: app_name
|
||||||
|
remove = pyqtSignal(str)
|
||||||
|
# InstallQueueItemModel
|
||||||
|
force = pyqtSignal(InstallQueueItemModel)
|
||||||
|
|
||||||
|
|
||||||
|
def __init__(self, item: InstallQueueItemModel, old_igame: InstalledGame, parent=None):
|
||||||
|
super(QueueWidget, self).__init__(parent=parent)
|
||||||
|
self.ui = Ui_DownloadWidget()
|
||||||
|
self.ui.setupUi(self)
|
||||||
|
# lk: setObjectName has to be after `setupUi` because it is also set in that function
|
||||||
|
self.setObjectName(widget_object_name(self, item.options.app_name))
|
||||||
|
|
||||||
|
self.item = item
|
||||||
|
|
||||||
|
self.ui.update_buttons.setVisible(False)
|
||||||
|
|
||||||
|
self.ui.move_up_button.setIcon(icon("fa.arrow-up"))
|
||||||
|
self.ui.move_up_button.clicked.connect(
|
||||||
|
lambda: self.move_up.emit(self.item.download.game.app_name)
|
||||||
|
)
|
||||||
|
|
||||||
|
self.ui.move_down_button.setIcon(icon("fa.arrow-down"))
|
||||||
|
self.ui.move_down_button.clicked.connect(
|
||||||
|
lambda: self.move_down.emit(self.item.download.game.app_name)
|
||||||
|
)
|
||||||
|
|
||||||
|
self.info_widget = InfoWidget(
|
||||||
|
item.download.game, item.download.igame, item.download.analysis, old_igame, parent=self
|
||||||
|
)
|
||||||
|
self.ui.info_layout.addWidget(self.info_widget)
|
||||||
|
|
||||||
|
self.ui.remove_button.clicked.connect(lambda: self.remove.emit(self.item.download.game.app_name))
|
||||||
|
self.ui.force_button.clicked.connect(lambda: self.force.emit(self.item))
|
||||||
|
|
||||||
|
def toggle_arrows(self, index: int, length: int):
|
||||||
|
self.ui.move_up_button.setEnabled(bool(index))
|
||||||
|
self.ui.move_down_button.setEnabled(bool(length - (index + 1)))
|
|
@ -1,6 +1,6 @@
|
||||||
import time
|
import time
|
||||||
from logging import getLogger
|
from logging import getLogger
|
||||||
from typing import Tuple, Dict, List, Set
|
from typing import Dict, List
|
||||||
|
|
||||||
from PyQt5.QtCore import QSettings, Qt, pyqtSlot
|
from PyQt5.QtCore import QSettings, Qt, pyqtSlot
|
||||||
from PyQt5.QtWidgets import QStackedWidget, QVBoxLayout, QWidget, QScrollArea, QFrame
|
from PyQt5.QtWidgets import QStackedWidget, QVBoxLayout, QWidget, QScrollArea, QFrame
|
||||||
|
@ -39,14 +39,10 @@ class GamesTab(QStackedWidget):
|
||||||
self.image_manager = ImageManagerSingleton()
|
self.image_manager = ImageManagerSingleton()
|
||||||
self.settings = QSettings()
|
self.settings = QSettings()
|
||||||
|
|
||||||
self.widgets: Dict[str, Tuple[IconGameWidget, ListGameWidget]] = {}
|
|
||||||
self.game_updates: Set[RareGame] = set()
|
|
||||||
self.active_filter: int = 0
|
self.active_filter: int = 0
|
||||||
|
|
||||||
self.game_list: List[Game] = self.api_results.game_list
|
self.game_list: List[Game] = self.api_results.game_list
|
||||||
self.dlcs: Dict[str, List[Game]] = self.api_results.dlcs
|
self.dlcs: Dict[str, List[Game]] = self.api_results.dlcs
|
||||||
self.bit32: List[str] = self.api_results.bit32_games
|
|
||||||
self.mac_games: List[str] = self.api_results.mac_games
|
|
||||||
self.no_assets: List[Game] = self.api_results.no_asset_games
|
self.no_assets: List[Game] = self.api_results.no_asset_games
|
||||||
|
|
||||||
self.game_utils = GameUtils(parent=self)
|
self.game_utils = GameUtils(parent=self)
|
||||||
|
@ -71,18 +67,6 @@ class GamesTab(QStackedWidget):
|
||||||
self.integrations_tabs.back_clicked.connect(lambda: self.setCurrentWidget(self.games))
|
self.integrations_tabs.back_clicked.connect(lambda: self.setCurrentWidget(self.games))
|
||||||
self.addWidget(self.integrations_tabs)
|
self.addWidget(self.integrations_tabs)
|
||||||
|
|
||||||
for i in self.game_list:
|
|
||||||
if i.app_name.startswith("UE_4"):
|
|
||||||
pixmap = self.image_manager.get_pixmap(i.app_name)
|
|
||||||
if pixmap.isNull():
|
|
||||||
continue
|
|
||||||
self.ue_name = i.app_name
|
|
||||||
logger.debug(f"Found Unreal AppName {self.ue_name}")
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
logger.warning("No Unreal engine in library found")
|
|
||||||
self.ue_name = ""
|
|
||||||
|
|
||||||
self.no_asset_names = []
|
self.no_asset_names = []
|
||||||
if not self.args.offline:
|
if not self.args.offline:
|
||||||
for game in self.no_assets:
|
for game in self.no_assets:
|
||||||
|
@ -108,9 +92,7 @@ class GamesTab(QStackedWidget):
|
||||||
self.list_view.setLayout(QVBoxLayout(self.list_view))
|
self.list_view.setLayout(QVBoxLayout(self.list_view))
|
||||||
self.list_view.layout().setContentsMargins(3, 3, 9, 3)
|
self.list_view.layout().setContentsMargins(3, 3, 9, 3)
|
||||||
self.list_view.layout().setAlignment(Qt.AlignTop)
|
self.list_view.layout().setAlignment(Qt.AlignTop)
|
||||||
self.library_controller = LibraryWidgetController(
|
self.library_controller = LibraryWidgetController(self.icon_view, self.list_view, self)
|
||||||
self.icon_view, self.list_view, self
|
|
||||||
)
|
|
||||||
self.icon_view_scroll.setWidget(self.icon_view)
|
self.icon_view_scroll.setWidget(self.icon_view)
|
||||||
self.list_view_scroll.setWidget(self.list_view)
|
self.list_view_scroll.setWidget(self.list_view)
|
||||||
self.view_stack.addWidget(self.icon_view_scroll)
|
self.view_stack.addWidget(self.icon_view_scroll)
|
||||||
|
@ -182,14 +164,15 @@ class GamesTab(QStackedWidget):
|
||||||
|
|
||||||
# FIXME: Remove this when RareCore is in place
|
# FIXME: Remove this when RareCore is in place
|
||||||
def __create_game_with_dlcs(self, game: Game) -> RareGame:
|
def __create_game_with_dlcs(self, game: Game) -> RareGame:
|
||||||
rgame = RareGame(game, self.core, self.image_manager)
|
rgame = RareGame(self.core, self.image_manager, game)
|
||||||
if rgame.has_update:
|
|
||||||
self.game_updates.add(rgame)
|
|
||||||
if game_dlcs := self.dlcs[rgame.game.catalog_item_id]:
|
if game_dlcs := self.dlcs[rgame.game.catalog_item_id]:
|
||||||
for dlc in game_dlcs:
|
for dlc in game_dlcs:
|
||||||
rdlc = RareGame(dlc, self.core, self.image_manager)
|
rdlc = RareGame(self.core, self.image_manager, dlc)
|
||||||
if rdlc.has_update:
|
self.rcore.add_game(rdlc)
|
||||||
self.game_updates.add(rdlc)
|
# lk: plug dlc progress signals to the game's
|
||||||
|
rdlc.signals.progress.start.connect(rgame.signals.progress.start)
|
||||||
|
rdlc.signals.progress.update.connect(rgame.signals.progress.update)
|
||||||
|
rdlc.signals.progress.finish.connect(rgame.signals.progress.finish)
|
||||||
rdlc.set_pixmap()
|
rdlc.set_pixmap()
|
||||||
rgame.owned_dlcs.append(rdlc)
|
rgame.owned_dlcs.append(rdlc)
|
||||||
return rgame
|
return rgame
|
||||||
|
@ -210,12 +193,11 @@ class GamesTab(QStackedWidget):
|
||||||
|
|
||||||
def add_library_widget(self, rgame: RareGame):
|
def add_library_widget(self, rgame: RareGame):
|
||||||
try:
|
try:
|
||||||
icon_widget, list_widget = self.library_controller.add_game(rgame, self.game_utils, self)
|
icon_widget, list_widget = self.library_controller.add_game(rgame, self.game_utils)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise e
|
raise e
|
||||||
logger.error(f"{rgame.app_name} is broken. Don't add it to game list: {e}")
|
logger.error(f"{rgame.app_name} is broken. Don't add it to game list: {e}")
|
||||||
return None, None
|
return None, None
|
||||||
self.widgets[rgame.app_name] = (icon_widget, list_widget)
|
|
||||||
icon_widget.show_info.connect(self.show_game_info)
|
icon_widget.show_info.connect(self.show_game_info)
|
||||||
list_widget.show_info.connect(self.show_game_info)
|
list_widget.show_info.connect(self.show_game_info)
|
||||||
return icon_widget, list_widget
|
return icon_widget, list_widget
|
||||||
|
|
|
@ -168,7 +168,7 @@ class GameInfo(QWidget):
|
||||||
|
|
||||||
def verify_game(self, rgame: RareGame):
|
def verify_game(self, rgame: RareGame):
|
||||||
self.ui.verify_widget.setCurrentIndex(1)
|
self.ui.verify_widget.setCurrentIndex(1)
|
||||||
verify_worker = VerifyWorker(rgame, self.core, self.args)
|
verify_worker = VerifyWorker(self.core, self.args, rgame)
|
||||||
verify_worker.signals.status.connect(self.verify_status)
|
verify_worker.signals.status.connect(self.verify_status)
|
||||||
verify_worker.signals.result.connect(self.verify_result)
|
verify_worker.signals.result.connect(self.verify_result)
|
||||||
verify_worker.signals.error.connect(self.verify_error)
|
verify_worker.signals.error.connect(self.verify_error)
|
||||||
|
|
|
@ -4,9 +4,10 @@ from PyQt5.QtCore import QObject, pyqtSlot
|
||||||
from PyQt5.QtWidgets import QWidget
|
from PyQt5.QtWidgets import QWidget
|
||||||
|
|
||||||
from rare.lgndr.core import LegendaryCore
|
from rare.lgndr.core import LegendaryCore
|
||||||
|
from rare.models.apiresults import ApiResults
|
||||||
from rare.models.game import RareGame
|
from rare.models.game import RareGame
|
||||||
from rare.models.signals import GlobalSignals
|
from rare.models.signals import GlobalSignals
|
||||||
from rare.shared import LegendaryCoreSingleton, GlobalSignalsSingleton, ApiResultsSingleton
|
from rare.shared import RareCore
|
||||||
from rare.shared.game_utils import GameUtils
|
from rare.shared.game_utils import GameUtils
|
||||||
from .icon_game_widget import IconGameWidget
|
from .icon_game_widget import IconGameWidget
|
||||||
from .list_game_widget import ListGameWidget
|
from .list_game_widget import ListGameWidget
|
||||||
|
@ -17,33 +18,21 @@ class LibraryWidgetController(QObject):
|
||||||
super(LibraryWidgetController, self).__init__(parent=parent)
|
super(LibraryWidgetController, self).__init__(parent=parent)
|
||||||
self._icon_container: QWidget = icon_container
|
self._icon_container: QWidget = icon_container
|
||||||
self._list_container: QWidget = list_container
|
self._list_container: QWidget = list_container
|
||||||
self.core: LegendaryCore = LegendaryCoreSingleton()
|
self.rcore = RareCore.instance()
|
||||||
self.signals: GlobalSignals = GlobalSignalsSingleton()
|
self.core: LegendaryCore = self.rcore.core()
|
||||||
self.api_results = ApiResultsSingleton()
|
self.signals: GlobalSignals = self.rcore.signals()
|
||||||
|
self.api_results: ApiResults = self.rcore.api_results()
|
||||||
|
|
||||||
self.signals.progress.started.connect(self.start_progress)
|
def add_game(self, rgame: RareGame, game_utils: GameUtils):
|
||||||
self.signals.progress.value.connect(self.update_progress)
|
return self.add_widgets(rgame, game_utils)
|
||||||
self.signals.progress.finished.connect(self.finish_progress)
|
|
||||||
|
|
||||||
self.signals.game.installed.connect(self.on_install)
|
def add_widgets(self, rgame: RareGame, game_utils: GameUtils) -> Tuple[IconGameWidget, ListGameWidget]:
|
||||||
self.signals.game.uninstalled.connect(self.on_uninstall)
|
icon_widget = IconGameWidget(rgame, game_utils, self._icon_container)
|
||||||
self.signals.game.verified.connect(self.on_verified)
|
list_widget = ListGameWidget(rgame, game_utils, self._list_container)
|
||||||
|
|
||||||
def add_game(self, rgame: RareGame, game_utils: GameUtils, parent):
|
|
||||||
return self.add_widgets(rgame, game_utils, parent)
|
|
||||||
|
|
||||||
def add_widgets(self, rgame: RareGame, game_utils: GameUtils, parent) -> Tuple[IconGameWidget, ListGameWidget]:
|
|
||||||
icon_widget = IconGameWidget(rgame, game_utils, parent)
|
|
||||||
list_widget = ListGameWidget(rgame, game_utils, parent)
|
|
||||||
return icon_widget, list_widget
|
return icon_widget, list_widget
|
||||||
|
|
||||||
def __find_widget(self, app_name: str) -> Tuple[Union[IconGameWidget, None], Union[ListGameWidget, None]]:
|
|
||||||
iw = self._icon_container.findChild(IconGameWidget, app_name)
|
|
||||||
lw = self._list_container.findChild(ListGameWidget, app_name)
|
|
||||||
return iw, lw
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def __visibility(widget, filter_name, search_text) -> Tuple[bool, float]:
|
def __visibility(widget: Union[IconGameWidget,ListGameWidget], filter_name, search_text) -> Tuple[bool, float]:
|
||||||
if filter_name == "installed":
|
if filter_name == "installed":
|
||||||
visible = widget.rgame.is_installed
|
visible = widget.rgame.is_installed
|
||||||
elif filter_name == "offline":
|
elif filter_name == "offline":
|
||||||
|
@ -92,7 +81,12 @@ class LibraryWidgetController(QObject):
|
||||||
self._icon_container.layout().sort(lambda x: (sort_by not in x.widget().rgame.app_title.lower(),))
|
self._icon_container.layout().sort(lambda x: (sort_by not in x.widget().rgame.app_title.lower(),))
|
||||||
else:
|
else:
|
||||||
self._icon_container.layout().sort(
|
self._icon_container.layout().sort(
|
||||||
lambda x: (
|
key=lambda x: (
|
||||||
|
# Sort by grant date
|
||||||
|
# x.widget().rgame.is_installed,
|
||||||
|
# not x.widget().rgame.is_non_asset,
|
||||||
|
# x.widget().rgame.grant_date(),
|
||||||
|
# ), reverse=True
|
||||||
not x.widget().rgame.is_installed,
|
not x.widget().rgame.is_installed,
|
||||||
x.widget().rgame.is_non_asset,
|
x.widget().rgame.is_non_asset,
|
||||||
x.widget().rgame.app_title,
|
x.widget().rgame.app_title,
|
||||||
|
@ -103,6 +97,8 @@ class LibraryWidgetController(QObject):
|
||||||
list_widgets.sort(key=lambda x: (sort_by not in x.rgame.app_title.lower(),))
|
list_widgets.sort(key=lambda x: (sort_by not in x.rgame.app_title.lower(),))
|
||||||
else:
|
else:
|
||||||
list_widgets.sort(
|
list_widgets.sort(
|
||||||
|
# Sort by grant date
|
||||||
|
# key=lambda x: (x.rgame.is_installed, not x.rgame.is_non_asset, x.rgame.grant_date()), reverse=True
|
||||||
key=lambda x: (not x.rgame.is_installed, x.rgame.is_non_asset, x.rgame.app_title)
|
key=lambda x: (not x.rgame.is_installed, x.rgame.is_non_asset, x.rgame.app_title)
|
||||||
)
|
)
|
||||||
for idx, wl in enumerate(list_widgets):
|
for idx, wl in enumerate(list_widgets):
|
||||||
|
@ -122,41 +118,19 @@ class LibraryWidgetController(QObject):
|
||||||
new_icon_app_names = game_app_names.difference(icon_app_names)
|
new_icon_app_names = game_app_names.difference(icon_app_names)
|
||||||
new_list_app_names = game_app_names.difference(list_app_names)
|
new_list_app_names = game_app_names.difference(list_app_names)
|
||||||
for app_name in new_icon_app_names:
|
for app_name in new_icon_app_names:
|
||||||
game = self.rare_core.get_game(app_name)
|
game = self.rcore.get_game(app_name)
|
||||||
iw = IconGameWidget(game)
|
iw = IconGameWidget(game)
|
||||||
self._icon_container.layout().addWidget(iw)
|
self._icon_container.layout().addWidget(iw)
|
||||||
for app_name in new_list_app_names:
|
for app_name in new_list_app_names:
|
||||||
game = self.rare_core.get_game(app_name)
|
game = self.rcore.get_game(app_name)
|
||||||
lw = ListGameWidget(game)
|
lw = ListGameWidget(game)
|
||||||
self._list_container.layout().addWidget(lw)
|
self._list_container.layout().addWidget(lw)
|
||||||
self.sort_list()
|
self.sort_list()
|
||||||
|
|
||||||
@pyqtSlot(list)
|
def __find_widget(self, app_name: str) -> Tuple[Union[IconGameWidget, None], Union[ListGameWidget, None]]:
|
||||||
def on_install(self, app_names: List[str]):
|
iw = self._icon_container.findChild(IconGameWidget, app_name)
|
||||||
for app_name in app_names:
|
lw = self._list_container.findChild(ListGameWidget, app_name)
|
||||||
iw, lw = self.__find_widget(app_name)
|
return iw, lw
|
||||||
if iw is not None:
|
|
||||||
iw.rgame.set_installed(True)
|
|
||||||
if lw is not None:
|
|
||||||
lw.rgame.set_installed(True)
|
|
||||||
self.sort_list()
|
|
||||||
|
|
||||||
@pyqtSlot(str)
|
|
||||||
def on_uninstall(self, app_name: str):
|
|
||||||
iw, lw = self.__find_widget(app_name)
|
|
||||||
if iw is not None:
|
|
||||||
iw.rgame.set_installed(False)
|
|
||||||
if lw is not None:
|
|
||||||
lw.rgame.set_installed(False)
|
|
||||||
self.sort_list()
|
|
||||||
|
|
||||||
@pyqtSlot(str)
|
|
||||||
def on_verified(self, app_name: str):
|
|
||||||
iw, lw = self.__find_widget(app_name)
|
|
||||||
if iw is not None:
|
|
||||||
iw.rgame.needs_verification = False
|
|
||||||
if lw is not None:
|
|
||||||
lw.rgame.needs_verification = False
|
|
||||||
|
|
||||||
# lk: this should go in downloads and happen once
|
# lk: this should go in downloads and happen once
|
||||||
def __find_game_for_dlc(self, app_name: str) -> Optional[str]:
|
def __find_game_for_dlc(self, app_name: str) -> Optional[str]:
|
||||||
|
@ -174,27 +148,3 @@ class LibraryWidgetController(QObject):
|
||||||
)
|
)
|
||||||
return game[0].app_name
|
return game[0].app_name
|
||||||
return app_name
|
return app_name
|
||||||
|
|
||||||
@pyqtSlot(str)
|
|
||||||
def start_progress(self, app_name: str):
|
|
||||||
iw, lw = self.__find_widget(app_name)
|
|
||||||
if iw is not None:
|
|
||||||
iw.rgame.start_progress()
|
|
||||||
if lw is not None:
|
|
||||||
lw.rgame.start_progress()
|
|
||||||
|
|
||||||
@pyqtSlot(str, int)
|
|
||||||
def update_progress(self, app_name: str, progress: int):
|
|
||||||
iw, lw = self.__find_widget(app_name)
|
|
||||||
if iw is not None:
|
|
||||||
iw.rgame.update_progress(progress)
|
|
||||||
if lw is not None:
|
|
||||||
lw.rgame.update_progress(progress)
|
|
||||||
|
|
||||||
@pyqtSlot(str, bool)
|
|
||||||
def finish_progress(self, app_name: str, stopped: bool):
|
|
||||||
iw, lw = self.__find_widget(app_name)
|
|
||||||
if iw is not None:
|
|
||||||
iw.rgame.finish_progress(not stopped, 0, "")
|
|
||||||
if lw is not None:
|
|
||||||
lw.rgame.finish_progress(not stopped, 0, "")
|
|
||||||
|
|
|
@ -108,7 +108,7 @@ class GameWidget(LibraryWidget):
|
||||||
self.addAction(uninstall)
|
self.addAction(uninstall)
|
||||||
uninstall.triggered.connect(
|
uninstall.triggered.connect(
|
||||||
lambda: self.signals.game.uninstalled.emit(self.rgame.app_name)
|
lambda: self.signals.game.uninstalled.emit(self.rgame.app_name)
|
||||||
if self.game_utils.uninstall_game(self.rgame.app_name)
|
if self.game_utils.uninstall_game(self.rgame)
|
||||||
else None
|
else None
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
from logging import getLogger
|
from logging import getLogger
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
from PyQt5.QtCore import QEvent, pyqtSignal, pyqtSlot
|
from PyQt5.QtCore import QEvent, pyqtSlot
|
||||||
|
|
||||||
from rare.models.game import RareGame
|
from rare.models.game import RareGame
|
||||||
from rare.shared.game_utils import GameUtils
|
from rare.shared.game_utils import GameUtils
|
||||||
|
@ -12,9 +13,6 @@ logger = getLogger("IconGameWidget")
|
||||||
|
|
||||||
|
|
||||||
class IconGameWidget(GameWidget):
|
class IconGameWidget(GameWidget):
|
||||||
is_ready = False
|
|
||||||
update_game = pyqtSignal()
|
|
||||||
|
|
||||||
def __init__(self, rgame: RareGame, game_utils: GameUtils, parent=None):
|
def __init__(self, rgame: RareGame, game_utils: GameUtils, parent=None):
|
||||||
super(IconGameWidget, self).__init__(rgame, game_utils, parent)
|
super(IconGameWidget, self).__init__(rgame, game_utils, parent)
|
||||||
self.setObjectName(f"{rgame.app_name}")
|
self.setObjectName(f"{rgame.app_name}")
|
||||||
|
@ -30,7 +28,7 @@ class IconGameWidget(GameWidget):
|
||||||
self.ui.install_btn.clicked.connect(self.install)
|
self.ui.install_btn.clicked.connect(self.install)
|
||||||
self.ui.install_btn.setVisible(not self.rgame.is_installed)
|
self.ui.install_btn.setVisible(not self.rgame.is_installed)
|
||||||
|
|
||||||
self.game_utils.game_launched.connect(self.game_started)
|
# self.game_utils.game_launched.connect(self.game_started)
|
||||||
|
|
||||||
self.is_ready = True
|
self.is_ready = True
|
||||||
self.ui.launch_btn.setEnabled(self.rgame.can_launch)
|
self.ui.launch_btn.setEnabled(self.rgame.can_launch)
|
||||||
|
@ -41,13 +39,13 @@ class IconGameWidget(GameWidget):
|
||||||
def set_status(self):
|
def set_status(self):
|
||||||
super(IconGameWidget, self).set_status(self.ui.status_label)
|
super(IconGameWidget, self).set_status(self.ui.status_label)
|
||||||
|
|
||||||
def enterEvent(self, a0: QEvent = None) -> None:
|
def enterEvent(self, a0: Optional[QEvent] = None) -> None:
|
||||||
if a0 is not None:
|
if a0 is not None:
|
||||||
a0.accept()
|
a0.accept()
|
||||||
self.ui.tooltip_label.setText(self.enterEventText)
|
self.ui.tooltip_label.setText(self.enterEventText)
|
||||||
self.ui.enterAnimation(self)
|
self.ui.enterAnimation(self)
|
||||||
|
|
||||||
def leaveEvent(self, a0: QEvent = None) -> None:
|
def leaveEvent(self, a0: Optional[QEvent] = None) -> None:
|
||||||
if a0 is not None:
|
if a0 is not None:
|
||||||
a0.accept()
|
a0.accept()
|
||||||
self.ui.leaveAnimation(self)
|
self.ui.leaveAnimation(self)
|
||||||
|
|
|
@ -133,7 +133,7 @@ class IconWidget(object):
|
||||||
|
|
||||||
# layout for the image, holds the mini widget and a spacer item
|
# layout for the image, holds the mini widget and a spacer item
|
||||||
image_layout = QVBoxLayout()
|
image_layout = QVBoxLayout()
|
||||||
image_layout.setContentsMargins(0, 0, 0, 0)
|
image_layout.setContentsMargins(2, 2, 2, 2)
|
||||||
|
|
||||||
# layout for the mini widget, holds the top row and the info label
|
# layout for the mini widget, holds the top row and the info label
|
||||||
mini_layout = QVBoxLayout()
|
mini_layout = QVBoxLayout()
|
||||||
|
|
|
@ -22,8 +22,6 @@ logger = getLogger("ListGameWidget")
|
||||||
|
|
||||||
|
|
||||||
class ListGameWidget(GameWidget):
|
class ListGameWidget(GameWidget):
|
||||||
signal = pyqtSignal(str)
|
|
||||||
update_game = pyqtSignal()
|
|
||||||
|
|
||||||
def __init__(self, rgame: RareGame, game_utils: GameUtils, parent=None):
|
def __init__(self, rgame: RareGame, game_utils: GameUtils, parent=None):
|
||||||
super(ListGameWidget, self).__init__(rgame, game_utils, parent)
|
super(ListGameWidget, self).__init__(rgame, game_utils, parent)
|
||||||
|
|
|
@ -209,7 +209,9 @@ class EOSGroup(QGroupBox, Ui_EosWidget):
|
||||||
self.enabled_cb.setChecked(enabled)
|
self.enabled_cb.setChecked(enabled)
|
||||||
|
|
||||||
def install_overlay(self, update=False):
|
def install_overlay(self, update=False):
|
||||||
base_path = os.path.expanduser("~/legendary/.overlay")
|
base_path = os.path.join(
|
||||||
|
self.core.lgd.config.get("Legendary", "install_dir", fallback=os.path.expanduser("~/legendary")),".overlay"
|
||||||
|
)
|
||||||
if update:
|
if update:
|
||||||
if not self.overlay:
|
if not self.overlay:
|
||||||
self.overlay_stack.setCurrentIndex(1)
|
self.overlay_stack.setCurrentIndex(1)
|
||||||
|
@ -218,8 +220,9 @@ class EOSGroup(QGroupBox, Ui_EosWidget):
|
||||||
return
|
return
|
||||||
base_path = self.overlay.install_path
|
base_path = self.overlay.install_path
|
||||||
|
|
||||||
options = InstallOptionsModel(app_name="", base_path=base_path,
|
options = InstallOptionsModel(
|
||||||
platform="Windows", overlay=True)
|
app_name=eos.EOSOverlayApp.app_name, base_path=base_path, platform="Windows", overlay=True
|
||||||
|
)
|
||||||
|
|
||||||
self.signals.game.install.emit(options)
|
self.signals.game.install.emit(options)
|
||||||
|
|
||||||
|
|
|
@ -66,8 +66,7 @@ class TrayIcon(QSystemTrayIcon):
|
||||||
a = QAction(rgame.app_title)
|
a = QAction(rgame.app_title)
|
||||||
a.setProperty("app_name", rgame.app_name)
|
a.setProperty("app_name", rgame.app_name)
|
||||||
a.triggered.connect(
|
a.triggered.connect(
|
||||||
lambda: self.__parent.tab_widget.games_tab.game_utils.prepare_launch(
|
lambda: self.rcore.get_game(self.sender().property("app_name")).launch()
|
||||||
self.sender().property("app_name"))
|
|
||||||
)
|
)
|
||||||
self.menu.insertAction(self.separator, a)
|
self.menu.insertAction(self.separator, a)
|
||||||
self.game_actions.append(a)
|
self.game_actions.append(a)
|
||||||
|
|
|
@ -12,11 +12,10 @@ 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
|
||||||
from rare.shared.image_manager import ImageManager
|
|
||||||
from rare.shared.game_process import GameProcess
|
from rare.shared.game_process import GameProcess
|
||||||
from rare.utils.paths import data_dir
|
from rare.shared.image_manager import ImageManager
|
||||||
from rare.utils.misc import get_rare_executable
|
from rare.utils.misc import get_rare_executable
|
||||||
|
from rare.utils.paths import data_dir
|
||||||
|
|
||||||
logger = getLogger("RareGame")
|
logger = getLogger("RareGame")
|
||||||
|
|
||||||
|
@ -32,25 +31,31 @@ class RareGame(QObject):
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class Metadata:
|
class Metadata:
|
||||||
|
auto_update: bool = False
|
||||||
queued: bool = False
|
queued: bool = False
|
||||||
queue_pos: Optional[int] = None
|
queue_pos: Optional[int] = None
|
||||||
last_played: Optional[datetime] = None
|
last_played: Optional[datetime] = None
|
||||||
|
grant_date: Optional[datetime] = None
|
||||||
tags: List[str] = field(default_factory=list)
|
tags: List[str] = field(default_factory=list)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_dict(cls, data: Dict):
|
def from_dict(cls, data: Dict):
|
||||||
return cls(
|
return cls(
|
||||||
|
auto_update=data.get("auto_update", False),
|
||||||
queued=data.get("queued", False),
|
queued=data.get("queued", False),
|
||||||
queue_pos=data.get("queue_pos", None),
|
queue_pos=data.get("queue_pos", None),
|
||||||
last_played=datetime.strptime(data.get("last_played", "None"), "%Y-%m-%dT%H:%M:%S.%f"),
|
last_played=datetime.fromisoformat(data["last_played"]) if data.get("last_played", None) else None,
|
||||||
|
grant_date=datetime.fromisoformat(data["grant_date"]) if data.get("grant_date", None) else None,
|
||||||
tags=data.get("tags", []),
|
tags=data.get("tags", []),
|
||||||
)
|
)
|
||||||
|
|
||||||
def as_dict(self):
|
def as_dict(self):
|
||||||
return dict(
|
return dict(
|
||||||
|
auto_update=self.auto_update,
|
||||||
queued=self.queued,
|
queued=self.queued,
|
||||||
queue_pos=self.queue_pos,
|
queue_pos=self.queue_pos,
|
||||||
last_played=self.last_played.strftime("%Y-%m-%dT%H:%M:%S.%f"),
|
last_played=self.last_played.isoformat() if self.last_played else None,
|
||||||
|
grant_date=self.grant_date.isoformat() if self.grant_date else None,
|
||||||
tags=self.tags,
|
tags=self.tags,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -68,9 +73,10 @@ class RareGame(QObject):
|
||||||
|
|
||||||
class Game(QObject):
|
class Game(QObject):
|
||||||
install = pyqtSignal(InstallOptionsModel)
|
install = pyqtSignal(InstallOptionsModel)
|
||||||
uninstalled = pyqtSignal()
|
installed = pyqtSignal(str)
|
||||||
launched = pyqtSignal()
|
uninstalled = pyqtSignal(str)
|
||||||
finished = pyqtSignal()
|
launched = pyqtSignal(str)
|
||||||
|
finished = pyqtSignal(str)
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super(RareGame.Signals, self).__init__()
|
super(RareGame.Signals, self).__init__()
|
||||||
|
@ -78,7 +84,7 @@ class RareGame(QObject):
|
||||||
self.widget = RareGame.Signals.Widget()
|
self.widget = RareGame.Signals.Widget()
|
||||||
self.game = RareGame.Signals.Game()
|
self.game = RareGame.Signals.Game()
|
||||||
|
|
||||||
def __init__(self, game: Game, legendary_core: LegendaryCore, image_manager: ImageManager):
|
def __init__(self, legendary_core: LegendaryCore, image_manager: ImageManager, game: Game):
|
||||||
super(RareGame, self).__init__()
|
super(RareGame, self).__init__()
|
||||||
self.signals = RareGame.Signals()
|
self.signals = RareGame.Signals()
|
||||||
|
|
||||||
|
@ -108,29 +114,30 @@ class RareGame(QObject):
|
||||||
|
|
||||||
self.state = RareGame.State.IDLE
|
self.state = RareGame.State.IDLE
|
||||||
|
|
||||||
self.game_process = GameProcess(self)
|
self.game_process = GameProcess(self.game)
|
||||||
self.game_process.launched.connect(self.__game_launched)
|
self.game_process.launched.connect(self.__game_launched)
|
||||||
self.game_process.finished.connect(self.__game_finished)
|
self.game_process.finished.connect(self.__game_finished)
|
||||||
if self.is_installed and not self.is_dlc:
|
if self.is_installed and not self.is_dlc:
|
||||||
self.game_process.connect(on_startup=True)
|
self.game_process.connect_to_server(on_startup=True)
|
||||||
|
# self.grant_date(True)
|
||||||
|
|
||||||
@pyqtSlot(int)
|
@pyqtSlot(int)
|
||||||
def __game_launched(self, code: int):
|
def __game_launched(self, code: int):
|
||||||
self.state = RareGame.State.RUNNING
|
|
||||||
if code == GameProcess.Code.ON_STARTUP:
|
if code == GameProcess.Code.ON_STARTUP:
|
||||||
return
|
return
|
||||||
|
self.state = RareGame.State.RUNNING
|
||||||
self.metadata.last_played = datetime.now()
|
self.metadata.last_played = datetime.now()
|
||||||
self.__save_metadata()
|
self.__save_metadata()
|
||||||
self.signals.game.launched.emit()
|
self.signals.game.launched.emit()
|
||||||
|
|
||||||
@pyqtSlot(int)
|
@pyqtSlot(int)
|
||||||
def __game_finished(self, exit_code: int):
|
def __game_finished(self, exit_code: int):
|
||||||
self.state = RareGame.State.IDLE
|
|
||||||
if exit_code == GameProcess.Code.ON_STARTUP:
|
if exit_code == GameProcess.Code.ON_STARTUP:
|
||||||
return
|
return
|
||||||
|
self.state = RareGame.State.IDLE
|
||||||
self.signals.game.finished.emit()
|
self.signals.game.finished.emit()
|
||||||
|
|
||||||
__metadata_json: Optional[Dict] = None
|
__metadata_json: Dict = None
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def __load_metadata_json() -> Dict:
|
def __load_metadata_json() -> Dict:
|
||||||
|
@ -270,9 +277,11 @@ class RareGame(QObject):
|
||||||
@return None
|
@return None
|
||||||
"""
|
"""
|
||||||
if installed:
|
if installed:
|
||||||
self.igame = self.core.get_installed_game(self.app_name)
|
self.update_igame()
|
||||||
|
self.signals.game.installed.emit(self.app_name)
|
||||||
else:
|
else:
|
||||||
self.igame = None
|
self.igame = None
|
||||||
|
self.signals.game.uninstalled.emit(self.app_name)
|
||||||
self.set_pixmap()
|
self.set_pixmap()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -401,14 +410,33 @@ class RareGame(QObject):
|
||||||
|
|
||||||
@return bool If the game is an Origin game
|
@return bool If the game is an Origin game
|
||||||
"""
|
"""
|
||||||
return self.game.metadata.get("customAttributes", {}).get("ThirdPartyManagedApp", {}).get("value") == "Origin"
|
return (
|
||||||
|
self.game.metadata.get("customAttributes", {}).get("ThirdPartyManagedApp", {}).get("value")
|
||||||
|
== "Origin"
|
||||||
|
)
|
||||||
|
|
||||||
|
def grant_date(self, force=False) -> datetime:
|
||||||
|
if self.metadata.grant_date is None or force:
|
||||||
|
entitlements = self.core.lgd.entitlements
|
||||||
|
matching = filter(lambda ent: ent["namespace"] == self.game.namespace, entitlements)
|
||||||
|
entitlement = next(matching, None)
|
||||||
|
grant_date = datetime.fromisoformat(
|
||||||
|
entitlement["grantDate"].replace("Z", "+00:00")
|
||||||
|
) if entitlement else None
|
||||||
|
if force:
|
||||||
|
print(grant_date)
|
||||||
|
self.metadata.grant_date = grant_date
|
||||||
|
self.__save_metadata()
|
||||||
|
return self.metadata.grant_date
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def can_launch(self) -> bool:
|
def can_launch(self) -> bool:
|
||||||
if self.is_installed:
|
if self.is_installed:
|
||||||
if self.is_non_asset:
|
if self.is_origin:
|
||||||
return True
|
return True
|
||||||
if self.state == RareGame.State.RUNNING or self.needs_verification:
|
if not self.state == RareGame.State.IDLE or self.needs_verification:
|
||||||
|
return False
|
||||||
|
if self.is_foreign and not self.can_run_offline:
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
@ -442,7 +470,7 @@ class RareGame(QObject):
|
||||||
def repair(self, repair_and_update):
|
def repair(self, repair_and_update):
|
||||||
self.signals.game.install.emit(
|
self.signals.game.install.emit(
|
||||||
InstallOptionsModel(
|
InstallOptionsModel(
|
||||||
app_name=self.app_name, repair_mode=True, repair_and_update=repair_and_update, update=True
|
app_name=self.app_name, repair_mode=True, repair_and_update=repair_and_update, update=repair_and_update
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -450,15 +478,16 @@ class RareGame(QObject):
|
||||||
self,
|
self,
|
||||||
offline: bool = False,
|
offline: bool = False,
|
||||||
skip_update_check: bool = False,
|
skip_update_check: bool = False,
|
||||||
wine_bin: str = None,
|
wine_bin: Optional[str] = None,
|
||||||
wine_pfx: str = None,
|
wine_pfx: Optional[str] = None,
|
||||||
ask_sync_saves: bool = False,
|
ask_sync_saves: bool = False,
|
||||||
):
|
):
|
||||||
executable = get_rare_executable()
|
if not self.can_launch:
|
||||||
executable, args = executable[0], executable[1:]
|
return
|
||||||
args.extend([
|
|
||||||
"start", self.app_name
|
cmd_line = get_rare_executable()
|
||||||
])
|
executable, args = cmd_line[0], cmd_line[1:]
|
||||||
|
args.extend(["start", self.app_name])
|
||||||
if offline:
|
if offline:
|
||||||
args.append("--offline")
|
args.append("--offline")
|
||||||
if skip_update_check:
|
if skip_update_check:
|
||||||
|
@ -473,4 +502,52 @@ class RareGame(QObject):
|
||||||
# kill me, if I don't change it before commit
|
# kill me, if I don't change it before commit
|
||||||
QProcess.startDetached(executable, args)
|
QProcess.startDetached(executable, args)
|
||||||
logger.info(f"Start new Process: ({executable} {' '.join(args)})")
|
logger.info(f"Start new Process: ({executable} {' '.join(args)})")
|
||||||
self.game_process.connect(on_startup=False)
|
self.game_process.connect_to_server(on_startup=False)
|
||||||
|
|
||||||
|
|
||||||
|
class RareEosOverlay(QObject):
|
||||||
|
def __init__(self, legendary_core: LegendaryCore, image_manager: ImageManager, game: Game):
|
||||||
|
super(RareEosOverlay, self).__init__()
|
||||||
|
self.signals = RareGame.Signals()
|
||||||
|
|
||||||
|
self.core = legendary_core
|
||||||
|
self.image_manager = image_manager
|
||||||
|
|
||||||
|
self.game: Game = game
|
||||||
|
|
||||||
|
# None if origin or not installed
|
||||||
|
self.igame: Optional[InstalledGame] = self.core.lgd.get_overlay_install_info()
|
||||||
|
|
||||||
|
self.state = RareGame.State.IDLE
|
||||||
|
|
||||||
|
@property
|
||||||
|
def app_name(self) -> str:
|
||||||
|
return self.igame.app_name if self.igame is not None else self.game.app_name
|
||||||
|
|
||||||
|
@property
|
||||||
|
def app_title(self) -> str:
|
||||||
|
return self.igame.title if self.igame is not None else self.game.app_title
|
||||||
|
|
||||||
|
@property
|
||||||
|
def title(self) -> str:
|
||||||
|
return self.app_title
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_installed(self) -> bool:
|
||||||
|
return self.igame is not None
|
||||||
|
|
||||||
|
def set_installed(self, installed: bool) -> None:
|
||||||
|
if installed:
|
||||||
|
self.igame = self.core.lgd.get_overlay_install_info()
|
||||||
|
self.signals.game.installed.emit(self.app_name)
|
||||||
|
else:
|
||||||
|
self.igame = None
|
||||||
|
self.signals.game.uninstalled.emit(self.app_name)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_mac(self) -> bool:
|
||||||
|
return False
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_win32(self) -> bool:
|
||||||
|
return False
|
||||||
|
|
|
@ -58,8 +58,8 @@ class InstallDownloadModel:
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class InstallQueueItemModel:
|
class InstallQueueItemModel:
|
||||||
download: Optional[InstallDownloadModel] = None
|
|
||||||
options: Optional[InstallOptionsModel] = None
|
options: Optional[InstallOptionsModel] = None
|
||||||
|
download: Optional[InstallDownloadModel] = None
|
||||||
|
|
||||||
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)
|
||||||
|
|
|
@ -20,28 +20,18 @@ class GlobalSignals:
|
||||||
# none
|
# none
|
||||||
update_tray = pyqtSignal()
|
update_tray = pyqtSignal()
|
||||||
|
|
||||||
class ProgressSignals(QObject):
|
|
||||||
# str: app_name
|
|
||||||
started = pyqtSignal(str)
|
|
||||||
# str: app_name, int: progress
|
|
||||||
value = pyqtSignal(str, int)
|
|
||||||
# str: app_name, bool: stopped
|
|
||||||
finished = pyqtSignal(str, bool)
|
|
||||||
|
|
||||||
class GameSignals(QObject):
|
class GameSignals(QObject):
|
||||||
install = pyqtSignal(InstallOptionsModel)
|
install = pyqtSignal(InstallOptionsModel)
|
||||||
# list: app_name
|
# str: app_name
|
||||||
installed = pyqtSignal(list)
|
installed = pyqtSignal(str)
|
||||||
# str: app_name
|
# str: app_name
|
||||||
uninstalled = pyqtSignal(str)
|
uninstalled = pyqtSignal(str)
|
||||||
# str: app_name
|
|
||||||
verified = pyqtSignal(str)
|
|
||||||
|
|
||||||
class DownloadSignals(QObject):
|
class DownloadSignals(QObject):
|
||||||
# str: app_name
|
# str: app_name
|
||||||
enqueue_game = pyqtSignal(str)
|
enqueue = pyqtSignal(str)
|
||||||
# none
|
# str: app_name
|
||||||
update_tab = pyqtSignal()
|
dequeue = pyqtSignal(str)
|
||||||
|
|
||||||
class DiscordRPCSignals(QObject):
|
class DiscordRPCSignals(QObject):
|
||||||
# str: app_title
|
# str: app_title
|
||||||
|
@ -51,7 +41,6 @@ class GlobalSignals:
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.application = GlobalSignals.ApplicationSignals()
|
self.application = GlobalSignals.ApplicationSignals()
|
||||||
self.progress = GlobalSignals.ProgressSignals()
|
|
||||||
self.game = GlobalSignals.GameSignals()
|
self.game = GlobalSignals.GameSignals()
|
||||||
self.download = GlobalSignals.DownloadSignals()
|
self.download = GlobalSignals.DownloadSignals()
|
||||||
self.discord_rpc = GlobalSignals.DiscordRPCSignals()
|
self.discord_rpc = GlobalSignals.DiscordRPCSignals()
|
||||||
|
@ -59,8 +48,6 @@ class GlobalSignals:
|
||||||
def deleteLater(self):
|
def deleteLater(self):
|
||||||
self.application.deleteLater()
|
self.application.deleteLater()
|
||||||
del self.application
|
del self.application
|
||||||
self.progress.deleteLater()
|
|
||||||
del self.progress
|
|
||||||
self.game.deleteLater()
|
self.game.deleteLater()
|
||||||
del self.game
|
del self.game
|
||||||
self.download.deleteLater()
|
self.download.deleteLater()
|
||||||
|
|
|
@ -46,7 +46,7 @@ class GameProcess(QObject):
|
||||||
self.socket.readyRead.connect(self.__on_message)
|
self.socket.readyRead.connect(self.__on_message)
|
||||||
self.socket.disconnected.connect(self.__close)
|
self.socket.disconnected.connect(self.__close)
|
||||||
|
|
||||||
def connect(self, on_startup: bool):
|
def connect_to_server(self, on_startup: bool):
|
||||||
self.on_startup = on_startup
|
self.on_startup = on_startup
|
||||||
self.timer.start(200)
|
self.timer.start(200)
|
||||||
|
|
||||||
|
|
|
@ -67,7 +67,6 @@ def uninstall_game(core: LegendaryCore, app_name: str, keep_files=False, keep_co
|
||||||
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)
|
||||||
game_launched = pyqtSignal(RareGame)
|
|
||||||
update_list = pyqtSignal(str)
|
update_list = pyqtSignal(str)
|
||||||
|
|
||||||
def __init__(self, parent=None):
|
def __init__(self, parent=None):
|
||||||
|
@ -105,7 +104,8 @@ class GameUtils(QObject):
|
||||||
success, message = uninstall_game(self.core, rgame.app_name, keep_files, keep_config)
|
success, message = uninstall_game(self.core, rgame.app_name, keep_files, keep_config)
|
||||||
if not success:
|
if not success:
|
||||||
QMessageBox.warning(None, self.tr("Uninstall - {}").format(rgame.title), message, QMessageBox.Close)
|
QMessageBox.warning(None, self.tr("Uninstall - {}").format(rgame.title), message, QMessageBox.Close)
|
||||||
self.signals.game.uninstalled.emit(rgame.app_name)
|
rgame.set_installed(False)
|
||||||
|
self.signals.download.dequeue.emit(rgame.app_name)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def prepare_launch(
|
def prepare_launch(
|
||||||
|
|
|
@ -6,11 +6,12 @@ from logging import getLogger
|
||||||
from typing import Optional, Dict, Iterator, Callable
|
from typing import Optional, Dict, Iterator, Callable
|
||||||
|
|
||||||
from PyQt5.QtCore import QObject
|
from PyQt5.QtCore import QObject
|
||||||
|
from legendary.lfs.eos import EOSOverlayApp
|
||||||
from legendary.models.game import Game, SaveGameFile
|
from legendary.models.game import Game, SaveGameFile
|
||||||
|
|
||||||
from rare.lgndr.core import LegendaryCore
|
from rare.lgndr.core import LegendaryCore
|
||||||
from rare.models.apiresults import ApiResults
|
from rare.models.apiresults import ApiResults
|
||||||
from rare.models.game import RareGame
|
from rare.models.game import RareGame, RareEosOverlay
|
||||||
from rare.models.signals import GlobalSignals
|
from rare.models.signals import GlobalSignals
|
||||||
from .image_manager import ImageManager
|
from .image_manager import ImageManager
|
||||||
|
|
||||||
|
@ -24,11 +25,11 @@ class RareCore(QObject):
|
||||||
if self._instance is not None:
|
if self._instance is not None:
|
||||||
raise RuntimeError("RareCore already initialized")
|
raise RuntimeError("RareCore already initialized")
|
||||||
super(RareCore, self).__init__()
|
super(RareCore, self).__init__()
|
||||||
self._args: Optional[Namespace] = None
|
self.__args: Optional[Namespace] = None
|
||||||
self._signals: Optional[GlobalSignals] = None
|
self.__signals: Optional[GlobalSignals] = None
|
||||||
self._core: Optional[LegendaryCore] = None
|
self.__core: Optional[LegendaryCore] = None
|
||||||
self._image_manager: Optional[ImageManager] = None
|
self.__image_manager: Optional[ImageManager] = None
|
||||||
self._api_results: Optional[ApiResults] = None
|
self.__api_results: Optional[ApiResults] = None
|
||||||
|
|
||||||
self.args(args)
|
self.args(args)
|
||||||
self.signals(init=True)
|
self.signals(init=True)
|
||||||
|
@ -37,6 +38,8 @@ class RareCore(QObject):
|
||||||
|
|
||||||
self.__games: Dict[str, RareGame] = {}
|
self.__games: Dict[str, RareGame] = {}
|
||||||
|
|
||||||
|
self.__eos_overlay_rgame = RareEosOverlay(self.__core, self.__image_manager, EOSOverlayApp)
|
||||||
|
|
||||||
RareCore._instance = self
|
RareCore._instance = self
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
@ -46,31 +49,31 @@ class RareCore(QObject):
|
||||||
return RareCore._instance
|
return RareCore._instance
|
||||||
|
|
||||||
def signals(self, init: bool = False) -> GlobalSignals:
|
def signals(self, init: bool = False) -> GlobalSignals:
|
||||||
if self._signals is None and not init:
|
if self.__signals is None and not init:
|
||||||
raise RuntimeError("Uninitialized use of GlobalSignalsSingleton")
|
raise RuntimeError("Uninitialized use of GlobalSignalsSingleton")
|
||||||
if self._signals is not None and init:
|
if self.__signals is not None and init:
|
||||||
raise RuntimeError("GlobalSignals already initialized")
|
raise RuntimeError("GlobalSignals already initialized")
|
||||||
if init:
|
if init:
|
||||||
self._signals = GlobalSignals()
|
self.__signals = GlobalSignals()
|
||||||
return self._signals
|
return self.__signals
|
||||||
|
|
||||||
def args(self, args: Namespace = None) -> Optional[Namespace]:
|
def args(self, args: Namespace = None) -> Optional[Namespace]:
|
||||||
if self._args is None and args is None:
|
if self.__args is None and args is None:
|
||||||
raise RuntimeError("Uninitialized use of ArgumentsSingleton")
|
raise RuntimeError("Uninitialized use of ArgumentsSingleton")
|
||||||
if self._args is not None and args is not None:
|
if self.__args is not None and args is not None:
|
||||||
raise RuntimeError("Arguments already initialized")
|
raise RuntimeError("Arguments already initialized")
|
||||||
if args is not None:
|
if args is not None:
|
||||||
self._args = args
|
self.__args = args
|
||||||
return self._args
|
return self.__args
|
||||||
|
|
||||||
def core(self, init: bool = False) -> LegendaryCore:
|
def core(self, init: bool = False) -> LegendaryCore:
|
||||||
if self._core is None and not init:
|
if self.__core is None and not init:
|
||||||
raise RuntimeError("Uninitialized use of LegendaryCoreSingleton")
|
raise RuntimeError("Uninitialized use of LegendaryCoreSingleton")
|
||||||
if self._core is not None and init:
|
if self.__core is not None and init:
|
||||||
raise RuntimeError("LegendaryCore already initialized")
|
raise RuntimeError("LegendaryCore already initialized")
|
||||||
if init:
|
if init:
|
||||||
try:
|
try:
|
||||||
self._core = LegendaryCore()
|
self.__core = LegendaryCore()
|
||||||
except configparser.MissingSectionHeaderError as e:
|
except configparser.MissingSectionHeaderError as e:
|
||||||
logger.warning(f"Config is corrupt: {e}")
|
logger.warning(f"Config is corrupt: {e}")
|
||||||
if config_path := os.environ.get("XDG_CONFIG_HOME"):
|
if config_path := os.environ.get("XDG_CONFIG_HOME"):
|
||||||
|
@ -79,70 +82,74 @@ class RareCore(QObject):
|
||||||
path = os.path.expanduser("~/.config/legendary")
|
path = os.path.expanduser("~/.config/legendary")
|
||||||
with open(os.path.join(path, "config.ini"), "w") as config_file:
|
with open(os.path.join(path, "config.ini"), "w") as config_file:
|
||||||
config_file.write("[Legendary]")
|
config_file.write("[Legendary]")
|
||||||
self._core = LegendaryCore()
|
self.__core = LegendaryCore()
|
||||||
if "Legendary" not in self._core.lgd.config.sections():
|
if "Legendary" not in self.__core.lgd.config.sections():
|
||||||
self._core.lgd.config.add_section("Legendary")
|
self.__core.lgd.config.add_section("Legendary")
|
||||||
self._core.lgd.save_config()
|
self.__core.lgd.save_config()
|
||||||
# workaround if egl sync enabled, but no programdata_path
|
# workaround if egl sync enabled, but no programdata_path
|
||||||
# programdata_path might be unset if logging in through the browser
|
# programdata_path might be unset if logging in through the browser
|
||||||
if self._core.egl_sync_enabled:
|
if self.__core.egl_sync_enabled:
|
||||||
if self._core.egl.programdata_path is None:
|
if self.__core.egl.programdata_path is None:
|
||||||
self._core.lgd.config.remove_option("Legendary", "egl_sync")
|
self.__core.lgd.config.remove_option("Legendary", "egl_sync")
|
||||||
self._core.lgd.save_config()
|
self.__core.lgd.save_config()
|
||||||
else:
|
else:
|
||||||
if not os.path.exists(self._core.egl.programdata_path):
|
if not os.path.exists(self.__core.egl.programdata_path):
|
||||||
self._core.lgd.config.remove_option("Legendary", "egl_sync")
|
self.__core.lgd.config.remove_option("Legendary", "egl_sync")
|
||||||
self._core.lgd.save_config()
|
self.__core.lgd.save_config()
|
||||||
return self._core
|
return self.__core
|
||||||
|
|
||||||
def image_manager(self, init: bool = False) -> ImageManager:
|
def image_manager(self, init: bool = False) -> ImageManager:
|
||||||
if self._image_manager is None and not init:
|
if self.__image_manager is None and not init:
|
||||||
raise RuntimeError("Uninitialized use of ImageManagerSingleton")
|
raise RuntimeError("Uninitialized use of ImageManagerSingleton")
|
||||||
if self._image_manager is not None and init:
|
if self.__image_manager is not None and init:
|
||||||
raise RuntimeError("ImageManager already initialized")
|
raise RuntimeError("ImageManager already initialized")
|
||||||
if self._image_manager is None:
|
if self.__image_manager is None:
|
||||||
self._image_manager = ImageManager(self.signals(), self.core())
|
self.__image_manager = ImageManager(self.signals(), self.core())
|
||||||
return self._image_manager
|
return self.__image_manager
|
||||||
|
|
||||||
def api_results(self, res: ApiResults = None) -> Optional[ApiResults]:
|
def api_results(self, res: ApiResults = None) -> Optional[ApiResults]:
|
||||||
if self._api_results is None and res is None:
|
if self.__api_results is None and res is None:
|
||||||
raise RuntimeError("Uninitialized use of ApiResultsSingleton")
|
raise RuntimeError("Uninitialized use of ApiResultsSingleton")
|
||||||
if self._api_results is not None and res is not None:
|
if self.__api_results is not None and res is not None:
|
||||||
raise RuntimeError("ApiResults already initialized")
|
raise RuntimeError("ApiResults already initialized")
|
||||||
if res is not None:
|
if res is not None:
|
||||||
self._api_results = res
|
self.__api_results = res
|
||||||
return self._api_results
|
return self.__api_results
|
||||||
|
|
||||||
def deleteLater(self) -> None:
|
def deleteLater(self) -> None:
|
||||||
del self._api_results
|
del self.__api_results
|
||||||
self._api_results = None
|
self.__api_results = None
|
||||||
|
|
||||||
self._image_manager.deleteLater()
|
self.__image_manager.deleteLater()
|
||||||
del self._image_manager
|
del self.__image_manager
|
||||||
self._image_manager = None
|
self.__image_manager = None
|
||||||
|
|
||||||
self._core.exit()
|
self.__core.exit()
|
||||||
del self._core
|
del self.__core
|
||||||
self._core = None
|
self.__core = None
|
||||||
|
|
||||||
self._signals.deleteLater()
|
self.__signals.deleteLater()
|
||||||
del self._signals
|
del self.__signals
|
||||||
self._signals = None
|
self.__signals = None
|
||||||
|
|
||||||
del self._args
|
del self.__args
|
||||||
self._args = None
|
self.__args = None
|
||||||
|
|
||||||
RareCore._instance = None
|
RareCore._instance = None
|
||||||
|
|
||||||
super(RareCore, self).deleteLater()
|
super(RareCore, self).deleteLater()
|
||||||
|
|
||||||
def get_game(self, app_name: str) -> RareGame:
|
def get_game(self, app_name: str) -> RareGame:
|
||||||
|
if app_name == EOSOverlayApp.app_name:
|
||||||
|
return self.__eos_overlay_rgame
|
||||||
return self.__games[app_name]
|
return self.__games[app_name]
|
||||||
|
|
||||||
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.finished.connect(self._signals.application.update_tray)
|
rgame.signals.game.installed.connect(self.__signals.game.installed)
|
||||||
rgame.signals.game.finished.connect(lambda: self._signals.discord_rpc.set_title.emit(""))
|
rgame.signals.game.uninstalled.connect(self.__signals.game.uninstalled)
|
||||||
|
rgame.signals.game.finished.connect(self.__signals.application.update_tray)
|
||||||
|
rgame.signals.game.finished.connect(lambda: self.__signals.discord_rpc.set_title.emit(""))
|
||||||
self.__games[rgame.app_name] = rgame
|
self.__games[rgame.app_name] = rgame
|
||||||
|
|
||||||
def __filter_games(self, condition: Callable[[RareGame], bool]) -> Iterator[RareGame]:
|
def __filter_games(self, condition: Callable[[RareGame], bool]) -> Iterator[RareGame]:
|
||||||
|
|
|
@ -23,7 +23,7 @@ class VerifyWorker(QRunnable):
|
||||||
num: int = 0
|
num: int = 0
|
||||||
total: int = 1 # set default to 1 to avoid DivisionByZero before it is initialized
|
total: int = 1 # set default to 1 to avoid DivisionByZero before it is initialized
|
||||||
|
|
||||||
def __init__(self, rgame: RareGame, core: LegendaryCore, args: Namespace):
|
def __init__(self, core: LegendaryCore, args: Namespace, rgame: RareGame):
|
||||||
sys.excepthook = sys.__excepthook__
|
sys.excepthook = sys.__excepthook__
|
||||||
super(VerifyWorker, self).__init__()
|
super(VerifyWorker, self).__init__()
|
||||||
self.signals = VerifyWorker.Signals()
|
self.signals = VerifyWorker.Signals()
|
||||||
|
|
|
@ -14,7 +14,7 @@ from PyQt5 import QtCore, QtGui, QtWidgets
|
||||||
class Ui_DownloadWidget(object):
|
class Ui_DownloadWidget(object):
|
||||||
def setupUi(self, DownloadWidget):
|
def setupUi(self, DownloadWidget):
|
||||||
DownloadWidget.setObjectName("DownloadWidget")
|
DownloadWidget.setObjectName("DownloadWidget")
|
||||||
DownloadWidget.resize(574, 70)
|
DownloadWidget.resize(332, 70)
|
||||||
DownloadWidget.setWindowTitle("DownloadWidget")
|
DownloadWidget.setWindowTitle("DownloadWidget")
|
||||||
DownloadWidget.setFrameShape(QtWidgets.QFrame.StyledPanel)
|
DownloadWidget.setFrameShape(QtWidgets.QFrame.StyledPanel)
|
||||||
self.download_widget_layout = QtWidgets.QHBoxLayout(DownloadWidget)
|
self.download_widget_layout = QtWidgets.QHBoxLayout(DownloadWidget)
|
||||||
|
@ -54,12 +54,12 @@ class Ui_DownloadWidget(object):
|
||||||
self.queue_buttons_layout = QtWidgets.QVBoxLayout(self.queue_buttons)
|
self.queue_buttons_layout = QtWidgets.QVBoxLayout(self.queue_buttons)
|
||||||
self.queue_buttons_layout.setContentsMargins(0, 0, 0, 0)
|
self.queue_buttons_layout.setContentsMargins(0, 0, 0, 0)
|
||||||
self.queue_buttons_layout.setObjectName("queue_buttons_layout")
|
self.queue_buttons_layout.setObjectName("queue_buttons_layout")
|
||||||
|
self.force_button = QtWidgets.QPushButton(self.queue_buttons)
|
||||||
|
self.force_button.setObjectName("force_button")
|
||||||
|
self.queue_buttons_layout.addWidget(self.force_button)
|
||||||
self.remove_button = QtWidgets.QPushButton(self.queue_buttons)
|
self.remove_button = QtWidgets.QPushButton(self.queue_buttons)
|
||||||
self.remove_button.setObjectName("remove_button")
|
self.remove_button.setObjectName("remove_button")
|
||||||
self.queue_buttons_layout.addWidget(self.remove_button, 0, QtCore.Qt.AlignTop)
|
self.queue_buttons_layout.addWidget(self.remove_button, 0, QtCore.Qt.AlignTop)
|
||||||
self.force_button = QtWidgets.QPushButton(self.queue_buttons)
|
|
||||||
self.force_button.setObjectName("force_button")
|
|
||||||
self.queue_buttons_layout.addWidget(self.force_button, 0, QtCore.Qt.AlignTop)
|
|
||||||
self.download_widget_layout.addWidget(self.queue_buttons)
|
self.download_widget_layout.addWidget(self.queue_buttons)
|
||||||
self.update_buttons = QtWidgets.QWidget(DownloadWidget)
|
self.update_buttons = QtWidgets.QWidget(DownloadWidget)
|
||||||
self.update_buttons.setObjectName("update_buttons")
|
self.update_buttons.setObjectName("update_buttons")
|
||||||
|
@ -79,8 +79,8 @@ class Ui_DownloadWidget(object):
|
||||||
|
|
||||||
def retranslateUi(self, DownloadWidget):
|
def retranslateUi(self, DownloadWidget):
|
||||||
_translate = QtCore.QCoreApplication.translate
|
_translate = QtCore.QCoreApplication.translate
|
||||||
self.remove_button.setText(_translate("DownloadWidget", "Remove from queue"))
|
|
||||||
self.force_button.setText(_translate("DownloadWidget", "Update now"))
|
self.force_button.setText(_translate("DownloadWidget", "Update now"))
|
||||||
|
self.remove_button.setText(_translate("DownloadWidget", "Remove from queue"))
|
||||||
self.update_button.setText(_translate("DownloadWidget", "Update game"))
|
self.update_button.setText(_translate("DownloadWidget", "Update game"))
|
||||||
self.settings_button.setText(_translate("DownloadWidget", "Update with settings"))
|
self.settings_button.setText(_translate("DownloadWidget", "Update with settings"))
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
<rect>
|
<rect>
|
||||||
<x>0</x>
|
<x>0</x>
|
||||||
<y>0</y>
|
<y>0</y>
|
||||||
<width>574</width>
|
<width>332</width>
|
||||||
<height>70</height>
|
<height>70</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
|
|
|
@ -17,7 +17,7 @@ from PyQt5.QtCore import (
|
||||||
QDir,
|
QDir,
|
||||||
)
|
)
|
||||||
from PyQt5.QtGui import QPalette, QColor, QImage
|
from PyQt5.QtGui import QPalette, QColor, QImage
|
||||||
from PyQt5.QtWidgets import qApp, QStyleFactory
|
from PyQt5.QtWidgets import qApp, QStyleFactory, QWidget
|
||||||
from legendary.core import LegendaryCore
|
from legendary.core import LegendaryCore
|
||||||
from legendary.models.game import Game
|
from legendary.models.game import Game
|
||||||
from requests.exceptions import HTTPError
|
from requests.exceptions import HTTPError
|
||||||
|
@ -350,3 +350,7 @@ def icon(icn_str: str, fallback: str = None, **kwargs):
|
||||||
if kwargs.get("color"):
|
if kwargs.get("color"):
|
||||||
kwargs["color"] = "red"
|
kwargs["color"] = "red"
|
||||||
return qtawesome.icon("ei.error", **kwargs)
|
return qtawesome.icon("ei.error", **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
def widget_object_name(widget: QWidget, app_name: str) -> str:
|
||||||
|
return f"{type(widget).__name__}_{app_name}"
|
Loading…
Reference in a new issue