Take the server's shards and dates when the scan completes, not after the sweep
Benchmarks / CPU and I/O (per commit) (push) Successful in 8m28s
Benchmarks / Frame budget (on demand) (push) Skipped
Build and test / Desktop (Linux) (push) Failing after 36s
Build and test / Layer separation (push) Successful in 28s
Traceability / Requirement traces (push) Failing after 39s
🐳 Android image / Build and push (push) Successful in 2s
Build and test / android-image (push) Successful in 2s
🐳 Windows image / Build and push (push) Successful in 1s
Build and test / windows-image (push) Successful in 1s
Build and test / Android (aarch64) (push) Successful in 43m11s
Build and test / Windows (x86_64, cross) (push) Failing after 41m4s
Benchmarks / CPU and I/O (per commit) (push) Successful in 8m28s
Benchmarks / Frame budget (on demand) (push) Skipped
Build and test / Desktop (Linux) (push) Failing after 36s
Build and test / Layer separation (push) Successful in 28s
Traceability / Requirement traces (push) Failing after 39s
🐳 Android image / Build and push (push) Successful in 2s
Build and test / android-image (push) Successful in 2s
🐳 Windows image / Build and push (push) Successful in 1s
Build and test / windows-image (push) Successful in 1s
Build and test / Android (aarch64) (push) Successful in 43m11s
Build and test / Windows (x86_64, cross) (push) Failing after 41m4s
The derived sync fired only after the metadata sweep, so a fresh device re-derived every thumbnail it scrolled past, re-detected faces and re-read every header for hours before adopting the shards and snapshot that held all of it. It now fires as soon as the scan completes — the first moment the rows the merges key on exist — and the sweep starts behind it. In steady state that pass is one listing. The catalog merge gains a fourth half: capture metadata (captured_at, offset, camera, lens, ISO) for images still at metadata_state < 2, matched by oc:fileid from a remote row at 2. A date is a fact about the file's bytes, not local state, and the snapshot already carried it. The sweep's per-chunk query then finds nothing left, and the timeline is whole on a fresh device without a header fetch. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -67,6 +67,9 @@ pub struct SyncReport {
|
||||
/// devices already had gains *no* collection, and reporting only the
|
||||
/// former left the sidebar showing no count beside a full collection.
|
||||
pub members_gained: usize,
|
||||
/// Images dated from the remote's snapshot rather than by this device's
|
||||
/// own sweep — what makes the timeline whole on a fresh device.
|
||||
pub dates_gained: usize,
|
||||
|
||||
// Face data is counted apart from thumbnails for the same reason keywords
|
||||
// are counted apart from collections: "adopted 4,812 faces" is a sentence
|
||||
@@ -815,6 +818,7 @@ fn merge_downloaded(
|
||||
report.catalog_merged = true;
|
||||
report.collections_gained += merge.inserted + merge.updated;
|
||||
report.members_gained += merge.members_added;
|
||||
report.dates_gained += merge.metadata_adopted;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -1378,6 +1378,15 @@ fn drain_scan(
|
||||
}
|
||||
}
|
||||
load_window(&w, ctl);
|
||||
// Take the server's shards and catalog *now*,
|
||||
// before the sweep: the rows they key on exist
|
||||
// from this moment, and on a fresh device
|
||||
// every thumbnail, face and collection a peer
|
||||
// has already made is on the server. Waiting
|
||||
// for the sweep — hours on a large library —
|
||||
// meant re-deriving all of it here first. In
|
||||
// steady state this is one listing.
|
||||
start_derived_sync(&w, ctl);
|
||||
// Everything the grid did not touch: the rest
|
||||
// of the library gets a thumbnail and a date,
|
||||
// so the timeline describes all of it rather
|
||||
@@ -4201,8 +4210,11 @@ fn spawn_scheduled_backup(ctl: &Rc<LibraryController>) {
|
||||
|
||||
/// Push shards and the catalog to the server, and take what it has.
|
||||
///
|
||||
/// Fired after the sweep completes, when there is a finished index worth
|
||||
/// sharing, and from the Sync button for an explicit exchange.
|
||||
/// Fired when the scan completes, so a fresh device inherits its peers'
|
||||
/// work before deriving any of its own; after the sweep completes, when
|
||||
/// there is a finished index worth sharing; and from the Sync button for an
|
||||
/// explicit exchange. A pass still running when the next trigger fires is
|
||||
/// left to finish — the guard below.
|
||||
fn start_derived_sync(window: &AppWindow, ctl: &Rc<LibraryController>) {
|
||||
// An escape hatch for running the app against a *copied* library without
|
||||
// touching the account's real server.
|
||||
@@ -4326,7 +4338,7 @@ fn start_derived_sync(window: &AppWindow, ctl: &Rc<LibraryController>) {
|
||||
crate::derived_sync::SyncMessage::Finished(report) => {
|
||||
log::info!(
|
||||
"sync: {} shard(s) up, {} down ({} thumbnails), \
|
||||
catalog {}{}",
|
||||
catalog {}{}{}",
|
||||
report.shards_uploaded,
|
||||
report.shards_downloaded,
|
||||
report.thumbnails_adopted,
|
||||
@@ -4343,6 +4355,11 @@ fn start_derived_sync(window: &AppWindow, ctl: &Rc<LibraryController>) {
|
||||
format!(", {} collection(s) gained", report.collections_gained)
|
||||
} else {
|
||||
String::new()
|
||||
},
|
||||
if report.dates_gained > 0 {
|
||||
format!(", {} date(s) gained", report.dates_gained)
|
||||
} else {
|
||||
String::new()
|
||||
}
|
||||
);
|
||||
w.set_library_syncing(false);
|
||||
@@ -4359,13 +4376,22 @@ fn start_derived_sync(window: &AppWindow, ctl: &Rc<LibraryController>) {
|
||||
w.set_library_status(format!("synced · {summary}").into());
|
||||
}
|
||||
// Adopted thumbnails and merged collections both change
|
||||
// what the grid should show.
|
||||
// what the grid should show; adopted dates change its
|
||||
// order, and the timeline beside it.
|
||||
if report.thumbnails_adopted > 0
|
||||
|| report.collections_gained > 0
|
||||
|| report.members_gained > 0
|
||||
|| report.dates_gained > 0
|
||||
{
|
||||
load_window(&w, &ctl_cb);
|
||||
}
|
||||
if report.dates_gained > 0 {
|
||||
let borrow = ctl_cb.catalog();
|
||||
let borrow = borrow.borrow();
|
||||
if let Some(cat) = borrow.as_ref() {
|
||||
refresh_timeline(&w, cat, &ctl_cb);
|
||||
}
|
||||
}
|
||||
// TRACES: FR-CAT-7
|
||||
// And the sidebar, which the grid reload does not
|
||||
// touch. Membership counts as a change: a sync that
|
||||
|
||||
Reference in New Issue
Block a user