sources: Remove skip_ids
This commit is contained in:
@@ -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)
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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"],
|
||||||
|
|||||||
40
cartridges/sources/imported.py
Normal file
40
cartridges/sources/imported.py
Normal 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
|
||||||
@@ -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)
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user