fix: ignore local watcher access events

The peer CLI could flood LocalGamesUpdated events when run from the Docker
harness. The local monitor rescans game roots, and some bind-mounted filesystems
report those read/close operations back as notify access events. Treating those
non-mutating events as real library changes queued another rescan, making the
headless CLI unusable for manual peer-to-peer testing.

Ignore access events before mapping paths to game IDs. Create, modify, remove,
and rename events still flow through the existing per-game rescan gate, while
fallback scans continue to reconcile missed writes.

Test Plan:
- just fmt
- just test
- just clippy

Refs: manual peer-cli P2P testing
This commit is contained in:
2026-05-16 19:50:10 +02:00
parent ed007f7844
commit 3380d137fc
@@ -7,7 +7,7 @@ use std::{
time::Duration,
};
use notify::{Config, Event, RecommendedWatcher, RecursiveMode, Watcher};
use notify::{Config, Event, EventKind, RecommendedWatcher, RecursiveMode, Watcher};
use tokio::sync::{RwLock, mpsc::UnboundedSender};
use crate::{
@@ -219,6 +219,10 @@ async fn handle_watch_event(
}
};
if matches!(event.kind, EventKind::Access(_)) {
return;
}
let game_dir = ctx.game_dir.read().await.clone();
let ids = event
.paths
@@ -338,7 +342,10 @@ mod tests {
time::Duration,
};
use notify::EventKind;
use notify::{
EventKind,
event::{AccessKind, AccessMode},
};
use tokio::sync::{RwLock, mpsc};
use tokio_util::{sync::CancellationToken, task::TaskTracker};
@@ -470,6 +477,40 @@ mod tests {
assert!(gate.pending.read().await.is_empty());
}
#[tokio::test]
async fn access_watch_event_is_ignored() {
let temp = TempDir::new("lanspread-local-monitor");
write_file(&temp.path().join("game").join("version.ini"), b"20250101");
let ctx = test_ctx(
temp.path().to_path_buf(),
HashSet::from(["game".to_string()]),
);
let gate = RescanGate::default();
let (tx, mut rx) = mpsc::unbounded_channel();
handle_watch_event(
&ctx,
&tx,
&gate,
Ok(
Event::new(EventKind::Access(AccessKind::Close(AccessMode::Read)))
.add_path(temp.path().join("game").join("version.ini")),
),
)
.await;
ctx.task_tracker.close();
ctx.task_tracker.wait().await;
assert!(
tokio::time::timeout(Duration::from_millis(50), rx.recv())
.await
.is_err(),
"access events should not schedule a UI update"
);
assert!(gate.running.read().await.is_empty());
assert!(gate.pending.read().await.is_empty());
}
#[tokio::test]
async fn burst_watch_events_collapse_to_two_rescans_for_same_game() {
let temp = TempDir::new("lanspread-local-monitor");