#![doc(html_root_url = "https://docs.rs/elif/0.0.4")]
use std::error::Error;
use std::io::{Read, BufReader};
use std::fs;
use std::path::PathBuf;
use md5;
pub fn file_meta(p: &PathBuf) -> Result<fs::Metadata, Box<dyn Error>> {
let Ok(mf) = fs::metadata(p) else {
return Err(format!("cannot get metadata: {}", p.display()).into())
};
Ok(mf)
}
pub fn md5sum(p: &PathBuf, sz: u64) -> Result<String, Box<dyn Error>> {
let mut t: usize = 0;
let f = fs::File::open(p)?;
let mut rdr = BufReader::new(f);
let mut buf = vec![0u8; 8192]; let mut ctx = md5::Context::new();
loop {
match rdr.read(&mut buf) {
Err(_e) => { break; },
Ok(l) => { ctx.consume(&buf[..l]); t += l; if t >= sz as usize { break; } }
}
}
if t < sz as usize { return Err("can't read all".into()); }
let digest = ctx.compute();
Ok(format!("{:x}", digest))
}
pub fn read_dir_entries(bpath: &str) ->
Result<Vec<fs::DirEntry>, Box<dyn Error>> {
let mut entries: Vec<fs::DirEntry> = Vec::new();
match fs::read_dir(bpath) {
Err(e) => eprintln!("err: {}", e),
Ok(rdir) => for item in rdir.into_iter() { match item {
Err(e) => eprintln!("err: {}", e),
Ok(entry) => entries.push(entry) }
}
}
entries.sort_by(|a, b| a.path().cmp(&b.path()));
entries.sort_by(|a, b| a.path().is_file().cmp(&b.path().is_file()));
Ok(entries)
}
pub fn walk_dir_entries(inf: bool,
ignores: &Vec<String>, bpaths: &Vec<&str>, dep: u64) ->
Result<u64, Box<dyn Error>> {
let mut total: u64 = 0;
let depth = String::from_utf8((0..dep).into_iter().map(|_| 0x20).collect())?;
let mut msg = vec![]; let mut de = vec![];
for i in 0..2 {
match read_dir_entries(bpaths[i]) {
Err(e) => { msg.push(format!("no dir entries [{}] [{}]", e, bpaths[i])); },
Ok(ent) => { de.push(ent); }
}
}
if msg.len() > 0 || de.len() < 2 { return Err(msg.join("\x0A").into()); }
for pe in &de[0] {
let p = &pe.path(); let mut q = PathBuf::from(""); let mut f = false;
for qe in &de[1] {
let qpb = &qe.path();
if qpb.file_name() == p.file_name() { q = qpb.clone(); f = true; break; }
}
let mut msg = vec![]; let mut sz = vec![];
let mut digest = vec![];
if p.is_file() {
for p in [p, &q].iter() {
sz.push(match file_meta(p) {
Err(e) => { msg.push(format!("metadata: {}", e)); 0 },
Ok(mf) => { mf.len() }
});
}
if sz[0] != sz[1] {
for _i in 0..2 { digest.push("".to_string()); } } else {
for (i, p) in [p, &q].iter().enumerate() {
digest.push(match md5sum(p, sz[i]) {
Err(e) => { msg.push(format!("md5sum: {}", e)); "".to_string() },
Ok(s) => { s }
});
}
}
total += sz[0];
if msg.len() > 0 || sz.len() < 2 || digest.len() < 2 {
f = false;
}
if sz[0] != sz[1] || digest[0] != digest[1] { f = false; }
}
if p.is_dir() || (inf || !f) {
print!("{}", if f {"T"}else{"F"});
print!("{}{}", depth, if p.is_dir() {"+"}else{"-"});
println!("{}", p.display()); if !p.is_dir() {
for i in 0..2 { println!(" {} {}", sz[i], digest[i]); }
}
}
if p.is_dir() {
let e = pe.file_name().to_str().expect("invalid_name").to_string();
if !ignores.contains(&e) {
let st = [p, &q].iter().map(|p| {
p.to_str().expect("invalid_path")
}).collect::<Vec<_>>();
total += walk_dir_entries(inf, ignores, &st, dep + 1)?;
}
}
}
Ok(total)
}
#[macro_export]
macro_rules! walk_dir_entries {
($inf: expr, $ignores: expr, $bpaths: expr) => {
walk_dir_entries($inf, $ignores, $bpaths, 0)
};
($inf: expr, $ignores: expr) => {
walk_dir_entries($inf, $ignores, vec![".", "."], 0)
};
}
pub fn walker(inf: bool, dirs: Vec<PathBuf>) -> Result<(), Box<dyn Error>> {
let ignores = vec![".git", "target"].into_iter().map(|s|
s.to_string()).collect();
let f = |pbs: &Vec<&str>| {
println!("[{}] - [{}]", pbs[0], pbs[1]);
let t = match walk_dir_entries!(inf, &ignores, pbs) {
Err(e) => { eprintln!("{:?}", e); 0 },
Ok(t) => t
};
println!("total: {}", t);
};
let mut pbs = dirs.iter().map(|p| p.to_str().unwrap()).collect::<Vec<_>>();
f(&pbs);
pbs.reverse();
f(&pbs);
Ok(())
}
pub fn take2<T>(args: T) -> Vec<PathBuf> where T: Iterator<Item = String> {
let mut dirs = Vec::<PathBuf>::new();
for a in args {
let p = PathBuf::from(a);
if !p.exists() { continue; }
if !p.is_dir() { continue; }
dirs.push(p);
}
println!("dirs: {}", dirs.len());
for (i, p) in dirs.iter().enumerate() {
println!("dirs[{}]: {}", i, p.display());
}
if dirs.len() < 2 {
println!("Usage: {} a dir0 dir1 ...", env!("CARGO_PKG_NAME"));
return vec!["src", "src"].into_iter().map(|s| PathBuf::from(s)).collect();
}
dirs
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_walker() {
assert_eq!(walker(false, take2(std::env::args().skip(1))).unwrap(), ());
}
}