feat(calendar): Abgleich nur der Aenderungen (sync-collection, RFC 6578)

Bis hierher holte jeder Durchgang die volle Terminliste: ctag abfragen,
Aenderung sehen, alles neu laden. Eine verschobene Besprechung kostete
damit so viel wie ein Neuanfang -- bei einem Jahr Termine, alle fuenfzehn
Minuten, auf Akku und Datenvolumen des Telefons.

Kein zweiter Zaehler: Das ctag **ist** bereits eine Zahl je Kalender, die
bei jedem Schreiben um eins steigt. Jedes Objekt merkt sich in sync_seq den
Stand, bei dem es zuletzt angefasst wurde (Migration 24).

Grabsteine (cal_tombstones) halten geloeschte UIDs fest -- ohne sie bliebe
ein geloeschter Termin auf jedem Geraet stehen, das gerade nicht verbunden
war. Sie stehen 180 Tage; beim Raeumen wandert sync_floor mit, und ein
Token darunter bekommt 403 <valid-sync-token/> statt einer Antwort mit
fehlenden Loeschungen.

go-webdav kennt fuer REPORT nur calendar-query und calendar-multiget, und
weder sync-token noch supported-report-set. Beide gehen denselben Weg wie
getctag und calendar-color. Ohne supported-report-set probiert DAVx5
sync-collection gar nicht erst aus.

Zwei Fehler noch vor dem Ausliefern gefunden:

* Die Anfuehrungszeichen am ETag standen **innerhalb** der Maskierung --
  xml.EscapeText machte daraus &#34;. Gueltig, aber genug Clients
  vergleichen ETags roh und saehen bei jedem Durchgang eine Aenderung.
* pruneTombstones nahm die erste Zeile eines DELETE ... RETURNING als
  Maximum. Das hat keine Reihenfolge -- der sync_floor waere zu niedrig
  gesetzt worden, und ein Client dazwischen haette Loeschungen verpasst.

caldav-selbsttest.py: 62 -> 86 Pruefungen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
marcandClaude Opus 5 committed 2026-09-12 00:03:08 +02:00
1 parent df9d2d6ded
commit 8fdde182b7
11 files changed
+728 -33

No files matched your search

+47 -1
View File
@@ -86,7 +86,7 @@ Die abgeleiteten Spalten sind nur zum Vorsortieren da.
| **1** | CalDAV lesend und schreibend, ein Kalender, Anmeldung über App-Passwörter, Auffindpfade (`/.well-known/caldav`, `current-user-principal`, `calendar-home-set`). Ziel: DAVx5 gleicht ab. | ✅ |
| **2** | Mehrere Kalender, Farbe, Umbenennen, Löschen. Export als `.ics`, Import einer bestehenden Datei. | ✅ |
| **3** | Weboberfläche in Aether: Monat, Woche, Liste; Termin anlegen, ändern, löschen. Der Teil, der Spaß macht – und dessen Fehler man **sieht**. | ✅ |
| **4** | `sync-collection` statt Vollabgleich, Aufgabenansicht, Teilen mit anderen Nutzern, öffentlicher Abo-Link, Kalender in der HoneyOS-Leiste. | offen |
| **4** | `sync-collection` ✅, Aufgabenansicht, Teilen mit anderen Nutzern, öffentlicher Abo-Link, Kalender in der HoneyOS-Leiste. | läuft |
| **5** | Kontakte (CardDAV). Dieselbe Maschinerie, dieselbe Bibliothek – und **einfacher**, weil es dort keine Wiederholungen gibt. | später entscheiden |
Über die Phasen hinaus dazugekommen, weil es sich beim Prüfen als nötig
@@ -109,6 +109,52 @@ erwies:
Wege führen auf dieselben Daten, aber über völlig verschiedenen Code – ein
grüner Lauf des einen sagt nichts über den anderen.
## Abgleich nur der Änderungen (Phase 4, erledigt 12.09.2026)
Bis hierher lief jeder Durchgang so: Der Client fragt das `ctag` ab, sieht es
verändert und holt daraufhin die **volle** Terminliste. Eine verschobene
Besprechung kostet damit denselben Verkehr wie ein Neuanfang – bei einem Jahr
Termine, jedes Mal, alle fünfzehn Minuten. Bezahlt wird das in Akku und
Datenvolumen des Telefons.
`sync-collection` (RFC 6578) dreht das um: Der Client sagt „ich bin auf Stand
41" und bekommt zurück, was seither geschah, plus ein neues Token.
**Kein zweiter Zähler.** Das `ctag` *ist* bereits eine Zahl je Kalender, die
bei jedem Schreiben um eins steigt – also genau die Sequenz, die hier gebraucht
wird. Jedes Objekt merkt sich in `sync_seq` den Stand, bei dem es zuletzt
angefasst wurde. Ein eigener Sync-Zähler daneben wäre eine zweite Wahrheit über
denselben Sachverhalt gewesen, und zwei Wahrheiten laufen auseinander.
**Grabsteine.** Eine gelöschte Zeile kann nicht mehr erzählen, dass es sie gab.
`cal_tombstones` hält deshalb UID und Stand jedes gelöschten Termins fest –
sonst bliebe er auf jedem Gerät stehen, das während des Löschens nicht
verbunden war. Das ist der teuerste Fehler dieser Art, weil er unsichtbar ist.
Grabsteine stehen 180 Tage; beim Räumen wandert `sync_floor` mit. Ein Token
darunter lässt sich nicht mehr vollständig beantworten, und dann ist die
ehrliche Antwort `403 <valid-sync-token/>` – der Client gleicht daraufhin voll
ab. Eine lückenhafte Antwort wäre schlimmer als eine Absage: Der Client hielte
sie für den ganzen Unterschied.
Drei Feinheiten, die beim Bauen auffielen:
- **Beim ersten Abgleich keine Grabsteine.** Wer den Kalender noch gar nicht
kennt, braucht keine Löschmeldungen für Termine, die er nie gesehen hat.
- **Auferstehung.** Wird eine gelöschte UID neu angelegt, muss ihr Grabstein
weg – sonst bekäme der Client die Datei und ihre Todesmeldung im selben Zug.
- **Anführungszeichen am ETag stehen außerhalb der Maskierung.** `xml.EscapeText`
macht daraus sonst `&#34;`; gültiges XML, aber genug Clients vergleichen
ETags als rohe Zeichenkette und sähen bei jedem Durchgang eine Änderung.
Dazu zwei Eigenschaften, die go-webdav gar nicht kennt und die jetzt denselben
Weg gehen wie `getctag` und `calendar-color` (`propfind.go`): `sync-token` und
`supported-report-set`. Ohne die zweite probiert DAVx5 `sync-collection` nicht
einmal aus.
Geprüft in `scripts/caldav-selbsttest.py` (86 statt 62 Prüfungen), darunter der
Fall, auf den es ankommt: *ohne Änderung kommt nichts zurück*.
## Teilen – drei Bedeutungen, zwei davon gehen
„Geteilter Kalender" meint je nach Person etwas anderes: