import hashlib import re import threading from pathlib import Path from server.services.http import http CACHE_DIR = Path(__file__).resolve().parents[2] / "cache" / "covers" _lock = threading.Lock() class CoverCache: def local_url(self, remote: str) -> str: remote = (remote or "").strip() if not remote.startswith(("http://", "https://")): return remote digest = hashlib.sha256(remote.encode("utf-8")).hexdigest() CACHE_DIR.mkdir(parents=True, exist_ok=True) meta = CACHE_DIR / f"{digest}.url" if not meta.is_file(): meta.write_text(remote, encoding="utf-8") return f"/api/covers/{digest}" def read(self, digest: str) -> tuple[bytes, str]: if not re.fullmatch(r"[0-9a-f]{64}", digest): raise RuntimeError("封面不存在") meta = CACHE_DIR / f"{digest}.url" body = CACHE_DIR / digest if not meta.is_file(): raise RuntimeError("封面不存在") if body.is_file() and body.stat().st_size > 0: return body.read_bytes(), _media_type(body) remote = meta.read_text(encoding="utf-8").strip() data, media = http.get_bytes(remote) with _lock: CACHE_DIR.mkdir(parents=True, exist_ok=True) body.write_bytes(data) (CACHE_DIR / f"{digest}.type").write_text(media, encoding="utf-8") return data, media def _media_type(path: Path) -> str: sidecar = Path(f"{path}.type") if sidecar.is_file(): media = sidecar.read_text(encoding="utf-8").strip() if media.startswith("image/"): return media return "image/jpeg" covers = CoverCache()