use std::fs; use std::path::{Path, PathBuf}; pub fn sync_directory( source: &Path, target: &Path, protect_prefix: Option<&str>, // Will not get touched in target (file or directory at top level) ) -> Result<(), Box> { // Ensure the target directory exists fs::create_dir_all(target)?; // Gather all paths in the source and target directories let filter = |entry: Result| { entry .ok() .and_then(|entry| match (entry.file_name().to_str(), protect_prefix) { (Some(name), Some(prefix)) if name.starts_with(prefix) => None, _ => Some(entry.path()), }) }; let mut source_entries: Vec = fs::read_dir(source)?.filter_map(filter).collect(); let mut target_entries: Vec = fs::read_dir(target)?.filter_map(filter).collect(); // Sort entries to ensure consistent order for comparison source_entries.sort_unstable(); target_entries.sort_unstable(); // Two-pointer technique: iterate through both directories simultaneously let mut i = 0; let mut j = 0; while i < source_entries.len() || j < target_entries.len() { if i == source_entries.len() { // If there are remaining entries in the target, remove them remove_entry(&target_entries[j])?; j += 1; } else if j == target_entries.len() { // If there are remaining entries in the source, copy them to the target sync_entry(&source_entries[i], target)?; i += 1; } else { let source_path = &source_entries[i]; let target_path = &target_entries[j]; let source_name = source_path.file_name().unwrap(); let target_name = target_path.file_name().unwrap(); if source_name == target_name { // If both entries are the same, sync them sync_entry(source_path, target)?; i += 1; j += 1; } else if source_name < target_name { // If the source entry is less than the target entry, copy it to the target sync_entry(source_path, target)?; i += 1; } else { // If the target entry is less than the source entry, remove it from the target remove_entry(target_path)?; j += 1; } } } Ok(()) } fn sync_entry(source: &Path, target: &Path) -> Result<(), Box> { let target_path = target.join(source.file_name().unwrap()); if source.is_file() { // Copy the file if it doesn't exist or differs if !target_path.exists() || fs::metadata(source)?.modified()? != fs::metadata(&target_path)?.modified()? { println!("File copied/replaced: {:?}", source); fs::create_dir_all(target_path.parent().unwrap())?; fs::copy(source, &target_path)?; } } else if source.is_dir() { // Recursively sync directories sync_directory(source, &target_path, None)?; } Ok(()) } fn remove_entry(path: &Path) -> Result<(), Box> { println!("File/dir removed: {:?}", path); if path.is_file() { fs::remove_file(path)?; } else if path.is_dir() { fs::remove_dir_all(path)?; } Ok(()) }