mirror of
https://github.com/storytold/lightcraft.git
synced 2026-10-27 11:37:13 -06:00
Synchronize Folder: an absent folder is offline, disks only on request (review fix)
A folder that isn't there (moved whole, or on a disk that isn't connected) used to scan as "every photo missing", one tick away from moving a whole disk to Recently Deleted. The scan (on its worker, so an asleep share never stalls the UI) now reports it as offline and both commands refuse it; the dialog says so. A whole disk or a folder holding disks needs `disk: true`, as Remove Disk from Library does; the disk rows pass it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
832d3c8d3d
commit
1259506edd
@@ -1323,7 +1323,7 @@ fn merge_rules(base: &lightcraft_catalog::Filter, patch: &Value, c: &str) -> Res
|
||||
/// Whether `path` lies above a disk: some of its `ids` photos are on a disk (not the startup
|
||||
/// disk) that the folder does not lie in. Such a folder (`/Volumes`, `/mnt`…) is not a folder of
|
||||
/// one disk but a way to reach several.
|
||||
fn covers_other_disks(s: &Session, path: &str, ids: &[PhotoId]) -> bool {
|
||||
pub(crate) fn covers_other_disks(s: &Session, path: &str, ids: &[PhotoId]) -> bool {
|
||||
ids.iter()
|
||||
.filter_map(|id| s.catalog.photo(*id))
|
||||
.filter_map(|p| lightcraft_catalog::folders::volume_of(p))
|
||||
|
||||
@@ -17,12 +17,12 @@ pub fn specs() -> Vec<CommandSpec> {
|
||||
"Scan Folder for Changes",
|
||||
[],
|
||||
None,
|
||||
"{path} — compare a folder of the library (and the folders inside it) with the disk; changes nothing → {path, new: [{path, name, format, …}] files the library doesn't have, duplicates: files whose content it already has, unreadable: [{path, error}], missing: [{id, path}] photos whose file is gone, metadata: [{id, path}] photos whose XMP sidecar was saved after they came in or were last edited here and says something the library doesn't}",
|
||||
"{path, disk?: bool (a whole disk or share, or a folder holding disks)} — compare a folder of the library (and the folders inside it) with the disk; changes nothing → {path, new: [{path, name, format, …}] files the library doesn't have, duplicates: files whose content it already has, unreadable: [{path, error}], missing: [{id, path}] photos whose file is gone, metadata: [{id, path}] photos whose XMP sidecar was saved after they came in or were last edited here and says something the library doesn't}",
|
||||
always,
|
||||
|s, p| {
|
||||
const C: &str = "folder.scanChanges";
|
||||
let path = path(p, C)?;
|
||||
let changes = scan(s, &path).map_err(|e| bad(C, e.to_string()))?;
|
||||
let changes = scan(s, &path, bool_or(p, "disk", false)).map_err(|e| bad(C, e.to_string()))?;
|
||||
let v = serde_json::to_value(&changes).unwrap_or_default();
|
||||
s.folder_changes = Some(changes);
|
||||
Ok(v)
|
||||
@@ -33,7 +33,7 @@ pub fn specs() -> Vec<CommandSpec> {
|
||||
"Synchronize Folder",
|
||||
[],
|
||||
None,
|
||||
"{path, importNew?: true, removeMissing?: false, readMetadata?: false} — bring a folder of the library up to date with the disk (see folder.scanChanges): import its new files in place, move photos whose file is gone to Recently Deleted, read XMP sidecars saved by other apps (the sidecar wins); acts on the last folder.scanChanges of the folder while nothing changed in the library since (else scans again); one undo step, no file is touched → {imported, removed, read, failed: [[path, error]]}",
|
||||
"{path, disk?: bool, importNew?: true, removeMissing?: false, readMetadata?: false} — bring a folder of the library up to date with the disk (see folder.scanChanges): import its new files in place, move photos whose file is gone to Recently Deleted, read XMP sidecars saved by other apps (the sidecar wins); acts on the last folder.scanChanges of the folder while nothing changed in the library since (else scans again); one undo step, no file is touched → {imported, removed, read, failed: [[path, error]]}",
|
||||
always,
|
||||
sync
|
||||
),
|
||||
@@ -51,7 +51,7 @@ fn sync(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
};
|
||||
let changes = match s.take_folder_changes(&path) {
|
||||
Some(c) => c,
|
||||
None => scan(s, &path).map_err(|e| bad(C, e.to_string()))?,
|
||||
None => scan(s, &path, bool_or(p, "disk", false)).map_err(|e| bad(C, e.to_string()))?,
|
||||
};
|
||||
let r = synchronize(s, changes, choice)?;
|
||||
Ok(json!({"imported": r.imported, "removed": r.removed, "read": r.read, "failed": r.failed}))
|
||||
|
||||
@@ -65,6 +65,9 @@ pub struct FolderChanges {
|
||||
pub missing: Vec<FolderPhoto>,
|
||||
/// Photos whose sidecar has news.
|
||||
pub metadata: Vec<FolderPhoto>,
|
||||
/// The folder itself is not there (moved or renamed whole, or on a disk that isn't
|
||||
/// connected): nothing else is reported, rather than every photo as missing.
|
||||
pub offline: bool,
|
||||
/// The probes of the new files, kept for the import that follows.
|
||||
#[serde(skip)]
|
||||
probes: HashMap<String, ProbeInfo>,
|
||||
@@ -110,8 +113,10 @@ fn invalid(msg: impl Into<String>) -> EngineError {
|
||||
}
|
||||
|
||||
impl SyncInput {
|
||||
/// The scan of folder `path`: a folder the library holds photos in, not the startup disk.
|
||||
pub fn new(s: &mut Session, path: &str) -> Result<SyncInput> {
|
||||
/// The scan of folder `path`: a folder the library holds photos in, not the startup disk. A
|
||||
/// whole disk or share, or a folder that holds disks (`/Volumes`, `/mnt`…), only with `disk`
|
||||
/// (as for Remove Disk from Library: its missing photos can be a whole disk's).
|
||||
pub fn new(s: &mut Session, path: &str, disk: bool) -> Result<SyncInput> {
|
||||
let path = path.trim();
|
||||
if path.is_empty() {
|
||||
return Err(invalid("missing `path`"));
|
||||
@@ -124,6 +129,9 @@ impl SyncInput {
|
||||
if ids.is_empty() {
|
||||
return Err(invalid(format!("{path}: no photo in the library was imported from it")));
|
||||
}
|
||||
if !disk && (lightcraft_catalog::folders::is_disk_root(path) || crate::cmd::library::covers_other_disks(s, path, &ids)) {
|
||||
return Err(invalid(format!("{path} is a whole disk or holds disks: pass `disk: true` to synchronize it")));
|
||||
}
|
||||
let photos = ids.iter().filter_map(|id| s.catalog.photo(*id).map(|p| (Arc::clone(p), s.sidecar_naming(*id)))).collect();
|
||||
let (scan, _) = ScanInput::new(s, &[path.to_string()]);
|
||||
Ok(SyncInput { folder: path.to_string(), revision: s.catalog.revision, scan, photos, labels: s.catalog.clone() })
|
||||
@@ -137,6 +145,9 @@ impl SyncInput {
|
||||
/// library's photos, which run on several threads too. Stops early when `progress.cancel` is set.
|
||||
pub fn scan_with(input: SyncInput, progress: &ScanProgress) -> FolderChanges {
|
||||
let SyncInput { folder, revision, scan, photos, labels } = input;
|
||||
if !cfg!(target_arch = "wasm32") && !Path::new(&folder).is_dir() {
|
||||
return FolderChanges { path: folder, revision, offline: true, ..Default::default() };
|
||||
}
|
||||
// (no threads on the web: one after the other there)
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
let (out, checks) = (crate::import::scan_with(scan, std::slice::from_ref(&folder), progress), check_photos(&photos, &labels, progress));
|
||||
@@ -224,9 +235,17 @@ fn check_photos(photos: &[(Arc<Photo>, SidecarNaming)], labels: &Catalog, progre
|
||||
}
|
||||
|
||||
/// [`scan_with`] on the calling thread.
|
||||
pub fn scan(s: &mut Session, path: &str) -> Result<FolderChanges> {
|
||||
let input = SyncInput::new(s, path)?;
|
||||
Ok(scan_with(input, &ScanProgress::default()))
|
||||
pub fn scan(s: &mut Session, path: &str, disk: bool) -> Result<FolderChanges> {
|
||||
let input = SyncInput::new(s, path, disk)?;
|
||||
let changes = scan_with(input, &ScanProgress::default());
|
||||
if changes.offline {
|
||||
return Err(not_there(&changes.path));
|
||||
}
|
||||
Ok(changes)
|
||||
}
|
||||
|
||||
fn not_there(path: &str) -> EngineError {
|
||||
invalid(format!("{path} is not there (moved, renamed, or on a disk that isn't connected)"))
|
||||
}
|
||||
|
||||
impl Session {
|
||||
@@ -265,6 +284,9 @@ fn sidecar_has_news(labels: &Catalog, p: &Photo, naming: SidecarNaming) -> bool
|
||||
/// Act on `changes` as `choice` says, as one undo step ("Synchronize Folder"). A missing photo
|
||||
/// whose file is back by now is left alone.
|
||||
pub fn synchronize(s: &mut Session, changes: FolderChanges, choice: SyncChoice) -> Result<SyncReport> {
|
||||
if changes.offline {
|
||||
return Err(not_there(&changes.path));
|
||||
}
|
||||
let mark = s.undo.len();
|
||||
let mut report = SyncReport::default();
|
||||
let FolderChanges { new, missing, metadata, probes, .. } = changes;
|
||||
|
||||
@@ -243,7 +243,7 @@ fn a_cancelled_scan_stops_checking_files() {
|
||||
let dir = Scratch::new("cancel");
|
||||
let mut s = library(&dir);
|
||||
std::fs::remove_file(dir.path("trip/day1/b.png")).unwrap();
|
||||
let input = crate::sync::SyncInput::new(&mut s, &dir.path("trip")).unwrap();
|
||||
let input = crate::sync::SyncInput::new(&mut s, &dir.path("trip"), false).unwrap();
|
||||
let progress = crate::import::ScanProgress::default();
|
||||
progress.cancel.store(true, std::sync::atomic::Ordering::Relaxed);
|
||||
let c = crate::sync::scan_with(input, &progress);
|
||||
@@ -270,3 +270,40 @@ fn many_photos_are_checked_in_parallel_with_the_same_answer() {
|
||||
assert_eq!(paths(&r, "missing"), expect_missing);
|
||||
assert_eq!(paths(&r, "metadata"), expect_meta);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_folder_that_is_not_there_is_not_scanned_as_if_everything_went_missing() {
|
||||
let dir = Scratch::new("offline");
|
||||
let mut s = library(&dir);
|
||||
// an unplugged disk, or a folder moved away whole
|
||||
std::fs::rename(dir.0.join("trip"), dir.0.join("trip-elsewhere")).unwrap();
|
||||
for cmd in ["folder.scanChanges", "folder.synchronize"] {
|
||||
let e = s.execute(cmd, &json!({"path": dir.path("trip"), "removeMissing": true})).unwrap_err().to_string();
|
||||
assert!(e.contains("not there"), "{cmd}: {e}");
|
||||
}
|
||||
assert_eq!(s.catalog.photos().filter(|p| p.in_library()).count(), 2, "nothing was removed");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_whole_disk_or_a_folder_holding_disks_is_synchronized_only_on_request() {
|
||||
let dir = Scratch::new("disks");
|
||||
let mut s = library(&dir);
|
||||
// a library photo on another disk makes the scratch folder's root look like it holds disks
|
||||
let id = s.catalog.alloc_photo_id();
|
||||
let p = lightcraft_catalog::Photo::new(
|
||||
id,
|
||||
lightcraft_catalog::Source::File { path: "/Volumes/nas/x.jpg".into() },
|
||||
"x.jpg",
|
||||
"JPEG",
|
||||
6,
|
||||
4,
|
||||
"2026-01-01",
|
||||
);
|
||||
s.catalog.apply(lightcraft_catalog::Op::AddPhoto { photo: Box::new(p) }).unwrap();
|
||||
if std::path::Path::new("/Volumes").is_dir() {
|
||||
let e = s.execute("folder.scanChanges", &json!({"path": "/Volumes"})).unwrap_err().to_string();
|
||||
assert!(e.contains("disk: true"), "{e}");
|
||||
}
|
||||
// a folder inside one disk needs nothing more
|
||||
assert!(s.execute("folder.scanChanges", &json!({"path": dir.path("trip")})).is_ok());
|
||||
}
|
||||
|
||||
@@ -1903,5 +1903,6 @@
|
||||
"Missing photos move to Recently Deleted and can be restored; no file on disk is touched.": "Fehlende Fotos werden nach „Zuletzt gelöscht“ verschoben und können wiederhergestellt werden; keine Datei auf dem Datenträger wird verändert.",
|
||||
"Reading a sidecar replaces the photo's rating, labels, metadata and edits with what it says.": "Beim Lesen einer Filialdatei werden Bewertung, Markierungen, Metadaten und Bearbeitungen des Fotos durch ihren Inhalt ersetzt.",
|
||||
"Skipped: files already in the library elsewhere": "Übersprungen: Dateien, die bereits an anderer Stelle in der Bibliothek sind",
|
||||
"Skipped: files that can't be read": "Übersprungen: Dateien, die nicht gelesen werden können"
|
||||
"Skipped: files that can't be read": "Übersprungen: Dateien, die nicht gelesen werden können",
|
||||
"This folder isn't there: it was moved or renamed, or its disk isn't connected.": "Dieser Ordner ist nicht vorhanden: Er wurde verschoben oder umbenannt, oder sein Datenträger ist nicht verbunden."
|
||||
}
|
||||
|
||||
@@ -1748,5 +1748,6 @@
|
||||
"Missing photos move to Recently Deleted and can be restored; no file on disk is touched.": "Las fotos que faltan pasan a Eliminadas recientemente y se pueden restaurar; no se toca ningún archivo del disco.",
|
||||
"Reading a sidecar replaces the photo's rating, labels, metadata and edits with what it says.": "Leer un archivo complementario sustituye la valoración, las etiquetas, los metadatos y las ediciones de la foto por lo que contiene.",
|
||||
"Skipped: files already in the library elsewhere": "Omitidos: archivos que ya están en la biblioteca en otro lugar",
|
||||
"Skipped: files that can't be read": "Omitidos: archivos que no se pueden leer"
|
||||
"Skipped: files that can't be read": "Omitidos: archivos que no se pueden leer",
|
||||
"This folder isn't there: it was moved or renamed, or its disk isn't connected.": "Esta carpeta no está: se movió o se le cambió el nombre, o su disco no está conectado."
|
||||
}
|
||||
|
||||
@@ -1748,5 +1748,6 @@
|
||||
"Missing photos move to Recently Deleted and can be restored; no file on disk is touched.": "見つからない写真は「最近削除した写真」に移動し、復元できます。ディスク上のファイルには手を加えません",
|
||||
"Reading a sidecar replaces the photo's rating, labels, metadata and edits with what it says.": "サイドカーを読み込むと、写真のレーティング、ラベル、メタデータ、編集内容がその内容に置き換わります。",
|
||||
"Skipped: files already in the library elsewhere": "スキップ: 別の場所ですでにライブラリにあるファイル",
|
||||
"Skipped: files that can't be read": "スキップ: 読み込めないファイル"
|
||||
"Skipped: files that can't be read": "スキップ: 読み込めないファイル",
|
||||
"This folder isn't there: it was moved or renamed, or its disk isn't connected.": "このフォルダが見つかりません。移動または名前の変更がされたか、ディスクが接続されていません。"
|
||||
}
|
||||
|
||||
@@ -1748,5 +1748,6 @@
|
||||
"Missing photos move to Recently Deleted and can be restored; no file on disk is touched.": "As fotos ausentes vão para Excluídos recentemente e podem ser restauradas; nenhum arquivo no disco é alterado.",
|
||||
"Reading a sidecar replaces the photo's rating, labels, metadata and edits with what it says.": "Ler um arquivo auxiliar substitui a classificação, os rótulos, os metadados e as edições da foto pelo que ele contém.",
|
||||
"Skipped: files already in the library elsewhere": "Ignorados: arquivos que já estão na biblioteca em outro lugar",
|
||||
"Skipped: files that can't be read": "Ignorados: arquivos que não podem ser lidos"
|
||||
"Skipped: files that can't be read": "Ignorados: arquivos que não podem ser lidos",
|
||||
"This folder isn't there: it was moved or renamed, or its disk isn't connected.": "Esta pasta não está lá: foi movida ou renomeada, ou o disco dela não está conectado."
|
||||
}
|
||||
|
||||
@@ -1421,5 +1421,6 @@
|
||||
"Missing photos move to Recently Deleted and can be restored; no file on disk is touched.": "Отсутствующие фото перемещаются в «Недавно удалённые» и могут быть восстановлены; файлы на диске не затрагиваются.",
|
||||
"Reading a sidecar replaces the photo's rating, labels, metadata and edits with what it says.": "Чтение файла XMP заменяет рейтинг, метки, метаданные и правки фото его содержимым.",
|
||||
"Skipped: files already in the library elsewhere": "Пропущено: файлы, которые уже есть в медиатеке в другом месте",
|
||||
"Skipped: files that can't be read": "Пропущено: файлы, которые не удаётся прочитать"
|
||||
"Skipped: files that can't be read": "Пропущено: файлы, которые не удаётся прочитать",
|
||||
"This folder isn't there: it was moved or renamed, or its disk isn't connected.": "Этой папки нет: её переместили или переименовали, или её диск не подключён."
|
||||
}
|
||||
|
||||
@@ -1748,5 +1748,6 @@
|
||||
"Missing photos move to Recently Deleted and can be restored; no file on disk is touched.": "缺失的照片会移到“最近删除”,可以恢复;磁盘上的文件不会被改动。",
|
||||
"Reading a sidecar replaces the photo's rating, labels, metadata and edits with what it says.": "读取附属文件会用其内容替换照片的评级、标签、元数据和编辑。",
|
||||
"Skipped: files already in the library elsewhere": "已跳过:已在图库其他位置的文件",
|
||||
"Skipped: files that can't be read": "已跳过:无法读取的文件"
|
||||
"Skipped: files that can't be read": "已跳过:无法读取的文件",
|
||||
"This folder isn't there: it was moved or renamed, or its disk isn't connected.": "此文件夹不存在:它已被移动或重命名,或其磁盘未连接。"
|
||||
}
|
||||
|
||||
@@ -1748,5 +1748,6 @@
|
||||
"Missing photos move to Recently Deleted and can be restored; no file on disk is touched.": "遺失的照片會移至「最近刪除」,可以還原;磁碟上的檔案不會被更動。",
|
||||
"Reading a sidecar replaces the photo's rating, labels, metadata and edits with what it says.": "讀取附屬檔案會以其內容取代照片的評等、標籤、中繼資料和編輯。",
|
||||
"Skipped: files already in the library elsewhere": "已略過:已在照片庫其他位置的檔案",
|
||||
"Skipped: files that can't be read": "已略過:無法讀取的檔案"
|
||||
"Skipped: files that can't be read": "已略過:無法讀取的檔案",
|
||||
"This folder isn't there: it was moved or renamed, or its disk isn't connected.": "此資料夾不存在:它已被移動或重新命名,或其磁碟未連接。"
|
||||
}
|
||||
|
||||
@@ -1247,7 +1247,8 @@ fn synchronize_item(app: &mut LightcraftApp, ui: &mut egui::Ui, n: &FolderNode,
|
||||
.on_hover_text(crate::i18n::tr("Find photos added to or missing from this folder on disk, and XMP sidecars changed by other apps"));
|
||||
crate::widgets::register(ui.ctx(), "folderSynchronize", r.rect);
|
||||
if r.clicked() {
|
||||
if let Err(e) = crate::sync::open(app, &n.path, name) {
|
||||
// a disk, or a folder holding disks, was chosen as such
|
||||
if let Err(e) = crate::sync::open(app, &n.path, name, n.volume || !n.selectable) {
|
||||
app.toast(ui.ctx(), e);
|
||||
}
|
||||
ui.close();
|
||||
|
||||
@@ -671,6 +671,9 @@ pub enum Dialog {
|
||||
path: String,
|
||||
/// What the dialog calls it (a folder's last two names).
|
||||
name: String,
|
||||
/// A whole disk, or a folder holding disks (`folder.synchronize` `disk`).
|
||||
#[serde(default)]
|
||||
disk: bool,
|
||||
/// What the scan found (`None` while it runs).
|
||||
counts: Option<SyncCounts>,
|
||||
import_new: bool,
|
||||
@@ -685,6 +688,9 @@ pub enum Dialog {
|
||||
#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct SyncCounts {
|
||||
/// The folder isn't there (moved, renamed, or on a disk that isn't connected).
|
||||
#[serde(default)]
|
||||
pub offline: bool,
|
||||
pub new: usize,
|
||||
pub duplicates: usize,
|
||||
pub unreadable: usize,
|
||||
|
||||
@@ -22,9 +22,9 @@ pub struct SyncTask {
|
||||
}
|
||||
|
||||
/// Open the dialog for folder `path` (called `name` in it) and start scanning.
|
||||
pub fn open(app: &mut LightcraftApp, path: &str, name: &str) -> Result<(), String> {
|
||||
pub fn open(app: &mut LightcraftApp, path: &str, name: &str, disk: bool) -> Result<(), String> {
|
||||
cancel(app);
|
||||
let input = SyncInput::new(&mut app.session, path).map_err(|e| e.to_string())?;
|
||||
let input = SyncInput::new(&mut app.session, path, disk).map_err(|e| e.to_string())?;
|
||||
let progress = Arc::new(ScanProgress::default());
|
||||
let (tx, rx) = std::sync::mpsc::channel();
|
||||
let p = progress.clone();
|
||||
@@ -40,6 +40,7 @@ pub fn open(app: &mut LightcraftApp, path: &str, name: &str) -> Result<(), Strin
|
||||
app.ui.dialog = Some(Dialog::SynchronizeFolder {
|
||||
path: path.to_string(),
|
||||
name: name.to_string(),
|
||||
disk,
|
||||
counts: None,
|
||||
import_new: d.import_new,
|
||||
remove_missing: d.remove_missing,
|
||||
@@ -87,6 +88,7 @@ pub fn poll(app: &mut LightcraftApp, ctx: &egui::Context) {
|
||||
};
|
||||
app.sync = None;
|
||||
let c = SyncCounts {
|
||||
offline: changes.offline,
|
||||
new: changes.new.len(),
|
||||
duplicates: changes.duplicates,
|
||||
unreadable: changes.unreadable.len(),
|
||||
@@ -106,7 +108,7 @@ fn progress(app: &LightcraftApp) -> Option<(usize, usize)> {
|
||||
|
||||
/// The dialog's body.
|
||||
pub fn body(app: &LightcraftApp, ui: &mut egui::Ui, dlg: &mut Dialog) {
|
||||
let Dialog::SynchronizeFolder { path, name, counts, import_new, remove_missing, read_metadata } = dlg else { return };
|
||||
let Dialog::SynchronizeFolder { path, name, counts, import_new, remove_missing, read_metadata, .. } = dlg else { return };
|
||||
let t = Tokens::get(ui.ctx());
|
||||
ui.set_min_width(420.0);
|
||||
ui.label(crate::i18n::tr("Bring the library up to date with this folder and the folders inside it:"));
|
||||
@@ -123,6 +125,10 @@ pub fn body(app: &LightcraftApp, ui: &mut egui::Ui, dlg: &mut Dialog) {
|
||||
});
|
||||
return;
|
||||
};
|
||||
if c.offline {
|
||||
ui.label(crate::i18n::tr("This folder isn't there: it was moved or renamed, or its disk isn't connected."));
|
||||
return;
|
||||
}
|
||||
let choice = |ui: &mut egui::Ui, on: &mut bool, n: usize, text: String, widget: &str| {
|
||||
let r = ui.add_enabled(n > 0, egui::Checkbox::new(on, text));
|
||||
crate::widgets::register(ui.ctx(), widget, r.rect);
|
||||
@@ -152,11 +158,13 @@ pub fn body(app: &LightcraftApp, ui: &mut egui::Ui, dlg: &mut Dialog) {
|
||||
|
||||
/// Synchronize: `folder.synchronize` with the dialog's choices.
|
||||
pub fn confirm(app: &mut LightcraftApp, dlg: &Dialog) -> Result<Value, String> {
|
||||
let Dialog::SynchronizeFolder { path, counts: Some(_), import_new, remove_missing, read_metadata, .. } = dlg else {
|
||||
let Dialog::SynchronizeFolder { path, disk, counts: Some(_), import_new, remove_missing, read_metadata, .. } = dlg else {
|
||||
return Err("the folder is still being scanned".into());
|
||||
};
|
||||
let r =
|
||||
app.run("folder.synchronize", json!({"path": path, "importNew": import_new, "removeMissing": remove_missing, "readMetadata": read_metadata}));
|
||||
let r = app.run(
|
||||
"folder.synchronize",
|
||||
json!({"path": path, "disk": disk, "importNew": import_new, "removeMissing": remove_missing, "readMetadata": read_metadata}),
|
||||
);
|
||||
app.session.folder_changes = None;
|
||||
r
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user