This commit is contained in:
kramo
2023-08-16 19:18:03 +02:00
parent a3aa7f9ccf
commit eeb18eb017
17 changed files with 102 additions and 89 deletions

View File

@@ -19,7 +19,7 @@
import os import os
from time import time from time import time
from typing import Any from typing import Any, Optional
from gi.repository import Adw, Gio, GLib, Gtk from gi.repository import Adw, Gio, GLib, Gtk
from PIL import Image from PIL import Image
@@ -55,7 +55,7 @@ class DetailsWindow(Adw.Window):
cover_changed: bool = False cover_changed: bool = False
def __init__(self, game: Game | None = None, **kwargs: Any): def __init__(self, game: Optional[Game] = None, **kwargs: Any):
super().__init__(**kwargs) super().__init__(**kwargs)
self.game: Game = game self.game: Game = game

View File

@@ -8,14 +8,14 @@ class ErrorProducer:
Specifies the report_error and collect_errors methods in a thread-safe manner. Specifies the report_error and collect_errors methods in a thread-safe manner.
""" """
errors: list[Exception] = None errors: list[Exception]
errors_lock: Lock = None errors_lock: Lock
def __init__(self) -> None: def __init__(self) -> None:
self.errors = [] self.errors = []
self.errors_lock = Lock() self.errors_lock = Lock()
def report_error(self, error: Exception): def report_error(self, error: Exception) -> None:
"""Report an error""" """Report an error"""
with self.errors_lock: with self.errors_lock:
self.errors.append(error) self.errors.append(error)

View File

@@ -1,4 +1,4 @@
from typing import Iterable from typing import Iterable, Optional
class FriendlyError(Exception): class FriendlyError(Exception):
@@ -27,8 +27,8 @@ class FriendlyError(Exception):
self, self,
title: str, title: str,
subtitle: str, subtitle: str,
title_args: Iterable[str] = None, title_args: Optional[Iterable[str]] = None,
subtitle_args: Iterable[str] = None, subtitle_args: Optional[Iterable[str]] = None,
) -> None: ) -> None:
"""Create a friendly error """Create a friendly error

View File

@@ -23,7 +23,7 @@ import shlex
import subprocess import subprocess
from pathlib import Path from pathlib import Path
from time import time from time import time
from typing import Any from typing import Any, Optional
from gi.repository import Adw, GLib, GObject, Gtk from gi.repository import Adw, GLib, GObject, Gtk
@@ -57,7 +57,7 @@ class Game(Gtk.Box):
hidden: bool = False hidden: bool = False
last_played: int = 0 last_played: int = 0
name: str name: str
developer: str | None = None developer: Optional[str] = None
removed: bool = False removed: bool = False
blacklisted: bool = False blacklisted: bool = False
game_cover: GameCover = None game_cover: GameCover = None
@@ -97,7 +97,7 @@ class Game(Gtk.Box):
def save(self) -> None: def save(self) -> None:
self.emit("save-ready", {}) self.emit("save-ready", {})
def create_toast(self, title: str, action: str | None = None) -> None: def create_toast(self, title: str, action: Optional[str] = None) -> None:
toast = Adw.Toast.new(title.format(self.name)) toast = Adw.Toast.new(title.format(self.name))
toast.set_priority(Adw.ToastPriority.HIGH) toast.set_priority(Adw.ToastPriority.HIGH)
@@ -180,7 +180,7 @@ class Game(Gtk.Box):
self.cover.set_opacity(int(not loading)) self.cover.set_opacity(int(not loading))
self.spinner.set_spinning(loading) self.spinner.set_spinning(loading)
def get_cover_path(self) -> Path | None: def get_cover_path(self) -> Optional[Path]:
cover_path = shared.covers_dir / f"{self.game_id}.gif" cover_path = shared.covers_dir / f"{self.game_id}.gif"
if cover_path.is_file(): if cover_path.is_file():
return cover_path # type: ignore return cover_path # type: ignore

View File

