games: Use Python's JSON module
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
# SPDX-FileCopyrightText: Copyright 2025 Zoey Ahmed
|
# SPDX-FileCopyrightText: Copyright 2025 Zoey Ahmed
|
||||||
|
# SPDX-FileCopyrightText: Copyright 2025 Jamie Gravendeel
|
||||||
|
|
||||||
import gettext
|
import gettext
|
||||||
import locale
|
import locale
|
||||||
@@ -12,13 +13,14 @@ import gi
|
|||||||
gi.require_versions({
|
gi.require_versions({
|
||||||
"Gtk": "4.0",
|
"Gtk": "4.0",
|
||||||
"Adw": "1",
|
"Adw": "1",
|
||||||
"Json": "1.0",
|
|
||||||
})
|
})
|
||||||
|
|
||||||
from gi.repository import Gio
|
from gi.repository import Gio, GLib
|
||||||
|
|
||||||
from .config import LOCALEDIR, PKGDATADIR
|
from .config import LOCALEDIR, PKGDATADIR
|
||||||
|
|
||||||
|
DATA_DIR = Path(GLib.get_user_data_dir(), "cartridges")
|
||||||
|
|
||||||
_RESOURCES = ("data", "icons", "ui")
|
_RESOURCES = ("data", "icons", "ui")
|
||||||
|
|
||||||
signal.signal(signal.SIGINT, signal.SIG_DFL)
|
signal.signal(signal.SIGINT, signal.SIG_DFL)
|
||||||
|
|||||||
@@ -1,22 +1,35 @@
|
|||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
# SPDX-FileCopyrightText: Copyright 2025 Zoey Ahmed
|
# SPDX-FileCopyrightText: Copyright 2025 Zoey Ahmed
|
||||||
# SPDX-FileCopyrightText: Copyright 2025 kramo
|
# SPDX-FileCopyrightText: Copyright 2025 kramo
|
||||||
|
# SPDX-FileCopyrightText: Copyright 2025 Jamie Gravendeel
|
||||||
|
|
||||||
|
import json
|
||||||
from collections.abc import Generator
|
from collections.abc import Generator
|
||||||
from pathlib import Path
|
from json import JSONDecodeError
|
||||||
from typing import cast
|
from types import UnionType
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
from gi.repository import (
|
from gi.repository import Gdk, Gio, GLib, GObject
|
||||||
Gdk,
|
|
||||||
Gio,
|
|
||||||
GLib,
|
|
||||||
GObject,
|
|
||||||
Json, # pyright: ignore[reportAttributeAccessIssue]
|
|
||||||
)
|
|
||||||
|
|
||||||
DATA_DIR = Path(GLib.get_user_data_dir(), "cartridges")
|
from cartridges import DATA_DIR
|
||||||
GAMES_DIR = DATA_DIR / "games"
|
|
||||||
COVERS_DIR = DATA_DIR / "covers"
|
_GAMES_DIR = DATA_DIR / "games"
|
||||||
|
_COVERS_DIR = DATA_DIR / "covers"
|
||||||
|
|
||||||
|
_SPEC_VERSION = 2.0
|
||||||
|
_PROPERTIES: dict[str, tuple[type | UnionType, bool]] = {
|
||||||
|
"added": (int, False),
|
||||||
|
"executable": (str | list[str], True),
|
||||||
|
"game_id": (str, True),
|
||||||
|
"source": (str, True),
|
||||||
|
"hidden": (bool, False),
|
||||||
|
"last_played": (int, False),
|
||||||
|
"name": (str, True),
|
||||||
|
"developer": (str, False),
|
||||||
|
"removed": (bool, False),
|
||||||
|
"blacklisted": (bool, False),
|
||||||
|
"version": (float, False),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class Game(GObject.Object):
|
class Game(GObject.Object):
|
||||||
@@ -29,30 +42,52 @@ class Game(GObject.Object):
|
|||||||
game_id = GObject.Property(type=str)
|
game_id = GObject.Property(type=str)
|
||||||
source = GObject.Property(type=str)
|
source = GObject.Property(type=str)
|
||||||
hidden = GObject.Property(type=bool, default=False)
|
hidden = GObject.Property(type=bool, default=False)
|
||||||
last_played = GObject.Property(type=int, default=0)
|
last_played = GObject.Property(type=int)
|
||||||
name = GObject.Property(type=str)
|
name = GObject.Property(type=str)
|
||||||
developer = GObject.Property(type=str)
|
developer = GObject.Property(type=str)
|
||||||
removed = GObject.Property(type=bool, default=False)
|
removed = GObject.Property(type=bool, default=False)
|
||||||
blacklisted = GObject.Property(type=bool, default=False)
|
blacklisted = GObject.Property(type=bool, default=False)
|
||||||
version = GObject.Property(type=float, default=2.0)
|
version = GObject.Property(type=float, default=_SPEC_VERSION)
|
||||||
|
|
||||||
cover = GObject.Property(type=Gdk.Texture)
|
cover = GObject.Property(type=Gdk.Texture)
|
||||||
|
|
||||||
|
def __init__(self, data: dict[str, Any]):
|
||||||
|
super().__init__()
|
||||||
|
|
||||||
def load() -> Generator[Game]:
|
for name, (type_, required) in _PROPERTIES.items():
|
||||||
"""Load the user's games from disk."""
|
value = data.get(name)
|
||||||
for path in GAMES_DIR.glob("*.json"):
|
|
||||||
|
if not required and value is None:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if not isinstance(value, type_):
|
||||||
|
raise TypeError
|
||||||
|
|
||||||
|
match name:
|
||||||
|
case "executable" if isinstance(value, list):
|
||||||
|
value = " ".join(value)
|
||||||
|
case "version" if value and value > _SPEC_VERSION:
|
||||||
|
raise TypeError
|
||||||
|
case "version":
|
||||||
|
continue
|
||||||
|
|
||||||
|
setattr(self, name, value)
|
||||||
|
|
||||||
|
|
||||||
|
def _load() -> Generator[Game]:
|
||||||
|
for path in _GAMES_DIR.glob("*.json"):
|
||||||
try:
|
try:
|
||||||
data = path.read_text("utf-8")
|
with path.open(encoding="utf-8") as f:
|
||||||
except UnicodeError:
|
data = json.load(f)
|
||||||
|
except (JSONDecodeError, UnicodeDecodeError):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
try:
|
try:
|
||||||
game = cast(Game, Json.gobject_from_data(Game, data, len(data)))
|
game = Game(data)
|
||||||
except GLib.Error:
|
except TypeError:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
cover_path = COVERS_DIR / game.game_id
|
cover_path = _COVERS_DIR / game.game_id
|
||||||
for ext in ".gif", ".tiff":
|
for ext in ".gif", ".tiff":
|
||||||
filename = str(cover_path.with_suffix(ext))
|
filename = str(cover_path.with_suffix(ext))
|
||||||
try:
|
try:
|
||||||
@@ -66,4 +101,4 @@ def load() -> Generator[Game]:
|
|||||||
|
|
||||||
|
|
||||||
model = Gio.ListStore.new(Game)
|
model = Gio.ListStore.new(Game)
|
||||||
model.splice(0, 0, tuple(load()))
|
model.splice(0, 0, tuple(_load()))
|
||||||
|
|||||||
Reference in New Issue
Block a user