Skip to content

Releases: t1nk333r/davkeep

0.7.3 — a given-up edit always comes back

Choose a tag to compare

@t1nk333r t1nk333r released this 24 Sep 02:22

Two fixes to what happens between sending an edit and reading the server back.

  • an edit the server gave back — refused by a read-only Collection, or beaten
    by a 412 — is now fetched again on every run until the server answers, so a
    fetch that fails once no longer leaves the phone's rejected text in place; a
    resource the server has since deleted is removed from the phone
  • deleting one occurrence of a recurring event that already had its own entry
    could stay on the phone forever: the deletion left no trace an upload could
    see, and the server kept the occurrence. The event is now sent without it.

0.7.2 — fixes from an independent review

Choose a tag to compare

@t1nk333r t1nk333r released this 24 Sep 00:46

Fixes from an independent review of everything written since 0.5 — three reviewers, a debugger, and every fix verified on an emulator before it shipped.

Could overwrite the server

A conflict on a recurring event could send your rejected edit without a version check. When the server had changed an event and a sync hit that conflict while you were editing one of its occurrences, the app gave up the edit for the main event but not the occurrence. The next sync then sent the rejected edit with no version check, replacing the server's copy. Giving up an edit is now all-or-nothing, and an update is never sent without the server's version.

Could leave your phone out of step

An edit refused by a read-only Collection could stay on the phone as if it had synced. On servers that report changes incrementally (Radicale is one), nothing ever fetched the server's copy back, so the refused text stayed on the phone looking current. Read-only is the default for every Collection. The server's copy is now fetched back in the same sync.

Toggling two Collections quickly, or while "Check collections" ran, could undo one of the toggles.

After a device restore, a Collection's settings screen closed as soon as it opened, which left the "send edits" switch out of reach.

Upgrading from before 0.7.0

Large address books now finish adopting photo baselines. Adoption used to be one batch, and above roughly 1,600 contacts that batch was too large to go through — so it failed, every run.

Same signing key as 0.6.0 and later, so this updates in place.

0.7.1 — upgrading keeps your contacts photos

Choose a tag to compare

@t1nk333r t1nk333r released this 23 Sep 22:56

Upgrading from an earlier release no longer costs a contact its photo on the server.

0.7.0 moved the baseline that decides whether a photo changed onto the raw contact. Contacts synced by an earlier version arrive with none — and an absent baseline reads as "changed", so the first edit to each of them, including one that only touched a name, would have replaced the server's PHOTO with the provider's re-encode.

A clean contact without a baseline is now given one before anything can be uploaded: the digest of the photo it already holds. A clean contact's rows are the server's, so that states a fact rather than assuming one. A contact with an unsent edit is skipped, because its photo may be that edit.

Verified by performing the upgrade rather than reasoning about it: 0.6.1 installed, a contact with a 19072-character inline photo synced, sync4 confirmed empty — the state every phone upgrading from an earlier release is in. This build installed over it, one sync, baseline adopted; then a name-only edit left the server's photo byte-identical.

Same signing key as 0.6.x and 0.7.0, so this updates in place.

0.7.0 — the answer names the row it is about

Choose a tag to compare

@t1nk333r t1nk333r released this 23 Sep 20:44

Two data-loss defects, both found by audit and both settled on a device rather than by reading the code.

An edit made while its own upload was in flight was acknowledged by that upload

A provider row can move between the moment the app reads it and the moment the server's answer is written back. The user's newer edit had its dirty flag cleared, was never uploaded, and was later overwritten by the server's copy.

Contacts now guard every such write on RawContacts.VERSION, the counter the provider moves on every change to a contact's rows, and commit everything that depends on that claim in the same transaction. Calendars keep no counter, so every row of a resource is marked DIRTY=2 before its body is read: every editor path in the calendar provider writes the literal 1, so a row that is no longer 2 was touched in flight and is not cleared.

Shown against a server stalled 18 seconds on writes: the phone uploaded one version, a second edit was typed during the request, the run stored the new ETag and left the row dirty, and the next run sent the newer version.

A contact's photo was replaced by an edit that never touched it