@@ -18,7 +18,7 @@
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
from pathlib import Path from pathlib import Path
from typing import Any, Callable from typing import Any, Callable, Optional
from gi.repository import Gdk, GdkPixbuf, Gio, GLib, Gtk from gi.repository import Gdk, GdkPixbuf, Gio, GLib, Gtk
from PIL import Image, ImageFilter, ImageStat from PIL import Image, ImageFilter, ImageStat
@@ -27,12 +27,12 @@ from src import shared
class GameCover: class GameCover:
texture: Gdk.Texture | None texture: Optional[Gdk.Texture]
blurred: Gdk.Texture | None blurred: Optional[Gdk.Texture]
luminance: tuple[float, float] | None luminance: Optional[tuple[float, float]]
path: Path | None path: Optional[Path]
animation: GdkPixbuf.PixbufAnimation | None animation: Optional[GdkPixbuf.PixbufAnimation]
anim_iter: GdkPixbuf.PixbufAnimationIter | None anim_iter: Optional[GdkPixbuf.PixbufAnimationIter]
placeholder = Gdk.Texture.new_from_resource( placeholder = Gdk.Texture.new_from_resource(
shared.PREFIX + "/library_placeholder.svg" shared.PREFIX + "/library_placeholder.svg"
@@ -41,12 +41,12 @@ class GameCover:
shared.PREFIX + "/library_placeholder_small.svg" shared.PREFIX + "/library_placeholder_small.svg"
) )
def __init__(self, pictures: set[Gtk.Picture], path: Path | None = None) -> None: def __init__(self, pictures: set[Gtk.Picture], path: Optional[Path] = None) -> None:
self.pictures = pictures self.pictures = pictures
self.new_cover(path) self.new_cover(path)
# Wrap the function in another one as Gio.Task.run_in_thread does not allow for passing args # Wrap the function in another one as Gio.Task.run_in_thread does not allow for passing args
def create_func(self, path: Path | None) -> Callable: def create_func(self, path: Optional[Path]) -> Callable:
self.animation = GdkPixbuf.PixbufAnimation.new_from_file(str(path)) self.animation = GdkPixbuf.PixbufAnimation.new_from_file(str(path))
self.anim_iter = self.animation.get_iter() self.anim_iter = self.animation.get_iter()
@@ -55,7 +55,7 @@ class GameCover:
return wrapper return wrapper
def new_cover(self, path: Path | None = None) -> None: def new_cover(self, path: Optional[Path] = None) -> None:
self.animation = None self.animation = None
self.texture = None self.texture = None
self.blurred = None self.blurred = None

View File

