Catalogue a merged panorama the moment it is written

A finished merge drained the outbox and started a rescan beside it. The
scan raced the upload: on a folder library the 800 MB copy was still
running when the folder was listed, on Nextcloud the upload takes
minutes, and either way the listing lacked the composite, recorded the
folder's validator, and nothing looked again until the next sync pass.

The job now reports what the catalog needs (the name, the size of the
picture it opens on, the capture time it wrote into the DNG) and the
library writes the row at once, in one transaction, keyed where the scan
will list the file; a composite with no time of its own takes its
sources' earliest. The grid reloads and shows it beside its sources.

The upload then gives the row what only the server knows: after sending
a file the catalog already has a row for, it lists the folder once, takes
the file id the server assigned, and records it (and, once the merge
makes them, the thumbnails waiting beside the payload) under that id.
Every drain now rescans when something landed in the library, after the
upload rather than beside it. A second merge of the same frames is no
longer named over the first: the name is checked against the catalog's
names in that folder, which is all that knows it once the outbox is empty.
This commit is contained in:
2026-09-28 19:57:37 -04:00
parent 37136f7377
commit 98a67393d9
11 changed files with 1335 additions and 89 deletions
+1 -1
View File
@@ -48,8 +48,8 @@ mod window;
pub use controller::LibraryController;
pub use grid::wire;
pub use open::{catalogue_composite, open, reload};
pub(crate) use open::{forget_catalog, show_catalog_now, start_rescan};
pub use open::{open, reload};
pub use ratings_keywords::paste_settings_to_selection;
pub(crate) use ratings_keywords::{save_judgements, start_sidecar_writes};
pub use timeline::format_date;
+46
View File
@@ -41,6 +41,52 @@ pub fn reload(window: &AppWindow, ctl: &Rc<LibraryController>) {
note_place(window, ctl);
}
/// TRACES: FR-MRG-6
/// Put a composite the merge has just staged into the catalog and the grid,
/// before its upload has finished and before any scan could find it.
///
/// See `library::composite` for why this does not wait for the scan. The
/// row is keyed where the scan will list the file, so the scan after the
/// upload updates it rather than adding a second.
pub fn catalogue_composite(
window: &AppWindow,
ctl: &Rc<LibraryController>,
placed: &crate::merge_ui::Placed,
) {
let Some((conn, _)) = ctl.session.borrow().clone() else {
return;
};
let root = conn.account.root.clone();
let c = &placed.composite;
let source_ref = crate::export::staged_remote_path(&root, &placed.remote_dir, &c.name);
let row = library::CompositeRow {
source_ref: source_ref.as_str().to_string(),
width: c.width,
height: c.height,
captured_at: c.captured_at,
captured_offset: c.captured_offset,
camera: c.camera.clone(),
lens: c.lens.clone(),
iso: c.iso,
file_size: c.file_size,
sources: placed.sources.clone(),
};
let written = {
let borrow = ctl.catalog.borrow();
let Some(catalog) = borrow.as_ref() else {
return;
};
library::catalogue_composite(catalog, &root, &row)
};
match written {
Ok(image) => {
log::info!("merge: {} catalogued as image {image}", row.source_ref);
reload(window, ctl);
}
Err(e) => log::warn!("merge: cataloguing {}: {e}", row.source_ref),
}
}
/// Open a library: show the grid, start a scan, then fill in thumbnails.
///
/// Called from the launch screen's "Open library" button — the callback that
+1 -17
View File
@@ -112,23 +112,7 @@ pub(super) fn start_derived_sync(window: &AppWindow, ctl: &Rc<LibraryController>
let outbox = crate::export::outbox_dir(&conn.account);
if crate::export::pending_count(&outbox) > 0 {
let rx = crate::export::spawn_upload(conn.clone(), conn.account.root.clone(), outbox);
executors::spawn(Executor::Io, "upload-log", move || {
while let Ok(msg) = rx.recv() {
match msg {
crate::export::UploadMessage::Status(s) => log::info!("export: {s}"),
crate::export::UploadMessage::Finished {
uploaded,
remaining,
error,
} => {
log::info!("export: {uploaded} uploaded, {remaining} still queued");
if let Some(e) = error {
log::warn!("export upload stopped: {e}");
}
}
}
}
});
crate::export::watch_upload(rx, window.as_weak());
}
}