Files
ImpTune/imptune/api/icons.py
T
2026-04-15 17:57:12 +02:00

89 lines
2.5 KiB
Python

"""Icon upload API — POST /printers/{printer_id}/icon."""
from __future__ import annotations
import hashlib
import io
from pathlib import Path
from fastapi import APIRouter, UploadFile
from fastapi.responses import HTMLResponse
from PIL import Image
import imptune.config as cfg
from imptune.db.models import Icon, Printer
router = APIRouter(prefix="/printers")
MAX_ICON_BYTES = 750 * 1024 # 750 KB
@router.post("/{printer_id}/icon", response_class=HTMLResponse)
def upload_icon(printer_id: int, file: UploadFile) -> HTMLResponse:
"""Accept a printer icon PNG, validate it, store it, and update the Icon record.
Validation rules:
- Format: PNG only
- Dimensions: exactly 256x256 pixels
- Size: at most 750 KB
Replaces any previously uploaded icon for this printer.
Returns an HTMX-friendly HTML fragment.
"""
# Check printer exists
printer = Printer.get_or_none(Printer.id == printer_id)
if printer is None:
return HTMLResponse(
content="<p>Printer not found.</p>",
status_code=404,
)
# Read file (read one byte extra to detect oversized files)
data = file.file.read(MAX_ICON_BYTES + 1)
if len(data) > MAX_ICON_BYTES:
return HTMLResponse(
content="<p>Icon exceeds 750 KB limit.</p>",
status_code=422,
)
# Validate with Pillow
try:
img = Image.open(io.BytesIO(data))
except Exception:
return HTMLResponse(
content="<p>Icon must be PNG format.</p>",
status_code=422,
)
if img.format != "PNG":
return HTMLResponse(
content="<p>Icon must be PNG format.</p>",
status_code=422,
)
if img.size != (256, 256):
return HTMLResponse(
content=f"<p>Icon must be 256x256 pixels, got {img.size}.</p>",
status_code=422,
)
# Store SHA256-addressed on disk
sha256 = hashlib.sha256(data).hexdigest()
icons_dir = Path(cfg.DATA_DIR) / "icons"
icons_dir.mkdir(parents=True, exist_ok=True)
icon_path = icons_dir / sha256
icon_path.write_bytes(data)
# Replace existing Icon record for this printer
Icon.delete().where(Icon.printer == printer_id).execute()
Icon.create(
printer=printer_id,
sha256=sha256,
original_filename=file.filename or "icon.png",
size_bytes=len(data),
)
return HTMLResponse(
content="<p>Icon uploaded successfully</p>",
status_code=200,
)