The baseline that decided whether a photo had changed was the photo row's DATA_VERSION, stored in that same row — and storing it bumps the version, because AOSP's trigger increments on every update. It was false the moment it was written, so every upload re-encoded the photo. An edit that changed only a name replaced the server's 8932-character PHOTO with a 6660-character re-encode.

The baseline is now a digest of the photo's bytes, which storing cannot invalidate. A name-only edit now leaves the server's photo byte-identical.

Signed with the project release key. Installing over a debug build needs an uninstall.

0.6.1 — the setup screen closes when the save worked

Choose a tag to compare

@t1nk333r t1nk333r released this 23 Sep 17:15

Saving an account left the form on screen with a report under it; the only way back to the account list was the Close button.

A save that reports nothing now closes the form and returns to the list, with a toast saying the one thing the list cannot — that the address answered just now.

Anything reported keeps the screen open, because that is where it can be acted on: the check runs after the account is stored, so a server that did not answer is a report about an account that exists, and the fields that caused it are right there. A certificate that would not import counts the same way.

Signed with the project release key, as 0.6.0 was. Installing over a debug build still needs an uninstall.

0.6.0 — DAVKeep, signed and licensed

Choose a tag to compare

@t1nk333r t1nk333r released this 23 Sep 16:53

First release under the name DAVKeep, and the first one that is a real release build rather than a debug APK.

