diff --git a/backend/routers/channels.py b/backend/routers/channels.py index 95b734c..44e0749 100644 --- a/backend/routers/channels.py +++ b/backend/routers/channels.py @@ -680,7 +680,20 @@ def _fetch_popular_task(channel_id: int, youtube_channel_id: str, channel_name: from ..database import SessionLocal from concurrent.futures import ThreadPoolExecutor, as_completed + task_id = f"popular-{channel_id}" + label = f"Popular fetch — {channel_name}" if channel_name else "Popular fetch" + # Phase 1 — flat-playlist: crawl all channel videos quickly + with _tasks_lock: + _tasks[task_id] = { + "id": task_id, + "label": label, + "phase": "Crawling channel…", + "total": 0, + "done": 0, + "started_at": datetime.utcnow().isoformat(), + } + if youtube_channel_id.startswith("@"): url = f"https://www.youtube.com/{youtube_channel_id}/videos" else: @@ -750,17 +763,15 @@ def _fetch_popular_task(channel_id: int, youtube_channel_id: str, channel_name: db.close() if not video_ids: + with _tasks_lock: + _tasks.pop(task_id, None) return - task_id = f"popular-{channel_id}" with _tasks_lock: - _tasks[task_id] = { - "id": task_id, - "label": f"Popular fetch — {channel_name}" if channel_name else "Popular fetch", - "total": len(video_ids), - "done": 0, - "started_at": datetime.utcnow().isoformat(), - } + if task_id in _tasks: + _tasks[task_id]["phase"] = "Enriching view counts…" + _tasks[task_id]["total"] = len(video_ids) + _tasks[task_id]["done"] = 0 results = {} try: diff --git a/frontend/src/components/Layout.jsx b/frontend/src/components/Layout.jsx index 97d6ed2..fda6dfd 100644 --- a/frontend/src/components/Layout.jsx +++ b/frontend/src/components/Layout.jsx @@ -2,7 +2,7 @@ import { Outlet, NavLink, Link, useLocation } from "react-router-dom"; import { useQuery } from "@tanstack/react-query"; import { useAuth } from "../hooks/useAuth"; import SearchBar from "./SearchBar"; -import { getDownloads, getChannels } from "../api"; +import { getDownloads, getChannels, getActiveTasks } from "../api"; function BottomNav({ newCount }) { const tabs = [ @@ -73,7 +73,7 @@ function BottomNav({ newCount }) { } function DownloadIndicator() { - const { data } = useQuery({ + const { data: downloads } = useQuery({ queryKey: ["downloads"], queryFn: () => getDownloads().then((r) => r.data), refetchInterval: (query) => { @@ -84,27 +84,48 @@ function DownloadIndicator() { }, }); - const active = (data ?? []).filter( + const { data: tasks = [] } = useQuery({ + queryKey: ["active-tasks"], + queryFn: () => getActiveTasks().then((r) => r.data), + refetchInterval: (query) => (query.state.data?.length > 0 ? 2000 : 10_000), + }); + + const activeDownloads = (downloads ?? []).filter( (d) => d.status === "pending" || d.status === "downloading" ); - if (!active.length) return null; - const top = active[0]; - const pct = top.progress_percent ?? 0; + if (!activeDownloads.length && !tasks.length) return null; + + const totalActive = activeDownloads.length + tasks.length; + + // Show download progress if there are active downloads, otherwise show task phase + let label; + if (activeDownloads.length) { + const pct = activeDownloads[0].progress_percent ?? 0; + label = {pct.toFixed(0)}%; + } else { + const task = tasks[0]; + const pct = task.total > 0 ? Math.round((task.done / task.total) * 100) : null; + label = ( + + {pct !== null ? `${pct}%` : task.phase || "…"} + + ); + } return ( 1 ? "s" : ""} in progress`} + title={`${totalActive} task${totalActive > 1 ? "s" : ""} in progress`} > - {pct.toFixed(0)}% - {active.length > 1 && ( - +{active.length - 1} + {label} + {totalActive > 1 && ( + +{totalActive - 1} )} ); diff --git a/frontend/src/pages/Downloads.jsx b/frontend/src/pages/Downloads.jsx index 6e66777..084eef9 100644 --- a/frontend/src/pages/Downloads.jsx +++ b/frontend/src/pages/Downloads.jsx @@ -141,9 +141,12 @@ export default function DownloadsPage() { const pct = task.total > 0 ? (task.done / task.total) * 100 : 0; return (
+

{task.label}

-

{task.label}

- {task.done}/{task.total} + {task.phase || "Running…"} + {task.total > 0 && ( + {task.done}/{task.total} + )}