import shutil import time import json from pathlib import Path from typing import Any, Dict, List, Optional from backend.types import SessionRecord class SessionStore: def __init__(self, root: Path, ttl_seconds: int) -> None: self.root = Path(root) self.ttl_seconds = ttl_seconds self.root.mkdir(parents=True, exist_ok=True) self._sessions: Dict[str, SessionRecord] = {} def _now(self) -> float: return time.time() def ensure_session(self, session_id: str) -> SessionRecord: now = self._now() session_root = self.root / session_id session_root.mkdir(parents=True, exist_ok=True) for child in ("uploads", "previews", "renders", "exports"): (session_root / child).mkdir(exist_ok=True) existing = self._sessions.get(session_id) if existing is None: existing = SessionRecord( session_id=session_id, root=session_root, created_at=now, touched_at=now, ) self._sessions[session_id] = existing else: existing.touched_at = now return existing def touch(self, session_id: str) -> SessionRecord: session = self.ensure_session(session_id) session.touched_at = self._now() return session def save_json(self, session_id: str, name: str, payload: Dict[str, Any]) -> Path: session = self.touch(session_id) path = session.root / name path.write_text(json.dumps(payload, ensure_ascii=True, indent=2), encoding="utf-8") return path def load_json(self, session_id: str, name: str) -> Dict[str, Any]: session = self.touch(session_id) path = session.root / name if not path.exists(): raise FileNotFoundError(f"Session asset not found: {name}") return json.loads(path.read_text(encoding="utf-8")) def cleanup_expired(self, now: Optional[float] = None) -> List[str]: current = self._now() if now is None else now removed: List[str] = [] for session_id, record in list(self._sessions.items()): if current - record.touched_at <= self.ttl_seconds: continue if record.root.exists(): shutil.rmtree(record.root) removed.append(session_id) del self._sessions[session_id] return removed