You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
|`util.rs`| Shared helpers |`blocking_with_timeout` — runs a blocking closure on the blocking thread pool with a timeout, returning a fallback value on timeout. Used by all filesystem-touching commands. |
|`volumes_linux.rs`| Volume management (Linux) | Same interface as `volumes.rs`, delegates to `volumes_linux` module |
@@ -35,14 +35,24 @@ immediately to business-logic modules. No significant logic lives here.
35
35
**Decision**: `blocking_with_timeout` for all filesystem-touching commands, not just read-only ones.
36
36
**Why**: `spawn_blocking` alone doesn't protect against hung NFS/SMB mounts where even a simple `path.exists()` can block indefinitely. The timeout wrapper (2s for reads, 5s for writes, 15s for trash, 30s for recursive scans) returns a fallback value (or error for `Result`-returning commands) instead of freezing the IPC thread or exhausting the blocking pool. The helper lives in `util.rs` so all command files can share it. Commands that already use `spawn_blocking` (P2 commands like `rename_file`, `move_to_trash`) wrap the existing `spawn_blocking` with `tokio::time::timeout` instead.
37
37
38
+
**Decision**: Timeout-aware return types (`TimedOut<T>` and `IpcError`) for all timeout-protected commands.
39
+
**Why**: The original `blocking_with_timeout` returned a fallback value indistinguishable from a real empty/none result. The frontend couldn't tell "no volumes mounted" from "timed out before listing volumes." Two wrapper types solve this:
40
+
-`TimedOut<T>` (`{ data: T, timedOut: bool }`) — for commands that don't return `Result` (collections, `Option`, `()`). Use `blocking_with_timeout_flag` to produce these.
41
+
-`IpcError` (`{ message: String, timedOut: bool }`) — for commands returning `Result<T, _>`. Use `blocking_result_with_timeout` or map `tokio::time::timeout` errors to `IpcError::timeout()`.
42
+
The frontend has matching TypeScript types in `$lib/tauri-commands/ipc-types.ts` (`TimedOut<T>`, `IpcError`, `isIpcError`, `getIpcErrorMessage`). The old `blocking_with_timeout` is kept for `path_exists` and other commands where timeout distinction isn't needed.
43
+
38
44
**Decision**: No `commands/ai.rs` file -- AI commands register directly from `ai::manager` and `ai::suggestions`.
39
45
**Why**: The AI subsystem has its own complex lifecycle (model loading, suggestion pipelines). Adding a thin wrapper in `commands/` would just be boilerplate forwarding. Registering directly keeps the AI command surface co-located with its implementation, which changes frequently.
40
46
41
47
## Key patterns and gotchas
42
48
43
49
-**No business logic here.** If you find yourself adding branching or data transformation, move it to the relevant subsystem module.
44
50
-**`spawn_blocking` for filesystem I/O.** All blocking operations in async commands are wrapped in `tokio::task::spawn_blocking`.
45
-
-**`blocking_with_timeout` for potentially slow I/O.** All filesystem-touching commands use either `blocking_with_timeout` (from `util.rs`) or `tokio::time::timeout` around `spawn_blocking`. Timeouts: 2s for reads, 5s for writes (`create_directory`, `rename_file`), 15s for trash (`move_to_trash` — macOS NSFileManager is slow on cold start), 30s for recursive scans (`scan_volume_for_copy`, `scan_volume_for_conflicts`). The helper returns a fallback value on timeout or `JoinError`.
51
+
-**Timeout-protected I/O with distinguishable results.** All filesystem-touching commands use timeout wrappers from `util.rs`. Timeouts: 2s for reads, 5s for writes (`create_directory`, `rename_file`), 15s for trash (`move_to_trash`), 30s for recursive scans. Three helpers:
52
+
-`blocking_with_timeout(duration, fallback, closure)` — returns raw `T`, timeout indistinguishable from fallback. Used only where distinction isn't needed (like `path_exists`).
53
+
-`blocking_with_timeout_flag(duration, fallback, closure)` → `TimedOut<T>` — for commands returning `Vec`, `HashMap`, `Option`, or `()`. Frontend unwraps `.data` and can check `.timedOut`.
54
+
-`blocking_result_with_timeout(duration, closure)` → `Result<T, IpcError>` — for commands that already return `Result`. Timeout becomes `Err(IpcError { message, timedOut: true })`.
55
+
- For P2 commands using raw `tokio::time::timeout`, map the `Elapsed` error to `IpcError::timeout()` and other errors to `IpcError::from_err(msg)`.
46
56
-**`expand_tilde`** is applied conditionally: for `list_directory` it's gated on `volume_id == "root"`, but for write operations (copy, move, delete, scan preview) it's always applied. MTP and network volume paths must never be tilde-expanded.
47
57
-**AI commands** are registered directly from `ai::manager` and `ai::suggestions` — there is no `commands/ai.rs` file.
48
58
-**Platform gates.**`volumes` is macOS-only; `mtp` and `network` are macOS+Linux; `volumes_linux` is Linux-only. Individual functions also use `#[cfg]` where behaviour differs (e.g., `sync_status`).
0 commit comments