"""Stockage local de la licence signée, hors dépôt. La licence est stockée dans l'espace utilisateur, jamais dans le dépôt Git : - Windows : ``%LOCALAPPDATA%/Aivanov/Anonymisation/license.json`` - Linux/dev : ``~/.local/share/aivanov/anonymisation/license.json`` Règle de sécurité : ce module ne journalise jamais le contenu de la licence (le token signé est sensible). Aucune impression, aucun log du payload. """ from __future__ import annotations import json import os import sys from pathlib import Path from typing import Any, Optional _APP_VENDOR = "Aivanov" _APP_NAME = "Anonymisation" _LICENSE_FILENAME = "license.json" def default_license_path() -> Path: """Retourne le chemin du fichier licence dans l'espace utilisateur. Le répertoire n'est pas créé ici ; il l'est à la première écriture. """ if sys.platform.startswith("win"): base = os.environ.get("LOCALAPPDATA") root = Path(base) if base else Path.home() / "AppData" / "Local" return root / _APP_VENDOR / _APP_NAME / _LICENSE_FILENAME # Linux / macOS / dev : respecte XDG_DATA_HOME si défini. base = os.environ.get("XDG_DATA_HOME") root = Path(base) if base else Path.home() / ".local" / "share" return root / _APP_VENDOR.lower() / _APP_NAME.lower() / _LICENSE_FILENAME class LicenseStore: """Lit / écrit / supprime le fichier licence local (JSON). Le chemin est injectable pour les tests (vrai fichier dans un tmp_path) ; par défaut il pointe vers l'emplacement utilisateur de la plateforme. """ def __init__(self, path: Optional[Path] = None) -> None: self._path = Path(path) if path is not None else default_license_path() @property def path(self) -> Path: return self._path def exists(self) -> bool: return self._path.is_file() def load(self) -> Optional[dict[str, Any]]: """Retourne le payload licence, ou ``None`` si absent / illisible.""" if not self._path.is_file(): return None try: with self._path.open("r", encoding="utf-8") as handle: data = json.load(handle) except (json.JSONDecodeError, OSError, ValueError): return None return data if isinstance(data, dict) else None def save(self, data: dict[str, Any]) -> Path: """Écrit le payload licence de façon atomique. Crée le répertoire parent.""" self._path.parent.mkdir(parents=True, exist_ok=True) tmp = self._path.with_name(self._path.name + ".tmp") with tmp.open("w", encoding="utf-8") as handle: json.dump(data, handle, ensure_ascii=False, indent=2) os.replace(tmp, self._path) return self._path def delete(self) -> bool: """Supprime le fichier licence. Retourne True s'il existait.""" try: self._path.unlink() return True except FileNotFoundError: return False