diff --git a/rare/components/dialogs/install_dialog.py b/rare/components/dialogs/install_dialog.py
index 5b3f2386..596b6a1e 100644
--- a/rare/components/dialogs/install_dialog.py
+++ b/rare/components/dialogs/install_dialog.py
@@ -3,11 +3,12 @@ import platform as pf
import sys
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.QtWidgets import QDialog, QFileDialog, QCheckBox, QLayout, QWidget, QVBoxLayout, QApplication
+from legendary.lfs.eos import EOSOverlayApp
from legendary.models.downloading import ConditionCheckResult
-from legendary.models.game import Game
from legendary.utils.selective_dl import get_sdl_appname
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.exception import LgndrException
from rare.lgndr.glue.monkeys import LgndrIndirectStatus
-from rare.models.install import InstallDownloadModel, InstallQueueItemModel
-from rare.shared import LegendaryCoreSingleton, ApiResultsSingleton, ArgumentsSingleton
+from rare.models.game import RareGame
+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_advanced import Ui_InstallDialogAdvanced
from rare.utils import config_helper
@@ -37,7 +39,7 @@ class InstallDialogAdvanced(CollapsibleFrame):
class InstallDialog(QDialog):
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)
self.setAttribute(Qt.WA_DeleteOnClose, True)
self.setWindowFlags(Qt.Dialog | Qt.CustomizeWindowHint | Qt.WindowTitleHint)
@@ -45,14 +47,9 @@ class InstallDialog(QDialog):
self.ui.setupUi(self)
self.core = LegendaryCoreSingleton()
- self.api_results = ApiResultsSingleton()
- self.dl_item = dl_item
- self.app_name = self.dl_item.options.app_name
- 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.rgame = rgame
+ self.options = options
+ self.__download: Optional[InstallDownloadModel] = None
self.advanced = InstallDialogAdvanced(parent=self)
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.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.worker_running = False
self.reject_close = True
@@ -73,24 +64,31 @@ class InstallDialog(QDialog):
self.threadpool = QThreadPool(self)
self.threadpool.setMaxThreadCount(1)
- header = self.tr("Update") if update else self.tr("Install")
- self.ui.install_dialog_label.setText(f'
{header} "{self.game.app_title}"
')
- self.setWindowTitle(f'{QApplication.instance().applicationName()} - {header} "{self.game.app_title}"')
+ if options.repair_mode:
+ header = self.tr("Repair")
+ 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'{header} "{self.rgame.app_title}"
')
+ self.setWindowTitle(f'{QApplication.instance().applicationName()} - {header} "{self.rgame.app_title}"')
- if not self.dl_item.options.base_path:
- self.dl_item.options.base_path = self.core.lgd.config.get(
+ if not self.options.base_path:
+ self.options.base_path = self.core.lgd.config.get(
"Legendary", "install_dir", fallback=os.path.expanduser("~/legendary")
)
self.install_dir_edit = PathEdit(
- path=self.dl_item.options.base_path,
+ path=self.options.base_path,
file_type=QFileDialog.DirectoryOnly,
edit_func=self.option_changed,
parent=self,
)
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.install_dir_edit.setEnabled(False)
self.ui.shortcut_label.setEnabled(False)
@@ -101,9 +99,9 @@ class InstallDialog(QDialog):
self.error_box()
platforms = ["Windows"]
- if dl_item.options.app_name in self.api_results.bit32_games:
+ if self.rgame.is_win32:
platforms.append("Win32")
- if dl_item.options.app_name in self.api_results.mac_games:
+ if self.rgame.is_mac:
platforms.append("Mac")
self.ui.platform_combo.addItems(platforms)
self.ui.platform_combo.currentIndexChanged.connect(lambda: self.option_changed(None))
@@ -145,16 +143,16 @@ class InstallDialog(QDialog):
self.ui.install_button.setEnabled(False)
- if self.dl_item.options.overlay:
- self.ui.platform_label.setVisible(False)
- self.ui.platform_combo.setVisible(False)
- self.advanced.ui.ignore_space_label.setVisible(False)
- self.advanced.ui.ignore_space_check.setVisible(False)
- self.advanced.ui.download_only_label.setVisible(False)
- self.advanced.ui.download_only_check.setVisible(False)
- self.ui.shortcut_label.setVisible(False)
- self.ui.shortcut_check.setVisible(False)
- self.selectable.setVisible(False)
+ if self.options.overlay:
+ self.ui.platform_label.setEnabled(False)
+ self.ui.platform_combo.setEnabled(False)
+ self.advanced.ui.ignore_space_label.setEnabled(False)
+ self.advanced.ui.ignore_space_check.setEnabled(False)
+ self.advanced.ui.download_only_label.setEnabled(False)
+ self.advanced.ui.download_only_check.setEnabled(False)
+ self.ui.shortcut_label.setEnabled(False)
+ self.ui.shortcut_check.setEnabled(False)
+ self.selectable.setEnabled(False)
if pf.system() == "Darwin":
self.ui.shortcut_check.setDisabled(True)
@@ -173,12 +171,12 @@ class InstallDialog(QDialog):
self.ui.verify_button.clicked.connect(self.verify_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)
def execute(self):
- if self.silent:
+ if self.options.silent:
self.reject_close = False
self.get_download_info()
else:
@@ -192,10 +190,10 @@ class InstallDialog(QDialog):
cb.deleteLater()
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(",")
- config_disable_sdl = self.core.lgd.config.getboolean(self.game.app_name, 'disable_sdl', fallback=False)
- sdl_name = get_sdl_appname(self.game.app_name)
+ config_disable_sdl = self.core.lgd.config.getboolean(self.rgame.app_name, 'disable_sdl', fallback=False)
+ sdl_name = get_sdl_appname(self.rgame.app_name)
if not config_disable_sdl and sdl_name is not None:
# FIXME: this should be updated whenever platform changes
sdl_data = self.core.get_sdl_data(sdl_name, platform=platform)
@@ -220,28 +218,26 @@ class InstallDialog(QDialog):
self.selectable.setDisabled(True)
def get_options(self):
- self.dl_item.options.base_path = self.install_dir_edit.text() if not self.update else None
-
- self.dl_item.options.max_workers = self.advanced.ui.max_workers_spin.value()
- self.dl_item.options.shared_memory = self.advanced.ui.max_memory_spin.value()
- self.dl_item.options.order_opt = self.advanced.ui.dl_optimizations_check.isChecked()
- self.dl_item.options.force = self.advanced.ui.force_download_check.isChecked()
- self.dl_item.options.ignore_space = self.advanced.ui.ignore_space_check.isChecked()
- self.dl_item.options.no_install = self.advanced.ui.download_only_check.isChecked()
- self.dl_item.options.platform = self.ui.platform_combo.currentText()
- self.dl_item.options.install_prereqs = self.advanced.ui.install_prereqs_check.isChecked()
- self.dl_item.options.create_shortcut = self.ui.shortcut_check.isChecked()
+ 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.options.shared_memory = self.advanced.ui.max_memory_spin.value()
+ self.options.order_opt = self.advanced.ui.dl_optimizations_check.isChecked()
+ self.options.force = self.advanced.ui.force_download_check.isChecked()
+ self.options.ignore_space = self.advanced.ui.ignore_space_check.isChecked()
+ self.options.no_install = self.advanced.ui.download_only_check.isChecked()
+ self.options.platform = self.ui.platform_combo.currentText()
+ self.options.install_prereqs = self.advanced.ui.install_prereqs_check.isChecked()
+ self.options.create_shortcut = self.ui.shortcut_check.isChecked()
if self.selectable_checks:
- self.dl_item.options.install_tag = [""]
+ self.options.install_tag = [""]
for cb in self.selectable_checks:
if data := cb.isChecked():
# noinspection PyTypeChecker
- self.dl_item.options.install_tag.extend(data)
+ self.options.install_tag.extend(data)
def get_download_info(self):
- self.dl_item.download = None
- info_worker = InstallInfoWorker(self.core, self.dl_item, self.game)
- info_worker.setAutoDelete(True)
+ self.__download = None
+ info_worker = InstallInfoWorker(self.core, self.options)
info_worker.signals.result.connect(self.on_worker_result)
info_worker.signals.failed.connect(self.on_worker_failed)
info_worker.signals.finished.connect(self.on_worker_finished)
@@ -270,21 +266,21 @@ class InstallDialog(QDialog):
def non_reload_option_changed(self, option: str):
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":
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":
- 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):
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:
# 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.close()
@@ -292,33 +288,35 @@ class InstallDialog(QDialog):
self.reject_close = False
self.close()
- def on_worker_result(self, dl_item: InstallDownloadModel):
- self.dl_item.download = dl_item
- download_size = self.dl_item.download.analysis.dl_size
- install_size = self.dl_item.download.analysis.install_size
+ @pyqtSlot(InstallQueueItemModel)
+ def on_worker_result(self, dl_item: InstallQueueItemModel):
+ self.__download = dl_item.download
+ 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:
- 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.install_button.setEnabled(not self.options_changed)
else:
self.ui.install_size_text.setText(self.tr("Game already installed"))
self.ui.install_size_text.setStyleSheet("font-style: italics; font-weight: normal")
- self.ui.install_size_text.setText("{}".format(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.verify_button.setEnabled(self.options_changed)
self.ui.cancel_button.setEnabled(True)
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_label.setEnabled(True)
self.advanced.ui.install_prereqs_check.setChecked(True)
- prereq_name = dl_item.igame.prereq_info.get("name", "")
- prereq_path = os.path.split(dl_item.igame.prereq_info.get("path", ""))[-1]
+ prereq_name = dl_item.download.igame.prereq_info.get("name", "")
+ prereq_path = os.path.split(dl_item.download.igame.prereq_info.get("path", ""))[-1]
prereq_desc = prereq_name if prereq_name else prereq_path
self.advanced.ui.install_prereqs_check.setText(
self.tr("Also install: {}").format(prereq_desc)
)
- if self.silent:
+ if self.options.silent:
self.close()
def on_worker_failed(self, message: str):
@@ -328,7 +326,7 @@ class InstallDialog(QDialog):
self.error_box(error_text, message)
self.ui.verify_button.setEnabled(self.options_changed)
self.ui.cancel_button.setEnabled(True)
- if self.silent:
+ if self.options.silent:
self.show()
def error_box(self, label: str = "", message: str = ""):
@@ -349,7 +347,7 @@ class InstallDialog(QDialog):
else:
self.threadpool.clear()
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)
def keyPressEvent(self, e: QKeyEvent) -> None:
@@ -359,51 +357,51 @@ class InstallDialog(QDialog):
class InstallInfoWorker(QRunnable):
class Signals(QObject):
- result = pyqtSignal(InstallDownloadModel)
+ result = pyqtSignal(InstallQueueItemModel)
failed = pyqtSignal(str)
finished = pyqtSignal()
- def __init__(self, core: LegendaryCore, dl_item: InstallQueueItemModel, game: Game = None):
+ def __init__(self, core: LegendaryCore, options: InstallOptionsModel):
sys.excepthook = sys.__excepthook__
super(InstallInfoWorker, self).__init__()
+ self.setAutoDelete(True)
self.signals = InstallInfoWorker.Signals()
self.core = core
- self.dl_item = dl_item
- self.game = game
+ self.options = options
@pyqtSlot()
def run(self):
try:
- if not self.dl_item.options.overlay:
+ if not self.options.overlay:
cli = LegendaryCLI(self.core)
status = LgndrIndirectStatus()
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:
download = InstallDownloadModel(*result)
else:
raise LgndrException(status.message)
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)
dlm, analysis, igame = self.core.prepare_overlay_install(
- path=self.dl_item.options.base_path
+ path=self.options.base_path
)
download = InstallDownloadModel(
dlm=dlm,
analysis=analysis,
igame=igame,
- game=self.game,
+ game=EOSOverlayApp,
repair=False,
repair_file="",
res=ConditionCheckResult(), # empty
)
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:
self.signals.failed.emit("\n".join(str(i) for i in download.res.failures))
except LgndrException as ret:
@@ -421,3 +419,6 @@ class TagCheckBox(QCheckBox):
def isChecked(self) -> Union[bool, List[str]]:
return self.tags if super(TagCheckBox, self).isChecked() else False
+
+
+
diff --git a/rare/components/dialogs/launch_dialog.py b/rare/components/dialogs/launch_dialog.py
index 7bef22f6..0018b003 100644
--- a/rare/components/dialogs/launch_dialog.py
+++ b/rare/components/dialogs/launch_dialog.py
@@ -282,6 +282,8 @@ class LaunchDialog(QDialog):
if self.completed >= 2:
logger.info("App starting")
ApiResultsSingleton(self.api_results)
+ # FIXME: Add this to RareCore
+ self.core.lgd.entitlements = self.core.egs.get_user_entitlements()
self.completed += 1
self.start_app.emit()
diff --git a/rare/components/main_window.py b/rare/components/main_window.py
index a6aee0fd..241dc4f9 100644
--- a/rare/components/main_window.py
+++ b/rare/components/main_window.py
@@ -164,9 +164,7 @@ class MainWindow(QMainWindow):
defaultButton=QMessageBox.No,
)
if reply == QMessageBox.Yes:
- # clear queue
- self.tab_widget.downloads_tab.queue_widget.update_queue([])
- self.tab_widget.downloads_tab.stop_download()
+ self.tab_widget.downloads_tab.stop_download(on_exit=True)
else:
e.ignore()
return
diff --git a/rare/components/tabs/__init__.py b/rare/components/tabs/__init__.py
index 1bb950fc..915e4d74 100644
--- a/rare/components/tabs/__init__.py
+++ b/rare/components/tabs/__init__.py
@@ -32,11 +32,10 @@ class TabWidget(QTabWidget):
# Downloads Tab after Games Tab to use populated RareCore games list
if not self.args.offline:
self.downloads_tab = DownloadsTab(self)
- updates = list(self.rcore.updates)
- self.addTab(
- self.downloads_tab,
- self.tr("Downloads {}").format(f"({len(updates) if updates else 0})"),
- )
+ # update dl tab text
+ self.addTab(self.downloads_tab, "")
+ self.__on_downloads_update_title(self.downloads_tab.queues_count())
+ self.downloads_tab.update_title.connect(self.__on_downloads_update_title)
self.store = Shop(self.core)
self.addTab(self.store, self.tr("Store (Beta)"))
@@ -71,12 +70,9 @@ class TabWidget(QTabWidget):
# set current index
# 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
self.tabBarClicked.connect(self.mouse_clicked)
- self.setIconSize(QSize(25, 25))
+ self.setIconSize(QSize(24, 24))
# shortcuts
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+4", self).activated.connect(lambda: self.setCurrentIndex(5))
- def update_dl_tab_text(self):
- num_downloads = len(
- set(
- [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")
+ @pyqtSlot(int)
+ def __on_downloads_update_title(self, num_downloads: int):
+ self.setTabText(self.indexOf(self.downloads_tab), self.tr("Downloads ({})").format(num_downloads))
def mouse_clicked(self, tab_num):
if tab_num == 0:
diff --git a/rare/components/tabs/downloads/__init__.py b/rare/components/tabs/downloads/__init__.py
index c829a5de..c27b497b 100644
--- a/rare/components/tabs/downloads/__init__.py
+++ b/rare/components/tabs/downloads/__init__.py
@@ -1,303 +1,265 @@
import datetime
+from ctypes import c_ulonglong
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 (
QWidget,
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.tabs.downloads.dl_queue_widget import DlQueueWidget, DlWidget
-from rare.components.tabs.downloads.download_thread import DownloadThread
+from rare.components.dialogs.install_dialog import InstallDialog, InstallInfoWorker
from rare.lgndr.models.downloading import UIUpdate
from rare.models.game import RareGame
from rare.models.install import InstallOptionsModel, InstallQueueItemModel
-from rare.shared import RareCore, LegendaryCoreSingleton, GlobalSignalsSingleton
+from rare.shared import RareCore, LegendaryCoreSingleton, GlobalSignalsSingleton, ArgumentsSingleton
from rare.ui.components.tabs.downloads.downloads_tab import Ui_DownloadsTab
from rare.utils.misc import get_size, create_desktop_link
+from .groups import UpdateGroup, QueueGroup
+from .thread import DlThread, DlResultModel, DlResultCode
+from .widgets import UpdateWidget, QueueWidget
logger = getLogger("Download")
-class DownloadsTab(QWidget, Ui_DownloadsTab):
- thread: QThread
- dl_queue: List[InstallQueueItemModel] = []
- dl_status = pyqtSignal(int)
+def get_time(seconds: Union[int, float]) -> str:
+ return str(datetime.timedelta(seconds=seconds))
+
+
+class DownloadsTab(QWidget):
+ # int: number of updates
+ update_title = pyqtSignal(int)
def __init__(self, parent=None):
super(DownloadsTab, self).__init__(parent=parent)
- self.setupUi(self)
+ self.ui = Ui_DownloadsTab()
+ self.ui.setupUi(self)
self.rcore = RareCore.instance()
self.core = LegendaryCoreSingleton()
self.signals = GlobalSignalsSingleton()
+ self.args = ArgumentsSingleton()
- self.active_game: Optional[Game] = None
- self.analysis = None
+ self.thread: Optional[DlThread] = 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_widget.update_list.connect(self.update_dl_queue)
- self.queue_scroll_contents_layout.addWidget(self.queue_widget)
+ self.queue_group = QueueGroup(self)
+ # lk: todo recreate update 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.setObjectName("updates_group")
- self.update_layout = QVBoxLayout(self.updates)
- self.queue_scroll_contents_layout.addWidget(self.updates)
+ self.updates_group = UpdateGroup(self)
+ self.updates_group.enqueue.connect(self.__get_install_options)
+ self.ui.queue_scroll_contents_layout.addWidget(self.updates_group)
- self.update_widgets: Dict[str, UpdateWidget] = {}
+ self.__check_updates()
- self.update_text = QLabel(self.tr("No updates available"))
- self.update_layout.addWidget(self.update_text)
+ self.signals.game.install.connect(self.__get_install_options)
+ 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:
- has_updates = True
- self.add_update(rgame)
+ self.__add_update(rgame)
- self.queue_widget.item_removed.connect(self.queue_item_removed)
-
- self.signals.game.install.connect(self.get_install_options)
- self.signals.game.uninstalled.connect(self.queue_item_removed)
- self.signals.game.uninstalled.connect(self.remove_update)
-
- self.signals.download.enqueue_game.connect(
- lambda app_name: self.add_update(app_name)
- )
- self.signals.game.uninstalled.connect(self.game_uninstalled)
-
- self.reset_infos()
-
- def queue_item_removed(self, app_name):
- if w := self.update_widgets.get(app_name):
- w.update_button.setDisabled(False)
- w.update_with_settings.setDisabled(False)
-
- def add_update(self, rgame: RareGame):
- if old_widget := self.update_widgets.get(rgame.app_name, False):
- old_widget.deleteLater()
- 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)
+ def __add_update(self, update: Union[str,RareGame]):
+ if isinstance(update, str):
+ update = self.rcore.get_game(update)
+ if update.metadata.auto_update or QSettings().value("auto_update", False, bool):
+ self.__get_install_options(
+ InstallOptionsModel(app_name=update.app_name, update=True, silent=True)
)
- widget.update_button.setDisabled(True)
- self.update_text.setVisible(False)
+ else:
+ self.updates_group.append(update.game, update.igame)
- 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
+ @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)
- # if game is updating
- if self.active_game and self.active_game.app_name == app_name:
+ @pyqtSlot(InstallQueueItemModel)
+ def __on_queue_force(self, item: InstallQueueItemModel):
+ if self.thread:
+ self.stop_download()
+ self.forced_item = item
+ else:
+ self.__start_installation(item)
+
+ @pyqtSlot(str)
+ def __on_game_uninstalled(self, app_name):
+ if self.thread and self.thread.item.options.app_name == app_name:
self.stop_download()
- # game has available update
- if app_name in self.update_widgets.keys():
- self.remove_update(app_name)
+ if self.queue_group.contains(app_name):
+ self.queue_group.remove(app_name)
- def remove_update(self, app_name):
- if w := self.update_widgets.get(app_name):
- w.deleteLater()
- self.update_widgets.pop(app_name)
+ if self.updates_group.contains(app_name):
+ self.updates_group.remove(app_name)
- if len(self.update_widgets) == 0:
- self.update_text.setVisible(True)
+ self.update_title.emit(self.queues_count())
- self.signals.download.update_tab.emit()
-
- def update_dl_queue(self, dl_queue):
- self.dl_queue = dl_queue
-
- def stop_download(self):
+ def stop_download(self, on_exit=False):
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):
- if self.active_game is None:
- self.start_installation(queue_item)
- else:
- self.dl_queue.append(queue_item)
- self.queue_widget.update_queue(self.dl_queue)
+ def __start_installation(self, item: InstallQueueItemModel):
+ thread = DlThread(item, self.rcore.get_game(item.options.app_name), self.core, self.args.debug)
+ thread.result.connect(self.__on_download_result)
+ thread.progress.connect(self.__on_download_progress)
+ thread.finished.connect(thread.deleteLater)
+ 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):
- if self.dl_queue:
- 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)
+ def queues_count(self) -> int:
+ return self.updates_group.count() + self.queue_group.count()
- 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)
- def status(self, result: DownloadThread.ReturnStatus):
- if result.ret_code == result.ReturnCode.FINISHED:
+ @pyqtSlot(str)
+ def __on_info_worker_failed(self, message: str):
+ 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 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
pass
else:
logger.info("Desktop shortcut written")
+ logger.info(
+ f"Download finished: {result.item.download.game.app_name} ({result.item.download.game.app_title})"
+ )
- self.dl_name.setText(self.tr("Download finished. Reload library"))
- 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])
+ if result.item.options.overlay:
+ self.signals.application.overlay_installed.emit()
else:
- self.queue_widget.update_queue(self.dl_queue)
+ self.signals.application.notify.emit(result.item.download.game.app_name)
- elif result.ret_code == result.ReturnCode.ERROR:
- QMessageBox.warning(self, self.tr("Error"), f"Download error: {result.message}")
+ if self.updates_group.contains(result.item.options.app_name):
+ self.updates_group.set_widget_enabled(result.item.options.app_name, True)
- 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])
+ 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}")
- 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
+ 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
- @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(
+ 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'}"
)
- self.downloaded.setText(
- f"{get_size(ui_update.total_downloaded)} / {get_size(self.analysis.dl_size)}"
- )
- 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.downloaded.setText(
+ f"{get_size(ui_update.total_downloaded)} / {get_size(dl_size.value)}"
)
+ self.ui.time_left.setText(get_time(ui_update.estimated_time_left))
- def get_time(self, seconds: Union[int, float]) -> str:
- return str(datetime.timedelta(seconds=seconds))
-
- def on_install_dialog_closed(self, download_item: InstallQueueItemModel):
- if download_item:
- self.install_game(download_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)
+ @pyqtSlot(InstallQueueItemModel)
+ def __on_install_dialog_closed(self, item: InstallQueueItemModel):
+ if item:
+ # lk: start update only if there is no other active thread and there is no queue
+ if self.thread is None and not self.queue_group.count():
+ self.__start_installation(item)
+ else:
+ rgame = self.rcore.get_game(item.options.app_name)
+ self.queue_group.push_back(item, rgame.igame)
+ # 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 w := self.update_widgets.get(download_item.options.app_name):
- w.update_button.setDisabled(False)
- w.update_with_settings.setDisabled(False)
+ 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(
- InstallQueueItemModel(options=options),
- update=options.update,
- silent=options.silent,
+ self.rcore.get_game(options.app_name),
+ options=options,
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()
@property
def is_download_active(self):
- return self.active_game 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: {} >> {}")
- .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
+ return self.thread is not None
diff --git a/rare/components/tabs/downloads/dl_queue_widget.py b/rare/components/tabs/downloads/dl_queue_widget.py
deleted file mode 100644
index 18152273..00000000
--- a/rare/components/tabs/downloads/dl_queue_widget.py
+++ /dev/null
@@ -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)
diff --git a/rare/components/tabs/downloads/groups.py b/rare/components/tabs/downloads/groups.py
new file mode 100644
index 00000000..8dcef8ef
--- /dev/null
+++ b/rare/components/tabs/downloads/groups.py
@@ -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_())
\ No newline at end of file
diff --git a/rare/components/tabs/downloads/download_thread.py b/rare/components/tabs/downloads/thread.py
similarity index 70%
rename from rare/components/tabs/downloads/download_thread.py
rename to rare/components/tabs/downloads/thread.py
index 6e25cdd2..5d67511b 100644
--- a/rare/components/tabs/downloads/download_thread.py
+++ b/rare/components/tabs/downloads/thread.py
@@ -2,62 +2,75 @@ import os
import platform
import queue
import time
+from ctypes import c_ulonglong
from dataclasses import dataclass
from enum import IntEnum
from logging import getLogger
from typing import List, Optional, Dict
from PyQt5.QtCore import QThread, pyqtSignal, QProcess
-from legendary.core import LegendaryCore
from rare.lgndr.cli import LegendaryCLI
+from rare.lgndr.core import LegendaryCore
from rare.lgndr.glue.monkeys import DLManagerSignals
from rare.lgndr.models.downloading import UIUpdate
+from rare.models.game import RareGame
from rare.models.install import InstallQueueItemModel
-from rare.shared import GlobalSignalsSingleton, ArgumentsSingleton
logger = getLogger("DownloadThread")
-class DownloadThread(QThread):
- @dataclass
- class ReturnStatus:
- class ReturnCode(IntEnum):
- ERROR = 1
- STOPPED = 2
- FINISHED = 3
+class DlResultCode(IntEnum):
+ ERROR = 1
+ STOPPED = 2
+ FINISHED = 3
- app_name: str
- ret_code: ReturnCode = ReturnCode.ERROR
- message: str = ""
- dlcs: Optional[List[Dict]] = None
- sync_saves: bool = False
- tip_url: str = ""
- shortcuts: bool = False
+@dataclass
+class DlResultModel:
+ item: InstallQueueItemModel
+ code: DlResultCode = DlResultCode.ERROR
+ message: str = ""
+ dlcs: Optional[List[Dict]] = None
+ sync_saves: bool = False
+ tip_url: str = ""
+ shortcuts: bool = False
- ret_status = pyqtSignal(ReturnStatus)
- ui_update = pyqtSignal(str, UIUpdate)
+class DlThread(QThread):
+ result = pyqtSignal(DlResultModel)
+ progress = pyqtSignal(UIUpdate, c_ulonglong)
- def __init__(self, core: LegendaryCore, item: InstallQueueItemModel):
- super(DownloadThread, self).__init__()
- self.signals = GlobalSignalsSingleton()
+ def __init__(self, item: InstallQueueItemModel, rgame: RareGame, core: LegendaryCore, debug: bool = False):
+ super(DlThread, self).__init__()
+ self.dlm_signals: DLManagerSignals = DLManagerSignals()
self.core: LegendaryCore = core
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):
cli = LegendaryCLI(self.core)
self.item.download.dlm.logging_queue = cli.logging_queue
- self.item.download.dlm.proc_debug = ArgumentsSingleton().debug
- ret = DownloadThread.ReturnStatus(self.item.download.game.app_name)
+ self.item.download.dlm.proc_debug = self.debug
+ result = DlResultModel(self.item)
start_t = time.time()
try:
self.item.download.dlm.start()
+ self.rgame.state = RareGame.State.DOWNLOADING
+ self.rgame.signals.progress.start.emit()
time.sleep(1)
while self.item.download.dlm.is_alive():
try:
- self.ui_update.emit(self.item.download.game.app_name,
- self.item.download.dlm.status_queue.get(timeout=1.0))
+ status = 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:
pass
if self.dlm_signals.update:
@@ -68,28 +81,29 @@ class DownloadThread(QThread):
time.sleep(self.item.download.dlm.update_interval / 10)
self.item.download.dlm.join()
except Exception as e:
+ self.kill()
+ self.item.download.dlm.join()
end_t = time.time()
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}.")
- ret.ret_code = ret.ReturnCode.ERROR
- ret.message = f"{e!r}"
- self.ret_status.emit(ret)
+ result.code = DlResultCode.ERROR
+ result.message = f"{e!r}"
+ self.__result_emit(result)
return
else:
end_t = time.time()
if self.dlm_signals.kill is True:
logger.info(f"Download stopped after {end_t - start_t:.02f} seconds.")
- ret.ret_code = ret.ReturnCode.STOPPED
- self.ret_status.emit(ret)
+ result.code = DlResultCode.STOPPED
+ self.__result_emit(result)
return
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:
- self.signals.application.overlay_installed.emit()
self.core.finish_overlay_install(self.item.download.igame)
- self.ret_status.emit(ret)
+ self.__result_emit(result)
return
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)
if dlcs and not self.item.options.skip_dlcs:
- ret.dlcs = []
+ result.dlcs = []
for dlc in dlcs:
- ret.dlcs.append(
+ result.dlcs.append(
{
"app_name": dlc.app_name,
"app_title": dlc.app_title,
@@ -119,11 +133,11 @@ class DownloadThread(QThread):
self.item.download.game.supports_cloud_saves
or self.item.download.game.supports_mac_cloud_saves
) 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
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(
self.item.download.game,
@@ -133,9 +147,9 @@ class DownloadThread(QThread):
)
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):
logger.info("This game lists the following prerequisites to be installed:")
diff --git a/rare/components/tabs/downloads/widgets.py b/rare/components/tabs/downloads/widgets.py
new file mode 100644
index 00000000..ed1f5e66
--- /dev/null
+++ b/rare/components/tabs/downloads/widgets.py
@@ -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)))
diff --git a/rare/components/tabs/games/__init__.py b/rare/components/tabs/games/__init__.py
index 9417cdcd..04be62d4 100644
--- a/rare/components/tabs/games/__init__.py
+++ b/rare/components/tabs/games/__init__.py
@@ -1,6 +1,6 @@
import time
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.QtWidgets import QStackedWidget, QVBoxLayout, QWidget, QScrollArea, QFrame
@@ -39,14 +39,10 @@ class GamesTab(QStackedWidget):
self.image_manager = ImageManagerSingleton()
self.settings = QSettings()
- self.widgets: Dict[str, Tuple[IconGameWidget, ListGameWidget]] = {}
- self.game_updates: Set[RareGame] = set()
self.active_filter: int = 0
self.game_list: List[Game] = self.api_results.game_list
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.game_utils = GameUtils(parent=self)
@@ -71,18 +67,6 @@ class GamesTab(QStackedWidget):
self.integrations_tabs.back_clicked.connect(lambda: self.setCurrentWidget(self.games))
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 = []
if not self.args.offline:
for game in self.no_assets:
@@ -108,9 +92,7 @@ class GamesTab(QStackedWidget):
self.list_view.setLayout(QVBoxLayout(self.list_view))
self.list_view.layout().setContentsMargins(3, 3, 9, 3)
self.list_view.layout().setAlignment(Qt.AlignTop)
- self.library_controller = LibraryWidgetController(
- self.icon_view, self.list_view, self
- )
+ self.library_controller = LibraryWidgetController(self.icon_view, self.list_view, self)
self.icon_view_scroll.setWidget(self.icon_view)
self.list_view_scroll.setWidget(self.list_view)
self.view_stack.addWidget(self.icon_view_scroll)
@@ -182,14 +164,15 @@ class GamesTab(QStackedWidget):
# FIXME: Remove this when RareCore is in place
def __create_game_with_dlcs(self, game: Game) -> RareGame:
- rgame = RareGame(game, self.core, self.image_manager)
- if rgame.has_update:
- self.game_updates.add(rgame)
+ rgame = RareGame(self.core, self.image_manager, game)
if game_dlcs := self.dlcs[rgame.game.catalog_item_id]:
for dlc in game_dlcs:
- rdlc = RareGame(dlc, self.core, self.image_manager)
- if rdlc.has_update:
- self.game_updates.add(rdlc)
+ rdlc = RareGame(self.core, self.image_manager, dlc)
+ self.rcore.add_game(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()
rgame.owned_dlcs.append(rdlc)
return rgame
@@ -210,12 +193,11 @@ class GamesTab(QStackedWidget):
def add_library_widget(self, rgame: RareGame):
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:
raise e
logger.error(f"{rgame.app_name} is broken. Don't add it to game list: {e}")
return None, None
- self.widgets[rgame.app_name] = (icon_widget, list_widget)
icon_widget.show_info.connect(self.show_game_info)
list_widget.show_info.connect(self.show_game_info)
return icon_widget, list_widget
diff --git a/rare/components/tabs/games/game_info/game_info.py b/rare/components/tabs/games/game_info/game_info.py
index f4bca665..5c5863a3 100644
--- a/rare/components/tabs/games/game_info/game_info.py
+++ b/rare/components/tabs/games/game_info/game_info.py
@@ -168,7 +168,7 @@ class GameInfo(QWidget):
def verify_game(self, rgame: RareGame):
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.result.connect(self.verify_result)
verify_worker.signals.error.connect(self.verify_error)
diff --git a/rare/components/tabs/games/game_widgets/__init__.py b/rare/components/tabs/games/game_widgets/__init__.py
index c09da179..726ff958 100644
--- a/rare/components/tabs/games/game_widgets/__init__.py
+++ b/rare/components/tabs/games/game_widgets/__init__.py
@@ -4,9 +4,10 @@ from PyQt5.QtCore import QObject, pyqtSlot
from PyQt5.QtWidgets import QWidget
from rare.lgndr.core import LegendaryCore
+from rare.models.apiresults import ApiResults
from rare.models.game import RareGame
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 .icon_game_widget import IconGameWidget
from .list_game_widget import ListGameWidget
@@ -17,33 +18,21 @@ class LibraryWidgetController(QObject):
super(LibraryWidgetController, self).__init__(parent=parent)
self._icon_container: QWidget = icon_container
self._list_container: QWidget = list_container
- self.core: LegendaryCore = LegendaryCoreSingleton()
- self.signals: GlobalSignals = GlobalSignalsSingleton()
- self.api_results = ApiResultsSingleton()
+ self.rcore = RareCore.instance()
+ self.core: LegendaryCore = self.rcore.core()
+ self.signals: GlobalSignals = self.rcore.signals()
+ self.api_results: ApiResults = self.rcore.api_results()
- self.signals.progress.started.connect(self.start_progress)
- self.signals.progress.value.connect(self.update_progress)
- self.signals.progress.finished.connect(self.finish_progress)
+ def add_game(self, rgame: RareGame, game_utils: GameUtils):
+ return self.add_widgets(rgame, game_utils)
- self.signals.game.installed.connect(self.on_install)
- self.signals.game.uninstalled.connect(self.on_uninstall)
- self.signals.game.verified.connect(self.on_verified)
-
- 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)
+ def add_widgets(self, rgame: RareGame, game_utils: GameUtils) -> Tuple[IconGameWidget, ListGameWidget]:
+ icon_widget = IconGameWidget(rgame, game_utils, self._icon_container)
+ list_widget = ListGameWidget(rgame, game_utils, self._list_container)
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
- 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":
visible = widget.rgame.is_installed
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(),))
else:
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,
x.widget().rgame.is_non_asset,
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(),))
else:
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)
)
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_list_app_names = game_app_names.difference(list_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)
self._icon_container.layout().addWidget(iw)
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)
self._list_container.layout().addWidget(lw)
self.sort_list()
- @pyqtSlot(list)
- def on_install(self, app_names: List[str]):
- for app_name in app_names:
- iw, lw = self.__find_widget(app_name)
- 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
+ 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
# lk: this should go in downloads and happen once
def __find_game_for_dlc(self, app_name: str) -> Optional[str]:
@@ -174,27 +148,3 @@ class LibraryWidgetController(QObject):
)
return game[0].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, "")
diff --git a/rare/components/tabs/games/game_widgets/game_widget.py b/rare/components/tabs/games/game_widgets/game_widget.py
index ba0646a7..76b686d5 100644
--- a/rare/components/tabs/games/game_widgets/game_widget.py
+++ b/rare/components/tabs/games/game_widgets/game_widget.py
@@ -108,7 +108,7 @@ class GameWidget(LibraryWidget):
self.addAction(uninstall)
uninstall.triggered.connect(
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
)
diff --git a/rare/components/tabs/games/game_widgets/icon_game_widget.py b/rare/components/tabs/games/game_widgets/icon_game_widget.py
index c9f14c19..b5e48908 100644
--- a/rare/components/tabs/games/game_widgets/icon_game_widget.py
+++ b/rare/components/tabs/games/game_widgets/icon_game_widget.py
@@ -1,6 +1,7 @@
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.shared.game_utils import GameUtils
@@ -12,9 +13,6 @@ logger = getLogger("IconGameWidget")
class IconGameWidget(GameWidget):
- is_ready = False
- update_game = pyqtSignal()
-
def __init__(self, rgame: RareGame, game_utils: GameUtils, parent=None):
super(IconGameWidget, self).__init__(rgame, game_utils, parent)
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.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.ui.launch_btn.setEnabled(self.rgame.can_launch)
@@ -41,13 +39,13 @@ class IconGameWidget(GameWidget):
def set_status(self):
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:
a0.accept()
self.ui.tooltip_label.setText(self.enterEventText)
self.ui.enterAnimation(self)
- def leaveEvent(self, a0: QEvent = None) -> None:
+ def leaveEvent(self, a0: Optional[QEvent] = None) -> None:
if a0 is not None:
a0.accept()
self.ui.leaveAnimation(self)
diff --git a/rare/components/tabs/games/game_widgets/icon_widget.py b/rare/components/tabs/games/game_widgets/icon_widget.py
index e184ece5..e0572e83 100644
--- a/rare/components/tabs/games/game_widgets/icon_widget.py
+++ b/rare/components/tabs/games/game_widgets/icon_widget.py
@@ -133,7 +133,7 @@ class IconWidget(object):
# layout for the image, holds the mini widget and a spacer item
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
mini_layout = QVBoxLayout()
diff --git a/rare/components/tabs/games/game_widgets/list_game_widget.py b/rare/components/tabs/games/game_widgets/list_game_widget.py
index 25bf6c42..71fd9acd 100644
--- a/rare/components/tabs/games/game_widgets/list_game_widget.py
+++ b/rare/components/tabs/games/game_widgets/list_game_widget.py
@@ -22,8 +22,6 @@ logger = getLogger("ListGameWidget")
class ListGameWidget(GameWidget):
- signal = pyqtSignal(str)
- update_game = pyqtSignal()
def __init__(self, rgame: RareGame, game_utils: GameUtils, parent=None):
super(ListGameWidget, self).__init__(rgame, game_utils, parent)
diff --git a/rare/components/tabs/games/integrations/eos_group.py b/rare/components/tabs/games/integrations/eos_group.py
index e9feb511..3c5f67b4 100644
--- a/rare/components/tabs/games/integrations/eos_group.py
+++ b/rare/components/tabs/games/integrations/eos_group.py
@@ -209,7 +209,9 @@ class EOSGroup(QGroupBox, Ui_EosWidget):
self.enabled_cb.setChecked(enabled)
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 not self.overlay:
self.overlay_stack.setCurrentIndex(1)
@@ -218,8 +220,9 @@ class EOSGroup(QGroupBox, Ui_EosWidget):
return
base_path = self.overlay.install_path
- options = InstallOptionsModel(app_name="", base_path=base_path,
- platform="Windows", overlay=True)
+ options = InstallOptionsModel(
+ app_name=eos.EOSOverlayApp.app_name, base_path=base_path, platform="Windows", overlay=True
+ )
self.signals.game.install.emit(options)
diff --git a/rare/components/tray_icon.py b/rare/components/tray_icon.py
index 6b6aa57b..381ad252 100644
--- a/rare/components/tray_icon.py
+++ b/rare/components/tray_icon.py
@@ -66,8 +66,7 @@ class TrayIcon(QSystemTrayIcon):
a = QAction(rgame.app_title)
a.setProperty("app_name", rgame.app_name)
a.triggered.connect(
- lambda: self.__parent.tab_widget.games_tab.game_utils.prepare_launch(
- self.sender().property("app_name"))
+ lambda: self.rcore.get_game(self.sender().property("app_name")).launch()
)
self.menu.insertAction(self.separator, a)
self.game_actions.append(a)
diff --git a/rare/models/game.py b/rare/models/game.py
index 6a6b4400..bc8df777 100644
--- a/rare/models/game.py
+++ b/rare/models/game.py
@@ -12,11 +12,10 @@ from legendary.models.game import Game, InstalledGame, SaveGameFile
from rare.lgndr.core import LegendaryCore
from rare.models.install import InstallOptionsModel
-from rare.shared.image_manager import ImageManager
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.paths import data_dir
logger = getLogger("RareGame")
@@ -32,25 +31,31 @@ class RareGame(QObject):
@dataclass
class Metadata:
+ auto_update: bool = False
queued: bool = False
queue_pos: Optional[int] = None
last_played: Optional[datetime] = None
+ grant_date: Optional[datetime] = None
tags: List[str] = field(default_factory=list)
@classmethod
def from_dict(cls, data: Dict):
return cls(
+ auto_update=data.get("auto_update", False),
queued=data.get("queued", False),
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", []),
)
def as_dict(self):
return dict(
+ auto_update=self.auto_update,
queued=self.queued,
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,
)
@@ -68,9 +73,10 @@ class RareGame(QObject):
class Game(QObject):
install = pyqtSignal(InstallOptionsModel)
- uninstalled = pyqtSignal()
- launched = pyqtSignal()
- finished = pyqtSignal()
+ installed = pyqtSignal(str)
+ uninstalled = pyqtSignal(str)
+ launched = pyqtSignal(str)
+ finished = pyqtSignal(str)
def __init__(self):
super(RareGame.Signals, self).__init__()
@@ -78,7 +84,7 @@ class RareGame(QObject):
self.widget = RareGame.Signals.Widget()
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__()
self.signals = RareGame.Signals()
@@ -108,29 +114,30 @@ class RareGame(QObject):
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.finished.connect(self.__game_finished)
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)
def __game_launched(self, code: int):
- self.state = RareGame.State.RUNNING
if code == GameProcess.Code.ON_STARTUP:
return
+ self.state = RareGame.State.RUNNING
self.metadata.last_played = datetime.now()
self.__save_metadata()
self.signals.game.launched.emit()
@pyqtSlot(int)
def __game_finished(self, exit_code: int):
- self.state = RareGame.State.IDLE
if exit_code == GameProcess.Code.ON_STARTUP:
return
+ self.state = RareGame.State.IDLE
self.signals.game.finished.emit()
- __metadata_json: Optional[Dict] = None
+ __metadata_json: Dict = None
@staticmethod
def __load_metadata_json() -> Dict:
@@ -270,9 +277,11 @@ class RareGame(QObject):
@return None
"""
if installed:
- self.igame = self.core.get_installed_game(self.app_name)
+ self.update_igame()
+ self.signals.game.installed.emit(self.app_name)
else:
self.igame = None
+ self.signals.game.uninstalled.emit(self.app_name)
self.set_pixmap()
@property
@@ -401,14 +410,33 @@ class RareGame(QObject):
@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
def can_launch(self) -> bool:
if self.is_installed:
- if self.is_non_asset:
+ if self.is_origin:
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 True
return False
@@ -442,23 +470,24 @@ class RareGame(QObject):
def repair(self, repair_and_update):
self.signals.game.install.emit(
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
)
)
def launch(
- self,
- offline: bool = False,
- skip_update_check: bool = False,
- wine_bin: str = None,
- wine_pfx: str = None,
- ask_sync_saves: bool = False,
+ self,
+ offline: bool = False,
+ skip_update_check: bool = False,
+ wine_bin: Optional[str] = None,
+ wine_pfx: Optional[str] = None,
+ ask_sync_saves: bool = False,
):
- executable = get_rare_executable()
- executable, args = executable[0], executable[1:]
- args.extend([
- "start", self.app_name
- ])
+ if not self.can_launch:
+ return
+
+ cmd_line = get_rare_executable()
+ executable, args = cmd_line[0], cmd_line[1:]
+ args.extend(["start", self.app_name])
if offline:
args.append("--offline")
if skip_update_check:
@@ -473,4 +502,52 @@ class RareGame(QObject):
# kill me, if I don't change it before commit
QProcess.startDetached(executable, 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
diff --git a/rare/models/install.py b/rare/models/install.py
index 6b5d6122..33afa730 100644
--- a/rare/models/install.py
+++ b/rare/models/install.py
@@ -58,8 +58,8 @@ class InstallDownloadModel:
@dataclass
class InstallQueueItemModel:
- download: Optional[InstallDownloadModel] = None
options: Optional[InstallOptionsModel] = None
+ download: Optional[InstallDownloadModel] = None
def __bool__(self):
return (self.download is not None) and (self.options is not None)
diff --git a/rare/models/signals.py b/rare/models/signals.py
index c9b6115f..e828756e 100644
--- a/rare/models/signals.py
+++ b/rare/models/signals.py
@@ -20,28 +20,18 @@ class GlobalSignals:
# none
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):
install = pyqtSignal(InstallOptionsModel)
- # list: app_name
- installed = pyqtSignal(list)
+ # str: app_name
+ installed = pyqtSignal(str)
# str: app_name
uninstalled = pyqtSignal(str)
- # str: app_name
- verified = pyqtSignal(str)
class DownloadSignals(QObject):
# str: app_name
- enqueue_game = pyqtSignal(str)
- # none
- update_tab = pyqtSignal()
+ enqueue = pyqtSignal(str)
+ # str: app_name
+ dequeue = pyqtSignal(str)
class DiscordRPCSignals(QObject):
# str: app_title
@@ -51,7 +41,6 @@ class GlobalSignals:
def __init__(self):
self.application = GlobalSignals.ApplicationSignals()
- self.progress = GlobalSignals.ProgressSignals()
self.game = GlobalSignals.GameSignals()
self.download = GlobalSignals.DownloadSignals()
self.discord_rpc = GlobalSignals.DiscordRPCSignals()
@@ -59,8 +48,6 @@ class GlobalSignals:
def deleteLater(self):
self.application.deleteLater()
del self.application
- self.progress.deleteLater()
- del self.progress
self.game.deleteLater()
del self.game
self.download.deleteLater()
diff --git a/rare/shared/game_process.py b/rare/shared/game_process.py
index 0533a7a7..537f5190 100644
--- a/rare/shared/game_process.py
+++ b/rare/shared/game_process.py
@@ -46,7 +46,7 @@ class GameProcess(QObject):
self.socket.readyRead.connect(self.__on_message)
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.timer.start(200)
diff --git a/rare/shared/game_utils.py b/rare/shared/game_utils.py
index 71f789e8..acdf974f 100644
--- a/rare/shared/game_utils.py
+++ b/rare/shared/game_utils.py
@@ -67,7 +67,6 @@ def uninstall_game(core: LegendaryCore, app_name: str, keep_files=False, keep_co
class GameUtils(QObject):
finished = pyqtSignal(str, str) # app_name, error
cloud_save_finished = pyqtSignal(str)
- game_launched = pyqtSignal(RareGame)
update_list = pyqtSignal(str)
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)
if not success:
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
def prepare_launch(
diff --git a/rare/shared/rare_core.py b/rare/shared/rare_core.py
index 60b9ae29..bce59aed 100644
--- a/rare/shared/rare_core.py
+++ b/rare/shared/rare_core.py
@@ -6,11 +6,12 @@ from logging import getLogger
from typing import Optional, Dict, Iterator, Callable
from PyQt5.QtCore import QObject
+from legendary.lfs.eos import EOSOverlayApp
from legendary.models.game import Game, SaveGameFile
from rare.lgndr.core import LegendaryCore
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 .image_manager import ImageManager
@@ -24,11 +25,11 @@ class RareCore(QObject):
if self._instance is not None:
raise RuntimeError("RareCore already initialized")
super(RareCore, self).__init__()
- self._args: Optional[Namespace] = None
- self._signals: Optional[GlobalSignals] = None
- self._core: Optional[LegendaryCore] = None
- self._image_manager: Optional[ImageManager] = None
- self._api_results: Optional[ApiResults] = None
+ self.__args: Optional[Namespace] = None
+ self.__signals: Optional[GlobalSignals] = None
+ self.__core: Optional[LegendaryCore] = None
+ self.__image_manager: Optional[ImageManager] = None
+ self.__api_results: Optional[ApiResults] = None
self.args(args)
self.signals(init=True)
@@ -37,6 +38,8 @@ class RareCore(QObject):
self.__games: Dict[str, RareGame] = {}
+ self.__eos_overlay_rgame = RareEosOverlay(self.__core, self.__image_manager, EOSOverlayApp)
+
RareCore._instance = self
@staticmethod
@@ -46,31 +49,31 @@ class RareCore(QObject):
return RareCore._instance
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")
- if self._signals is not None and init:
+ if self.__signals is not None and init:
raise RuntimeError("GlobalSignals already initialized")
if init:
- self._signals = GlobalSignals()
- return self._signals
+ self.__signals = GlobalSignals()
+ return self.__signals
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")
- 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")
if args is not None:
- self._args = args
- return self._args
+ self.__args = args
+ return self.__args
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")
- if self._core is not None and init:
+ if self.__core is not None and init:
raise RuntimeError("LegendaryCore already initialized")
if init:
try:
- self._core = LegendaryCore()
+ self.__core = LegendaryCore()
except configparser.MissingSectionHeaderError as e:
logger.warning(f"Config is corrupt: {e}")
if config_path := os.environ.get("XDG_CONFIG_HOME"):
@@ -79,70 +82,74 @@ class RareCore(QObject):
path = os.path.expanduser("~/.config/legendary")
with open(os.path.join(path, "config.ini"), "w") as config_file:
config_file.write("[Legendary]")
- self._core = LegendaryCore()
- if "Legendary" not in self._core.lgd.config.sections():
- self._core.lgd.config.add_section("Legendary")
- self._core.lgd.save_config()
+ self.__core = LegendaryCore()
+ if "Legendary" not in self.__core.lgd.config.sections():
+ self.__core.lgd.config.add_section("Legendary")
+ self.__core.lgd.save_config()
# workaround if egl sync enabled, but no programdata_path
# programdata_path might be unset if logging in through the browser
- if self._core.egl_sync_enabled:
- if self._core.egl.programdata_path is None:
- self._core.lgd.config.remove_option("Legendary", "egl_sync")
- self._core.lgd.save_config()
+ if self.__core.egl_sync_enabled:
+ if self.__core.egl.programdata_path is None:
+ self.__core.lgd.config.remove_option("Legendary", "egl_sync")
+ self.__core.lgd.save_config()
else:
- if not os.path.exists(self._core.egl.programdata_path):
- self._core.lgd.config.remove_option("Legendary", "egl_sync")
- self._core.lgd.save_config()
- return self._core
+ if not os.path.exists(self.__core.egl.programdata_path):
+ self.__core.lgd.config.remove_option("Legendary", "egl_sync")
+ self.__core.lgd.save_config()
+ return self.__core
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")
- if self._image_manager is not None and init:
+ if self.__image_manager is not None and init:
raise RuntimeError("ImageManager already initialized")
- if self._image_manager is None:
- self._image_manager = ImageManager(self.signals(), self.core())
- return self._image_manager
+ if self.__image_manager is None:
+ self.__image_manager = ImageManager(self.signals(), self.core())
+ return self.__image_manager
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")
- 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")
if res is not None:
- self._api_results = res
- return self._api_results
+ self.__api_results = res
+ return self.__api_results
def deleteLater(self) -> None:
- del self._api_results
- self._api_results = None
+ del self.__api_results
+ self.__api_results = None
- self._image_manager.deleteLater()
- del self._image_manager
- self._image_manager = None
+ self.__image_manager.deleteLater()
+ del self.__image_manager
+ self.__image_manager = None
- self._core.exit()
- del self._core
- self._core = None
+ self.__core.exit()
+ del self.__core
+ self.__core = None
- self._signals.deleteLater()
- del self._signals
- self._signals = None
+ self.__signals.deleteLater()
+ del self.__signals
+ self.__signals = None
- del self._args
- self._args = None
+ del self.__args
+ self.__args = None
RareCore._instance = None
super(RareCore, self).deleteLater()
def get_game(self, app_name: str) -> RareGame:
+ if app_name == EOSOverlayApp.app_name:
+ return self.__eos_overlay_rgame
return self.__games[app_name]
def add_game(self, rgame: RareGame) -> None:
- rgame.signals.game.install.connect(self._signals.game.install)
- rgame.signals.game.finished.connect(self._signals.application.update_tray)
- rgame.signals.game.finished.connect(lambda: self._signals.discord_rpc.set_title.emit(""))
+ rgame.signals.game.install.connect(self.__signals.game.install)
+ rgame.signals.game.installed.connect(self.__signals.game.installed)
+ rgame.signals.game.uninstalled.connect(self.__signals.game.uninstalled)
+ rgame.signals.game.finished.connect(self.__signals.application.update_tray)
+ rgame.signals.game.finished.connect(lambda: self.__signals.discord_rpc.set_title.emit(""))
self.__games[rgame.app_name] = rgame
def __filter_games(self, condition: Callable[[RareGame], bool]) -> Iterator[RareGame]:
diff --git a/rare/shared/workers/verify.py b/rare/shared/workers/verify.py
index 1a9bb1eb..d75551c7 100644
--- a/rare/shared/workers/verify.py
+++ b/rare/shared/workers/verify.py
@@ -23,7 +23,7 @@ class VerifyWorker(QRunnable):
num: int = 0
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__
super(VerifyWorker, self).__init__()
self.signals = VerifyWorker.Signals()
diff --git a/rare/ui/components/tabs/downloads/download_widget.py b/rare/ui/components/tabs/downloads/download_widget.py
index 1ef498a0..5b5b20d4 100644
--- a/rare/ui/components/tabs/downloads/download_widget.py
+++ b/rare/ui/components/tabs/downloads/download_widget.py
@@ -14,7 +14,7 @@ from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_DownloadWidget(object):
def setupUi(self, DownloadWidget):
DownloadWidget.setObjectName("DownloadWidget")
- DownloadWidget.resize(574, 70)
+ DownloadWidget.resize(332, 70)
DownloadWidget.setWindowTitle("DownloadWidget")
DownloadWidget.setFrameShape(QtWidgets.QFrame.StyledPanel)
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.setContentsMargins(0, 0, 0, 0)
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.setObjectName("remove_button")
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.update_buttons = QtWidgets.QWidget(DownloadWidget)
self.update_buttons.setObjectName("update_buttons")
@@ -79,8 +79,8 @@ class Ui_DownloadWidget(object):
def retranslateUi(self, DownloadWidget):
_translate = QtCore.QCoreApplication.translate
- self.remove_button.setText(_translate("DownloadWidget", "Remove from queue"))
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.settings_button.setText(_translate("DownloadWidget", "Update with settings"))
diff --git a/rare/ui/components/tabs/downloads/download_widget.ui b/rare/ui/components/tabs/downloads/download_widget.ui
index 59d01820..a5fcb3d8 100644
--- a/rare/ui/components/tabs/downloads/download_widget.ui
+++ b/rare/ui/components/tabs/downloads/download_widget.ui
@@ -6,7 +6,7 @@
0
0
- 574
+ 332
70
diff --git a/rare/utils/misc.py b/rare/utils/misc.py
index ce766d9e..29356dc4 100644
--- a/rare/utils/misc.py
+++ b/rare/utils/misc.py
@@ -17,7 +17,7 @@ from PyQt5.QtCore import (
QDir,
)
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.models.game import Game
from requests.exceptions import HTTPError
@@ -350,3 +350,7 @@ def icon(icn_str: str, fallback: str = None, **kwargs):
if kwargs.get("color"):
kwargs["color"] = "red"
return qtawesome.icon("ei.error", **kwargs)
+
+
+def widget_object_name(widget: QWidget, app_name: str) -> str:
+ return f"{type(widget).__name__}_{app_name}"
\ No newline at end of file