photocheck/src/main.rs

69 lines
2.1 KiB
Rust

use std::{collections::{HashMap, HashSet}, error::Error, os::unix::prelude::MetadataExt, path::PathBuf};
use clap::Parser;
use walkdir::WalkDir;
type StdError<T> = Result<T, Box<dyn Error>>;
#[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<Self> {
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(())
}