Open a photograph with the edit it already carries, when DarkRoom has none

The library's DNGs carry the photographer's earlier develop settings
in their embedded XMP — the house style their photographs were made
with. A photograph opened with no edit of DarkRoom's now starts from
that earlier edit, translated (HSL bands, highlights, blacks and the
rest), as one undoable step named "Earlier Edit"; from there it is an
ordinary edit, saved with the photograph. Export does the same, so a
photograph never opened exports as opening it would show.

Only on positive evidence that there is no DarkRoom edit: a local file
with no sidecar beside it, or a server that answered "no such file"
with nothing in the cache. The stored-edit fetch now says which
(FetchedSidecar::absent). Offline, unreachable or unreadable never
counts — the earlier edit would otherwise be saved over a real edit
that merely failed to arrive.
This commit is contained in:
2026-10-03 14:22:49 -04:00
parent 013596e1bd
commit e12783da9f
6 changed files with 222 additions and 79 deletions
+43 -11
View File
@@ -440,7 +440,7 @@ pub fn spawn_sidecar_fetch(
image_path: String,
cache_dir: PathBuf,
offline: bool,
) -> Receiver<Option<dr_pipeline::Sidecar>> {
) -> Receiver<FetchedSidecar> {
let (tx, rx) = std::sync::mpsc::channel();
executors::spawn(Executor::Network, "sidecars", move || {
@@ -455,7 +455,7 @@ pub fn spawn_sidecar_fetch(
// which the next save would then write back over the top of.
if cache.is_pending(&path_str) {
log::debug!("{path_str} has queued local edits; opening from the cache");
let _ = tx.send(cache.load(&path_str));
let _ = tx.send(FetchedSidecar::unknown(cache.load(&path_str)));
return;
}
@@ -472,7 +472,7 @@ pub fn spawn_sidecar_fetch(
}
};
let Some(rt) = rt else {
let _ = tx.send(cache.load(&path_str));
let _ = tx.send(FetchedSidecar::unknown(cache.load(&path_str)));
return;
};
@@ -481,7 +481,7 @@ pub fn spawn_sidecar_fetch(
Ok(b) => b,
Err(e) => {
log::debug!("sidecar fetch backend: {e}");
let _ = tx.send(cache.load(&path_str));
let _ = tx.send(FetchedSidecar::unknown(cache.load(&path_str)));
return;
}
};
@@ -491,12 +491,21 @@ pub fn spawn_sidecar_fetch(
// A 404 is the normal case on a library that has never been
// edited, so this is `ok()` rather than an error path.
let Ok(bytes) = backend.get(&id, None).await else {
// Unreachable, or no such file. The cache cannot tell those
// apart and does not need to: either way it holds the best
// answer this device has.
let _ = tx.send(cache.load(&path_str));
return;
let bytes = match backend.get(&id, None).await {
Ok(bytes) => bytes,
Err(e) => {
// Unreachable, or no such file. The cache holds the best
// answer this device has either way — but only a server
// that said "no such file", with nothing in the cache,
// is the word that there is no edit (FR-DEV-6).
let cached = cache.load(&path_str);
let absent = cached.is_none() && matches!(e, dr_sync::RemoteError::NotFound(_));
let _ = tx.send(FetchedSidecar {
sidecar: cached,
absent,
});
return;
}
};
let text = String::from_utf8_lossy(&bytes).into_owned();
@@ -530,13 +539,36 @@ pub fn spawn_sidecar_fetch(
}
}
let _ = tx.send(parsed);
let _ = tx.send(FetchedSidecar::unknown(parsed));
});
});
rx
}
/// TRACES: FR-CAT-8 | FR-DEV-6
/// What a stored-edit fetch answered.
///
/// `absent` is true only when the server said there is no such file and no
/// cached copy stood in — the one answer that means "DarkRoom has no edit of
/// this photograph", and so the only one that lets its earlier edit in.
/// Every other way of arriving at no sidecar — offline, unreachable, a file
/// that would not parse — leaves it false.
#[derive(Debug, Default)]
pub struct FetchedSidecar {
pub sidecar: Option<dr_pipeline::Sidecar>,
pub absent: bool,
}
impl FetchedSidecar {
fn unknown(sidecar: Option<dr_pipeline::Sidecar>) -> Self {
Self {
sidecar,
absent: false,
}
}
}
/// Fetch one file in full, for opening it in develop.
///
/// Deliberately *not* the preview path. Browsing fetches a range and decodes