From 1fa78256cf8ed30c11cd058301ba42950c5d70ea Mon Sep 17 00:00:00 2001
From: Developer
Date: Tue, 21 Jul 2026 12:35:55 +0000
Subject: [PATCH] feat(qbittorrent): show share ratio for active torrents
---
.../src/media_library_viewer_api/widgets/sources.py | 1 +
backend/tests/test_widgets.py | 3 +++
docs/REQUIREMENTS.md | 2 ++
.../src/widgets/QbittorrentActiveTorrentsWidget.tsx | 10 +++++++++-
.../__tests__/QbittorrentActiveTorrentsWidget.test.tsx | 2 ++
5 files changed, 17 insertions(+), 1 deletion(-)
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", () => {