diff --git a/backend/src/media_library_viewer_api/widgets/sources.py b/backend/src/media_library_viewer_api/widgets/sources.py index 8576338..eacea56 100644 --- a/backend/src/media_library_viewer_api/widgets/sources.py +++ b/backend/src/media_library_viewer_api/widgets/sources.py @@ -547,6 +547,7 @@ class QbittorrentWidgetSource: "direction": direction, "size": torrent.get("size"), "progress": torrent.get("progress"), + "ratio": torrent.get("ratio"), "dl_speed": torrent.get("dlspeed"), "up_speed": torrent.get("upspeed"), } diff --git a/backend/tests/test_widgets.py b/backend/tests/test_widgets.py index 1a3ee20..57dff99 100644 --- a/backend/tests/test_widgets.py +++ b/backend/tests/test_widgets.py @@ -1085,6 +1085,7 @@ def _fake_qbit_maindata(): "state": "downloading", "size": 1000, "progress": 0.5, + "ratio": 1.25, "dlspeed": 500, "upspeed": 10, }, @@ -1093,6 +1094,7 @@ def _fake_qbit_maindata(): "state": "uploading", "size": 2000, "progress": 1.0, + "ratio": 0.5, "dlspeed": 0, "upspeed": 100, }, @@ -1178,6 +1180,7 @@ async def test_qbittorrent_active_filters_current_transfers_only(): assert len(active) == 2 names = [torrent["name"] for torrent in active] assert names == ["Movie.mkv", "Show.mkv"] + assert [torrent["ratio"] for torrent in active] == [1.25, 0.5] assert all((torrent["dl_speed"] or 0) > 0 or (torrent["up_speed"] or 0) > 0 for torrent in active) diff --git a/docs/REQUIREMENTS.md b/docs/REQUIREMENTS.md index 343a008..1f9ddba 100644 --- a/docs/REQUIREMENTS.md +++ b/docs/REQUIREMENTS.md @@ -324,6 +324,8 @@ These do not reference a service. - The qBittorrent client must merge incremental torrent patches with the prior snapshot so active-transfer rows retain their name, size, progress, and state when only throughput changes. +- Active-torrent entries must show each torrent's qBittorrent share ratio + (uploaded ÷ downloaded) alongside its size and completion progress. - The service UI should expose polling settings, current status, stale-data state, a manual `Run now` action, the shared selectable chart windows, an **All values** option that fetches every retained speed sample, and paginated scheduled-action history. - Scheduled-action runs should use dedicated generic records, retain at most 30 days or 1,000 runs per service/action, and never store secrets or raw credentials. - Disabling a qBittorrent service pauses polling while retaining history; deleting the service purges its samples and scheduler history through the existing cascade-delete behavior. diff --git a/frontend/src/widgets/QbittorrentActiveTorrentsWidget.tsx b/frontend/src/widgets/QbittorrentActiveTorrentsWidget.tsx index 2736db5..9806b0b 100644 --- a/frontend/src/widgets/QbittorrentActiveTorrentsWidget.tsx +++ b/frontend/src/widgets/QbittorrentActiveTorrentsWidget.tsx @@ -17,6 +17,7 @@ interface ActiveTorrent { direction?: "downloading" | "uploading"; size: number | null; progress: number | null; + ratio: number | null; dl_speed: number | null; up_speed: number | null; } @@ -40,6 +41,12 @@ function formatProgress(progress: number | null): string { return `${Math.round(progress * 100)}% complete`; } +function formatRatio(ratio: number | null): string { + if (ratio === null || !Number.isFinite(ratio) || ratio < 0) + return "Ratio unknown"; + return `Ratio ${ratio.toFixed(2)}`; +} + function formatState( state: string | null, direction?: ActiveTorrent["direction"], @@ -103,7 +110,8 @@ export function QbittorrentActiveTorrentsWidget({

{formatSize(torrent.size)} ·{" "} - {formatProgress(torrent.progress)} + {formatProgress(torrent.progress)} ·{" "} + {formatRatio(torrent.ratio)}

{ state: "downloading", size: 1000, progress: 0.5, + ratio: 1.25, dl_speed: 500000, up_speed: 1000, }, @@ -77,6 +78,7 @@ describe("QbittorrentActiveTorrentsWidget", () => { expect(screen.getByText("Show.mkv")).toBeInTheDocument(); expect(screen.getByText("Downloading")).toBeInTheDocument(); expect(screen.getByText("Uploading")).toBeInTheDocument(); + expect(screen.getByText(/Ratio 1\.25/)).toBeInTheDocument(); }); it("shows empty state when no active torrents", () => {