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:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user