gtauri: enable fast mtime-diff filesystem watcher on Android (adds & deletes reflected in ~1s)
This commit is contained in:
parent
2c03577a67
commit
15a857ed7f
|
|
@ -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.
|
/// Diff two dir-mtime signatures: dirs added, removed, or changed.
|
||||||
fn changed_dirs(
|
fn changed_dirs(
|
||||||
old: &HashMap<PathBuf, i64>,
|
old: &HashMap<PathBuf, i64>,
|
||||||
|
|
@ -1181,15 +1222,13 @@ fn watch_files(app: tauri::AppHandle) {
|
||||||
});
|
});
|
||||||
#[cfg(not(target_os = "android"))]
|
#[cfg(not(target_os = "android"))]
|
||||||
spawn_fs_watcher(tx);
|
spawn_fs_watcher(tx);
|
||||||
// Desktop only: the mtime-diff machinery. On Android the MediaStore
|
|
||||||
// URI payloads ARE the change signal (exact paths, no scanning),
|
let mut watched_dirs = collect_watched_dirs(&scan::roots());
|
||||||
// and diffing would cost a 10k-entry readdir every second on the
|
let mut sig = fast_dir_sig(&watched_dirs);
|
||||||
// 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 baseline = true;
|
let mut baseline = true;
|
||||||
|
let mut last_dir_refresh = std::time::Instant::now();
|
||||||
let mut last_full = std::time::Instant::now();
|
let mut last_full = std::time::Instant::now();
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
let woke = match rx.recv_timeout(std::time::Duration::from_secs(1)) {
|
let woke = match rx.recv_timeout(std::time::Duration::from_secs(1)) {
|
||||||
Ok(e) => Some(e),
|
Ok(e) => Some(e),
|
||||||
|
|
@ -1198,7 +1237,6 @@ fn watch_files(app: tauri::AppHandle) {
|
||||||
};
|
};
|
||||||
let do_full = matches!(woke, Some(WEvent::Full));
|
let do_full = matches!(woke, Some(WEvent::Full));
|
||||||
if let Some(WEvent::Fs) = woke {
|
if let Some(WEvent::Fs) = woke {
|
||||||
// Coalesce the burst, let the write settle, coalesce again.
|
|
||||||
while rx.try_recv().is_ok() {}
|
while rx.try_recv().is_ok() {}
|
||||||
std::thread::sleep(std::time::Duration::from_millis(600));
|
std::thread::sleep(std::time::Duration::from_millis(600));
|
||||||
while rx.try_recv().is_ok() {}
|
while rx.try_recv().is_ok() {}
|
||||||
|
|
@ -1206,52 +1244,43 @@ fn watch_files(app: tauri::AppHandle) {
|
||||||
if do_full {
|
if do_full {
|
||||||
tr("watch: full rescan (5min)");
|
tr("watch: full rescan (5min)");
|
||||||
rescan(&app);
|
rescan(&app);
|
||||||
|
watched_dirs = collect_watched_dirs(&scan::roots());
|
||||||
|
sig = fast_dir_sig(&watched_dirs);
|
||||||
last_full = std::time::Instant::now();
|
last_full = std::time::Instant::now();
|
||||||
continue;
|
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
|
// MediaStore event path (Android): MainActivity drops a one-line
|
||||||
// payload per changed URI and wakes us via nativeFsChange.
|
// payload per changed URI and wakes us via nativeFsChange.
|
||||||
// Consume on EVERY tick (single read_dir of our own app dir —
|
let _applied_ms = if RESCANNING.load(std::sync::atomic::Ordering::Relaxed) {
|
||||||
// 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) {
|
|
||||||
false
|
false
|
||||||
} else {
|
} else {
|
||||||
apply_mediasotre_payload(&app)
|
apply_mediasotre_payload(&app)
|
||||||
};
|
};
|
||||||
// Android fallback: the OS said "media changed" but no payload
|
|
||||||
// arrived (permission missing, scanner gap, or the payload was
|
// Fast directory mtime-diff watch (Android and desktop): catches
|
||||||
// parked behind a rescan). Full rescan, throttled — the
|
// out-of-band filesystem adds/removes within ~1 second without
|
||||||
// degraded path, not the common one. Parking behind a rescan
|
// scanning 10k files.
|
||||||
// also counts as "not applied": the rescan's next 1 s tick
|
let next = fast_dir_sig(&watched_dirs);
|
||||||
// re-evaluates and either drains the payload or rescans.
|
let changed = if baseline {
|
||||||
#[cfg(target_os = "android")]
|
baseline = false;
|
||||||
if matches!(woke, Some(WEvent::Fs)) && !applied_ms
|
Vec::new()
|
||||||
&& last_full.elapsed() >= std::time::Duration::from_secs(30)
|
} else {
|
||||||
{
|
changed_dirs(&sig, &next)
|
||||||
tr("watch: full rescan (event, no payload)");
|
};
|
||||||
rescan(&app);
|
sig = next;
|
||||||
last_full = std::time::Instant::now();
|
|
||||||
continue;
|
if !changed.is_empty() {
|
||||||
}
|
|
||||||
// Desktop: diff the dir-mtime signature, rescan changed subtrees.
|
|
||||||
#[cfg(not(target_os = "android"))]
|
|
||||||
{
|
|
||||||
let next = dir_sig(&scan::roots());
|
|
||||||
let changed = if baseline {
|
|
||||||
baseline = false;
|
|
||||||
Vec::new()
|
|
||||||
} else {
|
|
||||||
changed_dirs(&sig, &next)
|
|
||||||
};
|
|
||||||
sig = next;
|
|
||||||
if changed.is_empty() {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
tr(&format!("watch: {} changed dir(s)", changed.len()));
|
tr(&format!("watch: {} changed dir(s)", changed.len()));
|
||||||
rescan_subtrees(&app, &changed);
|
rescan_subtrees(&app, &changed);
|
||||||
|
watched_dirs = collect_watched_dirs(&scan::roots());
|
||||||
|
sig = fast_dir_sig(&watched_dirs);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user