初始化奇妙小屏幕控制器项目

This commit is contained in:
2026-09-08 22:56:52 +08:00
commit 8d368de3b5
491 changed files with 67678 additions and 0 deletions
@@ -0,0 +1,138 @@
from __future__ import annotations
import errno
import hashlib
import json
import threading
from pathlib import Path
from typing import Any
from uuid import UUID
from app.persistence import atomic_write_bytes
LIBRARY_ORDER_SCHEMA_VERSION = 1
LIBRARY_ITEM_TYPES = frozenset({"template", "animation"})
LIBRARY_ORDER_FIELDS = frozenset({"schema_version", "items"})
LIBRARY_ITEM_FIELDS = frozenset({"type", "id"})
class LibraryOrderError(RuntimeError):
pass
class LibraryOrderValidationError(LibraryOrderError):
pass
class LibraryOrderConflictError(LibraryOrderError):
pass
class LibraryOrderStorageFullError(LibraryOrderError):
pass
def normalize_library_items(value: Any) -> list[dict[str, str]]:
if not isinstance(value, list):
raise LibraryOrderValidationError("library order items must be an array")
normalized: list[dict[str, str]] = []
seen: set[tuple[str, str]] = set()
for item in value:
if not isinstance(item, dict) or set(item) != LIBRARY_ITEM_FIELDS:
raise LibraryOrderValidationError("library order item fields are invalid")
item_type = item.get("type")
item_id = item.get("id")
if item_type not in LIBRARY_ITEM_TYPES:
raise LibraryOrderValidationError("library order item type is invalid")
if not isinstance(item_id, str):
raise LibraryOrderValidationError("library order item id must be a UUID")
try:
normalized_id = str(UUID(item_id))
except (ValueError, TypeError, AttributeError) as exc:
raise LibraryOrderValidationError("library order item id must be a UUID") from exc
if normalized_id != item_id:
raise LibraryOrderValidationError("library order item id must be a canonical UUID")
key = (item_type, normalized_id)
if key in seen:
raise LibraryOrderValidationError("library order items must be unique")
seen.add(key)
normalized.append({"type": item_type, "id": normalized_id})
return normalized
def library_order_revision(items: list[dict[str, str]]) -> str:
canonical = json.dumps(items, ensure_ascii=False, sort_keys=True, separators=(",", ":"))
return hashlib.sha256(canonical.encode("utf-8")).hexdigest()[:32]
class LibraryOrderStore:
def __init__(self, data_dir: Path, *, forbidden_ids: set[str] | None = None):
self.path = Path(data_dir) / "library" / "order.json"
self._forbidden_ids = set(forbidden_ids or ())
self._lock = threading.RLock()
if self.path.exists():
self._read_items()
@staticmethod
def _json_bytes(items: list[dict[str, str]]) -> bytes:
document = {
"schema_version": LIBRARY_ORDER_SCHEMA_VERSION,
"items": items,
}
return (json.dumps(document, ensure_ascii=False, sort_keys=True, separators=(",", ":")) + "\n").encode("utf-8")
def _read_items(self) -> list[dict[str, str]] | None:
if not self.path.exists():
return None
try:
document = json.loads(self.path.read_bytes().decode("utf-8"))
except (OSError, UnicodeError, json.JSONDecodeError) as exc:
raise LibraryOrderValidationError("library order file is unreadable") from exc
if not isinstance(document, dict) or set(document) != LIBRARY_ORDER_FIELDS:
raise LibraryOrderValidationError("library order fields do not match the current schema")
version = document.get("schema_version")
if type(version) is not int or version != LIBRARY_ORDER_SCHEMA_VERSION:
raise LibraryOrderValidationError("library order schema version is unsupported")
items = normalize_library_items(document.get("items"))
if any(item["id"] in self._forbidden_ids for item in items):
raise LibraryOrderValidationError("demo items cannot be included in library order")
return items
@staticmethod
def _resolve_items(
stored: list[dict[str, str]] | None,
defaults: list[dict[str, str]],
) -> list[dict[str, str]]:
current = {(item["type"], item["id"]) for item in defaults}
resolved = [item for item in (stored or []) if (item["type"], item["id"]) in current]
included = {(item["type"], item["id"]) for item in resolved}
resolved.extend(item for item in defaults if (item["type"], item["id"]) not in included)
return resolved
def get(self, default_items: Any) -> dict[str, Any]:
defaults = normalize_library_items(default_items)
with self._lock:
items = self._resolve_items(self._read_items(), defaults)
return {"items": items, "revision": library_order_revision(items)}
def update(self, items: Any, default_items: Any, expected_revision: str) -> dict[str, Any]:
requested = normalize_library_items(items)
if any(item["id"] in self._forbidden_ids for item in requested):
raise LibraryOrderValidationError("demo items cannot be included in library order")
defaults = normalize_library_items(default_items)
with self._lock:
current = self._resolve_items(self._read_items(), defaults)
if library_order_revision(current) != expected_revision:
raise LibraryOrderConflictError("library order was changed by another client")
requested_set = {(item["type"], item["id"]) for item in requested}
current_set = {(item["type"], item["id"]) for item in defaults}
if requested_set != current_set:
raise LibraryOrderConflictError("library contents changed; refresh before sorting")
try:
atomic_write_bytes(self.path, self._json_bytes(requested))
except OSError as exc:
if exc.errno in {errno.ENOSPC, getattr(errno, "EDQUOT", errno.ENOSPC)}:
raise LibraryOrderStorageFullError("not enough disk space to save library order") from exc
raise
return {"items": requested, "revision": library_order_revision(requested)}