mod checked; mod chunk; mod uploading; use std::{ ffi::OsStr, fs, io::{self, Read}, path::Path, }; use base64ct::{Base64, Encoding}; use blake2b_simd::Params as Blake2b; pub use checked::Checked; pub use chunk::Chunk; use log::debug; pub use uploading::Uploading; fn compute_file_hash(path: &Path, size: u64, on_progress: impl Fn(u64)) -> io::Result { let mut file = fs::File::open(path)?; let mut hasher = Blake2b::new().hash_length(64).to_state(); let mut buf = vec![0u8; 4 * 1024 * 1024]; let mut bytes_read = 0; loop { let n = file.read(&mut buf)?; if n == 0 { break; } hasher.update(&buf[..n]); bytes_read += n as u64; on_progress(n as u64); } if bytes_read != size { return Err(io::Error::other(format!( "Hashed {bytes_read:?} bytes, known file size {size:?}!" ))); } let result = Base64::encode_string(hasher.finalize().as_bytes()); debug!("hashed {:?}: {result:?}", path.display()); Ok(result) } fn check_file_hash( path: &Path, size: u64, hash: Option<&String>, on_progress: impl Fn(u64), ) -> io::Result { let Some(hash) = hash else { debug!("no hash to check for {:?}!", path.display()); return Ok(false); }; let result = *hash == compute_file_hash(path, size, on_progress)?; debug!("matches {:?}: {result:?}", *hash); Ok(result) } pub trait FileTrait<'t> { /// extract the filename part of a `Path` reference /// /// # Panics /// /// Expects `path::Path::file_name` and `ffi::OsStr::to_str` to succeed on the given path fn extract_file_name(p: &'t Path) -> &'t str { p.file_name() .and_then(OsStr::to_str) .expect("bad file name") } /// get a reference to the file's name fn get_name(&'t self) -> &'t str; /// get the file's size fn get_size(&self) -> u64; fn check_hash(&self, on_progress: impl Fn(u64)) -> io::Result; }