diff --git a/rare/app.py b/rare/app.py index bba47458..efd10a64 100644 --- a/rare/app.py +++ b/rare/app.py @@ -1,4 +1,3 @@ -import configparser import logging import os import platform @@ -23,10 +22,8 @@ from rare.shared import ( LegendaryCoreSingleton, GlobalSignalsSingleton, ArgumentsSingleton, - ApiResultsSingleton, - clear_singleton_instance ) -from rare.shared.image_manager import ImageManagerSingleton +from rare.shared.rare_core import RareCore from rare.utils import legendary_utils, config_helper from rare.utils.paths import cache_dir, tmp_dir from rare.widgets.rare_app import RareApp @@ -61,8 +58,12 @@ def excepthook(exc_type, exc_value, exc_tb): class App(RareApp): def __init__(self, args: Namespace): super(App, self).__init__() + self.rare_core = RareCore(args=args) + self.args = ArgumentsSingleton() + self.signals = GlobalSignalsSingleton() self.core = LegendaryCoreSingleton() - self.args = ArgumentsSingleton(args) # add some options + + config_helper.init_config_handler(self.core) lang = self.settings.value("language", self.core.language_code, type=str) self.load_translator(lang) @@ -71,9 +72,6 @@ class App(RareApp): self.mainwindow: Optional[MainWindow] = None self.launch_dialog: Optional[LaunchDialog] = None - self.signals = GlobalSignalsSingleton(init=True) - self.image_manager = ImageManagerSingleton(init=True) - # launch app self.launch_dialog = LaunchDialog(parent=None) self.launch_dialog.quit_app.connect(self.launch_dialog.close) @@ -141,9 +139,8 @@ class App(RareApp): if self.mainwindow is not None: self.mainwindow.close() self.mainwindow = None - clear_singleton_instance(self.signals) - clear_singleton_instance(self.args) - clear_singleton_instance(ApiResultsSingleton()) + self.rare_core.deleteLater() + del self.rare_core self.processEvents() shutil.rmtree(tmp_dir) os.makedirs(tmp_dir) @@ -182,15 +179,11 @@ def start(args): logger.info(f"Operating System: {platform.system()}") while True: - core = LegendaryCoreSingleton(init=True) - config_helper.init_config_handler(core) app = App(args) exit_code = app.exec_() # if not restart # restart app del app - core.exit() - clear_singleton_instance(core) if exit_code != -133742: break diff --git a/rare/components/dialogs/launch_dialog.py b/rare/components/dialogs/launch_dialog.py index 458b5591..91c507aa 100644 --- a/rare/components/dialogs/launch_dialog.py +++ b/rare/components/dialogs/launch_dialog.py @@ -7,12 +7,11 @@ from requests.exceptions import ConnectionError, HTTPError from rare.components.dialogs.login import LoginDialog from rare.models.apiresults import ApiResults -from rare.shared import LegendaryCoreSingleton, ArgumentsSingleton, ApiResultsSingleton -from rare.shared.image_manager import ImageManagerSingleton +from rare.shared import LegendaryCoreSingleton, ArgumentsSingleton, ApiResultsSingleton, ImageManagerSingleton from rare.ui.components.dialogs.launch_dialog import Ui_LaunchDialog from rare.utils.misc import CloudWorker -logger = getLogger("Login") +logger = getLogger("LoginDialog") class LaunchWorker(QRunnable): diff --git a/rare/components/tabs/games/__init__.py b/rare/components/tabs/games/__init__.py index e6db3967..71e319f7 100644 --- a/rare/components/tabs/games/__init__.py +++ b/rare/components/tabs/games/__init__.py @@ -11,7 +11,7 @@ from rare.shared import ( ArgumentsSingleton, ApiResultsSingleton, ) -from rare.shared.image_manager import ImageManagerSingleton +from rare.shared import ImageManagerSingleton from rare.widgets.library_layout import LibraryLayout from rare.widgets.sliding_stack import SlidingStackedWidget from .cloud_save_utils import CloudSaveUtils diff --git a/rare/components/tabs/games/game_info/game_dlc.py b/rare/components/tabs/games/game_info/game_dlc.py index 192cd28c..82510dea 100644 --- a/rare/components/tabs/games/game_info/game_dlc.py +++ b/rare/components/tabs/games/game_info/game_dlc.py @@ -3,8 +3,8 @@ from PyQt5.QtWidgets import QFrame, QWidget, QMessageBox from legendary.models.game import Game from rare.components.tabs.games.game_utils import GameUtils -from rare.shared import LegendaryCoreSingleton, GlobalSignalsSingleton -from rare.shared.image_manager import ImageManagerSingleton, ImageSize +from rare.shared import LegendaryCoreSingleton, GlobalSignalsSingleton, ImageManagerSingleton +from rare.shared.image_manager import ImageSize from rare.ui.components.tabs.games.game_info.game_dlc import Ui_GameDlc from rare.ui.components.tabs.games.game_info.game_dlc_widget import Ui_GameDlcWidget from rare.models.install import InstallOptionsModel diff --git a/rare/components/tabs/games/game_info/game_info.py b/rare/components/tabs/games/game_info/game_info.py index 4d513b7a..58ffc112 100644 --- a/rare/components/tabs/games/game_info/game_info.py +++ b/rare/components/tabs/games/game_info/game_info.py @@ -27,7 +27,8 @@ from rare.shared import ( GlobalSignalsSingleton, ArgumentsSingleton, ) -from rare.shared.image_manager import ImageManagerSingleton, ImageSize +from rare.shared import ImageManagerSingleton +from rare.shared.image_manager import ImageSize from rare.ui.components.tabs.games.game_info.game_info import Ui_GameInfo from rare.utils.legendary_utils import VerifyWorker from rare.utils.misc import get_size diff --git a/rare/components/tabs/games/game_info/uninstalled_info.py b/rare/components/tabs/games/game_info/uninstalled_info.py index 5c99e648..85f8b188 100644 --- a/rare/components/tabs/games/game_info/uninstalled_info.py +++ b/rare/components/tabs/games/game_info/uninstalled_info.py @@ -11,7 +11,8 @@ from rare.shared import ( ArgumentsSingleton, ApiResultsSingleton, ) -from rare.shared.image_manager import ImageManagerSingleton, ImageSize +from rare.shared import ImageManagerSingleton +from rare.shared.image_manager import ImageSize from rare.ui.components.tabs.games.game_info.game_info import Ui_GameInfo from rare.utils.extra_widgets import SideTabWidget from rare.utils.json_formatter import QJsonModel diff --git a/rare/components/tabs/games/game_widgets/base_installed_widget.py b/rare/components/tabs/games/game_widgets/base_installed_widget.py index 7f1d9837..f493553e 100644 --- a/rare/components/tabs/games/game_widgets/base_installed_widget.py +++ b/rare/components/tabs/games/game_widgets/base_installed_widget.py @@ -7,8 +7,8 @@ from PyQt5.QtGui import QPixmap from PyQt5.QtWidgets import QFrame, QMessageBox, QAction from rare.components.tabs.games.game_utils import GameUtils -from rare.shared import LegendaryCoreSingleton, GlobalSignalsSingleton, ArgumentsSingleton -from rare.shared.image_manager import ImageManagerSingleton, ImageSize +from rare.shared import LegendaryCoreSingleton, GlobalSignalsSingleton, ArgumentsSingleton, ImageManagerSingleton +from rare.shared.image_manager import ImageSize from rare.utils.misc import create_desktop_link from rare.widgets.image_widget import ImageWidget diff --git a/rare/components/tabs/games/game_widgets/base_uninstalled_widget.py b/rare/components/tabs/games/game_widgets/base_uninstalled_widget.py index 01860985..4ff61cf8 100644 --- a/rare/components/tabs/games/game_widgets/base_uninstalled_widget.py +++ b/rare/components/tabs/games/game_widgets/base_uninstalled_widget.py @@ -4,7 +4,8 @@ from PyQt5.QtCore import pyqtSignal, Qt from PyQt5.QtWidgets import QFrame, QAction from legendary.models.game import Game -from rare.shared.image_manager import ImageManagerSingleton, ImageSize +from rare.shared import ImageManagerSingleton +from rare.shared.image_manager import ImageSize from rare.widgets.image_widget import ImageWidget logger = getLogger("Uninstalled") diff --git a/rare/components/tabs/games/game_widgets/installing_game_widget.py b/rare/components/tabs/games/game_widgets/installing_game_widget.py index 4800a757..cc7cf4da 100644 --- a/rare/components/tabs/games/game_widgets/installing_game_widget.py +++ b/rare/components/tabs/games/game_widgets/installing_game_widget.py @@ -3,8 +3,8 @@ from PyQt5.QtGui import QPixmap from PyQt5.QtWidgets import QVBoxLayout, QHBoxLayout, QWidget, QFrame from legendary.models.game import Game -from rare.shared import LegendaryCoreSingleton -from rare.shared.image_manager import ImageManagerSingleton, ImageSize +from rare.shared import LegendaryCoreSingleton, ImageManagerSingleton +from rare.shared.image_manager import ImageSize from rare.widgets.elide_label import ElideLabel from .library_widget import LibraryWidget diff --git a/rare/shared/__init__.py b/rare/shared/__init__.py index 788e63fc..3decb371 100644 --- a/rare/shared/__init__.py +++ b/rare/shared/__init__.py @@ -5,105 +5,34 @@ Each of the objects in this module should be instantiated ONCE and only ONCE! """ -import configparser import logging -import os from argparse import Namespace -from typing import Optional, Union +from typing import Optional from rare.lgndr.core import LegendaryCore from rare.models.apiresults import ApiResults from rare.models.signals import GlobalSignals +from .image_manager import ImageManager +from .rare_core import RareCore -logger = logging.getLogger("Singleton") - -_legendary_core_singleton: Optional[LegendaryCore] = None -_global_signals_singleton: Optional[GlobalSignals] = None -_arguments_singleton: Optional[Namespace] = None -_api_results_singleton: Optional[ApiResults] = None +logger = logging.getLogger("Shared") -def LegendaryCoreSingleton(init: bool = False) -> LegendaryCore: - global _legendary_core_singleton - if _legendary_core_singleton is None and not init: - raise RuntimeError("Uninitialized use of LegendaryCoreSingleton") - if _legendary_core_singleton is not None and init: - raise RuntimeError("LegendaryCore already initialized") - if init: - try: - _legendary_core_singleton = LegendaryCore() - except configparser.MissingSectionHeaderError as e: - logger.warning(f"Config is corrupt: {e}") - if config_path := os.environ.get("XDG_CONFIG_HOME"): - path = os.path.join(config_path, "legendary") - else: - path = os.path.expanduser("~/.config/legendary") - with open(os.path.join(path, "config.ini"), "w") as config_file: - config_file.write("[Legendary]") - _legendary_core_singleton = LegendaryCore() - if "Legendary" not in _legendary_core_singleton.lgd.config.sections(): - _legendary_core_singleton.lgd.config.add_section("Legendary") - _legendary_core_singleton.lgd.save_config() - # workaround if egl sync enabled, but no programdata_path - # programdata_path might be unset if logging in through the browser - if _legendary_core_singleton.egl_sync_enabled: - if _legendary_core_singleton.egl.programdata_path is None: - _legendary_core_singleton.lgd.config.remove_option("Legendary", "egl_sync") - _legendary_core_singleton.lgd.save_config() - else: - if not os.path.exists(_legendary_core_singleton.egl.programdata_path): - _legendary_core_singleton.lgd.config.remove_option("Legendary", "egl_sync") - _legendary_core_singleton.lgd.save_config() - return _legendary_core_singleton +def ArgumentsSingleton() -> Optional[Namespace]: + return RareCore.instance().args() -def GlobalSignalsSingleton(init: bool = False) -> GlobalSignals: - global _global_signals_singleton - if _global_signals_singleton is None and not init: - raise RuntimeError("Uninitialized use of GlobalSignalsSingleton") - if _global_signals_singleton is not None and init: - raise RuntimeError("GlobalSignals already initialized") - if init: - _global_signals_singleton = GlobalSignals() - return _global_signals_singleton +def GlobalSignalsSingleton() -> GlobalSignals: + return RareCore.instance().signals() -def ArgumentsSingleton(args: Namespace = None) -> Optional[Namespace]: - global _arguments_singleton - if _arguments_singleton is None and args is None: - raise RuntimeError("Uninitialized use of ArgumentsSingleton") - if _arguments_singleton is not None and args is not None: - raise RuntimeError("Arguments already initialized") - if args is not None: - _arguments_singleton = args - return _arguments_singleton +def LegendaryCoreSingleton() -> LegendaryCore: + return RareCore.instance().core() + + +def ImageManagerSingleton() -> ImageManager: + return RareCore.instance().image_manager() def ApiResultsSingleton(res: ApiResults = None) -> Optional[ApiResults]: - global _api_results_singleton - if _api_results_singleton is None and res is None: - raise RuntimeError("Uninitialized use of ApiResultsSingleton") - if _api_results_singleton is not None and res is not None: - raise RuntimeError("ApiResults already initialized") - if res is not None: - _api_results_singleton = res - return _api_results_singleton - - -def clear_singleton_instance(instance: Union[LegendaryCore, GlobalSignals, Namespace, ApiResults]): - global _legendary_core_singleton, _global_signals_singleton, _arguments_singleton, _api_results_singleton - if isinstance(instance, LegendaryCore): - del instance - _legendary_core_singleton = None - elif isinstance(instance, GlobalSignals): - instance.deleteLater() - del instance - _global_signals_singleton = None - elif isinstance(instance, Namespace): - del instance - _arguments_singleton = None - elif isinstance(instance, ApiResults): - del instance - _api_results_singleton = None - else: - raise RuntimeError(f"Instance is of unknown type \"{type(instance)}\"") + return RareCore.instance().api_results(res) diff --git a/rare/shared/image_manager.py b/rare/shared/image_manager.py index 9d2791cd..a64cb62f 100644 --- a/rare/shared/image_manager.py +++ b/rare/shared/image_manager.py @@ -7,7 +7,7 @@ import zlib from logging import getLogger from pathlib import Path from typing import TYPE_CHECKING -from typing import Tuple, Dict, Union, Type, List, Callable, Optional +from typing import Tuple, Dict, Union, Type, List, Callable import requests from PyQt5.QtCore import ( @@ -26,7 +26,8 @@ from PyQt5.QtGui import ( from PyQt5.QtWidgets import QApplication from legendary.models.game import Game -from rare.shared import LegendaryCoreSingleton, GlobalSignalsSingleton +from rare.lgndr.core import LegendaryCore +from rare.models.signals import GlobalSignals from rare.utils.paths import image_dir, resources_path if TYPE_CHECKING: @@ -111,10 +112,10 @@ class ImageManager(QObject): __dl_retries = 1 __worker_app_names: List[str] = list() - def __init__(self): + def __init__(self, signals: GlobalSignals, core: LegendaryCore): super(QObject, self).__init__() - self.core = LegendaryCoreSingleton() - self.signals = GlobalSignalsSingleton() + self.signals = signals + self.core = core self.image_dir = Path(image_dir) if not self.image_dir.is_dir(): @@ -357,15 +358,3 @@ class ImageManager(QObject): """ image: QImage = self.__get_cover(QImage, app_name, color) return image - - -_image_manager_singleton: Optional[ImageManager] = None - - -def ImageManagerSingleton(init: bool = False) -> ImageManager: - global _image_manager_singleton - if _image_manager_singleton is None and not init: - raise RuntimeError("Uninitialized use of ImageManagerSingleton") - if _image_manager_singleton is None: - _image_manager_singleton = ImageManager() - return _image_manager_singleton diff --git a/rare/shared/rare_core.py b/rare/shared/rare_core.py new file mode 100644 index 00000000..def6af99 --- /dev/null +++ b/rare/shared/rare_core.py @@ -0,0 +1,133 @@ +import configparser +import os +from argparse import Namespace +from logging import getLogger +from typing import Optional + +from PyQt5.QtCore import QObject + +from rare.lgndr.core import LegendaryCore +from rare.models.apiresults import ApiResults +from rare.models.signals import GlobalSignals +from .image_manager import ImageManager + +logger = getLogger("RareCore") + + +class RareCore(QObject): + _instance: Optional['RareCore'] = None + + def __init__(self, args: Namespace): + 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(args) + self.signals(init=True) + self.core(init=True) + self.image_manager(init=True) + + RareCore._instance = self + + @staticmethod + def instance() -> 'RareCore': + if RareCore._instance is None: + raise RuntimeError("Uninitialized use of RareCore") + return RareCore._instance + + def signals(self, init: bool = False) -> GlobalSignals: + if self._signals is None and not init: + raise RuntimeError("Uninitialized use of GlobalSignalsSingleton") + if self._signals is not None and init: + raise RuntimeError("GlobalSignals already initialized") + if init: + self._signals = GlobalSignals() + return self._signals + + def args(self, args: Namespace = None) -> Optional[Namespace]: + 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: + raise RuntimeError("Arguments already initialized") + if args is not None: + self._args = args + return self._args + + def core(self, init: bool = False) -> LegendaryCore: + if self._core is None and not init: + raise RuntimeError("Uninitialized use of LegendaryCoreSingleton") + if self._core is not None and init: + raise RuntimeError("LegendaryCore already initialized") + if init: + try: + self._core = LegendaryCore() + except configparser.MissingSectionHeaderError as e: + logger.warning(f"Config is corrupt: {e}") + if config_path := os.environ.get("XDG_CONFIG_HOME"): + path = os.path.join(config_path, "legendary") + else: + 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() + # 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() + 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 + + def image_manager(self, init: bool = False) -> ImageManager: + if self._image_manager is None and not init: + raise RuntimeError("Uninitialized use of ImageManagerSingleton") + 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 + + def api_results(self, res: ApiResults = None) -> Optional[ApiResults]: + 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: + raise RuntimeError("ApiResults already initialized") + if res is not None: + self._api_results = res + return self._api_results + + def deleteLater(self) -> None: + del self._api_results + self._api_results = None + + self._image_manager.deleteLater() + del self._image_manager + self._image_manager = None + + self._core.exit() + del self._core + self._core = None + + self._signals.deleteLater() + del self._signals + self._signals = None + + del self._args + self._args = None + + RareCore._instance = None + + super(RareCore, self).deleteLater() +