mirror of
https://github.com/storytold/lightcraft.git
synced 2026-10-24 10:09:01 -06:00
Lightroom import: one warning per skipped image, as the other import warnings
Go back to one "image N: not imported; …" warning per image, the format the import's other skip warnings use; the result window already caps the list (first eight, then "N more warnings"). Note the behaviour in the LRC-LIB-CATALOG-IMPORT tracker row. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RhZLBHaSnSko1eMByPibfh
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
ad43a976f3
commit
1de6401aeb
@@ -524,9 +524,9 @@ pub fn read_with_progress(path: &Path, cancel: &AtomicBool, total: &AtomicUsize,
|
||||
warnings,
|
||||
};
|
||||
// images whose file, folder or root record is gone, or whose path is empty (Lightroom leaves
|
||||
// these behind and ignores them); they and their virtual copies are skipped with one warning
|
||||
// per reason instead of failing the import
|
||||
let mut orphans: BTreeMap<&str, Vec<i64>> = BTreeMap::new();
|
||||
// these behind and ignores them); they and their virtual copies are skipped with a warning
|
||||
// instead of failing the import
|
||||
let mut orphans = HashSet::new();
|
||||
for image in table("Adobe_images", true)? {
|
||||
ensure_read_active(cancel)?;
|
||||
let id = number(&image, "id_local");
|
||||
@@ -538,7 +538,8 @@ pub fn read_with_progress(path: &Path, cancel: &AtomicBool, total: &AtomicUsize,
|
||||
let (file, folder, root) = match record {
|
||||
Ok(found) => found,
|
||||
Err(why) => {
|
||||
orphans.entry(why).or_default().push(id);
|
||||
out.warnings.push(format!("image {id}: not imported; {why}"));
|
||||
orphans.insert(id);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
@@ -547,7 +548,8 @@ pub fn read_with_progress(path: &Path, cancel: &AtomicBool, total: &AtomicUsize,
|
||||
let folder_path = text(folder, "pathFromRoot");
|
||||
let name = text(file, "idx_filename");
|
||||
if root_path.is_empty() || name.is_empty() {
|
||||
orphans.entry("incomplete original path").or_default().push(id);
|
||||
out.warnings.push(format!("image {id}: not imported; incomplete original path"));
|
||||
orphans.insert(id);
|
||||
continue;
|
||||
}
|
||||
let (photo_path, original) = original_path(root_path, folder_path, name, path);
|
||||
@@ -576,46 +578,29 @@ pub fn read_with_progress(path: &Path, cancel: &AtomicBool, total: &AtomicUsize,
|
||||
original,
|
||||
});
|
||||
}
|
||||
for (why, ids) in &orphans {
|
||||
out.warnings.push(skipped_warning(ids, why));
|
||||
}
|
||||
drop_copies_of(&mut out, &orphans.into_values().flatten().collect());
|
||||
drop_copies_of(&mut out, &orphans);
|
||||
refuse_non_media(&mut out);
|
||||
ensure_read_active(cancel)?;
|
||||
Ok(out)
|
||||
}
|
||||
|
||||
/// One warning for a group of images skipped for the same reason, listing the first few ids so a
|
||||
/// catalog with many broken records doesn't bury the other warnings.
|
||||
fn skipped_warning(ids: &[i64], why: &str) -> String {
|
||||
const SHOWN: usize = 10;
|
||||
let mut list = ids.iter().take(SHOWN).map(i64::to_string).collect::<Vec<_>>().join(", ");
|
||||
if ids.len() > SHOWN {
|
||||
list.push_str(&format!(", … ({} more)", ids.len() - SHOWN));
|
||||
}
|
||||
let n = ids.len();
|
||||
format!("{n} image{} not imported ({why}): {list}", if n == 1 { "" } else { "s" })
|
||||
}
|
||||
|
||||
/// Drop the virtual copies whose master was not imported, saying so in the import's warnings: a
|
||||
/// copy has no original of its own.
|
||||
fn drop_copies_of(data: &mut CatalogImport, masters: &HashSet<i64>) {
|
||||
if masters.is_empty() {
|
||||
return;
|
||||
}
|
||||
let mut dropped = Vec::new();
|
||||
let mut warnings = Vec::new();
|
||||
data.photos.retain(|p| {
|
||||
let master = number(&p.image, "masterImage");
|
||||
// 0 marks a master, never a copy
|
||||
if master == 0 || !masters.contains(&master) {
|
||||
return true;
|
||||
}
|
||||
dropped.push(p.source_id);
|
||||
warnings.push(format!("image {}: not imported; its original was not imported", p.source_id));
|
||||
false
|
||||
});
|
||||
if !dropped.is_empty() {
|
||||
data.warnings.push(skipped_warning(&dropped, "virtual copy of an image not imported"));
|
||||
}
|
||||
data.warnings.extend(warnings);
|
||||
}
|
||||
|
||||
/// Drop the catalog's images whose original is not a file type LightCraft reads, saying so in the
|
||||
@@ -1641,7 +1626,7 @@ mod tests {
|
||||
let catalog = read(&path).unwrap();
|
||||
std::fs::remove_file(path).unwrap();
|
||||
assert!(catalog.photos.is_empty());
|
||||
assert!(catalog.warnings.iter().any(|w| w == "1 image not imported (missing file record): 1"), "{:?}", catalog.warnings);
|
||||
assert!(catalog.warnings.iter().any(|w| w == "image 1: not imported; missing file record"), "{:?}", catalog.warnings);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -1653,16 +1638,7 @@ mod tests {
|
||||
let catalog = read(&path).unwrap();
|
||||
std::fs::remove_file(path).unwrap();
|
||||
assert!(catalog.photos.is_empty());
|
||||
assert!(catalog.warnings.iter().any(|w| w == "1 image not imported (incomplete original path): 1"), "{:?}", catalog.warnings);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn skipped_images_are_summarised_in_one_warning() {
|
||||
let ids: Vec<i64> = (1..=12).collect();
|
||||
assert_eq!(
|
||||
skipped_warning(&ids, "missing file record"),
|
||||
"12 images not imported (missing file record): 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, … (2 more)"
|
||||
);
|
||||
assert!(catalog.warnings.iter().any(|w| w == "image 1: not imported; incomplete original path"), "{:?}", catalog.warnings);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -1671,7 +1647,7 @@ mod tests {
|
||||
data.photos.retain(|p| p.source_id != 1);
|
||||
drop_copies_of(&mut data, &HashSet::from([1]));
|
||||
assert!(data.photos.is_empty());
|
||||
assert_eq!(data.warnings, ["1 image not imported (virtual copy of an image not imported): 2"]);
|
||||
assert_eq!(data.warnings, ["image 2: not imported; its original was not imported"]);
|
||||
validate(&data).unwrap();
|
||||
}
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ again. Previously browsed Local records join All Photos even when their original
|
||||
their promotion is counted once and undone together with the catalog import.
|
||||
An image whose file, folder or root folder record is gone from the catalog, or whose original path
|
||||
is empty (Lightroom leaves these behind and ignores them), is skipped together with its virtual
|
||||
copies instead of failing the whole import; one warning per reason lists the skipped images.
|
||||
copies instead of failing the whole import, with a warning for each.
|
||||
Virtual copies count as added records too; reimport reports zero additions when all
|
||||
records already exist. Existing LightCraft
|
||||
edits are preserved by default; `updateExisting=true` explicitly replaces them. Persistent import
|
||||
|
||||
@@ -698,7 +698,7 @@ From `08-lightroom-classic-extras.md` (Classic-only features) and part 2 of `06-
|
||||
| LRC-LIB-SURVEY | Survey view | P2 | ✅ | `cmd:view.survey` | N |
|
||||
| LRC-LIB-REFVIEW | Reference view | P2 | ✅ | `cmd:view.reference`, `cmd:photo.setReference`, `crates/ui-egui/src/panels/compare.rs` (`show_reference`) | View ▸ Reference View (⇧R): the reference photo beside the active one (edited with the Edit panel); photo menu ▸ Set as Reference Photo; each pane says “Rendering…” (widget `compare:rendering:<pane>`) or “This photo can't be opened” (`compare:renderFailed:<pane>`) as the loupe does |
|
||||
| LRC-LIB-CATALOG-PANEL | Catalog sets | P1 | ✅ | `cmd:library.source` (`missing`) | all, recently added, picks, recently deleted, missing photos (when any) |
|
||||
| LRC-LIB-CATALOG-IMPORT | Native Lightroom Classic catalog import | P1 | 🟡 | `cmd:library.inspectLightroom`, `cmd:library.importLightroom`, `crates/engine/src/lightroom_catalog.rs`, `docs/lightroom-catalog-import.md` | read-only `.lrcat` plus committed WAL pages (also WAL-only tables with main schema format 0), in-tree read-only Rust SQLite B-tree reader; background inspection/import with progress/cancellation, per-open library identity checks; persistent result with destination library, offline-original counts and Show in Library, dismissed when switching libraries; offline Local records are promoted without probing missing files; originals in place, ratings/flags/labels, XMP, hierarchical keywords, collection sets/regular collections, virtual copies, mapped develop settings; stable reimport identities, undo, missing-file placeholders, a root folder not found at its recorded path looked up relative to the catalog (Lightroom's `relativePathFromCatalog`, so a project folder copied from another computer stays linked; reported as a warning), existing edits preserved. Best-effort deduplicated recovery archives retain settings/history/snapshots/collection rules (32 MiB each, eight archives/128 MiB aggregate); archive limits/write failures and optional-table limits warn without blocking photo import; unrelated unsupported tables and opaque SQL BLOBs skipped. Custom white balance on relative-WB raws (`RAW` in Lightroom's terms) stays As Shot and is reported, since the catalog keeps no as-shot white to shift from (issue #510). Adobe rendering, AI data and native history replay remain incomplete; no writes to Lightroom catalogs. Open Library given a Lightroom catalog folder, an `.lrdata` / `.lrcat-data` folder or the `.lrcat` makes no library there and offers this import with the catalog found (`cmd:app.openLibrary`, `crates/engine/src/lightroom_folder.rs`; issue #891) |
|
||||
| LRC-LIB-CATALOG-IMPORT | Native Lightroom Classic catalog import | P1 | 🟡 | `cmd:library.inspectLightroom`, `cmd:library.importLightroom`, `crates/engine/src/lightroom_catalog.rs`, `docs/lightroom-catalog-import.md` | read-only `.lrcat` plus committed WAL pages (also WAL-only tables with main schema format 0), in-tree read-only Rust SQLite B-tree reader; background inspection/import with progress/cancellation, per-open library identity checks; persistent result with destination library, offline-original counts and Show in Library, dismissed when switching libraries; offline Local records are promoted without probing missing files; originals in place, ratings/flags/labels, XMP, hierarchical keywords, collection sets/regular collections, virtual copies, mapped develop settings; stable reimport identities, undo, missing-file placeholders, a root folder not found at its recorded path looked up relative to the catalog (Lightroom's `relativePathFromCatalog`, so a project folder copied from another computer stays linked; reported as a warning), existing edits preserved. Best-effort deduplicated recovery archives retain settings/history/snapshots/collection rules (32 MiB each, eight archives/128 MiB aggregate); archive limits/write failures and optional-table limits warn without blocking photo import; unrelated unsupported tables and opaque SQL BLOBs skipped. Custom white balance on relative-WB raws (`RAW` in Lightroom's terms) stays As Shot and is reported, since the catalog keeps no as-shot white to shift from (issue #510). An image whose file, folder or root folder record is missing from the catalog, or whose original path is empty, is skipped with its virtual copies, each with a warning, instead of failing the whole import (PR #2230). Adobe rendering, AI data and native history replay remain incomplete; no writes to Lightroom catalogs. Open Library given a Lightroom catalog folder, an `.lrdata` / `.lrcat-data` folder or the `.lrcat` makes no library there and offers this import with the catalog found (`cmd:app.openLibrary`, `crates/engine/src/lightroom_folder.rs`; issue #891) |
|
||||
| LRC-LIB-CATALOG-LOCK | Catalog open in one program at a time | P1 | ✅ | `cmd:app.openLibrary`, `crates/catalog/src/lock.rs`, `crates/engine/src/library.rs` (`open_library`) | exclusive OS lock on `catalog.lock` for the session (app and `lightcraft-cli --library`); a second opener is refused with who has it (`catalog.lock.owner`: program, process, computer); released by the OS on a crash, so never stale; file systems without locks open unprotected with a warning (the app's notices, `library.info` → `settingsWarnings`, and the CLI's stderr; issue #171); no read-only mode; the browser build (OPFS) has no guard across tabs yet. A library that can't be opened at launch (locked, unreadable, newer format, drive missing) shows a window — Try Again, Choose Another Library…, Continue Without Saving (then a banner; nothing written), Quit — instead of a silent demo session (`crates/ui-egui/src/panels/library_problem.rs`); the lock is released explicitly when the library closes, not by letting its file handle close, so a program another thread is starting at that moment (which holds a copy of every file handle until it is launched) cannot keep the library "already open" for a moment (issue #680) |
|
||||
| LRC-LIB-CATALOG-SWITCH | New / Open Recent catalog, catalog name shown | P1 | ✅ | `cmd:app.newLibrary`, `cmd:app.openLibrary`, `cmd:app.recentLibraries`, `cmd:app.clearRecentLibraries`, `crates/ui-egui/src/panels/settings.rs`, `crates/ui-egui/src/panels/left.rs` | File ▸ New Library… (name, then location), File ▸ Open Recent Library (last 8; missing ones greyed out, dropped when opened; Clear), the open library's name under My Photos and in the window title (issue #902); a library that can't be opened leaves the current one open; ⌘O (Ctrl+O on Windows and Linux) runs Open Library… and asks for a folder once (`crates/ui-egui/src/tests_keymap.rs`: `cmd_o_opens_the_library_picker_once`); a dialog or a text field with the focus keeps the key |
|
||||
| LRC-LIB-FOLDERS | Disk folder tree | P1 | ✅ | `cmd:library.browse`, `cmd:folder.rename`, `cmd:folder.move`, `crates/ui-egui/src/panels/left.rs` (`local_section`, `folder_tree`) | standard places + the browsed folder + Browse Folder…, each an expandable tree of subfolders, breadcrumb navigation; context menu: Rename Folder…, Move Folder To…, Show in Finder (on disk, photos relinked; one undo step each: undo / redo move the folder back and forth, refused when the destination is taken) |
|
||||
|
||||
Reference in New Issue
Block a user