mirror of
https://github.com/storytold/lightcraft.git
synced 2026-11-10 18:39:51 -06:00
LRC-LIB-FOLDERS: Move Folder To… waits while Open Library… or New Library… asks for a folder (#3140)
This commit is contained in:
@@ -552,18 +552,37 @@ fn library_node_for(tree: &[FolderNode], path: &str) -> Option<(FolderNode, Stri
|
||||
Some((node, name))
|
||||
}
|
||||
|
||||
/// Move Folder To… can't start while Open Library… or New Library… waits for its folder: the
|
||||
/// dialogs answer in the order they were opened ([`crate::pick::poll`]), so the library could
|
||||
/// switch first and the move then run in the other library, moving the folder on disk with
|
||||
/// nothing to relink (issue #3140).
|
||||
fn can_move_folder(app: &LightcraftApp) -> Result<(), String> {
|
||||
for (command, label) in [("app.openLibrary", "Open Library…"), ("app.newLibrary", "New Library…")] {
|
||||
if app.pending_picks.iter().any(|p| p.command == command) {
|
||||
return Err(format!("wait for {label} to finish before moving a folder"));
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Move Folder To… of a folder row (widget `folderMoveTo`): asks for the destination through the
|
||||
/// shared folder dialog, off the UI thread where the host has one (issue #3140: a dialog on the UI
|
||||
/// thread froze the window); `folder.move` then runs when it closes ([`crate::pick::poll`]).
|
||||
/// Disabled while a library dialog is up ([`can_move_folder`]).
|
||||
fn move_folder_item(app: &mut LightcraftApp, ui: &mut egui::Ui, path: &str) {
|
||||
if app.services.pick_folder.is_none() && app.services.picker.is_none() {
|
||||
return;
|
||||
}
|
||||
let r = ui.button(crate::i18n::tr("Move Folder To…"));
|
||||
let r = ui.add_enabled(can_move_folder(app).is_ok(), egui::Button::new(crate::i18n::tr("Move Folder To…")));
|
||||
register(ui, "folderMoveTo", r.rect);
|
||||
if !r.clicked() {
|
||||
return;
|
||||
}
|
||||
if let Err(e) = can_move_folder(app) {
|
||||
app.toast(ui.ctx(), e);
|
||||
ui.close();
|
||||
return;
|
||||
}
|
||||
let req = crate::pick::PickRequest::folder(crate::i18n::tr("Move Folder To…"));
|
||||
let params = json!({"path": path});
|
||||
let picked = crate::pick::ask(app, "folder.move", ¶ms, "into", req, |s| s.pick_folder.as_mut().and_then(|f| f()).map(|x| vec![x]));
|
||||
|
||||
@@ -179,3 +179,33 @@ fn no_library_switch_while_move_folder_to_waits_for_its_destination() {
|
||||
h.app.run("app.openLibrary", json!({"path": other})).expect("the switch works once the move is done");
|
||||
assert_eq!(library(&h), Some(other));
|
||||
}
|
||||
|
||||
/// Issue #3140: while Open Library… waits for its folder, Move Folder To… can't start (both
|
||||
/// dialogs answer in the order opened, so the library could switch before the move ran, which
|
||||
/// would then move the folder in the other library with nothing to relink): the item is disabled,
|
||||
/// no destination is asked for, and the folder stays. Once the library dialog is cancelled it works.
|
||||
#[test]
|
||||
fn move_folder_to_waits_for_open_library_to_finish() {
|
||||
let dir = Scratch::new("after-open");
|
||||
std::fs::create_dir_all(dir.path("dest")).unwrap();
|
||||
let (mut h, asked, blocking) = app_with_trip(&dir);
|
||||
assert_eq!(h.app.run("app.openLibrary", json!({})).ok(), Some(serde_json::Value::Null));
|
||||
assert_eq!(asked.lock().unwrap().len(), 1, "the Open Library dialog is up");
|
||||
open_row_menu(&mut h, &format!("source:libfolder:{}", dir.path("trip")));
|
||||
let _ = h.request("ui.clickWidget", json!({"id": "folderMoveTo"}), T);
|
||||
h.settle(SETTLE);
|
||||
assert_eq!(asked.lock().unwrap().len(), 1, "no destination is asked for");
|
||||
assert_eq!(blocking.load(Ordering::Relaxed), 0, "not on the UI thread either");
|
||||
assert!(!h.app.pending_picks.iter().any(|p| p.command == "folder.move"), "nothing waits to move");
|
||||
assert!(std::path::Path::new(&dir.path("trip/a.png")).exists(), "the folder stays");
|
||||
// the Open Library dialog is cancelled: Move Folder To… asks for its destination again
|
||||
asked.lock().unwrap()[0].1.send(vec![]).unwrap();
|
||||
h.settle(SETTLE);
|
||||
assert!(h.app.pending_picks.is_empty());
|
||||
open_row_menu(&mut h, &format!("source:libfolder:{}", dir.path("trip")));
|
||||
let r = h.request("ui.clickWidget", json!({"id": "folderMoveTo"}), T);
|
||||
assert_eq!(r["ok"], true, "{r}");
|
||||
h.step();
|
||||
assert_eq!(asked.lock().unwrap().len(), 2, "the destination is asked for");
|
||||
assert!(h.app.pending_picks.iter().any(|p| p.command == "folder.move"));
|
||||
}
|
||||
|
||||
+2
-1
@@ -7,7 +7,8 @@
|
||||
Folder… do: the window keeps drawing and answering while you choose, and the folder moves when the dialog closes,
|
||||
with the same "Moved; N photo(s) relinked" message. Before, the dialog held up the whole window until it closed;
|
||||
on Linux with X11 the app could stop responding altogether. While the dialog is open, another library can't be
|
||||
opened (the move belongs to the library it was started in).
|
||||
opened (the move belongs to the library it was started in), and while Open Library… or New Library… waits for
|
||||
its folder, Move Folder To… is unavailable.
|
||||
|
||||
## 2026-10-24
|
||||
|
||||
|
||||
Reference in New Issue
Block a user