45 lines
1.7 KiB
Python
45 lines
1.7 KiB
Python
"""Covers are fetched once and kept under the config dir, so a dead link or a
|
|
slow host does not take the picture with it."""
|
|
import hashlib
|
|
import logging
|
|
import mimetypes
|
|
import tempfile
|
|
from pathlib import Path
|
|
|
|
import httpx
|
|
|
|
from . import config
|
|
|
|
MAX_BYTES = 5 * 1024 * 1024
|
|
log = logging.getLogger(__name__)
|
|
|
|
|
|
# shortcut: covers of deleted requests stay on disk, add a sweep if the dir grows
|
|
async def cached(url: str) -> Path | None:
|
|
"""The file for this url, downloaded on first use; None if it cannot be had."""
|
|
if not url.startswith(("http://", "https://")):
|
|
return None
|
|
folder = config.CONFIG_DIR / "covers"
|
|
name = hashlib.sha256(url.encode()).hexdigest()
|
|
if hit := next(folder.glob(name + ".*"), None):
|
|
return hit
|
|
try:
|
|
async with httpx.AsyncClient(timeout=15, follow_redirects=True) as client:
|
|
resp = await client.get(url)
|
|
resp.raise_for_status()
|
|
except httpx.HTTPError as exc:
|
|
log.warning("cover %s failed: %s", url, exc)
|
|
return None
|
|
ctype = resp.headers.get("content-type", "").split(";")[0].strip()
|
|
if not ctype.startswith("image/") or len(resp.content) > MAX_BYTES:
|
|
log.warning("cover %s is no usable image (%s, %d bytes)", url, ctype, len(resp.content))
|
|
return None
|
|
folder.mkdir(parents=True, exist_ok=True)
|
|
# written aside and renamed, so a parallel request never serves half a file
|
|
with tempfile.NamedTemporaryFile(dir=folder, suffix=".tmp", delete=False) as tmp:
|
|
tmp.write(resp.content)
|
|
path = folder / (name + (mimetypes.guess_extension(ctype) or ".img"))
|
|
Path(tmp.name).chmod(0o644) # NamedTemporaryFile creates 0600
|
|
Path(tmp.name).replace(path)
|
|
return path
|