mirror of
https://github.com/storytold/lightcraft.git
synced 2026-11-02 14:39:41 -06:00
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.
100 lines
4.5 KiB
Rust
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);
|
|
}
|