mod checked; mod chunk; mod uploading; use std::{ffi::OsStr, fs, io::Read, path::Path}; use base64::{Engine, prelude::BASE64_STANDARD_NO_PAD as BASE64}; use blake2b_simd::Params as Blake2b; pub use checked::Checked; pub use chunk::Chunk; use log::{debug, warn}; pub use uploading::Uploading; fn compute_file_hash(path: &Path, size: u64, on_progress: impl Fn(u64)) -> crate::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(crate::Error::mismatch(size, bytes_read)); } let result = BASE64.encode(hasher.finalize()); debug!("hashed {:?}: {result:?}", path.display()); Ok(result) } fn check_file_hash( path: &Path, size: u64, hash: Option<&String>, on_progress: impl Fn(u64), ) -> crate::Result<()> { let Some(expected) = hash else { return Err(crate::Error::mismatch("hash", path.display())); }; let actual = &compute_file_hash(path, size, on_progress)?; if expected == actual { debug!("hash matches {expected:?}"); Ok(()) } else { warn!("hash mismatch for file {:?}", path.display()); Err(crate::Error::mismatch(expected, actual)) } } 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)) -> crate::Result<()>; }