perf(tauri): move blocking command work off async runtime

This commit is contained in:
t8y2 committed 2026-06-09 17:53:07 +08:00
1 parent a5d9addbf3
commit 348f39ff35
11 files changed
+135 -64

No files matched your search

+8 -2
View File
@@ -1,4 +1,4 @@
use std::sync::Arc;
use std::{path::PathBuf, sync::Arc};
use dbx_core::storage::DesktopSettings;
use tauri::{AppHandle, Manager, State};
@@ -37,9 +37,15 @@ pub async fn save_pinned_tree_node_ids(state: State<'_, Arc<AppState>>, ids: Vec
#[tauri::command]
pub async fn load_native_debug_logs(app: AppHandle) -> Result<String, String> {
let log_dir = app.path().app_log_dir().map_err(|e| e.to_string())?;
tauri::async_runtime::spawn_blocking(move || load_native_debug_logs_from_dir(log_dir))
.await
.map_err(|err| err.to_string())?
}
fn load_native_debug_logs_from_dir(log_dir: PathBuf) -> Result<String, String> {
const MAX_FILES: usize = 6;
const MAX_FILE_BYTES: u64 = 512 * 1024;
let log_dir = app.path().app_log_dir().map_err(|e| e.to_string())?;
if !log_dir.exists() {
return Ok(format!("Native log dir does not exist yet: {}", log_dir.display()));
}
+7 -3
View File
@@ -15,9 +15,13 @@ pub struct QueryResultCsvExportRequest {
}
#[tauri::command]
pub fn export_query_result_csv(request: QueryResultCsvExportRequest) -> Result<(), String> {
let csv = format_csv(&request.columns, &request.rows);
std::fs::write(&request.file_path, format!("\u{FEFF}{csv}")).map_err(|err| err.to_string())
pub async fn export_query_result_csv(request: QueryResultCsvExportRequest) -> Result<(), String> {
tauri::async_runtime::spawn_blocking(move || {
let csv = format_csv(&request.columns, &request.rows);
std::fs::write(&request.file_path, format!("\u{FEFF}{csv}")).map_err(|err| err.to_string())
})
.await
.map_err(|err| err.to_string())?
}
#[tauri::command]
+12 -3
View File
@@ -12,8 +12,8 @@ pub fn pending_open_sql_files(state: tauri::State<'_, ExternalSqlOpenState>) ->
}
#[tauri::command]
pub fn read_external_sql_file(path: String) -> Result<String, String> {
read_external_sql_file_content(Path::new(&path))
pub async fn read_external_sql_file(path: String) -> Result<String, String> {
read_external_sql_file_content_async(PathBuf::from(path)).await
}
#[derive(Default)]
@@ -62,7 +62,8 @@ pub fn is_sql_file_path(path: &Path) -> bool {
path.extension().and_then(|ext| ext.to_str()).map(|ext| ext.eq_ignore_ascii_case("sql")).unwrap_or(false)
}
pub fn read_external_sql_file_content(path: &Path) -> Result<String, String> {
#[cfg(test)]
fn read_external_sql_file_content(path: &Path) -> Result<String, String> {
if !is_sql_file_path(path) {
return Err("Only .sql files can be opened this way".to_string());
}
@@ -70,6 +71,14 @@ pub fn read_external_sql_file_content(path: &Path) -> Result<String, String> {
decode_sql_file_bytes(&bytes)
}
async fn read_external_sql_file_content_async(path: PathBuf) -> Result<String, String> {
if !is_sql_file_path(&path) {
return Err("Only .sql files can be opened this way".to_string());
}
let bytes = tokio::fs::read(&path).await.map_err(|e| format!("Failed to read SQL file: {e}"))?;
decode_sql_file_bytes(&bytes)
}
fn dedupe_paths(paths: Vec<String>) -> Vec<String> {
let mut unique = Vec::new();
for path in paths {
+25 -21
View File
@@ -1,5 +1,3 @@
use std::process::Command;
/// Read a macOS Keychain generic password by service name.
/// Triggers a system authorization dialog (Touch ID / password) for each unique service.
#[tauri::command]
@@ -12,28 +10,34 @@ pub async fn read_keychain_password(service: String, account: Option<String>) ->
#[cfg(target_os = "macos")]
{
let mut cmd = Command::new("security");
cmd.args(["find-generic-password", "-s", &service, "-w"]);
if let Some(ref acct) = account {
cmd.args(["-a", acct]);
}
tauri::async_runtime::spawn_blocking(move || read_keychain_password_blocking(service, account))
.await
.map_err(|err| err.to_string())?
}
}
let output = cmd.output().map_err(|e| format!("Failed to run security command: {e}"))?;
#[cfg(target_os = "macos")]
fn read_keychain_password_blocking(service: String, account: Option<String>) -> Result<String, String> {
let mut cmd = std::process::Command::new("security");
cmd.args(["find-generic-password", "-s", &service, "-w"]);
if let Some(ref acct) = account {
cmd.args(["-a", acct]);
}
if output.status.success() {
let password = String::from_utf8_lossy(&output.stdout).trim().to_string();
Ok(password)
let output = cmd.output().map_err(|e| format!("Failed to run security command: {e}"))?;
if output.status.success() {
let password = String::from_utf8_lossy(&output.stdout).trim().to_string();
Ok(password)
} else {
// Exit code 44 = user cancelled the authorization dialog
let stderr = String::from_utf8_lossy(&output.stderr).to_string();
if output.status.code() == Some(44) || stderr.contains("User canceled") {
Ok(String::new())
} else if stderr.contains("could not be found") || stderr.contains("The specified item could not be found") {
Ok(String::new())
} else {
// Exit code 44 = user cancelled the authorization dialog
let stderr = String::from_utf8_lossy(&output.stderr).to_string();
if output.status.code() == Some(44) || stderr.contains("User canceled") {
Ok(String::new()) // Return empty instead of error for cancelled
} else if stderr.contains("could not be found") || stderr.contains("The specified item could not be found")
{
Ok(String::new()) // No entry found — not an error
} else {
Err(format!("Keychain read failed: {}", stderr.trim()))
}
Err(format!("Keychain read failed: {}", stderr.trim()))
}
}
}
+14 -6
View File
@@ -31,11 +31,17 @@ struct NpmLatestPackage {
#[tauri::command]
pub async fn check_mcp_server_status() -> Result<McpServerStatus, String> {
let npm_available = command_success("npm", &["--version"]);
let node_version = command_stdout("node", &["--version"]).ok().and_then(first_non_empty_line);
let current_version = if npm_available { installed_mcp_version() } else { None };
let bin_path = locate_mcp_bin();
let latest_version = fetch_latest_mcp_version().await.ok();
let local_status = tauri::async_runtime::spawn_blocking(|| {
let npm_available = command_success("npm", &["--version"]);
let node_version = command_stdout("node", &["--version"]).ok().and_then(first_non_empty_line);
let current_version = if npm_available { installed_mcp_version() } else { None };
let bin_path = locate_mcp_bin();
(npm_available, node_version, current_version, bin_path)
});
let latest_version = fetch_latest_mcp_version();
let (local_status, latest_version) = tokio::join!(local_status, latest_version);
let (npm_available, node_version, current_version, bin_path) = local_status.map_err(|err| err.to_string())?;
let latest_version = latest_version.ok();
let update_available = current_version
.as_deref()
.zip(latest_version.as_deref())
@@ -58,7 +64,9 @@ pub async fn check_mcp_server_status() -> Result<McpServerStatus, String> {
async fn fetch_latest_mcp_version() -> Result<String, String> {
let mut builder = reqwest::Client::builder().timeout(Duration::from_secs(10)).user_agent("dbx-mcp-status-checker");
if let Some(proxy_url) = dbx_core::update::system_proxy_url() {
let proxy_url =
tauri::async_runtime::spawn_blocking(dbx_core::update::system_proxy_url).await.map_err(|e| e.to_string())?;
if let Some(proxy_url) = proxy_url {
let proxy = reqwest::Proxy::all(&proxy_url).map_err(|e| format!("Invalid system proxy URL: {e}"))?;
builder = builder.proxy(proxy);
}
+20 -5
View File
@@ -8,7 +8,10 @@ use super::connection::AppState;
#[tauri::command]
pub async fn list_plugins(state: State<'_, Arc<AppState>>) -> Result<Vec<InstalledPlugin>, String> {
state.plugins.list_installed()
let root_dir = state.plugins.root_dir().to_path_buf();
tauri::async_runtime::spawn_blocking(move || dbx_core::plugins::PluginRegistry::new(root_dir).list_installed())
.await
.map_err(|err| err.to_string())?
}
#[tauri::command]
@@ -31,12 +34,18 @@ pub async fn install_jdbc_plugin_local(
#[tauri::command]
pub async fn uninstall_jdbc_plugin(state: State<'_, Arc<AppState>>) -> Result<JdbcPluginStatus, String> {
jdbc::uninstall_jdbc_plugin(state.plugins.root_dir())
let root_dir = state.plugins.root_dir().to_path_buf();
tauri::async_runtime::spawn_blocking(move || jdbc::uninstall_jdbc_plugin(&root_dir))
.await
.map_err(|err| err.to_string())?
}
#[tauri::command]
pub async fn list_jdbc_drivers(state: State<'_, Arc<AppState>>) -> Result<Vec<JdbcDriverInfo>, String> {
jdbc::list_jdbc_drivers(state.plugins.root_dir())
let root_dir = state.plugins.root_dir().to_path_buf();
tauri::async_runtime::spawn_blocking(move || jdbc::list_jdbc_drivers(&root_dir))
.await
.map_err(|err| err.to_string())?
}
#[tauri::command]
@@ -44,10 +53,16 @@ pub async fn import_jdbc_drivers(
state: State<'_, Arc<AppState>>,
paths: Vec<String>,
) -> Result<Vec<JdbcDriverInfo>, String> {
jdbc::import_jdbc_drivers(state.plugins.root_dir(), &paths)
let root_dir = state.plugins.root_dir().to_path_buf();
tauri::async_runtime::spawn_blocking(move || jdbc::import_jdbc_drivers(&root_dir, &paths))
.await
.map_err(|err| err.to_string())?
}
#[tauri::command]
pub async fn delete_jdbc_driver(state: State<'_, Arc<AppState>>, path: String) -> Result<Vec<JdbcDriverInfo>, String> {
jdbc::delete_jdbc_driver(state.plugins.root_dir(), &path)
let root_dir = state.plugins.root_dir().to_path_buf();
tauri::async_runtime::spawn_blocking(move || jdbc::delete_jdbc_driver(&root_dir, &path))
.await
.map_err(|err| err.to_string())?
}
+4 -2
View File
@@ -1,4 +1,6 @@
#[tauri::command]
pub fn list_system_fonts() -> Result<Vec<String>, String> {
Ok(dbx_core::jdbc::list_system_fonts())
pub async fn list_system_fonts() -> Result<Vec<String>, String> {
tauri::async_runtime::spawn_blocking(|| Ok(dbx_core::jdbc::list_system_fonts()))
.await
.map_err(|err| err.to_string())?
}
+18 -8
View File
@@ -11,19 +11,29 @@ pub struct QueryResultTextExportRequest {
}
impl QueryResultTextExportRequest {
fn data(&self) -> QueryResultTextExportData {
QueryResultTextExportData { columns: self.columns.clone(), rows: self.rows.clone() }
fn into_data(self) -> QueryResultTextExportData {
QueryResultTextExportData { columns: self.columns, rows: self.rows }
}
}
#[tauri::command]
pub fn export_query_result_json(request: QueryResultTextExportRequest) -> Result<(), String> {
let content = format_json(&request.data())?;
std::fs::write(&request.file_path, format!("\u{FEFF}{content}")).map_err(|err| err.to_string())
pub async fn export_query_result_json(request: QueryResultTextExportRequest) -> Result<(), String> {
tauri::async_runtime::spawn_blocking(move || {
let file_path = request.file_path.clone();
let content = format_json(&request.into_data())?;
std::fs::write(file_path, format!("\u{FEFF}{content}")).map_err(|err| err.to_string())
})
.await
.map_err(|err| err.to_string())?
}
#[tauri::command]
pub fn export_query_result_markdown(request: QueryResultTextExportRequest) -> Result<(), String> {
let content = format_markdown(&request.data());
std::fs::write(&request.file_path, format!("\u{FEFF}{content}")).map_err(|err| err.to_string())
pub async fn export_query_result_markdown(request: QueryResultTextExportRequest) -> Result<(), String> {
tauri::async_runtime::spawn_blocking(move || {
let file_path = request.file_path.clone();
let content = format_markdown(&request.into_data());
std::fs::write(file_path, format!("\u{FEFF}{content}")).map_err(|err| err.to_string())
})
.await
.map_err(|err| err.to_string())?
}
+2 -2
View File
@@ -10,6 +10,6 @@ pub async fn check_for_updates() -> Result<UpdateInfo, String> {
}
#[tauri::command]
pub fn get_system_proxy_url() -> Option<String> {
dbx_core::update::system_proxy_url()
pub async fn get_system_proxy_url() -> Option<String> {
tauri::async_runtime::spawn_blocking(dbx_core::update::system_proxy_url).await.ok().flatten()
}
+11 -7
View File
@@ -12,11 +12,15 @@ pub struct QueryResultXlsxExportRequest {
}
#[tauri::command]
pub fn export_query_result_xlsx(request: QueryResultXlsxExportRequest) -> Result<(), String> {
let workbook = build_xlsx_workbook(&XlsxWorksheetData {
sheet_name: request.sheet_name,
columns: request.columns,
rows: request.rows,
})?;
std::fs::write(&request.file_path, workbook).map_err(|err| err.to_string())
pub async fn export_query_result_xlsx(request: QueryResultXlsxExportRequest) -> Result<(), String> {
tauri::async_runtime::spawn_blocking(move || {
let workbook = build_xlsx_workbook(&XlsxWorksheetData {
sheet_name: request.sheet_name,
columns: request.columns,
rows: request.rows,
})?;
std::fs::write(&request.file_path, workbook).map_err(|err| err.to_string())
})
.await
.map_err(|err| err.to_string())?
}