From 1236296ea6474c9f1ec0ae827ee1e1872bf83741 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Thu, 6 Nov 2025 14:55:59 -0300 Subject: [PATCH] feat: use async fd when using tokio (#4927) * feat: use async fd when using tokio * whoops fix ios --- Cargo.lock | 2 +- packages/desktop/src/file_upload.rs | 88 ++++++++++++++++++++++++++++- packages/desktop/src/protocol.rs | 29 ++++++++-- 3 files changed, 112 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 398c74e56..04d9f3b4e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5839,7 +5839,7 @@ dependencies = [ "getrandom 0.3.4", "http-range", "ouroboros", - "pollster", + "pollster 0.4.0", "rand 0.9.2", "reqwest 0.12.24", "separator", diff --git a/packages/desktop/src/file_upload.rs b/packages/desktop/src/file_upload.rs index c85393a25..198a89ffe 100644 --- a/packages/desktop/src/file_upload.rs +++ b/packages/desktop/src/file_upload.rs @@ -54,6 +54,19 @@ impl FileDialogRequest { vec![] } + #[cfg(not(any( + target_os = "windows", + target_os = "macos", + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd" + )))] + pub(crate) async fn get_file_event_async(&self) -> Vec { + vec![] + } + #[cfg(any( target_os = "windows", target_os = "macos", @@ -63,9 +76,8 @@ impl FileDialogRequest { target_os = "netbsd", target_os = "openbsd" ))] - pub(crate) fn get_file_event(&self) -> Vec { + pub(crate) fn get_file_event_sync(&self) -> Vec { let dialog = rfd::FileDialog::new(); - if self.directory { self.get_file_event_for_folder(dialog) } else { @@ -73,6 +85,78 @@ impl FileDialogRequest { } } + #[cfg(any( + target_os = "windows", + target_os = "macos", + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd" + ))] + pub(crate) async fn get_file_event_async(&self) -> Vec { + let mut dialog = rfd::AsyncFileDialog::new(); + + if self.directory { + if self.multiple { + dialog + .pick_folders() + .await + .into_iter() + .flatten() + .map(|f| f.path().to_path_buf()) + .collect() + } else { + dialog + .pick_folder() + .await + .into_iter() + .map(|f| f.path().to_path_buf()) + .collect() + } + } else { + let filters: Vec<_> = self + .accept + .as_deref() + .unwrap_or(".*") + .split(',') + .filter_map(|s| Filters::from_str(s.trim()).ok()) + .collect(); + + let file_extensions: Vec<_> = filters + .iter() + .flat_map(|f| f.as_extensions().into_iter()) + .collect(); + + let filter_name = file_extensions + .iter() + .map(|extension| format!("*.{extension}")) + .collect::>() + .join(", "); + + dialog = dialog.add_filter(filter_name, file_extensions.as_slice()); + + let files: Vec<_> = if self.multiple { + dialog + .pick_files() + .await + .into_iter() + .flatten() + .map(|f| f.path().to_path_buf()) + .collect() + } else { + dialog + .pick_file() + .await + .into_iter() + .map(|f| f.path().to_path_buf()) + .collect() + }; + + files + } + } + #[cfg(any( target_os = "windows", target_os = "macos", diff --git a/packages/desktop/src/protocol.rs b/packages/desktop/src/protocol.rs index de8fae167..95be7cf48 100644 --- a/packages/desktop/src/protocol.rs +++ b/packages/desktop/src/protocol.rs @@ -1,3 +1,5 @@ +use std::path::PathBuf; + use crate::{assets::*, webview::WebviewEdits}; use crate::{document::NATIVE_EVAL_JS, file_upload::FileDialogRequest}; use base64::prelude::BASE64_STANDARD; @@ -63,9 +65,10 @@ pub(super) fn desktop_handler( // If the request is asking for a file dialog, handle that, returning the list of files selected if trimmed_uri == "__file_dialog" { - if let Err(err) = file_dialog_responder(request, responder) { + if let Err(err) = file_dialog_responder_sync(request, responder) { tracing::error!("Failed to handle file dialog request: {err:?}"); } + return; } @@ -177,7 +180,7 @@ fn module_loader(root_id: &str, headless: bool, edit_state: &WebviewEdits) -> St ) } -fn file_dialog_responder( +fn file_dialog_responder_sync( request: wry::http::Request>, responder: wry::RequestAsyncResponder, ) -> dioxus_core::Result<()> { @@ -191,11 +194,29 @@ fn file_dialog_responder( let data_from_header = base64::Engine::decode(&BASE64_STANDARD, header.as_bytes()) .context("Failed to decode x-dioxus-data header from base64")?; - let mut file_dialog: FileDialogRequest = serde_json::from_slice(&data_from_header) + let file_dialog: FileDialogRequest = serde_json::from_slice(&data_from_header) .context("Failed to parse x-dioxus-data header as JSON")?; - let file_list = file_dialog.get_file_event(); + #[cfg(feature = "tokio_runtime")] + tokio::spawn(async move { + let file_list = file_dialog.get_file_event_async().await; + _ = respond_to_file_dialog(file_dialog, file_list, responder); + }); + #[cfg(not(feature = "tokio_runtime"))] + { + let file_list = file_dialog.get_file_event_sync(); + respond_to_file_dialog(file_dialog, file_list, responder)?; + } + + Ok(()) +} + +fn respond_to_file_dialog( + mut file_dialog: FileDialogRequest, + file_list: Vec, + responder: wry::RequestAsyncResponder, +) -> dioxus_core::Result<()> { // Get the position of the entry we're updating, so we can insert new entries in the same place // If we can't find it, just append to the end. This is usually due to the input not being in a form element. let position_of_entry = file_dialog