sources: Remove skip_ids

This commit is contained in:
kramo
2025-12-22 21:29:32 +01:00
parent 84972c06d6
commit d85c6e0530
7 changed files with 77 additions and 66 deletions

View File

@@ -2,15 +2,12 @@
# SPDX-FileCopyrightText: Copyright 2025 Zoey Ahmed # SPDX-FileCopyrightText: Copyright 2025 Zoey Ahmed
# SPDX-FileCopyrightText: Copyright 2025 kramo # SPDX-FileCopyrightText: Copyright 2025 kramo
from collections.abc import Generator, Iterable
from gettext import gettext as _ from gettext import gettext as _
from typing import override from typing import override
from gi.repository import Adw from gi.repository import Adw
from cartridges import collections, games from cartridges import collections, games
from cartridges.games import Game
from cartridges.sources import Source, heroic, steam
from .config import APP_ID, PREFIX from .config import APP_ID, PREFIX
from .ui.window import Window from .ui.window import Window
@@ -28,20 +25,9 @@ class Application(Adw.Application):
)) ))
self.set_accels_for_action("app.quit", ("<Control>q",)) self.set_accels_for_action("app.quit", ("<Control>q",))
saved = tuple(games.load()) games.load()
new = self.import_games(steam, heroic, skip_ids={g.game_id for g in saved})
games.model.splice(0, 0, (*saved, *new))
collections.load() collections.load()
@staticmethod
def import_games(*sources: Source, skip_ids: Iterable[str]) -> Generator[Game]:
"""Import games from `sources`, skipping ones in `skip_ids`."""
for source in sources:
try:
yield from source.get_games(skip_ids=skip_ids)
except OSError:
continue
@override @override
def do_startup(self): def do_startup(self):
Adw.Application.do_startup(self) Adw.Application.do_startup(self)

View File