@@ -19,6 +19,7 @@
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
import logging import logging
from typing import Any, Optional
from gi.repository import Adw, GLib, Gtk from gi.repository import Adw, GLib, Gtk
@@ -37,22 +38,22 @@ from src.utils.task import Task
class Importer(ErrorProducer): class Importer(ErrorProducer):
"""A class in charge of scanning sources for games""" """A class in charge of scanning sources for games"""
progressbar = None progressbar: Gtk.ProgressBar
import_statuspage = None import_statuspage: Adw.StatusPage
import_dialog = None import_dialog: Adw.MessageDialog
summary_toast = None summary_toast: Adw.Toast
sources: set[Source] = None sources: set[Source]
n_source_tasks_created: int = 0 n_source_tasks_created: int = 0
n_source_tasks_done: int = 0 n_source_tasks_done: int = 0
n_pipelines_done: int = 0 n_pipelines_done: int = 0
game_pipelines: set[Pipeline] = None game_pipelines: set[Pipeline]
removed_game_ids: set[str] = set() removed_game_ids: set[str] = set()
imported_game_ids: set[str] = set() imported_game_ids: set[str] = set()
def __init__(self): def __init__(self) -> None:
super().__init__() super().__init__()
# TODO: make this stateful # TODO: make this stateful
@@ -63,23 +64,23 @@ class Importer(ErrorProducer):
self.sources = set() self.sources = set()
@property @property
def n_games_added(self): def n_games_added(self) -> int:
return sum( return sum(
1 if not (pipeline.game.blacklisted or pipeline.game.removed) else 0 1 if not (pipeline.game.blacklisted or pipeline.game.removed) else 0
for pipeline in self.game_pipelines for pipeline in self.game_pipelines
) )
@property @property
def pipelines_progress(self): def pipelines_progress(self) -> float:
progress = sum(pipeline.progress for pipeline in self.game_pipelines) progress = sum(pipeline.progress for pipeline in self.game_pipelines)
try: try:
progress = progress / len(self.game_pipelines) progress = progress / len(self.game_pipelines)
except ZeroDivisionError: except ZeroDivisionError:
progress = 0 progress = 0
return progress return progress # type: ignore
@property @property
def sources_progress(self): def sources_progress(self) -> float:
try: try:
progress = self.n_source_tasks_done / self.n_source_tasks_created progress = self.n_source_tasks_done / self.n_source_tasks_created
except ZeroDivisionError: except ZeroDivisionError:
@@ -87,16 +88,16 @@ class Importer(ErrorProducer):
return progress return progress
@property @property
def finished(self): def finished(self) -> bool:
return ( return (
self.n_source_tasks_created == self.n_source_tasks_done self.n_source_tasks_created == self.n_source_tasks_done
and len(self.game_pipelines) == self.n_pipelines_done and len(self.game_pipelines) == self.n_pipelines_done
) )
def add_source(self, source): def add_source(self, source: Source) -> None:
self.sources.add(source) self.sources.add(source)
def run(self): def run(self) -> None:
"""Use several Gio.Task to import games from added sources""" """Use several Gio.Task to import games from added sources"""
shared.win.get_application().lookup_action("import").set_enabled(False) shared.win.get_application().lookup_action("import").set_enabled(False)
@@ -121,7 +122,7 @@ class Importer(ErrorProducer):
self.progress_changed_callback() self.progress_changed_callback()
def create_dialog(self): def create_dialog(self) -> None:
"""Create the import dialog""" """Create the import dialog"""
self.progressbar = Gtk.ProgressBar(margin_start=12, margin_end=12) self.progressbar = Gtk.ProgressBar(margin_start=12, margin_end=12)
self.import_statuspage = Adw.StatusPage( self.import_statuspage = Adw.StatusPage(
@@ -138,7 +139,9 @@ class Importer(ErrorProducer):
) )
self.import_dialog.present() self.import_dialog.present()
def source_task_thread_func(self, _task, _obj, data, _cancellable): def source_task_thread_func(
self, _task: Any, _obj: Any, data: tuple, _cancellable: Any
) -> None:
"""Source import task code""" """Source import task code"""
source: Source source: Source
@@ -192,27 +195,27 @@ class Importer(ErrorProducer):
pipeline.connect("advanced", self.pipeline_advanced_callback) pipeline.connect("advanced", self.pipeline_advanced_callback)
self.game_pipelines.add(pipeline) self.game_pipelines.add(pipeline)
def update_progressbar(self): def update_progressbar(self) -> None:
"""Update the progressbar to show the overall import progress""" """Update the progressbar to show the overall import progress"""
# Reserve 10% for the sources discovery, the rest is the pipelines # Reserve 10% for the sources discovery, the rest is the pipelines
self.progressbar.set_fraction( self.progressbar.set_fraction(
(0.1 * self.sources_progress) + (0.9 * self.pipelines_progress) (0.1 * self.sources_progress) + (0.9 * self.pipelines_progress)
) )
def source_callback(self, _obj, _result, data): def source_callback(self, _obj: Any, _result: Any, data: tuple) -> None:
"""Callback executed when a source is fully scanned""" """Callback executed when a source is fully scanned"""
source, *_rest = data source, *_rest = data
logging.debug("Import done for source %s", source.source_id) logging.debug("Import done for source %s", source.source_id)
self.n_source_tasks_done += 1 self.n_source_tasks_done += 1
self.progress_changed_callback() self.progress_changed_callback()
def pipeline_advanced_callback(self, pipeline: Pipeline): def pipeline_advanced_callback(self, pipeline: Pipeline) -> None:
"""Callback called when a pipeline for a game has advanced""" """Callback called when a pipeline for a game has advanced"""
if pipeline.is_done: if pipeline.is_done:
self.n_pipelines_done += 1 self.n_pipelines_done += 1
self.progress_changed_callback() self.progress_changed_callback()
def progress_changed_callback(self): def progress_changed_callback(self) -> None:
""" """
Callback called when the import process has progressed Callback called when the import process has progressed
@@ -225,7 +228,7 @@ class Importer(ErrorProducer):
if self.finished: if self.finished:
self.import_callback() self.import_callback()
def remove_games(self): def remove_games(self) -> None:
"""Set removed to True for missing games""" """Set removed to True for missing games"""
if not shared.schema.get_boolean("remove-missing"): if not shared.schema.get_boolean("remove-missing"):
return return
@@ -249,7 +252,7 @@ class Importer(ErrorProducer):
game.update() game.update()
self.removed_game_ids.add(game.game_id) self.removed_game_ids.add(game.game_id)
def import_callback(self): def import_callback(self) -> None:
"""Callback called when importing has finished""" """Callback called when importing has finished"""
logging.info("Import done") logging.info("Import done")
self.remove_games() self.remove_games()
@@ -261,11 +264,11 @@ class Importer(ErrorProducer):
self.create_error_dialog() self.create_error_dialog()
shared.win.get_application().lookup_action("import").set_enabled(True) shared.win.get_application().lookup_action("import").set_enabled(True)
def create_error_dialog(self): def create_error_dialog(self) -> None:
"""Dialog containing all errors raised by importers""" """Dialog containing all errors raised by importers"""
# Collect all errors that happened in the importer and the managers # Collect all errors that happened in the importer and the managers
errors: list[Exception] = [] errors = []
errors.extend(self.collect_errors()) errors.extend(self.collect_errors())
for manager in shared.store.managers.values(): for manager in shared.store.managers.values():
errors.extend(manager.collect_errors()) errors.extend(manager.collect_errors())
@@ -313,7 +316,7 @@ class Importer(ErrorProducer):
dialog.present() dialog.present()
def undo_import(self, *_args): def undo_import(self, *_args: Any) -> None:
for game_id in self.imported_game_ids: for game_id in self.imported_game_ids:
shared.store[game_id].removed = True shared.store[game_id].removed = True
shared.store[game_id].update() shared.store[game_id].update()
@@ -330,7 +333,7 @@ class Importer(ErrorProducer):
logging.info("Import undone") logging.info("Import undone")
def create_summary_toast(self): def create_summary_toast(self) -> Adw.Toast:
"""N games imported, removed toast""" """N games imported, removed toast"""
toast = Adw.Toast(timeout=0, priority=Adw.ToastPriority.HIGH) toast = Adw.Toast(timeout=0, priority=Adw.ToastPriority.HIGH)
@@ -371,16 +374,20 @@ class Importer(ErrorProducer):
shared.win.toast_overlay.add_toast(toast) shared.win.toast_overlay.add_toast(toast)
return toast return toast
def open_preferences(self, page=None, expander_row=None): def open_preferences(
self,
page_name: Optional[str] = None,
expander_row: Optional[Adw.ExpanderRow] = None,
) -> Adw.PreferencesWindow:
return shared.win.get_application().on_preferences_action( return shared.win.get_application().on_preferences_action(
page_name=page, expander_row=expander_row page_name=page_name, expander_row=expander_row
) )
def timeout_toast(self, *_args): def timeout_toast(self, *_args: Any) -> None:
"""Manually timeout the toast after the user has dismissed all warnings""" """Manually timeout the toast after the user has dismissed all warnings"""
GLib.timeout_add_seconds(5, self.summary_toast.dismiss) GLib.timeout_add_seconds(5, self.summary_toast.dismiss)
def dialog_response_callback(self, _widget, response, *args): def dialog_response_callback(self, _widget: Any, response: str, *args: Any) -> None:
"""Handle after-import dialogs callback""" """Handle after-import dialogs callback"""
logging.debug("After-import dialog response: %s (%s)", response, str(args)) logging.debug("After-import dialog response: %s (%s)", response, str(args))
if response == "open_preferences": if response == "open_preferences":

