From 1d14c1a82d149dbe13aefc27fc0de6aec3728215 Mon Sep 17 00:00:00 2001 From: thePR0M3TH3AN <53631862+PR0M3TH3AN@users.noreply.github.com> Date: Mon, 26 May 2025 18:06:46 -0400 Subject: [PATCH] Normalize canonical paths on Windows --- libmarlin/src/utils.rs | 18 +++++++++++++++++- libmarlin/src/watcher.rs | 15 +++++++++++---- libmarlin/src/watcher_tests.rs | 14 ++++++++++---- 3 files changed, 38 insertions(+), 9 deletions(-) diff --git a/libmarlin/src/utils.rs b/libmarlin/src/utils.rs index feb16ba..5341aa2 100644 --- a/libmarlin/src/utils.rs +++ b/libmarlin/src/utils.rs @@ -45,12 +45,28 @@ pub fn determine_scan_root(pattern: &str) -> PathBuf { } } +/// Canonicalize a path, stripping any Windows device prefix ("\\?\") and +/// falling back to the original on error. +pub fn canonicalize_lossy>(p: P) -> PathBuf { + let path = std::fs::canonicalize(&p).unwrap_or_else(|_| p.as_ref().to_path_buf()); + #[cfg(windows)] + { + const VERBATIM_PREFIX: &str = "\\\\?\\"; + let s = path.to_string_lossy(); + if let Some(stripped) = s.strip_prefix(VERBATIM_PREFIX) { + return PathBuf::from(stripped); + } + } + path +} + /// Convert a filesystem path to a normalized database path. /// /// On Windows this replaces backslashes with forward slashes so that paths /// stored in the database are consistent across platforms. pub fn to_db_path>(p: P) -> String { - let s = p.as_ref().to_string_lossy(); + let canonical = canonicalize_lossy(p); + let s = canonical.to_string_lossy(); #[cfg(windows)] { s.replace('\\', "/") diff --git a/libmarlin/src/watcher.rs b/libmarlin/src/watcher.rs index d33b3ed..ec3b083 100644 --- a/libmarlin/src/watcher.rs +++ b/libmarlin/src/watcher.rs @@ -6,7 +6,7 @@ //! watcher can be paused, resumed and shut down cleanly. use crate::db::{self, Database}; -use crate::utils::to_db_path; +use crate::utils::{canonicalize_lossy, to_db_path}; use anyhow::{anyhow, Context, Result}; use crossbeam_channel::{bounded, Receiver}; use notify::{ @@ -256,13 +256,20 @@ impl FileWatcher { // ── start actual OS watcher ─────────────────────────────────────────── let event_tx = tx.clone(); let mut actual_watcher = RecommendedWatcher::new( - move |ev| { + move |ev: Result| { + let ev = ev.map(|mut e| { + for p in &mut e.paths { + *p = canonicalize_lossy(&*p); + } + e + }); let _ = event_tx.send(ev); }, notify::Config::default(), )?; - for p in &paths { + let canonical_paths: Vec = paths.iter().map(canonicalize_lossy).collect(); + for p in &canonical_paths { actual_watcher .watch(p, RecursiveMode::Recursive) .with_context(|| format!("Failed to watch path {}", p.display()))?; @@ -532,7 +539,7 @@ impl FileWatcher { Ok(Self { state, _config: config, - watched_paths: paths, + watched_paths: canonical_paths, _event_receiver: rx, _watcher: actual_watcher, processor_thread: Some(processor_thread), diff --git a/libmarlin/src/watcher_tests.rs b/libmarlin/src/watcher_tests.rs index 9bafbb6..74b2640 100644 --- a/libmarlin/src/watcher_tests.rs +++ b/libmarlin/src/watcher_tests.rs @@ -6,7 +6,7 @@ mod tests { use crate::backup::BackupManager; // These are still from the watcher module use crate::db::open as open_marlin_db; - use crate::utils::to_db_path; + use crate::utils::{canonicalize_lossy, to_db_path}; use crate::watcher::{FileWatcher, WatcherConfig, WatcherState}; // Use your project's DB open function use crate::Marlin; @@ -25,12 +25,13 @@ mod tests { timeout: Duration, ) { let start = Instant::now(); + let target = canonicalize_lossy(path); loop { let count: i64 = marlin .conn() .query_row( "SELECT COUNT(*) FROM files WHERE path = ?1", - [to_db_path(path)], + [to_db_path(&target)], |r| r.get(0), ) .unwrap(); @@ -201,7 +202,12 @@ mod tests { thread::sleep(Duration::from_millis(100)); let new_file = dir.join("b.txt"); fs::rename(&file, &new_file).unwrap(); - wait_for_row_count(&marlin, &new_file, 1, Duration::from_secs(10)); + wait_for_row_count( + &marlin, + &canonicalize_lossy(&new_file), + 1, + Duration::from_secs(10), + ); watcher.stop().unwrap(); assert!( watcher.status().unwrap().events_processed > 0, @@ -249,7 +255,7 @@ mod tests { fs::rename(&sub, &new).unwrap(); for fname in ["one.txt", "two.txt"] { let p = new.join(fname); - wait_for_row_count(&marlin, &p, 1, Duration::from_secs(10)); + wait_for_row_count(&marlin, &canonicalize_lossy(&p), 1, Duration::from_secs(10)); } watcher.stop().unwrap(); assert!(