Sync WebCal and CalDAV directly; add a skip_past cutoff

WebCal is calcalist's own (src/webcal.rs), with Calendar::split_by_uid to turn
one feed document into one file per UID. Shared crate::http and crate::secret
came out of google/ so every leg uses them.

CalDAV follows, behind a per-endpoint 'native' flag so it can run beside
pimsync one collection at a time: src/caldav/client.rs speaks PROPFIND,
sync-collection, calendar-multiget and conditional PUT/DELETE, and
src/caldav/mod.rs is the pull/push pair. Radicale answers an initial
sync-collection with no members, so a cursor is only stored alongside a
complete read. doctor reports what a server advertises.

skip_past (src/horizon.rs) takes a date, not a flag: a rolling 'before now'
would retire events from the aggregate as they aged, deleting yesterday's
events on every cycle. Pinned to a date, what is mirrored stays mirrored. On
the real configuration it leaves 2600 of 3282 events behind. The filter sits
above the prior-links lookup in mirror_source, which is what makes it safe:
from there DeleteSource is unreachable, so excluding an event can never be
read as the user having deleted its mirror.

pimsync: CWD pinned so vstorage's relative unlink cannot reach the user's
files; the dry-run workspace is removed by Drop rather than by a line every
'?' stepped over.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
randogoth 2026-09-13 15:33:22 +03:00
parent d03748472a
commit 622a71dcd6
28 changed files with 2717 additions and 188 deletions

View file

@ -67,8 +67,32 @@ hand-editable artefact. Anything that writes it must preserve comments,
ordering and omitted defaults — `toml::to_string` on the `Serialize` derive
does none of those things.
**WebCal and Google are calcalist's own; CalDAV is being taken back too.** A
feed is one iCalendar document fetched over HTTP and read-only by protocol, so
routing it through a generated config, a subprocess and a status database bought
nothing — it cost three minutes a cycle in observed stalls. CalDAV has followed,
behind a per-endpoint `native` flag so the two run side by side until the
native client has earned the default. The argument
is not that pimsync is bad but that calcalist was paying for generality it never
used: one server, against 528 lines of adapter, a 59.6 MB flake closure, an
undocumented `discover` format that is scraped, a version range to maintain, and
a `vstorage` bug that unlinked `order` relative to the working directory.
**Radicale is the CalDAV target; other servers are untested, not unsupported.**
Narrowing is what makes owning the protocol reasonable: the integration tests
already run against Radicale, so every assumption is checked on every
`devbox run check`, and when the RFC is ambiguous its source settles it. The
discipline that keeps this honest is that server-specific accommodations stay
*named*, never absorbed into the general path — Radicale answers an initial
`sync-collection` with a token and no members, which is written as "always load
in full first, then go incremental", correct anywhere, rather than as a quirk
worked around in place. `doctor` reports what a server advertises so that
pointing calcalist elsewhere fails legibly.
## Known gap
A failing `pimsync sync` stops the whole CalDAV leg. Unlike the Google side this
cannot be narrowed: pimsync is one process covering every pair, and a failure
does not say which pair it belongs to.
A failing `pimsync sync` stops every CalDAV endpoint pimsync still owns. It is
one process covering every pair, and a failure does not say which pair it
belongs to, so the endpoints cannot be told apart the way the Google ones are.
`native = true` takes an endpoint out of its reach entirely, which is the way
out rather than a narrowing of pimsync's own reporting.