"""Gestion de la file de téléchargements + progression temps réel (SSE).""" import json import logging from collections.abc import AsyncIterator from fastapi import APIRouter, Depends, HTTPException from pydantic import BaseModel from sse_starlette.sse import EventSourceResponse from app.routers.auth import current_user from app.scrapers.base import decode_internal_url from app.services.downloads import download_manager logger = logging.getLogger(__name__) router = APIRouter( prefix="/api/downloads", tags=["downloads"], dependencies=[Depends(current_user)] ) class EnqueueRequest(BaseModel): internal_url: str # format `video_url|page_url|titre` @router.get("") async def list_downloads() -> list[dict]: return await download_manager.list_all() @router.post("", status_code=201) async def enqueue(payload: EnqueueRequest) -> dict: try: video_url, page_url, title = decode_internal_url(payload.internal_url) except ValueError as exc: raise HTTPException(422, detail=str(exc)) from exc return await download_manager.enqueue(video_url, page_url, title) @router.post("/{download_id}/pause") async def pause(download_id: int) -> dict: await download_manager.pause(download_id) return {"ok": True} @router.post("/{download_id}/resume") async def resume(download_id: int) -> dict: if not await download_manager.resume(download_id): raise HTTPException(409, detail="Ce téléchargement n'est pas en pause") return {"ok": True} @router.post("/{download_id}/retry") async def retry(download_id: int) -> dict: if not await download_manager.retry(download_id): raise HTTPException( 409, detail="Seules les tâches en échec/annulées peuvent être relancées" ) return {"ok": True} @router.post("/{download_id}/cancel") async def cancel(download_id: int) -> dict: await download_manager.cancel(download_id) return {"ok": True} @router.delete("/{download_id}") async def delete_download(download_id: int, delete_file: bool = False) -> dict: """Retire une tâche de la file ; delete_file=true efface aussi le fichier.""" if not await download_manager.delete(download_id, delete_file=delete_file): raise HTTPException(404, detail="Téléchargement introuvable") return {"ok": True} @router.post("/cancel-all") async def cancel_all() -> dict: return {"cancelled": await download_manager.cancel_all()} @router.post("/clear-finished") async def clear_finished() -> dict: return {"removed": await download_manager.clear_finished()} @router.get("/events") async def events() -> EventSourceResponse: """Flux SSE : progression de tous les téléchargements en temps réel.""" async def stream() -> AsyncIterator[dict]: yield {"data": json.dumps({"type": "snapshot", "items": await download_manager.list_all()})} async for message in download_manager.subscribe(): yield {"data": json.dumps(message, default=str)} return EventSourceResponse(stream())