RareCore: Add options to exclude non-asset games and entitlements from startup
* Add options in settings to not request non-asset games and entitlements. This greatly improves startup time but reduces functionality a bit. Both options are disabled by default. * Remove the old options to exclude Win32 and Mac games as they were no longer relevant. * Add a performance context manager to log execution time when used. * Fixed an issue with UbisoftGroup where multiple threads would spawn.
This commit is contained in:
parent
a246f4ee16
commit
3c43da1594
5 changed files with 127 additions and 59 deletions
|
@ -116,6 +116,19 @@ class LegendarySettings(QWidget, Ui_LegendarySettings):
|
||||||
lambda: self.settings.setValue("unreal_meta", self.fetch_unreal_check.isChecked())
|
lambda: self.settings.setValue("unreal_meta", self.fetch_unreal_check.isChecked())
|
||||||
)
|
)
|
||||||
|
|
||||||
|
self.exclude_non_asset_check.setChecked(
|
||||||
|
self.settings.value("exclude_non_asset", False, bool)
|
||||||
|
)
|
||||||
|
self.exclude_non_asset_check.stateChanged.connect(
|
||||||
|
lambda: self.settings.setValue("exclude_non_asset", self.exclude_non_asset_check.isChecked())
|
||||||
|
)
|
||||||
|
self.exclude_entitlements_check.setChecked(
|
||||||
|
self.settings.value("exclude_entitlements", False, bool)
|
||||||
|
)
|
||||||
|
self.exclude_entitlements_check.stateChanged.connect(
|
||||||
|
lambda: self.settings.setValue("exclude_entitlements", self.exclude_entitlements_check.isChecked())
|
||||||
|
)
|
||||||
|
|
||||||
self.refresh_metadata_button.clicked.connect(self.refresh_metadata)
|
self.refresh_metadata_button.clicked.connect(self.refresh_metadata)
|
||||||
# FIXME: Disable the button for now because it interferes with RareCore
|
# FIXME: Disable the button for now because it interferes with RareCore
|
||||||
self.refresh_metadata_button.setEnabled(False)
|
self.refresh_metadata_button.setEnabled(False)
|
||||||
|
|
|
@ -9,12 +9,13 @@ from typing import Dict, Iterator, Callable, Optional, List, Union, Iterable, Tu
|
||||||
from PyQt5.QtCore import QObject, pyqtSignal, QSettings, pyqtSlot, QThreadPool, QRunnable, QTimer
|
from PyQt5.QtCore import QObject, pyqtSignal, QSettings, pyqtSlot, QThreadPool, QRunnable, QTimer
|
||||||
from legendary.lfs.eos import EOSOverlayApp
|
from legendary.lfs.eos import EOSOverlayApp
|
||||||
from legendary.models.game import Game, SaveGameFile
|
from legendary.models.game import Game, SaveGameFile
|
||||||
from requests import HTTPError
|
from requests.exceptions import HTTPError, ConnectionError
|
||||||
|
|
||||||
from rare.lgndr.core import LegendaryCore
|
from rare.lgndr.core import LegendaryCore
|
||||||
from rare.models.base_game import RareSaveGame
|
from rare.models.base_game import RareSaveGame
|
||||||
from rare.models.game import RareGame, RareEosOverlay
|
from rare.models.game import RareGame, RareEosOverlay
|
||||||
from rare.models.signals import GlobalSignals
|
from rare.models.signals import GlobalSignals
|
||||||
|
from rare.utils.metrics import timelogger
|
||||||
from .image_manager import ImageManager
|
from .image_manager import ImageManager
|
||||||
from .workers import (
|
from .workers import (
|
||||||
QueueWorker,
|
QueueWorker,
|
||||||
|
@ -50,7 +51,7 @@ class RareCore(QObject):
|
||||||
self.__core: Optional[LegendaryCore] = None
|
self.__core: Optional[LegendaryCore] = None
|
||||||
self.__image_manager: Optional[ImageManager] = None
|
self.__image_manager: Optional[ImageManager] = None
|
||||||
|
|
||||||
self.__start_time = time.time()
|
self.__start_time = time.perf_counter()
|
||||||
|
|
||||||
self.args(args)
|
self.args(args)
|
||||||
self.signals(init=True)
|
self.signals(init=True)
|
||||||
|
@ -310,12 +311,12 @@ class RareCore(QObject):
|
||||||
self.progress.emit(15, self.tr("Preparing library"))
|
self.progress.emit(15, self.tr("Preparing library"))
|
||||||
self.__add_games_and_dlcs(*result)
|
self.__add_games_and_dlcs(*result)
|
||||||
self.progress.emit(100, self.tr("Launching Rare"))
|
self.progress.emit(100, self.tr("Launching Rare"))
|
||||||
logger.debug(f"Fetch time {time.time() - self.__start_time} seconds")
|
logger.debug(f"Fetch time {time.perf_counter() - self.__start_time} seconds")
|
||||||
QTimer.singleShot(100, self.__post_init)
|
QTimer.singleShot(100, self.__post_init)
|
||||||
self.completed.emit()
|
self.completed.emit()
|
||||||
|
|
||||||
def fetch(self):
|
def fetch(self):
|
||||||
self.__start_time = time.time()
|
self.__start_time = time.perf_counter()
|
||||||
fetch_worker = FetchWorker(self.__core, self.__args)
|
fetch_worker = FetchWorker(self.__core, self.__args)
|
||||||
fetch_worker.signals.progress.connect(self.progress)
|
fetch_worker.signals.progress.connect(self.progress)
|
||||||
fetch_worker.signals.result.connect(self.__on_fetch_result)
|
fetch_worker.signals.result.connect(self.__on_fetch_result)
|
||||||
|
@ -323,9 +324,9 @@ class RareCore(QObject):
|
||||||
|
|
||||||
def fetch_saves(self):
|
def fetch_saves(self):
|
||||||
def __fetch() -> None:
|
def __fetch() -> None:
|
||||||
start_time = time.time()
|
|
||||||
saves_dict: Dict[str, List[SaveGameFile]] = {}
|
saves_dict: Dict[str, List[SaveGameFile]] = {}
|
||||||
try:
|
try:
|
||||||
|
with timelogger(logger, "Request saves"):
|
||||||
saves_list = self.__core.get_save_games()
|
saves_list = self.__core.get_save_games()
|
||||||
for s in saves_list:
|
for s in saves_list:
|
||||||
if s.app_name not in saves_dict.keys():
|
if s.app_name not in saves_dict.keys():
|
||||||
|
@ -337,26 +338,28 @@ class RareCore(QObject):
|
||||||
continue
|
continue
|
||||||
self.__library[app_name].load_saves(saves)
|
self.__library[app_name].load_saves(saves)
|
||||||
except (HTTPError, ConnectionError) as e:
|
except (HTTPError, ConnectionError) as e:
|
||||||
logger.error(f"Exception while fetching saves from EGS: {e}")
|
logger.error(f"Exception while fetching saves from EGS.")
|
||||||
|
logger.error(e)
|
||||||
return
|
return
|
||||||
logger.debug(f"Saves: {len(saves_dict)}")
|
logger.info(f"Saves: {len(saves_dict)}")
|
||||||
logger.debug(f"Request saves: {time.time() - start_time} seconds")
|
|
||||||
|
|
||||||
saves_worker = QRunnable.create(__fetch)
|
saves_worker = QRunnable.create(__fetch)
|
||||||
QThreadPool.globalInstance().start(saves_worker)
|
QThreadPool.globalInstance().start(saves_worker)
|
||||||
|
|
||||||
def fetch_entitlements(self) -> None:
|
def fetch_entitlements(self) -> None:
|
||||||
def __fetch() -> None:
|
def __fetch() -> None:
|
||||||
start_time = time.time()
|
|
||||||
try:
|
try:
|
||||||
|
if (entitlements := self.__core.lgd.entitlements) is None:
|
||||||
|
with timelogger(logger, "Request entitlements"):
|
||||||
entitlements = self.__core.egs.get_user_entitlements()
|
entitlements = self.__core.egs.get_user_entitlements()
|
||||||
|
self.__core.lgd.entitlements = entitlements
|
||||||
for game in self.__library.values():
|
for game in self.__library.values():
|
||||||
game.grant_date()
|
game.grant_date()
|
||||||
except (HTTPError, ConnectionError) as e:
|
except (HTTPError, ConnectionError) as e:
|
||||||
logger.error(f"Failed to retrieve user entitlements from EGS: {e}")
|
logger.error(f"Exception while fetching entitlements from EGS.")
|
||||||
|
logger.error(e)
|
||||||
return
|
return
|
||||||
logger.debug(f"Entitlements: {len(list(entitlements))}")
|
logger.info(f"Entitlements: {len(list(entitlements))}")
|
||||||
logger.debug(f"Request Entitlements: {time.time() - start_time} seconds")
|
|
||||||
|
|
||||||
entitlements_worker = QRunnable.create(__fetch)
|
entitlements_worker = QRunnable.create(__fetch)
|
||||||
QThreadPool.globalInstance().start(entitlements_worker)
|
QThreadPool.globalInstance().start(entitlements_worker)
|
||||||
|
@ -368,7 +371,7 @@ class RareCore(QObject):
|
||||||
def __post_init(self) -> None:
|
def __post_init(self) -> None:
|
||||||
if not self.__args.offline:
|
if not self.__args.offline:
|
||||||
self.fetch_saves()
|
self.fetch_saves()
|
||||||
self.fetch_entitlements()
|
# self.fetch_entitlements()
|
||||||
self.resolve_origin()
|
self.resolve_origin()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
import platform
|
import platform
|
||||||
import time
|
|
||||||
from argparse import Namespace
|
from argparse import Namespace
|
||||||
from enum import IntEnum
|
from enum import IntEnum
|
||||||
from logging import getLogger
|
from logging import getLogger
|
||||||
|
|
||||||
from PyQt5.QtCore import QObject, pyqtSignal, QSettings
|
from PyQt5.QtCore import QObject, pyqtSignal, QSettings
|
||||||
from requests.exceptions import ConnectionError, HTTPError
|
from requests.exceptions import HTTPError, ConnectionError
|
||||||
|
|
||||||
from rare.lgndr.core import LegendaryCore
|
from rare.lgndr.core import LegendaryCore
|
||||||
|
from rare.utils.metrics import timelogger
|
||||||
from .worker import Worker
|
from .worker import Worker
|
||||||
|
|
||||||
logger = getLogger("FetchWorker")
|
logger = getLogger("FetchWorker")
|
||||||
|
@ -32,13 +32,11 @@ class FetchWorker(Worker):
|
||||||
|
|
||||||
def run_real(self):
|
def run_real(self):
|
||||||
# Fetch regular EGL games with assets
|
# Fetch regular EGL games with assets
|
||||||
start_time = time.time()
|
|
||||||
|
|
||||||
want_unreal = self.settings.value("unreal_meta", False, bool) or self.args.debug
|
want_unreal = self.settings.value("unreal_meta", False, bool) or self.args.debug
|
||||||
want_win32 = self.settings.value("win32_meta", False, bool)
|
want_win32 = self.settings.value("win32_meta", False, bool)
|
||||||
want_macos = self.settings.value("macos_meta", False, bool)
|
want_macos = self.settings.value("macos_meta", False, bool)
|
||||||
need_macos = platform.system() == "Darwin"
|
need_macos = platform.system() == "Darwin"
|
||||||
need_windows = not any([want_win32, want_macos, need_macos, self.args.debug])
|
need_windows = not any([want_win32, want_macos, need_macos, self.args.debug]) and not self.args.offline
|
||||||
|
|
||||||
if want_win32 or self.args.debug:
|
if want_win32 or self.args.debug:
|
||||||
logger.info(
|
logger.info(
|
||||||
|
@ -47,6 +45,7 @@ class FetchWorker(Worker):
|
||||||
"with" if want_unreal else "without"
|
"with" if want_unreal else "without"
|
||||||
)
|
)
|
||||||
self.signals.progress.emit(00, self.signals.tr("Updating game metadata for Windows"))
|
self.signals.progress.emit(00, self.signals.tr("Updating game metadata for Windows"))
|
||||||
|
with timelogger(logger, "Request Win32 games"):
|
||||||
self.core.get_game_and_dlc_list(
|
self.core.get_game_and_dlc_list(
|
||||||
update_assets=not self.args.offline, platform="Win32", skip_ue=not want_unreal
|
update_assets=not self.args.offline, platform="Win32", skip_ue=not want_unreal
|
||||||
)
|
)
|
||||||
|
@ -58,6 +57,7 @@ class FetchWorker(Worker):
|
||||||
"with" if want_unreal else "without"
|
"with" if want_unreal else "without"
|
||||||
)
|
)
|
||||||
self.signals.progress.emit(15, self.signals.tr("Updating game metadata for macOS"))
|
self.signals.progress.emit(15, self.signals.tr("Updating game metadata for macOS"))
|
||||||
|
with timelogger(logger, "Request macOS games"):
|
||||||
self.core.get_game_and_dlc_list(
|
self.core.get_game_and_dlc_list(
|
||||||
update_assets=not self.args.offline, platform="Mac", skip_ue=not want_unreal
|
update_assets=not self.args.offline, platform="Mac", skip_ue=not want_unreal
|
||||||
)
|
)
|
||||||
|
@ -68,19 +68,24 @@ class FetchWorker(Worker):
|
||||||
"Requesting Windows metadata, %s Unreal engine",
|
"Requesting Windows metadata, %s Unreal engine",
|
||||||
"with" if want_unreal else "without"
|
"with" if want_unreal else "without"
|
||||||
)
|
)
|
||||||
|
with timelogger(logger, "Request Windows games"):
|
||||||
games, dlc_dict = self.core.get_game_and_dlc_list(
|
games, dlc_dict = self.core.get_game_and_dlc_list(
|
||||||
update_assets=need_windows, platform="Windows", skip_ue=not want_unreal
|
update_assets=need_windows, platform="Windows", skip_ue=not want_unreal
|
||||||
)
|
)
|
||||||
logger.debug(f"Games {len(games)}, games with DLCs {len(dlc_dict)}")
|
logger.info(f"Games: %s. Games with DLCs: %s", len(games), len(dlc_dict))
|
||||||
logger.debug(f"Request games: {time.time() - start_time} seconds")
|
|
||||||
|
want_non_asset = not self.settings.value("exclude_non_asset", False, bool)
|
||||||
|
want_entitlements = not self.settings.value("exclude_entitlements", False, bool)
|
||||||
|
|
||||||
# Fetch non-asset games
|
# Fetch non-asset games
|
||||||
|
if want_non_asset:
|
||||||
self.signals.progress.emit(30, self.signals.tr("Updating non-asset game metadata"))
|
self.signals.progress.emit(30, self.signals.tr("Updating non-asset game metadata"))
|
||||||
start_time = time.time()
|
|
||||||
try:
|
try:
|
||||||
|
with timelogger(logger, "Request non-asset"):
|
||||||
na_games, na_dlc_dict = self.core.get_non_asset_library_items(force_refresh=False, skip_ue=False)
|
na_games, na_dlc_dict = self.core.get_non_asset_library_items(force_refresh=False, skip_ue=False)
|
||||||
except (HTTPError, ConnectionError) as e:
|
except (HTTPError, ConnectionError) as e:
|
||||||
logger.warning(f"Exception while fetching non asset games from EGS: {e}")
|
logger.error("Network exception while fetching non asset games from EGS.")
|
||||||
|
logger.error(e)
|
||||||
na_games, na_dlc_dict = ([], {})
|
na_games, na_dlc_dict = ([], {})
|
||||||
# FIXME:
|
# FIXME:
|
||||||
# This is here because of broken appIds from Epic:
|
# This is here because of broken appIds from Epic:
|
||||||
|
@ -89,10 +94,10 @@ class FetchWorker(Worker):
|
||||||
# on Windows as they can't handle tabs at the end of the filename (?)
|
# on Windows as they can't handle tabs at the end of the filename (?)
|
||||||
# Legendary and Heroic are also affected, but it completely breaks Rare, so dodge it for now pending a fix.
|
# Legendary and Heroic are also affected, but it completely breaks Rare, so dodge it for now pending a fix.
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Exception while fetching non asset games from EGS: {e}")
|
logger.error("General exception while fetching non asset games from EGS.")
|
||||||
|
logger.error(e)
|
||||||
na_games, na_dlc_dict = ([], {})
|
na_games, na_dlc_dict = ([], {})
|
||||||
logger.debug(f"Non-asset {len(na_games)}, games with non-asset DLCs {len(na_dlc_dict)}")
|
logger.info("Non-asset: %s. Non-asset with DLCs: %s", len(na_games), len(na_dlc_dict))
|
||||||
logger.debug(f"Request non-asset: {time.time() - start_time} seconds")
|
|
||||||
|
|
||||||
# Combine the two games lists and the two dlc dictionaries between regular and non-asset results
|
# Combine the two games lists and the two dlc dictionaries between regular and non-asset results
|
||||||
games += na_games
|
games += na_games
|
||||||
|
@ -101,6 +106,15 @@ class FetchWorker(Worker):
|
||||||
dlc_dict[catalog_id] += dlcs
|
dlc_dict[catalog_id] += dlcs
|
||||||
else:
|
else:
|
||||||
dlc_dict[catalog_id] = dlcs
|
dlc_dict[catalog_id] = dlcs
|
||||||
logger.debug(f"Games {len(games)}, games with DLCs {len(dlc_dict)}")
|
logger.info(f"Games: {len(games)}. Games with DLCs: {len(dlc_dict)}")
|
||||||
|
|
||||||
|
if want_entitlements:
|
||||||
|
# Get entitlements, Ubisoft integration also uses them
|
||||||
|
self.signals.progress.emit(40, self.signals.tr("Updating entitlements"))
|
||||||
|
with timelogger(logger, "Request entitlements"):
|
||||||
|
entitlements = self.core.egs.get_user_entitlements()
|
||||||
|
self.core.lgd.entitlements = entitlements
|
||||||
|
logger.info(f"Entitlements: {len(list(entitlements))}")
|
||||||
|
|
||||||
|
self.signals.progress.emit(50, self.signals.tr("Preparing games"))
|
||||||
self.signals.result.emit((games, dlc_dict), FetchWorker.Result.COMBINED)
|
self.signals.result.emit((games, dlc_dict), FetchWorker.Result.COMBINED)
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
# Form implementation generated from reading ui file 'rare/ui/components/tabs/settings/legendary.ui'
|
# Form implementation generated from reading ui file 'rare/ui/components/tabs/settings/legendary.ui'
|
||||||
#
|
#
|
||||||
# Created by: PyQt5 UI code generator 5.15.10
|
# Created by: PyQt5 UI code generator 5.15.7
|
||||||
#
|
#
|
||||||
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
|
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
|
||||||
# run again. Do not edit this file unless you know what you are doing.
|
# run again. Do not edit this file unless you know what you are doing.
|
||||||
|
@ -139,6 +139,12 @@ class Ui_LegendarySettings(object):
|
||||||
self.fetch_unreal_check = QtWidgets.QCheckBox(self.metadata_group)
|
self.fetch_unreal_check = QtWidgets.QCheckBox(self.metadata_group)
|
||||||
self.fetch_unreal_check.setObjectName("fetch_unreal_check")
|
self.fetch_unreal_check.setObjectName("fetch_unreal_check")
|
||||||
self.verticalLayout_2.addWidget(self.fetch_unreal_check)
|
self.verticalLayout_2.addWidget(self.fetch_unreal_check)
|
||||||
|
self.exclude_non_asset_check = QtWidgets.QCheckBox(self.meta_group)
|
||||||
|
self.exclude_non_asset_check.setObjectName("exclude_non_asset_check")
|
||||||
|
self.verticalLayout_2.addWidget(self.exclude_non_asset_check)
|
||||||
|
self.exclude_entitlements_check = QtWidgets.QCheckBox(self.meta_group)
|
||||||
|
self.exclude_entitlements_check.setObjectName("exclude_entitlements_check")
|
||||||
|
self.verticalLayout_2.addWidget(self.exclude_entitlements_check)
|
||||||
self.metadata_info = QtWidgets.QLabel(self.metadata_group)
|
self.metadata_info = QtWidgets.QLabel(self.metadata_group)
|
||||||
font = QtGui.QFont()
|
font = QtGui.QFont()
|
||||||
font.setItalic(True)
|
font.setItalic(True)
|
||||||
|
@ -175,6 +181,14 @@ class Ui_LegendarySettings(object):
|
||||||
self.fetch_win32_check.setText(_translate("LegendarySettings", "Include Win32 games"))
|
self.fetch_win32_check.setText(_translate("LegendarySettings", "Include Win32 games"))
|
||||||
self.fetch_macos_check.setText(_translate("LegendarySettings", "Include macOS games"))
|
self.fetch_macos_check.setText(_translate("LegendarySettings", "Include macOS games"))
|
||||||
self.fetch_unreal_check.setText(_translate("LegendarySettings", "Include Unreal engine"))
|
self.fetch_unreal_check.setText(_translate("LegendarySettings", "Include Unreal engine"))
|
||||||
|
self.exclude_non_asset_check.setToolTip(_translate("LegendarySettings", "Do not load metadata for non-asset games (i.e. Origin games) on start-up.\n"
|
||||||
|
"\n"
|
||||||
|
"Disabling this greatly improves start-up time, but some games might not be visible in your library."))
|
||||||
|
self.exclude_non_asset_check.setText(_translate("LegendarySettings", "Exclude non-asset games"))
|
||||||
|
self.exclude_entitlements_check.setToolTip(_translate("LegendarySettings", "Do not load entitlement data (i.e game\'s date of purchase) on start-up.\n"
|
||||||
|
"\n"
|
||||||
|
"Disabling this greatly improves start-up time, but some library filters may not work."))
|
||||||
|
self.exclude_entitlements_check.setText(_translate("LegendarySettings", "Exclude entitlements"))
|
||||||
self.metadata_info.setText(_translate("LegendarySettings", "Restart Rare to apply"))
|
self.metadata_info.setText(_translate("LegendarySettings", "Restart Rare to apply"))
|
||||||
self.refresh_metadata_button.setText(_translate("LegendarySettings", "Refresh metadata"))
|
self.refresh_metadata_button.setText(_translate("LegendarySettings", "Refresh metadata"))
|
||||||
|
|
||||||
|
|
|
@ -231,7 +231,7 @@
|
||||||
<property name="title">
|
<property name="title">
|
||||||
<string>Platforms</string>
|
<string>Platforms</string>
|
||||||
</property>
|
</property>
|
||||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
<layout class="QVBoxLayout" name="metadata_layout">
|
||||||
<item>
|
<item>
|
||||||
<widget class="QCheckBox" name="fetch_win32_check">
|
<widget class="QCheckBox" name="fetch_win32_check">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
|
@ -253,6 +253,30 @@
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QCheckBox" name="exclude_non_asset_check">
|
||||||
|
<property name="toolTip">
|
||||||
|
<string>Do not load metadata for non-asset games (i.e. Origin games) on start-up.
|
||||||
|
|
||||||
|
Disabling this greatly improves start-up time, but some games might not be visible in your library.</string>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Exclude non-asset games</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QCheckBox" name="exclude_entitlements_check">
|
||||||
|
<property name="toolTip">
|
||||||
|
<string>Do not load entitlement data (i.e game's date of purchase) on start-up.
|
||||||
|
|
||||||
|
Disabling this greatly improves start-up time, but some library filters may not work.</string>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Exclude entitlements</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<widget class="QLabel" name="metadata_info">
|
<widget class="QLabel" name="metadata_info">
|
||||||
<property name="font">
|
<property name="font">
|
||||||
|
|
Loading…
Reference in a new issue