fix(config): honor DB-overridable download layout + retention (env-vs-DB drift)

download_layout and download_retention_days are registered ConfigSpec keys
(admin-editable on the Configuration page), but worker.py read them from
settings.* (env) at download/edit finalize time — so an admin's edit was a
silent no-op (files kept the old layout; TTL stayed at the env default). Read
them via sysconfig (_download_settings), which falls back to the env default
when there's no DB override. Same class as the Downloads B3 fix.

ruff clean, localdev boots, re-review clean.
This commit is contained in:
npeter83 2026-07-11 19:44:53 +02:00
parent 31c1284eb7
commit 4e68bdf920

View file

@ -26,6 +26,7 @@ from pathlib import Path
import yt_dlp import yt_dlp
from sqlalchemy import text from sqlalchemy import text
from app import sysconfig
from app.config import settings from app.config import settings
from app.db import SessionLocal from app.db import SessionLocal
from app.downloads import edit as editmod from app.downloads import edit as editmod
@ -371,6 +372,16 @@ def _extract_with_fallback(job_id: int, asset_id: int, url: str, spec: dict, sta
raise last_exc # unreachable (loop either returns or raises) raise last_exc # unreachable (loop either returns or raises)
def _download_settings() -> tuple[str, int]:
"""Layout + retention days from the DB config (admin-overridable on the Configuration page),
falling back to the env defaults. Read here, not from `settings`, so an admin edit takes effect."""
with SessionLocal() as db:
return (
sysconfig.get_str(db, "download_layout"),
sysconfig.get_int(db, "download_retention_days"),
)
def _download(job_id: int, asset_id: int, source_kind: str, source_ref: str, spec: dict) -> None: def _download(job_id: int, asset_id: int, source_kind: str, source_ref: str, spec: dict) -> None:
staging = _staging_dir(asset_id) staging = _staging_dir(asset_id)
try: try:
@ -402,14 +413,15 @@ def _download(job_id: int, asset_id: int, source_kind: str, source_ref: str, spe
thumbnail_url=info.get("thumbnail"), thumbnail_url=info.get("thumbnail"),
) )
rel = storage.rel_path(meta, ext, settings.download_layout) layout, retention_days = _download_settings()
rel = storage.rel_path(meta, ext, layout)
thumb = _find_thumbnail(staging, produced) thumb = _find_thumbnail(staging, produced)
storage.place_file(produced, settings.download_root, rel) storage.place_file(produced, settings.download_root, rel)
nfo_ok = storage.write_sidecars(settings.download_root, rel, meta, thumb) nfo_ok = storage.write_sidecars(settings.download_root, rel, meta, thumb)
final = storage.abs_path(settings.download_root, rel) final = storage.abs_path(settings.download_root, rel)
size = final.stat().st_size if final.exists() else None size = final.stat().st_size if final.exists() else None
expires = datetime.now(timezone.utc) + timedelta(days=settings.download_retention_days) expires = datetime.now(timezone.utc) + timedelta(days=retention_days)
with SessionLocal() as db: with SessionLocal() as db:
asset = db.get(MediaAsset, asset_id) asset = db.get(MediaAsset, asset_id)
@ -554,7 +566,8 @@ def _process_edit(job_id: int, asset_id: int) -> None:
storage.place_file(dest_staging, settings.download_root, rel) storage.place_file(dest_staging, settings.download_root, rel)
final = storage.abs_path(settings.download_root, rel) final = storage.abs_path(settings.download_root, rel)
size = final.stat().st_size if final.exists() else None size = final.stat().st_size if final.exists() else None
expires = datetime.now(timezone.utc) + timedelta(days=settings.download_retention_days) _, retention_days = _download_settings()
expires = datetime.now(timezone.utc) + timedelta(days=retention_days)
with SessionLocal() as db: with SessionLocal() as db:
asset = db.get(MediaAsset, asset_id) asset = db.get(MediaAsset, asset_id)