diff --git a/crates/engine/src/cmd/query.rs b/crates/engine/src/cmd/query.rs index 09fbbbdb..e7ffde14 100644 --- a/crates/engine/src/cmd/query.rs +++ b/crates/engine/src/cmd/query.rs @@ -113,7 +113,7 @@ pub fn specs() -> Vec { .map(|r| json!({"group": r.group, "tag": r.tag, "name": r.name, "value": r.value})) .collect(); // XMP: the sidecar if there is one, else the file's own packet - let packet = crate::sidecar::read_packet(path, ph.kind, s.xmp.naming).map(|(x, _)| x).or_else(|| lightcraft_meta::embedded(&bytes).xmp); + let packet = crate::sidecar::read_packet(path, ph.kind, s.sidecar_naming(ph.id)).map(|(x, _)| x).or_else(|| lightcraft_meta::embedded(&bytes).xmp); let xmp: Vec = packet .and_then(|x| lightcraft_meta::parse_xmp(&x).ok()) .map(|d| d.properties.into_iter().filter(|(k, _)| !k.starts_with("lc:")).map(|(k, v)| json!({"name": k, "value": v.join("; ")})).collect()) diff --git a/crates/engine/src/cmd/xmp.rs b/crates/engine/src/cmd/xmp.rs index 7b7d4325..b83e7275 100644 --- a/crates/engine/src/cmd/xmp.rs +++ b/crates/engine/src/cmd/xmp.rs @@ -9,14 +9,26 @@ use crate::{Result, Session}; fn save(s: &mut Session, p: &Value) -> Result { let mut written = Vec::new(); + let mut merged = Vec::new(); + let mut backups = Vec::new(); let mut failed = Vec::new(); + let owners = crate::sidecar::StemOwners::of(&s.catalog); for id in s.targets(p) { - match s.save_sidecar(id) { - Ok(path) => written.push(path.display().to_string()), + match s.save_sidecar_with(id, &owners) { + Ok(r) => { + let path = r.path.display().to_string(); + if r.merged { + merged.push(path.clone()); + } + if let Some(b) = r.backup { + backups.push(json!({"path": path, "backup": b.display().to_string()})); + } + written.push(path); + } Err(e) => failed.push(json!({"id": id.0, "error": e.to_string()})), } } - Ok(json!({"written": written, "failed": failed})) + Ok(json!({"written": written, "merged": merged, "backups": backups, "failed": failed})) } fn read(s: &mut Session, p: &Value) -> Result { @@ -63,7 +75,7 @@ pub fn specs() -> Vec { "Save Metadata to File", ["Photo"], Some("Cmd+S"), - "{ids?} — writes each photo's XMP sidecar (metadata + develop settings) next to the original → {written: [path], failed}", + "{ids?} — writes each photo's XMP sidecar (metadata + develop settings) next to the original, merged into an existing one (other apps' data kept; an unreadable one is backed up first) → {written: [path], merged: [path], backups: [{path, backup}], failed}", has_selection, save ), diff --git a/crates/engine/src/sidecar.rs b/crates/engine/src/sidecar.rs index a49e5fee..7b65a98a 100644 --- a/crates/engine/src/sidecar.rs +++ b/crates/engine/src/sidecar.rs @@ -5,12 +5,25 @@ //! creator, keywords, capture time and GPS in the standard namespaces, plus our complete develop //! settings (`lc:settings`, JSON), the pick/reject flag (`lc:flag`) and location (`lc:location`). //! +//! An existing sidecar is never replaced wholesale (issue #92): LightCraft's properties +//! ([`OWNED`], [`OWNED_IF_STATED`]) are merged into it ([`lightcraft_meta::merge_xmp`]) and +//! everything else — another editor's `crs:` develop settings, `xmpMM` history, unknown +//! namespaces — is kept byte for byte. A sidecar that can't be read as XMP is first copied to +//! `.xmp.bak-