Schedule pickers, and target progress at one row

"Check feeds every" becomes a number plus a unit dropdown in both global
and per-feed settings; parse_interval gained weeks to back it. The
per-feed dropdown can select the global default, clearing the override.

Fixes progress painting every pending row: the event carried no enclosure
id, so the handler had nothing to target and set the width on all of them.
Adding a feed looked like it was downloading everything. Progress,
DownloadDone and DownloadError now carry the enclosure id.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AdXho5tTkjFLeUXKbEjKBh
This commit is contained in:
2026-09-10 02:38:14 +00:00
parent 9960befed5
commit e97f2b9c2f
8 changed files with 155 additions and 30 deletions

View File

@@ -17,14 +17,17 @@ pub enum Event {
FeedError { feed: String, msg: String },
Progress {
feed: String,
/// Which enclosure this is about. Without it a UI cannot tell one download's
/// progress from another's and ends up animating every pending row.
enclosure: i64,
url: String,
file: String,
done: u64,
#[serde(skip_serializing_if = "Option::is_none")]
total: Option<u64>,
},
DownloadDone { feed: String, url: String, path: String, bytes: u64 },
DownloadError { feed: String, url: String, msg: String },
DownloadDone { feed: String, enclosure: i64, url: String, path: String, bytes: u64 },
DownloadError { feed: String, enclosure: i64, url: String, msg: String },
TorrentDeferred { feed: String, url: String },
Reaped { path: String, bytes: u64 },
/// Terminal: a client that asked for work stops reading here.
@@ -252,6 +255,7 @@ mod tests {
fn events_serialise_to_the_documented_shape() {
let ev = Event::Progress {
feed: "atp".into(),
enclosure: 42,
url: "https://x/ep.mp3".into(),
file: "ep.mp3".into(),
done: 10_485_760,
@@ -260,10 +264,12 @@ mod tests {
let json = serde_json::to_string(&ev).unwrap();
assert!(json.starts_with(r#"{"ev":"progress""#), "got {json}");
assert!(json.contains(r#""done":10485760"#));
assert!(json.contains(r#""enclosure":42"#), "a UI needs this to target one row");
// total is omitted rather than null when the server sent no length.
let ev = Event::Progress {
feed: "a".into(),
enclosure: 1,
url: "u".into(),
file: "f".into(),
done: 1,