Verify the catalog snapshot before it is sent, and after it lands

Two checks around the upload, both cheap next to what they prevent.

Before: the snapshot is quick_checked before it leaves. It is the copy
every other device merges from, and a damaged one costs each of them a
download, a failed merge and a refusal to push.

After: the staged upload's size on the server is compared to the bytes
sent before it is rotated into place. A chunked upload is assembled
server-side, and an assembly that goes wrong is a file of plausible
size no device can open — caught here, on the device that caused it,
for one listing; otherwise on every other device, after the fact. A
mismatch, or a size the server will not confirm, discards the upload
and leaves the current copy and its generations untouched.
This commit is contained in:
2026-09-13 19:31:58 +02:00
parent 2ce0fcc74a
commit f100db89ca
3 changed files with 110 additions and 18 deletions
+22
View File
@@ -54,9 +54,31 @@ pub fn checkpoint(conn: &Connection) -> Result<(), CatalogError> {
pub fn snapshot_for_upload(conn: &Connection, dest: &Path) -> Result<(), CatalogError> {
let out = copy_to(conn, dest)?;
strip_face_crops(&out)?;
verify_snapshot(&out)?;
Ok(())
}
/// TRACES: NFR-R2
/// Refuse to hand over a snapshot that will not pass `quick_check`.
///
/// The upload is the copy every other device merges from, and a damaged one
/// costs far more than the check: each device downloads it, fails, and — for
/// a week, once — declines to push over it. `quick_check` reads every page
/// but skips index verification, which is the affordable version of "is this
/// a database" on a 40 MB file that has just been written and is still in the
/// page cache. A failure here is [`CatalogError::Corrupt`], the same thing a
/// receiving device would have said, so the sync reports it the same way.
fn verify_snapshot(snapshot: &Connection) -> Result<(), CatalogError> {
let verdict: String = snapshot.query_row("PRAGMA quick_check", [], |r| r.get(0))?;
if verdict == "ok" {
Ok(())
} else {
Err(CatalogError::Corrupt {
detail: format!("the snapshot for upload failed quick_check: {verdict}"),
})
}
}
/// Checkpoint, then copy the whole database to `dest`, and hand back the
/// connection to the copy.
///
+5 -5
View File
@@ -10,7 +10,7 @@ Denominators are parsed from [`requirements.md`](requirements.md) at run time, n
| Metric | Value |
|---|---|
| Source files scanned | 354 |
| TRACES tags found | 1474 |
| TRACES tags found | 1476 |
| Requirements defined | 191 |
| Requirements covered | 140 |
| **Coverage** | **73.3%** (140/191) |
@@ -106,13 +106,13 @@ _None._
| FR-NC-6 | [`ui/dr-ui/src/activity.rs:1`](../ui/dr-ui/src/activity.rs#L1) |
| FR-NC-6a | [`core/dr-catalog/src/cache.rs:1`](../core/dr-catalog/src/cache.rs#L1), [`core/dr-catalog/src/cache.rs:225`](../core/dr-catalog/src/cache.rs#L225), [`core/dr-catalog/src/schema.rs:1296`](../core/dr-catalog/src/schema.rs#L1296), [`core/dr-catalog/src/schema.rs:895`](../core/dr-catalog/src/schema.rs#L895), [`core/dr-sync-folder/src/borrow.rs:1`](../core/dr-sync-folder/src/borrow.rs#L1), [`core/dr-types/src/selector.rs:1`](../core/dr-types/src/selector.rs#L1), [`core/dr-types/src/settings.rs:1`](../core/dr-types/src/settings.rs#L1), [`ui/dr-ui/src/collections_ui.rs:2940`](../ui/dr-ui/src/collections_ui.rs#L2940), [`ui/dr-ui/src/collections_ui.rs:3182`](../ui/dr-ui/src/collections_ui.rs#L3182), [`ui/dr-ui/src/collections_ui.rs:4083`](../ui/dr-ui/src/collections_ui.rs#L4083), [`ui/dr-ui/src/collections_ui.rs:785`](../ui/dr-ui/src/collections_ui.rs#L785), [`ui/dr-ui/src/collections_ui.rs:856`](../ui/dr-ui/src/collections_ui.rs#L856), [`ui/dr-ui/src/lib.rs:2404`](../ui/dr-ui/src/lib.rs#L2404), [`ui/dr-ui/src/lib.rs:3762`](../ui/dr-ui/src/lib.rs#L3762), [`ui/dr-ui/src/library.rs:2109`](../ui/dr-ui/src/library.rs#L2109), [`ui/dr-ui/src/library.rs:2132`](../ui/dr-ui/src/library.rs#L2132), [`ui/dr-ui/src/library.rs:2407`](../ui/dr-ui/src/library.rs#L2407), [`ui/dr-ui/src/library_ui.rs:1445`](../ui/dr-ui/src/library_ui.rs#L1445), [`ui/dr-ui/src/library_ui.rs:1518`](../ui/dr-ui/src/library_ui.rs#L1518), [`ui/dr-ui/src/library_ui.rs:1619`](../ui/dr-ui/src/library_ui.rs#L1619), [`ui/dr-ui/src/library_ui.rs:1674`](../ui/dr-ui/src/library_ui.rs#L1674), [`ui/dr-ui/src/library_ui.rs:1809`](../ui/dr-ui/src/library_ui.rs#L1809), [`ui/dr-ui/src/library_ui.rs:1927`](../ui/dr-ui/src/library_ui.rs#L1927), [`ui/dr-ui/src/library_ui.rs:2694`](../ui/dr-ui/src/library_ui.rs#L2694), [`ui/dr-ui/src/library_ui.rs:344`](../ui/dr-ui/src/library_ui.rs#L344), [`ui/dr-ui/src/library_ui.rs:355`](../ui/dr-ui/src/library_ui.rs#L355), [`ui/dr-ui/src/library_ui.rs:370`](../ui/dr-ui/src/library_ui.rs#L370), [`ui/dr-ui/src/library_ui.rs:379`](../ui/dr-ui/src/library_ui.rs#L379), [`ui/dr-ui/src/library_ui.rs:509`](../ui/dr-ui/src/library_ui.rs#L509), [`ui/dr-ui/src/library_ui.rs:524`](../ui/dr-ui/src/library_ui.rs#L524), [`ui/dr-ui/src/library_ui.rs:571`](../ui/dr-ui/src/library_ui.rs#L571), [`ui/dr-ui/src/library_ui.rs:634`](../ui/dr-ui/src/library_ui.rs#L634), [`ui/dr-ui/src/library_ui.rs:665`](../ui/dr-ui/src/library_ui.rs#L665), [`ui/dr-ui/src/library_ui.rs:677`](../ui/dr-ui/src/library_ui.rs#L677), [`ui/dr-ui/src/library_ui.rs:6811`](../ui/dr-ui/src/library_ui.rs#L6811), [`ui/dr-ui/src/library_ui.rs:6829`](../ui/dr-ui/src/library_ui.rs#L6829), [`ui/dr-ui/src/settings_store.rs:1`](../ui/dr-ui/src/settings_store.rs#L1), [`ui/dr-ui/src/settings_ui.rs:1`](../ui/dr-ui/src/settings_ui.rs#L1), [`ui/dr-ui/ui/app.slint:1518`](../ui/dr-ui/ui/app.slint#L1518), [`ui/dr-ui/ui/app.slint:3067`](../ui/dr-ui/ui/app.slint#L3067), [`ui/dr-ui/ui/app.slint:535`](../ui/dr-ui/ui/app.slint#L535), [`ui/dr-ui/ui/app.slint:543`](../ui/dr-ui/ui/app.slint#L543), [`ui/dr-ui/ui/app.slint:549`](../ui/dr-ui/ui/app.slint#L549), [`ui/dr-ui/ui/collections.slint:1037`](../ui/dr-ui/ui/collections.slint#L1037), [`ui/dr-ui/ui/collections.slint:1048`](../ui/dr-ui/ui/collections.slint#L1048), [`ui/dr-ui/ui/collections.slint:1127`](../ui/dr-ui/ui/collections.slint#L1127), [`ui/dr-ui/ui/collections.slint:272`](../ui/dr-ui/ui/collections.slint#L272), [`ui/dr-ui/ui/collections.slint:424`](../ui/dr-ui/ui/collections.slint#L424), [`ui/dr-ui/ui/collections.slint:433`](../ui/dr-ui/ui/collections.slint#L433), [`ui/dr-ui/ui/collections.slint:436`](../ui/dr-ui/ui/collections.slint#L436), [`ui/dr-ui/ui/collections.slint:482`](../ui/dr-ui/ui/collections.slint#L482), [`ui/dr-ui/ui/collections.slint:52`](../ui/dr-ui/ui/collections.slint#L52), [`ui/dr-ui/ui/collections.slint:720`](../ui/dr-ui/ui/collections.slint#L720), [`ui/dr-ui/ui/collections.slint:877`](../ui/dr-ui/ui/collections.slint#L877), [`ui/dr-ui/ui/collections.slint:91`](../ui/dr-ui/ui/collections.slint#L91), [`ui/dr-ui/ui/icons.slint:262`](../ui/dr-ui/ui/icons.slint#L262) |
| FR-NC-6b | [`ui/dr-ui/src/library_ui.rs:1674`](../ui/dr-ui/src/library_ui.rs#L1674), [`ui/dr-ui/src/memory.rs:1`](../ui/dr-ui/src/memory.rs#L1) |
| FR-NC-6c | [`core/dr-catalog/src/cache.rs:225`](../core/dr-catalog/src/cache.rs#L225), [`core/dr-sync-folder/src/borrow.rs:1`](../core/dr-sync-folder/src/borrow.rs#L1), [`core/dr-sync-folder/src/lib.rs:197`](../core/dr-sync-folder/src/lib.rs#L197), [`core/dr-sync-folder/src/lib.rs:73`](../core/dr-sync-folder/src/lib.rs#L73), [`core/dr-sync-folder/src/lib.rs:818`](../core/dr-sync-folder/src/lib.rs#L818), [`core/dr-sync-folder/src/lib.rs:857`](../core/dr-sync-folder/src/lib.rs#L857), [`core/dr-sync-folder/src/vfs.rs:1`](../core/dr-sync-folder/src/vfs.rs#L1), [`core/dr-sync-nextcloud/src/desktop_client.rs:172`](../core/dr-sync-nextcloud/src/desktop_client.rs#L172), [`core/dr-sync-nextcloud/src/desktop_client.rs:35`](../core/dr-sync-nextcloud/src/desktop_client.rs#L35), [`core/dr-sync/src/capability.rs:41`](../core/dr-sync/src/capability.rs#L41), [`core/dr-sync/src/error.rs:39`](../core/dr-sync/src/error.rs#L39), [`core/dr-sync/src/lib.rs:158`](../core/dr-sync/src/lib.rs#L158), [`core/dr-sync/src/types.rs:101`](../core/dr-sync/src/types.rs#L101), [`core/dr-types/src/lib.rs:122`](../core/dr-types/src/lib.rs#L122), [`core/dr-types/src/lib.rs:204`](../core/dr-types/src/lib.rs#L204), [`ui/dr-ui/src/activity.rs:1`](../ui/dr-ui/src/activity.rs#L1), [`ui/dr-ui/src/collections_ui.rs:4083`](../ui/dr-ui/src/collections_ui.rs#L4083), [`ui/dr-ui/src/collections_ui.rs:785`](../ui/dr-ui/src/collections_ui.rs#L785), [`ui/dr-ui/src/collections_ui.rs:856`](../ui/dr-ui/src/collections_ui.rs#L856), [`ui/dr-ui/src/derived_sync.rs:1002`](../ui/dr-ui/src/derived_sync.rs#L1002), [`ui/dr-ui/src/derived_sync.rs:631`](../ui/dr-ui/src/derived_sync.rs#L631), [`ui/dr-ui/src/library.rs:1020`](../ui/dr-ui/src/library.rs#L1020), [`ui/dr-ui/src/library.rs:2229`](../ui/dr-ui/src/library.rs#L2229), [`ui/dr-ui/src/library.rs:2319`](../ui/dr-ui/src/library.rs#L2319), [`ui/dr-ui/src/library.rs:3889`](../ui/dr-ui/src/library.rs#L3889), [`ui/dr-ui/src/library.rs:4398`](../ui/dr-ui/src/library.rs#L4398), [`ui/dr-ui/src/library_ui.rs:1445`](../ui/dr-ui/src/library_ui.rs#L1445), [`ui/dr-ui/src/library_ui.rs:1518`](../ui/dr-ui/src/library_ui.rs#L1518), [`ui/dr-ui/src/library_ui.rs:1717`](../ui/dr-ui/src/library_ui.rs#L1717), [`ui/dr-ui/src/remote.rs:40`](../ui/dr-ui/src/remote.rs#L40), [`ui/dr-ui/ui/collections.slint:272`](../ui/dr-ui/ui/collections.slint#L272), [`ui/dr-ui/ui/collections.slint:877`](../ui/dr-ui/ui/collections.slint#L877), [`ui/dr-ui/ui/icons.slint:262`](../ui/dr-ui/ui/icons.slint#L262) |
| FR-NC-6c | [`core/dr-catalog/src/cache.rs:225`](../core/dr-catalog/src/cache.rs#L225), [`core/dr-sync-folder/src/borrow.rs:1`](../core/dr-sync-folder/src/borrow.rs#L1), [`core/dr-sync-folder/src/lib.rs:197`](../core/dr-sync-folder/src/lib.rs#L197), [`core/dr-sync-folder/src/lib.rs:73`](../core/dr-sync-folder/src/lib.rs#L73), [`core/dr-sync-folder/src/lib.rs:818`](../core/dr-sync-folder/src/lib.rs#L818), [`core/dr-sync-folder/src/lib.rs:857`](../core/dr-sync-folder/src/lib.rs#L857), [`core/dr-sync-folder/src/vfs.rs:1`](../core/dr-sync-folder/src/vfs.rs#L1), [`core/dr-sync-nextcloud/src/desktop_client.rs:172`](../core/dr-sync-nextcloud/src/desktop_client.rs#L172), [`core/dr-sync-nextcloud/src/desktop_client.rs:35`](../core/dr-sync-nextcloud/src/desktop_client.rs#L35), [`core/dr-sync/src/capability.rs:41`](../core/dr-sync/src/capability.rs#L41), [`core/dr-sync/src/error.rs:39`](../core/dr-sync/src/error.rs#L39), [`core/dr-sync/src/lib.rs:158`](../core/dr-sync/src/lib.rs#L158), [`core/dr-sync/src/types.rs:101`](../core/dr-sync/src/types.rs#L101), [`core/dr-types/src/lib.rs:122`](../core/dr-types/src/lib.rs#L122), [`core/dr-types/src/lib.rs:204`](../core/dr-types/src/lib.rs#L204), [`ui/dr-ui/src/activity.rs:1`](../ui/dr-ui/src/activity.rs#L1), [`ui/dr-ui/src/collections_ui.rs:4083`](../ui/dr-ui/src/collections_ui.rs#L4083), [`ui/dr-ui/src/collections_ui.rs:785`](../ui/dr-ui/src/collections_ui.rs#L785), [`ui/dr-ui/src/collections_ui.rs:856`](../ui/dr-ui/src/collections_ui.rs#L856), [`ui/dr-ui/src/derived_sync.rs:1031`](../ui/dr-ui/src/derived_sync.rs#L1031), [`ui/dr-ui/src/derived_sync.rs:631`](../ui/dr-ui/src/derived_sync.rs#L631), [`ui/dr-ui/src/library.rs:1020`](../ui/dr-ui/src/library.rs#L1020), [`ui/dr-ui/src/library.rs:2229`](../ui/dr-ui/src/library.rs#L2229), [`ui/dr-ui/src/library.rs:2319`](../ui/dr-ui/src/library.rs#L2319), [`ui/dr-ui/src/library.rs:3889`](../ui/dr-ui/src/library.rs#L3889), [`ui/dr-ui/src/library.rs:4398`](../ui/dr-ui/src/library.rs#L4398), [`ui/dr-ui/src/library_ui.rs:1445`](../ui/dr-ui/src/library_ui.rs#L1445), [`ui/dr-ui/src/library_ui.rs:1518`](../ui/dr-ui/src/library_ui.rs#L1518), [`ui/dr-ui/src/library_ui.rs:1717`](../ui/dr-ui/src/library_ui.rs#L1717), [`ui/dr-ui/src/remote.rs:40`](../ui/dr-ui/src/remote.rs#L40), [`ui/dr-ui/ui/collections.slint:272`](../ui/dr-ui/ui/collections.slint#L272), [`ui/dr-ui/ui/collections.slint:877`](../ui/dr-ui/ui/collections.slint#L877), [`ui/dr-ui/ui/icons.slint:262`](../ui/dr-ui/ui/icons.slint#L262) |
| FR-NC-6d | [`core/dr-sync-folder/src/borrow.rs:1`](../core/dr-sync-folder/src/borrow.rs#L1), [`core/dr-sync-folder/src/lib.rs:1`](../core/dr-sync-folder/src/lib.rs#L1), [`core/dr-sync-folder/src/vfs.rs:1`](../core/dr-sync-folder/src/vfs.rs#L1) |
| FR-NC-7 | [`core/dr-catalog/src/face_shard.rs:1`](../core/dr-catalog/src/face_shard.rs#L1), [`core/dr-sync-nextcloud/src/lib.rs:105`](../core/dr-sync-nextcloud/src/lib.rs#L105), [`ui/dr-ui/src/derived_sync.rs:1`](../ui/dr-ui/src/derived_sync.rs#L1), [`ui/dr-ui/src/library.rs:4278`](../ui/dr-ui/src/library.rs#L4278), [`ui/dr-ui/src/library_ui.rs:4463`](../ui/dr-ui/src/library_ui.rs#L4463), [`ui/dr-ui/ui/settings.slint:420`](../ui/dr-ui/ui/settings.slint#L420) |
| FR-NC-7a | [`core/dr-ingest/src/layout.rs:1`](../core/dr-ingest/src/layout.rs#L1), [`core/dr-sync/src/upload.rs:1`](../core/dr-sync/src/upload.rs#L1), [`core/dr-sync/src/upload.rs:40`](../core/dr-sync/src/upload.rs#L40), [`core/dr-types/src/settings.rs:309`](../core/dr-types/src/settings.rs#L309), [`ui/dr-ui/src/import.rs:1`](../ui/dr-ui/src/import.rs#L1), [`ui/dr-ui/src/import.rs:97`](../ui/dr-ui/src/import.rs#L97), [`ui/dr-ui/src/import_ui.rs:1`](../ui/dr-ui/src/import_ui.rs#L1), [`ui/dr-ui/src/lib.rs:1508`](../ui/dr-ui/src/lib.rs#L1508), [`ui/dr-ui/ui/import.slint:5`](../ui/dr-ui/ui/import.slint#L5) |
| FR-NC-7b | [`core/dr-ingest/src/lib.rs:733`](../core/dr-ingest/src/lib.rs#L733), [`core/dr-sync/src/upload.rs:1`](../core/dr-sync/src/upload.rs#L1), [`ui/dr-ui/src/import.rs:122`](../ui/dr-ui/src/import.rs#L122), [`ui/dr-ui/src/import.rs:336`](../ui/dr-ui/src/import.rs#L336), [`ui/dr-ui/src/import.rs:584`](../ui/dr-ui/src/import.rs#L584), [`ui/dr-ui/src/import.rs:97`](../ui/dr-ui/src/import.rs#L97), [`ui/dr-ui/src/import_ui.rs:1`](../ui/dr-ui/src/import_ui.rs#L1), [`ui/dr-ui/src/lib.rs:1508`](../ui/dr-ui/src/lib.rs#L1508) |
| FR-NC-8 | [`core/dr-catalog/src/rating.rs:204`](../core/dr-catalog/src/rating.rs#L204), [`core/dr-catalog/src/rating.rs:66`](../core/dr-catalog/src/rating.rs#L66), [`core/dr-catalog/src/rating.rs:929`](../core/dr-catalog/src/rating.rs#L929), [`core/dr-catalog/src/schema.rs:179`](../core/dr-catalog/src/schema.rs#L179), [`core/dr-pipeline/src/sidecar.rs:122`](../core/dr-pipeline/src/sidecar.rs#L122), [`core/dr-pipeline/src/sidecar.rs:2944`](../core/dr-pipeline/src/sidecar.rs#L2944), [`core/dr-pipeline/src/sidecar.rs:645`](../core/dr-pipeline/src/sidecar.rs#L645), [`core/dr-pipeline/src/sidecar.rs:720`](../core/dr-pipeline/src/sidecar.rs#L720), [`core/dr-pipeline/src/sidecar.rs:96`](../core/dr-pipeline/src/sidecar.rs#L96), [`ui/dr-ui/src/lib.rs:2374`](../ui/dr-ui/src/lib.rs#L2374), [`ui/dr-ui/src/library.rs:1062`](../ui/dr-ui/src/library.rs#L1062), [`ui/dr-ui/src/library.rs:2538`](../ui/dr-ui/src/library.rs#L2538), [`ui/dr-ui/src/library.rs:500`](../ui/dr-ui/src/library.rs#L500), [`ui/dr-ui/src/library.rs:760`](../ui/dr-ui/src/library.rs#L760), [`ui/dr-ui/src/library.rs:7751`](../ui/dr-ui/src/library.rs#L7751), [`ui/dr-ui/src/library_ui.rs:586`](../ui/dr-ui/src/library_ui.rs#L586), [`ui/dr-ui/src/presets.rs:288`](../ui/dr-ui/src/presets.rs#L288), [`ui/dr-ui/src/presets.rs:336`](../ui/dr-ui/src/presets.rs#L336) |
| FR-NC-9 | [`core/dr-catalog/src/merge.rs:1`](../core/dr-catalog/src/merge.rs#L1), [`core/dr-catalog/src/rating.rs:204`](../core/dr-catalog/src/rating.rs#L204), [`core/dr-catalog/src/rating.rs:66`](../core/dr-catalog/src/rating.rs#L66), [`core/dr-catalog/src/rating.rs:929`](../core/dr-catalog/src/rating.rs#L929), [`core/dr-catalog/src/schema.rs:179`](../core/dr-catalog/src/schema.rs#L179), [`core/dr-catalog/src/schema.rs:557`](../core/dr-catalog/src/schema.rs#L557), [`core/dr-catalog/src/schema.rs:838`](../core/dr-catalog/src/schema.rs#L838), [`core/dr-catalog/src/sync.rs:1`](../core/dr-catalog/src/sync.rs#L1), [`core/dr-pipeline/src/sidecar.rs:177`](../core/dr-pipeline/src/sidecar.rs#L177), [`core/dr-pipeline/src/sidecar.rs:204`](../core/dr-pipeline/src/sidecar.rs#L204), [`core/dr-pipeline/src/sidecar.rs:2473`](../core/dr-pipeline/src/sidecar.rs#L2473), [`core/dr-pipeline/src/sidecar.rs:2710`](../core/dr-pipeline/src/sidecar.rs#L2710), [`core/dr-pipeline/src/sidecar.rs:2944`](../core/dr-pipeline/src/sidecar.rs#L2944), [`core/dr-pipeline/src/sidecar.rs:380`](../core/dr-pipeline/src/sidecar.rs#L380), [`core/dr-pipeline/src/sidecar.rs:478`](../core/dr-pipeline/src/sidecar.rs#L478), [`core/dr-pipeline/src/sidecar.rs:645`](../core/dr-pipeline/src/sidecar.rs#L645), [`core/dr-pipeline/src/sidecar.rs:695`](../core/dr-pipeline/src/sidecar.rs#L695), [`core/dr-pipeline/src/sidecar.rs:720`](../core/dr-pipeline/src/sidecar.rs#L720), [`core/dr-pipeline/src/spot.rs:245`](../core/dr-pipeline/src/spot.rs#L245), [`core/dr-pipeline/tests/mask_sidecar.rs:1075`](../core/dr-pipeline/tests/mask_sidecar.rs#L1075), [`core/dr-pipeline/tests/spot_sidecar.rs:1`](../core/dr-pipeline/tests/spot_sidecar.rs#L1), [`core/dr-sync-nextcloud/src/lib.rs:124`](../core/dr-sync-nextcloud/src/lib.rs#L124), [`core/dr-sync/src/scan.rs:31`](../core/dr-sync/src/scan.rs#L31), [`core/dr-sync/src/scan.rs:56`](../core/dr-sync/src/scan.rs#L56), [`core/dr-sync/src/scan.rs:926`](../core/dr-sync/src/scan.rs#L926), [`core/dr-xmp/src/lib.rs:733`](../core/dr-xmp/src/lib.rs#L733), [`core/dr-xmp/src/lib.rs:746`](../core/dr-xmp/src/lib.rs#L746), [`ui/dr-ui/src/derived_sync.rs:1030`](../ui/dr-ui/src/derived_sync.rs#L1030), [`ui/dr-ui/src/derived_sync.rs:602`](../ui/dr-ui/src/derived_sync.rs#L602), [`ui/dr-ui/src/derived_sync.rs:631`](../ui/dr-ui/src/derived_sync.rs#L631), [`ui/dr-ui/src/derived_sync.rs:664`](../ui/dr-ui/src/derived_sync.rs#L664), [`ui/dr-ui/src/derived_sync.rs:781`](../ui/dr-ui/src/derived_sync.rs#L781), [`ui/dr-ui/src/library.rs:1062`](../ui/dr-ui/src/library.rs#L1062), [`ui/dr-ui/src/library.rs:1082`](../ui/dr-ui/src/library.rs#L1082), [`ui/dr-ui/src/library.rs:1391`](../ui/dr-ui/src/library.rs#L1391), [`ui/dr-ui/src/library.rs:1426`](../ui/dr-ui/src/library.rs#L1426), [`ui/dr-ui/src/library.rs:2538`](../ui/dr-ui/src/library.rs#L2538), [`ui/dr-ui/src/library.rs:73`](../ui/dr-ui/src/library.rs#L73), [`ui/dr-ui/src/library.rs:7583`](../ui/dr-ui/src/library.rs#L7583), [`ui/dr-ui/src/library.rs:760`](../ui/dr-ui/src/library.rs#L760), [`ui/dr-ui/src/library.rs:7751`](../ui/dr-ui/src/library.rs#L7751), [`ui/dr-ui/src/library.rs:832`](../ui/dr-ui/src/library.rs#L832), [`ui/dr-ui/src/library.rs:916`](../ui/dr-ui/src/library.rs#L916), [`ui/dr-ui/src/library_ui.rs:1302`](../ui/dr-ui/src/library_ui.rs#L1302), [`ui/dr-ui/src/presets.rs:288`](../ui/dr-ui/src/presets.rs#L288), [`ui/dr-ui/src/presets.rs:336`](../ui/dr-ui/src/presets.rs#L336), [`ui/dr-ui/src/xmp_sync.rs:1`](../ui/dr-ui/src/xmp_sync.rs#L1), [`ui/dr-ui/src/xmp_sync.rs:287`](../ui/dr-ui/src/xmp_sync.rs#L287) |
| FR-NC-9 | [`core/dr-catalog/src/merge.rs:1`](../core/dr-catalog/src/merge.rs#L1), [`core/dr-catalog/src/rating.rs:204`](../core/dr-catalog/src/rating.rs#L204), [`core/dr-catalog/src/rating.rs:66`](../core/dr-catalog/src/rating.rs#L66), [`core/dr-catalog/src/rating.rs:929`](../core/dr-catalog/src/rating.rs#L929), [`core/dr-catalog/src/schema.rs:179`](../core/dr-catalog/src/schema.rs#L179), [`core/dr-catalog/src/schema.rs:557`](../core/dr-catalog/src/schema.rs#L557), [`core/dr-catalog/src/schema.rs:838`](../core/dr-catalog/src/schema.rs#L838), [`core/dr-catalog/src/sync.rs:1`](../core/dr-catalog/src/sync.rs#L1), [`core/dr-pipeline/src/sidecar.rs:177`](../core/dr-pipeline/src/sidecar.rs#L177), [`core/dr-pipeline/src/sidecar.rs:204`](../core/dr-pipeline/src/sidecar.rs#L204), [`core/dr-pipeline/src/sidecar.rs:2473`](../core/dr-pipeline/src/sidecar.rs#L2473), [`core/dr-pipeline/src/sidecar.rs:2710`](../core/dr-pipeline/src/sidecar.rs#L2710), [`core/dr-pipeline/src/sidecar.rs:2944`](../core/dr-pipeline/src/sidecar.rs#L2944), [`core/dr-pipeline/src/sidecar.rs:380`](../core/dr-pipeline/src/sidecar.rs#L380), [`core/dr-pipeline/src/sidecar.rs:478`](../core/dr-pipeline/src/sidecar.rs#L478), [`core/dr-pipeline/src/sidecar.rs:645`](../core/dr-pipeline/src/sidecar.rs#L645), [`core/dr-pipeline/src/sidecar.rs:695`](../core/dr-pipeline/src/sidecar.rs#L695), [`core/dr-pipeline/src/sidecar.rs:720`](../core/dr-pipeline/src/sidecar.rs#L720), [`core/dr-pipeline/src/spot.rs:245`](../core/dr-pipeline/src/spot.rs#L245), [`core/dr-pipeline/tests/mask_sidecar.rs:1075`](../core/dr-pipeline/tests/mask_sidecar.rs#L1075), [`core/dr-pipeline/tests/spot_sidecar.rs:1`](../core/dr-pipeline/tests/spot_sidecar.rs#L1), [`core/dr-sync-nextcloud/src/lib.rs:124`](../core/dr-sync-nextcloud/src/lib.rs#L124), [`core/dr-sync/src/scan.rs:31`](../core/dr-sync/src/scan.rs#L31), [`core/dr-sync/src/scan.rs:56`](../core/dr-sync/src/scan.rs#L56), [`core/dr-sync/src/scan.rs:926`](../core/dr-sync/src/scan.rs#L926), [`core/dr-xmp/src/lib.rs:733`](../core/dr-xmp/src/lib.rs#L733), [`core/dr-xmp/src/lib.rs:746`](../core/dr-xmp/src/lib.rs#L746), [`ui/dr-ui/src/derived_sync.rs:1059`](../ui/dr-ui/src/derived_sync.rs#L1059), [`ui/dr-ui/src/derived_sync.rs:602`](../ui/dr-ui/src/derived_sync.rs#L602), [`ui/dr-ui/src/derived_sync.rs:631`](../ui/dr-ui/src/derived_sync.rs#L631), [`ui/dr-ui/src/derived_sync.rs:664`](../ui/dr-ui/src/derived_sync.rs#L664), [`ui/dr-ui/src/derived_sync.rs:810`](../ui/dr-ui/src/derived_sync.rs#L810), [`ui/dr-ui/src/library.rs:1062`](../ui/dr-ui/src/library.rs#L1062), [`ui/dr-ui/src/library.rs:1082`](../ui/dr-ui/src/library.rs#L1082), [`ui/dr-ui/src/library.rs:1391`](../ui/dr-ui/src/library.rs#L1391), [`ui/dr-ui/src/library.rs:1426`](../ui/dr-ui/src/library.rs#L1426), [`ui/dr-ui/src/library.rs:2538`](../ui/dr-ui/src/library.rs#L2538), [`ui/dr-ui/src/library.rs:73`](../ui/dr-ui/src/library.rs#L73), [`ui/dr-ui/src/library.rs:7583`](../ui/dr-ui/src/library.rs#L7583), [`ui/dr-ui/src/library.rs:760`](../ui/dr-ui/src/library.rs#L760), [`ui/dr-ui/src/library.rs:7751`](../ui/dr-ui/src/library.rs#L7751), [`ui/dr-ui/src/library.rs:832`](../ui/dr-ui/src/library.rs#L832), [`ui/dr-ui/src/library.rs:916`](../ui/dr-ui/src/library.rs#L916), [`ui/dr-ui/src/library_ui.rs:1302`](../ui/dr-ui/src/library_ui.rs#L1302), [`ui/dr-ui/src/presets.rs:288`](../ui/dr-ui/src/presets.rs#L288), [`ui/dr-ui/src/presets.rs:336`](../ui/dr-ui/src/presets.rs#L336), [`ui/dr-ui/src/xmp_sync.rs:1`](../ui/dr-ui/src/xmp_sync.rs#L1), [`ui/dr-ui/src/xmp_sync.rs:287`](../ui/dr-ui/src/xmp_sync.rs#L287) |
| FR-PLAT-AND-2 | [`core/dr-catalog/src/walk.rs:1022`](../core/dr-catalog/src/walk.rs#L1022), [`core/dr-catalog/src/walk.rs:435`](../core/dr-catalog/src/walk.rs#L435), [`core/dr-sync-folder/src/lib.rs:242`](../core/dr-sync-folder/src/lib.rs#L242), [`core/dr-sync-folder/src/tests.rs:51`](../core/dr-sync-folder/src/tests.rs#L51), [`core/dr-sync/src/error.rs:113`](../core/dr-sync/src/error.rs#L113), [`core/dr-sync/src/error.rs:195`](../core/dr-sync/src/error.rs#L195), [`core/dr-sync/src/scan.rs:211`](../core/dr-sync/src/scan.rs#L211), [`core/dr-sync/src/scan.rs:519`](../core/dr-sync/src/scan.rs#L519), [`core/dr-sync/src/scan.rs:545`](../core/dr-sync/src/scan.rs#L545), [`core/dr-sync/src/scan.rs:574`](../core/dr-sync/src/scan.rs#L574), [`ui/dr-ui/src/library.rs:1291`](../ui/dr-ui/src/library.rs#L1291), [`ui/dr-ui/src/library.rs:1344`](../ui/dr-ui/src/library.rs#L1344), [`ui/dr-ui/src/library.rs:1375`](../ui/dr-ui/src/library.rs#L1375), [`ui/dr-ui/src/library.rs:1955`](../ui/dr-ui/src/library.rs#L1955), [`ui/dr-ui/src/library_ui.rs:1273`](../ui/dr-ui/src/library_ui.rs#L1273), [`ui/dr-ui/src/library_ui.rs:1369`](../ui/dr-ui/src/library_ui.rs#L1369), [`ui/dr-ui/src/library_ui.rs:1968`](../ui/dr-ui/src/library_ui.rs#L1968), [`ui/dr-ui/src/library_ui.rs:328`](../ui/dr-ui/src/library_ui.rs#L328), [`ui/dr-ui/src/library_ui.rs:555`](../ui/dr-ui/src/library_ui.rs#L555) |
| FR-PLAT-AND-3 | [`core/dr-catalog/src/jobs.rs:1`](../core/dr-catalog/src/jobs.rs#L1), [`core/dr-catalog/src/runner.rs:1`](../core/dr-catalog/src/runner.rs#L1) |
| FR-PLAT-AND-4 | [`core/dr-catalog/src/runner.rs:1`](../core/dr-catalog/src/runner.rs#L1) |
@@ -137,7 +137,7 @@ _None._
| FR-UI-5 | [`ui/dr-ui/src/collections_ui.rs:1`](../ui/dr-ui/src/collections_ui.rs#L1), [`ui/dr-ui/src/lib.rs:3239`](../ui/dr-ui/src/lib.rs#L3239), [`ui/dr-ui/src/lib.rs:4068`](../ui/dr-ui/src/lib.rs#L4068), [`ui/dr-ui/src/lib.rs:4259`](../ui/dr-ui/src/lib.rs#L4259), [`ui/dr-ui/src/masks_ui.rs:1518`](../ui/dr-ui/src/masks_ui.rs#L1518), [`ui/dr-ui/ui/app.slint:2473`](../ui/dr-ui/ui/app.slint#L2473), [`ui/dr-ui/ui/app.slint:2513`](../ui/dr-ui/ui/app.slint#L2513), [`ui/dr-ui/ui/collections.slint:4`](../ui/dr-ui/ui/collections.slint#L4), [`ui/dr-ui/ui/session.slint:75`](../ui/dr-ui/ui/session.slint#L75) |
| FR-UI-6 | [`ui/dr-ui/ui/widgets.slint:1`](../ui/dr-ui/ui/widgets.slint#L1) |
| FR-UI-7 | [`core/dr-pipeline/src/descriptor.rs:195`](../core/dr-pipeline/src/descriptor.rs#L195), [`core/dr-pipeline/src/framing.rs:395`](../core/dr-pipeline/src/framing.rs#L395), [`core/dr-types/src/settings.rs:424`](../core/dr-types/src/settings.rs#L424), [`ui/dr-ui/src/develop.rs:1330`](../ui/dr-ui/src/develop.rs#L1330), [`ui/dr-ui/src/lib.rs:3893`](../ui/dr-ui/src/lib.rs#L3893), [`ui/dr-ui/ui/adjust.slint:974`](../ui/dr-ui/ui/adjust.slint#L974), [`ui/dr-ui/ui/app.slint:2229`](../ui/dr-ui/ui/app.slint#L2229), [`ui/dr-ui/ui/settings.slint:609`](../ui/dr-ui/ui/settings.slint#L609), [`ui/dr-ui/ui/toolrail.slint:89`](../ui/dr-ui/ui/toolrail.slint#L89) |
| FR-UI-8 | [`core/dr-catalog/src/collections.rs:654`](../core/dr-catalog/src/collections.rs#L654), [`core/dr-catalog/src/collections.rs:675`](../core/dr-catalog/src/collections.rs#L675), [`core/dr-types/src/place.rs:1`](../core/dr-types/src/place.rs#L1), [`ui/dr-ui/src/derived_sync.rs:216`](../ui/dr-ui/src/derived_sync.rs#L216), [`ui/dr-ui/src/derived_sync.rs:81`](../ui/dr-ui/src/derived_sync.rs#L81), [`ui/dr-ui/src/derived_sync.rs:854`](../ui/dr-ui/src/derived_sync.rs#L854), [`ui/dr-ui/src/derived_sync.rs:864`](../ui/dr-ui/src/derived_sync.rs#L864), [`ui/dr-ui/src/derived_sync.rs:946`](../ui/dr-ui/src/derived_sync.rs#L946), [`ui/dr-ui/src/library.rs:1123`](../ui/dr-ui/src/library.rs#L1123), [`ui/dr-ui/src/library.rs:4945`](../ui/dr-ui/src/library.rs#L4945), [`ui/dr-ui/src/library.rs:5663`](../ui/dr-ui/src/library.rs#L5663), [`ui/dr-ui/src/library.rs:5729`](../ui/dr-ui/src/library.rs#L5729), [`ui/dr-ui/src/library_ui.rs:195`](../ui/dr-ui/src/library_ui.rs#L195), [`ui/dr-ui/src/library_ui.rs:2155`](../ui/dr-ui/src/library_ui.rs#L2155), [`ui/dr-ui/src/library_ui.rs:2344`](../ui/dr-ui/src/library_ui.rs#L2344), [`ui/dr-ui/src/library_ui.rs:2361`](../ui/dr-ui/src/library_ui.rs#L2361), [`ui/dr-ui/src/library_ui.rs:2459`](../ui/dr-ui/src/library_ui.rs#L2459), [`ui/dr-ui/src/library_ui.rs:5758`](../ui/dr-ui/src/library_ui.rs#L5758), [`ui/dr-ui/src/library_ui.rs:5798`](../ui/dr-ui/src/library_ui.rs#L5798), [`ui/dr-ui/src/library_ui.rs:5827`](../ui/dr-ui/src/library_ui.rs#L5827), [`ui/dr-ui/src/library_ui.rs:6090`](../ui/dr-ui/src/library_ui.rs#L6090), [`ui/dr-ui/src/library_ui.rs:6313`](../ui/dr-ui/src/library_ui.rs#L6313), [`ui/dr-ui/src/library_ui.rs:7037`](../ui/dr-ui/src/library_ui.rs#L7037), [`ui/dr-ui/src/library_ui.rs:891`](../ui/dr-ui/src/library_ui.rs#L891), [`ui/dr-ui/src/library_ui.rs:924`](../ui/dr-ui/src/library_ui.rs#L924), [`ui/dr-ui/src/library_ui.rs:970`](../ui/dr-ui/src/library_ui.rs#L970), [`ui/dr-ui/src/place.rs:1`](../ui/dr-ui/src/place.rs#L1) |
| FR-UI-8 | [`core/dr-catalog/src/collections.rs:654`](../core/dr-catalog/src/collections.rs#L654), [`core/dr-catalog/src/collections.rs:675`](../core/dr-catalog/src/collections.rs#L675), [`core/dr-types/src/place.rs:1`](../core/dr-types/src/place.rs#L1), [`ui/dr-ui/src/derived_sync.rs:216`](../ui/dr-ui/src/derived_sync.rs#L216), [`ui/dr-ui/src/derived_sync.rs:81`](../ui/dr-ui/src/derived_sync.rs#L81), [`ui/dr-ui/src/derived_sync.rs:883`](../ui/dr-ui/src/derived_sync.rs#L883), [`ui/dr-ui/src/derived_sync.rs:893`](../ui/dr-ui/src/derived_sync.rs#L893), [`ui/dr-ui/src/derived_sync.rs:975`](../ui/dr-ui/src/derived_sync.rs#L975), [`ui/dr-ui/src/library.rs:1123`](../ui/dr-ui/src/library.rs#L1123), [`ui/dr-ui/src/library.rs:4945`](../ui/dr-ui/src/library.rs#L4945), [`ui/dr-ui/src/library.rs:5663`](../ui/dr-ui/src/library.rs#L5663), [`ui/dr-ui/src/library.rs:5729`](../ui/dr-ui/src/library.rs#L5729), [`ui/dr-ui/src/library_ui.rs:195`](../ui/dr-ui/src/library_ui.rs#L195), [`ui/dr-ui/src/library_ui.rs:2155`](../ui/dr-ui/src/library_ui.rs#L2155), [`ui/dr-ui/src/library_ui.rs:2344`](../ui/dr-ui/src/library_ui.rs#L2344), [`ui/dr-ui/src/library_ui.rs:2361`](../ui/dr-ui/src/library_ui.rs#L2361), [`ui/dr-ui/src/library_ui.rs:2459`](../ui/dr-ui/src/library_ui.rs#L2459), [`ui/dr-ui/src/library_ui.rs:5758`](../ui/dr-ui/src/library_ui.rs#L5758), [`ui/dr-ui/src/library_ui.rs:5798`](../ui/dr-ui/src/library_ui.rs#L5798), [`ui/dr-ui/src/library_ui.rs:5827`](../ui/dr-ui/src/library_ui.rs#L5827), [`ui/dr-ui/src/library_ui.rs:6090`](../ui/dr-ui/src/library_ui.rs#L6090), [`ui/dr-ui/src/library_ui.rs:6313`](../ui/dr-ui/src/library_ui.rs#L6313), [`ui/dr-ui/src/library_ui.rs:7037`](../ui/dr-ui/src/library_ui.rs#L7037), [`ui/dr-ui/src/library_ui.rs:891`](../ui/dr-ui/src/library_ui.rs#L891), [`ui/dr-ui/src/library_ui.rs:924`](../ui/dr-ui/src/library_ui.rs#L924), [`ui/dr-ui/src/library_ui.rs:970`](../ui/dr-ui/src/library_ui.rs#L970), [`ui/dr-ui/src/place.rs:1`](../ui/dr-ui/src/place.rs#L1) |
| NFR-A11Y-2 | [`ui/dr-ui/tests/ui_controls_are_accessible.rs:1`](../ui/dr-ui/tests/ui_controls_are_accessible.rs#L1), [`ui/dr-ui/ui/adjust.slint:171`](../ui/dr-ui/ui/adjust.slint#L171), [`ui/dr-ui/ui/app.slint:2646`](../ui/dr-ui/ui/app.slint#L2646), [`ui/dr-ui/ui/app.slint:2684`](../ui/dr-ui/ui/app.slint#L2684) |
| NFR-A11Y-3 | [`ui/dr-ui/src/histogram.rs:356`](../ui/dr-ui/src/histogram.rs#L356), [`ui/dr-ui/ui/histogram.slint:137`](../ui/dr-ui/ui/histogram.slint#L137), [`ui/dr-ui/ui/library.slint:761`](../ui/dr-ui/ui/library.slint#L761), [`ui/dr-ui/ui/library.slint:909`](../ui/dr-ui/ui/library.slint#L909), [`ui/dr-ui/ui/peaking.slint:1`](../ui/dr-ui/ui/peaking.slint#L1) |
| NFR-ARCH-2 | [`core/dr-catalog/src/jobs.rs:1`](../core/dr-catalog/src/jobs.rs#L1), [`ui/dr-ui/src/faces.rs:1`](../ui/dr-ui/src/faces.rs#L1), [`ui/dr-ui/src/library.rs:3679`](../ui/dr-ui/src/library.rs#L3679) |
@@ -156,7 +156,7 @@ _None._
| NFR-PORT-2 | [`core/dr-gpu/src/lib.rs:1`](../core/dr-gpu/src/lib.rs#L1) |
| NFR-PORT-3 | [`platform/dr-plat/src/storage.rs:1`](../platform/dr-plat/src/storage.rs#L1) |
| NFR-R1 | [`core/dr-catalog/src/sync.rs:1`](../core/dr-catalog/src/sync.rs#L1), [`core/dr-gpu/src/lib.rs:192`](../core/dr-gpu/src/lib.rs#L192), [`core/dr-sync/src/account.rs:226`](../core/dr-sync/src/account.rs#L226), [`ui/dr-ui/src/library.rs:1170`](../ui/dr-ui/src/library.rs#L1170) |
| NFR-R2 | [`core/dr-catalog/src/recovery.rs:1`](../core/dr-catalog/src/recovery.rs#L1), [`core/dr-catalog/src/trash.rs:1`](../core/dr-catalog/src/trash.rs#L1), [`ui/dr-ui/src/derived_sync.rs:602`](../ui/dr-ui/src/derived_sync.rs#L602) |
| NFR-R2 | [`core/dr-catalog/src/recovery.rs:1`](../core/dr-catalog/src/recovery.rs#L1), [`core/dr-catalog/src/sync.rs:61`](../core/dr-catalog/src/sync.rs#L61), [`core/dr-catalog/src/trash.rs:1`](../core/dr-catalog/src/trash.rs#L1), [`ui/dr-ui/src/derived_sync.rs:602`](../ui/dr-ui/src/derived_sync.rs#L602), [`ui/dr-ui/src/derived_sync.rs:725`](../ui/dr-ui/src/derived_sync.rs#L725) |
| NFR-R4 | [`core/dr-types/src/settings.rs:92`](../core/dr-types/src/settings.rs#L92), [`ui/dr-ui/src/library.rs:1563`](../ui/dr-ui/src/library.rs#L1563), [`ui/dr-ui/src/library.rs:1579`](../ui/dr-ui/src/library.rs#L1579), [`ui/dr-ui/src/library_ui.rs:3411`](../ui/dr-ui/src/library_ui.rs#L3411), [`ui/dr-ui/src/library_ui.rs:387`](../ui/dr-ui/src/library_ui.rs#L387), [`ui/dr-ui/src/library_ui.rs:519`](../ui/dr-ui/src/library_ui.rs#L519), [`ui/dr-ui/src/settings_ui.rs:506`](../ui/dr-ui/src/settings_ui.rs#L506), [`ui/dr-ui/src/xmp_sync.rs:1`](../ui/dr-ui/src/xmp_sync.rs#L1), [`ui/dr-ui/ui/settings.slint:1001`](../ui/dr-ui/ui/settings.slint#L1001), [`ui/dr-ui/ui/settings.slint:184`](../ui/dr-ui/ui/settings.slint#L184) |
| NFR-R5 | [`core/dr-catalog/src/collections.rs:1`](../core/dr-catalog/src/collections.rs#L1), [`core/dr-catalog/src/error.rs:1`](../core/dr-catalog/src/error.rs#L1), [`core/dr-catalog/src/keywords.rs:1`](../core/dr-catalog/src/keywords.rs#L1), [`core/dr-catalog/src/schema.rs:1`](../core/dr-catalog/src/schema.rs#L1) |
| NFR-R6 | [`core/dr-catalog/src/error.rs:1`](../core/dr-catalog/src/error.rs#L1), [`core/dr-catalog/src/lib.rs:255`](../core/dr-catalog/src/lib.rs#L255), [`core/dr-catalog/src/recovery.rs:1`](../core/dr-catalog/src/recovery.rs#L1) |
+83 -13
View File
@@ -716,11 +716,40 @@ async fn sync_catalog(
// this whole scheme exists to survive. Under its own name a failure leaves
// the current copy untouched and costs one stray file, retried next pass.
let staging = RemotePath::new(format!("{}/{UPLOAD_NAME}", base.as_str()));
let sent = bytes.len() as u64;
if let Err(e) = backend.put(&staging, bytes, None).await {
log::warn!("uploading catalog: {e}");
return Ok(());
}
// TRACES: NFR-R2
// Confirm the server holds what was sent before it becomes the copy every
// other device reads. A chunked upload is assembled server-side, and an
// assembly that went wrong is a file of plausible size that no device can
// open — the one failure the generations exist to survive, and cheaper
// to catch here, on the device that caused it, than on every other one
// after. One listing; the size is what the server can vouch for without
// reading the file back.
match remote_size(backend, base, UPLOAD_NAME).await {
Some(held) if held == sent => {}
Some(held) => {
log::warn!(
"not replacing the catalog: sent {sent} bytes but the server holds {held}; \
the upload is discarded and retried next pass"
);
let _ = backend.delete(&RemoteId::Path(staging), None).await;
return Ok(());
}
None => {
log::warn!(
"not replacing the catalog: the server would not confirm the upload's size; \
it is discarded and retried next pass"
);
let _ = backend.delete(&RemoteId::Path(staging), None).await;
return Ok(());
}
}
// Rotate, then move the upload into place. Every step here is a rename on
// the server, and every destination is empty by the time it is written to
// — a `move_to` will not overwrite, by design — so a failure at any point
@@ -1223,6 +1252,9 @@ mod derived_guard_tests {
bodies: std::collections::HashMap<String, Vec<u8>>,
/// Every `move_to`, as (from, to) names, in order.
moves: Arc<std::sync::Mutex<Vec<(String, String)>>>,
/// What `list` claims the staged upload's size is, when a test wants
/// the server to have assembled it wrongly. `None` reports the truth.
staged_size: Option<u64>,
}
impl Fussy {
@@ -1247,6 +1279,7 @@ mod derived_guard_tests {
advertise: None,
bodies: Default::default(),
moves: Default::default(),
staged_size: None,
},
puts,
last_put,
@@ -1267,20 +1300,33 @@ mod derived_guard_tests {
dir: &RemotePath,
_since: Option<&dr_sync::Validator>,
) -> Result<Vec<dr_sync::RemoteEntry>, RemoteError> {
let Some(size) = self.advertise else {
return Ok(Vec::new());
let entry = |name: &str, size: u64| {
let path = RemotePath::new(format!("{}/{name}", dir.as_str()));
dr_sync::RemoteEntry {
id: RemoteId::Path(path.clone()),
path,
kind: dr_sync::EntryKind::File,
validator: dr_sync::Validator::new("v"),
size,
modified: None,
has_preview: false,
materialised: true,
}
};
let path = RemotePath::new(format!("{}/catalog.sqlite", dir.as_str()));
Ok(vec![dr_sync::RemoteEntry {
id: RemoteId::Path(path.clone()),
path,
kind: dr_sync::EntryKind::File,
validator: dr_sync::Validator::new("v"),
size,
modified: None,
has_preview: false,
materialised: true,
}])
let mut out = Vec::new();
if let Some(size) = self.advertise {
out.push(entry(CATALOG_NAME, size));
}
// The staged upload lists at the size of what was last put, as a
// server that assembled it correctly would report — unless a test
// says the assembly went wrong.
if self.puts.load(Ordering::SeqCst) > 0 {
let held = self
.staged_size
.unwrap_or(self.last_put.lock().unwrap().len() as u64);
out.push(entry(UPLOAD_NAME, held));
}
Ok(out)
}
async fn dir_validator(
&self,
@@ -1566,6 +1612,30 @@ mod derived_guard_tests {
);
}
#[tokio::test]
async fn an_upload_the_server_holds_at_the_wrong_size_is_not_rotated_in() {
// The assembly went wrong on the server. Rotating that into place would
// hand every other device the damaged file the generations exist to
// survive; discarding it costs one retry.
let (catalog_path, scratch) = fixture("wrong-size");
let (mut backend, puts) = Fussy::reading(Some(RemoteError::NotFound("nope".into())));
backend.staged_size = Some(7);
let moves = backend.moves.clone();
let mut report = SyncReport::default();
sync_catalog(
&backend,
&RemotePath::new(".darkroom-derived"),
&catalog_path,
&scratch,
&mut report,
)
.await
.unwrap();
assert_eq!(puts.load(Ordering::SeqCst), 1, "it was uploaded");
assert!(!report.catalog_uploaded, "but not accepted");
assert!(moves.lock().unwrap().is_empty(), "and nothing was rotated");
}
async fn run_with_moves(
fail_with: Option<RemoteError>,
name: &str,