diff --git a/prebuilt-x86-64-linux/lndups b/prebuilt-x86-64-linux/lndups index b723325..30f360e 100755 Binary files a/prebuilt-x86-64-linux/lndups and b/prebuilt-x86-64-linux/lndups differ diff --git a/src/main.rs b/src/main.rs index 6d12b5f..0bf26f5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,6 +7,7 @@ use std::os::linux::fs::MetadataExt as MetadataExtLinux; use crate::structopt::StructOpt; + fn main() -> Result<(), Box> { let (run_paths, cfg) = process_args(); for paths in run_paths { @@ -16,6 +17,7 @@ fn main() -> Result<(), Box> { Ok(()) } + struct Config { dry_run: bool, min_size: u64, @@ -23,6 +25,7 @@ struct Config { no_brace_output: bool } + #[derive(StructOpt)] #[structopt( about="Hardlink duplicate files recursively", @@ -66,6 +69,7 @@ struct CLIArguments { targets: Vec, } + /// return whether or not user gave confirmation fn prompt_confirm(run_targets: &Vec>) -> bool { println!("Are you sure you want to link all duplicates in each of these sets of targets?"); @@ -86,6 +90,7 @@ fn prompt_confirm(run_targets: &Vec>) -> bool { response.to_lowercase().starts_with("y") } + fn read_argument_file(arg_file: &Path, dest: &mut Vec) -> Result<(), String> { if !arg_file.is_file() { return Err(format!("File does not exist or is not a normal file ({})", shlex::quote(&arg_file.to_string_lossy()))); @@ -104,6 +109,8 @@ fn read_argument_file(arg_file: &Path, dest: &mut Vec) -> Result<(), Str } } + +/// exits on error fn process_args() -> (Vec>, Config) { let mut args = CLIArguments::from_args(); let verbosity = args.verbose - args.quiet; @@ -162,6 +169,7 @@ fn process_args() -> (Vec>, Config) { }) } + /// exit on error fn get_st_dev(file: &PathBuf) -> Result { if let Ok(metadata) = std::fs::metadata(file) { @@ -171,7 +179,6 @@ fn get_st_dev(file: &PathBuf) -> Result { } } -/// minimum length of slice = 2 fn assert_all_same_device(paths: &[PathBuf]) -> Result<(), String> { if paths.len() <= 1 { return Ok(()) @@ -186,16 +193,18 @@ fn assert_all_same_device(paths: &[PathBuf]) -> Result<(), String> { if wrong.is_empty() { Ok(()) } else { - let mut s = String::new(); + let mut s = String::with_capacity(wrong.len()*120); // 67 max estimated len of path, 52 for prefix msg, 1 for newline for path in wrong { s.push_str("Device ids must all be the same; got different for: {}"); s.push_str(&shlex::quote(&path.to_string_lossy())); + s.push_str("\n"); } Err(s) } } -/// perform a full run with pre-processed inputs + +/// perform a full run fn run(paths: Vec, cfg: &Config) -> Result<(), Box> { let mut registry: HashMap> = HashMap::new(); @@ -208,7 +217,7 @@ fn run(paths: Vec, cfg: &Config) -> Result<(), Box 0 { - writeln!(stdout_buffer, "considering {} total files for duplicates", registry.iter().map(|(_,files)| files.len()).sum::()).unwrap(); + writeln!(stdout_buffer, "Considering {} total files for duplicates", registry.iter().map(|(_,files)| files.len()).sum::()).unwrap(); } for (fsize, mut files) in registry { @@ -216,7 +225,7 @@ fn run(paths: Vec, cfg: &Config) -> Result<(), Box 1 { - writeln!(stdout_buffer, "considering {} files of size {} for duplicates\n", files.len(), fsize).unwrap(); + writeln!(stdout_buffer, "Considering {} files of size {} for duplicates\n", files.len(), fsize).unwrap(); } for i in (0..files.len()).rev() { let f1 = &files[i]; @@ -240,19 +249,21 @@ fn run(paths: Vec, cfg: &Config) -> Result<(), Box Result<(), &'static str> { if let Err(_) = std::fs::remove_file(f2) { - Err("failed to remove second file for hardlinking") + Err("Failed to remove second file for hardlinking") } else if let Err(_) = std::fs::hard_link(f1, f2) { // same as ln in terms of args: left args's inode becomes right arg's inode match std::fs::copy(f1, f2) { - Ok(_) => Err("failed to hardlink (copied instead)"), - Err(_) => Err("failed to hardlink or copy") + Ok(_) => Err("Failed to hardlink (copied instead)"), + Err(_) => Err("Failed to hardlink or copy") } } else { Ok(()) } } + fn format_pair(f1: &PathBuf, f2: &PathBuf, cfg: &Config) -> String { let f1s = f1.to_string_lossy(); let f2s = f2.to_string_lossy(); @@ -324,6 +335,7 @@ fn register(path: PathBuf, registry: &mut HashMap>, cfg: &Conf } } + fn are_hardlinked(f1: &PathBuf, f2: &PathBuf) -> bool { if let (Ok(md1), Ok(md2)) = (std::fs::metadata(f1), std::fs::metadata(f2)) { md1.st_ino() == md2.st_ino() @@ -332,6 +344,7 @@ fn are_hardlinked(f1: &PathBuf, f2: &PathBuf) -> bool { } } + /// check equality of contents of two paths to files fn cmp(f1: &PathBuf, f2: &PathBuf) -> bool { if let (Ok(mut f1), Ok(mut f2)) = (std::fs::File::open(f1), std::fs::File::open(f2)) { @@ -364,6 +377,7 @@ fn cmp_files(f1: &mut std::fs::File, f2: &mut std::fs::File) -> bool { } } + fn common_prefix<'a>(s1: &'a str, s2: &'a str) -> &'a str { let len = s1 .chars() @@ -382,6 +396,7 @@ fn common_suffix<'a>(s1: &'a str, s2: &'a str) -> &'a str { &s1[s1.len() - len..] } + fn split_vec(input: &[String], delimiter: &String) -> Vec> { let mut result: Vec> = Vec::new(); let mut current_vec: Vec = Vec::new();