gtauri: enable fast mtime-diff filesystem watcher on Android (adds & deletes reflected in ~1s)

This commit is contained in:
Greg Pomerantz 2026-11-03 08:49:06 -05:00
parent 2c03577a67
commit 15a857ed7f

View File

@ -1141,6 +1141,47 @@ fn spawn_fs_watcher(tx: mpsc::Sender<WEvent>) {
});
}
fn collect_watched_dirs(roots: &[PathBuf]) -> HashSet<PathBuf> {
let mut dirs = HashSet::new();
for r in roots {
if r.is_dir() {
dirs.insert(r.clone());
if let Ok(rd) = std::fs::read_dir(r) {
for ent in rd.flatten() {
if let Ok(ft) = ent.file_type() {
if ft.is_dir() {
let p = ent.path();
if !p.file_name().map_or(false, |n| n.to_string_lossy().starts_with('.')) {
dirs.insert(p);
}
}
}
}
}
}
}
if let Ok(mut conn) = db::open(&db::db_path()) {
if let Ok(entries) = db::list_entries(&mut conn) {
for e in entries {
if let Some(parent) = std::path::Path::new(&e.path).parent() {
dirs.insert(parent.to_path_buf());
}
}
}
}
dirs
}
fn fast_dir_sig(dirs: &HashSet<PathBuf>) -> HashMap<PathBuf, i64> {
let mut out = HashMap::new();
for d in dirs {
if let Ok(m) = std::fs::metadata(d) {
out.insert(d.clone(), mtime_ns(&m));
}
}
out
}
/// Diff two dir-mtime signatures: dirs added, removed, or changed.
fn changed_dirs(
old: &HashMap<PathBuf, i64>,
@ -1181,15 +1222,13 @@ fn watch_files(app: tauri::AppHandle) {
});
#[cfg(not(target_os = "android"))]
spawn_fs_watcher(tx);
// Desktop only: the mtime-diff machinery. On Android the MediaStore
// URI payloads ARE the change signal (exact paths, no scanning),
// and diffing would cost a 10k-entry readdir every second on the
// phone — pure overhead for what the payloads already deliver.
#[cfg(not(target_os = "android"))]
let mut sig: HashMap<PathBuf, i64> = HashMap::new();
#[cfg(not(target_os = "android"))]
let mut watched_dirs = collect_watched_dirs(&scan::roots());
let mut sig = fast_dir_sig(&watched_dirs);
let mut baseline = true;
let mut last_dir_refresh = std::time::Instant::now();
let mut last_full = std::time::Instant::now();
loop {
let woke = match rx.recv_timeout(std::time::Duration::from_secs(1)) {
Ok(e) => Some(e),
@ -1198,7 +1237,6 @@ fn watch_files(app: tauri::AppHandle) {
};
let do_full = matches!(woke, Some(WEvent::Full));
if let Some(WEvent::Fs) = woke {
// Coalesce the burst, let the write settle, coalesce again.
while rx.try_recv().is_ok() {}
std::thread::sleep(std::time::Duration::from_millis(600));
while rx.try_recv().is_ok() {}
@ -1206,40 +1244,30 @@ fn watch_files(app: tauri::AppHandle) {
if do_full {
tr("watch: full rescan (5min)");
rescan(&app);
watched_dirs = collect_watched_dirs(&scan::roots());
sig = fast_dir_sig(&watched_dirs);
last_full = std::time::Instant::now();
continue;
}
// Refresh watched dirs periodically (every 30s) to pick up new subdirs
if last_dir_refresh.elapsed() >= std::time::Duration::from_secs(30) {
watched_dirs = collect_watched_dirs(&scan::roots());
last_dir_refresh = std::time::Instant::now();
}
// MediaStore event path (Android): MainActivity drops a one-line
// payload per changed URI and wakes us via nativeFsChange.
// Consume on EVERY tick (single read_dir of our own app dir —
// no-op when empty, so a lost JNI wake self-heals within the 1 s
// backstop) — but NOT while a full rescan is mid-walk: those
// payloads wait as files and apply after the walk's apply, so
// mid-walk deletions win over the walk's stale snapshot.
let applied_ms = if RESCANNING.load(std::sync::atomic::Ordering::Relaxed) {
let _applied_ms = if RESCANNING.load(std::sync::atomic::Ordering::Relaxed) {
false
} else {
apply_mediasotre_payload(&app)
};
// Android fallback: the OS said "media changed" but no payload
// arrived (permission missing, scanner gap, or the payload was
// parked behind a rescan). Full rescan, throttled — the
// degraded path, not the common one. Parking behind a rescan
// also counts as "not applied": the rescan's next 1 s tick
// re-evaluates and either drains the payload or rescans.
#[cfg(target_os = "android")]
if matches!(woke, Some(WEvent::Fs)) && !applied_ms
&& last_full.elapsed() >= std::time::Duration::from_secs(30)
{
tr("watch: full rescan (event, no payload)");
rescan(&app);
last_full = std::time::Instant::now();
continue;
}
// Desktop: diff the dir-mtime signature, rescan changed subtrees.
#[cfg(not(target_os = "android"))]
{
let next = dir_sig(&scan::roots());
// Fast directory mtime-diff watch (Android and desktop): catches
// out-of-band filesystem adds/removes within ~1 second without
// scanning 10k files.
let next = fast_dir_sig(&watched_dirs);
let changed = if baseline {
baseline = false;
Vec::new()
@ -1247,11 +1275,12 @@ fn watch_files(app: tauri::AppHandle) {
changed_dirs(&sig, &next)
};
sig = next;
if changed.is_empty() {
continue;
}
if !changed.is_empty() {
tr(&format!("watch: {} changed dir(s)", changed.len()));
rescan_subtrees(&app, &changed);
watched_dirs = collect_watched_dirs(&scan::roots());
sig = fast_dir_sig(&watched_dirs);
}
}
}