Téléchargements : suppression par épisode et regroupement par animé

- DELETE /api/downloads/{id} (?delete_file=true pour effacer aussi le fichier)
- Événement SSE « removed » → toutes les pages ouvertes se synchronisent
- Page Téléchargements groupée par animé : groupes repliables (dépliés si
  actifs), compteurs par statut, taille totale, tri par numéro d'épisode
- Actions par groupe : 🧹 retirer les terminés, ✖ annuler les actifs
- Limite de liste 200 → 2000 tâches
This commit is contained in:
Roman
2026-09-22 12:17:27 +00:00
parent affc97c527
commit 9500a84a5e
7 changed files with 283 additions and 58 deletions
+15
View File
@@ -69,6 +69,21 @@ async def test_stream_range(client, admin_cookies):
)
assert r.status_code == 416
async def test_delete_download(client, admin_cookies):
path = get_settings().download_dir / "del1.mp4"
path.write_bytes(b"data" * 100)
cursor = await db.execute(
"INSERT INTO downloads (source_key, video_url, page_url, title, file_path, status, "
"total_bytes, downloaded_bytes) "
"VALUES ('k2', 'u', 'p', 'Anime Y - E9 (VOSTFR)', 'del1.mp4', 'done', 400, 400)"
)
download_id = cursor.lastrowid
r = await client.delete(f"/api/downloads/{download_id}?delete_file=true", cookies=admin_cookies)
assert r.status_code == 200 and r.json() == {"ok": True}
assert not path.exists()
r = await client.delete(f"/api/downloads/{download_id}", cookies=admin_cookies)
assert r.status_code == 404
async def test_admin_required(client):
await client.post("/auth/register", data={"username": "admin2", "password": "secret123"})
+30
View File
@@ -102,3 +102,33 @@ async def test_cancel(file_server, manager):
await dm.cancel(d["id"])
data = await dm.get(d["id"])
assert data["status"] in ("cancelled", "done") # course possible si déjà fini
async def test_delete_keeps_file(file_server, manager):
d = await dm.enqueue(f"{file_server}/video.mp4", "http://page", "Anime X - E1 (VOSTFR)")
data = await _wait_status(d["id"], {"done", "failed"})
assert data["status"] == "done", data.get("error")
path = get_settings().download_dir / data["file_path"]
assert path.is_file()
assert await dm.delete(d["id"]) is True
assert await dm.get(d["id"]) is None
assert path.is_file() # le fichier vidéo est conservé
async def test_delete_with_file(file_server, manager):
d = await dm.enqueue(f"{file_server}/video.mp4", "http://page", "Anime X - E2 (VOSTFR)")
data = await _wait_status(d["id"], {"done", "failed"})
path = get_settings().download_dir / data["file_path"]
assert await dm.delete(d["id"], delete_file=True) is True
assert await dm.get(d["id"]) is None
assert not path.exists()
assert await dm.delete(d["id"]) is False # déjà supprimé → introuvable
async def test_delete_active(file_server, manager):
d = await dm.enqueue(f"{file_server}/video.mp4", "http://page", "Anime X - E3")
filename = d["file_path"]
assert await dm.delete(d["id"]) is True
assert await dm.get(d["id"]) is None
download_dir = get_settings().download_dir
assert not (download_dir / filename).exists()
assert not (download_dir / (filename + ".part")).exists()