|
| 1 | +# File loading |
| 2 | + |
| 3 | +How directory listings are loaded, from user action to rendered list. |
| 4 | + |
| 5 | +## Overview |
| 6 | + |
| 7 | +When a user navigates to a directory, the app: |
| 8 | + |
| 9 | +1. Reads the directory contents from disk (Rust) |
| 10 | +2. Transfers the data to the frontend (Tauri IPC with JSON) |
| 11 | +3. Renders the file list progressively (Svelte) |
| 12 | + |
| 13 | +For large directories (50k+ files), this uses **cursor-based pagination** to show the first chunk immediately while |
| 14 | +loading the rest in the background. |
| 15 | + |
| 16 | +## Architecture diagram |
| 17 | + |
| 18 | +```mermaid |
| 19 | +sequenceDiagram |
| 20 | + participant User |
| 21 | + participant FilePane |
| 22 | + participant TauriIPC |
| 23 | + participant RustBackend |
| 24 | + participant FileSystem |
| 25 | +
|
| 26 | + User->>FilePane: Navigate to directory |
| 27 | + FilePane->>TauriIPC: listDirectoryStartSession(path, 5000) |
| 28 | + TauriIPC->>RustBackend: list_directory_start_session |
| 29 | + RustBackend->>FileSystem: Read directory |
| 30 | + FileSystem-->>RustBackend: All entries |
| 31 | + RustBackend->>RustBackend: Sort, cache in session |
| 32 | + RustBackend-->>TauriIPC: First 5000 entries + sessionId |
| 33 | + TauriIPC-->>FilePane: JSON response |
| 34 | + FilePane->>User: Render first chunk immediately |
| 35 | +
|
| 36 | + loop While hasMore |
| 37 | + FilePane->>TauriIPC: listDirectoryNextChunk(sessionId, 5000) |
| 38 | + TauriIPC->>RustBackend: list_directory_next_chunk |
| 39 | + RustBackend-->>TauriIPC: Next chunk from cache |
| 40 | + TauriIPC-->>FilePane: JSON response |
| 41 | + FilePane->>User: Append entries |
| 42 | + end |
| 43 | +
|
| 44 | + FilePane->>TauriIPC: listDirectoryEndSession(sessionId) |
| 45 | +``` |
| 46 | + |
| 47 | +## Data flow layers |
| 48 | + |
| 49 | +### 1. Frontend: FilePane.svelte |
| 50 | + |
| 51 | +The |
| 52 | +[FilePane](file:///Users/veszelovszki/Library/CloudStorage/Dropbox/projects-git/vdavid/rusty-commander/src/lib/file-explorer/FilePane.svelte) |
| 53 | +component orchestrates directory loading. |
| 54 | + |
| 55 | +**Key function:** `loadDirectory(path, selectName?)` |
| 56 | + |
| 57 | +1. Shows loading state |
| 58 | +2. Calls `listDirectoryStartSession()` to get first chunk |
| 59 | +3. Renders first chunk immediately |
| 60 | +4. Calls `listDirectoryNextChunk()` in a loop for remaining data |
| 61 | +5. Uses `requestAnimationFrame()` between chunks to keep UI responsive |
| 62 | +6. Calls `listDirectoryEndSession()` to clean up |
| 63 | + |
| 64 | +**Reactivity optimization:** The file list is stored in a plain array (`allFilesRaw`) rather than Svelte's `$state` to |
| 65 | +avoid the overhead of making 50k objects reactive. A simple counter (`filesVersion`) triggers updates. |
| 66 | + |
| 67 | +### 2. IPC layer: tauri-commands.ts |
| 68 | + |
| 69 | +The |
| 70 | +[tauri-commands](file:///Users/veszelovszki/Library/CloudStorage/Dropbox/projects-git/vdavid/rusty-commander/src/lib/tauri-commands.ts) |
| 71 | +module provides typed wrappers for Rust commands. |
| 72 | + |
| 73 | +**Session API functions:** |
| 74 | + |
| 75 | +- `listDirectoryStartSession(path, chunkSize)` → `SessionStartResult` |
| 76 | +- `listDirectoryNextChunk(sessionId, chunkSize)` → `ChunkNextResult` |
| 77 | +- `listDirectoryEndSession(sessionId)` → void |
| 78 | + |
| 79 | +For serialization format rationale, see |
| 80 | +[ADR 007: Use JSON for Tauri IPC](file:///Users/veszelovszki/Library/CloudStorage/Dropbox/projects-git/vdavid/rusty-commander/docs/adr/007-json-for-ipc.md). |
| 81 | + |
| 82 | +### 3. Rust commands: commands/file_system.rs |
| 83 | + |
| 84 | +The |
| 85 | +[file_system commands](file:///Users/veszelovszki/Library/CloudStorage/Dropbox/projects-git/vdavid/rusty-commander/src-tauri/src/commands/file_system.rs) |
| 86 | +expose Tauri commands that call the file system operations. |
| 87 | + |
| 88 | +**Commands:** |
| 89 | + |
| 90 | +- `list_directory_start_session` - Starts a session, reads directory, caches entries, returns first chunk |
| 91 | +- `list_directory_next_chunk` - Returns next chunk from cache |
| 92 | +- `list_directory_end_session` - Cleans up the session cache |
| 93 | + |
| 94 | +### 4. File system operations: file_system/operations.rs |
| 95 | + |
| 96 | +The |
| 97 | +[operations module](file:///Users/veszelovszki/Library/CloudStorage/Dropbox/projects-git/vdavid/rusty-commander/src-tauri/src/file_system/operations.rs) |
| 98 | +contains the core logic. |
| 99 | + |
| 100 | +**Session cache:** A static `HashMap<String, CachedDirectory>` stores directory listings keyed by session ID. Sessions |
| 101 | +expire after 60 seconds to prevent memory leaks. |
| 102 | + |
| 103 | +**Key function:** `list_directory(path)` |
| 104 | + |
| 105 | +1. Reads directory entries with `fs::read_dir()` |
| 106 | +2. Extracts metadata (size, permissions, timestamps) |
| 107 | +3. Resolves owner/group names (with caching) |
| 108 | +4. Generates icon IDs |
| 109 | +5. Sorts: directories first, then files, both alphabetically |
| 110 | + |
| 111 | +## Latency breakdown (50k files) |
| 112 | + |
| 113 | +Based on benchmarks on a MacBook Pro M1: |
| 114 | + |
| 115 | +| Step | Time | Notes | |
| 116 | +| ----------------------- | ----------- | --------------------------------- | |
| 117 | +| Rust `list_directory()` | ~300ms | Disk I/O + metadata extraction | |
| 118 | +| JSON serialization | ~18ms | 17 MB payload | |
| 119 | +| IPC transfer | ~1.4s | WebView JSON parsing | |
| 120 | +| Svelte reactivity | ~50ms | With optimized non-reactive array | |
| 121 | +| **First chunk visible** | **~350ms** | User sees files quickly | |
| 122 | +| **Full list loaded** | **~2-2.5s** | Competitive with Commander One | |
| 123 | + |
| 124 | +## Configuration |
| 125 | + |
| 126 | +**Chunk size:** 5000 entries (defined in `FilePane.svelte`) |
| 127 | + |
| 128 | +This balances: |
| 129 | + |
| 130 | +- Time to first content (smaller = faster) |
| 131 | +- Number of IPC calls (larger = fewer calls) |
| 132 | +- Memory overhead (larger = more cached) |
| 133 | + |
| 134 | +## Key design decisions |
| 135 | + |
| 136 | +1. **JSON over MessagePack** - Native JSON is faster through Tauri's IPC. See |
| 137 | + [ADR 007](file:///Users/veszelovszki/Library/CloudStorage/Dropbox/projects-git/vdavid/rusty-commander/docs/adr/007-json-for-ipc.md). |
| 138 | + |
| 139 | +2. **Session-based caching** - Directory is read once, chunks served from memory. Avoids O(n²) re-reading. |
| 140 | + |
| 141 | +3. **Non-reactive file array** - Svelte's `$state` on 50k objects caused ~9.5s overhead. Using a plain array with manual |
| 142 | + reactivity trigger reduced this to ~50ms. |
| 143 | + |
| 144 | +4. **Progressive rendering** - First chunk appears in ~350ms. Remaining chunks load without blocking the UI. |
| 145 | + |
| 146 | +## Future improvements |
| 147 | + |
| 148 | +- **Virtual scrolling** - Only render visible rows (phase 4) |
| 149 | +- **Lazy metadata loading** - Load only names first, fetch metadata on demand |
| 150 | +- **File system watcher** - Auto-refresh on external changes |
| 151 | +- **Cancellation** - Cancel in-progress directory reads when navigating away |
0 commit comments