mirror of
https://github.com/storytold/lightcraft.git
synced 2026-10-29 12:37:51 -06:00
UI: a Synchronize Folder and an import never run at once (review fix)
Each readies its files against the library as it was when it started, so with the synchronize in the background, dropping the same folder on the window (or Auto Import) while it ran could add the same files twice. Imports (the review, dropped paths, Auto Import) wait while a folder is being synchronized, and Synchronize waits for a running import. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
0f8b23875d
commit
f6bb274da3
@@ -1914,5 +1914,7 @@
|
||||
"Failed": "Fehlgeschlagen",
|
||||
"Stopped": "Angehalten",
|
||||
"Stopped by an error (see the log)": "Durch einen Fehler angehalten (siehe Protokoll)",
|
||||
"Not saved": "Nicht gespeichert"
|
||||
"Not saved": "Nicht gespeichert",
|
||||
"A folder is being synchronized": "Ein Ordner wird gerade synchronisiert",
|
||||
"Wait for the import to finish": "Warten Sie, bis der Import abgeschlossen ist"
|
||||
}
|
||||
|
||||
@@ -1759,5 +1759,7 @@
|
||||
"Failed": "Con errores",
|
||||
"Stopped": "Detenido",
|
||||
"Stopped by an error (see the log)": "Detenido por un error (ver el registro)",
|
||||
"Not saved": "Sin guardar"
|
||||
"Not saved": "Sin guardar",
|
||||
"A folder is being synchronized": "Se está sincronizando una carpeta",
|
||||
"Wait for the import to finish": "Espera a que termine la importación"
|
||||
}
|
||||
|
||||
@@ -1759,5 +1759,7 @@
|
||||
"Failed": "失敗",
|
||||
"Stopped": "停止しました",
|
||||
"Stopped by an error (see the log)": "エラーで停止しました(ログを参照)",
|
||||
"Not saved": "保存されていません"
|
||||
"Not saved": "保存されていません",
|
||||
"A folder is being synchronized": "フォルダを同期中です",
|
||||
"Wait for the import to finish": "読み込みが終わるまでお待ちください"
|
||||
}
|
||||
|
||||
@@ -1759,5 +1759,7 @@
|
||||
"Failed": "Com falha",
|
||||
"Stopped": "Interrompido",
|
||||
"Stopped by an error (see the log)": "Interrompido por um erro (veja o log)",
|
||||
"Not saved": "Não salvo"
|
||||
"Not saved": "Não salvo",
|
||||
"A folder is being synchronized": "Uma pasta está sendo sincronizada",
|
||||
"Wait for the import to finish": "Aguarde a importação terminar"
|
||||
}
|
||||
|
||||
@@ -1433,5 +1433,7 @@
|
||||
"Failed": "Ошибки",
|
||||
"Stopped": "Остановлено",
|
||||
"Stopped by an error (see the log)": "Остановлено из-за ошибки (см. журнал)",
|
||||
"Not saved": "Не сохранено"
|
||||
"Not saved": "Не сохранено",
|
||||
"A folder is being synchronized": "Идёт синхронизация папки",
|
||||
"Wait for the import to finish": "Дождитесь окончания импорта"
|
||||
}
|
||||
|
||||
@@ -1759,5 +1759,7 @@
|
||||
"Failed": "失败",
|
||||
"Stopped": "已停止",
|
||||
"Stopped by an error (see the log)": "因错误而停止(见日志)",
|
||||
"Not saved": "未保存"
|
||||
"Not saved": "未保存",
|
||||
"A folder is being synchronized": "正在同步文件夹",
|
||||
"Wait for the import to finish": "请等待导入完成"
|
||||
}
|
||||
|
||||
@@ -1759,5 +1759,7 @@
|
||||
"Failed": "失敗",
|
||||
"Stopped": "已停止",
|
||||
"Stopped by an error (see the log)": "因錯誤而停止(請見記錄)",
|
||||
"Not saved": "未儲存"
|
||||
"Not saved": "未儲存",
|
||||
"A folder is being synchronized": "正在同步資料夾",
|
||||
"Wait for the import to finish": "請等待匯入完成"
|
||||
}
|
||||
|
||||
@@ -393,6 +393,12 @@ pub struct ScanTask {
|
||||
sources: Vec<String>,
|
||||
}
|
||||
|
||||
/// An import and a Synchronize Folder never run at once: each readies its files against the
|
||||
/// library as it was when it started, so both could add the same file.
|
||||
pub(crate) fn busy_synchronizing(app: &LightcraftApp) -> Result<(), String> {
|
||||
if app.sync_run.is_some() { Err(crate::i18n::tr("A folder is being synchronized").to_string()) } else { Ok(()) }
|
||||
}
|
||||
|
||||
/// Scan `paths` in the background, then open the review dialog (see [`poll_scan`]).
|
||||
pub fn open(app: &mut LightcraftApp, paths: Vec<String>) -> Result<Value, String> {
|
||||
if app.scan.is_some() {
|
||||
@@ -565,6 +571,7 @@ impl ScanTask {
|
||||
|
||||
/// Start importing the dialog's checked files (the dialog's OK / `ui.dialog.confirm`).
|
||||
pub fn start(app: &mut LightcraftApp, d: &ImportDialog) -> Result<Value, String> {
|
||||
busy_synchronizing(app)?;
|
||||
let queue = d.selected_paths();
|
||||
if queue.is_empty() {
|
||||
return Err("no photos selected".into());
|
||||
@@ -616,6 +623,7 @@ pub fn start(app: &mut LightcraftApp, d: &ImportDialog) -> Result<Value, String>
|
||||
|
||||
/// Start importing `paths` (files or folders) in the background, e.g. dropped on the window.
|
||||
pub fn start_paths(app: &mut LightcraftApp, paths: Vec<String>) -> Result<Value, String> {
|
||||
busy_synchronizing(app)?;
|
||||
if app.import.is_some() || app.scan.as_ref().is_some_and(|t| !t.browse) {
|
||||
return Err("an import is running".into());
|
||||
}
|
||||
|
||||
@@ -772,7 +772,12 @@ impl LightcraftApp {
|
||||
if let Some(folder) = self.session.import_defaults.auto_folder.clone() {
|
||||
const LABEL: &str = "Auto Import";
|
||||
let now = ctx.input(|i| i.time);
|
||||
if now - self.ui.auto_import_at >= 3.0 && self.import.is_none() && self.lightroom.is_none() && !self.tasks.is_running(LABEL) {
|
||||
if now - self.ui.auto_import_at >= 3.0
|
||||
&& self.import.is_none()
|
||||
&& self.sync_run.is_none()
|
||||
&& self.lightroom.is_none()
|
||||
&& !self.tasks.is_running(LABEL)
|
||||
{
|
||||
self.ui.auto_import_at = now;
|
||||
let work = move || lightcraft_engine::cmd::library::list_auto_import_folder(&folder);
|
||||
let done = |app: &mut LightcraftApp, _ctx: &egui::Context, listing: Result<Vec<(String, u64)>, String>| {
|
||||
|
||||
@@ -172,6 +172,10 @@ pub fn confirm(app: &mut LightcraftApp, dlg: &Dialog) -> Result<Value, String> {
|
||||
if app.sync_run.is_some() {
|
||||
return Err(crate::i18n::tr("A folder is already being synchronized").to_string());
|
||||
}
|
||||
// (an import readies its files against the library as it was: both could add the same file)
|
||||
if app.import.is_some() {
|
||||
return Err(crate::i18n::tr("Wait for the import to finish").to_string());
|
||||
}
|
||||
let choice = SyncChoice { import_new: *import_new, relink_moved: *relink_moved, remove_missing: *remove_missing, read_metadata: *read_metadata };
|
||||
app.sync_owns_changes = false;
|
||||
let started = match app.session.take_folder_changes(path) {
|
||||
|
||||
@@ -217,3 +217,32 @@ fn a_stopped_synchronize_says_it_was_stopped_and_stays_at_most_one_step() {
|
||||
assert!(toast.contains("Stopped"), "never 'up to date': {toast:?}");
|
||||
assert!(h.app.session.undo.len() <= steps + 1, "what was done by then is at most one step");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_synchronize_and_an_import_never_run_at_once() {
|
||||
let dir = Scratch::new("exclusive");
|
||||
let mut h = changed_folder(&dir);
|
||||
open_and_scan(&mut h, &dir.path("trip"));
|
||||
let dlg = h.app.ui.dialog.take().expect("the dialog is open");
|
||||
assert!(crate::panels::dialogs::confirm_dialog(&mut h.app, &dlg).is_ok());
|
||||
// dropping the same folder on the window while the run works would add its files twice
|
||||
let r = crate::import::start_paths(&mut h.app, vec![dir.path("trip")]);
|
||||
assert!(r.is_err(), "no import while synchronizing: {r:?}");
|
||||
assert!(h.step_until(T, |h| h.app.sync_run.is_none()));
|
||||
// and the other way round: an import is running (no frame has run since it started)
|
||||
let r = crate::import::start_paths(&mut h.app, vec![dir.path("trip/a.png")]);
|
||||
assert!(r.is_ok(), "{r:?}");
|
||||
assert!(h.app.import.is_some());
|
||||
let d = Dialog::SynchronizeFolder {
|
||||
path: dir.path("trip"),
|
||||
name: "trip".into(),
|
||||
disk: false,
|
||||
counts: Some(Default::default()),
|
||||
import_new: true,
|
||||
relink_moved: true,
|
||||
remove_missing: false,
|
||||
read_metadata: false,
|
||||
};
|
||||
let r = crate::panels::dialogs::confirm_dialog(&mut h.app, &d);
|
||||
assert!(r.is_err() && h.app.sync_run.is_none(), "no synchronize while importing: {r:?}");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user