Issue #2589: a Lightroom import never creates a photo already in Recently Deleted

A catalog photo whose file matched a photo in Recently Deleted by content was cloned with the trashed
record's `deleted` flag (and its virtual-copy fields), so it landed in Recently Deleted while the report
counted it as imported. A new virtual copy of a trashed master did the same. Both are now live records,
as Create Virtual Copy already does.

Catalog photos matched by path to a trashed photo stay there (the Lightroom import's OnDeleted::Skip,
#706); the report counts them as `inRecentlyDeleted` and the result window says so in one line.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Chad Elliott
2026-10-29 23:56:50 -05:00
co-authored by Claude Opus 5.5
parent b2d89895ad
commit de9b0a8951
20 changed files with 128 additions and 1 deletions
+16 -1
View File
@@ -956,6 +956,9 @@ pub(crate) fn apply_prepared(s: &mut crate::Session, mut data: CatalogImport, co
let mut promoted = HashSet::new();
let mut unmapped = BTreeMap::new();
let mut preserved = 0;
// Library photos in Recently Deleted that a catalog photo matched: they stay there (only an
// import of the files themselves brings a photo back, see `OnDeleted`), and the report says so.
let mut in_trash = HashSet::new();
// Masters precede their virtual copies.
let mut photos: Vec<_> = data.photos.iter().collect();
photos.sort_by_key(|p| number(&p.image, "masterImage") != 0);
@@ -980,6 +983,9 @@ pub(crate) fn apply_prepared(s: &mut crate::Session, mut data: CatalogImport, co
.filter(|id| s.catalog.photo(*id).is_some_and(|p| source_path(p).is_some_and(|path| key(path) == key(&src.path))));
let existing_id = indexed.or_else(|| if master == 0 { by_path.get(&key(&src.path)).copied() } else { None });
let mut p = if let Some(id) = existing_id.and_then(|id| s.catalog.photo(id)) {
if id.deleted {
in_trash.insert(id.id);
}
(**id).clone()
} else if master != 0 {
let master_id = ids.get(&master).copied().ok_or_else(|| error(format!("virtual copy {}: missing master", src.source_id)))?;
@@ -991,12 +997,21 @@ pub(crate) fn apply_prepared(s: &mut crate::Session, mut data: CatalogImport, co
p.id = s.catalog.alloc_photo_id();
p.copy_of = Some(master_id);
p.copy_name = Some(text(&src.image, "copyName").to_string());
// a new copy of a master in Recently Deleted is imported, not trashed with it
p.deleted = false;
p
} else if let Some(duplicate) = duplicates.get(&key(&src.path)).and_then(|id| s.catalog.photo(*id)) {
// Catalogs can contain distinct originals with identical bytes and independent edits.
// The clone is a new record of its own: never in Recently Deleted because the photo with
// the same bytes is (#2589), nor a virtual copy because that photo is one.
let mut p = (**duplicate).clone();
p.id = s.catalog.alloc_photo_id();
p.source = Source::File { path: src.path.clone() };
p.deleted = false;
p.copy_of = None;
p.copy_name = None;
p.local_baseline = None;
p.imported = now.clone();
p.file_name = Path::new(&src.path).file_name().map(|n| n.to_string_lossy().into()).unwrap_or_else(|| src.path.clone());
p
} else {
@@ -1105,7 +1120,7 @@ pub(crate) fn apply_prepared(s: &mut crate::Session, mut data: CatalogImport, co
}
s.merge_undo(s.undo.len().saturating_sub(undo0), "Import Lightroom Catalog");
Ok(AppliedImport {
report: json!({"source":data.source,"photos":ids.len(),"imported":imported,"library":s.library.as_ref().filter(|l| l.on_disk).map(|l| l.dir.to_string_lossy()),"collections":album_count,"preservedExistingEdits":preserved,"missing":missing,"failed":files.failed,"warnings":warnings,"skipped":data.skipped,"unmapped":unmapped,"archive":archive_path,"mapping":ids}),
report: json!({"source":data.source,"photos":ids.len(),"imported":imported,"library":s.library.as_ref().filter(|l| l.on_disk).map(|l| l.dir.to_string_lossy()),"collections":album_count,"preservedExistingEdits":preserved,"inRecentlyDeleted":in_trash.len(),"missing":missing,"failed":files.failed,"warnings":warnings,"skipped":data.skipped,"unmapped":unmapped,"archive":archive_path,"mapping":ids}),
index: index.clone(),
})
}
@@ -157,6 +157,82 @@ fn imports_copies_collections_ratings_and_mapped_edits_in_one_undo_step() {
assert!(s.catalog.albums().next().is_none());
assert!(data.photos[1].settings.contains("CameraProfile")); // unmapped source stays intact
}
/// A library photo at `path`, already committed.
fn library_photo(s: &mut crate::Session, path: &str, edit: impl FnOnce(&mut Photo)) -> PhotoId {
let id = s.catalog.alloc_photo_id();
let mut p = Photo::new(id, Source::File { path: path.into() }, "a.ARW", "ARW", 20, 10, "before");
p.content_hash = Some("same-bytes".into());
edit(&mut p);
s.commit("Add", Op::AddPhoto { photo: Box::new(p) }).unwrap();
id
}
/// A catalog photo whose file has the same bytes as a library photo in Recently Deleted (or a
/// virtual copy) is a new record of its own: it was counted as imported but landed in Recently
/// Deleted (or became a "copy" of nothing it was copied from), so a large import into a library
/// with a trash went missing photo by photo (#2589).
#[test]
fn a_content_duplicate_of_a_trashed_photo_or_virtual_copy_is_a_new_live_photo() {
let trashed: fn(&mut Photo) = |p| p.deleted = true;
let virtual_copy: fn(&mut Photo) = |p| {
p.copy_of = Some(PhotoId(999));
p.copy_name = Some("Copy 1".into());
};
for (case, edit) in [("trashed", trashed), ("virtual copy", virtual_copy)] {
let mut s = crate::Session::new();
let other = library_photo(&mut s, "/elsewhere/a.ARW", edit);
let mut data = sample();
data.photos.retain(|p| number(&p.image, "masterImage") == 0);
let files = crate::import::ImportReport {
duplicates: vec![crate::import::Duplicate {
path: "/missing/test.ARW".into(),
existing: Some(other.0),
reason: "content",
existing_deleted: case == "trashed",
}],
..Default::default()
};
let undo0 = s.undo.len();
let r = apply_prepared(
&mut s,
data,
ApplyPrepared {
update_existing: false,
files,
index: &mut ImportIndex::default(),
archive_path: None,
missing_sources: &HashSet::new(),
undo0,
},
)
.unwrap()
.report;
assert_eq!(r["imported"], 1, "{case}: {r}");
assert_eq!(r["inRecentlyDeleted"], 0, "{case}: {r}");
let new = s.catalog.photo(PhotoId(r["mapping"]["1"].as_u64().unwrap())).unwrap();
assert_ne!(new.id, other, "{case}");
assert!(!new.deleted, "{case}: the new record must not be in Recently Deleted");
assert_eq!((new.copy_of, new.copy_name.as_deref()), (None, None), "{case}: the new record is no virtual copy");
assert_ne!(new.imported, "before", "{case}: imported now, not when the other photo was");
assert_eq!(new.rating, 5, "{case}: the catalog's own metadata applies");
assert_eq!(s.catalog.photo(other).unwrap().deleted, case == "trashed", "{case}: the other photo is left as it was");
}
}
/// A catalog photo matched (by path) to a library photo in Recently Deleted leaves it there, as
/// the import was set up to do (a Lightroom import doesn't restore, see `OnDeleted`), and the report
/// counts it so the photos don't go missing unexplained (#2589). A new virtual copy of it is
/// imported, not trashed with its master.
#[test]
fn a_catalog_photo_matching_a_trashed_record_stays_there_and_is_counted() {
let mut s = crate::Session::new();
let trashed = library_photo(&mut s, "/missing/test.ARW", |p| p.deleted = true);
let r = apply(&mut s, sample(), false).unwrap();
assert_eq!(r["mapping"]["1"], trashed.0, "{r}");
assert!(s.catalog.photo(trashed).unwrap().deleted, "the photo stays in Recently Deleted");
assert_eq!(r["inRecentlyDeleted"], 1, "{r}");
let copy = s.catalog.photos().find(|p| p.copy_of == Some(trashed)).unwrap();
assert!(!copy.deleted, "the new virtual copy is imported, not trashed");
assert_eq!(r["imported"], 1, "only the copy was added: {r}");
}
#[test]
fn preserves_existing_edits_and_rejects_invalid_structure_before_mutation() {
let mut s = crate::Session::new();
+1
View File
@@ -267,6 +267,7 @@
"Update “{name}” with Current Settings": "Aktualizovat „{name}“ podle aktuálního nastavení",
"{n} preset{}": "Přednastavení: {n}{:.0}",
"{count} more warnings.": "Další varování: {count}.",
"{in_trash} catalog photos are in this library's Recently Deleted and stay there; restore them from Recently Deleted to see them.": "Fotografie katalogu v nedávno odstraněných této knihovny: {in_trash}. Zůstávají tam; chcete-li je vidět, obnovte je z nedávno odstraněných.",
"{skipped} catalog images were not imported because their records in the catalog are incomplete.": "Neimportované obrázky katalogu: {skipped}. Jejich záznamy v katalogu jsou neúplné.",
"{photos} catalog records processed; {collections} collections.": "Zpracováno záznamů katalogu: {photos}; kolekcí: {collections}.",
"{missing} originals are missing. Their catalog records are available; use Find Missing Photos to relink them.": "Chybějící originály: {missing}. Jejich záznamy jsou v katalogu dostupné; použijte Najít chybějící fotografie a znovu je propojte.",
+1
View File
@@ -269,6 +269,7 @@
"{n} originals opened in the other editor": "{n} Originale im anderen Editor geöffnet",
"· {in_source} identical copies": "· {in_source} identische Kopien",
"{count} more warnings.": "{count} weitere Warnungen.",
"{in_trash} catalog photos are in this library's Recently Deleted and stay there; restore them from Recently Deleted to see them.": "{in_trash} Katalogfotos liegen in dieser Bibliothek unter „Zuletzt gelöscht“ und bleiben dort; stelle sie dort wieder her, um sie zu sehen.",
"{skipped} catalog images were not imported because their records in the catalog are incomplete.": "{skipped} Katalogbilder wurden nicht importiert, weil ihre Einträge im Katalog unvollständig sind.",
"{photos} catalog records processed; {collections} collections.": "{photos} Katalogeinträge verarbeitet; {collections} Sammlungen.",
"{missing} originals are missing. Their catalog records are available; use Find Missing Photos to relink them.": "{missing} Originale fehlen. Ihre Katalogeinträge sind verfügbar; verwende „Fehlende Fotos suchen“, um sie neu zu verknüpfen.",
+1
View File
@@ -269,6 +269,7 @@
"{n} originals opened in the other editor": "{n} originales abiertos en el otro editor",
"· {in_source} identical copies": "· {in_source} copias idénticas",
"{count} more warnings.": "{count} advertencias más.",
"{in_trash} catalog photos are in this library's Recently Deleted and stay there; restore them from Recently Deleted to see them.": "{in_trash} fotos del catálogo están en Eliminadas recientemente de esta biblioteca y permanecen allí; restáuralas desde Eliminadas recientemente para verlas.",
"{skipped} catalog images were not imported because their records in the catalog are incomplete.": "No se importaron {skipped} imágenes del catálogo porque sus registros en el catálogo están incompletos.",
"{photos} catalog records processed; {collections} collections.": "{photos} registros del catálogo procesados; {collections} colecciones.",
"{missing} originals are missing. Their catalog records are available; use Find Missing Photos to relink them.": "Faltan {missing} originales. Sus registros del catálogo están disponibles; usa Buscar fotos que faltan para volver a vincularlos.",
+1
View File
@@ -257,6 +257,7 @@
"{pid} (missing)": "{pid} (puuttuu)",
"{sel_n} selected · {counted}": "Valittu: {sel_n} · {counted}",
"{size} GB": "{size} Gt",
"{in_trash} catalog photos are in this library's Recently Deleted and stay there; restore them from Recently Deleted to see them.": "Katalogin kuvia tämän kirjaston Äskettäin poistetuissa: {in_trash}. Ne jäävät sinne; palauta ne Äskettäin poistetuista nähdäksesi ne.",
"{skipped} catalog images were not imported because their records in the catalog are incomplete.": "{skipped} catalog images were not imported because their records in the catalog are incomplete.",
"{stage}… {:.0}%": "{stage}… {:.0}%",
"{s} s of AV1 video": "{s} s AV1-videota",
+1
View File
@@ -269,6 +269,7 @@
"{n} originals opened in the other editor": "{n} originaux ouverts dans l’autre éditeur",
"· {in_source} identical copies": "· {in_source} copies identiques",
"{count} more warnings.": "{count} avertissements supplémentaires.",
"{in_trash} catalog photos are in this library's Recently Deleted and stay there; restore them from Recently Deleted to see them.": "{in_trash} photos du catalogue sont dans « Supprimés récemment » de cette photothèque et y restent ; restaurez-les depuis « Supprimés récemment » pour les voir.",
"{skipped} catalog images were not imported because their records in the catalog are incomplete.": "{skipped} images du catalogue n’ont pas été importées, car leurs entrées dans le catalogue sont incomplètes.",
"{photos} catalog records processed; {collections} collections.": "{photos} entrées du catalogue traitées ; {collections} collections.",
"{missing} originals are missing. Their catalog records are available; use Find Missing Photos to relink them.": "{missing} originaux sont manquants. Leurs entrées de catalogue sont disponibles ; utilisez Rechercher les photos manquantes pour rétablir les liens.",
+1
View File
@@ -296,6 +296,7 @@
"“{to}” is already a person. Combining them makes one person; Undo separates them again.": "“{to}” sudah terdaftar sebagai orang. Jika digabungkan, keduanya menjadi satu orang; Urungkan akan memisahkannya lagi.",
"{what} merge added at {mp}: its full size, {full}, needs more memory than this computer has": "Hasil penggabungan {what} ditambahkan dengan resolusi {mp}: resolusi penuhnya ({full}) memerlukan memori melebihi kapasitas komputer ini",
"{what} merge added at {mp}, the largest a panorama can be (its full size is {full})": "Hasil penggabungan {what} ditambahkan dengan resolusi {mp}, resolusi maksimum untuk panorama (resolusi penuhnya {full})",
"{in_trash} catalog photos are in this library's Recently Deleted and stay there; restore them from Recently Deleted to see them.": "{in_trash} foto katalog ada di Baru dihapus pada pustaka ini dan tetap di sana; pulihkan dari Baru dihapus untuk melihatnya.",
"{skipped} catalog images were not imported because their records in the catalog are incomplete.": "{skipped} catalog images were not imported because their records in the catalog are incomplete.",
"Listing folders — folders: {folders}, files: {files}": "Listing folders — folders: {folders}, files: {files}",
"Added {n} photo{}": "{n} foto ditambahkan{:.0}",
+1
View File
@@ -250,6 +250,7 @@
"{s} s of AV1 video": "{s} s di video AV1",
"Delete {n} effect preset{}? This can't be undone.": "Eliminare i preset effetto ({n})?{:.0} L'operazione non si può annullare.",
"{count} more warnings.": "Altri avvisi: {count}.",
"{in_trash} catalog photos are in this library's Recently Deleted and stay there; restore them from Recently Deleted to see them.": "{in_trash} foto del catalogo sono in Eliminate di recente di questa libreria e vi restano; ripristinale da Eliminate di recente per vederle.",
"{skipped} catalog images were not imported because their records in the catalog are incomplete.": "{skipped} immagini del catalogo non sono state importate perché i loro record nel catalogo sono incompleti.",
"· {in_source} identical copies": "· copie identiche: {in_source}",
"{what} merge added at {mp}, the largest a panorama can be (its full size is {full})": "Unione {what} aggiunta a {mp}, la dimensione massima di un panorama (la dimensione piena è {full})",
+1
View File
@@ -273,6 +273,7 @@
"{n} originals opened in the other editor": "{n}個の元ファイルを別のエディタで開きました",
"· {in_source} identical copies": "・同一のコピー {in_source} 件",
"{count} more warnings.": "ほかに{count}件の警告があります。",
"{in_trash} catalog photos are in this library's Recently Deleted and stay there; restore them from Recently Deleted to see them.": "カタログの写真 {in_trash} 枚は、このライブラリの「最近削除した写真」にあり、そのまま残ります。表示するには「最近削除した写真」から復元してください。",
"{skipped} catalog images were not imported because their records in the catalog are incomplete.": "カタログ内のレコードが不完全なため、{skipped}件の画像を読み込めませんでした。",
"{photos} catalog records processed; {collections} collections.": "カタログの{photos}件のレコードを処理しました。コレクションは{collections}件です。",
"{missing} originals are missing. Their catalog records are available; use Find Missing Photos to relink them.": "{missing}件のオリジナルが見つかりません。カタログのレコードは利用できます。「見つからない写真を検索」で再リンクしてください。",
+1
View File
@@ -279,6 +279,7 @@
"Updated {name}": "Updated {name}",
"new, inside {parent}": "new, inside {parent}",
"{count} more warnings.": "{count} more warnings.",
"{in_trash} catalog photos are in this library's Recently Deleted and stay there; restore them from Recently Deleted to see them.": "კატალოგის ფოტოები ამ ბიბლიოთეკის ახლახან წაშლილებში: {in_trash}. ისინი იქ რჩება; სანახავად აღადგინეთ ისინი ახლახან წაშლილებიდან.",
"{skipped} catalog images were not imported because their records in the catalog are incomplete.": "კატალოგის {skipped} სურათი არ დაიმპორტდა, რადგან მათი ჩანაწერები კატალოგში არასრულია.",
"{missing} originals are missing. Their catalog records are available; use Find Missing Photos to relink them.": "{missing} originals are missing. Their catalog records are available; use Find Missing Photos to relink them.",
"{n} JPEG files": "{n} JPEG files",
+1
View File
@@ -261,6 +261,7 @@
"Update “{name}” with Current Settings": "Zaktualizuj „{name}” bieżącymi ustawieniami",
"{n} preset{}": "Ustawienia: {n}{:.0}",
"{count} more warnings.": "Pozostałe ostrzeżenia: {count}.",
"{in_trash} catalog photos are in this library's Recently Deleted and stay there; restore them from Recently Deleted to see them.": "Zdjęcia katalogu w Ostatnio usuniętych tej biblioteki: {in_trash}. Pozostają tam; przywróć je z Ostatnio usuniętych, aby je zobaczyć.",
"{skipped} catalog images were not imported because their records in the catalog are incomplete.": "Nie zaimportowano obrazów z katalogu: {skipped}. Ich rekordy w katalogu są niekompletne.",
"{photos} catalog records processed; {collections} collections.": "Przetworzone rekordy katalogu: {photos}; kolekcje: {collections}.",
"{missing} originals are missing. Their catalog records are available; use Find Missing Photos to relink them.": "Brakujące oryginały: {missing}. Ich rekordy w katalogu są dostępne; użyj polecenia Znajdź brakujące zdjęcia, aby je ponownie powiązać.",
@@ -273,6 +273,7 @@
"{n} originals opened in the other editor": "{n} originais abertos no outro editor",
"· {in_source} identical copies": "· {in_source} cópias idênticas",
"{count} more warnings.": "Mais {count} avisos.",
"{in_trash} catalog photos are in this library's Recently Deleted and stay there; restore them from Recently Deleted to see them.": "{in_trash} fotos do catálogo estão em Excluídos recentemente desta biblioteca e continuam lá; restaure-as de Excluídos recentemente para vê-las.",
"{skipped} catalog images were not imported because their records in the catalog are incomplete.": "{skipped} imagens do catálogo não foram importadas porque seus registros no catálogo estão incompletos.",
"{photos} catalog records processed; {collections} collections.": "{photos} registros do catálogo processados; {collections} coleções.",
"{missing} originals are missing. Their catalog records are available; use Find Missing Photos to relink them.": "{missing} originais estão ausentes. Seus registros no catálogo estão disponíveis; use Encontrar fotos ausentes para vinculá-los novamente.",
+1
View File
@@ -269,6 +269,7 @@
"{n} originals opened in the other editor": "Открыто оригиналов в другом редакторе: {n}",
"· {in_source} identical copies": "· идентичных копий: {in_source}",
"{count} more warnings.": "Других предупреждений: {count}.",
"{in_trash} catalog photos are in this library's Recently Deleted and stay there; restore them from Recently Deleted to see them.": "Фото каталога в «Недавно удалённых» этой библиотеки: {in_trash}. Они остаются там; чтобы увидеть их, восстановите их из «Недавно удалённых».",
"{skipped} catalog images were not imported because their records in the catalog are incomplete.": "Не импортированы изображения каталога: {skipped}. Их записи в каталоге неполные.",
"{photos} catalog records processed; {collections} collections.": "Обработано записей каталога: {photos}; коллекций: {collections}.",
"{missing} originals are missing. Their catalog records are available; use Find Missing Photos to relink them.": "Не найдены оригиналы: {missing}. Их записи каталога доступны; используйте поиск отсутствующих фотографий, чтобы восстановить ссылки.",
+1
View File
@@ -296,6 +296,7 @@
"Move {n} photo{} to “{}”?": "Move {n} photo{} to “{}”?",
"Moved {n} photo{} to “{}”. Edit → Undo moves them back.": "Moved {n} photo{} to “{}”. Edit → Undo moves them back.",
"{n} selected photos · {pages} pages": "{n} selected photos · {pages} pages",
"{in_trash} catalog photos are in this library's Recently Deleted and stay there; restore them from Recently Deleted to see them.": "{in_trash} katalogfoton finns i Nyligen raderade i det här biblioteket och ligger kvar där; återställ dem från Nyligen raderade för att se dem.",
"{skipped} catalog images were not imported because their records in the catalog are incomplete.": "{skipped} catalog images were not imported because their records in the catalog are incomplete.",
"Listing folders — folders: {folders}, files: {files}": "Listing folders — folders: {folders}, files: {files}",
"Added {n} photo{}": "{n} foto tillagda{:.0}",
+1
View File
@@ -269,6 +269,7 @@
"{n} originals opened in the other editor": "Відкрито оригіналів в іншому редакторі: {n}",
"· {in_source} identical copies": "· ідентичних копій: {in_source}",
"{count} more warnings.": "Інших попереджень: {count}.",
"{in_trash} catalog photos are in this library's Recently Deleted and stay there; restore them from Recently Deleted to see them.": "Фото каталогу в нещодавно видалених цієї бібліотеки: {in_trash}. Вони залишаються там; щоб побачити їх, відновіть їх із нещодавно видалених.",
"{skipped} catalog images were not imported because their records in the catalog are incomplete.": "Не імпортовано зображення каталогу: {skipped}. Їхні записи в каталозі неповні.",
"{photos} catalog records processed; {collections} collections.": "Оброблено записів каталогу: {photos}; колекцій: {collections}.",
"{missing} originals are missing. Their catalog records are available; use Find Missing Photos to relink them.": "Не знайдено оригінали: {missing}. Їхні записи каталогу доступні; скористайтеся пошуком відсутніх фотографій, щоб відновити посилання.",
+1
View File
@@ -257,6 +257,7 @@
"{pid} (missing)": "{pid} (thiếu)",
"{sel_n} selected · {counted}": "Đã chọn {sel_n} · {counted}",
"{size} GB": "{size} GB",
"{in_trash} catalog photos are in this library's Recently Deleted and stay there; restore them from Recently Deleted to see them.": "{in_trash} ảnh của danh mục nằm trong Đã xóa gần đây của thư viện này và vẫn ở đó; hãy khôi phục chúng từ Đã xóa gần đây để xem.",
"{skipped} catalog images were not imported because their records in the catalog are incomplete.": "{skipped} catalog images were not imported because their records in the catalog are incomplete.",
"{stage}… {:.0}%": "{stage}… {:.0}%",
"{s} s of AV1 video": "{s} s of AV1 video",
@@ -273,6 +273,7 @@
"{n} originals opened in the other editor": "已在其他编辑器中打开 {n} 个原始文件",
"· {in_source} identical copies": "· {in_source} 个相同副本",
"{count} more warnings.": "另有 {count} 条警告。",
"{in_trash} catalog photos are in this library's Recently Deleted and stay there; restore them from Recently Deleted to see them.": "目录中有 {in_trash} 张照片位于此图库的“最近删除”中,并将保留在那里;如需查看,请从“最近删除”中恢复。",
"{skipped} catalog images were not imported because their records in the catalog are incomplete.": "有 {skipped} 张图像未导入,因为它们在目录中的记录不完整。",
"{photos} catalog records processed; {collections} collections.": "已处理 {photos} 条目录记录;{collections} 个收藏集。",
"{missing} originals are missing. Their catalog records are available; use Find Missing Photos to relink them.": "有 {missing} 个原始文件缺失。其目录记录可用;请使用“查找缺失照片”重新链接文件。",
@@ -273,6 +273,7 @@
"{n} originals opened in the other editor": "已在其他編輯器中開啟 {n} 個原始檔案",
"· {in_source} identical copies": "· {in_source} 個相同副本",
"{count} more warnings.": "另有 {count} 則警告。",
"{in_trash} catalog photos are in this library's Recently Deleted and stay there; restore them from Recently Deleted to see them.": "目錄中有 {in_trash} 張照片位於此圖庫的「最近刪除」中,並會保留在那裡;如需檢視,請從「最近刪除」中復原。",
"{skipped} catalog images were not imported because their records in the catalog are incomplete.": "有 {skipped} 張影像未匯入,因為它們在目錄中的記錄不完整。",
"{photos} catalog records processed; {collections} collections.": "已處理 {photos} 筆目錄記錄;{collections} 個收藏集。",
"{missing} originals are missing. Their catalog records are available; use Find Missing Photos to relink them.": "有 {missing} 個原始檔案遺失。其目錄記錄可用;請使用「尋找遺失相片」重新連結檔案。",
+19
View File
@@ -340,6 +340,13 @@ fn result_dialog(value: &Value, summary: &str) -> crate::state::Dialog {
missing = missing
));
}
let in_trash = report["inRecentlyDeleted"].as_u64().unwrap_or(0);
if in_trash > 0 {
details.push(crate::i18n::tr_format!(
"{in_trash} catalog photos are in this library's Recently Deleted and stay there; restore them from Recently Deleted to see them.",
in_trash = in_trash
));
}
// images whose catalog records are incomplete come as data (`skipped`), said here in the UI
// language; their ids are in the report for the CLI and agents
let skipped = report["skipped"].as_array().map_or(0, Vec::len);
@@ -713,6 +720,18 @@ mod tests {
assert_eq!(&en[2..], ["w1", "w2"]);
}
#[test]
fn photos_left_in_recently_deleted_are_one_line() {
let value = json!({"report": {"photos": 3, "collections": 0, "inRecentlyDeleted": 2}});
crate::i18n::set_language(crate::i18n::Locale::En);
let crate::state::Dialog::LightroomResult { details, .. } = result_dialog(&value, "done") else { panic!("not a Lightroom result") };
assert_eq!(details.len(), 2, "{details:?}");
assert!(details[1].starts_with("2 catalog photos are in this library's Recently Deleted"), "{details:?}");
let value = json!({"report": {"photos": 3, "collections": 0, "inRecentlyDeleted": 0}});
let crate::state::Dialog::LightroomResult { details, .. } = result_dialog(&value, "done") else { panic!("not a Lightroom result") };
assert_eq!(details.len(), 1, "no line when none are: {details:?}");
}
#[test]
fn skipped_images_do_not_take_the_warning_slots() {
let warnings: Vec<_> = (0..10).map(|i| format!("w{i}")).collect();