f8addbee5390220dbbf3021c0d9399588565f46c
5
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
896188a489 |
Read the sidecars other editors write, and write them back on request
Benchmarks / CPU and I/O (per commit) (push) Successful in 10m59s
Benchmarks / Frame budget (on demand) (push) Skipped
Build and test / Desktop (Linux) (push) Successful in 1h33m36s
Build and test / Layer separation (push) Successful in 1m2s
Traceability / Requirement traces (push) Successful in 1m25s
🐳 Android image / Build and push (push) Successful in 9s
Build and test / android-image (push) Successful in 9s
Build and test / Android (aarch64) (push) Successful in 56m59s
FR-CAT-13 asked for standard XMP and `core/dr-xmp` answered the file: it
has read and written `dc:subject`, `xmp:Rating`, `xmp:Label` and the IPTC
core since
|
||
|
|
ca2a135e28 |
Let two devices name the same photograph's version the same way
A version's uuid is the identity a cross-device merge keys on, and it was minted at random, per catalog, per image. Two devices indexing one Nextcloud library therefore held two different uuids for the same photograph — so the sidecar they shared collected a `default = 1` block each, `Version::merge` was never handed a matching pair to reconcile, and an afternoon's culling on the tablet did not exist as far as the laptop was concerned. `crate::merge` has said so in a comment since it was written: version uuids do not reconcile across devices, a uuid-keyed join unions nothing, so keywords are landed on the local default version instead. It named the problem and worked around it. `rating`'s own comment asserted the opposite — that generating the uuid here was what made it a cross-device identity — and `library::amend` repeated the claim. Uniqueness was never the difficulty; agreement was. `derived_version_uuid` computes it from `oc:fileid` instead. The server assigns that integer, every client pointed at the library sees the same one, and it survives a server-side rename and move — the three properties that already made `ASSIGN_BY_FILE_ID` prefer it to a content hash. The layout is a UUIDv8 (RFC 9562, an application-defined form) carrying all sixty-four bits verbatim across the variable fields with a fixed tag in the node field, so the mapping is injective by construction rather than by a hash's good behaviour, and a uuid in a sidecar can be read back to the file it belongs to by eye. A library with no server behind it has no shared identity to derive and keeps a generated one. The split is still reachable there if the folder is synced by something else; `Sidecar::fuse_default_versions` repairs that case rather than preventing it. Deriving it for new rows alone would have fixed nothing — every image in an existing library already has a version, so every one of them would have carried on writing to its own rival identity. `align_default_version_uuids` moves them, and runs from `schema::backfill` on every catalog open. It selects on the tag in SQL, so a catalog already realigned matches no rows and writes nothing, and it declines rather than fails where a virtual copy already holds the target. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
62188ec740 |
Keep both devices' keywords when the catalogs meet
Keywords are catalog state, and the catalog syncs. Without this, two devices keywording the same library would resolve to whichever synced last, and an afternoon of work would vanish with no sign it had ever happened. The vocabulary merges per row on the rule collections already use: revision first, timestamp only to break a tie, so a device with a skewed clock cannot win by having the wrong idea of the time. Assignments merge as a set union, which is FR-NC-9's principle applied to metadata instead of edit nodes — disjoint work survives on both sides. Three things needed care and are commented where they happen: A deletion travels *by name*, not by identity. Both devices may have minted their own uuid for one word before they ever synced, so deleting by uuid would tombstone a row nothing was assigned to and leave every photograph still carrying the word. The union then refuses to readmit a word a winning tombstone has just removed — without that filter the remote's live assignments would resurrect it on the very same pass. Images are resolved by the server's file id first and the content hash second. Membership has always used the hash alone, but the hash is computed only when import dedup or a reconnect asks for it, which for most libraries is never — so a hash-only union would have quietly done nothing for the ordinary photograph. A word lands on the local default version. Version uuids do not reconcile in the catalog at all: ensure_default_versions mints a fresh one per device, so a uuid-keyed join would have unioned nothing. Removal still does not propagate. That is the trade collection membership already makes, for the same reason — an unwanted keyword is removed again in a second, and a silently lost afternoon is not recoverable at all. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
03326242a1 |
Make the CI checks say what they mean, and format the workspace
The Android job's "Verify minimum API level" step has never verified the minimum API level. It took the first `*.so` anywhere under the target directory, which is a host proc-macro from debug/deps — an x86-64 object built by the runner's gcc, whose .comment section cannot mention Android and so can never contradict the expected value. It now reads the artifact under the target triple, compares against MIN_API parsed from the Dockerfile rather than a second copy of the number, and fails on a mismatch. Both sides are checked non-empty first: two failed parses would otherwise compare equal and pass, which is the same silent success in a new costume. The Android image installs one SDK package per layer and keeps the output. sdkmanager is a JVM program that aborts when it cannot get memory, and the single `> /dev/null` step reported that as a bare "exit code 134" while a retry re-downloaded everything that had already succeeded. tools/ci-local.sh runs all four jobs — desktop, android, layering, traceability — against the host toolchain, which is pinned to the same 1.92.0 CI installs. Its matrix check compares regeneration against the working tree rather than against HEAD: CI starts from a clean checkout, so git's answer is the right one there and reports every local run stale here. The rest is rustfmt across the workspace, and the clippy findings that surfaced once it did: manual_contains in dr-thumbs and collections_ui, a map iterated as pairs for its keys, an index loop over a slice, and two runtime assertions on a constant now made at compile time. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
d5b1f6bff5 |
Add collections, ratings, and soft delete to the catalog
Three features over a shared schema migration. Collections: a tree of manual collections plus smart collections whose membership *is* their stored selector. Dropping images onto a smart collection is refused rather than silently discarded, so the UI can say why the drop did nothing — member rows there would be a second source of truth that nothing reads. Ratings: the star and pick/reject axes, kept independent. Trash: soft delete to a folder, then permanent delete. Catalog::open now backfills after migrating. A migration adds a column but cannot know what the value should be for rows that already existed; backfilling on open is what stops those rows being silently partial. Timeline queries exclude shadowed JPEGs, which would otherwise double every paired shot in the histogram, and gain a range-bounded variant so zooming in returns finer buckets rather than the same coarse ones with the ends cropped. Assisted-by: LLM |