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.save()
self.game.update() self.game.update()
# TODO: this is fucked up
# Get a cover from SGDB if none is present # Get a cover from SGDB if none is present
if not self.game_cover.get_pixbuf(): if not self.game_cover.get_pixbuf():
self.game.set_loading(1) self.game.set_loading(1)

View File

@@ -17,7 +17,6 @@
# #
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
import json
import logging import logging
import os import os
import shlex import shlex
@@ -25,10 +24,9 @@ import subprocess
from pathlib import Path from pathlib import Path
from time import time 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 import shared # pylint: disable=no-name-in-module
from src.game_cover import GameCover
# pylint: disable=too-many-instance-attributes # pylint: disable=too-many-instance-attributes
@@ -86,74 +84,15 @@ class Game(Gtk.Box):
shared.schema.connect("changed", self.schema_changed) 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): def update_values(self, data):
for key, value in data.items(): for key, value in data.items():
setattr(self, key, value) setattr(self, key, value)
def update(self):
self.emit("update-ready", {})
def save(self): def save(self):
shared.games_dir.mkdir(parents=True, exist_ok=True) self.emit("save-ready", {})
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,
)
def create_toast(self, title, action=None): def create_toast(self, title, action=None):
toast = Adw.Toast.new(title.format(self.name)) toast = Adw.Toast.new(title.format(self.name))
@@ -270,3 +209,11 @@ class Game(Gtk.Box):
def schema_changed(self, _settings, key): def schema_changed(self, _settings, key):
if key == "cover-launches-game": if key == "cover-launches-game":
self.set_play_icon() 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 # Create the games store ready to load games from disk
if not shared.store: if not shared.store:
shared.store = Store() shared.store = Store()
shared.store.add_manager(FileManager(), False)
shared.store.add_manager(DisplayManager()) shared.store.add_manager(DisplayManager())
self.load_games_from_disk() self.load_games_from_disk()
@@ -91,7 +92,8 @@ class CartridgesApplication(Adw.Application):
shared.store.add_manager(SteamAPIManager()) shared.store.add_manager(SteamAPIManager())
shared.store.add_manager(OnlineCoverManager()) shared.store.add_manager(OnlineCoverManager())
shared.store.add_manager(SGDBManager()) shared.store.add_manager(SGDBManager())
shared.store.add_manager(FileManager())
shared.store.manager_to_pipeline(FileManager)
# Create actions # Create actions
self.create_actions( self.create_actions(
@@ -137,7 +139,7 @@ class CartridgesApplication(Adw.Application):
for game_file in shared.games_dir.iterdir(): for game_file in shared.games_dir.iterdir():
data = json.load(game_file.open()) data = json.load(game_file.open())
game = Game(data, allow_side_effects=False) 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): def on_about_action(self, *_args):
about = Adw.AboutWindow( about = Adw.AboutWindow(
@@ -148,9 +150,9 @@ class CartridgesApplication(Adw.Application):
version=shared.VERSION, version=shared.VERSION,
developers=[ developers=[
"kramo https://kramo.hu", "kramo https://kramo.hu",
"Geoffrey Coulaud https://geoffrey-coulaud.fr",
"Arcitec https://github.com/Arcitec", "Arcitec https://github.com/Arcitec",
"Domenico https://github.com/Domefemia", "Domenico https://github.com/Domefemia",
"Geoffrey Coulaud https://geoffrey-coulaud.fr",
"Paweł Lidwin https://github.com/imLinguin", "Paweł Lidwin https://github.com/imLinguin",
"Rafael Mardojai CM https://mardojai.com", "Rafael Mardojai CM https://mardojai.com",
], ],

View File

@@ -1,16 +1,55 @@
from src import shared # pylint: disable=no-name-in-module from src import shared # pylint: disable=no-name-in-module
from src.game import Game 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.sgdb_manager import SGDBManager
from src.store.managers.steam_api_manager import SteamAPIManager from src.store.managers.steam_api_manager import SteamAPIManager
from src.store.managers.manager import Manager
class DisplayManager(Manager): class DisplayManager(Manager):
"""Manager in charge of adding a game to the UI""" """Manager in charge of adding a game to the UI"""
run_after = (SteamAPIManager, SGDBManager) run_after = (SteamAPIManager, SGDBManager)
signals = {"update-ready"}
def manager_logic(self, game: Game, _additional_data: dict) -> None: def manager_logic(self, game: Game, _additional_data: dict) -> None:
# TODO decouple a game from its widget
shared.win.games[game.game_id] = game 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.game import Game
from src.store.managers.async_manager import AsyncManager from src.store.managers.async_manager import AsyncManager
from src.store.managers.steam_api_manager import SteamAPIManager 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""" """Manager in charge of saving a game to a file"""
run_after = (SteamAPIManager,) run_after = (SteamAPIManager,)
signals = {"save-ready"}
def manager_logic(self, game: Game, _additional_data: dict) -> None: def manager_logic(self, game: Game, additional_data: dict) -> None:
game.save() 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() retryable_on: Container[type[Exception]] = tuple()
continue_on: Container[type[Exception]] = tuple() continue_on: Container[type[Exception]] = tuple()
signals: Container[type[str]] = set()
retry_delay: int = 3 retry_delay: int = 3
max_tries: int = 3 max_tries: int = 3
@@ -110,6 +111,5 @@ class Manager:
self, game: Game, additional_data: dict, callback: Callable[["Manager"], Any] self, game: Game, additional_data: dict, callback: Callable[["Manager"], Any]
) -> None: ) -> None:
"""Pass the game through the manager""" """Pass the game through the manager"""
# TODO: connect to signals here
self.execute_resilient_manager_logic(game, additional_data) self.execute_resilient_manager_logic(game, additional_data)
callback(self) callback(self)

View File

@@ -16,9 +16,12 @@ class Store:
self.games = {} self.games = {}
self.pipelines = {} 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""" """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( def add_game(
self, game: Game, additional_data: dict, replace=False self, game: Game, additional_data: dict, replace=False
@@ -50,8 +53,17 @@ class Store:
path.unlink(missing_ok=True) path.unlink(missing_ok=True)
return None 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 # 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.games[game.game_id] = game
self.pipelines[game.game_id] = pipeline self.pipelines[game.game_id] = pipeline
pipeline.advance() pipeline.advance()