Add feed check scheduling, global with per-feed override
general.schedule and feeds.<id>.schedule take "every 30m", "4h", "1d" or bare minutes. The legacy interval_mins is still read. An explicit per-feed schedule wins over the publisher's ttl; without one, ttl still raises the interval when they ask to be polled less often. Fixes two bugs found while testing it: null never cleared a field. serde maps JSON null onto the outer None of an Option<Option<T>>, so "clear this" was indistinguishable from "not supplied" and every clear silently no-opped with a 204. The daemon ignored SIGTERM while working. select! races branches only at selection time, so a signal queued behind an in-flight download and the process had to be SIGKILLed. The stop signal now cancels work in progress: SIGTERM mid-download exits in 1s. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RPyeapneuXrCdojsaiXGbe
This commit is contained in:
44
PROGRESS.md
44
PROGRESS.md
@@ -56,6 +56,50 @@ and until now nothing set them.
|
||||
|
||||
---
|
||||
|
||||
## 2026-09-10 — Scheduling, and two bugs it uncovered
|
||||
|
||||
**Scheduling.** The original engine had none — it only skipped feeds on `<ttl>`; the schedule lived
|
||||
in the Cocoa GUI that was never open-sourced, so there was no original behaviour to match. Scope
|
||||
agreed with Ray: interval only, global default plus per-feed override, no quiet hours.
|
||||
|
||||
`general.schedule` takes "every 30m", "every 4h", "1d", or a bare number of minutes;
|
||||
`feeds.<id>.schedule` overrides it. The legacy `interval_mins` is still read so existing configs
|
||||
work, and saving from the UI migrates it. A malformed value in the file warns and falls back rather
|
||||
than stopping the daemon; a malformed value over the API is a 400.
|
||||
|
||||
Precedence, which is a judgement call: an explicit per-feed schedule wins outright, including over
|
||||
the publisher's `<ttl>`. With no per-feed schedule, `<ttl>` still raises the interval when the
|
||||
publisher asks to be polled less often. Visible in practice — TWiT resolves to 720 min from its own
|
||||
ttl against a 60 min global.
|
||||
|
||||
UI: a gear opens global Settings (schedule, quota, max age); each feed's Settings gains a schedule
|
||||
field; the feed header shows "every 4 hours · next in 37m".
|
||||
|
||||
**Bug 1: `null` never cleared anything.** For `Option<Option<T>>`, serde maps JSON `null` onto the
|
||||
*outer* `None`, which the handler reads as "field absent, leave alone" — so `Some(None)` was
|
||||
unreachable and every clear was a no-op returning 204. That silently affected `folder`, `schedule`
|
||||
and `max_new_per_check`; unsetting any of them from the UI did nothing and looked like it worked.
|
||||
Fixed with a `double_option` deserializer, and pinned by a test covering absent / null / value.
|
||||
|
||||
**Bug 2: the daemon ignored SIGTERM while working.** `tokio::select!` races its branches only at
|
||||
selection time; once inside `run(...).await` the shutdown future was not polled at all, so a signal
|
||||
queued behind a 90 MB download. Under systemd every restart would have hit `TimeoutStopSec` and been
|
||||
SIGKILLed mid-transfer.
|
||||
|
||||
This one cost real time: `pkill` appeared to work but didn't, a stale daemon kept port 8099, and
|
||||
because the restart also `rm -f`'d the socket it defeated the "already listening" guard and a second
|
||||
daemon started. Every PATCH "verified" after that went to the *old* binary — so bug 1 was reported
|
||||
fixed while completely untested. Lesson: confirm the process actually died and the socket guard is
|
||||
doing its job, rather than trusting the restart.
|
||||
|
||||
The stop signal now lives in a `watch` channel raced *inside* each job. Measured: SIGTERM mid-
|
||||
download exits in 1s (was: blocked until the transfer finished). An abandoned download leaves a
|
||||
partial in `.ipx-incomplete`, discarded on the next attempt.
|
||||
|
||||
`cargo test` 37/37.
|
||||
|
||||
---
|
||||
|
||||
## 2026-09-10 — App name, and a transparent icon
|
||||
|
||||
Title is `iPodderX` in the browser tab and the sidebar. The heading's `text-transform: uppercase`
|
||||
|
||||
Reference in New Issue
Block a user