use std::io::{BufReader, BufWriter};
use clap::Parser;
use log::{error, info, LevelFilter};
use mimalloc::MiMalloc;
use rdupes::{DupeFinder, DupeParams};
use std::io::Write;
use tokio::time::Instant;
#[global_allocator]
static GLOBAL: MiMalloc = MiMalloc;
#[derive(Parser, Debug)]
#[command(version, about, long_about = None)]
struct Args {
paths: Vec<String>,
#[arg(short, long)]
output_path: Option<clio::OutputPath>,
#[arg(short = 's', long)]
checkpoint_save_path: Option<clio::OutputPath>,
#[arg(short = 'l', long)]
checkpoint_load_path: Option<clio::Input>,
#[arg(short, long, default_value_t = 4096)]
min_file_size: u64,
#[arg(short, long, default_value_t = 4)]
read_concurrency: usize,
#[arg(short, long, default_value_t = false)]
disable_mmap: bool,
#[arg(short, long, default_value_t = false)]
verbose: bool,
}
#[tokio::main]
async fn main() -> std::io::Result<()> {
let args = Args::parse();
let log_level = if args.verbose {
LevelFilter::Debug
} else {
LevelFilter::Info
};
simple_logger::SimpleLogger::new()
.with_level(log_level)
.init()
.map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))?;
info!("Parameters: {:?}", args);
if args.read_concurrency == 0 {
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidInput,
"read_concurrency must be greater than 0",
));
}
let params = DupeParams {
min_file_size: args.min_file_size,
read_concurrency: args.read_concurrency,
disable_mmap: args.disable_mmap,
};
let df = DupeFinder::new(params);
let now = Instant::now();
let size_map = if let Some(f) = args.checkpoint_load_path {
info!("Reading checkpoint from: {:?}", f.path());
let reader = BufReader::new(f);
ciborium::from_reader(reader).map_err(|e| {
error!("Failed to parse checkpoint data: {:?}", e);
std::io::Error::new(std::io::ErrorKind::InvalidData, e)
})?
} else {
info!("Traversing paths: {:?}", args.paths);
df.traverse_paths(args.paths.clone()).await?
};
if let Some(path) = args.checkpoint_save_path {
info!("Saving checkpoint to: {path:?}");
let file = path.create()?;
let writer = BufWriter::new(file);
ciborium::into_writer(&size_map, writer).map_err(|e| {
error!("Failed to save checkpoint data: {:?}", e);
std::io::Error::new(std::io::ErrorKind::Other, e)
})?;
}
let mut dupes_rx = df.find_duplicates(size_map);
let mut out = args
.output_path
.map(|f| f.create())
.transpose()
.map_err(|e| {
error!("Unable to open output file: {:?}", e);
e
})?;
while let Some(mut ds) = dupes_rx.recv().await {
if let Some(ref mut f) = out {
ds.sort_paths(&args.paths);
let source = &ds.paths[0];
for dest in &ds.paths[1..] {
f.write_all(format!("{source}\0{dest}\0").as_ref())?;
}
}
}
info!("Elapsed time: {:.1}s", now.elapsed().as_secs_f32());
Ok(())
}