Skip to content

Commit 57ef103

Browse files
committed
Bugfix: refresh_listing always re-reads local volumes
M1's short-circuit ("watcher-backed listing? skip the re-read") was right for MTP and SMB — a 1k-entry MTP folder takes ~17 s and holds the USB session, so an FE post-transfer refresh wedged the next user op. But for local volumes the short-circuit broke the post-transfer pane update: FSEvents on macOS races with the `/tmp` ↔ `/private/tmp` symlink boundary and with the fixture-recreate cycle E2E tests run in `beforeEach`, so the cache wasn't reliably fresh at the moment `refresh_listing` landed, and the FE's `refreshPanesAfterTransfer` had no recovery path. Symptom: copy MTP → local completed on disk and emitted "Copy complete", but the destination pane stayed empty (frame-by-frame inspection of the Playwright recording confirmed it). Gate the short-circuit on `volume.local_path().is_none()` so MTP/SMB keep their fast path and local volumes always re-read. A local `list_directory` is sub-ms, so paying for it on an explicit refresh is the right trade — and that's exactly what the FE/user asked for when they called `refresh`. Verified: the 3 previously-failing `MTP → local` Playwright tests (`mtp.spec.ts:333`, `:699`, `:1083`) now pass in 3.5-4 s each.
1 parent ecb495f commit 57ef103

1 file changed

Lines changed: 20 additions & 12 deletions

File tree

  • apps/desktop/src-tauri/src/commands/file_system

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

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -323,31 +323,39 @@ pub fn list_directory_end(listing_id: String) {
323323
/// Force a re-read of a watched directory listing, emitting any diff.
324324
/// Used after write operations (move) when the file watcher may not fire promptly.
325325
///
326-
/// Short-circuits when the listing's volume reports `listing_is_watched(path) == true`.
327-
/// In that case the cache is already being kept fresh by the volume's `notify_mutation`
328-
/// pipeline (per-file `Added` / `Removed` / `Modified` events patched into `LISTING_CACHE`
329-
/// after every successful mutation), so a full `list_directory` re-read is redundant
330-
/// and costs a lot on slow backends: a 1k-entry MTP folder takes ~17 s and holds the
331-
/// USB session, colliding with the user's next op. Returns `TimedOut { data: (),
332-
/// timed_out: false }` immediately when the short-circuit fires, matching the
333-
/// `timed_out: false` shape the FE already handles on the fast-path.
326+
/// Short-circuits when the listing lives on a **non-local** volume that reports
327+
/// `listing_is_watched(path) == true`. There the cache is being kept fresh by the
328+
/// volume's `notify_mutation` pipeline (per-file `Added` / `Removed` / `Modified`
329+
/// events patched into `LISTING_CACHE` after every successful mutation), so a full
330+
/// `list_directory` re-read is pure redundancy and costs a lot on slow backends:
331+
/// a 1k-entry MTP folder takes ~17 s and holds the USB session, colliding with
332+
/// the user's next op.
333+
///
334+
/// Local volumes always re-read. FSEvents on macOS races with `/tmp` ↔ `/private/tmp`
335+
/// symlink resolution and with the fixture-recreate beforeEach loops we run in E2E,
336+
/// so the cache is not reliably fresh at the moment `refresh_listing` lands — and
337+
/// a local `list_directory` is sub-millisecond, so paying for a re-read is the
338+
/// right trade. The whole point of the user/FE calling `refresh` is "I think the
339+
/// cache might be stale, please update it"; on local FS that's exactly what we do.
340+
///
341+
/// Returns `TimedOut { data: (), timed_out: false }` immediately when the
342+
/// short-circuit fires, matching the `timed_out: false` shape the FE already
343+
/// handles on the fast-path.
334344
///
335345
/// Note: only this user-triggered command is gated. The FSEvents/SMB/MTP watcher
336346
/// callbacks call `handle_directory_change` directly and are intentionally left
337347
/// alone — they're how the cache stays in sync in the first place.
338348
#[tauri::command]
339349
#[specta::specta]
340350
pub async fn refresh_listing(listing_id: String) -> TimedOut<()> {
341-
// Short-circuit on watcher-backed listings: the volume's `notify_mutation`
342-
// pipeline keeps `LISTING_CACHE` fresh, so a full re-read here is pure
343-
// redundancy. See the doc comment above for why this matters on MTP/SMB.
344351
if let Some((volume_id, path)) = crate::file_system::listing::get_listing_volume_id_and_path(&listing_id)
345352
&& let Some(volume) = get_volume_manager().get(&volume_id)
353+
&& volume.local_path().is_none()
346354
&& volume.listing_is_watched(&path)
347355
{
348356
log::debug!(
349357
target: "refresh_listing",
350-
"refresh_listing: short-circuit, watcher-backed listing (listing_id={}, volume_id={}, path={})",
358+
"refresh_listing: short-circuit, watcher-backed non-local listing (listing_id={}, volume_id={}, path={})",
351359
listing_id,
352360
volume_id,
353361
path.display(),

0 commit comments

Comments
 (0)