You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit 677a546
Browse filesBrowse the repository at this point in the historyBrowse files
Copy file name to clipboardExpand all lines: CHANGELOG.md
+3-3Lines changed: 3 additions & 3 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -22,7 +22,7 @@ Renamed from mirador. The binary, the config directory, the `CARILLON_CONFIG` va
22
22
- Added the JMAP backend, polling `Email/changes` and resolving the changed ids through `Email/get` to keep the ones inside the watched mailbox (requires the `jmap` cargo feature).
23
23
- Added watch event hooks, under the `hook` table of the backend that fires them (`hooks` also reads). Each backend declares only the events its protocol can express, named after what it holds: `on-message-added` and `on-message-removed` over IMAP, JMAP and Maildir, `on-card-added`, `on-card-removed` and `on-card-changed` over CardDAV, the same three under `on-event-` and `on-task-` over CalDAV, and `on-item-` over a plain DAV collection. A hook a backend can never fire is refused when the configuration is read, naming the line and the events that backend has.
24
24
- Added `on-flag-added` and `on-flag-removed`, on the backends that have flags, firing once for each flag that moved so `$flag` always names the flag it fired for. An optional `flags = [...]` filter narrows them to the flags it lists, case-insensitively and with or without the leading `\` or `$`.
25
-
- A hook's notification is checked against the variables its event carries, when the configuration is read. `$id`and `$collection` are available everywhere, `$flag` to a flag hook, and the envelope names to the IMAP arrival hook alone, that being the only one anything resolves an envelope for. Anything else is refused, naming the hook and what it may use instead; write `${name:default}` where a hook can do without the value. A variable a hook may use but one item lacks, such as a message with no sender or an arrival whose resolution failed, expands to nothing rather than dropping the notification.
25
+
- A hook's notification is checked against the variables its event carries, when the configuration is read. `$id`is available everywhere, the collection under the name its backend configures it as (`$mailbox`, `$calendar`, `$addressbook`, `$collection`), `$flag` to a flag hook, and the envelope names to the IMAP arrival hook alone, that being the only one anything resolves an envelope for. Anything else is refused, naming the hook and what it may use instead; write `${name:default}` where a hook can do without the value. A variable a hook may use but one item lacks, such as a message with no sender or an arrival whose resolution failed, expands to nothing rather than dropping the notification.
- Added a global `-b/--backend {auto,imap,jmap,maildir,caldav,carddav,dav}` flag that pins which backend block is opened on accounts declaring more than one.
28
28
@@ -38,15 +38,15 @@ Renamed from mirador. The binary, the config directory, the `CARILLON_CONFIG` va
38
38
39
39
- Added three WebDAV backends, one per domain a collection can hold: `caldav` for a calendar, `carddav` for an addressbook, `dav` for a collection naming neither. All three share one shape (`server` is the DAV root, `auth` is basic, bearer or nothing, `watch.poll.interval` the gap between two RFC 6578 `sync-collection` reports, a minute by default) and differ only in the events they fire, so a card hook on a calendar is refused rather than silent. The report asks for etags only, so a poll never carries a contact or an event. A calendar is asked for its `supported-calendar-component-set` when the watch starts: one holding a single component answers for all its members at once, and one holding both events and tasks reads a member's `getcontenttype` to tell them apart, still without reading the member. Behind a `dav` cargo feature, on by default.
40
40
41
-
- One account is one backend watching one collection, one way. `mailbox` became a required `collection` (the old name still reads), `-m/--mailbox` is gone since what an account watches is its config, and each DAV backend's `server`became the DAV root with the collection as its path. Watching a second collection is a second account, which is also how it gets its own hooks.
41
+
- One account is one backend watching one collection, one way, and the collection lives under the backend that watches it, under the name its own domain uses: `imap.mailbox`, `jmap.mailbox`, `maildir.mailbox`, `caldav.calendar`, `carddav.addressbook`, `dav.collection`, all required. There is no account-level `collection` and no `-m/--mailbox`, since what an account watches is its config, and each DAV backend's `server`is the DAV root the collection hangs under. Watching a second collection is a second account, which is also how it gets its own hooks.
42
42
43
43
- Added `watch` under each backend, naming how an account learns about a change: `imap.watch.idle`, `imap.watch.poll.interval`, `jmap.watch.push.ping`, `jmap.watch.poll.interval`, `maildir.watch.poll.interval`, and `watch.poll.interval` under each of `caldav`, `carddav` and `dav`. Unset takes the best that backend has. Each backend declares only the methods it has, so asking Maildir to idle is a parse error naming the line rather than a failure at watch time.
44
44
45
45
- JMAP is pushed to again, over the RFC 8620 event-source stream, asking the server to close after each state change so the same socket carries the `Email/changes` round that follows. This closes a regression: the poll that replaced it when io-email was removed is now one method among the others.
46
46
47
47
- IMAP can poll, for a server whose idle cannot be trusted. It needed io-imap, where the watch coroutine now yields a wait for its driver to honour rather than holding a connection.
48
48
49
-
-The hook variable naming what changed is `$collection`; `$mailbox` still reaches a hook under its former name.
49
+
-One file no longer loads in `himalaya`, `himalaya-tui` and carillon alike, and the claim that it did has been dropped. An account block is still written the same way, but every backend block is `deny_unknown_fields` on each side and carries keys the others do not know: carillon has `mailbox`, `watch` and `hook` under `imap`, himalaya has `alpn` and `sort`.
50
50
51
51
- Ctrl+C is now honoured within about a second on every path: idling, polling, backing off, or resolving an arrival's envelope. Each connection carries a short read deadline and hands back the not-ready failures instead of letting the transport retry them away for a minute, which is what the deadline is for. Needs the unreleased io-imap watch option, so [Cargo.toml](./Cargo.toml) patches io-imap to a local path until it ships.
Copy file name to clipboardExpand all lines: README.md
+4-4Lines changed: 4 additions & 4 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -21,11 +21,11 @@ CLI to watch PIM collection changes, written in Rust
21
21
## Features
22
22
23
23
-**Six backends**, mail and not only mail: IMAP idles, JMAP is pushed to, Maildir re-lists, and CalDAV, CardDAV and plain WebDAV each ask a collection what moved.
24
-
-**One account, one collection, one method**: both are its config, so nothing is passed on the command line. Any backend can poll instead, for a server whose idle or push cannot be trusted.
24
+
-**One account, one collection, one method**: all three are its config, so nothing is passed on the command line. Each backend names the collection it watches in its own word (`imap.mailbox`, `caldav.calendar`, `carddav.addressbook`, `dav.collection`), and a hook templates against that same word. Any backend can poll instead, for a server whose idle or push cannot be trusted.
25
25
-**Events named after what they carry**: mail fires `on-message-*`, an addressbook `on-card-*`, a calendar `on-event-*` and `on-task-*`, an untyped collection `on-item-*`, and anything with flags `on-flag-*`, once per flag. Flag names are the same on every backend, so a filter written once fires everywhere.
26
26
-**Hooks under their backend**: a desktop notification, a shell command, or both, with the event's fields as placeholders. Each backend takes only the events it can report, and each hook only the variables its event carries, so a hook that could never fire is refused when the file is read.
27
27
-**Every account at once**, one thread each, reopening a dropped watch with a capped backoff and reading the credential again each time.
28
-
-**Shared configuration** with `himalaya` and `himalaya-tui`, secrets read from your own password manager.
28
+
-**Familiar configuration**: the account block keeps the shape `himalaya` and `himalaya-tui` read, and secrets come from your own password manager.
29
29
## Coverage
30
30
31
31
| Spec | What is covered |
@@ -104,9 +104,9 @@ nix run
104
104
105
105
Copy the annotated [config.sample.toml](./config.sample.toml), keep one backend and the hooks you want, delete the rest. It documents every key.
106
106
107
-
A configuration is read from `$XDG_CONFIG_HOME/carillon/config.toml`, `$HOME/.config/carillon/config.toml` or `$HOME/.carillonrc`, overridden by `-c <PATH>` or `CARILLON_CONFIG=<PATH>`. Those are the paths [himalaya](https://github.com/pimalaya/himalaya) and [himalaya-tui](https://github.com/pimalaya/himalaya-tui) read too, so one file backs all three.
107
+
A configuration is read from `$XDG_CONFIG_HOME/carillon/config.toml`, `$HOME/.config/carillon/config.toml` or `$HOME/.carillonrc`, overridden by `-c <PATH>` or `CARILLON_CONFIG=<PATH>`. Those are the paths [himalaya](https://github.com/pimalaya/himalaya) and [himalaya-tui](https://github.com/pimalaya/himalaya-tui) read too, and an account block is written the same way, but one file does not load in all three: every backend block is strict on each side and carries keys the others do not know.
108
108
109
-
An account declares one backend block (`imap`, `jmap`, `maildir`, `caldav`, `carddav`, `dav`), the `collection` it watches, and under that backend both how it watches (`watch`) and what it fires (`hook`). Declaring several backends is allowed; `-b/--backend` then picks one.
109
+
An account declares one backend block (`imap`, `jmap`, `maildir`, `caldav`, `carddav`, `dav`) carrying everything that backend needs: the collection it watchesunder its own name, how it watches (`watch`), and what it fires (`hook`). Declaring several backends is allowed; `-b/--backend` then picks one.
### Requirement: The collection belongs to the backend, under its own name
9
+
Each backend SHALL take the one collection it watches, required, under the name its domain uses: `imap.mailbox`, `jmap.mailbox`, `maildir.mailbox`, `caldav.calendar`, `carddav.addressbook` and `dav.collection`, the last being generic because a plain DAV collection has no domain to name. No account-level key SHALL name it, so an account block carries nothing that needs a backend to be understood. A hook SHALL template against the same name its backend configures, `$id` being the one variable every backend means the same way.
10
+
11
+
#### Scenario: A mail hook naming its mailbox
12
+
-**GIVEN** an account whose `imap.hook.on-message-added` summary reads `New mail in $mailbox`
13
+
-**WHEN** a message arrives
14
+
-**THEN** the notification names the mailbox from `imap.mailbox`
15
+
16
+
#### Scenario: A hook naming another backend's word
17
+
-**GIVEN** an account whose `caldav.hook.on-event-added` summary reads `$mailbox`
18
+
-**WHEN** the configuration is read
19
+
-**THEN** it is refused, since a calendar is configured and templated as `$calendar`
20
+
21
+
## MODIFIED Requirements
22
+
23
+
### Requirement: A watch runs from a TOML file
24
+
The daemon SHALL read its accounts from a TOML config file, resolved from an explicit path then the standard user paths. Each account SHALL carry at least one backend block (`imap`, `jmap`, `maildir`, `caldav`, `carddav`, `dav`), and each block SHALL carry the collection it watches, how it watches, and the hooks it fires. The account block SHALL keep the shape himalaya CLI and himalaya TUI read, and unknown keys SHALL be ignored there rather than refused, so an account can be recognised across the binaries. A whole file SHALL NOT be claimed to load in all three, since every backend block is strict on both sides and each has keys the other does not know.
25
+
26
+
#### Scenario: A local watch from a config file
27
+
-**GIVEN** a config describing one IMAP account with an `imap.mailbox` and an `imap.hook.on-message-added` notify hook
28
+
-**WHEN** the daemon runs and a message arrives
29
+
-**THEN** a desktop notification fires, with no network delivery and no account with any service
30
+
31
+
### Requirement: An account watches one collection, one way
32
+
An account SHALL watch the one collection its backend names, and MAY name the one method it watches with. Neither SHALL be overridable from the command line: what an account watches is its configuration, and watching a second collection is a second account, which is also how it gets its own hooks. Every backend SHALL read its collection the same way, the DAV ones included, whose `server` names the DAV root and whose collection is the path under it.
33
+
34
+
#### Scenario: A second collection
35
+
-**GIVEN** an account watching one mailbox
36
+
-**WHEN** a second mailbox is to be watched
37
+
-**THEN** it is a second account, with its own hooks, and no flag exists to ask for it
38
+
39
+
### Requirement: A hook templates against what its event carries
40
+
Each hook SHALL declare the variables it can fill, and a notification naming anything else SHALL be refused when the configuration is read. `$id` SHALL be available to every hook, the collection SHALL be available under the name its backend configures it as, `$flag` to a flag hook, and the envelope names only to the arrival hook of a backend that resolves one, which is IMAP alone. A `${name:default}` SHALL keep working whatever the name, a default being how a template says it can do without the value. A command SHALL NOT be validated, its placeholders reaching it as environment variables where an unset name is ordinary.
41
+
42
+
#### Scenario: A removal that asks for an envelope
43
+
-**GIVEN** an account whose `imap.hook.on-message-removed` notification body reads `$subject`
44
+
-**WHEN** the configuration is read
45
+
-**THEN** it is refused, naming the hook and the variables it may use, since an expunged message has no envelope to read
46
+
47
+
#### Scenario: A variable that is legitimate but absent
48
+
-**GIVEN** an account whose `imap.hook.on-message-added` notification summary reads `New mail from $sender`
49
+
-**WHEN** a message arrives whose envelope carries no sender, or whose resolution failed
50
+
-**THEN** the notification fires with that part empty, rather than being dropped
# Put the collection under its backend too, and name it for what it holds
9
+
10
+
## Why
11
+
12
+
`collection` is the last account-level key describing something only a backend knows. It is required whatever the account declares, so an account with an `imap` block has to spell its mailbox under a word chosen to also cover a calendar, and an account with a `caldav` block has to spell a calendar path under a word chosen to also cover a mailbox. Neither reads like the thing it names, and the account is the wrong place for either, since what it means depends entirely on which backend reads it.
13
+
14
+
The same holds for the variable a hook templates against. `$collection` is a lowest common denominator in a notification body about mail, where `$mailbox` is what the person writing it would say, and in one about a calendar, where `$calendar` is.
15
+
16
+
This is the same argument as [watch-under-backend](../watch-under-backend/proposal.md) and [hook-under-backend](../hook-under-backend/proposal.md), applied to the one key those two left behind. With it moved, an account block carries nothing that needs a backend to be understood.
17
+
18
+
## What
19
+
20
+
Each backend takes the collection it watches, required, under the name its own domain uses: `imap.mailbox`, `jmap.mailbox`, `maildir.mailbox`, `caldav.calendar`, `carddav.addressbook`, `dav.collection`. The account-level `collection` goes, and with it the `mailbox` alias it carried.
21
+
22
+
A hook templates against the same name its backend configures, so a mail hook says `$mailbox`, a calendar hook `$calendar`, an addressbook hook `$addressbook` and a plain DAV hook `$collection`. `$id` is unchanged, being the one thing every backend means the same way. A hook naming another backend's word is refused when the file is read, the same as any other variable its event cannot fill.
23
+
24
+
CalDAV and CardDAV go domain-specific rather than stopping at `collection`, since a backend that already fires `on-event-*` and `on-card-*` has no reason to describe what holds them in the generic word. Only a plain DAV collection keeps `collection`, having no domain to name.
25
+
26
+
## The himalaya compatibility claim is already false, and this does not break it further
27
+
28
+
The module header, the README and the spec all say one file backs carillon, himalaya CLI and himalaya TUI. That stopped being true when `watch` moved under the backends: himalaya's `ImapConfig` is `deny_unknown_fields` and has no `watch` and no `hook`, so it refuses a carillon config today. It fails the other way too, carillon's `ImapConfig` being equally strict and having no `alpn` and no `sort`.
29
+
30
+
So this change adds a third refused key to a block that is already refused, and the claim has to be corrected either way. It is corrected here rather than quietly left standing.
31
+
32
+
Restoring the promise is a separate decision worth taking on its own terms: dropping `deny_unknown_fields` from carillon's backend blocks would do it, and would cost nothing this repository built, since the load-time refusals live on the nested `watch` and `hook` tables rather than on the block itself. What it would cost is catching `imap.serverr`. Not decided here.
0 commit comments