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 08ea378
Browse filesBrowse the repository at this point in the historyBrowse files
Copy file name to clipboardExpand all lines: CHANGELOG.md
+8-8Lines changed: 8 additions & 8 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -14,17 +14,17 @@ Renamed from mirador. The binary, the config directory, the `CARILLON_CONFIG` va
14
14
15
15
- Reopen a watch that ends. A session lost to a dropped connection is retried with a capped exponential backoff that a healthy session resets, and the credential is resolved again on every attempt rather than held, so a rotated secret is picked up by the next reconnect.
16
16
17
-
- Resolve an arrival's envelope for `on-message-added`. A watch learns of a new item by its id, not its subject, so the envelope is fetched on a second connection, only when that hook is configured, and only over IMAP. Adds `$date` to the templates beside `$subject`, `$sender` and `$recipient`.
17
+
- Resolve an arrival's envelope for `on-message-added`, only when that hook is configured. A watch learns of a new item by its id, not its subject, so IMAP fetches the envelope on a second connection, never the one holding the watch. JMAP takes it from the `Email/get` its round already makes, asking for `subject`, `receivedAt`, `from` and `to`only when a hook consumes them, so an arrival costs no second request there. Adds `$date` to the templates beside `$subject`, `$sender` and `$recipient`.
18
18
19
19
- Added `imap.sasl-ir`, forcing the RFC 4959 SASL-IR initial response on or off for providers such as Coremail (126.com, 163.com) that advertise the capability and then reject the inline form.
20
20
21
21
- Initiated the project from [Himalaya CLI](https://github.com/pimalaya/himalaya) and [Neverest CLI](https://github.com/pimalaya/neverest).
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
-
- 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.
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). It watches over the RFC 8620 §7.3 event stream by default, on a connection of its own, since a stream asked to close after its state change is the connection the server hangs up. A round that fails is retried once on a fresh connection, which is what a polling watch needs when its server closed the connection it slept on.
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, and the same three under `on-event-` and `on-task-` over CalDAV. 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` 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.
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`), `$flag` to a flag hook, and the envelope names to the IMAP and JMAP arrival hooks alone, those being the two that read one: IMAP on a second connection, JMAP from the `Email/get` its round already makes. 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.
27
+
- Added a global `-b/--backend {auto,imap,jmap,maildir,caldav,carddav}` flag that pins which backend block is opened on accounts declaring more than one.
28
28
29
29
### Changed
30
30
@@ -36,11 +36,11 @@ Renamed from mirador. The binary, the config directory, the `CARILLON_CONFIG` va
36
36
37
37
- The Maildir mailbox is resolved through io-maildir's store rather than by joining the configured root with the mailbox name. Joining by hand bypassed the layout, so on a Maildir++ store a subfolder resolved to a directory that does not exist and the watch reported nothing, forever, without ever erroring. Resolving through the store also checks that `cur`, `new` and `tmp` are there, so a wrong mailbox name fails at startup; `.` and `INBOX` both name the root.
38
38
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.
39
+
- Added two WebDAV backends, one per domain a collection holds: `caldav` for a calendar, `carddav` for an addressbook. Both 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, 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.
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` and `carddav.addressbook`, 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
-
- 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.
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`and `carddav`. 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.
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
@@ -20,9 +20,9 @@ CLI to watch PIM collection changes, written in Rust
20
20
21
21
## Features
22
22
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**: 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
-
-**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.
23
+
-**Five backends**, mail and not only mail: IMAP idles, JMAP is pushed to, Maildir re-lists, and CalDAVand CardDAV each ask a collection what moved.
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`), and a hook templates against that same word. Any backend can poll instead, for a server whose idle or push cannot be trusted.
25
+
-**Events named after what they carry**: mail fires `on-message-*`, an addressbook `on-card-*`, a calendar `on-event-*` and `on-task-*`, 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
28
-**Familiar configuration**: the account block keeps the shape `himalaya` and `himalaya-tui` read, and secrets come from your own password manager.
@@ -106,7 +106,7 @@ Copy the annotated [config.sample.toml](./config.sample.toml), keep one backend
106
106
107
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`) carrying everything that backend needs: the collection it watches under its own name, 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`) carrying everything that backend needs: the collection it watches under its own name, how it watches (`watch`), and what it fires (`hook`). Declaring several backends is allowed; `-b/--backend` then picks one.
### Requirement: A watch survives the change it reports
9
+
A watch SHALL keep working across the changes it reports. A connection a backend's own protocol closes as part of reporting SHALL be reopened before the next request rather than written into, and a round that fails SHALL be retried once on a fresh connection before the session is given up. A round SHALL advance no state it did not complete, so running it twice reports nothing twice.
10
+
11
+
#### Scenario: A JMAP event stream that closes after its state change
12
+
-**GIVEN** a JMAP account watching over the event stream, which the server closes after reporting one state change
13
+
-**WHEN** a message arrives
14
+
-**THEN** the round that follows runs on a fresh connection and fires the hook, rather than losing the session and re-baselining the change away
15
+
16
+
#### Scenario: A polling watch whose idle connection was closed
17
+
-**GIVEN** a JMAP account polling on an interval, whose server closed the connection while it slept
18
+
-**WHEN** the next round runs
19
+
-**THEN** it reconnects and runs again, and the session is given up only if that also fails
20
+
21
+
## MODIFIED Requirements
22
+
23
+
### Requirement: Arrivals are resolved only when a hook wants them
24
+
A watch learns that an item arrived, and sometimes what it says. A backend SHALL report an arrival together with the summary it already read, and SHALL read one it does not have only when the active backend configures the arrival hook of that domain. JMAP SHALL take its summary from the `Email/get` its round already makes, asking for the envelope properties only when a hook wants them. IMAP SHALL read one on a second connection, never the one holding the watch, an IMAP delta naming a UID and nothing more. A backend that can read no envelope SHALL leave the summary empty, and a resolution failure SHALL degrade to an unresolved event rather than ending the watch.
25
+
26
+
#### Scenario: An account with no arrival hook
27
+
-**GIVEN** an account whose only hook is `imap.hook.on-flag-added`
28
+
-**WHEN** a message arrives
29
+
-**THEN** no envelope is fetched and no second connection is opened
30
+
31
+
#### Scenario: A JMAP arrival with an envelope
32
+
-**GIVEN** a JMAP account whose `jmap.hook.on-message-added` notification names `$subject` and `$sender`
33
+
-**WHEN** a message arrives
34
+
-**THEN** both are filled from the round's own `Email/get`, with no second request
35
+
36
+
### Requirement: A WebDAV collection is watchable
37
+
The daemon SHALL watch a WebDAV collection by polling an RFC 6578 `sync-collection` report, under whichever of `caldav` and `carddav` names the domain it holds, both sharing one server, authentication and poll shape. It SHALL request `getetag`, and the content type only where a mixed calendar needs it, so a poll never carries a contact or an event; it SHALL keep an href to etag and domain picture of the collection, so that a member it has never seen reads as an arrival, a known member whose etag moved reads as an edit, and a member that vanished is still reported under the domain it had. A truncated report SHALL be drained immediately rather than at the next interval, and a sync token the server rejects SHALL cause a re-enumeration, which reports nothing because a re-baseline is not news. No backend SHALL watch a collection holding neither calendars nor contacts: the domains that exist have their own backend, and a collection naming none of them has no hook worth firing.
38
+
39
+
#### Scenario: A contact is edited
40
+
-**GIVEN** a CardDAV account watching an addressbook it has already enumerated
41
+
-**WHEN** a contact is edited and its etag moves
42
+
-**THEN**`on-card-changed` fires for that href, and no vCard is read
43
+
44
+
#### Scenario: The server forgets its history
45
+
-**GIVEN** a watch holding a sync token the server no longer honours
46
+
-**WHEN** the next report is refused
47
+
-**THEN** the collection is enumerated again, no event is fired for what was already there, and the watch continues from the fresh token
48
+
49
+
### Requirement: An event is named after its domain
50
+
A hook SHALL be named after what it carries. Mail SHALL be `on-message-added` and `on-message-removed`, whichever of IMAP, JMAP and Maildir reports it. A CardDAV addressbook SHALL be `on-card-added`, `on-card-removed` and `on-card-changed`, and a CalDAV calendar the same three under `on-event-` and `on-task-`. A backend SHALL take only the domains it holds, so the domain a hook names is checked when the configuration is read rather than assumed while the watch runs. The domain SHALL be carried by the event itself, so that one hook runner still serves every backend.
51
+
52
+
#### Scenario: A calendar hook on an addressbook
53
+
-**GIVEN** an account configuring `carddav.hook.on-event-added`
54
+
-**WHEN** the configuration is read
55
+
-**THEN** it is refused, and the account is pointed at `on-card-added`
56
+
57
+
### Requirement: The collection belongs to the backend, under its own name
58
+
Each backend SHALL take the one collection it watches, required, under the name its domain uses: `imap.mailbox`, `jmap.mailbox`, `maildir.mailbox`, `caldav.calendar` and `carddav.addressbook`. 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.
59
+
60
+
#### Scenario: A mail hook naming its mailbox
61
+
-**GIVEN** an account whose `imap.hook.on-message-added` summary reads `New mail in $mailbox`
62
+
-**WHEN** a message arrives
63
+
-**THEN** the notification names the mailbox from `imap.mailbox`
64
+
65
+
#### Scenario: A hook naming another backend's word
66
+
-**GIVEN** an account whose `caldav.hook.on-event-added` summary reads `$mailbox`
67
+
-**WHEN** the configuration is read
68
+
-**THEN** it is refused, since a calendar is configured and templated as `$calendar`
69
+
70
+
## REMOVED Requirements
71
+
72
+
### Requirement: The untyped DAV backend
73
+
**Reason**: It was the whole WebDAV backend before CalDAV and CardDAV were named out of it, and what remained was a collection that is neither, whose members were called items because nothing better could be said. The two domains a PIM notifier watches now have a backend each, so nothing is left for the generic one to serve.
0 commit comments