diff --git a/src-tauri/src/db.rs b/src-tauri/src/db.rs
index 1e849c8b..ed666585 100644
--- a/src-tauri/src/db.rs
+++ b/src-tauri/src/db.rs
@@ -46,10 +46,8 @@ enum DbError {
PasswordMalformed,
#[error("sqlite has no password")]
SqliteNoPassword,
- /// The transport::blocking worker thread panicked (stringified JoinError).
- /// The one explicit stringly bridge; converted only at that call site.
- #[error("{0}")]
- Join(String),
+ #[error(transparent)]
+ Join(#[from] tauri::Error),
}
/// Single source of the over-budget check, so the streaming query and its test
@@ -251,9 +249,8 @@ fn password_for_endpoint(blob: Option<&str>, endpoint: &str) -> Result Result , DbError> {
let name = password_cred(profile_id);
- let blob = blocking(move || credentials::read_password(&name))
- .await
- .map_err(DbError::Join)??;
+ let blob =
+ tauri::async_runtime::spawn_blocking(move || credentials::read_password(&name)).await??;
password_for_endpoint(blob.as_deref(), endpoint)
}
diff --git a/src-tauri/src/excel.rs b/src-tauri/src/excel.rs
index 7f38d091..8f1f389e 100644
--- a/src-tauri/src/excel.rs
+++ b/src-tauri/src/excel.rs
@@ -8,7 +8,6 @@ use std::time::Duration;
use calamine::{open_workbook_auto, Data, Reader};
use crate::dataset::{Rows, ROW_CAP};
-use crate::transport::blocking;
/// Same budget as the db queries. Cannot cancel the blocking parse (calamine
/// materializes the whole sheet), but bounds what the user waits on.
@@ -44,10 +43,8 @@ enum ExcelError {
TooLargeUncompressed,
#[error("empty sheet: {0}")]
EmptySheet(String),
- /// The transport::blocking worker thread panicked (stringified JoinError).
- /// The one explicit stringly bridge; converted only in timed_read.
- #[error("{0}")]
- Join(String),
+ #[error(transparent)]
+ Join(#[from] tauri::Error),
}
fn check_size(path: &str) -> Result<(), ExcelError> {
@@ -72,12 +69,12 @@ fn check_size(path: &str) -> Result<(), ExcelError> {
}
async fn timed_read(
- work: impl std::future::Future, String>>,
+ work: impl std::future::Future, tauri::Error>>,
) -> Result {
let parsed = tokio::time::timeout(READ_TIMEOUT, work)
.await
.map_err(|_| ExcelError::Timeout)?;
- parsed.map_err(ExcelError::Join)?
+ parsed?
}
// calamine parses under the release panic="abort" profile, so a panic on a
@@ -85,56 +82,60 @@ async fn timed_read(
// check_size + the dialog-only pick keep realistic inputs benign.
#[tauri::command]
pub async fn excel_list_sheets(path: String) -> Result, String> {
- timed_read(blocking(move || -> Result, ExcelError> {
- check_size(&path)?;
- let workbook = open_workbook_auto(&path)?;
- Ok(workbook.sheet_names().to_vec())
- }))
+ timed_read(tauri::async_runtime::spawn_blocking(
+ move || -> Result, ExcelError> {
+ check_size(&path)?;
+ let workbook = open_workbook_auto(&path)?;
+ Ok(workbook.sheet_names().to_vec())
+ },
+ ))
.await
.map_err(|e| e.to_string())
}
#[tauri::command]
pub async fn excel_fetch(path: String, sheet: String) -> Result {
- timed_read(blocking(move || -> Result {
- check_size(&path)?;
- let mut workbook = open_workbook_auto(&path)?;
- let range = workbook.worksheet_range(&sheet)?;
- // The range starts at the first used cell; offset so a synthesized name
- // matches the sheet's real column number.
- let col_offset = range.start().map_or(0, |(_, c)| c as usize);
- let mut rows_iter = range.rows();
- let Some(header_row) = rows_iter.next() else {
- return Err(ExcelError::EmptySheet(sheet.clone()));
- };
- let headers: Vec = header_row
- .iter()
- .enumerate()
- // Blank header cells still need a stable, mappable name.
- .map(|(i, c)| {
- let name = cell_text(c);
- if name.is_empty() {
- format!("Column {}", col_offset + i + 1)
- } else {
- name
+ timed_read(tauri::async_runtime::spawn_blocking(
+ move || -> Result {
+ check_size(&path)?;
+ let mut workbook = open_workbook_auto(&path)?;
+ let range = workbook.worksheet_range(&sheet)?;
+ // The range starts at the first used cell; offset so a synthesized name
+ // matches the sheet's real column number.
+ let col_offset = range.start().map_or(0, |(_, c)| c as usize);
+ let mut rows_iter = range.rows();
+ let Some(header_row) = rows_iter.next() else {
+ return Err(ExcelError::EmptySheet(sheet));
+ };
+ let headers: Vec = header_row
+ .iter()
+ .enumerate()
+ // Blank header cells still need a stable, mappable name.
+ .map(|(i, c)| {
+ let name = cell_text(c);
+ if name.is_empty() {
+ format!("Column {}", col_offset + i + 1)
+ } else {
+ name
+ }
+ })
+ .collect();
+ let mut truncated = false;
+ let mut rows: Vec> = Vec::new();
+ for row in rows_iter {
+ if rows.len() >= ROW_CAP {
+ truncated = true;
+ break;
}
- })
- .collect();
- let mut truncated = false;
- let mut rows: Vec> = Vec::new();
- for row in rows_iter {
- if rows.len() >= ROW_CAP {
- truncated = true;
- break;
+ rows.push(row.iter().map(cell_text).collect());
}
- rows.push(row.iter().map(cell_text).collect());
- }
- Ok(Rows {
- headers,
- rows,
- truncated,
- })
- }))
+ Ok(Rows {
+ headers,
+ rows,
+ truncated,
+ })
+ },
+ ))
.await
.map_err(|e| e.to_string())
}
diff --git a/src-tauri/src/transport.rs b/src-tauri/src/transport.rs
index 1f74a865..b937dde6 100644
--- a/src-tauri/src/transport.rs
+++ b/src-tauri/src/transport.rs
@@ -34,8 +34,9 @@ pub(crate) fn check_payload(len: usize) -> Result<(), String> {
Ok(())
}
-/// Run a blocking closure on the runtime's blocking pool, flattening the
-/// `JoinError` into a String so every blocking command drops that boilerplate.
+/// Run a blocking closure on the pool, flattening the `JoinError` to a String
+/// for the String-returning command edges. Typed callers (db/excel) use
+/// `spawn_blocking` directly so `#[from] tauri::Error` folds the panic in.
pub(crate) async fn blocking(f: F) -> Result
where
F: FnOnce() -> T + Send + 'static,