feat: auto-téléchargement par catégorie avec souscription persistante
Docker / docker (push) Has been cancelled

- Bouton ⬇ sur chaque pill de catégorie pour activer/désactiver l'auto-DL
- Souscriptions sauvegardées en SQLite (table auto_dl_categories)
- Boucle background toutes les AUTO_DL_INTERVAL secondes (défaut 1h)
- Déduplication via already_enqueued() (évite re-queue si déjà queued/done)
- POST /api/auto-dl/check pour déclencher un check immédiat
- GET/POST/DELETE /api/auto-dl/{category} pour gérer les souscriptions

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
dev
2026-05-02 19:23:57 +02:00
parent f07352bd04
commit 189b65bd6d
5 changed files with 220 additions and 10 deletions
+78 -4
View File
@@ -1,6 +1,9 @@
import asyncio
import json
import logging
import os
import re
from contextlib import asynccontextmanager
from fastapi import BackgroundTasks, FastAPI, HTTPException, Request
from fastapi.responses import HTMLResponse, StreamingResponse
@@ -8,18 +11,61 @@ from fastapi.staticfiles import StaticFiles
from fastapi.templating import Jinja2Templates
from pydantic import BaseModel
from arte_api import fetch_concerts, invalidate_cache, CATEGORIES
from arte_api import fetch_concerts, get_concerts_by_category, invalidate_cache, CATEGORIES
from downloader import DownloadManager
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
app = FastAPI(title="Arte-dl")
app.mount("/static", StaticFiles(directory="static"), name="static")
templates = Jinja2Templates(directory="templates")
AUTO_DL_INTERVAL = int(os.getenv("AUTO_DL_INTERVAL", "3600"))
dm = DownloadManager()
async def _run_auto_dl_check() -> int:
cats = dm.get_watched_categories()
if not cats:
return 0
total = 0
for cat in cats:
concerts = await get_concerts_by_category(cat)
for c in concerts:
if not dm.already_enqueued(c["url"]):
m = re.search(r"\b(20\d{2})\b", c.get("subtitle", ""))
year = int(m.group(1)) if m else None
await dm.enqueue_direct(c["url"], c["title"], c.get("subtitle", ""), year, cat)
total += 1
return total
async def _auto_dl_loop():
await asyncio.sleep(60)
while True:
try:
n = await _run_auto_dl_check()
if n:
logger.info("Auto-DL: enqueued %d new concert(s)", n)
except Exception as e:
logger.warning("Auto-DL check failed: %s", e)
await asyncio.sleep(AUTO_DL_INTERVAL)
@asynccontextmanager
async def lifespan(app: FastAPI):
task = asyncio.create_task(_auto_dl_loop())
yield
task.cancel()
try:
await task
except asyncio.CancelledError:
pass
app = FastAPI(title="Arte-dl", lifespan=lifespan)
app.mount("/static", StaticFiles(directory="static"), name="static")
templates = Jinja2Templates(directory="templates")
# ------------------------------------------------------------------ pages
@@ -47,6 +93,34 @@ async def api_refresh():
return {"count": count}
# ------------------------------------------------------------------ API: auto-download
@app.get("/api/auto-dl")
async def api_auto_dl_list():
return dm.get_watched_categories()
@app.post("/api/auto-dl/check")
async def api_auto_dl_check():
n = await _run_auto_dl_check()
return {"enqueued": n}
@app.post("/api/auto-dl/{category}")
async def api_auto_dl_watch(category: str):
if category not in CATEGORIES:
raise HTTPException(status_code=404, detail="Unknown category")
dm.watch_category(category)
return {"watched": True}
@app.delete("/api/auto-dl/{category}")
async def api_auto_dl_unwatch(category: str):
dm.unwatch_category(category)
return {"watched": False}
# ------------------------------------------------------------------ API: downloads