Skip to content

Commit 7815d0f

Browse files
committed
Fix live file count during large folder loading
The listing-progress event was defined but never emitted, so opening 100k+ file folders showed no incremental count. - Add list_directory_with_progress to Volume trait - LocalPosixVolume reports stat loop progress every 200ms - streaming.rs now emits listing-progress Tauri events - Frontend handler was already wired up, no changes needed
1 parent 62d304a commit 7815d0f

6 files changed

Lines changed: 65 additions & 7 deletions

File tree

apps/desktop/src-tauri/src/file_system/listing/CLAUDE.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ Frontend Backend
6666
**Decision**: Three-stage progress: opening → progress → read-complete → complete
6767
**Why**: Gives user fine-grained feedback:
6868
- `listing-opening`: "About to start slow I/O" (for network folders)
69-
- `listing-progress`: "Loaded N files..." (every 500ms)
69+
- `listing-progress`: "Loaded N files..." (every 200ms, via `list_directory_core_with_progress`)
7070
- `listing-read-complete`: "All files read, sorting now"
7171
- `listing-complete`: "Ready to render"
7272

apps/desktop/src-tauri/src/file_system/listing/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ pub use operations::{
1414
ListingStartResult, ListingStats, ResortResult, find_file_index, get_file_at, get_file_range, get_listing_stats,
1515
get_max_filename_width, get_total_count, list_directory_end, list_directory_start_with_volume, resort_listing,
1616
};
17-
pub use reading::{get_single_entry, list_directory_core};
17+
pub use reading::{get_single_entry, list_directory_core, list_directory_core_with_progress};
1818
pub use sorting::{DirectorySortMode, SortColumn, SortOrder};
1919
pub use streaming::{StreamingListingStartResult, cancel_listing, list_directory_start_streaming};
2020

apps/desktop/src-tauri/src/file_system/listing/reading.rs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,25 @@ pub fn list_directory(path: &Path) -> Result<Vec<FileEntry>, std::io::Error> {
6363
///
6464
/// Use `get_extended_metadata_batch()` to fetch extended metadata later.
6565
pub fn list_directory_core(path: &Path) -> Result<Vec<FileEntry>, std::io::Error> {
66+
list_directory_core_impl(path, None)
67+
}
68+
69+
/// Like `list_directory_core`, but calls `on_progress(loaded_count)` every ~200ms
70+
/// during the stat loop so callers can report incremental progress.
71+
pub fn list_directory_core_with_progress(
72+
path: &Path,
73+
on_progress: &dyn Fn(usize),
74+
) -> Result<Vec<FileEntry>, std::io::Error> {
75+
list_directory_core_impl(path, Some(on_progress))
76+
}
77+
78+
/// Interval between progress callbacks during the stat loop.
79+
const PROGRESS_REPORT_INTERVAL: std::time::Duration = std::time::Duration::from_millis(200);
80+
81+
fn list_directory_core_impl(
82+
path: &Path,
83+
on_progress: Option<&dyn Fn(usize)>,
84+
) -> Result<Vec<FileEntry>, std::io::Error> {
6685
benchmark::log_event("list_directory_core START");
6786
let overall_start = std::time::Instant::now();
6887
let mut entries = Vec::new();
@@ -74,6 +93,7 @@ pub fn list_directory_core(path: &Path) -> Result<Vec<FileEntry>, std::io::Error
7493
benchmark::log_event_value("readdir END, count", dir_entries.len());
7594

7695
benchmark::log_event("stat_loop START");
96+
let mut last_progress = std::time::Instant::now();
7797
for entry in dir_entries {
7898
let entry = entry?;
7999
match process_dir_entry(&entry) {
@@ -107,6 +127,13 @@ pub fn list_directory_core(path: &Path) -> Result<Vec<FileEntry>, std::io::Error
107127
});
108128
}
109129
}
130+
131+
if let Some(cb) = on_progress
132+
&& last_progress.elapsed() >= PROGRESS_REPORT_INTERVAL
133+
{
134+
cb(entries.len());
135+
last_progress = std::time::Instant::now();
136+
}
110137
}
111138
benchmark::log_event_value("stat_loop END, entries", entries.len());
112139

