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>
98 lines
4.9 KiB
Markdown
98 lines
4.9 KiB
Markdown
# calcalist
|
|
|
|
Aggregates events between CalDAV, Google Calendar and iCal feeds. `README.md` is
|
|
the user-facing guide; the design rationale lives in the module doc comments,
|
|
beside the code it governs.
|
|
|
|
These conventions were `SPECS.md`'s, which has been retired now the roadmap is
|
|
finished. Git history holds it, and `TODO.md`, if the record is ever wanted.
|
|
|
|
## Development
|
|
|
|
Use devbox for dependency management and scripts. Use jj for version control
|
|
(colocated with git).
|
|
|
|
- `devbox run fmt` — format.
|
|
- `devbox run check` — format check, clippy with warnings denied, and tests.
|
|
This is the gate; jj has no commit step to hang a hook off, since the working
|
|
copy is itself a commit.
|
|
- `devbox run test` — tests only.
|
|
|
|
Keep `/target` in `.gitignore`: jj snapshots the working copy on every command,
|
|
with no staging step.
|
|
|
|
The integration tests in `tests/` need `radicale` and `pimsync`, both of which
|
|
devbox provides. Outside that shell they report what is missing and pass.
|
|
|
|
## Coding style
|
|
|
|
- Simple, readable, idiomatic.
|
|
- Explicit types for public APIs and important fields.
|
|
- Prefer immutable data structures.
|
|
- No global mutable state.
|
|
- No speculative features or dependencies.
|
|
- Keep functions short and single-purpose; extract helpers to avoid nesting.
|
|
- File and module naming: `snake_case.rs`. Types and traits: `PascalCase`.
|
|
Functions, variables and fields: `snake_case`. Constants:
|
|
`SCREAMING_SNAKE_CASE`.
|
|
- Run `devbox run check` before describing a change.
|
|
- Comment only when intent is not obvious from the code.
|
|
- Prefer composition and traits over deep type hierarchies.
|
|
|
|
## Decisions already taken
|
|
|
|
Settled after real design work. Reopen only with new information, not from
|
|
first principles.
|
|
|
|
**No configuration web interface.** Designed in full, then dropped. Its audience
|
|
would be people who find TOML hard — but every user must first create a Google
|
|
Cloud project, configure a consent screen and put a secret in a keyring, which
|
|
is a far higher bar than editing the config. Anyone who clears it can edit the
|
|
file; anyone who cannot never reaches the file. `doctor`, `status` and
|
|
`discover` cover what the interface was actually for.
|
|
|
|
**Bring-your-own OAuth client, not a shipped verified one.** Verification for
|
|
the Calendar scope needs a domain you own — a `github.io` address is rejected —
|
|
and Calendar API quota is per Cloud project, with charges announced for
|
|
exceeding it. A shipped client would pool every user's syncing into the
|
|
publisher's project, and their bill. Shipping an *unverified* shared client is
|
|
worse still: the 100-new-user cap is permanent for the project's lifetime.
|
|
|
|
**pimsync cannot reach Google, and never will.** It has no REST storage, and its
|
|
config exposes only HTTP Basic auth, which Google's CalDAV endpoint rejected in
|
|
March 2025. The Google leg is calcalist's own in every design.
|
|
|
|
**The config file is never machine-rewritten.** It is the portable,
|
|
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 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.
|