2025-06-04 13:25:00 +00:00
|
|
|
mod checked;
|
2025-06-10 23:39:08 +00:00
|
|
|
mod chunk;
|
2025-06-04 13:25:00 +00:00
|
|
|
mod uploading;
|
2025-05-27 00:42:43 +00:00
|
|
|
|
2025-06-26 09:56:29 +00:00
|
|
|
use std::{ffi::OsStr, fs, io::Read, path::Path};
|
2025-06-24 19:34:11 +00:00
|
|
|
|
|
|
|
|
use base64ct::{Base64, Encoding};
|
|
|
|
|
use blake2b_simd::Params as Blake2b;
|
2025-06-06 23:48:10 +00:00
|
|
|
|
2025-06-10 18:20:52 +00:00
|
|
|
pub use checked::Checked;
|
2025-06-10 23:39:08 +00:00
|
|
|
pub use chunk::Chunk;
|
2025-06-26 09:56:29 +00:00
|
|
|
use log::{debug, warn};
|
2025-06-10 18:20:52 +00:00
|
|
|
pub use uploading::Uploading;
|
2025-05-27 00:42:43 +00:00
|
|
|
|
2025-06-26 09:56:29 +00:00
|
|
|
use crate::error;
|
|
|
|
|
|
|
|
|
|
fn compute_file_hash(path: &Path, size: u64, on_progress: impl Fn(u64)) -> error::Result<String> {
|
2025-06-24 19:34:11 +00:00
|
|
|
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 {
|
2025-06-26 09:56:29 +00:00
|
|
|
return Err(error::Error::mismatch(size, bytes_read));
|
2025-06-24 19:34:11 +00:00
|
|
|
}
|
|
|
|
|
|
2025-06-25 10:44:36 +00:00
|
|
|
let result = Base64::encode_string(hasher.finalize().as_bytes());
|
|
|
|
|
debug!("hashed {:?}: {result:?}", path.display());
|
|
|
|
|
Ok(result)
|
2025-06-24 19:34:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn check_file_hash(
|
2025-06-25 10:44:36 +00:00
|
|
|
path: &Path,
|
2025-06-24 19:34:11 +00:00
|
|
|
size: u64,
|
2025-06-25 10:44:36 +00:00
|
|
|
hash: Option<&String>,
|
2025-06-24 19:34:11 +00:00
|
|
|
on_progress: impl Fn(u64),
|
2025-06-26 09:56:29 +00:00
|
|
|
) -> error::Result<()> {
|
|
|
|
|
let Some(expected) = hash else {
|
|
|
|
|
return Err(error::Error::mismatch("hash", path.display()));
|
2025-06-25 10:44:36 +00:00
|
|
|
};
|
2025-06-24 19:34:11 +00:00
|
|
|
|
2025-06-26 09:56:29 +00:00
|
|
|
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(error::Error::mismatch(expected, actual))
|
|
|
|
|
}
|
2025-06-24 19:34:11 +00:00
|
|
|
}
|
|
|
|
|
|
2025-06-10 18:20:52 +00:00
|
|
|
pub trait FileTrait<'t> {
|
2025-06-06 23:48:10 +00:00
|
|
|
/// 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")
|
|
|
|
|
}
|
|
|
|
|
|
2025-06-15 00:44:28 +00:00
|
|
|
/// get a reference to the file's name
|
2025-06-06 23:48:10 +00:00
|
|
|
fn get_name(&'t self) -> &'t str;
|
|
|
|
|
|
2025-06-15 00:44:28 +00:00
|
|
|
/// get the file's size
|
2025-06-06 23:48:10 +00:00
|
|
|
fn get_size(&self) -> u64;
|
2025-06-24 19:34:11 +00:00
|
|
|
|
2025-06-26 09:56:29 +00:00
|
|
|
fn check_hash(&self, on_progress: impl Fn(u64)) -> error::Result<()>;
|
2025-06-06 23:48:10 +00:00
|
|
|
}
|