From 5ba49edfd46a01567d39a8cd4b0ecf01cb0b5fa2 Mon Sep 17 00:00:00 2001 From: Miguel Palhas Date: Wed, 26 Aug 2026 12:27:42 +0100 Subject: [PATCH] fix(dl): take stalled from Transmission MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A zero download rate is not a stalled torrent: one between peers reads zero for a poll or two and finishes fine, and at §9.8's 15s cadence that flicker would raise the one chip reserved for a download that never finishes. Transmission already decides this with its own stalled window; carry isStalled and use it. --- crates/arr-api/src/downloads.rs | 6 +++++- crates/arr-dl/src/lib.rs | 11 +++++++++-- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/crates/arr-api/src/downloads.rs b/crates/arr-api/src/downloads.rs index b69a202..30fcdb8 100644 --- a/crates/arr-api/src/downloads.rs +++ b/crates/arr-api/src/downloads.rs @@ -116,7 +116,10 @@ fn phase(torrent: &Torrent) -> DownloadPhase { } else { match torrent.state { TorrentState::Seeding => DownloadPhase::Seeding, - TorrentState::Downloading if torrent.download_rate == 0 => DownloadPhase::Stalled, + // Transmission's own verdict, not a zero rate: a torrent between + // peers reads zero for a poll or two and finishes fine, and + // §9.8's stalled chip is for the case that never does. + TorrentState::Downloading if torrent.is_stalled => DownloadPhase::Stalled, TorrentState::Downloading => DownloadPhase::Downloading, _ => DownloadPhase::Queued, } @@ -148,6 +151,7 @@ mod tests { download_rate: rate, eta: None, error: error.map(str::to_owned), + is_stalled: rate == 0, download_dir: PathBuf::from("/downloads"), labels: Vec::new(), is_finished: false, diff --git a/crates/arr-dl/src/lib.rs b/crates/arr-dl/src/lib.rs index 73c306d..eb41ee8 100644 --- a/crates/arr-dl/src/lib.rs +++ b/crates/arr-dl/src/lib.rs @@ -75,6 +75,10 @@ pub struct Torrent { pub eta: Option, /// Transmission's error message, when the torrent has errored. pub error: Option, + /// Transmission's own verdict that this torrent has gone quiet for longer + /// than its configured stalled window. A rate of zero is not the same + /// fact: a torrent between peers reads zero for one poll and is fine. + pub is_stalled: bool, pub download_dir: PathBuf, pub labels: Vec, /// Transmission has stopped this torrent because its configured seeding @@ -237,8 +241,8 @@ impl TransmissionClient { json!({ "fields": [ "id", "name", "hashString", "status", "percentDone", - "rateDownload", "eta", "errorString", "downloadDir", - "labels", "isFinished" + "rateDownload", "eta", "errorString", "isStalled", + "downloadDir", "labels", "isFinished" ] }), ) @@ -398,6 +402,8 @@ struct RpcTorrent { eta: Option, #[serde(rename = "errorString", default)] error: String, + #[serde(rename = "isStalled", default)] + is_stalled: bool, #[serde(rename = "downloadDir")] download_dir: PathBuf, #[serde(default)] @@ -428,6 +434,7 @@ impl From for Torrent { download_rate: value.download_rate, eta: value.eta.filter(|eta| *eta >= 0), error: (!value.error.is_empty()).then_some(value.error), + is_stalled: value.is_stalled, download_dir: value.download_dir, labels: value.labels, is_finished: value.is_finished,