mirror of
https://github.com/storytold/lightcraft.git
synced 2026-10-24 10:09:01 -06:00
LR-META-XMP: Retry automatic XMP sidecar writes that failed
A sidecar write that failed because the folder was offline or read-only was only logged. Keep those photos waiting, write them again once their files are reachable, and tell the user.
This commit is contained in:
@@ -108,6 +108,18 @@ pub fn specs() -> Vec<CommandSpec> {
|
||||
always,
|
||||
prefs
|
||||
),
|
||||
cmd!(
|
||||
"library.retrySidecars",
|
||||
"Write Waiting XMP Sidecars",
|
||||
[],
|
||||
None,
|
||||
"{} — writes again the automatic XMP sidecars that failed (folder offline, read-only or full) whose originals are reachable now → {written, waiting}",
|
||||
always,
|
||||
|s, _| {
|
||||
let written = s.retry_unsaved_sidecars();
|
||||
Ok(json!({"written": written, "waiting": s.unsaved_sidecars()}))
|
||||
}
|
||||
),
|
||||
cmd!("library.toggleAutoWriteXmp", "Automatically Write Changes into XMP", ["File"], None, "{} → {autoWrite}", always, |s, _| {
|
||||
let on = !s.xmp.auto_write;
|
||||
prefs(s, &json!({"autoWrite": on}))
|
||||
|
||||
@@ -206,6 +206,9 @@ pub struct Session {
|
||||
/// Set by a command whose change must not rewrite the photo's XMP sidecar even with auto-write on
|
||||
/// (a catalog-only edit of data the sidecar writer does not emit); consumed when the command ends.
|
||||
pub(crate) skip_auto_write: bool,
|
||||
/// Photos whose automatic XMP sidecar write failed (their folder offline, read-only or full), written again by
|
||||
/// [`Session::retry_unsaved_sidecars`] once their file is reachable.
|
||||
pub(crate) unsaved_sidecars: std::collections::BTreeSet<PhotoId>,
|
||||
/// AI denoise: the model in use, the photos that have their picture and the work in progress.
|
||||
pub(crate) denoise: denoise::State,
|
||||
/// Where the host keeps face models (one folder each); `None` where there is no file system (the web).
|
||||
@@ -353,6 +356,7 @@ impl Session {
|
||||
pending_sidecar_repairs: Vec::new(),
|
||||
interaction: None,
|
||||
skip_auto_write: false,
|
||||
unsaved_sidecars: Default::default(),
|
||||
denoise: Default::default(),
|
||||
face_models_dir: None,
|
||||
face_downloads: Default::default(),
|
||||
|
||||
@@ -713,13 +713,61 @@ impl Session {
|
||||
}
|
||||
let owners = StemOwners::among(&self.catalog, &ids);
|
||||
for id in ids {
|
||||
if self.catalog.photo(id).is_some_and(|p| file_path(p).is_some() && p.copy_of.is_none())
|
||||
&& let Err(e) = self.save_sidecar_with(id, &owners)
|
||||
{
|
||||
log::warn!("auto-write XMP: {e}");
|
||||
if self.catalog.photo(id).is_some_and(|p| file_path(p).is_some() && p.copy_of.is_none()) {
|
||||
match self.save_sidecar_with(id, &owners) {
|
||||
Ok(_) => {
|
||||
self.unsaved_sidecars.remove(&id);
|
||||
}
|
||||
Err(e) => {
|
||||
// kept for a retry once the file is reachable: a network share that dropped out mustn't leave
|
||||
// the sidecar silently behind the edit
|
||||
log::warn!("auto-write XMP: {e}");
|
||||
self.unsaved_sidecars.insert(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// How many photos have an automatic sidecar write waiting (it failed: the folder offline, read-only or full).
|
||||
pub fn unsaved_sidecars(&self) -> usize {
|
||||
self.unsaved_sidecars.len()
|
||||
}
|
||||
|
||||
/// Write the waiting sidecars whose originals are reachable again (the availability cache answers without
|
||||
/// touching the disk for the others); photos that are gone, or with auto-write off, are dropped from the wait.
|
||||
/// Returns how many were written.
|
||||
pub fn retry_unsaved_sidecars(&mut self) -> usize {
|
||||
if !self.xmp.auto_write {
|
||||
self.unsaved_sidecars.clear();
|
||||
return 0;
|
||||
}
|
||||
let mut ready = Vec::new();
|
||||
for id in self.unsaved_sidecars.clone() {
|
||||
match self.catalog.photo(id).filter(|p| p.copy_of.is_none()).and_then(|p| file_path(p).map(str::to_owned)) {
|
||||
None => {
|
||||
self.unsaved_sidecars.remove(&id);
|
||||
}
|
||||
Some(path) if self.media.availability.exists(&path) == Some(true) => ready.push(id),
|
||||
Some(_) => {}
|
||||
}
|
||||
}
|
||||
if ready.is_empty() {
|
||||
return 0;
|
||||
}
|
||||
let owners = StemOwners::among(&self.catalog, &ready);
|
||||
let mut written = 0;
|
||||
for id in ready {
|
||||
match self.save_sidecar_with(id, &owners) {
|
||||
Ok(_) => {
|
||||
self.unsaved_sidecars.remove(&id);
|
||||
written += 1;
|
||||
}
|
||||
Err(e) => log::debug!("retry XMP: {e}"),
|
||||
}
|
||||
}
|
||||
written
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -1209,3 +1209,42 @@ fn files_sharing_a_stem_get_their_own_sidecars() {
|
||||
let _ = std::fs::remove_dir_all(&src);
|
||||
let _ = std::fs::remove_dir_all(&lib);
|
||||
}
|
||||
|
||||
/// An automatic sidecar write that fails (here the folder is read-only, as a network share that dropped out or went
|
||||
/// read-only) waits instead of being forgotten, and `library.retrySidecars` writes it once the folder takes writes
|
||||
/// again.
|
||||
#[cfg(unix)]
|
||||
#[test]
|
||||
fn a_failed_auto_write_waits_and_is_written_on_retry() {
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
let src = temp_dir("auto-retry");
|
||||
write_png(&src.join("a.png"), 4);
|
||||
let mut s = Session::new().with_fs();
|
||||
s.execute("library.import", &json!({"paths": [src.to_string_lossy()]})).unwrap();
|
||||
s.execute("library.xmpPreferences", &json!({"autoWrite": true})).unwrap();
|
||||
std::fs::set_permissions(&src, std::fs::Permissions::from_mode(0o555)).unwrap();
|
||||
// (root writes anyway: nothing to test there)
|
||||
if std::fs::write(src.join(".probe"), b"x").is_ok() {
|
||||
std::fs::set_permissions(&src, std::fs::Permissions::from_mode(0o755)).unwrap();
|
||||
return;
|
||||
}
|
||||
s.execute("photo.rate", &json!({"rating": 4})).unwrap();
|
||||
let sidecar = src.join("a.xmp");
|
||||
assert!(!sidecar.exists());
|
||||
assert_eq!(s.unsaved_sidecars(), 1, "the failed write waits");
|
||||
let r = s.execute("library.retrySidecars", &json!({})).unwrap();
|
||||
assert_eq!(r, json!({"written": 0, "waiting": 1}), "still read-only");
|
||||
std::fs::set_permissions(&src, std::fs::Permissions::from_mode(0o755)).unwrap();
|
||||
let r = s.execute("library.retrySidecars", &json!({})).unwrap();
|
||||
assert_eq!(r, json!({"written": 1, "waiting": 0}));
|
||||
assert!(std::fs::read_to_string(&sidecar).unwrap().contains("<xmp:Rating>4</xmp:Rating>"));
|
||||
// a later edit that writes fine leaves nothing waiting; turning auto-write off drops what still waits
|
||||
std::fs::set_permissions(&src, std::fs::Permissions::from_mode(0o555)).unwrap();
|
||||
s.execute("photo.rate", &json!({"rating": 3})).unwrap();
|
||||
assert_eq!(s.unsaved_sidecars(), 1);
|
||||
s.execute("library.xmpPreferences", &json!({"autoWrite": false})).unwrap();
|
||||
s.execute("library.retrySidecars", &json!({})).unwrap();
|
||||
assert_eq!(s.unsaved_sidecars(), 0);
|
||||
std::fs::set_permissions(&src, std::fs::Permissions::from_mode(0o755)).unwrap();
|
||||
let _ = std::fs::remove_dir_all(&src);
|
||||
}
|
||||
|
||||
@@ -61,6 +61,7 @@
|
||||
"Import cancelled · {} photo{} added": "Import abgebrochen · {} Foto{} hinzugefügt",
|
||||
"Import failed: {e}": "Import fehlgeschlagen: {e}",
|
||||
"Import warnings ({n}):\n{warnings}": "Importwarnungen ({n}):\n{warnings}",
|
||||
"The XMP sidecars of {n} photos couldn't be written (is their folder offline?). LightCraft keeps trying.": "Die XMP-Begleitdateien von {n} Fotos konnten nicht geschrieben werden (ist ihr Ordner offline?). LightCraft versucht es weiter.",
|
||||
"XMP profile \"{profile}\" was not applied for {path}; colors may differ from Lightroom.": "XMP-Profil \"{profile}\" wurde für {path} nicht angewendet; Farben können von Lightroom abweichen.",
|
||||
"Lightroom geometry was not preserved for {path}; {mode} will be recalculated.": "Lightroom-Geometrie wurde für {path} nicht beibehalten; {mode} wird neu berechnet.",
|
||||
"Lightroom geometry for {path} was not preserved; saved transform is ignored because Upright is Off.": "Lightroom-Geometrie für {path} wurde nicht beibehalten; gespeicherte Transformation wird ignoriert, weil Upright deaktiviert ist.",
|
||||
|
||||
@@ -1617,6 +1617,7 @@
|
||||
"Window": "Fenster",
|
||||
"Winter Blue": "Winterblau",
|
||||
"Without Saving and Quit (issue #100). It no longer quietly starts a demo session that looked like a reset library and": "Ohne Speichern fortfahren und Beenden (Issue #100). Sie startet keine unbemerkte Demo-Sitzung mehr, die wie eine zurückgesetzte Bibliothek wirkte und",
|
||||
"Wrote the XMP sidecars that were waiting": "Ausstehende XMP-Begleitdateien geschrieben",
|
||||
"XMP Sidecar Preferences": "Einstellungen für XMP-Begleitdateien",
|
||||
"XMP sidecars": "XMP-Begleitdateien",
|
||||
"Year": "Jahr",
|
||||
|
||||
@@ -61,6 +61,7 @@
|
||||
"Import cancelled · {} photo{} added": "Importación cancelada · {0} foto{1} añadida{1}",
|
||||
"Import failed: {e}": "Error de importación: {e}",
|
||||
"Import warnings ({n}):\n{warnings}": "Avisos de importación ({n}):\n{warnings}",
|
||||
"The XMP sidecars of {n} photos couldn't be written (is their folder offline?). LightCraft keeps trying.": "No se pudieron escribir los archivos XMP de {n} fotos (¿su carpeta está desconectada?). LightCraft lo sigue intentando.",
|
||||
"XMP profile \"{profile}\" was not applied for {path}; colors may differ from Lightroom.": "El perfil XMP \"{profile}\" no se aplicó a {path}; los colores pueden diferir de Lightroom.",
|
||||
"Lightroom geometry was not preserved for {path}; {mode} will be recalculated.": "La geometría de Lightroom no se conservó para {path}; {mode} se recalculará.",
|
||||
"Lightroom geometry for {path} was not preserved; saved transform is ignored because Upright is Off.": "La geometría de Lightroom no se conservó para {path}; la transformación guardada se ignora porque Vertical está desactivado.",
|
||||
|
||||
@@ -1581,6 +1581,7 @@
|
||||
"Will be restored": "Se restaurará",
|
||||
"Window": "Ventana",
|
||||
"Winter Blue": "Azul invernal",
|
||||
"Wrote the XMP sidecars that were waiting": "Se escribieron los archivos XMP pendientes",
|
||||
"XMP Sidecar Preferences": "Preferencias de archivos XMP complementarios",
|
||||
"XMP sidecars": "Archivos XMP complementarios",
|
||||
"Year": "Año",
|
||||
|
||||
@@ -65,6 +65,7 @@
|
||||
"Imported {n} preset{}": "{n} préréglage{} importé(s)",
|
||||
"Imported {n} preset{}, {f} file{} not readable": "{n} préréglage{} importé(s), {f} fichier{} illisible(s)",
|
||||
"Imported {profiles} profile{} (Profile browser ▸ their groups)": "{profiles} profil{} importé(s) (Navigateur de profils → leurs groupes)",
|
||||
"The XMP sidecars of {n} photos couldn't be written (is their folder offline?). LightCraft keeps trying.": "Les fichiers annexes XMP de {n} photos n'ont pas pu être écrits (leur dossier est-il hors ligne ?). LightCraft continue d'essayer.",
|
||||
"XMP profile \"{profile}\" was not applied for {path}; colors may differ from Lightroom.": "Le profil XMP « {profile} » n’a pas été appliqué à {path} ; les couleurs peuvent différer de Lightroom.",
|
||||
"Lightroom geometry was not preserved for {path}; {mode} will be recalculated.": "La géométrie Lightroom de {path} n’a pas été conservée ; {mode} sera recalculé.",
|
||||
"Lightroom geometry for {path} was not preserved; saved transform is ignored because Upright is Off.": "La géométrie Lightroom de {path} n’a pas été conservée ; la transformation enregistrée est ignorée, car Upright est désactivé.",
|
||||
|
||||
@@ -1543,6 +1543,7 @@
|
||||
"Will be restored": "Sera restaurée",
|
||||
"Window": "Fenêtre",
|
||||
"Winter Blue": "Bleu hivernal",
|
||||
"Wrote the XMP sidecars that were waiting": "Fichiers annexes XMP en attente écrits",
|
||||
"XMP Sidecar Preferences": "Préférences des fichiers annexes XMP",
|
||||
"XMP sidecars": "Fichiers annexes XMP",
|
||||
"Year": "Année",
|
||||
|
||||
@@ -66,6 +66,7 @@
|
||||
"Show {hidden} hidden location{}": "非表示の{hidden}か所を表示{:.0}",
|
||||
"Showing {n} of {}": "{}枚中{n}枚を表示",
|
||||
"Smart album: {}": "スマートアルバム:{}",
|
||||
"The XMP sidecars of {n} photos couldn't be written (is their folder offline?). LightCraft keeps trying.": "{n} 枚の写真の XMP サイドカーを書き込めませんでした(フォルダーがオフラインではありませんか?)。LightCraft は再試行を続けます。",
|
||||
"Undo {}": "取り消す:{}",
|
||||
"Version {} — a clean-room, pure-Rust photo library and raw developer.": "バージョン{} — Rust製の写真管理・RAW現像アプリ",
|
||||
"Version: {}": "バージョン:{}",
|
||||
|
||||
@@ -1471,6 +1471,7 @@
|
||||
"Will be restored": "復元されます",
|
||||
"Window": "ウィンドウ",
|
||||
"Winter Blue": "ウィンターブルー",
|
||||
"Wrote the XMP sidecars that were waiting": "保留中の XMP サイドカーを書き込みました",
|
||||
"XMP Sidecar Preferences": "XMPサイドカーファイルの環境設定",
|
||||
"XMP sidecars": "XMPサイドカーファイル",
|
||||
"Year": "年",
|
||||
|
||||
@@ -87,6 +87,7 @@
|
||||
"Show {hidden} hidden location{}": "Mostrar {hidden} local(is) oculto(s){:.0}",
|
||||
"Showing {n} of {}": "Mostrando {n} de {}",
|
||||
"Smart album: {}": "Álbum inteligente: {}",
|
||||
"The XMP sidecars of {n} photos couldn't be written (is their folder offline?). LightCraft keeps trying.": "Não foi possível gravar os arquivos XMP de {n} fotos (a pasta está offline?). O LightCraft continua tentando.",
|
||||
"Undo {}": "Desfazer {}",
|
||||
"Updated “{name}”": "“{name}” atualizado",
|
||||
"Version {} — a clean-room, pure-Rust photo library and raw developer.": "Versão {} — uma biblioteca de fotos e revelador RAW em Rust puro, desenvolvida de forma independente.",
|
||||
|
||||
@@ -1566,6 +1566,7 @@
|
||||
"Will be restored": "Será restaurada",
|
||||
"Window": "Janela",
|
||||
"Winter Blue": "Azul invernal",
|
||||
"Wrote the XMP sidecars that were waiting": "Os arquivos XMP pendentes foram gravados",
|
||||
"XMP Sidecar Preferences": "Preferências de sidecar XMP",
|
||||
"XMP sidecars": "Sidecars XMP",
|
||||
"Year": "Ano",
|
||||
|
||||
@@ -64,6 +64,7 @@
|
||||
"Import cancelled · {} photo{} added": "Импорт отменён · добавлено фото: {}{:.0}",
|
||||
"Import failed: {e}": "Ошибка импорта: {e}",
|
||||
"Import warnings ({n}):\n{warnings}": "Предупреждения импорта ({n}):\n{warnings}",
|
||||
"The XMP sidecars of {n} photos couldn't be written (is their folder offline?). LightCraft keeps trying.": "Не удалось записать XMP-файлы {n} фото (папка недоступна?). LightCraft продолжает попытки.",
|
||||
"XMP profile \"{profile}\" was not applied for {path}; colors may differ from Lightroom.": "Профиль XMP \"{profile}\" не применён к {path}; цвета могут отличаться от Lightroom.",
|
||||
"Lightroom geometry was not preserved for {path}; {mode} will be recalculated.": "Геометрия Lightroom для {path} не сохранена; режим {mode} будет пересчитан.",
|
||||
"Lightroom geometry for {path} was not preserved; saved transform is ignored because Upright is Off.": "Геометрия Lightroom для {path} не сохранена; сохранённое преобразование игнорируется, поскольку Upright выключен.",
|
||||
|
||||
@@ -1309,6 +1309,7 @@
|
||||
"Width × Height": "Ширина × высота",
|
||||
"Window": "Окно",
|
||||
"Winter Blue": "Зимняя синева",
|
||||
"Wrote the XMP sidecars that were waiting": "Ожидавшие XMP-файлы записаны",
|
||||
"XMP Sidecar Preferences": "Настройки XMP-файлов",
|
||||
"XMP sidecars": "XMP-файлы",
|
||||
"Year": "Год",
|
||||
|
||||
@@ -65,6 +65,7 @@
|
||||
"Imported {n} preset{}": "Імпортовано наборів налаштувань: {n}{:.0}",
|
||||
"Imported {n} preset{}, {f} file{} not readable": "Імпортовано наборів налаштувань: {n}{:.0}, файлів не читається: {f}{:.0}",
|
||||
"Imported {profiles} profile{} (Profile browser ▸ their groups)": "Імпортовано профілів: {profiles}{:.0} (Оглядач профілів → їхні групи)",
|
||||
"The XMP sidecars of {n} photos couldn't be written (is their folder offline?). LightCraft keeps trying.": "Не вдалося записати XMP-файли {n} фото (папка недоступна?). LightCraft продовжує спроби.",
|
||||
"XMP profile \"{profile}\" was not applied for {path}; colors may differ from Lightroom.": "Профіль XMP «{profile}» не застосовано до {path}; кольори можуть відрізнятися від Lightroom.",
|
||||
"Lightroom geometry was not preserved for {path}; {mode} will be recalculated.": "Геометрію Lightroom для {path} не збережено; режим {mode} буде перераховано.",
|
||||
"Lightroom geometry for {path} was not preserved; saved transform is ignored because Upright is Off.": "Геометрію Lightroom для {path} не збережено; збережене перетворення ігнорується, оскільки Upright вимкнено.",
|
||||
|
||||
@@ -305,6 +305,7 @@
|
||||
"Whites": "Білі тони",
|
||||
"Width": "Ширина",
|
||||
"Window": "Вікно",
|
||||
"Wrote the XMP sidecars that were waiting": "XMP-файли, що очікували, записано",
|
||||
"Yellow": "Жовтий",
|
||||
"Yes": "Так",
|
||||
"Zoom": "Масштаб",
|
||||
|
||||
@@ -66,6 +66,7 @@
|
||||
"Show {hidden} hidden location{}": "显示 {hidden} 个隐藏位置{0:.0}",
|
||||
"Showing {n} of {}": "显示 {0} 张中的 {n} 张",
|
||||
"Smart album: {}": "智能相册:{0}",
|
||||
"The XMP sidecars of {n} photos couldn't be written (is their folder offline?). LightCraft keeps trying.": "无法写入 {n} 张照片的 XMP 附属文件(其文件夹是否离线?)。LightCraft 会继续重试。",
|
||||
"Undo {}": "撤销 {0}",
|
||||
"Version {} — a clean-room, pure-Rust photo library and raw developer.": "版本 {0} — 净室实现的纯 Rust 照片图库与 RAW 编辑工具。",
|
||||
"Version: {}": "版本:{0}",
|
||||
|
||||
@@ -1547,6 +1547,7 @@
|
||||
"Will be restored": "将恢复",
|
||||
"Window": "窗口",
|
||||
"Winter Blue": "冬日蓝调",
|
||||
"Wrote the XMP sidecars that were waiting": "已写入等待中的 XMP 附属文件",
|
||||
"XMP Sidecar Preferences": "XMP 附属文件偏好设置",
|
||||
"XMP sidecars": "XMP 附属文件",
|
||||
"Year": "年",
|
||||
|
||||
@@ -83,6 +83,7 @@
|
||||
"Show {hidden} hidden location{}": "顯示 {hidden} 個隱藏的位置{:.0}",
|
||||
"Showing {n} of {}": "顯示 {n}/{} 張",
|
||||
"Smart album: {}": "智慧型相簿:{}",
|
||||
"The XMP sidecars of {n} photos couldn't be written (is their folder offline?). LightCraft keeps trying.": "無法寫入 {n} 張照片的 XMP 附屬檔案(其資料夾是否離線?)。LightCraft 會持續重試。",
|
||||
"Undo {}": "復原 {}",
|
||||
"Updated “{name}”": "已更新「{name}」",
|
||||
"Version {} — a clean-room, pure-Rust photo library and raw developer.": "版本 {} — 以純 Rust 獨立實作的照片管理與 RAW 顯像程式。",
|
||||
|
||||
@@ -1547,6 +1547,7 @@
|
||||
"Will be restored": "將還原",
|
||||
"Window": "視窗",
|
||||
"Winter Blue": "冬日藍調",
|
||||
"Wrote the XMP sidecars that were waiting": "已寫入等待中的 XMP 附屬檔案",
|
||||
"XMP Sidecar Preferences": "XMP 附屬檔案偏好設定",
|
||||
"XMP sidecars": "XMP 附屬檔案",
|
||||
"Year": "年",
|
||||
|
||||
@@ -1092,6 +1092,33 @@ impl LightcraftApp {
|
||||
}
|
||||
ctx.request_repaint_after(std::time::Duration::from_secs(3));
|
||||
}
|
||||
// automatic XMP writes that failed (a network share offline): tried again every few seconds once their files
|
||||
// are reachable, and said once when they start waiting and once when they're written
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
{
|
||||
if self.session.unsaved_sidecars() > 0 {
|
||||
let now = ctx.input(|i| i.time);
|
||||
if now - self.ui.sidecar_retry_at >= 10.0 {
|
||||
self.ui.sidecar_retry_at = now;
|
||||
self.session.retry_unsaved_sidecars();
|
||||
}
|
||||
ctx.request_repaint_after(std::time::Duration::from_secs(10));
|
||||
}
|
||||
let waiting = self.session.unsaved_sidecars();
|
||||
if waiting > 0 && self.ui.sidecars_waiting == 0 {
|
||||
self.toast_for(
|
||||
ctx,
|
||||
crate::i18n::tr_format!(
|
||||
"The XMP sidecars of {n} photos couldn't be written (is their folder offline?). LightCraft keeps trying.",
|
||||
n = waiting
|
||||
),
|
||||
8.0,
|
||||
);
|
||||
} else if waiting == 0 && self.ui.sidecars_waiting > 0 {
|
||||
self.toast(ctx, crate::i18n::tr("Wrote the XMP sidecars that were waiting"));
|
||||
}
|
||||
self.ui.sidecars_waiting = waiting;
|
||||
}
|
||||
self.session.persist_if_dirty();
|
||||
panels::denoise::pump(self, ctx);
|
||||
model_setup::pump(self, ctx);
|
||||
|
||||
@@ -613,6 +613,11 @@ pub struct UiState {
|
||||
/// When the watched folder was last scanned (egui time).
|
||||
#[serde(skip)]
|
||||
pub auto_import_at: f64,
|
||||
/// When the automatic XMP writes that failed were last tried again (egui time), and how many were waiting then.
|
||||
#[serde(skip)]
|
||||
pub sidecar_retry_at: f64,
|
||||
#[serde(skip)]
|
||||
pub sidecars_waiting: usize,
|
||||
/// The develop control whose slider is being dragged (geometry sliders show a grid).
|
||||
#[serde(skip)]
|
||||
pub dragging_control: Option<String>,
|
||||
@@ -1101,6 +1106,8 @@ impl Default for UiState {
|
||||
external_edits: Vec::new(),
|
||||
was_focused: true,
|
||||
auto_import_at: 0.0,
|
||||
sidecar_retry_at: 0.0,
|
||||
sidecars_waiting: 0,
|
||||
search: String::new(),
|
||||
focus_search: false,
|
||||
renaming_mask: None,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Lightroom parity checklist
|
||||
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-14 · **Change:** minor (integration batch 35: KEYC-SELECTONLYACTIVE, Select Only Active Photo ⌘⇧D; LR-BEHAV-AUTOSAVE, damaged snapshots repaired on open; LR-EDIT-OPTICS-PROFILE, Nikon Z 6II / Z 8 / Z f in-file distortion; LRC-LIB-CATALOG-IMPORT, WAL-only catalogs open; LR-EXP-DIM, enlargements stop at 512 MP; LR-IMP-CAMERA-COVERAGE, Canon EOS RP CR3 verified; model folder retry) · **Target:** Adobe Lightroom Classic 15.5 / Lightroom 9.6
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-14 · **Change:** minor (LR-META-XMP: failed automatic sidecar writes are retried; integration batch 35: KEYC-SELECTONLYACTIVE, Select Only Active Photo ⌘⇧D; LR-BEHAV-AUTOSAVE, damaged snapshots repaired on open; LR-EDIT-OPTICS-PROFILE, Nikon Z 6II / Z 8 / Z f in-file distortion; LRC-LIB-CATALOG-IMPORT, WAL-only catalogs open; LR-EXP-DIM, enlargements stop at 512 MP; LR-IMP-CAMERA-COVERAGE, Canon EOS RP CR3 verified; model folder retry) · **Target:** Adobe Lightroom Classic 15.5 / Lightroom 9.6
|
||||
|
||||
The measured, row-by-row checklist: one row per Lightroom feature, menu item and shortcut, what we
|
||||
have and what is missing. It answers *does the feature exist?* (feature breadth). Whether it is good
|
||||
@@ -790,6 +790,7 @@ Latin stays upright. Advanced Japanese composition remains open. Regression cove
|
||||
|
||||
| Date | Change | Summary |
|
||||
|---|---|---|
|
||||
| 2026-10-14 | patch | LR-META-XMP: automatic sidecar writes that fail (folder offline or read-only) wait and are written again once the files are reachable, with a notice (`cmd:library.retrySidecars`); status unchanged |
|
||||
| 2026-10-13 | patch | LR-VIEW-COMPARE, LRC-LIB-COMPARE: ⌘/Ctrl- and Shift-clicks on the filmstrip in Compare change the selection as in the grid; the two photos shown stay selected and the active photo is unchanged (#187); statuses unchanged |
|
||||
| 2026-10-13 | minor | KEYC-SELECTONLYACTIVE: Edit → Select Only Active Photo (⌘⇧D) lands as a new row; status 🟡 (headless-tested, native macOS menu path unverified) |
|
||||
| 2026-10-13 | patch | LRC-LIB-CATALOG-IMPORT: a catalog whose tables are all still in its `-wal` file (main file schema format 0) opens instead of failing as an unsupported schema format; status unchanged |
|
||||
|
||||
@@ -2,6 +2,12 @@
|
||||
|
||||
## 2026-10-14
|
||||
|
||||
### XMP sidecars catch up after a network share comes back
|
||||
- With automatic XMP writing on, an edit to a photo whose folder can't be written (a network share that dropped
|
||||
out, a folder that went read-only) used to be written only to the library, with a line in the log, so the sidecar
|
||||
stayed behind the edit. LightCraft now says once that the sidecars are waiting, tries again every few seconds once
|
||||
the files are reachable, and says when they're written. `library.retrySidecars` does it on request.
|
||||
|
||||
### Hard-edged Heal and Clone spots
|
||||
- A spot with Feather at 0 no longer draws black pixels exactly on its radius. This also fixes spots at 0% Opacity
|
||||
changing pixels along their edge. The hard edge now leaves pixels on the radius unchanged.
|
||||
|
||||
Reference in New Issue
Block a user