from __future__ import annotations import re from datetime import datetime, timedelta, timezone from app.config import RADAR_PRODUCT, RADAR_RETENTION_HOURS, RAW_RADAR_DIR from app.database import upsert_radar_meta from app.http_client import get_client from app.services.dwd_radar import RADAR_FILE_RE, fetch_radar_index _FILENAME_RE = re.compile(r"^[a-z0-9_-]+$", re.IGNORECASE) def utc_now() -> datetime: return datetime.now(timezone.utc) def cleanup_old_radar_files() -> None: cutoff = utc_now() - timedelta(hours=RADAR_RETENTION_HOURS) if not RAW_RADAR_DIR.exists(): return for path in RAW_RADAR_DIR.rglob("*"): if not path.is_file() or not RADAR_FILE_RE.fullmatch(path.name): continue modified = datetime.fromtimestamp(path.stat().st_mtime, timezone.utc) if modified < cutoff: path.unlink(missing_ok=True) async def sync_radar_files(product: str = RADAR_PRODUCT, limit: int = 6) -> dict: if not _FILENAME_RE.fullmatch(product): raise ValueError("Invalid product") latest = await fetch_radar_index(product=product, limit=limit) target_dir = RAW_RADAR_DIR / "composite" / product target_dir.mkdir(parents=True, exist_ok=True) downloaded: list[str] = [] skipped: list[str] = [] client = get_client() for item in latest["files"]: out = target_dir / item["name"] if out.exists() and out.stat().st_size > 0: skipped.append(item["name"]) continue response = await client.get(item["url"], timeout=90.0) response.raise_for_status() out.write_bytes(response.content) downloaded.append(item["name"]) modified = datetime.fromtimestamp(out.stat().st_mtime, timezone.utc) await upsert_radar_meta(product, item["name"], out.stat().st_size, modified, "pending") cleanup_old_radar_files() return { "product": product, "target_dir": str(target_dir), "downloaded": downloaded, "skipped": skipped, }