diff --git a/harnesses/gtauri/gen/android/app/src/main/java/wow/gmp/gallery/MainActivity.kt b/harnesses/gtauri/gen/android/app/src/main/java/wow/gmp/gallery/MainActivity.kt index c804fba..cb5d5d0 100644 --- a/harnesses/gtauri/gen/android/app/src/main/java/wow/gmp/gallery/MainActivity.kt +++ b/harnesses/gtauri/gen/android/app/src/main/java/wow/gmp/gallery/MainActivity.kt @@ -5,6 +5,7 @@ import android.content.ContentUris import android.database.ContentObserver import android.net.Uri +import android.os.FileObserver import android.os.Build import android.os.Bundle import android.os.Handler @@ -17,6 +18,7 @@ import java.util.concurrent.Executors class MainActivity : TauriActivity() { private var observer: ContentObserver? = null + private val fileObservers = mutableListOf() // MediaStore event path: the OS tells us WHICH URI changed; we query // that one row (~ms) and hand the result to Rust via a payload file + @@ -77,6 +79,50 @@ class MainActivity : TauriActivity() { } } + private fun startFileObservers() { + val roots = listOf( + android.os.Environment.getExternalStoragePublicDirectory(android.os.Environment.DIRECTORY_PICTURES), + android.os.Environment.getExternalStoragePublicDirectory(android.os.Environment.DIRECTORY_DCIM) + ) + for (root in roots) { + if (root != null && root.exists()) { + val dirs = mutableListOf(root) + root.listFiles()?.forEach { if (it.isDirectory) dirs.add(it) } + for (dir in dirs) { + try { + val observer = object : FileObserver(dir.absolutePath, CLOSE_WRITE or DELETE or MOVED_TO or MOVED_FROM) { + override fun onEvent(event: Int, path: String?) { + if (path.isNullOrEmpty()) return + if (path.startsWith(".")) return + val fullPath = java.io.File(dir, path).absolutePath + val ext = fullPath.substringAfterLast('.', "").lowercase() + val imageExts = setOf("jpg", "jpeg", "png", "webp", "heic", "heif", "bmp") + if (!imageExts.contains(ext)) return + + fsExecutor.execute { + when (event and FileObserver.ALL_EVENTS) { + FileObserver.CLOSE_WRITE, FileObserver.MOVED_TO -> { + val f = java.io.File(fullPath) + if (f.exists() && f.length() > 0) { + deliver("A|0|$fullPath|${f.length()}") + } + } + FileObserver.DELETE, FileObserver.MOVED_FROM -> { + deliver("D_PATH|$fullPath") + } + } + } + } + } + observer.startWatching() + fileObservers.add(observer) + } catch (e: Exception) { + android.util.Log.w("gallery-ms", "FileObserver error: " + e) + } + } + } + } + } /** Handle ONE changed URI: query that exact MediaStore row. * Row exists → A (added/modified). Row gone → D (deleted). */ private fun handleUri(uri: Uri?) { @@ -180,9 +226,12 @@ class MainActivity : TauriActivity() { // the observer won't replay — push whatever was indexed recently. mainHandler.postDelayed({ fsExecutor.execute { windowQuery() } }, 1500) requestMediaPermission() + startFileObservers() } override fun onDestroy() { + fileObservers.forEach { it.stopWatching() } + fileObservers.clear() android.util.Log.i("gallery-ms", "onDestroy: unregistering observer") observer?.let { contentResolver.unregisterContentObserver(it) } fsExecutor.shutdownNow() diff --git a/harnesses/gtauri/src/lib.rs b/harnesses/gtauri/src/lib.rs index f87d707..f221cd0 100644 --- a/harnesses/gtauri/src/lib.rs +++ b/harnesses/gtauri/src/lib.rs @@ -1068,6 +1068,25 @@ fn mediastore_apply(app: &tauri::AppHandle, text: &str) -> bool { let _ = db::unmap_media(&mut conn, id); } } + "D_PATH" => { + let Some(path) = f.next() else { continue }; + if let Some(rid) = conn + .query_row( + "SELECT rowid FROM items WHERE path = ?1", + rusqlite::params![path], + |r| r.get(0), + ) + .ok() + { + FAILS.lock().unwrap().remove(&rid); + } + if let Ok(n) = db::delete_by_path(&mut conn, path) { + removed += n; + if n > 0 { + tr(&format!("mediastore del path={path}")); + } + } + } _ => {} } } @@ -1230,7 +1249,7 @@ fn watch_files(app: tauri::AppHandle) { let mut last_full = std::time::Instant::now(); loop { - let woke = match rx.recv_timeout(std::time::Duration::from_secs(1)) { + let woke = match rx.recv_timeout(std::time::Duration::from_millis(100)) { Ok(e) => Some(e), Err(mpsc::RecvTimeoutError::Timeout) => None, Err(mpsc::RecvTimeoutError::Disconnected) => break,