Keep a stepped-past download from replacing the open photograph

Opening a photograph from the library starts a download and a timer that
polls for it. Every step along the roll started another, and each one put
its result on screen when it landed, so a frame stepped past earlier
could arrive last and replace the one whose name was showing. Each open
now takes a generation number; a download that lands for an older
generation is recorded in the activity list (its bytes are cached) and
goes no further.

The outgoing session also stayed live until the new download landed.
Its sliders kept working, and a second step before the first landed
saved that session's edit under the new photograph's identity. The
session is now dropped as soon as its edit is saved.
This commit is contained in:
2026-09-26 11:00:36 -04:00
parent 5794f8c6ea
commit 3b97195b37
3 changed files with 106 additions and 72 deletions
+34
View File
@@ -2670,6 +2670,14 @@ fn wire_remote_open(
let inspection = inspection.clone();
let prefetch = prefetch.clone();
// Which open is the current one. Every open starts its own download and
// its own timer, and a walk along the roll starts one per press — so the
// transfers land in whatever order the network finishes them, not the
// order they were asked for. Without this the photograph stepped past
// three presses ago could arrive last and replace the one whose name is
// on screen, and its edit would then be saved under that name.
let current = Rc::new(Cell::new(0u64));
{
let weak = window.as_weak();
let library = library.clone();
@@ -2689,6 +2697,18 @@ fn wire_remote_open(
// before the identity below is overwritten.
presets::save_open_edit(&w, &open_image.borrow(), &session, &library);
// And let the outgoing session go now, not when the download
// lands. Kept, its sliders stayed live over a photograph that was
// no longer the open one, and a second step before the first
// landed saved *that* session's edit under the new photograph's
// identity, which is set a few lines below.
*session.borrow_mut() = None;
rows.set_vec(Vec::<ParamRow>::new());
w.global::<Develop>().set_enabled(false);
current.set(current.get() + 1);
let mine = current.get();
let name = path.rsplit('/').next().unwrap_or(&path).to_string();
reset_view_state(&w);
w.set_filename(name.clone().into());
@@ -2748,6 +2768,7 @@ fn wire_remote_open(
}
log::info!("fetching {path} for develop");
w.set_load_error("Downloading…".into());
let rx = library::spawn_full_fetch(conn, path.clone(), cache);
@@ -2774,6 +2795,7 @@ fn wire_remote_open(
let path = path.clone();
let viewport = viewport.clone();
let inspection = inspection.clone();
let current = current.clone();
let sidecar_rx = Rc::new(sidecar_rx);
let timer = Rc::new(slint::Timer::default());
let held = timer.clone();
@@ -2786,6 +2808,18 @@ fn wire_remote_open(
held.stop();
let Some(w) = weak.upgrade() else { return };
// Stepped past while it was coming down. The transfer
// still counts — it is in the cache, and the step back
// will be a disk read — but it is not what is on screen.
if current.get() != mine {
match &got {
Ok(b) => job.finish(activity::describe_bytes(b.len() as u64)),
Err(e) => job.fail(e.message.clone()),
}
log::debug!("{name}: landed after the view moved on");
return;
}
let bytes = match got {
Ok(b) => b,
Err(e) => {