Files
lightcraft/crates/ui-egui/src/tests_open_lightroom.rs
T
Jason Baldwin 43f31532a7 LRC-LIB-CATALOG-IMPORT: Open Library recognises a Lightroom catalog and offers to import it (#891)
Open Library given a Lightroom Classic catalog folder, one of its .lrdata /
.lrcat-data folders, Backups/, the .lrcat or its -wal/-shm/.lock files used to
create an empty LightCraft library inside Lightroom's folder (or, for the
.lrcat file, close the current library and then fail). Now:

- engine: lightroom_folder::lightroom_catalog_at (names only, top-level scan
  bounded to 10 000 entries, never fails); Session::open_library refuses those
  places with EngineError::LightroomCatalog {path, catalog} before creating
  anything. A LightCraft library (catalog.snap / catalog.log) always wins.
- app: app.openLibrary checks before closing the current library, keeps it,
  returns {opened: false, lightroomCatalog, path} and shows a dialog with
  Import Lightroom Catalog… (file.importLightroom with the catalog found) and
  Cancel. The library problem window gets the error instead.
- Lightroom import status keeps the catalog path after the task ends.
- CLI --library explains how to import instead.
2026-10-11 08:40:18 +02:00

100 lines
4.5 KiB
Rust

//! Issue #891: File ▸ Open Library… given a Lightroom Classic catalog folder doesn't make a
//! LightCraft library inside it. The library open now stays, and a dialog offers Import Lightroom
//! Catalog… with the catalog found, which starts the import of that `.lrcat`.
use std::time::Duration;
use serde_json::json;
use crate::headless::Headless;
use crate::state::Dialog;
use crate::{LightcraftApp, Services};
const T: Duration = Duration::from_secs(20);
fn has(h: &Headless, id: &str) -> bool {
h.app.widgets.iter().any(|(w, _)| w == id)
}
fn names(dir: &std::path::Path) -> Vec<std::ffi::OsString> {
let mut v: Vec<_> = std::fs::read_dir(dir).unwrap().map(|e| e.unwrap().file_name()).collect();
v.sort();
v
}
#[test]
fn open_library_on_a_lightroom_catalog_offers_its_import() {
let dir = std::env::temp_dir().join(format!("lc-ui-openlrcat-{}", std::process::id()));
let _ = std::fs::remove_dir_all(&dir);
let lib = dir.join("LightCraft Library");
let lr = dir.join("Lightroom");
std::fs::create_dir_all(lr.join("Lightroom Catalog Previews.lrdata")).unwrap();
std::fs::create_dir_all(lr.join("Lightroom Catalog.lrcat-data")).unwrap();
std::fs::create_dir_all(lr.join("Backups")).unwrap();
let lrcat = lr.join("Lightroom Catalog.lrcat");
std::fs::write(&lrcat, b"not really SQLite").unwrap();
let before = names(&lr);
let mut session = lightcraft_engine::Session::new().with_fs();
session.open_library(&lib, true).unwrap();
let photos = session.catalog.len();
assert!(photos > 0);
let picked = lr.to_string_lossy().to_string();
let png = |img: &lightcraft_raster::Rgba8| {
lightcraft_codecs::encode_png(&lightcraft_codecs::EncodeImage::rgba8(img), &lightcraft_codecs::EncodeMeta::default()).unwrap_or_default()
};
let services = Services {
pick_folder: Some(Box::new(move || Some(picked.clone()))),
png: Some(Box::new(png)),
write: Some(Box::new(|p: &str, b: &[u8]| std::fs::write(p, b).map_err(|e| e.to_string()))),
..Default::default()
};
let mut h = Headless::new(LightcraftApp::new(session, services), [1200.0, 800.0], 1.0);
h.step();
// File ▸ Open Library…, the Lightroom folder chosen in the folder dialog
let r = h.app.run("app.openLibrary", json!({})).unwrap();
assert_eq!(r["opened"], false, "{r}");
assert_eq!(r["lightroomCatalog"], json!(lrcat.to_string_lossy()), "{r}");
h.step();
h.step();
assert!(
matches!(&h.app.ui.dialog, Some(Dialog::LightroomCatalog { catalog: Some(c), .. }) if *c == lrcat.to_string_lossy()),
"{:?}",
h.app.ui.dialog
);
assert!(has(&h, "button:dialogOk") && has(&h, "button:dialogCancel"));
// the library open before is still open, and nothing was made in Lightroom's folder
assert_eq!(h.app.session.library.as_ref().map(|l| l.dir.clone()), Some(lib.clone()));
assert_eq!(h.app.session.catalog.len(), photos);
assert_eq!(names(&lr), before);
if let Some(p) = std::env::var_os("LIGHTCRAFT_TEST_SHOTS") {
let r = h.request("ui.screenshot", json!({"path": std::path::Path::new(&p).join("prompt-891.png").to_string_lossy(), "headless": true}), T);
assert_eq!(r["ok"], true, "{r}");
}
// Cancel: nothing happens
assert_eq!(h.request("ui.clickWidget", json!({"id": "button:dialogCancel"}), T)["ok"], true);
h.step();
assert!(h.app.ui.dialog.is_none());
assert!(h.app.lightroom.is_none() && h.app.lightroom_last.is_none());
// a previews folder given by path (control channel, agents): the same offer
let r = h.app.run("app.openLibrary", json!({"path": lr.join("Lightroom Catalog Previews.lrdata").to_string_lossy()})).unwrap();
assert_eq!(r["lightroomCatalog"], json!(lrcat.to_string_lossy()), "{r}");
h.step();
h.step();
// Import Lightroom Catalog…: the import of that catalog starts (no file dialog), into the
// library open now. (This fake catalog then fails to read: only its path matters here.)
assert_eq!(h.request("ui.clickWidget", json!({"id": "button:dialogOk"}), T)["ok"], true);
h.step();
assert!(h.app.ui.dialog.is_none());
h.step_until(T, |h| h.app.lightroom.is_none());
let last = h.app.lightroom_last.clone().unwrap_or_default();
assert_eq!(last["path"], json!(lrcat), "{last}");
assert_eq!(h.app.session.library.as_ref().map(|l| l.dir.clone()), Some(lib.clone()));
assert_eq!(names(&lr), before, "the catalog is only read");
drop(h);
let _ = std::fs::remove_dir_all(&dir);
}