diff --git a/rare/components/tabs/games/integrations/__init__.py b/rare/components/tabs/games/integrations/__init__.py index 6c95dc66..0a3bf0fb 100644 --- a/rare/components/tabs/games/integrations/__init__.py +++ b/rare/components/tabs/games/integrations/__init__.py @@ -36,8 +36,8 @@ class IntegrationsTabs(SideTabWidget): ) self.ubisoft_group = UbisoftGroup(self.eos_ubisoft) self.eos_group = EOSGroup(self.eos_ubisoft) - self.eos_ubisoft.addWidget(self.ubisoft_group) self.eos_ubisoft.addWidget(self.eos_group) + self.eos_ubisoft.addWidget(self.ubisoft_group) self.eos_ubisoft_index = self.addTab(self.eos_ubisoft, self.tr("Epic Overlay and Ubisoft")) self.setCurrentIndex(self.import_index) diff --git a/rare/components/tabs/games/integrations/ubisoft_group.py b/rare/components/tabs/games/integrations/ubisoft_group.py index 4e027a7a..eddd303d 100644 --- a/rare/components/tabs/games/integrations/ubisoft_group.py +++ b/rare/components/tabs/games/integrations/ubisoft_group.py @@ -1,14 +1,28 @@ import time import webbrowser from logging import getLogger +from typing import Optional -from PyQt5.QtCore import QObject, pyqtSignal, QThreadPool, QSize -from PyQt5.QtWidgets import QWidget, QLabel, QHBoxLayout, QSizePolicy, QPushButton, QGroupBox, QVBoxLayout +from PyQt5.QtCore import QObject, pyqtSignal, QThreadPool, QSize, pyqtSlot, Qt +from PyQt5.QtGui import QShowEvent +from PyQt5.QtWidgets import ( + QFrame, + QLabel, + QHBoxLayout, + QSizePolicy, + QPushButton, + QGroupBox, + QVBoxLayout, +) from legendary.models.game import Game -from rare.shared import LegendaryCoreSingleton, ArgumentsSingleton +from rare.lgndr.core import LegendaryCore +from rare.shared import RareCore from rare.shared.workers.worker import Worker +from rare.utils.metrics import timelogger from rare.utils.misc import icon +from rare.widgets.elide_label import ElideLabel +from rare.widgets.loading_widget import LoadingWidget logger = getLogger("Ubisoft") @@ -17,15 +31,16 @@ class UbiGetInfoWorker(Worker): class Signals(QObject): worker_finished = pyqtSignal(set, set, str) - def __init__(self): + def __init__(self, core: LegendaryCore): super(UbiGetInfoWorker, self).__init__() self.signals = UbiGetInfoWorker.Signals() self.setAutoDelete(True) - self.core = LegendaryCoreSingleton() + self.core = core def run_real(self) -> None: try: - external_auths = self.core.egs.get_external_auths() + with timelogger(logger, "Request external auths"): + external_auths = self.core.egs.get_external_auths() for ext_auth in external_auths: if ext_auth["type"] != "ubisoft": continue @@ -35,12 +50,17 @@ class UbiGetInfoWorker(Worker): self.signals.worker_finished.emit(set(), set(), "") return - uplay_keys = self.core.egs.store_get_uplay_codes() + with timelogger(logger, "Request uplay codes"): + uplay_keys = self.core.egs.store_get_uplay_codes() key_list = uplay_keys["data"]["PartnerIntegration"]["accountUplayCodes"] redeemed = {k["gameId"] for k in key_list if k["redeemedOnUplay"]} - entitlements = self.core.egs.get_user_entitlements() + if (entitlements := self.core.lgd.entitlements) is None: + with timelogger(logger, "Request entitlements"): + entitlements = self.core.egs.get_user_entitlements() + self.core.lgd.entitlements = entitlements entitlements = {i["entitlementName"] for i in entitlements} + self.signals.worker_finished.emit(redeemed, entitlements, ubi_account_id) except Exception as e: logger.error(str(e)) @@ -51,11 +71,11 @@ class UbiConnectWorker(Worker): class Signals(QObject): linked = pyqtSignal(str) - def __init__(self, ubi_account_id, partner_link_id): + def __init__(self, core: LegendaryCore, ubi_account_id, partner_link_id): super(UbiConnectWorker, self).__init__() self.signals = UbiConnectWorker.Signals() self.setAutoDelete(True) - self.core = LegendaryCoreSingleton() + self.core = core self.ubi_account_id = ubi_account_id self.partner_link_id = partner_link_id @@ -65,9 +85,7 @@ class UbiConnectWorker(Worker): self.signals.linked.emit("") return try: - self.core.egs.store_claim_uplay_code( - self.ubi_account_id, self.partner_link_id - ) + self.core.egs.store_claim_uplay_code(self.ubi_account_id, self.partner_link_id) self.core.egs.store_redeem_uplay_codes(self.ubi_account_id) except Exception as e: self.signals.linked.emit(str(e)) @@ -76,55 +94,58 @@ class UbiConnectWorker(Worker): self.signals.linked.emit("") -class UbiLinkWidget(QWidget): - def __init__(self, game: Game, ubi_account_id): - super(UbiLinkWidget, self).__init__() - self.args = ArgumentsSingleton() - layout = QHBoxLayout() - layout.setContentsMargins(0, 0, 0, 0) +class UbiLinkWidget(QFrame): + def __init__(self, game: Game, ubi_account_id, activated: bool = False, parent=None): + super(UbiLinkWidget, self).__init__(parent=parent) + self.setFrameShape(QFrame.StyledPanel) + self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) + + self.args = RareCore.instance().args() self.game = game self.ubi_account_id = ubi_account_id - self.title_label = QLabel(game.app_title) - layout.addWidget(self.title_label, stretch=1) - - self.ok_indicator = QLabel() - self.ok_indicator.setPixmap(icon("fa.info-circle", color="grey").pixmap(20, 20)) + self.ok_indicator = QLabel(parent=self) + self.ok_indicator.setPixmap(icon("fa.circle-o", color="grey").pixmap(20, 20)) self.ok_indicator.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Preferred) - layout.addWidget(self.ok_indicator) - self.link_button = QPushButton( - self.tr("Redeem to Ubisoft") + ": Test" if self.args.debug else "" - ) - layout.addWidget(self.link_button) + self.title_label = ElideLabel(game.app_title, parent=self) + + self.link_button = QPushButton(self.tr("Redeem in Ubisoft"), parent=self) + self.link_button.setMinimumWidth(150) self.link_button.clicked.connect(self.activate) - self.setLayout(layout) + + if activated: + self.link_button.setText(self.tr("Already activated")) + self.link_button.setDisabled(True) + self.ok_indicator.setPixmap(icon("fa.check-circle-o", color="green").pixmap(QSize(20, 20))) + + layout = QHBoxLayout(self) + layout.setContentsMargins(-1, 0, 0, 0) + layout.addWidget(self.ok_indicator) + layout.addWidget(self.title_label, stretch=1) + layout.addWidget(self.link_button) def activate(self): self.link_button.setDisabled(True) # self.ok_indicator.setPixmap(icon("mdi.loading", color="grey").pixmap(20, 20)) - self.ok_indicator.setPixmap( - icon("mdi.transit-connection-horizontal", color="grey").pixmap(20, 20) - ) + self.ok_indicator.setPixmap(icon("mdi.transit-connection-horizontal", color="grey").pixmap(20, 20)) if self.args.debug: - worker = UbiConnectWorker(None, None) + worker = UbiConnectWorker(RareCore.instance().core(), None, None) else: - worker = UbiConnectWorker(self.ubi_account_id, self.game.partner_link_id) + worker = UbiConnectWorker( + RareCore.instance().core(), self.ubi_account_id, self.game.partner_link_id + ) worker.signals.linked.connect(self.worker_finished) QThreadPool.globalInstance().start(worker) def worker_finished(self, error): if not error: - self.ok_indicator.setPixmap( - icon("ei.ok-circle", color="green").pixmap(QSize(20, 20)) - ) + self.ok_indicator.setPixmap(icon("fa.check-circle-o", color="green").pixmap(QSize(20, 20))) self.link_button.setDisabled(True) self.link_button.setText(self.tr("Already activated")) else: - self.ok_indicator.setPixmap( - icon("fa.info-circle", color="red").pixmap(QSize(20, 20)) - ) + self.ok_indicator.setPixmap(icon("fa.times-circle-o", color="red").pixmap(QSize(20, 20))) self.ok_indicator.setToolTip(error) self.link_button.setText(self.tr("Try again")) self.link_button.setDisabled(False) @@ -134,86 +155,120 @@ class UbisoftGroup(QGroupBox): def __init__(self, parent=None): super(UbisoftGroup, self).__init__(parent=parent) self.setTitle(self.tr("Link Ubisoft Games")) - self.setLayout(QVBoxLayout()) - self.core = LegendaryCoreSingleton() - self.args = ArgumentsSingleton() + self.rcore = RareCore.instance() + self.core = RareCore.instance().core() + self.args = RareCore.instance().args() self.thread_pool = QThreadPool.globalInstance() - worker = UbiGetInfoWorker() - worker.signals.worker_finished.connect(self.show_ubi_games) - self.thread_pool.start(worker) + self.worker: Optional[UbiGetInfoWorker] = None + self.info_label = QLabel(parent=self) + self.info_label.setText(self.tr("Getting information about your redeemable Ubisoft games.")) + self.browser_button = QPushButton(self.tr("Link Ubisoft acccount"), parent=self) + self.browser_button.setMinimumWidth(140) + self.browser_button.clicked.connect( + lambda: webbrowser.open("https://www.epicgames.com/id/link/ubisoft") + ) + self.browser_button.setEnabled(False) + + self.loading_widget = LoadingWidget(self) + self.loading_widget.stop() + + header_layout = QHBoxLayout() + header_layout.addWidget(self.info_label, stretch=1) + header_layout.addWidget(self.browser_button) + + layout = QVBoxLayout(self) + layout.addLayout(header_layout) + layout.addWidget(self.loading_widget) + + def showEvent(self, a0: QShowEvent) -> None: + if a0.spontaneous(): + return super().showEvent(a0) + + if self.worker is not None: + return + + for widget in self.findChildren(UbiLinkWidget, options=Qt.FindDirectChildrenOnly): + widget.deleteLater() + self.loading_widget.start() + + self.worker = UbiGetInfoWorker(self.core) + self.worker.signals.worker_finished.connect(self.show_ubi_games) + self.thread_pool.start(self.worker) + super().showEvent(a0) + + @pyqtSlot(set, set, str) def show_ubi_games(self, redeemed: set, entitlements: set, ubi_account_id: str): + self.worker = None if not redeemed and ubi_account_id != "error": logger.error( "No linked ubisoft account found! Link your accounts via your browser and try again." ) - self.layout().addWidget( - QLabel( - self.tr( - "Your account is not linked with Ubisoft. Please link your account first" - ) - ) + self.info_label.setText( + self.tr("Your account is not linked with Ubisoft. Please link your account and try again.") ) - open_browser_button = QPushButton(self.tr("Open link page")) - open_browser_button.clicked.connect( - lambda: webbrowser.open("https://www.epicgames.com/id/link/ubisoft") - ) - self.layout().addWidget(open_browser_button) - return + self.browser_button.setEnabled(True) elif ubi_account_id == "error": - self.layout().addWidget(QLabel(self.tr("An error occurred"))) - return + self.info_label.setText( + self.tr("An error has occurred while requesting your account's Ubisoft information.") + ) + self.browser_button.setEnabled(True) + else: + self.browser_button.setEnabled(False) - games = self.core.get_game_list(False) - uplay_games = [] - activated = 0 - for game in games: - for dlc_data in game.metadata.get("dlcItemList", []): - if dlc_data["entitlementName"] not in entitlements: + uplay_games = [] + activated = 0 + for rgame in self.rcore.ubisoft_games: + game = rgame.game + for dlc_data in game.metadata.get("dlcItemList", []): + if dlc_data["entitlementName"] not in entitlements: + continue + try: + app_name = dlc_data["releaseInfo"][0]["appId"] + except (IndexError, KeyError): + app_name = "unknown" + + dlc_game = Game(app_name=app_name, app_title=dlc_data["title"], metadata=dlc_data) + if dlc_game.partner_link_type != "ubisoft": + continue + if dlc_game.partner_link_id in redeemed: + continue + uplay_games.append(dlc_game) + + if game.partner_link_type != "ubisoft": continue + if game.partner_link_id in redeemed: + activated += 1 + uplay_games.append(game) - try: - app_name = dlc_data["releaseInfo"][0]["appId"] - except (IndexError, KeyError): - app_name = "unknown" - - dlc_game = Game( - app_name=app_name, app_title=dlc_data["title"], metadata=dlc_data - ) - if dlc_game.partner_link_type != "ubisoft": - continue - if dlc_game.partner_link_id in redeemed: - continue - uplay_games.append(dlc_game) - - if game.partner_link_type != "ubisoft": - continue - if game.partner_link_id in redeemed: - activated += 1 - continue - uplay_games.append(game) - - if not uplay_games: - if activated >= 1: - self.layout().addWidget( - QLabel( - self.tr("All your Ubisoft games have already been activated") - ) - ) + if not uplay_games: + self.info_label.setText(self.tr("You don't own any Ubisoft games.")) else: - self.layout().addWidget( - QLabel(self.tr("You don't own any Ubisoft games")) - ) - if self.args.debug: + if activated == len(uplay_games): + self.info_label.setText(self.tr("All your Ubisoft games have already been activated.")) + else: + self.info_label.setText( + self.tr("You have {} games available to redeem.").format( + len(uplay_games) - activated + ) + ) + logger.info(f"Found {len(uplay_games) - activated} game(s) to redeem.") + + self.loading_widget.stop() + + for game in uplay_games: widget = UbiLinkWidget( - Game(app_name="Test", app_title="This is a test game"), - ubi_account_id, + game, ubi_account_id, activated=game.partner_link_id in redeemed, parent=self ) self.layout().addWidget(widget) - return - logger.info(f"Found {len(uplay_games)} game(s) to redeem") - for game in uplay_games: - widget = UbiLinkWidget(game, ubi_account_id) - self.layout().addWidget(widget) + if self.args.debug: + widget = UbiLinkWidget( + Game(app_name="RareTestGame", app_title="Super Fake Super Rare Super Test (Super?) Game"), + ubi_account_id, + activated=False, + parent=self, + ) + self.layout().addWidget(widget) + self.browser_button.setEnabled(True) diff --git a/rare/shared/rare_core.py b/rare/shared/rare_core.py index a5149e22..0ce8994c 100644 --- a/rare/shared/rare_core.py +++ b/rare/shared/rare_core.py @@ -372,6 +372,10 @@ class RareCore(QObject): def origin_games(self) -> Iterator[RareGame]: return self.__filter_games(lambda game: game.is_origin and not game.is_dlc) + @property + def ubisoft_games(self) -> Iterator[RareGame]: + return self.__filter_games(lambda game: game.is_ubisoft and not game.is_dlc) + @property def game_list(self) -> Iterator[Game]: for game in self.games: diff --git a/rare/utils/metrics.py b/rare/utils/metrics.py new file mode 100644 index 00000000..bcc969d7 --- /dev/null +++ b/rare/utils/metrics.py @@ -0,0 +1,10 @@ +import time +from contextlib import contextmanager +from logging import Logger + + +@contextmanager +def timelogger(logger: Logger, title: str): + start = time.perf_counter() + yield + logger.debug("%s: %s seconds", title, time.perf_counter() - start) diff --git a/rare/widgets/loading_widget.py b/rare/widgets/loading_widget.py index 24d5e542..fc3d6fc5 100644 --- a/rare/widgets/loading_widget.py +++ b/rare/widgets/loading_widget.py @@ -32,7 +32,10 @@ class LoadingWidget(QLabel): return super().event(e) def showEvent(self, a0: QShowEvent) -> None: + if a0.spontaneous(): + return super().showEvent(a0) self.__center_on_parent() + super().showEvent(a0) def eventFilter(self, a0: QObject, a1: QEvent) -> bool: if a0 is self.parent() and a1.type() == QEvent.Resize: