Download dashboard/db.py from afluakd9/Video-downloader-CPU-basic: direct link, hf CLI and curl.
- Browser
- Download file 14.5 kB
-
https://huggingface.co/spaces/afluakd9/Video-downloader-CPU-basic/resolve/main/dashboard/db.py
- Command line
-
hf download hf://spaces/afluakd9/Video-downloader-CPU-basic/dashboard/db.py
-
curl -L -o db.py https://huggingface.co/spaces/afluakd9/Video-downloader-CPU-basic/resolve/main/dashboard/db.py
14.5 kB
| """Database layer for the reclip_bot admin dashboard. | |
| Uses aiosqlite for async SQLite access with WAL journal mode. | |
| DB path is read from the DB_PATH environment variable (default: /data/reclip_bot.db). | |
| """ | |
| import os | |
| from contextlib import asynccontextmanager | |
| from datetime import datetime, timezone, timedelta | |
| from typing import Any, AsyncGenerator, Dict, List, Optional | |
| import aiosqlite | |
| _SCHEMA = """ | |
| PRAGMA journal_mode=WAL; | |
| CREATE TABLE IF NOT EXISTS downloads ( | |
| id INTEGER PRIMARY KEY AUTOINCREMENT, | |
| job_id TEXT UNIQUE NOT NULL, | |
| user_id INTEGER, | |
| username TEXT, | |
| chat_id INTEGER, | |
| url TEXT NOT NULL, | |
| title TEXT, | |
| platform TEXT, | |
| format TEXT, | |
| quality TEXT, | |
| file_size_bytes INTEGER, | |
| download_duration_sec REAL, | |
| status TEXT NOT NULL DEFAULT 'started', | |
| error_message TEXT, | |
| started_at TEXT NOT NULL, | |
| completed_at TEXT | |
| ); | |
| CREATE TABLE IF NOT EXISTS disk_snapshots ( | |
| id INTEGER PRIMARY KEY AUTOINCREMENT, | |
| total_bytes INTEGER NOT NULL, | |
| file_count INTEGER NOT NULL, | |
| snapshot_at TEXT NOT NULL DEFAULT (datetime('now')) | |
| ); | |
| """ | |
| # --------------------------------------------------------------------------- | |
| # Internal helpers | |
| # --------------------------------------------------------------------------- | |
| def _db_path() -> str: | |
| """Return current DB_PATH (re-read from env each call so tests can override).""" | |
| return os.environ.get("DB_PATH", "/data/reclip_bot.db") | |
| def _utc_now_str() -> str: | |
| return datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M:%S") | |
| async def _conn() -> AsyncGenerator[aiosqlite.Connection, None]: | |
| """Open a WAL-mode connection, run schema migrations, yield, then close.""" | |
| path = _db_path() | |
| os.makedirs(os.path.dirname(os.path.abspath(path)), exist_ok=True) | |
| async with aiosqlite.connect(path) as conn: | |
| conn.row_factory = aiosqlite.Row | |
| await conn.executescript(_SCHEMA) | |
| await conn.commit() | |
| yield conn | |
| # --------------------------------------------------------------------------- | |
| # Public: schema init | |
| # --------------------------------------------------------------------------- | |
| async def init_db(path: Optional[str] = None) -> None: | |
| """Initialise (or migrate) the database schema at the given path. | |
| If path is provided it temporarily overrides the env var for this call. | |
| """ | |
| if path is not None: | |
| old = os.environ.get("DB_PATH") | |
| os.environ["DB_PATH"] = path | |
| try: | |
| async with _conn(): | |
| pass | |
| finally: | |
| if path is not None: | |
| if old is None: | |
| del os.environ["DB_PATH"] | |
| else: | |
| os.environ["DB_PATH"] = old | |
| # --------------------------------------------------------------------------- | |
| # Public: CRUD — downloads | |
| # --------------------------------------------------------------------------- | |
| async def insert_download_start( | |
| *, | |
| job_id: str, | |
| user_id: Optional[int], | |
| username: Optional[str], | |
| chat_id: Optional[int], | |
| url: str, | |
| platform: Optional[str] = None, | |
| ) -> None: | |
| """Record the start of a download job.""" | |
| started_at = _utc_now_str() | |
| async with _conn() as conn: | |
| await conn.execute( | |
| """ | |
| INSERT INTO downloads (job_id, user_id, username, chat_id, url, platform, started_at) | |
| VALUES (?, ?, ?, ?, ?, ?, ?) | |
| """, | |
| (job_id, user_id, username, chat_id, url, platform, started_at), | |
| ) | |
| await conn.commit() | |
| async def update_download_done( | |
| *, | |
| job_id: str, | |
| title: Optional[str] = None, | |
| format: Optional[str] = None, | |
| quality: Optional[str] = None, | |
| file_size_bytes: Optional[int] = None, | |
| download_duration_sec: Optional[float] = None, | |
| ) -> None: | |
| """Mark a download as successfully completed.""" | |
| completed_at = _utc_now_str() | |
| async with _conn() as conn: | |
| await conn.execute( | |
| """ | |
| UPDATE downloads | |
| SET status='done', title=?, format=?, quality=?, | |
| file_size_bytes=?, download_duration_sec=?, completed_at=? | |
| WHERE job_id=? | |
| """, | |
| (title, format, quality, file_size_bytes, download_duration_sec, | |
| completed_at, job_id), | |
| ) | |
| await conn.commit() | |
| async def update_download_error(*, job_id: str, error_message: str) -> None: | |
| """Mark a download as failed.""" | |
| completed_at = _utc_now_str() | |
| async with _conn() as conn: | |
| await conn.execute( | |
| """ | |
| UPDATE downloads | |
| SET status='error', error_message=?, completed_at=? | |
| WHERE job_id=? | |
| """, | |
| (error_message, completed_at, job_id), | |
| ) | |
| await conn.commit() | |
| async def get_download_by_job_id(job_id: str) -> Optional[aiosqlite.Row]: | |
| """Return a single download row by job_id, or None.""" | |
| async with _conn() as conn: | |
| async with conn.execute( | |
| "SELECT * FROM downloads WHERE job_id=?", (job_id,) | |
| ) as cur: | |
| return await cur.fetchone() | |
| # --------------------------------------------------------------------------- | |
| # Public: stats queries | |
| # --------------------------------------------------------------------------- | |
| async def get_dashboard_stats() -> Dict[str, Any]: | |
| """Return high-level dashboard statistics.""" | |
| now = datetime.now(timezone.utc) | |
| today_str = now.strftime("%Y-%m-%d") | |
| yesterday_str = (now - timedelta(days=1)).strftime("%Y-%m-%d") | |
| async with _conn() as conn: | |
| async with conn.execute( | |
| "SELECT COUNT(*) FROM downloads WHERE status='done' AND started_at LIKE ?", | |
| (f"{today_str}%",), | |
| ) as cur: | |
| downloads_today = (await cur.fetchone())[0] | |
| async with conn.execute( | |
| "SELECT COUNT(*) FROM downloads WHERE status='done' AND started_at LIKE ?", | |
| (f"{yesterday_str}%",), | |
| ) as cur: | |
| downloads_yesterday = (await cur.fetchone())[0] | |
| async with conn.execute( | |
| "SELECT COUNT(*) FROM downloads WHERE started_at LIKE ?", | |
| (f"{today_str}%",), | |
| ) as cur: | |
| total_today = (await cur.fetchone())[0] | |
| async with conn.execute( | |
| "SELECT COUNT(*) FROM downloads WHERE status='error' AND started_at LIKE ?", | |
| (f"{today_str}%",), | |
| ) as cur: | |
| errors_today = (await cur.fetchone())[0] | |
| async with conn.execute( | |
| "SELECT COUNT(*) FROM downloads WHERE status='error' AND started_at LIKE ?", | |
| (f"{yesterday_str}%",), | |
| ) as cur: | |
| errors_yesterday = (await cur.fetchone())[0] | |
| async with conn.execute( | |
| "SELECT COUNT(*) FROM downloads WHERE started_at LIKE ?", | |
| (f"{yesterday_str}%",), | |
| ) as cur: | |
| total_yesterday = (await cur.fetchone())[0] | |
| since_24h = (now - timedelta(hours=24)).strftime("%Y-%m-%d %H:%M:%S") | |
| async with conn.execute( | |
| "SELECT COUNT(DISTINCT user_id) FROM downloads WHERE started_at >= ?", | |
| (since_24h,), | |
| ) as cur: | |
| active_users_24h = (await cur.fetchone())[0] | |
| error_rate = (errors_today / total_today * 100) if total_today else 0.0 | |
| error_rate_yesterday = (errors_yesterday / total_yesterday * 100) if total_yesterday else 0.0 | |
| return { | |
| "downloads_today": downloads_today, | |
| "downloads_yesterday": downloads_yesterday, | |
| "active_users_24h": active_users_24h, | |
| "error_rate": round(error_rate, 2), | |
| "error_rate_yesterday": round(error_rate_yesterday, 2), | |
| "errors_today": errors_today, | |
| "total_today": total_today, | |
| } | |
| async def get_chart_data(range_key: str) -> Dict[str, Any]: | |
| """Return time-series chart data for the given range. | |
| range_key: | |
| "1D" -> hourly buckets for the past 24 hours | |
| "7D" -> daily buckets for the past 7 days | |
| "1M" -> daily buckets for the past 30 days | |
| "1Y" -> monthly buckets for the past 12 months | |
| """ | |
| now = datetime.now(timezone.utc) | |
| if range_key == "1D": | |
| # Build list of datetime objects, one per hour, newest last | |
| bucket_dts = [(now - timedelta(hours=i)) for i in range(23, -1, -1)] | |
| labels = [dt.strftime("%H:00") for dt in bucket_dts] | |
| bucket_keys = [dt.strftime("%Y-%m-%d %H") for dt in bucket_dts] | |
| start_str = (now - timedelta(hours=24)).strftime("%Y-%m-%d %H:%M:%S") | |
| trunc_fmt = "%Y-%m-%d %H" | |
| elif range_key == "7D": | |
| bucket_dts = [(now - timedelta(days=i)) for i in range(6, -1, -1)] | |
| labels = [dt.strftime("%a %d") for dt in bucket_dts] | |
| bucket_keys = [dt.strftime("%Y-%m-%d") for dt in bucket_dts] | |
| start_str = (now - timedelta(days=7)).strftime("%Y-%m-%d %H:%M:%S") | |
| trunc_fmt = "%Y-%m-%d" | |
| elif range_key == "1M": | |
| bucket_dts = [(now - timedelta(days=i)) for i in range(29, -1, -1)] | |
| labels = [dt.strftime("%b %d") for dt in bucket_dts] | |
| bucket_keys = [dt.strftime("%Y-%m-%d") for dt in bucket_dts] | |
| start_str = (now - timedelta(days=30)).strftime("%Y-%m-%d %H:%M:%S") | |
| trunc_fmt = "%Y-%m-%d" | |
| else: # 1Y | |
| # Monthly: go back 12 months (approximate with 30-day steps) | |
| bucket_dts = [ | |
| (now.replace(day=1) - timedelta(days=30 * i)) | |
| for i in range(11, -1, -1) | |
| ] | |
| labels = [dt.strftime("%b %Y") for dt in bucket_dts] | |
| bucket_keys = [dt.strftime("%Y-%m") for dt in bucket_dts] | |
| start_str = (now - timedelta(days=365)).strftime("%Y-%m-%d %H:%M:%S") | |
| trunc_fmt = "%Y-%m" | |
| async with _conn() as conn: | |
| async with conn.execute( | |
| f""" | |
| SELECT strftime('{trunc_fmt}', started_at) AS bucket, COUNT(*) AS cnt | |
| FROM downloads | |
| WHERE started_at >= ? AND status='done' | |
| GROUP BY bucket | |
| """, | |
| (start_str,), | |
| ) as cur: | |
| rows = await cur.fetchall() | |
| bucket_map: Dict[str, int] = {r["bucket"]: r["cnt"] for r in rows} | |
| values = [bucket_map.get(k, 0) for k in bucket_keys] | |
| async with conn.execute( | |
| """ | |
| SELECT platform, COUNT(*) AS cnt | |
| FROM downloads | |
| WHERE started_at >= ? AND platform IS NOT NULL | |
| GROUP BY platform | |
| ORDER BY cnt DESC | |
| LIMIT 5 | |
| """, | |
| (start_str,), | |
| ) as cur: | |
| platform_rows = await cur.fetchall() | |
| platforms = [{"platform": r["platform"], "count": r["cnt"]} for r in platform_rows] | |
| async with conn.execute( | |
| """ | |
| SELECT username, COUNT(*) AS cnt | |
| FROM downloads | |
| WHERE started_at >= ? AND username IS NOT NULL | |
| GROUP BY username | |
| ORDER BY cnt DESC | |
| LIMIT 5 | |
| """, | |
| (start_str,), | |
| ) as cur: | |
| user_rows = await cur.fetchall() | |
| top_users = [{"username": r["username"], "count": r["cnt"]} for r in user_rows] | |
| return { | |
| "labels": labels, | |
| "values": values, | |
| "platforms": platforms, | |
| "top_users": top_users, | |
| } | |
| async def get_downloads_page( | |
| page: int = 1, | |
| per_page: int = 20, | |
| platform: Optional[str] = None, | |
| status: Optional[str] = None, | |
| user: Optional[str] = None, | |
| date_from: Optional[str] = None, | |
| date_to: Optional[str] = None, | |
| ) -> Dict[str, Any]: | |
| """Return paginated download history with optional filters.""" | |
| conditions: List[str] = [] | |
| params: List[Any] = [] | |
| if platform: | |
| conditions.append("platform = ?") | |
| params.append(platform) | |
| if status: | |
| conditions.append("status = ?") | |
| params.append(status) | |
| if user: | |
| conditions.append("username = ?") | |
| params.append(user) | |
| if date_from: | |
| conditions.append("started_at >= ?") | |
| params.append(f"{date_from} 00:00:00") | |
| if date_to: | |
| conditions.append("started_at <= ?") | |
| params.append(f"{date_to} 23:59:59") | |
| where = ("WHERE " + " AND ".join(conditions)) if conditions else "" | |
| offset = (page - 1) * per_page | |
| async with _conn() as conn: | |
| async with conn.execute( | |
| f"SELECT COUNT(*) FROM downloads {where}", params | |
| ) as cur: | |
| total = (await cur.fetchone())[0] | |
| async with conn.execute( | |
| f"SELECT * FROM downloads {where} ORDER BY started_at DESC LIMIT ? OFFSET ?", | |
| params + [per_page, offset], | |
| ) as cur: | |
| rows = await cur.fetchall() | |
| pages = (total + per_page - 1) // per_page if total else 1 | |
| return { | |
| "rows": [dict(r) for r in rows], | |
| "total": total, | |
| "pages": pages, | |
| "page": page, | |
| } | |
| async def get_error_downloads( | |
| date_from: Optional[str] = None, | |
| date_to: Optional[str] = None, | |
| ) -> List[Dict[str, Any]]: | |
| """Return all failed downloads, optionally filtered by date range.""" | |
| conditions = ["status = 'error'"] | |
| params: List[Any] = [] | |
| if date_from: | |
| conditions.append("started_at >= ?") | |
| params.append(f"{date_from} 00:00:00") | |
| if date_to: | |
| conditions.append("started_at <= ?") | |
| params.append(f"{date_to} 23:59:59") | |
| where = "WHERE " + " AND ".join(conditions) | |
| async with _conn() as conn: | |
| async with conn.execute( | |
| f"SELECT * FROM downloads {where} ORDER BY started_at DESC", | |
| params, | |
| ) as cur: | |
| rows = await cur.fetchall() | |
| return [dict(r) for r in rows] | |
| # --------------------------------------------------------------------------- | |
| # Public: disk snapshots | |
| # --------------------------------------------------------------------------- | |
| async def insert_disk_snapshot(total_bytes: int, file_count: int) -> None: | |
| """Record a disk usage snapshot.""" | |
| snapshot_at = _utc_now_str() | |
| async with _conn() as conn: | |
| await conn.execute( | |
| "INSERT INTO disk_snapshots (total_bytes, file_count, snapshot_at) VALUES (?, ?, ?)", | |
| (total_bytes, file_count, snapshot_at), | |
| ) | |
| await conn.commit() | |
| async def get_latest_disk_snapshot() -> Optional[Dict[str, Any]]: | |
| """Return the most recent disk snapshot, or None.""" | |
| async with _conn() as conn: | |
| async with conn.execute( | |
| "SELECT * FROM disk_snapshots ORDER BY id DESC LIMIT 1" | |
| ) as cur: | |
| row = await cur.fetchone() | |
| return dict(row) if row else None | |