Enhance PVideoDl with finished_at tracking for downloads and UI updates. Added finished_at field to Download model, updated storage to handle legacy databases, and improved frontend to display recent downloads. Refactored scripts for better clarity and error handling.

This commit is contained in:
Leonid Pershin
2026-06-19 16:27:20 +03:00
parent 303de2b26c
commit 3e47e95fc4
14 changed files with 316 additions and 126 deletions
+24 -3
View File
@@ -8,7 +8,7 @@ from __future__ import annotations
import json
from abc import ABC, abstractmethod
from datetime import datetime
from datetime import datetime, timezone
from pathlib import Path
from typing import Any
@@ -17,6 +17,10 @@ import aiosqlite
from app.models import Download, DownloadStatus
def _now() -> datetime:
return datetime.now(timezone.utc)
class Storage(ABC):
@abstractmethod
async def init(self) -> None: ...
@@ -52,6 +56,7 @@ _COLUMNS = (
"eta",
"error",
"created_at",
"finished_at",
)
@@ -59,6 +64,8 @@ def _row_to_download(row: aiosqlite.Row) -> Download:
data = dict(row)
data["status"] = DownloadStatus(data["status"])
data["created_at"] = datetime.fromisoformat(data["created_at"])
if data.get("finished_at"):
data["finished_at"] = datetime.fromisoformat(data["finished_at"])
return Download.model_validate(data)
@@ -98,22 +105,36 @@ class SqliteStorage(Storage):
speed REAL,
eta REAL,
error TEXT,
created_at TEXT NOT NULL
created_at TEXT NOT NULL,
finished_at TEXT
)
"""
)
# Миграция старых БД, созданных до появления finished_at.
await self._migrate_add_column("finished_at", "TEXT")
# На старте всё, что осталось "качающимся" после прошлого запуска,
# помечаем упавшим — воркеры этого процесса о них не знают.
await self._db.execute(
"UPDATE downloads SET status = ?, error = ? WHERE status = ?",
"UPDATE downloads SET status = ?, error = ?, finished_at = ? "
"WHERE status = ?",
(
DownloadStatus.FAILED.value,
"Прервано при перезапуске приложения",
_now().isoformat(),
DownloadStatus.DOWNLOADING.value,
),
)
await self._db.commit()
async def _migrate_add_column(self, name: str, decl: str) -> None:
"""Добавить колонку, если её ещё нет (idempotent-миграция старых БД)."""
async with self._conn.execute("PRAGMA table_info(downloads)") as cursor:
cols = {row["name"] for row in await cursor.fetchall()}
if name not in cols:
await self._conn.execute(
f"ALTER TABLE downloads ADD COLUMN {name} {decl}"
)
async def close(self) -> None:
if self._db is not None:
await self._db.close()