mod utils; use clap::Parser; use std::collections::HashMap; use std::io::Write; use std::{fs, io, time}; use utils::{Metadata, Zsdiff, get_hash}; use walkdir::WalkDir; struct FileInfo { path: String, relative_path: String, // Without dir prefix hash: String, } async fn walk_dir(dir: String) -> HashMap { let mut hash_list: HashMap = HashMap::new(); for e in WalkDir::new(&dir) { let e = e.unwrap(); let path = e.path(); if path.is_dir() { continue; } let content = fs::read(path).unwrap(); let hash = get_hash(content).await; let path_str = path.display().to_string(); let file_info = FileInfo { relative_path: path_str[dir.len() + 1..].to_string(), path: path_str, hash: hash.clone(), }; hash_list.entry(hash).or_insert(file_info); } hash_list } async fn compare_hashes(old: HashMap, new: HashMap) -> Zsdiff { let mut diff_files: HashMap> = HashMap::new(); let mut remove_files: Vec = vec![]; let mut hashes: HashMap = HashMap::new(); for (_, info) in &old { remove_files.push(info.relative_path.clone()); } for (new_hash, new_fileinfo) in &new { let old_fileinfo = old.get(new_hash); remove_files.retain(|filename| !filename.eq(&new_fileinfo.relative_path)); if old_fileinfo.is_none() { let path = new_fileinfo.relative_path.clone(); diff_files.insert(path.clone(), fs::read(new_fileinfo.path.clone()).unwrap()); hashes.insert( new_fileinfo.relative_path.clone(), new_fileinfo.hash.clone(), ); } } Zsdiff { content: diff_files.clone(), metadata: Metadata { diff_files: diff_files.keys().cloned().collect(), hashes, remove_files, }, } } pub async fn zsdiff( filename: String, old: String, new: String, level: i32, ) -> Result<(), io::Error> { let output_filename = &format!("{}.zdiff", filename); let old_hashes = walk_dir(old).await; let new_hashes = walk_dir(new).await; let compare_hashes = compare_hashes(old_hashes, new_hashes).await; let parts = compare_hashes.to_vec().await; let mut size_before = 0; for p in &parts { size_before += p.len(); } let now = time::Instant::now(); utils::compress_parts(parts, &fs::File::create(output_filename)?, level).await; let output_data = fs::read(output_filename)?; let size_after = output_data.len(); let output_hash = get_hash(output_data).await; fs::File::create(format!("{}.md5", output_filename))?.write_all(output_hash.as_bytes())?; let elapsed = now.elapsed(); println!("Zsdiff hash: {}", output_hash); println!("Size before: {:.1?}KB", size_before / 1024); println!("Size after: {:.1?}KB", size_after / 1024); println!( "Compress ratio: {:.2?}%", size_after as f64 / size_before as f64 * 100.0 ); print!("Time elapsed: {:.2?}s", elapsed); Ok(()) } #[derive(Parser)] struct Args { #[arg(short, long)] filename: String, #[arg(short, long, default_value_t = 11)] compress_level: i32, #[arg(short, long)] old: String, #[arg(short, long)] new: String, } #[tokio::main] async fn main() -> io::Result<()> { let args = Args::parse(); zsdiff(args.filename, args.old, args.new, args.compress_level).await }