fix: resume interrupted downloads after container restart
Docker / docker (push) Successful in 1m22s
Docker / docker (push) Successful in 1m22s
Store subtitle/year/category in downloads table. On startup, re-queue any download still in queued/downloading state (reset downloading → queued). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+32
-2
@@ -95,6 +95,9 @@ class DownloadManager:
|
|||||||
id TEXT PRIMARY KEY,
|
id TEXT PRIMARY KEY,
|
||||||
url TEXT NOT NULL,
|
url TEXT NOT NULL,
|
||||||
title TEXT NOT NULL,
|
title TEXT NOT NULL,
|
||||||
|
subtitle TEXT NOT NULL DEFAULT '',
|
||||||
|
year INTEGER,
|
||||||
|
category TEXT NOT NULL DEFAULT '',
|
||||||
filename TEXT,
|
filename TEXT,
|
||||||
state TEXT NOT NULL DEFAULT 'queued',
|
state TEXT NOT NULL DEFAULT 'queued',
|
||||||
progress REAL DEFAULT 0,
|
progress REAL DEFAULT 0,
|
||||||
@@ -105,6 +108,15 @@ class DownloadManager:
|
|||||||
error TEXT
|
error TEXT
|
||||||
)
|
)
|
||||||
""")
|
""")
|
||||||
|
for col, definition in [
|
||||||
|
("subtitle", "TEXT NOT NULL DEFAULT ''"),
|
||||||
|
("year", "INTEGER"),
|
||||||
|
("category", "TEXT NOT NULL DEFAULT ''"),
|
||||||
|
]:
|
||||||
|
try:
|
||||||
|
conn.execute(f"ALTER TABLE downloads ADD COLUMN {col} {definition}")
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
conn.execute("""
|
conn.execute("""
|
||||||
CREATE TABLE IF NOT EXISTS auto_dl_categories (
|
CREATE TABLE IF NOT EXISTS auto_dl_categories (
|
||||||
category TEXT PRIMARY KEY,
|
category TEXT PRIMARY KEY,
|
||||||
@@ -152,14 +164,32 @@ class DownloadManager:
|
|||||||
now = datetime.now().isoformat()
|
now = datetime.now().isoformat()
|
||||||
with _db() as conn:
|
with _db() as conn:
|
||||||
conn.execute(
|
conn.execute(
|
||||||
"INSERT INTO downloads (id, url, title, state, started_at) VALUES (?,?,?,'queued',?)",
|
"INSERT INTO downloads (id, url, title, subtitle, year, category, state, started_at) VALUES (?,?,?,?,?,?,'queued',?)",
|
||||||
(dl_id, url, title, now),
|
(dl_id, url, title, subtitle, year, category, now),
|
||||||
)
|
)
|
||||||
with self._lock:
|
with self._lock:
|
||||||
self._active[dl_id] = {"state": "queued", "progress": 0, "title": title}
|
self._active[dl_id] = {"state": "queued", "progress": 0, "title": title}
|
||||||
await self._queue.put((dl_id, url, title, subtitle, year, category))
|
await self._queue.put((dl_id, url, title, subtitle, year, category))
|
||||||
return dl_id
|
return dl_id
|
||||||
|
|
||||||
|
async def resume_pending(self):
|
||||||
|
"""Re-queue downloads interrupted by a container restart."""
|
||||||
|
with _db() as conn:
|
||||||
|
rows = conn.execute(
|
||||||
|
"SELECT id, url, title, subtitle, year, category FROM downloads"
|
||||||
|
" WHERE state IN ('queued', 'downloading') ORDER BY started_at"
|
||||||
|
).fetchall()
|
||||||
|
conn.execute(
|
||||||
|
"UPDATE downloads SET state='queued', progress=0, speed='', eta=NULL"
|
||||||
|
" WHERE state='downloading'"
|
||||||
|
)
|
||||||
|
for r in rows:
|
||||||
|
with self._lock:
|
||||||
|
self._active[r["id"]] = {"state": "queued", "progress": 0, "title": r["title"]}
|
||||||
|
await self._queue.put((r["id"], r["url"], r["title"], r["subtitle"] or "", r["year"], r["category"] or ""))
|
||||||
|
if rows:
|
||||||
|
logger.info("Resumed %d pending download(s)", len(rows))
|
||||||
|
|
||||||
async def start_worker(self):
|
async def start_worker(self):
|
||||||
loop = asyncio.get_running_loop()
|
loop = asyncio.get_running_loop()
|
||||||
while True:
|
while True:
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ async def _auto_dl_loop():
|
|||||||
|
|
||||||
@asynccontextmanager
|
@asynccontextmanager
|
||||||
async def lifespan(app: FastAPI):
|
async def lifespan(app: FastAPI):
|
||||||
|
await dm.resume_pending()
|
||||||
tasks = [
|
tasks = [
|
||||||
asyncio.create_task(dm.start_worker()),
|
asyncio.create_task(dm.start_worker()),
|
||||||
asyncio.create_task(_auto_dl_loop()),
|
asyncio.create_task(_auto_dl_loop()),
|
||||||
|
|||||||
Reference in New Issue
Block a user