49 lines
1.6 KiB
Python
49 lines
1.6 KiB
Python
"""Regenerable BLE library previews, isolated from user-owned media."""
|
|
from __future__ import annotations
|
|
|
|
import hashlib
|
|
import re
|
|
from io import BytesIO
|
|
from pathlib import Path
|
|
|
|
from PIL import Image
|
|
|
|
from app.persistence import atomic_write_bytes
|
|
|
|
|
|
_PREVIEW_FILE = re.compile(r"[0-9a-f]{64}\.png\Z")
|
|
|
|
|
|
class PreviewCache:
|
|
def __init__(self, data_dir: Path):
|
|
self.root = Path(data_dir) / "mobile" / "previews"
|
|
self.root.mkdir(parents=True, exist_ok=True)
|
|
|
|
@staticmethod
|
|
def name(item: dict) -> str:
|
|
identity = "\0".join(str(item[key]) for key in ("type", "id", "revision"))
|
|
return hashlib.sha256(identity.encode("utf-8")).hexdigest() + ".png"
|
|
|
|
def reconcile(self, items: list[dict]) -> None:
|
|
expected = {self.name(item) for item in items}
|
|
for path in self.root.iterdir():
|
|
if path.is_file() and _PREVIEW_FILE.fullmatch(path.name) and path.name not in expected:
|
|
path.unlink(missing_ok=True)
|
|
|
|
def get(self, item: dict, image: Image.Image) -> bytes:
|
|
path = self.root / self.name(item)
|
|
if path.exists():
|
|
try:
|
|
data = path.read_bytes()
|
|
with Image.open(BytesIO(data)) as cached:
|
|
cached.verify()
|
|
if cached.format == "PNG" and cached.size == (16, 16):
|
|
return data
|
|
except (OSError, ValueError):
|
|
pass
|
|
output = BytesIO()
|
|
image.convert("RGB").resize((16, 16), Image.Resampling.LANCZOS).save(output, format="PNG", optimize=True)
|
|
data = output.getvalue()
|
|
atomic_write_bytes(path, data)
|
|
return data
|