Fix freeze on file/folder picker dialogs: use the non-blocking dialog API instead of blocking_pick_files()/blocking_pick_folder(), which deadlock when called from a command handler
This commit is contained in:
Generated
+1
@@ -2536,6 +2536,7 @@ dependencies = [
|
||||
"tauri",
|
||||
"tauri-build",
|
||||
"tauri-plugin-dialog",
|
||||
"tokio",
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
|
||||
@@ -18,3 +18,4 @@ tauri-plugin-dialog = "2"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = "1"
|
||||
walkdir = "2"
|
||||
tokio = { version = "1", features = ["sync"] }
|
||||
|
||||
+31
-6
@@ -71,9 +71,23 @@ fn expand(paths: &[String]) -> Vec<FileEntry> {
|
||||
}
|
||||
|
||||
/// Native "Select Files" dialog, mirroring the original's Load Files button.
|
||||
///
|
||||
/// Uses the non-blocking callback API (not `blocking_pick_files()`): the blocking
|
||||
/// variant must never be called from a Tauri command handler, because on macOS it has
|
||||
/// to hand off to the main thread to actually show the panel and then wait for a
|
||||
/// response - if the command itself is occupying the thread the dialog needs, the app
|
||||
/// deadlocks the moment the dialog tries to close (this is exactly what "froze after I
|
||||
/// hit Cancel" looked like). The callback form lets the dialog signal completion
|
||||
/// asynchronously instead, which we bridge back into an `async fn` with a oneshot
|
||||
/// channel.
|
||||
#[tauri::command]
|
||||
pub fn pick_files(app: AppHandle) -> Vec<FileEntry> {
|
||||
let selection = app.dialog().file().blocking_pick_files();
|
||||
pub async fn pick_files(app: AppHandle) -> Vec<FileEntry> {
|
||||
let (tx, rx) = tokio::sync::oneshot::channel();
|
||||
app.dialog().file().pick_files(move |result| {
|
||||
let _ = tx.send(result);
|
||||
});
|
||||
let selection = rx.await.unwrap_or(None);
|
||||
|
||||
let paths: Vec<String> = selection
|
||||
.into_iter()
|
||||
.flatten()
|
||||
@@ -84,9 +98,16 @@ pub fn pick_files(app: AppHandle) -> Vec<FileEntry> {
|
||||
}
|
||||
|
||||
/// Native "Select Folders" dialog; the folder's contents are expanded recursively.
|
||||
/// See `pick_files` above for why this uses the callback API instead of
|
||||
/// `blocking_pick_folder()`.
|
||||
#[tauri::command]
|
||||
pub fn pick_folder(app: AppHandle) -> Vec<FileEntry> {
|
||||
let selection = app.dialog().file().blocking_pick_folder();
|
||||
pub async fn pick_folder(app: AppHandle) -> Vec<FileEntry> {
|
||||
let (tx, rx) = tokio::sync::oneshot::channel();
|
||||
app.dialog().file().pick_folder(move |result| {
|
||||
let _ = tx.send(result);
|
||||
});
|
||||
let selection = rx.await.unwrap_or(None);
|
||||
|
||||
let paths: Vec<String> = selection
|
||||
.into_iter()
|
||||
.filter_map(|f| f.into_path().ok())
|
||||
@@ -96,7 +117,11 @@ pub fn pick_folder(app: AppHandle) -> Vec<FileEntry> {
|
||||
}
|
||||
|
||||
/// Expands paths dropped onto the window (files and/or folders) into their files.
|
||||
/// Runs off the main thread too - not for a deadlock reason like the dialogs above,
|
||||
/// just so recursively walking a very large dropped folder doesn't stall the UI.
|
||||
#[tauri::command]
|
||||
pub fn expand_dropped_paths(paths: Vec<String>) -> Vec<FileEntry> {
|
||||
expand(&paths)
|
||||
pub async fn expand_dropped_paths(paths: Vec<String>) -> Vec<FileEntry> {
|
||||
tauri::async_runtime::spawn_blocking(move || expand(&paths))
|
||||
.await
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user