feat: queue de téléchargement séquentielle (un à la fois)
Docker / docker (push) Successful in 1m38s

asyncio.Queue dans DownloadManager + worker unique démarré dans le lifespan.
Les téléchargements s'exécutent un par un dans l'ordre d'arrivée.
Suppression de BackgroundTasks (plus nécessaire).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
dev
2026-05-03 11:30:07 +02:00
parent a4ffd6d63e
commit a4273557ad
2 changed files with 19 additions and 29 deletions
+9 -20
View File
@@ -8,7 +8,6 @@ from datetime import datetime
from pathlib import Path
import yt_dlp
from fastapi import BackgroundTasks
OUTPUT_DIR = "/data/Arte"
DB_PATH = "data/arte_dl.db"
@@ -87,6 +86,7 @@ class DownloadManager:
def __init__(self):
self._active: dict[str, dict] = {}
self._lock = threading.Lock()
self._queue: asyncio.Queue = asyncio.Queue()
self._init_db()
def _init_db(self):
@@ -147,7 +147,8 @@ class DownloadManager:
).fetchone()
return row is not None
def _insert_queued(self, url: str, title: str) -> str:
async def enqueue(self, url: str, title: str, subtitle: str,
year: int | None, category: str) -> str:
dl_id = str(uuid.uuid4())
now = datetime.now().isoformat()
with _db() as conn:
@@ -157,20 +158,15 @@ class DownloadManager:
)
with self._lock:
self._active[dl_id] = {"state": "queued", "progress": 0, "title": title}
await self._queue.put((dl_id, url, title, subtitle, year, category))
return dl_id
def enqueue(self, url: str, title: str, subtitle: str, year: int | None,
category: str, bg: BackgroundTasks) -> str:
dl_id = self._insert_queued(url, title)
bg.add_task(self._run, dl_id, url, title, subtitle, year, category)
return dl_id
async def enqueue_direct(self, url: str, title: str, subtitle: str,
year: int | None, category: str) -> str:
dl_id = self._insert_queued(url, title)
async def start_worker(self):
loop = asyncio.get_running_loop()
loop.run_in_executor(None, self._run, dl_id, url, title, subtitle, year, category)
return dl_id
while True:
job = await self._queue.get()
dl_id, url, title, subtitle, year, category = job
await loop.run_in_executor(None, self._run, dl_id, url, title, subtitle, year, category)
def status(self, dl_id: str) -> dict:
with self._lock:
@@ -183,13 +179,6 @@ class DownloadManager:
).fetchall()
return [dict(r) for r in rows]
def already_downloaded(self, url: str) -> bool:
with _db() as conn:
row = conn.execute(
"SELECT id FROM downloads WHERE url=? AND state='done' LIMIT 1", (url,)
).fetchone()
return row is not None
# ----------------------------------------------------------------- private
def _set(self, dl_id: str, **kw):
+10 -9
View File
@@ -5,7 +5,7 @@ import os
import re
from contextlib import asynccontextmanager
from fastapi import BackgroundTasks, FastAPI, HTTPException, Request
from fastapi import FastAPI, HTTPException, Request
from fastapi.responses import HTMLResponse, StreamingResponse
from fastapi.staticfiles import StaticFiles
from fastapi.templating import Jinja2Templates
@@ -52,13 +52,14 @@ async def _auto_dl_loop():
@asynccontextmanager
async def lifespan(app: FastAPI):
task = asyncio.create_task(_auto_dl_loop())
tasks = [
asyncio.create_task(dm.start_worker()),
asyncio.create_task(_auto_dl_loop()),
]
yield
task.cancel()
try:
await task
except asyncio.CancelledError:
pass
for t in tasks:
t.cancel()
await asyncio.gather(*tasks, return_exceptions=True)
app = FastAPI(title="Arte-dl", lifespan=lifespan)
@@ -133,10 +134,10 @@ class DownloadRequest(BaseModel):
@app.post("/api/download")
async def api_download(req: DownloadRequest, bg: BackgroundTasks):
async def api_download(req: DownloadRequest):
if not req.url:
raise HTTPException(status_code=400, detail="url required")
dl_id = dm.enqueue(req.url, req.title, req.subtitle, req.year, req.category, bg)
dl_id = await dm.enqueue(req.url, req.title, req.subtitle, req.year, req.category)
return {"id": dl_id}