From 3c43da15945b908438ed7ad28951b4a6b1a36c4e Mon Sep 17 00:00:00 2001
From: loathingKernel <142770+loathingKernel@users.noreply.github.com>
Date: Sun, 10 Sep 2023 19:08:47 +0300
Subject: [PATCH] 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.
---
rare/components/tabs/settings/legendary.py | 13 +++
rare/shared/rare_core.py | 33 ++++---
rare/shared/workers/fetch.py | 98 +++++++++++--------
rare/ui/components/tabs/settings/legendary.py | 16 ++-
rare/ui/components/tabs/settings/legendary.ui | 26 ++++-
5 files changed, 127 insertions(+), 59 deletions(-)
diff --git a/rare/components/tabs/settings/legendary.py b/rare/components/tabs/settings/legendary.py
index c4254608..2b08a01b 100644
--- a/rare/components/tabs/settings/legendary.py
+++ b/rare/components/tabs/settings/legendary.py
@@ -116,6 +116,19 @@ class LegendarySettings(QWidget, Ui_LegendarySettings):
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)
# FIXME: Disable the button for now because it interferes with RareCore
self.refresh_metadata_button.setEnabled(False)
diff --git a/rare/shared/rare_core.py b/rare/shared/rare_core.py
index 31a80f06..3582304a 100644
--- a/rare/shared/rare_core.py
+++ b/rare/shared/rare_core.py
@@ -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 legendary.lfs.eos import EOSOverlayApp
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.models.base_game import RareSaveGame
from rare.models.game import RareGame, RareEosOverlay
from rare.models.signals import GlobalSignals
+from rare.utils.metrics import timelogger
from .image_manager import ImageManager
from .workers import (
QueueWorker,
@@ -50,7 +51,7 @@ class RareCore(QObject):
self.__core: Optional[LegendaryCore] = None
self.__image_manager: Optional[ImageManager] = None
- self.__start_time = time.time()
+ self.__start_time = time.perf_counter()
self.args(args)
self.signals(init=True)
@@ -310,12 +311,12 @@ class RareCore(QObject):
self.progress.emit(15, self.tr("Preparing library"))
self.__add_games_and_dlcs(*result)
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)
self.completed.emit()
def fetch(self):
- self.__start_time = time.time()
+ self.__start_time = time.perf_counter()
fetch_worker = FetchWorker(self.__core, self.__args)
fetch_worker.signals.progress.connect(self.progress)
fetch_worker.signals.result.connect(self.__on_fetch_result)
@@ -323,10 +324,10 @@ class RareCore(QObject):
def fetch_saves(self):
def __fetch() -> None:
- start_time = time.time()
saves_dict: Dict[str, List[SaveGameFile]] = {}
try:
- saves_list = self.__core.get_save_games()
+ with timelogger(logger, "Request saves"):
+ saves_list = self.__core.get_save_games()
for s in saves_list:
if s.app_name not in saves_dict.keys():
saves_dict[s.app_name] = [s]
@@ -337,26 +338,28 @@ class RareCore(QObject):
continue
self.__library[app_name].load_saves(saves)
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
- logger.debug(f"Saves: {len(saves_dict)}")
- logger.debug(f"Request saves: {time.time() - start_time} seconds")
+ logger.info(f"Saves: {len(saves_dict)}")
saves_worker = QRunnable.create(__fetch)
QThreadPool.globalInstance().start(saves_worker)
def fetch_entitlements(self) -> None:
def __fetch() -> None:
- start_time = time.time()
try:
- 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
for game in self.__library.values():
game.grant_date()
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
- logger.debug(f"Entitlements: {len(list(entitlements))}")
- logger.debug(f"Request Entitlements: {time.time() - start_time} seconds")
+ logger.info(f"Entitlements: {len(list(entitlements))}")
entitlements_worker = QRunnable.create(__fetch)
QThreadPool.globalInstance().start(entitlements_worker)
@@ -368,7 +371,7 @@ class RareCore(QObject):
def __post_init(self) -> None:
if not self.__args.offline:
self.fetch_saves()
- self.fetch_entitlements()
+ # self.fetch_entitlements()
self.resolve_origin()
@property
diff --git a/rare/shared/workers/fetch.py b/rare/shared/workers/fetch.py
index b18ea83f..a1173ad5 100644
--- a/rare/shared/workers/fetch.py
+++ b/rare/shared/workers/fetch.py
@@ -1,13 +1,13 @@
import platform
-import time
from argparse import Namespace
from enum import IntEnum
from logging import getLogger
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.utils.metrics import timelogger
from .worker import Worker
logger = getLogger("FetchWorker")
@@ -32,13 +32,11 @@ class FetchWorker(Worker):
def run_real(self):
# Fetch regular EGL games with assets
- start_time = time.time()
-
want_unreal = self.settings.value("unreal_meta", False, bool) or self.args.debug
want_win32 = self.settings.value("win32_meta", False, bool)
want_macos = self.settings.value("macos_meta", False, bool)
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:
logger.info(
@@ -47,9 +45,10 @@ class FetchWorker(Worker):
"with" if want_unreal else "without"
)
self.signals.progress.emit(00, self.signals.tr("Updating game metadata for Windows"))
- self.core.get_game_and_dlc_list(
- update_assets=not self.args.offline, platform="Win32", skip_ue=not want_unreal
- )
+ with timelogger(logger, "Request Win32 games"):
+ self.core.get_game_and_dlc_list(
+ update_assets=not self.args.offline, platform="Win32", skip_ue=not want_unreal
+ )
if need_macos or want_macos or self.args.debug:
logger.info(
@@ -58,9 +57,10 @@ class FetchWorker(Worker):
"with" if want_unreal else "without"
)
self.signals.progress.emit(15, self.signals.tr("Updating game metadata for macOS"))
- self.core.get_game_and_dlc_list(
- update_assets=not self.args.offline, platform="Mac", skip_ue=not want_unreal
- )
+ with timelogger(logger, "Request macOS games"):
+ self.core.get_game_and_dlc_list(
+ update_assets=not self.args.offline, platform="Mac", skip_ue=not want_unreal
+ )
if need_windows:
self.signals.progress.emit(00, self.signals.tr("Updating game metadata for Windows"))
@@ -68,39 +68,53 @@ class FetchWorker(Worker):
"Requesting Windows metadata, %s Unreal engine",
"with" if want_unreal else "without"
)
- games, dlc_dict = self.core.get_game_and_dlc_list(
- update_assets=need_windows, platform="Windows", skip_ue=not want_unreal
- )
- logger.debug(f"Games {len(games)}, games with DLCs {len(dlc_dict)}")
- logger.debug(f"Request games: {time.time() - start_time} seconds")
+ with timelogger(logger, "Request Windows games"):
+ games, dlc_dict = self.core.get_game_and_dlc_list(
+ update_assets=need_windows, platform="Windows", skip_ue=not want_unreal
+ )
+ logger.info(f"Games: %s. Games with DLCs: %s", len(games), len(dlc_dict))
+
+ 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
- self.signals.progress.emit(30, self.signals.tr("Updating non-asset game metadata"))
- start_time = time.time()
- try:
- na_games, na_dlc_dict = self.core.get_non_asset_library_items(force_refresh=False, skip_ue=False)
- except (HTTPError, ConnectionError) as e:
- logger.warning(f"Exception while fetching non asset games from EGS: {e}")
- na_games, na_dlc_dict = ([], {})
- # FIXME:
- # This is here because of broken appIds from Epic:
- # https://discord.com/channels/826881530310819914/884510635642216499/1111321692703305729
- # There is a tab character in the appId of Fallout New Vegas: Honest Hearts DLC, this breaks metadata storage
- # 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.
- except Exception as e:
- logger.error(f"Exception while fetching non asset games from EGS: {e}")
- na_games, na_dlc_dict = ([], {})
- logger.debug(f"Non-asset {len(na_games)}, games with non-asset DLCs {len(na_dlc_dict)}")
- logger.debug(f"Request non-asset: {time.time() - start_time} seconds")
+ if want_non_asset:
+ self.signals.progress.emit(30, self.signals.tr("Updating non-asset game metadata"))
+ 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)
+ except (HTTPError, ConnectionError) as e:
+ logger.error("Network exception while fetching non asset games from EGS.")
+ logger.error(e)
+ na_games, na_dlc_dict = ([], {})
+ # FIXME:
+ # This is here because of broken appIds from Epic:
+ # https://discord.com/channels/826881530310819914/884510635642216499/1111321692703305729
+ # There is a tab character in the appId of Fallout New Vegas: Honest Hearts DLC, this breaks metadata storage
+ # 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.
+ except Exception as e:
+ logger.error("General exception while fetching non asset games from EGS.")
+ logger.error(e)
+ na_games, na_dlc_dict = ([], {})
+ logger.info("Non-asset: %s. Non-asset with DLCs: %s", len(na_games), len(na_dlc_dict))
- # Combine the two games lists and the two dlc dictionaries between regular and non-asset results
- games += na_games
- for catalog_id, dlcs in na_dlc_dict.items():
- if catalog_id in dlc_dict.keys():
- dlc_dict[catalog_id] += dlcs
- else:
- dlc_dict[catalog_id] = dlcs
- logger.debug(f"Games {len(games)}, games with DLCs {len(dlc_dict)}")
+ # Combine the two games lists and the two dlc dictionaries between regular and non-asset results
+ games += na_games
+ for catalog_id, dlcs in na_dlc_dict.items():
+ if catalog_id in dlc_dict.keys():
+ dlc_dict[catalog_id] += dlcs
+ else:
+ dlc_dict[catalog_id] = dlcs
+ 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)
diff --git a/rare/ui/components/tabs/settings/legendary.py b/rare/ui/components/tabs/settings/legendary.py
index 829a60ab..a8306397 100644
--- a/rare/ui/components/tabs/settings/legendary.py
+++ b/rare/ui/components/tabs/settings/legendary.py
@@ -2,7 +2,7 @@
# 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
# 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.setObjectName("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)
font = QtGui.QFont()
font.setItalic(True)
@@ -175,6 +181,14 @@ class Ui_LegendarySettings(object):
self.fetch_win32_check.setText(_translate("LegendarySettings", "Include Win32 games"))
self.fetch_macos_check.setText(_translate("LegendarySettings", "Include macOS games"))
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.refresh_metadata_button.setText(_translate("LegendarySettings", "Refresh metadata"))
diff --git a/rare/ui/components/tabs/settings/legendary.ui b/rare/ui/components/tabs/settings/legendary.ui
index 65e69f1c..9f1a9c4c 100644
--- a/rare/ui/components/tabs/settings/legendary.ui
+++ b/rare/ui/components/tabs/settings/legendary.ui
@@ -231,7 +231,7 @@
Platforms
-
+
-
@@ -253,6 +253,30 @@
+ -
+
+
+ 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.
+
+
+ Exclude non-asset games
+
+
+
+ -
+
+
+ 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.
+
+
+ Exclude entitlements
+
+
+
-