@@ -7,17 +7,16 @@ import itertools
import json import json
import os import os
import subprocess import subprocess
from collections.abc import Callable, Generator, Iterable from collections.abc import Callable, Iterable
from gettext import gettext as _ from gettext import gettext as _
from json import JSONDecodeError
from pathlib import Path from pathlib import Path
from shlex import quote from shlex import quote
from types import UnionType from types import UnionType
from typing import TYPE_CHECKING, Any, NamedTuple, Self, cast from typing import TYPE_CHECKING, Any, NamedTuple, Self, cast
from gi.repository import Gdk, Gio, GLib, GObject from gi.repository import Gdk, Gio, GObject
from cartridges import DATA_DIR from . import DATA_DIR
if TYPE_CHECKING: if TYPE_CHECKING:
from .application import Application from .application import Application
@@ -45,8 +44,8 @@ PROPERTIES: tuple[_GameProp, ...] = (
_GameProp("version", float), _GameProp("version", float),
) )
_GAMES_DIR = DATA_DIR / "games" GAMES_DIR = DATA_DIR / "games"
_COVERS_DIR = DATA_DIR / "covers" COVERS_DIR = DATA_DIR / "covers"
_SPEC_VERSION = 2.0 _SPEC_VERSION = 2.0
_MANUALLY_ADDED_ID = "imported" _MANUALLY_ADDED_ID = "imported"
@@ -150,8 +149,8 @@ class Game(Gio.SimpleActionGroup):
"""Save the game's properties to disk.""" """Save the game's properties to disk."""
properties = {prop.name: getattr(self, prop.name) for prop in PROPERTIES} properties = {prop.name: getattr(self, prop.name) for prop in PROPERTIES}
_GAMES_DIR.mkdir(parents=True, exist_ok=True) GAMES_DIR.mkdir(parents=True, exist_ok=True)
path = (_GAMES_DIR / self.game_id).with_suffix(".json") path = (GAMES_DIR / self.game_id).with_suffix(".json")
with path.open(encoding="utf-8") as f: with path.open(encoding="utf-8") as f:
json.dump(properties, f, indent=4) json.dump(properties, f, indent=4)
@@ -182,6 +181,13 @@ class Game(Gio.SimpleActionGroup):
window.send_toast(title, undo=undo) window.send_toast(title, undo=undo)
def load():
"""Populate `games.model` with all games from all sources."""
from . import sources
model.splice(0, 0, tuple(sources.get_games()))
def _increment_manually_added_id() -> int: def _increment_manually_added_id() -> int:
numbers = { numbers = {
game.game_id.split("_")[1] game.game_id.split("_")[1]
@@ -196,31 +202,4 @@ def _increment_manually_added_id() -> int:
raise ValueError raise ValueError
def load() -> Generator[Game]:
"""Load previously saved games from disk."""
for path in _GAMES_DIR.glob("*.json"):
try:
with path.open(encoding="utf-8") as f:
data = json.load(f)
except (JSONDecodeError, UnicodeDecodeError):
continue
try:
game = Game.from_data(data)
except TypeError:
continue
cover_path = _COVERS_DIR / game.game_id
for ext in ".gif", ".tiff":
filename = str(cover_path.with_suffix(ext))
try:
game.cover = Gdk.Texture.new_from_filename(filename)
except GLib.Error:
continue
else:
break
yield game
model = Gio.ListStore.new(Game) model = Gio.ListStore.new(Game)

View File

@@ -3,7 +3,7 @@
import os import os
import sys import sys
from collections.abc import Generator, Iterable from collections.abc import Generator
from pathlib import Path from pathlib import Path
from typing import Final, Protocol from typing import Final, Protocol
@@ -44,6 +44,17 @@ class Source(Protocol):
NAME: Final[str] NAME: Final[str]
@staticmethod @staticmethod
def get_games(*, skip_ids: Iterable[str]) -> Generator[Game]: def get_games() -> Generator[Game]:
"""Installed games, except those in `skip_ids`.""" """Installed games."""
... ...
def get_games() -> Generator[Game]:
"""Installed games from all sources."""
from . import heroic, imported, steam
for source in heroic, imported, steam:
try:
yield from source.get_games()
except OSError:
continue

View File

@@ -111,11 +111,11 @@ class _NileSource(_StoreSource):
yield entry["id"] yield entry["id"]
def get_games(*, skip_ids: Iterable[str]) -> Generator[Game]: def get_games() -> Generator[Game]:
"""Installed Heroic games.""" """Installed Heroic games."""
added = int(time.time()) added = int(time.time())
for source in _LegendarySource, _GOGSource, _NileSource, _SideloadSource: for source in _LegendarySource, _GOGSource, _NileSource, _SideloadSource:
yield from _games_from(source, added, skip_ids) yield from _games_from(source, added)
def _config_dir() -> Path: def _config_dir() -> Path:
@@ -139,9 +139,7 @@ def _hidden_app_names() -> Generator[str]:
yield game["appName"] yield game["appName"]
def _games_from( def _games_from(source: type[_Source], added: int) -> Generator[Game]:
source: type[_Source], added: int, skip_ids: Iterable[str]
) -> Generator[Game]:
try: try:
with (_config_dir() / source.library_path()).open() as fp: with (_config_dir() / source.library_path()).open() as fp:
library = json.load(fp) library = json.load(fp)
@@ -164,10 +162,6 @@ def _games_from(
if (installed is not None) and (app_name not in installed): if (installed is not None) and (app_name not in installed):
continue continue
game_id = f"{source_id}_{app_name}"
if game_id in skip_ids:
continue
cover_uri = f"{entry.get('art_square', '')}{source.COVER_URI_PARAMS}" cover_uri = f"{entry.get('art_square', '')}{source.COVER_URI_PARAMS}"
cover_path = images_cache / sha256(cover_uri.encode()).hexdigest() cover_path = images_cache / sha256(cover_uri.encode()).hexdigest()
@@ -179,7 +173,7 @@ def _games_from(
yield Game( yield Game(
added=added, added=added,
executable=f"{OPEN} heroic://launch/{entry['runner']}/{app_name}", executable=f"{OPEN} heroic://launch/{entry['runner']}/{app_name}",
game_id=game_id, game_id=f"{source_id}_{app_name}",
source=source_id, source=source_id,
hidden=app_name in hidden, hidden=app_name in hidden,
name=entry["title"], name=entry["title"],

View File

@@ -0,0 +1,40 @@
# SPDX-License-Identifier: GPL-3.0-or-later
# SPDX-FileCopyrightText: Copyright 2025 kramo
import json
from collections.abc import Generator
from gettext import gettext as _
from json import JSONDecodeError
from gi.repository import Gdk, GLib
from cartridges.games import COVERS_DIR, GAMES_DIR, Game
ID, NAME = "imported", _("Added")
def get_games() -> Generator[Game]:
"""Manually added games."""
for path in GAMES_DIR.glob("imported_*.json"):
try:
with path.open(encoding="utf-8") as f:
data = json.load(f)
except (JSONDecodeError, UnicodeDecodeError):
continue
try:
game = Game.from_data(data)
except TypeError:
continue
cover_path = COVERS_DIR / game.game_id
for ext in ".gif", ".tiff":
filename = str(cover_path.with_suffix(ext))
try:
game.cover = Gdk.Texture.new_from_filename(filename)
except GLib.Error:
continue
else:
break
yield game

View File

@@ -8,7 +8,7 @@ import re
import struct import struct
import time import time
from collections import defaultdict from collections import defaultdict
from collections.abc import Generator, Iterable, Sequence from collections.abc import Generator, Sequence
from contextlib import suppress from contextlib import suppress
from gettext import gettext as _ from gettext import gettext as _
from os import SEEK_CUR from os import SEEK_CUR
@@ -107,7 +107,7 @@ class _AppInfo(NamedTuple):
return cls(common.get("type"), developer, capsule) return cls(common.get("type"), developer, capsule)
def get_games(*, skip_ids: Iterable[str]) -> Generator[Game]: def get_games() -> Generator[Game]:
"""Installed Steam games.""" """Installed Steam games."""
added = int(time.time()) added = int(time.time())
@@ -115,7 +115,7 @@ def get_games(*, skip_ids: Iterable[str]) -> Generator[Game]:
with (_data_dir() / "appcache" / "appinfo.vdf").open("rb") as fp: with (_data_dir() / "appcache" / "appinfo.vdf").open("rb") as fp:
appinfo = defaultdict(_AppInfo, _parse_appinfo_vdf(fp)) appinfo = defaultdict(_AppInfo, _parse_appinfo_vdf(fp))
appids = {i.rsplit("_", 1)[-1] for i in skip_ids if i.startswith(f"{ID}_")} appids = set()
for manifest in _manifests(): for manifest in _manifests():
try: try:
name, appid, stateflags, lastplayed = _App.from_manifest(manifest) name, appid, stateflags, lastplayed = _App.from_manifest(manifest)

View File

@@ -4,6 +4,7 @@ cartridges/gamepads.py
cartridges/games.py cartridges/games.py
cartridges/sources/__init__.py cartridges/sources/__init__.py
cartridges/sources/heroic.py cartridges/sources/heroic.py
cartridges/sources/imported.py
cartridges/sources/steam.py cartridges/sources/steam.py
cartridges/ui/collections.py cartridges/ui/collections.py
cartridges/ui/cover.blp cartridges/ui/cover.blp