Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion misc/requirements.in
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ setuptools-scm
requests < 3.0
PySide6-Essentials >= 6.8.1
QtAwesome
legendary-gl @ https://github.com/RareDevs/legendary/archive/refs/tags/rare-1.12.0.zip
legendary-gl @ https://github.com/RareDevs/legendary/archive/85d9ea9.zip
orjson
vdf @ https://github.com/solsticegamestudios/vdf/archive/be1f7220238022f8b29fe747f0b643f280bfdb6e.zip
pywin32 ; platform_system == "Windows"
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ dependencies = [
"requests < 3.0",
"PySide6-Essentials >= 6.8.1",
"QtAwesome",
"legendary-gl @ https://github.com/RareDevs/legendary/archive/refs/tags/rare-1.12.0.zip",
"legendary-gl @ git+https://github.com/RareDevs/legendary@rare/achievements",
"orjson",
"vdf @ https://github.com/solsticegamestudios/vdf/archive/be1f7220238022f8b29fe747f0b643f280bfdb6e.zip",
"pywin32 ; platform_system == 'Windows'",
Expand Down
44 changes: 22 additions & 22 deletions rare/components/tabs/integrations/ubisoft_group.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import time
import webbrowser
from logging import getLogger
from typing import Optional

from legendary.models.game import Game
from PySide6.QtCore import QObject, QSize, Qt, QThreadPool, Signal, Slot
Expand All @@ -24,22 +23,21 @@
from rare.widgets.elide_label import ElideLabel
from rare.widgets.loading_widget import LoadingWidget

logger = getLogger("Ubisoft")

class UbiGetInfoWorkerSignals(QObject):
worker_finished = Signal(set, set, str)

class UbiGetInfoWorker(Worker):
class Signals(QObject):
worker_finished = Signal(set, set, str)

class UbiGetInfoWorker(Worker):
def __init__(self, core: LegendaryCore):
super(UbiGetInfoWorker, self).__init__()
self.signals = UbiGetInfoWorker.Signals()
self.signals = UbiGetInfoWorkerSignals()
self.setAutoDelete(True)
self.core = core

def run_real(self) -> None:
try:
with timelogger(logger, "Request external auths"):
with timelogger(self.logger, "Request external auths"):
external_auths = self.core.egs.get_external_auths()
for ext_auth in external_auths:
if ext_auth["type"] != "ubisoft":
Expand All @@ -50,34 +48,34 @@ def run_real(self) -> None:
self.signals.worker_finished.emit(set(), set(), "")
return

with timelogger(logger, "Request uplay codes"):
with timelogger(self.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"]}

if (entitlements := self.core.lgd.entitlements) is None:
with timelogger(logger, "Request entitlements"):
with timelogger(self.logger, "Request entitlements"):
try:
entitlements = self.core.egs.get_user_entitlements_full()
except AttributeError as e:
logger.warning(e)
entitlements = self.core.egs.get_user_entitlements()
except Exception as e:
self.logger.warning(e)
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(e)
self.logger.error(e)
self.signals.worker_finished.emit(set(), set(), "error")


class UbiConnectWorker(Worker):
class Signals(QObject):
linked = Signal(str)
class UbiConnectWorkerSignals(QObject):
linked = Signal(str)


class UbiConnectWorker(Worker):
def __init__(self, core: LegendaryCore, ubi_account_id, partner_link_id):
super(UbiConnectWorker, self).__init__()
self.signals = UbiConnectWorker.Signals()
self.signals = UbiConnectWorkerSignals()
self.core = core
self.ubi_account_id = ubi_account_id
self.partner_link_id = partner_link_id
Expand Down Expand Up @@ -160,14 +158,16 @@ def worker_finished(self, error):
class UbisoftGroup(QGroupBox):
def __init__(self, rcore: RareCore, parent=None):
super(UbisoftGroup, self).__init__(parent=parent)
self.logger = getLogger(type(self).__name__)

self.rcore = rcore
self.core = rcore.core()
self.args = rcore.args()

self.setTitle(self.tr("Link Ubisoft Games"))

self.thread_pool = QThreadPool.globalInstance()
self.worker: Optional[UbiGetInfoWorker] = None
self.worker: UbiGetInfoWorker = None

self.info_label = QLabel(parent=self)
self.info_label.setText(self.tr("Getting information about your redeemable Ubisoft games."))
Expand All @@ -192,7 +192,7 @@ def showEvent(self, a0: QShowEvent) -> None:
return super().showEvent(a0)

if self.worker is not None:
return
return super().showEvent(a0)

for widget in self.findChildren(UbiLinkWidget, options=Qt.FindChildOption.FindDirectChildrenOnly):
widget.deleteLater()
Expand All @@ -201,14 +201,14 @@ def showEvent(self, a0: QShowEvent) -> None:
self.worker = UbiGetInfoWorker(self.core)
self.worker.signals.worker_finished.connect(self.show_ubi_games)
self.thread_pool.start(self.worker)
super().showEvent(a0)
return super().showEvent(a0)

@Slot(set, set, str)
def show_ubi_games(self, redeemed: set, entitlements: set, ubi_account_id: str):
self.worker = None
self.loading_widget.stop()
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.logger.error("No linked ubisoft account found! Link your accounts via your browser and try again.")
self.info_label.setText(self.tr("Your account is not linked with Ubisoft. Please link your account and try again."))
self.link_button.setEnabled(True)
elif ubi_account_id == "error":
Expand Down Expand Up @@ -254,7 +254,7 @@ def show_ubi_games(self, redeemed: set, entitlements: set, ubi_account_id: str):
self.info_label.setText(
self.tr("You have <b>{}</b> games available to redeem.").format(len(uplay_games) - activated)
)
logger.info(f"Found {len(uplay_games) - activated} game(s) to redeem.")
self.logger.info(f"Found {len(uplay_games) - activated} game(s) to redeem.")

for game in uplay_games:
widget = UbiLinkWidget(
Expand Down
91 changes: 87 additions & 4 deletions rare/components/tabs/library/details/details.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import platform
from hashlib import sha1
from logging import getLogger
from typing import Optional, Tuple
from typing import Dict, Optional, Tuple

from PySide6.QtCore import (
QCoreApplication,
Expand All @@ -14,6 +14,9 @@
from PySide6.QtGui import QFontMetrics, QHideEvent, QShowEvent
from PySide6.QtWidgets import (
QCheckBox,
QFrame,
QHBoxLayout,
QLabel,
QLineEdit,
QMessageBox,
QSizePolicy,
Expand All @@ -28,9 +31,11 @@
from rare.shared import RareCore
from rare.shared.workers import MoveInfoWorker, MoveWorker, VerifyWorker
from rare.ui.components.tabs.library.details.details import Ui_GameDetails
from rare.utils.misc import format_size, qta_icon, style_hyperlink
from rare.utils.misc import format_size, qta_icon, relative_date, style_hyperlink
from rare.utils.paths import cache_dir
from rare.utils.qt_requests import QtRequests
from rare.widgets.dialogs import ButtonDialog, game_title
from rare.widgets.image_widget import ImageSize, ImageWidget
from rare.widgets.image_widget import ImageSize, ImageWidget, LoadingImageWidget
from rare.widgets.side_tab import SideTabContents

logger = getLogger("GameInfo")
Expand All @@ -42,6 +47,7 @@ class GameDetails(QWidget, SideTabContents):

def __init__(self, rcore: RareCore, parent=None):
super(GameDetails, self).__init__(parent=parent)
self.implements_scrollarea = True
self.ui = Ui_GameDetails()
self.ui.setupUi(self)
# lk: set object names for CSS properties
Expand All @@ -67,12 +73,14 @@ def __init__(self, rcore: RareCore, parent=None):
self.rcore = rcore
self.core = rcore.core()
self.args = rcore.args()
self.net_manager = QtRequests(cache=str(cache_dir().joinpath("achievements")), parent=self)

self.rgame: Optional[RareGame] = None

self.image = ImageWidget(self)
self.image.setFixedSize(ImageSize.DisplayTall)
self.ui.left_layout.insertWidget(0, self.image, alignment=Qt.AlignmentFlag.AlignTop)
self.ui.left_layout.setAlignment(Qt.AlignmentFlag.AlignTop)

self.ui.install_button.clicked.connect(self.__on_install)
self.ui.import_button.clicked.connect(self.__on_import)
Expand All @@ -96,8 +104,21 @@ def __init__(self, rcore: RareCore, parent=None):
self.ui.add_tag_button.setIcon(qta_icon("mdi.plus"))
self.ui.add_tag_button.clicked.connect(self.__on_tag_add)

ach_progress_layout = QVBoxLayout(self.ui.ach_progress_page)
ach_progress_layout.setContentsMargins(0, 0, 0, 0)
ach_progress_layout.setAlignment(Qt.AlignmentFlag.AlignTop)
ach_completed_layout = QVBoxLayout(self.ui.ach_completed_page)
ach_completed_layout.setContentsMargins(0, 0, 0, 0)
ach_completed_layout.setAlignment(Qt.AlignmentFlag.AlignTop)
ach_uninitiated_layout = QVBoxLayout(self.ui.ach_uninitiated_page)
ach_uninitiated_layout.setContentsMargins(0, 0, 0, 0)
ach_uninitiated_layout.setAlignment(Qt.AlignmentFlag.AlignTop)
ach_hidden_layout = QVBoxLayout(self.ui.ach_hidden_page)
ach_hidden_layout.setContentsMargins(0, 0, 0, 0)
ach_hidden_layout.setAlignment(Qt.AlignmentFlag.AlignTop)

# lk: hide unfinished things
self.ui.description_label.setVisible(False)
self.ui.description_field.setVisible(False)
self.ui.requirements_group.setVisible(False)

@Slot()
Expand Down Expand Up @@ -417,9 +438,71 @@ def update_game(self, rgame: RareGame):
else:
self.ui.install_button.setText(self.tr("Install"))

self.ui.description_field.setText(rgame.game.metadata['description'])

for page in (
self.ui.ach_progress_page, self.ui.ach_completed_page, self.ui.ach_uninitiated_page, self.ui.ach_hidden_page,
):
for w in page.findChildren(AchievementWidget, options=Qt.FindChildOption.FindDirectChildrenOnly):
page.layout().removeWidget(w)
w.deleteLater()

if ach := rgame.achievements:
self.ui.progress_field.setText(f"{ach.user_unlocked}/<b>{ach.total_achievements}</b>")
self.ui.exp_field.setText(f"{ach.user_xp}/<b>{ach.total_product_xp}</b>")

for group, page in zip(
(ach.hidden, ach.uninitiated, ach.completed, ach.in_progress, ),
(self.ui.ach_hidden_page, self.ui.ach_uninitiated_page, self.ui.ach_completed_page, self.ui.ach_progress_page, )
):
self.ui.achievements_toolbox.setItemEnabled(self.ui.achievements_toolbox.indexOf(page), bool(group))
if bool(group):
self.ui.achievements_toolbox.setCurrentWidget(page)
for item in group:
page.layout().addWidget(AchievementWidget(self.net_manager, item), alignment=Qt.AlignmentFlag.AlignTop)
else:
self.ui.progress_field.setText(self.tr("No data"))
self.ui.exp_field.setText(self.tr("No data"))
self.ui.achievements_group.setVisible(bool(ach))

self.rgame = rgame


class AchievementWidget(QFrame):
def __init__(self, manager: QtRequests, achievement: Dict, parent=None):
super().__init__(parent=parent)
self.setFrameShape(QFrame.Shape.StyledPanel)
self.setFrameShadow(QFrame.Shadow.Sunken)

image = LoadingImageWidget(manager, parent=self)
image.setFixedSize(ImageSize.LibraryIcon)
image.fetchPixmap(achievement['icon_link'])

title = QLabel(
f"<b><font color={achievement['tier']['hexColor']}>{achievement['display_name']}</font></b>"
f" ({achievement['xp']} XP)",
parent=self
)
title.setWordWrap(True)
description = QLabel(achievement['description'], parent=self)
description.setWordWrap(True)
unlock_date = achievement['unlock_date'].astimezone() if achievement['unlock_date'] else None
unlock_date_str = f" ( On: {relative_date(unlock_date)} )" if unlock_date else ""
progress = QLabel(f"Progress: <b>{achievement['progress'] * 100:,.2f}%</b> {unlock_date_str}", parent=self)
if unlock_date:
progress.setToolTip(str(unlock_date))

right_layout = QVBoxLayout()
right_layout.addWidget(title, alignment=Qt.AlignmentFlag.AlignTop)
right_layout.addWidget(description, alignment=Qt.AlignmentFlag.AlignTop, stretch=1)
right_layout.addWidget(progress, alignment=Qt.AlignmentFlag.AlignBottom)

main_layout = QHBoxLayout(self)
main_layout.setContentsMargins(3, 3, 3, 3)
main_layout.addWidget(image)
main_layout.addLayout(right_layout, stretch=1)


class GameTagCheckBox(QCheckBox):
checkStateChangedData = Signal(Qt.CheckState, str)

Expand Down
5 changes: 2 additions & 3 deletions rare/components/tabs/store/widgets/details.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,9 @@
from rare.ui.components.tabs.store.details import Ui_StoreDetailsWidget
from rare.utils.misc import qta_icon
from rare.widgets.elide_label import ElideLabel
from rare.widgets.image_widget import LoadingSpinnerImageWidget
from rare.widgets.side_tab import SideTabContents, SideTabWidget

from .image import LoadingImageWidget

logger = getLogger("StoreDetails")


Expand All @@ -45,7 +44,7 @@ def __init__(self, installed: List, store_api: StoreAPI, parent=None):
self.installed = installed
self.catalog_offer: CatalogOfferModel = None

self.image = LoadingImageWidget(store_api.cached_manager, self)
self.image = LoadingSpinnerImageWidget(store_api.cached_manager, self)
self.image.setFixedSize(ImageSize.DisplayTall)
self.ui.left_layout.insertWidget(0, self.image, alignment=Qt.AlignmentFlag.AlignTop)
self.ui.left_layout.setAlignment(Qt.AlignmentFlag.AlignTop)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,4 @@
from PySide6.QtCore import Qt
from PySide6.QtGui import (
QImage,
QPixmap,
)
from PySide6.QtWidgets import (
QHBoxLayout,
QLabel,
Expand All @@ -12,10 +8,6 @@
QWidget,
)

from rare.utils.qt_requests import QtRequests
from rare.widgets.image_widget import ImageWidget
from rare.widgets.loading_widget import LoadingWidget


class IconWidget(object):
def __init__(self):
Expand Down Expand Up @@ -81,36 +73,3 @@ def setupUi(self, widget: QWidget):
image_layout.addSpacerItem(QSpacerItem(0, 0, QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding))
image_layout.addWidget(self.mini_widget)
widget.setLayout(image_layout)


class LoadingImageWidget(ImageWidget):
def __init__(self, manager: QtRequests, parent=None):
super(LoadingImageWidget, self).__init__(parent=parent)
self.ui = IconWidget()
self.spinner = LoadingWidget(parent=self)
self.spinner.setVisible(False)
self.manager = manager

def fetchPixmap(self, url):
self.setPixmap(QPixmap())
self.spinner.setFixedSize(self._image_size.size)
self.spinner.start()
self.manager.get(
url,
self.__on_image_ready,
params={
"resize": 1,
"w": self._image_size.base.size.width(),
"h": self._image_size.base.size.height(),
},
)

def __on_image_ready(self, data):
cover = QImage()
cover.loadFromData(data)
# cover = cover.scaled(self._image_size.size, Qt.KeepAspectRatio, Qt.SmoothTransformation)
cover.setDevicePixelRatio(self._image_size.base.pixel_ratio)
cover = cover.convertToFormat(QImage.Format.Format_ARGB32_Premultiplied)
cover = QPixmap(cover)
self.setPixmap(cover)
self.spinner.stop()
Loading