diff --git a/src-tauri/src/vault/file.rs b/src-tauri/src/vault/file.rs index 6862c864..93af5aba 100644 --- a/src-tauri/src/vault/file.rs +++ b/src-tauri/src/vault/file.rs @@ -1,3 +1,4 @@ +use std::borrow::Cow; use std::fs; use std::io::{Error, ErrorKind, Write}; use std::path::Path; @@ -39,6 +40,24 @@ fn read_existing_note_bytes(path: &Path) -> Result, String> { fs::read(path).map_err(|e| format!("Failed to read {}: {}", path.display(), e)) } +struct RawNotePath<'a>(&'a str); + +impl<'a> RawNotePath<'a> { + fn is_windows_verbatim(&self) -> bool { + self.0.starts_with(r"\\?\") || self.0.starts_with(r"\??\") + } + + fn normalized_for_file_io(&self) -> Cow<'a, str> { + if !self.is_windows_verbatim() { + return Cow::Borrowed(self.0); + } + if !self.0.contains('/') { + return Cow::Borrowed(self.0); + } + Cow::Owned(self.0.replace('/', r"\")) + } +} + #[derive(Clone, Copy)] enum NoteIoOperation { Save, @@ -112,7 +131,8 @@ fn validate_save_path(file_path: &Path, display_path: &str) -> Result<(), String /// Write content to a note file. Creates parent directory if needed, validates path, /// then writes content to disk. pub fn save_note_content(path: &str, content: &str) -> Result<(), String> { - let file_path = Path::new(path); + let normalized_path = RawNotePath(path).normalized_for_file_io(); + let file_path = Path::new(normalized_path.as_ref()); if let Some(parent) = file_path.parent() { if !parent.exists() { fs::create_dir_all(parent).map_err(|e| { @@ -127,7 +147,8 @@ pub fn save_note_content(path: &str, content: &str) -> Result<(), String> { /// Create a new note file without overwriting any existing file. pub fn create_note_content(path: &str, content: &str) -> Result<(), String> { - let file_path = Path::new(path); + let normalized_path = RawNotePath(path).normalized_for_file_io(); + let file_path = Path::new(normalized_path.as_ref()); if let Some(parent) = file_path.parent() { if !parent.exists() { fs::create_dir_all(parent).map_err(|e| { @@ -166,6 +187,16 @@ mod tests { assert!(!message.contains("os error 123")); } + #[test] + fn normalizes_extended_windows_paths_before_file_io() { + let path = r"\\?\C:\Users\alex\Documents\Tolaria/Getting Started/untitled-project.md"; + + assert_eq!( + RawNotePath(path).normalized_for_file_io(), + r"\\?\C:\Users\alex\Documents\Tolaria\Getting Started\untitled-project.md" + ); + } + #[test] fn note_content_matches_detects_external_edits() { let dir = tempfile::TempDir::new().unwrap();