🚧 More work on managers

This commit is contained in:
GeoffreyCoulaud
2023-05-23 15:26:48 +02:00
parent 9ea2e9652d
commit abe41635fd
4 changed files with 130 additions and 43 deletions

View File

@@ -1,14 +1,46 @@
from abc import abstractmethod
from gi.repository import Gio
from src.game import Game
class Manager:
"""Class in charge of handling a post creation action for games.
May connect to signals on the game to handle them."""
* May connect to signals on the game to handle them.
* May cancel its running tasks on critical error,
in that case a new cancellable must be generated for new tasks to run.
"""
run_after: set[type["Manager"]]
cancellable: Gio.Cancellable
errors: list[Exception]
def __init__(self) -> None:
super().__init__()
self.cancellable = Gio.Cancellable()
self.errors = list()
def cancel_tasks(self):
"""Cancel all tasks for this manager"""
self.cancellable.cancel()
def reset_cancellable(self):
"""Reset the cancellable for this manager.
Alreadyn scheduled Tasks will no longer be cancellable."""
self.cancellable = Gio.Cancellable()
def report_error(self, error: Exception):
"""Report an error that happened in of run"""
self.errors.append(error)
def collect_errors(self) -> list[Exception]:
"""Get the errors produced by the manager and remove them from self.errors"""
errors = list(self.errors)
self.errors.clear()
return errors
@abstractmethod
def run(self, game: Game) -> None:
"""Pass the game through the manager.

View File

@@ -1,11 +1,22 @@
from src.store.manager import Manager
from requests import HTTPError
from src.game import Game
from src.utils.steamgriddb import SGDBHelper
from src.store.manager import Manager
from src.utils.steamgriddb import SGDBAuthError, SGDBError, SGDBHelper
class SGDBManager(Manager):
"""Manager in charge of downloading a game's cover from steamgriddb"""
def run(self, game: Game) -> None:
# TODO
pass
try:
sgdb = SGDBHelper()
sgdb.conditionaly_update_cover(game)
except SGDBAuthError as error:
# If invalid auth, cancel all SGDBManager tasks
self.cancellable.cancel()
self.report_error(error)
except (HTTPError, SGDBError) as error:
# On other error, just report it
self.report_error(error)
pass

View File

@@ -1,18 +1,39 @@
from typing import Iterable
from gi.repository import GObject
import src.shared as shared
from src.game import Game
from src.store.manager import Manager
from src.utils.task import Task
class Pipeline(set):
"""Class representing a set of Managers for a game"""
class Pipeline(GObject.Object):
"""Class representing a set of managers for a game"""
game: Game
waiting: set[Manager]
running: set[Manager]
done: set[Manager]
def __init__(self, managers: Iterable[Manager]) -> None:
super().__init__()
self.waiting = set(managers)
self.running = set()
self.done = set()
@property
def blocked_managers(self) -> set(Manager):
def not_done(self) -> set[Manager]:
"""Get the managers that are not done yet"""
return self.waiting + self.running
@property
def blocked(self) -> set[Manager]:
"""Get the managers that cannot run because their dependencies aren't done"""
blocked = set()
for manager_a in self:
for manager_b in self:
for manager_a in self.waiting:
for manager_b in self.not_done:
if manager_a == manager_b:
continue
if type(manager_b) in manager_a.run_after:
@@ -20,9 +41,43 @@ class Pipeline(set):
return blocked
@property
def startable_managers(self) -> set(Manager):
def ready(self) -> set[Manager]:
"""Get the managers that can be run"""
return self - self.blocked_managers
return self.waiting - self.blocked
def advance(self):
"""Spawn tasks for managers that are able to run for a game"""
for manager in self.ready:
self.waiting.remove(manager)
self.running.add(manager)
data = (manager,)
task = Task.new(self, manager.cancellable, self.manager_task_callback, data)
task.set_task_data(data)
task.run_in_thread(self.manager_task_thread_func)
@GObject.Signal(name="manager-started")
def manager_started(self, manager: Manager) -> None:
"""Signal emitted when a manager is started"""
pass
def manager_task_thread_func(self, _task, _source_object, data, cancellable):
"""Thread function for manager tasks"""
manager, *_rest = data
self.emit("manager-started", manager)
manager.run(self.game, cancellable)
@GObject.Signal(name="manager-done")
def manager_done(self, manager: Manager) -> None:
"""Signal emitted when a manager is done"""
pass
def manager_task_callback(self, _source_object, _result, data):
"""Callback function for manager tasks"""
manager, *_rest = data
self.running.remove(manager)
self.done.add(manager)
self.emit("manager-done", manager)
self.advance()
class Store:
@@ -42,35 +97,20 @@ class Store:
"""Add a manager class that will run when games are added"""
self.managers.add(manager)
def add_game(self, game: Game, replace=False):
def add_game(self, game: Game, replace=False) -> Pipeline:
"""Add a game to the app if not already there
:param replace bool: Replace the game if it already exists"""
:param replace bool: Replace the game if it already exists
:return:
"""
if (
game.game_id in self.games
and not self.games[game.game_id].removed
and not replace
):
return
pipeline = Pipeline(self.managers)
self.games[game.game_id] = game
self.pipelines[game.game_id] = Pipeline(self.managers)
self.advance_pipeline(game)
def advance_pipeline(self, game: Game):
"""Spawn tasks for managers that are able to run for a game"""
for manager in self.pipelines[game.game_id].startable_managers:
data = (manager, game)
task = Task.new(None, None, self.manager_task_callback, data)
task.set_task_data(data)
task.run_in_thread(self.manager_task_thread_func)
def manager_task_thread_func(self, _task, _source_object, data, _cancellable):
"""Thread function for manager tasks"""
manager, game, *_rest = data
manager.run(game)
def manager_task_callback(self, _source_object, _result, data):
"""Callback function for manager tasks"""
manager, game, *_rest = data
self.pipelines[game.game_id].remove(manager)
self.advance_pipeline(game)
self.pipelines[game.game_id] = pipeline
pipeline.advance()
return pipeline