View File

@@ -1,7 +1,7 @@
import logging import logging
from os import PathLike from os import PathLike
from pathlib import Path from pathlib import Path
from typing import Iterable, Mapping, NamedTuple from typing import Iterable, Mapping, NamedTuple, Optional
from src import shared from src import shared
@@ -41,7 +41,7 @@ class Location:
paths: Mapping[str, LocationSubPath] paths: Mapping[str, LocationSubPath]
invalid_subtitle: str invalid_subtitle: str
root: Path = None root: Optional[Path] = None
def __init__( def __init__(
self, self,
@@ -94,7 +94,9 @@ class Location:
shared.schema.set_string(self.schema_key, value) shared.schema.set_string(self.schema_key, value)
logging.debug("Resolved value for schema key %s: %s", self.schema_key, value) logging.debug("Resolved value for schema key %s: %s", self.schema_key, value)
def __getitem__(self, key: str): def __getitem__(self, key: str) -> Optional[Path]:
"""Get the computed path from its key for the location""" """Get the computed path from its key for the location"""
self.resolve() self.resolve()
return self.root / self.paths[key].segment if self.root:
return self.root / self.paths[key].segment
return None

View File

@@ -20,19 +20,19 @@
import sys import sys
from abc import abstractmethod from abc import abstractmethod
from collections.abc import Iterable from collections.abc import Iterable
from typing import Any, Collection, Generator from typing import Any, Collection, Generator, Optional
from src.game import Game from src.game import Game
from src.importer.sources.location import Location from src.importer.sources.location import Location
# Type of the data returned by iterating on a Source # Type of the data returned by iterating on a Source
SourceIterationResult = None | Game | tuple[Game, tuple[Any]] SourceIterationResult = Optional[Game | tuple[Game, tuple[Any]]]
class SourceIterable(Iterable): class SourceIterable(Iterable):
"""Data producer for a source of games""" """Data producer for a source of games"""
source: "Source" = None source: "Source"
def __init__(self, source: "Source") -> None: def __init__(self, source: "Source") -> None:
self.source = source self.source = source
@@ -53,7 +53,7 @@ class Source(Iterable):
source_id: str source_id: str
name: str name: str
variant: str = None variant: Optional[str] = None
available_on: set[str] = set() available_on: set[str] = set()
iterable_class: type[SourceIterable] iterable_class: type[SourceIterable]
@@ -65,7 +65,7 @@ class Source(Iterable):
def full_name(self) -> str: def full_name(self) -> str:
"""The source's full name""" """The source's full name"""
full_name_ = self.name full_name_ = self.name
if self.variant is not None: if self.variant:
full_name_ += f" ({self.variant})" full_name_ += f" ({self.variant})"
return full_name_ return full_name_
@@ -75,7 +75,7 @@ class Source(Iterable):
return self.source_id + "_{game_id}" return self.source_id + "_{game_id}"
@property @property
def is_available(self): def is_available(self) -> bool:
return sys.platform in self.available_on return sys.platform in self.available_on
@property @property

View File

@@ -29,7 +29,7 @@ class ColorLogFormatter(Formatter):
RED = "\033[31m" RED = "\033[31m"
YELLOW = "\033[33m" YELLOW = "\033[33m"
def format(self, record: LogRecord): def format(self, record: LogRecord) -> str:
super_format = super().format(record) super_format = super().format(record)
match record.levelname: match record.levelname:
case "CRITICAL": case "CRITICAL":

View File

@@ -18,11 +18,12 @@
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
import lzma import lzma
from io import StringIO from io import TextIOWrapper
from logging import StreamHandler from logging import StreamHandler
from lzma import FORMAT_XZ, PRESET_DEFAULT from lzma import FORMAT_XZ, PRESET_DEFAULT
from os import PathLike from os import PathLike
from pathlib import Path from pathlib import Path
from typing import Optional
from src import shared from src import shared
@@ -37,7 +38,7 @@ class SessionFileHandler(StreamHandler):
backup_count: int backup_count: int
filename: Path filename: Path
log_file: StringIO = None log_file: Optional[TextIOWrapper] = None
def create_dir(self) -> None: def create_dir(self) -> None:
"""Create the log dir if needed""" """Create the log dir if needed"""
@@ -83,7 +84,7 @@ class SessionFileHandler(StreamHandler):
logfiles.sort(key=self.file_sort_key, reverse=True) logfiles.sort(key=self.file_sort_key, reverse=True)
return logfiles return logfiles
def rotate_file(self, path: Path): def rotate_file(self, path: Path) -> None:
"""Rotate a file's number suffix and remove it if it's too old""" """Rotate a file's number suffix and remove it if it's too old"""
# If uncompressed, compress # If uncompressed, compress
@@ -128,5 +129,6 @@ class SessionFileHandler(StreamHandler):
super().__init__(self.log_file) super().__init__(self.log_file)
def close(self) -> None: def close(self) -> None:
self.log_file.close() if self.log_file:
self.log_file.close()
super().close() super().close()

View File

@@ -27,7 +27,7 @@ import sys
from src import shared from src import shared
def setup_logging(): def setup_logging() -> None:
"""Intitate the app's logging""" """Intitate the app's logging"""
is_dev = shared.PROFILE == "development" is_dev = shared.PROFILE == "development"
@@ -89,7 +89,7 @@ def setup_logging():
logging_dot_config.dictConfig(config) logging_dot_config.dictConfig(config)
def log_system_info(): def log_system_info() -> None:
"""Log system debug information""" """Log system debug information"""
logging.debug("Starting %s v%s (%s)", shared.APP_ID, shared.VERSION, shared.PROFILE) logging.debug("Starting %s v%s (%s)", shared.APP_ID, shared.VERSION, shared.PROFILE)

View File

@@ -21,7 +21,7 @@ import json
import lzma import lzma
import os import os
import sys import sys
from typing import Any from typing import Any, Optional
import gi import gi
@@ -199,8 +199,8 @@ class CartridgesApplication(Adw.Application):
self, self,
_action: Any = None, _action: Any = None,
_parameter: Any = None, _parameter: Any = None,
page_name: (str | None) = None, page_name: Optional[str] = None,
expander_row: (str | None) = None, expander_row: Optional[str] = None,
) -> CartridgesWindow: ) -> CartridgesWindow:
win = PreferencesWindow() win = PreferencesWindow()
if page_name: if page_name:

