use std::{collections::{HashMap, HashSet}, error::Error, os::unix::prelude::MetadataExt, path::PathBuf}; use clap::Parser; use walkdir::WalkDir; type StdError = Result>; #[derive(Parser)] #[clap(author, version, about)] struct Cli { /// Source directory #[clap(value_parser)] source: PathBuf, /// Destination directory #[clap(value_parser)] destination: PathBuf, // /// Be more verbose // #[clap(short, long, value_parser)] // verbose: bool, } // Map from (filesize, filename) to full file path relative to root #[derive(Default)] struct FileMap(HashMap<(u64, String), String>); impl FileMap { fn new(path: &PathBuf) -> StdError { let mut fm = Self::default(); for entry in WalkDir::new(path).into_iter().filter_entry(|e| { !e.file_name() .to_str() .map(|s| s.starts_with('.')) .unwrap_or(false) }) { let entry = entry?; let meta = &entry.metadata()?; if meta.is_dir() { continue; } let fname = entry.file_name().to_str().unwrap().to_owned(); let fpath = entry.path().to_str().unwrap().to_owned(); if let Some(old) = fm.0.insert((meta.size(), fname.clone()), fpath.clone()) { return Err(format!("Duplicate filesize/filename detected: {}, clashes with {}!", old, fpath).into()); } } Ok(fm) } } fn main() -> StdError<()> { let cli = Cli::parse(); let source = FileMap::new(&cli.source)?; let source_set: HashSet<_> = source.0.keys().collect(); eprintln!("{} files in source", source.0.len()); let dest = FileMap::new(&cli.destination)?; let dest_set = dest.0.keys().collect(); eprintln!("{} files in destination", dest.0.len()); let mut diffs: Vec<_> = source_set.difference(&dest_set).map(|d| source.0[d].clone()).collect(); diffs.sort(); if !diffs.is_empty() { eprintln!("Files present in source but not dest:"); for diff in diffs { eprintln!("{}", diff); } } Ok(()) }