Nouvelle version réécrite de zéro : recherche multi-sources (Vostfree, French-Manga), extraction 2 niveaux, proxy vidéo intégré, streaming/téléchargement HLS, métadonnées Kitsu, bibliothèque locale, comptes JWT + administration, découverte fusionnée, indexeur Torznab (Sonarr/Prowlarr).
88 lines
2.6 KiB
Python
88 lines
2.6 KiB
Python
"""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.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 update in download_manager.subscribe():
|
|
yield {"data": json.dumps({"type": "update", "item": update}, default=str)}
|
|
|
|
return EventSourceResponse(stream())
|