docs: decide to build our own store and delete the vendored provider
The vendored dmfs provider was kept on the grounds that it hands us the sync bookkeeping for free. The phase-1 sync audit measured that bookkeeping and found most of it broken, absent, or unusable: _DIRTY not set on delete, no home for a per-collection sync token, read-only collections inexpressible, ACCOUNT_TYPE write-once so enabling sync is a full migration, and cleanUpLists able to delete a user's lists after a backup restore. Sixteen findings are provider-imposed rather than platform- or protocol-imposed. Costing the alternative showed the swap is far smaller than assumed. TasksDataSource is already a 14-method, domain-shaped interface; exactly one file above the data layer references TasksContract. The work is a second implementation behind an interface built for it, not a rewrite. Against ~5 weeks to build, owning the store removes 2.5-4 weeks from the sync plan, and 8,200 of the vendored 14,555 lines are things we would never write - 23 migrations from a 2013 schema, 798 lines of full-text search the app has zero call sites for, and 1,581 lines of a type-safe layer over ContentValues that Room deletes. External mode (OpenTasks, tasks.org) is unaffected and keeps every file that describes somebody else's schema. STORAGE-DECISION.md is the reasoning; OWN-STORE.md is the architecture and the six-phase plan. :provider stays in-tree until phase 5 so recurrence parity can be tested against it before it goes. Also corrected here: the provider's JVM test count (51 -> 56, measured from the test-results XML) and a fourth site of the debunked "switching sync on is never a migration" claim, in StorageMode.kt. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -5,9 +5,14 @@ package de.jeanlucmakiola.agendula.data.tasks
|
||||
*
|
||||
* Only two values, though the document describes three modes. **Synced is not a
|
||||
* third store**: it is [LOCAL] with an account attached, so it is derived state
|
||||
* (does an account of ours exist?) rather than something the user picks. That
|
||||
* also means switching sync on is never a migration. Adding a `SYNCED` constant
|
||||
* here would imply otherwise.
|
||||
* (does an account of ours exist?) rather than something the user picks. Adding
|
||||
* a `SYNCED` constant here would imply otherwise.
|
||||
*
|
||||
* ⚠️ This used to add "so switching sync on is never a migration". That is wrong.
|
||||
* `TaskLists.ACCOUNT_TYPE` is write-once in the provider — `processors/lists/
|
||||
* Validating.java:68-76` throws `IllegalArgumentException` on any attempt to
|
||||
* change it — so attaching an account to an existing local list means recreating
|
||||
* every list and every task under the new account. See `docs/SYNC.md`.
|
||||
*
|
||||
* Nothing above the data layer reads this; it selects an authority for
|
||||
* [ProviderResolver] and stops there.
|
||||
|
||||
@@ -57,12 +57,23 @@ object ICalendarWriter {
|
||||
/**
|
||||
* The UID to write for [task].
|
||||
*
|
||||
* Local tasks have none: the dmfs provider only permits a sync adapter to
|
||||
* assign `_uid`, so in Local mode every task arrives here with `uid == null`.
|
||||
* A VTODO without a UID is invalid and, worse, un-mergeable — re-importing a
|
||||
* backup would duplicate every task instead of matching it. So we synthesise
|
||||
* one from the row id, which is stable for as long as the row is, and tag it
|
||||
* with our own domain so a synthesised UID is recognisable as such.
|
||||
* Local tasks have none, because nothing assigns one: the provider never
|
||||
* generates a `_uid` itself, and our write path does not set it either, so in
|
||||
* Local mode every task arrives here with `uid == null`.
|
||||
*
|
||||
* Note this is a gap we leave open, not one the provider imposes.
|
||||
* `processors/tasks/Validating.java:92-96` restricts `_uid` to sync adapters
|
||||
* on *update* only; `insert` does not check it, so any caller may assign a UID
|
||||
* at creation. Doing that would be strictly better than synthesising here —
|
||||
* see `docs/SYNC.md`, where it is a phase-1 item, because a real UID minted at
|
||||
* creation is what lets a local task later be pushed to CalDAV without
|
||||
* duplicating.
|
||||
*
|
||||
* Until then: a VTODO without a UID is invalid and, worse, un-mergeable —
|
||||
* re-importing a backup would duplicate every task instead of matching it. So
|
||||
* we synthesise one from the row id, which is stable for as long as the row
|
||||
* is, and tag it with our own domain so a synthesised UID is recognisable as
|
||||
* such.
|
||||
*/
|
||||
fun uidFor(task: ExportTask): String =
|
||||
task.uid?.takeIf { it.isNotBlank() } ?: "agendula-${task.taskId}@jeanlucmakiola.de"
|
||||
|
||||
Reference in New Issue
Block a user