diff --git a/src/file/checked.rs b/src/file/checked.rs index 0ff4ea8..127f15f 100644 --- a/src/file/checked.rs +++ b/src/file/checked.rs @@ -54,6 +54,12 @@ impl Checked { } } + /// calculate and store hash for this file + /// + /// # Errors + /// + /// - from `file::compute_hash` + /// - Mismatch if file already hashed pub fn hash(&mut self, f: impl Fn(u64)) -> crate::Result<()> { if self.hash.is_some() { return Err(crate::Error::mismatch("unhashed file", self.path.display())); @@ -64,15 +70,14 @@ impl Checked { Ok(()) } - /// start uploading this file + /// starts uploading this file /// - /// - tries to create a new entry in a share - /// - expects endpoint like `{base_uri}/alias/upload/{share_id}/files/tus` + /// - tries to create a new file using the client /// - consumes `self` into a `file::Uploading` struct /// /// # Errors /// - /// TODO documentation after `ClientError` rework + /// - from `sharry::Client::file_create` pub fn start_upload( self, client: &impl sharry::Client, @@ -87,19 +92,15 @@ impl Checked { } impl<'t> FileTrait<'t> for Checked { - /// get a reference to the file's name - /// - /// Uses `SharryFile::extract_file_name`, which may **panic**! fn get_name(&'t self) -> &'t str { ::extract_file_name(&self.path) } - /// get the file's size fn get_size(&self) -> u64 { self.size } - fn check_hash(&self, on_progress: impl Fn(u64)) -> crate::Result<()> { + fn check_hash(&self, on_progress: impl FnMut(u64)) -> crate::Result<()> { super::check_hash( &self.path, self.size, diff --git a/src/file/mod.rs b/src/file/mod.rs index 9340892..cb4c665 100644 --- a/src/file/mod.rs +++ b/src/file/mod.rs @@ -12,22 +12,38 @@ pub use chunk::Chunk; use log::{debug, warn}; pub use uploading::Uploading; +/// how many bytes to hash at once (default: 4 MiB) +const HASH_CHUNK_SIZE: usize = 4 * 1024 * 1024; + +/// compute hash for a file given its path. +/// Hash function: BLAKE2b, 512 bit +/// +/// # Params +/// +/// - `path` to the file to hash +/// - `size` of that file +/// - `on_progress` will be called for each processed chunk (max. `HASH_CHUNK_SIZE`) +/// +/// # Errors +/// +/// - from `fs::File::open` and `fs::File::read` +/// - Mismatch if given `size` does not match the file's size fn compute_hash(path: &Path, size: u64, mut on_progress: impl FnMut(u64)) -> crate::Result { let mut file = fs::File::open(path)?; // Blake2b-512 hasher (64 * 8 bit) let mut hasher = Blake2b::new().hash_length(64).to_state(); - // buffer (4 MiB) - let mut buf = vec![0; 4 * 1024 * 1024]; + // buffer + let mut buffer = vec![0; HASH_CHUNK_SIZE]; let mut bytes_read = 0; loop { - let n = file.read(&mut buf)?; + let n = file.read(&mut buffer)?; if n == 0 { break; } - hasher.update(&buf[..n]); + hasher.update(&buffer[..n]); // `buf` size must be < 2 EiB bytes_read += n as u64; @@ -43,16 +59,32 @@ fn compute_hash(path: &Path, size: u64, mut on_progress: impl FnMut(u64)) -> cra Ok(result) } +/// check hash for a file given its path, return Ok(()) on success +/// +/// # Params +/// +/// - `path` to the file to hash +/// - `size` of that file +/// - optional known `hash` +/// - `on_progress` will be called for each processed chunk (max. `HASH_CHUNK_SIZE`) +/// +/// # Errors +/// +/// - from `file::compute_hash` +/// - Mismatch if `hash` is `None` +/// - Mismatch if given `hash` does not match the computed hash fn check_hash( path: &Path, size: u64, hash: Option<&str>, on_progress: impl FnMut(u64), ) -> crate::Result<()> { + // check if hash is None let Some(expected) = hash else { return Err(crate::Error::mismatch("hash", path.display())); }; + // compute and check new hash let actual = &compute_hash(path, size, on_progress)?; if expected == actual { @@ -77,12 +109,19 @@ pub trait FileTrait<'t> { } /// get a reference to the file's name + /// + /// Uses `file::FileTrait::extract_file_name`, which may **panic**! 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<()>; + /// check this file's hash, return Ok(()) on success + /// + /// # Errors + /// + /// - from `file::check_hash` + fn check_hash(&self, on_progress: impl FnMut(u64)) -> crate::Result<()>; } #[cfg(test)] diff --git a/src/file/uploading.rs b/src/file/uploading.rs index 9ec85e9..70ea34f 100644 --- a/src/file/uploading.rs +++ b/src/file/uploading.rs @@ -95,9 +95,6 @@ impl Uploading { } impl<'t> FileTrait<'t> for Uploading { - /// get a reference to the file's name - /// - /// Uses `SharryFile::extract_file_name`, which may **panic**! fn get_name(&'t self) -> &'t str { ::extract_file_name(&self.path) } @@ -106,7 +103,7 @@ impl<'t> FileTrait<'t> for Uploading { self.size } - fn check_hash(&self, on_progress: impl Fn(u64)) -> crate::Result<()> { + fn check_hash(&self, on_progress: impl FnMut(u64)) -> crate::Result<()> { super::check_hash( &self.path, self.size,