apps/desktop/src-tauri/src/file_system/listing/streaming.rs

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,6 @@
33
//! Provides non-blocking directory reading with progress events and cancellation.
44
//! The implementation spawns background tasks and emits Tauri events.
55
6-
#![allow(dead_code, reason = "ListingProgressEvent is part of public API for future use")]
7-
86
use serde::{Deserialize, Serialize};
97
use std::collections::HashMap;
108
use std::path::Path;
@@ -111,7 +109,7 @@ pub(crate) static STREAMING_STATE: LazyLock<RwLock<HashMap<String, Arc<Streaming
111109
/// Starts a streaming directory listing that returns immediately and emits progress events.
112110
///
113111
/// This is non-blocking - the actual directory reading happens in a background task.
114-
/// Progress is reported via Tauri events every 500ms.
112+
/// Progress is reported via Tauri events every ~200ms.
115113
#[allow(
116114
clippy::too_many_arguments,
117115
reason = "Streaming operation requires many state parameters"
@@ -270,9 +268,21 @@ fn read_directory_with_progress(
270268
let read_start = std::time::Instant::now();
271269
let path_for_thread = path.to_path_buf();
272270
let (tx, rx) = mpsc::channel();
271+
let app_for_progress = app.clone();
272+
let listing_id_for_progress = listing_id.to_string();
273273

274274
std::thread::spawn(move || {
275-
let result = volume.list_directory(&path_for_thread);
275+
let on_progress = |loaded_count: usize| {
276+
use tauri::Emitter;
277+
let _ = app_for_progress.emit(
278+
"listing-progress",
279+
ListingProgressEvent {
280+
listing_id: listing_id_for_progress.clone(),
281+
loaded_count,
282+
},
283+
);
284+
};
285+
let result = volume.list_directory_with_progress(&path_for_thread, &on_progress);
276286
let _ = tx.send(result);
277287
});
278288

apps/desktop/src-tauri/src/file_system/volume/local_posix.rs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
use super::{
44
CopyScanResult, ScanConflict, SourceItemInfo, SpaceInfo, Volume, VolumeError, VolumeScanner, VolumeWatcher,
55
};
6-
use crate::file_system::listing::{FileEntry, get_single_entry, list_directory_core};
6+
use crate::file_system::listing::{FileEntry, get_single_entry, list_directory_core, list_directory_core_with_progress};
77
use crate::indexing::scanner::{self, ScanConfig, ScanError, ScanHandle, ScanSummary};
88
use crate::indexing::watcher::{DriveWatcher, FsChangeEvent, WatcherError};
99
use crate::indexing::writer::IndexWriter;
@@ -88,6 +88,15 @@ impl Volume for LocalPosixVolume {
8888
list_directory_core(&abs_path).map_err(VolumeError::from)
8989
}
9090

91+
fn list_directory_with_progress(
92+
&self,
93+
path: &Path,
94+
on_progress: &dyn Fn(usize),
95+
) -> Result<Vec<FileEntry>, VolumeError> {
96+
let abs_path = self.resolve(path);
97+
list_directory_core_with_progress(&abs_path, on_progress).map_err(VolumeError::from)
98+
}
99+
91100
fn get_metadata(&self, path: &Path) -> Result<FileEntry, VolumeError> {
92101
let abs_path = self.resolve(path);
93102
get_single_entry(&abs_path).map_err(VolumeError::from)

apps/desktop/src-tauri/src/file_system/volume/mod.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,18 @@ pub trait Volume: Send + Sync {
181181
/// Returns entries sorted with directories first, then files, both alphabetically.
182182
fn list_directory(&self, path: &Path) -> Result<Vec<FileEntry>, VolumeError>;
183183

184+
/// Like `list_directory`, but calls `on_progress(loaded_count)` periodically
185+
/// during the stat loop so callers can report incremental progress to the UI.
186+
///
187+
/// Default implementation delegates to `list_directory` with no incremental updates.
188+
fn list_directory_with_progress(
189+
&self,
190+
path: &Path,
191+
_on_progress: &dyn Fn(usize),
192+
) -> Result<Vec<FileEntry>, VolumeError> {
193+
self.list_directory(path)
194+
}
195+
184196
/// Gets metadata for a single path (relative to volume root).
185197
fn get_metadata(&self, path: &Path) -> Result<FileEntry, VolumeError>;
186198

0 commit comments

Comments
 (0)