This build

  • Per-collection settings screen. Sync, send-edits, colour and the server address moved off the account card onto a screen of their own.
  • Collection rows are two lines. They were five, most of it a wrapped URL; three collections used to fill the card.
  • Sending edits back to the serverPUT/DELETE with ETag preconditions, off per collection until you turn it on.
  • A real launcher icon in the APK. It existed only as XML before, so anything that reads a bitmap icon (F-Droid's index, some launchers, API 24-25) got nothing.
  • GPL-3.0-or-later, licence text now in the repository.

Installing

This APK is signed with the project's release key. Builds before it were debug-signed, so Android will refuse to install this over one of those — uninstall the old app first. Debug builds were also android:debuggable, which for an app that holds client certificates and passwords is not something to leave in anyone's hands.

Accounts survive an uninstall only through Export accounts on the settings screen; export before you uninstall, import after.

Obtainium

Add https://github.com/t1nk333r/davkeep as a GitHub source, or use the Add to Obtainium button on davkeep.app. The icon appears once the app is installed — Obtainium reads it from the installed package.

F-Droid

Not submitted yet. The recipe and what the submission involves are in fdroid/.

0.3.21 — an account page that reads, and two ways data went missing

Choose a tag to compare

@t1nk333r t1nk333r released this 19 Sep 17:49

Everything since 0.3.0: three bugs that made a sync's own report wrong, an account page you can read, and the two places this app could lose your data.

A delta sync no longer deletes what it just wrote. A REPORT that returned members alongside a new sync token saw those members deleted at the end of the same run — and the Collection reported complete, so the next run fetched them again and the one after deleted them again. Members fetched in a run are kept.

A calendar with no colour syncs. A plain calendar has no colour property, and treating one as required failed the whole Collection and blamed the server for a 404 it had never sent. A colour is now written only when there is one.

Discovery reads the body it parses. A peek that consumed the wrong stream stopped enumeration dead once a home set listing passed 2048 bytes — the kind of size a real account reaches.

The Add account page. An app bar that says where you are, a card per section with its own icon, and the headers, certificate and credentials folded behind Advanced, because a URL and a label are all most accounts need. The certificate's two buttons stack rather than wrapping, and Save no longer sits half inside the gesture zone.

Nothing is overwritten silently. The label is the Android account name, so saving a form whose label matched an existing account replaced that account's address, its password, its certificate and its Collection selection — none of which the form can show, and the label is derived from the host, so entering a URL that was already configured was enough to do it. The store refuses now, the screen says which account is in the way and what to do about it, and the note under the label turns red as soon as the collision is true rather than at Save.

Removal asks what happens to your synced data, and keeps it by default. Removing an account took every contact and event with it, and this app deleting no rows itself made no difference: the rows belong to the account, and Android's providers reap the rows of an account that is no longer registered. Removal now clears the configuration — address, credentials, certificate, Collection selection — and leaves the account and its data where they are, reading as Disconnected with a Configure that fills it in again. Deleting the data is the checkbox you have to reach for, because it is the one outcome that cannot be undone.

Also: Material 3 dialogs and fields throughout, the account card and the action area put on one emphasis ladder so the destructive control is the only thing wearing the error colour, a failure's cause in the log rather than only its class, and release builds signed from a key that never enters the repository.

Install: adb install -r dav-provider-android-0.3.21-debug.apk

0.3.0 — the last two spec gaps

Choose a tag to compare

@t1nk333r t1nk333r released this 19 Sep 03:18

Closes the two items in the spec's scheduling section that were specified but never built, and with them the feature list.

Per-account sync interval. Was a hardcoded fifteen minutes for every account. Now a setting — 15 minutes to daily — clamped at the framework's floor so a stored value from another build cannot become an interval the platform reads as as often as possible. Choosing one re-applies the schedule immediately rather than at the next run.

A battery-optimisation prompt, on evidence. Some Android builds suppress background work regardless of what the app asks for, and the app had no way to notice. It does now: a run arriving much later than the account's own interval counts the slots it skipped, a punctual run clears the count, and a deferral this app caused itself — unmetered-only holding a run back — counts as the slot firing, so the card cannot blame Android for something our own setting deferred.

After three missed slots the card offers the exemption, and dismissing it is permanent. The wording is deliberate: the last N scheduled syncs ran much later than the interval you set, and only then if Android is restricting this app's background work, exempting it can put the schedule back. What was observed, then a conditional. Not a diagnosis.

Under the hood this needed one new fact: a run had to be able to say whether it was automatic. It could not before, which is why a manual sync could mask a missed slot.

133 tests, up from 113. Debug-signed and sideload-only as before, and the known gaps are unchanged — the missing calendar event and the unlogged resource counts.

0.2.2 — the D, drawn properly

Choose a tag to compare

@t1nk333r t1nk333r released this 19 Sep 00:38

Supersedes 0.2.1, whose padlock icon was rejected on sight.

The icon is the app's own initial again, redrawn as one path with the counter cut out by evenOdd rather than painted over, so it survives themed and monochrome launchers. The background stays the blue the app has always used.

Note on the previous release: the 0.2.1 padlock was live for a few minutes and nobody should install it. Nothing else differs between the two builds.

Same debug-signed, sideload-only caveat as before, and the same known gaps — the missing calendar event and the unlogged resource counts remain.

0.2.1 — the icon, and two honesty fixes

Choose a tag to compare

@t1nk333r t1nk333r released this 19 Sep 00:16

What this is

The first published build. A working prototype of an Android CalDAV/CardDAV
sync provider that can attach arbitrary HTTP headers and a client certificate
per account — the gap that makes a DAV server behind an identity-aware proxy
unreachable from mainstream Android clients.

Verified against a real server: 368 contacts and 13 events synced
through a certificate-gated connection, with no VPN and no custom headers, and
a second run writing nothing, so repeat syncs update rather than duplicate.

In this build

  • A real icon. The previous one was a placeholder: a stock sync glyph at a
    third of the width, which launchers shrank to a smudge. This is a padlock
    whose shackle sweeps into an arrowhead, drawn on the adaptive grid, with the
    keyhole cut out rather than painted over so it survives themed and monochrome
    launchers.
  • Per-authority status. Contacts and calendars run separately; the account
    line previously showed whichever finished last, so a failed calendar was
    reported as "All Collections synced" whenever contacts succeeded afterwards.
  • The log screen matches the app. It was styled from a hardcoded dark
    palette; it now follows the theme, and the level designators keep the red,
    yellow and green that carry meaning.
  • Screenshots of the account and log screens in the README.

Installing

The APK is debug-signed — installable by sideloading, not intended for
distribution. Android will warn about an unknown source; that warning is
accurate.

It is read-only by design: the server is the source of truth and phone-side
edits are never pushed.

Known gaps

  • One calendar event present on the server has no row in the app, and a repeat
    sync reports success regardless. The log records rows written but not
    resources read, so the app cannot currently diagnose this itself.
  • The calendar path has not been tested against an event with a recurrence
    exception.
  • Two-way sync, tasks, and scheduling beyond a fixed interval are out of scope.