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.
|
||||
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,52 +1244,43 @@ 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());
|
||||
let changed = if baseline {
|
||||
baseline = false;
|
||||
Vec::new()
|
||||
} else {
|
||||
changed_dirs(&sig, &next)
|
||||
};
|
||||
sig = next;
|
||||
if changed.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 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()
|
||||
} else {
|
||||
changed_dirs(&sig, &next)
|
||||
};
|
||||
sig = next;
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user