Sync develop presets through the library

Presets were the one piece of the photographer's work that never left
the device: faces, sidecars, albums, collections, keywords and camera
profiles all travel with the sync pass, the preset library did not.

It now goes to <derived>/presets/library.drpl. PresetLibrary::merge
decides each name against the base the last exchange left (kept per
library beside place.json), so presets added on two devices both
survive, a deletion reaches the other device instead of being restored
by it, and an edit outlives a deletion made elsewhere. The upload is
If-Match / If-None-Match on the server's copy, and a 412 reads and
merges again, so two devices exchanging at once cannot save over each
other. A server copy that will not parse (a newer build's) is left
alone, and a local file that will not read stops the exchange rather
than being taken for an empty library.

The develop view's save merges with the file when the sync changed it
since the view read it, and a sync that brought presets reloads and
redraws the list.

Also corrects the register, which still said camera profiles do not
sync.
This commit is contained in:
2026-10-04 00:50:09 -04:00
parent 26e50ae723
commit 83f0461ce7
9 changed files with 597 additions and 55 deletions
+14
View File
@@ -39,6 +39,20 @@ pub fn place_path(account: &Account) -> PathBuf {
data_root().join(account.namespace()).join("place.json")
}
/// TRACES: FR-DEV-6
/// The preset library as this device and this library's server last agreed
/// on it — the base `PresetLibrary::merge` decides deletions against.
///
/// Per library, beside the place, because each library's server keeps its
/// own copy. In the data directory for the reason the place is: a base the
/// system deleted would turn the next exchange into a union, and every
/// preset deleted since the last one would come back.
pub fn presets_base_path(account: &Account) -> PathBuf {
data_root()
.join(account.namespace())
.join("presets.base.drpl")
}
/// The directory every account's data hangs off.
///
/// **Not the cache directory, and on Android that distinction is the whole