#![allow(dead_code)] pub const BUFFER_SIZE: usize = 4096; #[macro_use] mod log; mod bytes; mod ext; pub use ext::*; mod error; mod hash; mod container; mod config; mod arg; mod proc; #[cfg(test)] mod test { use super::*; use std::{ path::Path, }; #[test] pub fn args() -> Result<(), arg::Error> { macro_rules! string_literal { ($($strings:expr),*) => { vec![$( format!($strings), )*] } } let args = string_literal!["-lsd", "--load-file", "hello", "--load-save", "load-save!", "--", "test-input", "test-input", "test-input-3", "test-input-2"]; println!("{:?}", arg::parse(args)?); Ok(()) } #[test] pub fn test() -> Result<(), error::Error> { let mut cont = container::DupeMap::new(); let mode = config::Mode::default(); let path = Path::new("test-input"); assert_eq!(proc::DupeCount{total:4, dupes:2}, proc::do_dir(path, 0, &mut cont, &mode)?); Ok(()) } #[cfg(feature="threads")] pub async fn _test_async() -> Result<(), error::Error> { use std::sync::Arc; use tokio::{ sync::Mutex, }; let cont = Arc::new(Mutex::new(container::DupeMap::new())); let mode = config::Mode::default(); let path = Path::new("test-input"); assert_eq!(proc::DupeCount{total:4, dupes:2}, proc::do_dir_async(path, 0, cont, mode).await?); Ok(()) } #[cfg(feature="threads")] #[test] pub fn test_async() -> Result<(), error::Error> { tokio_test::block_on(_test_async()) } } fn parse_args() -> Result { match arg::parse_args()? { arg::Output::Normal(conf) => Ok(conf), _ => arg::usage(), } } #[cfg_attr(feature="threads", tokio::main)] #[cfg(feature="threads")] async fn main() -> Result<(), Box> { use tokio::{ fs::{ OpenOptions, }, sync::{ Mutex }, }; use std::{ path::Path, sync::Arc, }; let args = parse_args().into_string()?; let lmode = &args.mode.logging_mode; log!(Debug, lmode => "Args parsed: {:?}", args); let mut children = Vec::new(); let mut hashes = container::DupeMap::new(); // Load hashes for (transient, load) in args.load.iter().map(|x| (false, x)).chain(args.save.iter().map(|x| (true, x))) { let load = Path::new(load); if load.exists() { if load.is_file() { if let Some(mut file) = OpenOptions::new() .read(true) .open(load).await.log_and_forget(lmode, log::Level::Warning)? { log!(Info, lmode => "Hashes loading from {:?}", load); args.mode.error_mode.handle(hashes.load_async(&mut file, transient).await).log_and_forget(lmode, if transient {log::Level::Info} else {log::Level::Warning})?; } } else { log!(Warning, lmode => "Exclusing directory from load path {:?}", load); } } else { log!(Info, lmode => "Ignoring non-existant load path {:?}", load); } } log!(Debug, lmode => "Loaded hashes: {:?}", hashes); log!(Info, lmode => "Starting checks (threaded)"); let hashes = Arc::new(Mutex::new(hashes)); for path in args.paths.iter() { let path = Path::new(path); if path.is_dir() { log!(Debug, lmode => "Spawning for {:?}", path); let mode = args.mode.clone(); let path = path.to_owned(); let hashes= Arc::clone(&hashes); children.push(tokio::task::spawn(async move { log!(Debug, mode.logging_mode => " + {:?}", path); let res = mode.error_mode.handle(proc::do_dir_async(path.clone(), 0, hashes, mode.clone()).await).log_and_forget(&mode.logging_mode, log::Level::Error); log!(Info, mode.logging_mode => " - {:?}", path); res })); } } log!(Info, lmode => "Waiting on children"); let mut done = proc::DupeCount::default(); for child in children.into_iter() { done += args.mode.error_mode.handle(child.await?)?.unwrap_or_default().unwrap_or_default().unwrap_or_default(); } log!(Info, lmode => "Found: {:?}", done); let hashes = hashes.lock().await; log!(Debug, lmode => "New hashes: {:?}", hashes); for save in args.save.iter() { let save = Path::new(save); log!(Info, lmode => "Saving hashes to {:?}", save); if let Some(mut file) = OpenOptions::new() .create(true) //.append(true) .truncate(true) .write(true) .open(save).await.log_and_forget(lmode, log::Level::Warning)? { args.mode.error_mode.handle(hashes.save_async(&mut file).await).log_and_forget(lmode, log::Level::Warning)?; } } Ok(()) } #[cfg(not(feature="threads"))] fn main() -> Result<(), Box> { use std::{ path::Path, fs::{ OpenOptions, }, }; let args = parse_args().into_string()?; let lmode = &args.mode.logging_mode; log!(Debug, lmode => "Args parsed: {:?}", args); let mut hashes = container::DupeMap::new(); // Load hashes for load in args.load.iter() { let load = Path::new(load); if load.exists() { if load.is_file() { if let Some(mut file) = OpenOptions::new() .read(true) .open(load).log_and_forget(lmode, log::Level::Warning)? { log!(Info, lmode => "Hashes loading from {:?}", load); args.mode.error_mode.handle(hashes.load(&mut file)).log_and_forget(lmode, log::Level::Warning)?; } } else { log!(Warning, lmode => "Exclusing directory from load path {:?}", load); } } else { log!(Info, lmode => "Ignoring non-existant load path {:?}", load); } } log!(Debug, lmode => "Loaded hashes: {:?}", hashes); log!(Info, lmode => "Starting checks (threaded)"); let mut done = proc::DupeCount::default(); for path in args.paths.iter() { let path = Path::new(path); if path.is_dir() { log!(Debug, lmode => " + {:?}", path); done += args.mode.error_mode.handle(proc::do_dir(path.clone(), 0, &mut hashes, &args.mode)).log_and_forget(lmode, log::Level::Error)?.unwrap_or_default().unwrap_or_default(); log!(Info, lmode => " - {:?}", path); } } log!(Info, lmode => "Found: {:?}", done); log!(Debug, lmode => "New hashes: {:?}", hashes); for save in args.save.iter() { let save = Path::new(save); log!(Info, lmode => "Saving hashes to {:?}", save); if let Some(mut file) = OpenOptions::new() .create(true) //.append(true) .truncate(true) .write(true) .open(save).log_and_forget(lmode, log::Level::Warning)? { args.mode.error_mode.handle(hashes.save(&mut file)).log_and_forget(lmode, log::Level::Warning)?; } } Ok(()) }