View File

@@ -21,7 +21,7 @@ import logging
import re import re
from pathlib import Path from pathlib import Path
from shutil import rmtree from shutil import rmtree
from typing import Any, Callable from typing import Any, Callable, Optional
from gi.repository import Adw, Gio, GLib, Gtk from gi.repository import Adw, Gio, GLib, Gtk
@@ -215,7 +215,7 @@ class PreferencesWindow(Adw.PreferencesWindow):
) )
def choose_folder( def choose_folder(
self, _widget: Any, callback: Callable, callback_data: str | None = None self, _widget: Any, callback: Callable, callback_data: Optional[str] = None
) -> None: ) -> None:
self.file_chooser.select_folder(self.win, None, callback, callback_data) self.file_chooser.select_folder(self.win, None, callback, callback_data)

View File

@@ -46,7 +46,7 @@ class Manager(ErrorProducer):
max_tries: int = 3 max_tries: int = 3
@property @property
def name(self): def name(self) -> str:
return type(self).__name__ return type(self).__name__
@abstractmethod @abstractmethod
@@ -59,13 +59,13 @@ class Manager(ErrorProducer):
* May raise other exceptions that will be reported * May raise other exceptions that will be reported
""" """
def run(self, game: Game, additional_data: dict): def run(self, game: Game, additional_data: dict) -> None:
"""Handle errors (retry, ignore or raise) that occur in the manager logic""" """Handle errors (retry, ignore or raise) that occur in the manager logic"""
# Keep track of the number of tries # Keep track of the number of tries
tries = 1 tries = 1
def handle_error(error: Exception): def handle_error(error: Exception) -> None:
nonlocal tries nonlocal tries
# If FriendlyError, handle its cause instead # If FriendlyError, handle its cause instead
@@ -83,11 +83,11 @@ class Manager(ErrorProducer):
retrying_format = "Retrying %s in %s for %s" retrying_format = "Retrying %s in %s for %s"
unretryable_format = "Unretryable %s in %s for %s" unretryable_format = "Unretryable %s in %s for %s"
if error in self.continue_on: if type(error) in self.continue_on:
# Handle skippable errors (skip silently) # Handle skippable errors (skip silently)
return return
if error in self.retryable_on: if type(error) in self.retryable_on:
if tries > self.max_tries: if tries > self.max_tries:
# Handle being out of retries # Handle being out of retries
logging.error(out_of_retries_format, *log_args) logging.error(out_of_retries_format, *log_args)
@@ -104,7 +104,7 @@ class Manager(ErrorProducer):
logging.error(unretryable_format, *log_args, exc_info=error) logging.error(unretryable_format, *log_args, exc_info=error)
self.report_error(base_error) self.report_error(base_error)
def try_manager_logic(): def try_manager_logic() -> None:
try: try:
self.main(game, additional_data) self.main(game, additional_data)
except Exception as error: # pylint: disable=broad-exception-caught except Exception as error: # pylint: disable=broad-exception-caught

