#!/usr/bin/env python3 """ Session state store — persistent playback state. Stored as JSON in ``~/.local/share/neo-pod-desktop/session.json``. Replaces the ``[Playback]`` section of config.ini — session state (user's last track, position, volume, playback mode) is user data, not configuration. """ import json import logging import os from typing import Any from xdg_base import xdg_data_path logger = logging.getLogger(__name__) _SESSION_FILE = "session.json" class SessionStore: def __init__(self, path: str | None = None): self._path = path or os.path.join(xdg_data_path(), _SESSION_FILE) self._data: dict[str, Any] = {} self._load() # ── load / save ──────────────────────────────────────────────────── def _load(self) -> None: if not os.path.exists(self._path): return try: with open(self._path, "r", encoding="utf-8") as f: self._data = json.load(f) except (json.JSONDecodeError, OSError) as exc: logger.warning("Failed to load session: %s", exc) self._data = {} def save(self) -> None: tmp = self._path + ".tmp" try: os.makedirs(os.path.dirname(self._path), exist_ok=True) with open(tmp, "w", encoding="utf-8") as f: json.dump(self._data, f, ensure_ascii=False) os.replace(tmp, self._path) except OSError as exc: logger.warning("Failed to save session: %s", exc) # ── typed accessors ──────────────────────────────────────────────── def get_str(self, key: str, fallback: str = "") -> str: return str(self._data.get(key, fallback)) def get_int(self, key: str, fallback: int = 0) -> int: return int(self._data.get(key, fallback)) def get_bool(self, key: str, fallback: bool = False) -> bool: v = self._data.get(key, fallback) if isinstance(v, bool): return v return str(v).lower() in ("true", "1", "yes") def set(self, key: str, value: Any) -> None: self._data[key] = value # ── migrations ───────────────────────────────────────────────────── def migrate_from_config(self, config_loader: Any) -> None: """Import ``[Playback]`` section from the old config loader.""" keys = [ "last_track_path", "last_position_ms", "last_volume", "last_playing", "shuffle_active", "repeat_mode", ] changed = False for k in keys: if k not in self._data: v = config_loader.get("Playback", k, fallback="") self.set(k, v) changed = True if changed: self.save() # Wipe old Playback section so it isn't read again. if config_loader.config.has_section("Playback"): config_loader.config.remove_section("Playback") config_loader.save() logger.info("Migrated playback state from config.ini to session.json")