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
Very exciting! And it works!
- Add `SmbVolume` implementing `Volume` trait, backed by direct smb2 protocol I/O
- "Sneaky mount": OS mount stays for Finder/Terminal compat, Cmdr ops use smb2 (~4x faster)
- `Mutex<Option<(SmbClient, Tree)>>` with `Handle::block_on` bridging (MtpVolume pattern)
- `ConnectionState` (Direct/OsMount/Disconnected) with atomic lock-free reads
- `local_path()` returns `None` so copy strategy uses smb2 streaming, not OS mount
- Add `on_unmount()` to `Volume` trait for cleanup on external eject
- Add `register_if_absent()` to `VolumeManager` to prevent watcher overwriting SmbVolume
- Watcher calls `on_unmount()` before unregistering, uses `register_if_absent` for mounts
- Mount command establishes parallel smb2 session, registers `SmbVolume` (best-effort)
- 25 unit tests for type mappings, error classification, state machine, path conversion
Copy file name to clipboardExpand all lines: apps/desktop/src-tauri/src/file_system/volume/CLAUDE.md
+17-1Lines changed: 17 additions & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -14,6 +14,7 @@ Every file system operation (listing, copy, rename, delete, indexing, watching)
14
14
|`manager.rs`|`VolumeManager` — thread-safe `RwLock<HashMap>` registry; supports a default volume |
15
15
|`local_posix.rs`|`LocalPosixVolume` — real filesystem; delegates listing to `file_system::listing`, indexing to `indexing::scanner`, watching to `indexing::watcher` (FSEvents), copy scanning via `walkdir`. Uses `libc::statvfs` FFI for space info. |
16
16
|`mtp.rs`|`MtpVolume` — MTP device storage; synchronous `Volume` trait bridged to async MTP calls via `tokio::runtime::Handle::block_on`. Gated with `#[cfg(any(target_os = "macos", target_os = "linux"))]`. |
|`in_memory.rs`|`InMemoryVolume` — `RwLock<HashMap>` store for tests; also used for stress tests (`with_file_count`) |
18
19
19
20
## Architecture
@@ -23,6 +24,7 @@ VolumeManager (registry)
23
24
└─ Arc<dyn Volume>
24
25
├─ LocalPosixVolume → real FS, FSEvents watcher, jwalk scanner
25
26
├─ MtpVolume → async MTP ops via block_on (spawn_blocking context)
27
+
├─ SmbVolume → async smb2 ops via block_on (direct protocol, not OS mount)
26
28
└─ InMemoryVolume → HashMap, test/stress use only
27
29
```
28
30
@@ -35,7 +37,8 @@ Optional methods default to `Err(VolumeError::NotSupported)` or `false`, so new
35
37
-`supports_watching()` — enables the `notify`-based *listing* file watcher in `operations.rs` (separate from the `VolumeWatcher` trait used for drive indexing). `MtpVolume` returns `false` (it has its own USB event loop).
36
38
-`supports_export()` — enables copy/move UI. Both local and MTP return `true`.
37
39
-`supports_streaming()` — enables chunked MTP-to-MTP transfers. Only `MtpVolume` returns `true`.
38
-
-`local_path()` — returns `Some` only for local volumes; allows `copyfile(2)` fast-path in copy operations.
40
+
-`local_path()` — returns `Some` only for local volumes; allows `copyfile(2)` fast-path in copy operations. `SmbVolume` returns `None` so copies go through smb2 instead of the slow OS mount.
41
+
-`on_unmount()` — lifecycle hook called before unregistration. `SmbVolume` uses it to disconnect its smb2 session. Default is no-op.
39
42
-`scanner()` / `watcher()` — drive indexing hooks; `None` by default.
40
43
41
44
## Path handling gotchas
@@ -63,6 +66,9 @@ Optional methods default to `Err(VolumeError::NotSupported)` or `false`, so new
63
66
**Decision**: `VolumeManager` uses `RwLock<HashMap>` (not `DashMap` or `Mutex`)
64
67
**Why**: Volume registration/unregistration is rare (mount/unmount events); reads are frequent (every file operation resolves a volume). `RwLock` gives concurrent read access without pulling in an extra dependency. `DashMap` would work but is heavier than needed for a registry that rarely exceeds ~10 entries.
65
68
69
+
**Decision**: `VolumeManager::register_if_absent` for watcher registrations
70
+
**Why**: When the mount flow pre-registers an `SmbVolume`, the FSEvents watcher would overwrite it with a `LocalPosixVolume` via `register`. `register_if_absent` is a no-op if a volume is already registered, preserving the `SmbVolume`. The existing `register` (overwrite) is kept for explicit replacement (like SmbVolume replacing itself on reconnect).
71
+
66
72
**Decision**: `MtpVolume` bridges sync `Volume` trait to async MTP calls via `Handle::block_on`
67
73
**Why**: The `Volume` trait is synchronous because local filesystem ops are blocking and shouldn't touch the async executor. MTP operations are inherently async (USB bulk transfers), so `block_on` bridges the gap. This is safe because MTP methods are always called from `spawn_blocking` contexts (separate OS thread pool), avoiding nested-runtime panics.
68
74
@@ -83,10 +89,20 @@ Optional methods default to `Err(VolumeError::NotSupported)` or `false`, so new
**Why**: MTP has no single-file stat call — you must list the parent directory and search for the entry by name. The listing pipeline doesn't use `get_metadata` during normal browsing (it gets metadata from `list_directory` results), so implementing it would add an expensive round-trip for a code path that's currently unused.
85
91
92
+
**Decision**: `SmbVolume` uses `Mutex<Option<(SmbClient, Tree)>>`, not `RwLock`
93
+
**Why**: Every `SmbClient` method takes `&mut self` — there is no read-only access path. An `RwLock` where you only ever take write locks is strictly worse than a `Mutex` (higher overhead). The `Option` allows graceful cleanup on disconnect (set to `None`).
**Why**: `local_path()` is checked in `volume_copy.rs` to decide whether to use native OS copy APIs. If SmbVolume returned `Some(mount_path)`, copies would go through the slow OS mount — exactly what we're trying to avoid. `root()` still returns the mount path for frontend path resolution.
97
+
98
+
**Decision**: `on_unmount()` trait method instead of `Any` downcasting
99
+
**Why**: Avoids runtime type checking, extensible for future volume types (S3, FTP might also need cleanup), consistent with the trait's design of optional methods with default no-ops.
100
+
86
101
## Testing
87
102
88
103
-`in_memory_test.rs` — unit tests for `InMemoryVolume` (CRUD, sorting, concurrency, stress 50k entries)
-`mtp.rs` inline tests — path conversion and capability flags (no device needed)
108
+
-`smb.rs` inline tests — type mapping (DirectoryEntry→FileEntry, FsInfo→SpaceInfo, Error→VolumeError), connection state transitions, path conversion, capability flags (no server needed)
0 commit comments