View File

@@ -83,7 +83,7 @@ class Pipeline(GObject.Object):
progress = 1 progress = 1
return progress return progress
def advance(self): def advance(self) -> None:
"""Spawn tasks for managers that are able to run for a game""" """Spawn tasks for managers that are able to run for a game"""
# Separate blocking / async managers # Separate blocking / async managers

View File

@@ -18,7 +18,7 @@
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
import logging import logging
from typing import Any, Generator, MutableMapping from typing import Any, Generator, MutableMapping, Optional
from src import shared from src import shared
from src.game import Game from src.game import Game
@@ -77,13 +77,15 @@ class Store:
except KeyError: except KeyError:
return default return default
def add_manager(self, manager: Manager, in_pipeline=True): def add_manager(self, manager: Manager, in_pipeline: bool = True) -> None:
"""Add a manager to the store""" """Add a manager to the store"""
manager_type = type(manager) manager_type = type(manager)
self.managers[manager_type] = manager self.managers[manager_type] = manager
self.toggle_manager_in_pipelines(manager_type, in_pipeline) self.toggle_manager_in_pipelines(manager_type, in_pipeline)
def toggle_manager_in_pipelines(self, manager_type: type[Manager], enable: bool): def toggle_manager_in_pipelines(
self, manager_type: type[Manager], enable: bool
) -> None:
"""Change if a manager should run in new pipelines""" """Change if a manager should run in new pipelines"""
if enable: if enable:
self.pipeline_managers.add(self.managers[manager_type]) self.pipeline_managers.add(self.managers[manager_type])
@@ -108,8 +110,8 @@ class Store:
pass pass
def add_game( def add_game(
self, game: Game, additional_data: dict, run_pipeline=True self, game: Game, additional_data: dict, run_pipeline: bool = True
) -> Pipeline | None: ) -> Optional[Pipeline]:
"""Add a game to the app""" """Add a game to the app"""
# Ignore games from a newer spec version # Ignore games from a newer spec version

View File

@@ -17,7 +17,7 @@
# #
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
from typing import Any from typing import Any, Optional
from gi.repository import Adw, Gio, GLib, Gtk from gi.repository import Adw, Gio, GLib, Gtk
@@ -71,7 +71,7 @@ class CartridgesWindow(Adw.ApplicationWindow):
game_covers: dict = {} game_covers: dict = {}
toasts: dict = {} toasts: dict = {}
active_game: Game active_game: Game
details_view_game_cover: GameCover | None = None details_view_game_cover: Optional[GameCover] = None
sort_state: str = "a-z" sort_state: str = "a-z"
def __init__(self, **kwargs: Any) -> None: def __init__(self, **kwargs: Any) -> None:
@@ -334,7 +334,7 @@ class CartridgesWindow(Adw.ApplicationWindow):
index += 1 index += 1
def on_undo_action( def on_undo_action(
self, _widget: Any, game: Game | None = None, undo: str | None = None self, _widget: Any, game: Optional[Game] = None, undo: Optional[str] = None
) -> None: ) -> None:
if not game: # If the action was activated via Ctrl + Z if not game: # If the action was activated via Ctrl + Z
if shared.importer and ( if shared.importer and (