97 lines
3.4 KiB
Rust
97 lines
3.4 KiB
Rust
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<dyn std::error::Error>> {
|
|
// Ensure the target directory exists
|
|
fs::create_dir_all(target)?;
|
|
|
|
// Gather all paths in the source and target directories
|
|
let filter = |entry: Result<fs::DirEntry, std::io::Error>| {
|
|
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<PathBuf> = fs::read_dir(source)?.filter_map(filter).collect();
|
|
let mut target_entries: Vec<PathBuf> = 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<dyn std::error::Error>> {
|
|
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<dyn std::error::Error>> {
|
|
println!("File/dir removed: {:?}", path);
|
|
if path.is_file() {
|
|
fs::remove_file(path)?;
|
|
} else if path.is_dir() {
|
|
fs::remove_dir_all(path)?;
|
|
}
|
|
Ok(())
|
|
}
|