mirror of
https://github.com/Andre-Bzk/Rezept0r.git
synced 2026-11-15 15:43:04 +01:00
fix: Rezeptverlauf-Bilder lokal speichern und Windows-Subprocess-Fix
- Bilder beim Extrahieren lokal cachen (data/images/{id}.ext) und über GET /api/history/{id}/image ausliefern; Löschen bei Verlaufseintrag-Entfernung
- history.db in ./data/ Volume verschoben, damit sie Container-Rebuilds überlebt
- yt-dlp Subprocess-Aufrufe auf Windows gefixt: run_in_executor statt asyncio.create_subprocess_exec (SelectorEventLoop-Kompatibilität)
- ./data:/app/data Docker-Volume für persistente Datenhaltung hinzugefügt
This commit is contained in:
@@ -11,7 +11,7 @@ from app.config import get_settings
|
||||
|
||||
def _db_path() -> Path:
|
||||
settings = get_settings()
|
||||
return Path(settings.tmp_dir).parent / "history.db"
|
||||
return Path(settings.images_dir).parent / "history.db"
|
||||
|
||||
|
||||
def init_db() -> None:
|
||||
@@ -73,6 +73,15 @@ def delete_recipe(history_id: int) -> None:
|
||||
conn.commit()
|
||||
|
||||
|
||||
def update_image_url(history_id: int, image_url: str) -> None:
|
||||
with sqlite3.connect(_db_path()) as conn:
|
||||
conn.execute(
|
||||
"UPDATE recipe_history SET image_url = ? WHERE id = ?",
|
||||
(image_url, history_id),
|
||||
)
|
||||
conn.commit()
|
||||
|
||||
|
||||
def mark_imported(history_id: int, tandoor_recipe_id: Optional[int]) -> None:
|
||||
now = datetime.now(timezone.utc).isoformat()
|
||||
with sqlite3.connect(_db_path()) as conn:
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
"""Local image caching for recipe history."""
|
||||
from __future__ import annotations
|
||||
import logging
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
import httpx
|
||||
|
||||
from app.config import get_settings
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
MIME_TO_EXT = {
|
||||
"image/jpeg": "jpg",
|
||||
"image/png": "png",
|
||||
"image/webp": "webp",
|
||||
"image/gif": "gif",
|
||||
}
|
||||
|
||||
|
||||
def images_dir() -> Path:
|
||||
p = Path(get_settings().images_dir)
|
||||
p.mkdir(parents=True, exist_ok=True)
|
||||
return p
|
||||
|
||||
|
||||
def find_cached_image(history_id: int) -> Optional[Path]:
|
||||
"""Return the cached image Path for a history entry, or None."""
|
||||
for ext in MIME_TO_EXT.values():
|
||||
p = images_dir() / f"{history_id}.{ext}"
|
||||
if p.exists():
|
||||
return p
|
||||
return None
|
||||
|
||||
|
||||
async def download_and_cache(history_id: int, remote_url: str) -> Optional[str]:
|
||||
"""Download image from remote_url, save locally, return local API path or None on failure."""
|
||||
try:
|
||||
async with httpx.AsyncClient(timeout=15) as client:
|
||||
resp = await client.get(remote_url, follow_redirects=True)
|
||||
resp.raise_for_status()
|
||||
ct = resp.headers.get("content-type", "image/jpeg").split(";")[0].strip()
|
||||
ext = MIME_TO_EXT.get(ct, "jpg")
|
||||
dest = images_dir() / f"{history_id}.{ext}"
|
||||
dest.write_bytes(resp.content)
|
||||
log.info("Cached image for history %d → %s", history_id, dest)
|
||||
return f"/api/history/{history_id}/image"
|
||||
except Exception as exc:
|
||||
log.warning("Image cache failed for history %d: %s", history_id, exc)
|
||||
return None
|
||||
Reference in New Issue
Block a user