Use signals for updating and saving games

This commit is contained in:
kramo
2023-06-15 17:37:54 +02:00
parent e6afed6678
commit 39bc64c136
7 changed files with 107 additions and 78 deletions

View File

@@ -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)

View File

@@ -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"""

View File

@@ -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",
],

View File

@@ -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()

View File

@@ -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,
)

View File

@@ -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)

View File

@@ -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()