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
Backend:
- `volume_broadcast.rs` (new): single `volumes-changed` event replaces multiple separate events. 150ms debounce, 2s timeout, cross-platform
- `mtp/watcher.rs`: auto-connects MTP devices on USB hotplug instead of letting the frontend orchestrate
- `volumes/mod.rs`: removed 5s `LOCATIONS_CACHE`. Added `get_fsid()` guard in `get_attached_volumes()` to skip volumes whose mount hasn't settled
- `volumes/watcher.rs`: `spawn_mount_settle_watcher()` polls fsid up to 10x at 1s intervals after a mount, re-broadcasts when metadata is ready
- `mtp/connection/mod.rs` + `directory_ops.rs`: emit `volumes-changed` after MTP connect/disconnect
- `volumes_linux/watcher.rs`: emit `volumes-changed` after mount/unmount
Frontend:
- `volume-store.svelte.ts` (new): single source of truth for volumes. Subscribes to `volumes-changed`, tracks `timedOut`/`refreshing`/`retryFailed`
- `VolumeBreadcrumb.svelte`: pure presentational — reads from store, no fetching or event listeners
- `DualPaneExplorer.svelte`: reads from store, removed ~6 `listVolumes()` calls and 4 event listeners
- `mtp-store.svelte.ts`: passive consumer — backend handles all MTP connection orchestration
- `DialogManager.svelte` + `TransferDialog.svelte`: read volumes from store instead of prop
- Removed dead `onMtpDeviceDetected`/`onMtpDeviceRemoved` code
- Fixed 3 pre-existing ESLint errors
- Updated tests for new passive model
Docs:
- Add info about cmdr_lib:: prefix for RUST_LOG
Copy file name to clipboardExpand all lines: apps/desktop/src-tauri/src/mtp/CLAUDE.md
+20-10Lines changed: 20 additions & 10 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -11,7 +11,7 @@ On Linux, users may need udev rules for USB device permissions (see `resources/9
11
11
|`mod.rs`| Re-exports public surface; module-level doc |
12
12
|`types.rs`|`MtpDeviceInfo`, `MtpStorageInfo` — camelCase JSON via `serde(rename_all)`|
13
13
|`discovery.rs`|`list_mtp_devices()` via `mtp_rs::MtpDevice::list_devices()`; device IDs formatted as `"mtp-{location_id}"`|
14
-
|`watcher.rs`|`start_mtp_watcher()` — nusb hotplug watcher; 500 ms delay on connect before re-checking; emits `mtp-device-detected` / `mtp-device-removed` Tauri events|
14
+
|`watcher.rs`|`start_mtp_watcher()` — nusb hotplug watcher; 500 ms delay on connect before re-checking; auto-connects detected devices via `MtpConnectionManager::connect()` and auto-disconnects removed ones|
15
15
|`macos_workaround.rs`| macOS-only (`#[cfg(target_os = "macos")]`). Detects `ptpcamerad` via `ioreg`; exposes `PTPCAMERAD_WORKAROUND_COMMAND` (a bash one-liner) |
|`connection/bulk_ops.rs`|`scan_for_copy()`, `download_recursive()`, `upload_recursive()` — use `Box::pin` for async recursion |
24
-
|`virtual_device.rs`| Virtual MTP device for E2E testing; creates backing dirs + registers device via `mtp-rs`. Gated behind `virtual-mtp` feature. |
24
+
|`virtual_device.rs`| Virtual MTP device for E2E testing; creates backing dirs + registers device via `mtp-rs`. Gated behind `virtual-mtp` feature. Run with: `cd apps/desktop && pnpm tauri dev -c src-tauri/tauri.dev.json --features virtual-mtp`|
25
25
26
26
## Architecture / data flow
27
27
@@ -30,22 +30,32 @@ USB plug-in
30
30
→ nusb hotplug event (watcher.rs)
31
31
→ 500 ms delay
32
32
→ list_mtp_devices() (discovery.rs)
33
-
→ emit mtp-device-detected
33
+
→ auto_connect_device() (watcher.rs)
34
+
→ MtpConnectionManager::connect()
35
+
→ open_device() via MtpDeviceBuilder
36
+
→ probe_write_capability() per storage
37
+
→ register MtpVolume in global VolumeManager
38
+
→ start_event_loop() per device
39
+
→ emit mtp-device-connected
40
+
→ broadcast::emit_volumes_changed()
34
41
35
-
Frontend calls connect_mtp_device
36
-
→ MtpConnectionManager::connect()
37
-
→ open_device() via MtpDeviceBuilder
38
-
→ probe_write_capability() per storage
39
-
→ register MtpVolume in global VolumeManager
40
-
→ start_event_loop() per device
41
-
→ emit mtp-device-connected
42
+
USB unplug
43
+
→ nusb hotplug event (watcher.rs)
44
+
→ auto_disconnect_device() (watcher.rs)
45
+
→ MtpConnectionManager::disconnect()
46
+
→ emit mtp-device-disconnected
47
+
→ broadcast::emit_volumes_changed()
42
48
43
49
Event loop (event_loop.rs)
44
50
→ device.next_event()
45
51
→ compute_diff()
46
52
→ emit directory-diff (same format as local file watching)
47
53
```
48
54
55
+
The frontend is a passive consumer: it subscribes to `volumes-changed` (for the volume picker)
56
+
and `mtp-device-connected`/`mtp-device-disconnected` (for device connection state tracking).
57
+
It never orchestrates MTP connections.
58
+
49
59
## Key patterns and gotchas
50
60
51
61
-**Device lock**: `Arc<tokio::sync::Mutex<MtpDevice>>` held for the entire USB I/O call (tokio's Mutex can be held across `.await` points, unlike `std::sync::Mutex`). Operations are serialized per device with a 30 s timeout (`MTP_TIMEOUT_SECS`). Holding the lock too long logs a warning.
0 commit comments