rdupe/src/main.rs

231 lines
6.1 KiB
Rust

extern crate clap;
extern crate term;
extern crate sha2;
extern crate walkdir;
use sha2::{Sha256, Digest};
use std::path::Path;
use std::process;
use std::fs;
use std::io::Read;
use walkdir::WalkDir;
const BUFFER_SIZE: usize = 1024;
/* Note for myself : CLAP = _C_ommand _L_ine _A_rgument _P_arser */
use clap::{Arg,ArgMatches, App};
struct Args {
input: String,
output: String,
vlevel: u8,
dryrun: bool,
}
impl Args {
fn new(matches: ArgMatches) -> Args {
let i = matches.value_of("source").unwrap();
let o = matches.value_of("dest").unwrap();
let vl = match matches.occurrences_of("verbose") {
0 => 0,
1 => 1,
2 => 2,
3 | _ => 3,
};
let dr = matches.is_present("dry-run");
Args {
input: i.to_string(),
output: o.to_string(),
vlevel: vl,
dryrun: dr,
}
}
fn path_exist(&self) -> bool {
let mut result = true;
if ! Path::new(&self.input).exists() {
result = false;
println!("Error, input ( {} ) is not a valid directory", self.input);
}
if ! Path::new(&self.output).exists() {
result = false;
println!("Error, output ({} ) is not a valid directory", self.output);
}
result
}
fn check_not_same(&self) -> bool {
if self.input == self.output {
false
} else {
true
}
}
fn check_not_parent(&self) -> bool {
let vec = vec![&self.input, &self.output];
for (i, x) in vec.iter().enumerate() {
let mut a = Path::new(x);
let tmp = match i {
0 => &self.output,
1 => &self.input,
_ => "None",
};
loop {
let b = a.parent();
a = match b {
Some(b) => {
if b.to_str().unwrap() == tmp {
return false;
}
b
},
None => break,
};
}
}
true
}
}
fn main() {
let mut t = term::stdout().unwrap();
let matches = App::new("rdupe")
.version("0.1.0")
.author("Beneth <bmauduit@beneth.fr>")
.about("Symlink identical files from source to dest based on hash")
.arg(Arg::with_name("source")
.help("Input1 directory (will keep the real file)")
.short("i")
.long("input")
.takes_value(true)
.required(true)
)
.arg(Arg::with_name("dest")
.help("destination directory (will be symlink to real file)")
.short("o")
.long("dest")
.takes_value(true)
.required(true)
)
.arg(Arg::with_name("dry-run")
.short("d")
.long("dry-run")
.help("Dry run (Compare hash but do not symlink)")
)
.arg(Arg::with_name("verbose")
.short("v")
.long("verbose")
.multiple(true)
.help("Sets the level of verbosity")
)
.get_matches();
t.fg(term::color::GREEN).unwrap();
let args = Args::new(matches);
println!("Value for input: {}", args.input);
println!("Value for output: {}", args.output);
println!("Verbosity Level: {}", args.vlevel);
if args.dryrun == true {
println!("dry-run enabled");
} else {
if args.vlevel >= 2 {
t.fg(term::color::RED).unwrap();
println!("dry-run not enabled");
t.reset().unwrap();
}
}
// Check input & output
// 1 - Existence
if ! args.path_exist() {
println!("Exiting: Path 1 or Path 2 does not exist");
process::exit(1);
}
// 2 - Not the same path
if ! args.check_not_same() {
println!("Exiting: input and output are the same");
process::exit(1);
}
// 3 - Coherence (Path1|2 does not contain path1|2)
if ! args.check_not_parent() {
println!("Exiting: Path1 or Path2 are parent !");
process::exit(1);
}
// Output
let mut i = 0;
for entry in WalkDir::new(&args.output)
.into_iter()
.filter_map(|e| e.ok())
{
// symlink_metadata does not follow symlink :-]
let metadata = fs::symlink_metadata(entry.path()).unwrap();
let ft = metadata.file_type();
if ft.is_file() {
if let Ok(mut file) = fs::File::open(&entry.path()) {
process::<Sha256, _>(&mut file,
&format!("[{}] - {}",
i,
entry.path().display()));
i = i+1;
}
}
}
// let inputs = fs::read_dir(&args.input).unwrap();
// for path in inputs {
// let path_str = path.unwrap().path().into_os_string().into_string().unwrap();
// println!("[I] Name: {}", path_str);
// if let Ok(mut file) = fs::File::open(&path_str) {
// process::<Sha256, _>(&mut file,
// &path_str);
// }
// }
t.reset().unwrap();
t.fg(term::color::CYAN).unwrap();
println!("Cheers !");
}
/// From https://github.com/RustCrypto/hashes/blob/master/sha2/examples/sha256sum.rs
/// Compute digest value for given `Reader` and print it
/// On any error simply return without doing anything
fn process<D: Digest + Default, R: Read>(reader: &mut R, name: &str) {
let mut sh = D::default();
let mut buffer = [0u8; BUFFER_SIZE];
loop {
let n = match reader.read(&mut buffer) {
Ok(n) => n,
Err(_) => return,
};
sh.input(&buffer[..n]);
if n == 0 || n < BUFFER_SIZE {
break;
}
}
print_result(&sh.result(), name);
}
/// Print digest result as hex string and name pair
fn print_result(sum: &[u8], name: &str) {
for byte in sum {
print!("{:02x}", byte);
}
println!("\t{}", name);
}