diff --git a/src/details_window.py b/src/details_window.py index 86c3a83..25d6069 100644 --- a/src/details_window.py +++ b/src/details_window.py @@ -205,6 +205,7 @@ class DetailsWindow(Adw.Window): self.game.save() self.game.update() + # TODO: this is fucked up # Get a cover from SGDB if none is present if not self.game_cover.get_pixbuf(): self.game.set_loading(1) diff --git a/src/game.py b/src/game.py index 090dedb..e7be76c 100644 --- a/src/game.py +++ b/src/game.py @@ -17,7 +17,6 @@ # # SPDX-License-Identifier: GPL-3.0-or-later -import json import logging import os import shlex @@ -25,10 +24,9 @@ import subprocess from pathlib import Path from time import time -from gi.repository import Adw, Gtk +from gi.repository import Adw, GObject, Gtk from src import shared # pylint: disable=no-name-in-module -from src.game_cover import GameCover # pylint: disable=too-many-instance-attributes @@ -86,74 +84,15 @@ class Game(Gtk.Box): shared.schema.connect("changed", self.schema_changed) - def update(self): - if self.get_parent(): - self.get_parent().get_parent().remove(self) - if self.get_parent(): - self.get_parent().set_child() - - self.menu_button.set_menu_model( - self.hidden_game_options if self.hidden else self.game_options - ) - - self.title.set_label(self.name) - - self.menu_button.get_popover().connect( - "notify::visible", self.toggle_play, None - ) - self.menu_button.get_popover().connect( - "notify::visible", self.win.set_active_game, self - ) - - if self.game_id in self.win.game_covers: - self.game_cover = self.win.game_covers[self.game_id] - self.game_cover.add_picture(self.cover) - else: - self.game_cover = GameCover({self.cover}, self.get_cover_path()) - self.win.game_covers[self.game_id] = self.game_cover - - if ( - self.win.stack.get_visible_child() == self.win.details_view - and self.win.active_game == self - ): - self.win.show_details_view(self) - - if not self.removed and not self.blacklisted: - if self.hidden: - self.win.hidden_library.append(self) - else: - self.win.library.append(self) - self.get_parent().set_focusable(False) - - self.win.set_library_child() - def update_values(self, data): for key, value in data.items(): setattr(self, key, value) + def update(self): + self.emit("update-ready", {}) + def save(self): - shared.games_dir.mkdir(parents=True, exist_ok=True) - - attrs = ( - "added", - "executable", - "game_id", - "source", - "hidden", - "last_played", - "name", - "developer", - "removed", - "blacklisted", - "version", - ) - - json.dump( - {attr: getattr(self, attr) for attr in attrs if attr}, - (shared.games_dir / f"{self.game_id}.json").open("w"), - indent=4, - sort_keys=True, - ) + self.emit("save-ready", {}) def create_toast(self, title, action=None): toast = Adw.Toast.new(title.format(self.name)) @@ -270,3 +209,11 @@ class Game(Gtk.Box): def schema_changed(self, _settings, key): if key == "cover-launches-game": self.set_play_icon() + + @GObject.Signal(name="update-ready", arg_types=[object]) + def update_ready(self, _additional_data) -> None: + """Signal emitted when the game needs updating""" + + @GObject.Signal(name="save-ready", arg_types=[object]) + def save_ready(self, _additional_data) -> None: + """Signal emitted when the game needs saving""" diff --git a/src/main.py b/src/main.py index b59176c..c099ab1 100644 --- a/src/main.py +++ b/src/main.py @@ -82,6 +82,7 @@ class CartridgesApplication(Adw.Application): # Create the games store ready to load games from disk if not shared.store: shared.store = Store() + shared.store.add_manager(FileManager(), False) shared.store.add_manager(DisplayManager()) self.load_games_from_disk() @@ -91,7 +92,8 @@ class CartridgesApplication(Adw.Application): shared.store.add_manager(SteamAPIManager()) shared.store.add_manager(OnlineCoverManager()) shared.store.add_manager(SGDBManager()) - shared.store.add_manager(FileManager()) + + shared.store.manager_to_pipeline(FileManager) # Create actions self.create_actions( @@ -137,7 +139,7 @@ class CartridgesApplication(Adw.Application): for game_file in shared.games_dir.iterdir(): data = json.load(game_file.open()) game = Game(data, allow_side_effects=False) - shared.store.add_game(game, tuple()) + shared.store.add_game(game, {"skip_save": True}) def on_about_action(self, *_args): about = Adw.AboutWindow( @@ -148,9 +150,9 @@ class CartridgesApplication(Adw.Application): version=shared.VERSION, developers=[ "kramo https://kramo.hu", + "Geoffrey Coulaud https://geoffrey-coulaud.fr", "Arcitec https://github.com/Arcitec", "Domenico https://github.com/Domefemia", - "Geoffrey Coulaud https://geoffrey-coulaud.fr", "Paweł Lidwin https://github.com/imLinguin", "Rafael Mardojai CM https://mardojai.com", ], diff --git a/src/store/managers/display_manager.py b/src/store/managers/display_manager.py index 13418d4..847bdfb 100644 --- a/src/store/managers/display_manager.py +++ b/src/store/managers/display_manager.py @@ -1,16 +1,55 @@ from src import shared # pylint: disable=no-name-in-module from src.game import Game +from src.game_cover import GameCover +from src.store.managers.manager import Manager from src.store.managers.sgdb_manager import SGDBManager from src.store.managers.steam_api_manager import SteamAPIManager -from src.store.managers.manager import Manager class DisplayManager(Manager): """Manager in charge of adding a game to the UI""" run_after = (SteamAPIManager, SGDBManager) + signals = {"update-ready"} def manager_logic(self, game: Game, _additional_data: dict) -> None: - # TODO decouple a game from its widget shared.win.games[game.game_id] = game - game.update() + if game.get_parent(): + game.get_parent().get_parent().remove(game) + if game.get_parent(): + game.get_parent().set_child() + + game.menu_button.set_menu_model( + game.hidden_game_options if game.hidden else game.game_options + ) + + game.title.set_label(game.name) + + game.menu_button.get_popover().connect( + "notify::visible", game.toggle_play, None + ) + game.menu_button.get_popover().connect( + "notify::visible", game.win.set_active_game, game + ) + + if game.game_id in game.win.game_covers: + game.game_cover = game.win.game_covers[game.game_id] + game.game_cover.add_picture(game.cover) + else: + game.game_cover = GameCover({game.cover}, game.get_cover_path()) + game.win.game_covers[game.game_id] = game.game_cover + + if ( + game.win.stack.get_visible_child() == game.win.details_view + and game.win.active_game == game + ): + game.win.show_details_view(game) + + if not game.removed and not game.blacklisted: + if game.hidden: + game.win.hidden_library.append(game) + else: + game.win.library.append(game) + game.get_parent().set_focusable(False) + + game.win.set_library_child() diff --git a/src/store/managers/file_manager.py b/src/store/managers/file_manager.py index 7a72e5d..13d492f 100644 --- a/src/store/managers/file_manager.py +++ b/src/store/managers/file_manager.py @@ -1,3 +1,6 @@ +import json + +from src import shared # pylint: disable=no-name-in-module from src.game import Game from src.store.managers.async_manager import AsyncManager from src.store.managers.steam_api_manager import SteamAPIManager @@ -7,6 +10,31 @@ class FileManager(AsyncManager): """Manager in charge of saving a game to a file""" run_after = (SteamAPIManager,) + signals = {"save-ready"} - def manager_logic(self, game: Game, _additional_data: dict) -> None: - game.save() + def manager_logic(self, game: Game, additional_data: dict) -> None: + if additional_data.get("skip_save"): # Skip saving when loading games from disk + return + + shared.games_dir.mkdir(parents=True, exist_ok=True) + + attrs = ( + "added", + "executable", + "game_id", + "source", + "hidden", + "last_played", + "name", + "developer", + "removed", + "blacklisted", + "version", + ) + + json.dump( + {attr: getattr(game, attr) for attr in attrs if attr}, + (shared.games_dir / f"{game.game_id}.json").open("w"), + indent=4, + sort_keys=True, + ) diff --git a/src/store/managers/manager.py b/src/store/managers/manager.py index b3fa098..1124d55 100644 --- a/src/store/managers/manager.py +++ b/src/store/managers/manager.py @@ -21,6 +21,7 @@ class Manager: retryable_on: Container[type[Exception]] = tuple() continue_on: Container[type[Exception]] = tuple() + signals: Container[type[str]] = set() retry_delay: int = 3 max_tries: int = 3 @@ -110,6 +111,5 @@ class Manager: self, game: Game, additional_data: dict, callback: Callable[["Manager"], Any] ) -> None: """Pass the game through the manager""" - # TODO: connect to signals here self.execute_resilient_manager_logic(game, additional_data) callback(self) diff --git a/src/store/store.py b/src/store/store.py index c33ded2..c65b7c8 100644 --- a/src/store/store.py +++ b/src/store/store.py @@ -16,9 +16,12 @@ class Store: self.games = {} self.pipelines = {} - def add_manager(self, manager: Manager): + def add_manager(self, manager: Manager, in_pipeline=True): """Add a manager that will run when games are added""" - self.managers[type(manager)] = manager + self.managers[type(manager)] = [manager, in_pipeline] + + def manager_to_pipeline(self, manager_type: type[Manager]): + self.managers[manager_type][1] = True def add_game( self, game: Game, additional_data: dict, replace=False @@ -50,8 +53,17 @@ class Store: path.unlink(missing_ok=True) return None + # Connect signals + for manager, _in_pipeline in self.managers.values(): + for signal in manager.signals: + game.connect(signal, manager.execute_resilient_manager_logic) + # Run the pipeline for the game - pipeline = Pipeline(game, additional_data, self.managers.values()) + pipeline = Pipeline( + game, + additional_data, + (manager[0] for manager in self.managers.values() if manager[1]), + ) self.games[game.game_id] = game self.pipelines[game.game_id] = pipeline pipeline.advance()