use derive_builder::Builder;
use relative_path::RelativePathBuf;
use sha2::Digest;
use std::io::{self, Read, Write};
use std::path::{Path, PathBuf};
pub struct FileAttr {
pub path: PathBuf,
size: Option<usize>,
md5sum: Option<String>,
sha256sum: Option<String>,
sha512sum: Option<String>,
}
pub fn copy_with_buf<R: Read, W: Write>(
reader: &mut R,
writer: &mut W,
buf_size: usize,
) -> io::Result<()> {
let mut buf = vec![0u8; buf_size];
loop {
let len = match reader.read(&mut buf) {
Ok(0) => return Ok(()), Ok(len) => len,
Err(ref e) if e.kind() == io::ErrorKind::Interrupted => continue,
Err(e) => return Err(e),
};
writer.write_all(&buf[..len])?;
}
}
const HASH_BUF_SIZE: usize = 10 * 1024 * 1024;
impl FileAttr {
fn calc_md5sum(&self) -> io::Result<String> {
let mut hasher = md5::Context::new();
let mut file = std::fs::File::open(&self.path)?;
copy_with_buf(&mut file, &mut hasher, HASH_BUF_SIZE)?;
Ok(hex::encode(hasher.finalize().to_vec()))
}
fn calc_sha256sum(&self) -> io::Result<String> {
let mut hasher = sha2::Sha256::new();
let mut file = std::fs::File::open(&self.path)?;
copy_with_buf(&mut file, &mut hasher, HASH_BUF_SIZE)?;
Ok(hex::encode(hasher.finalize().to_vec()))
}
fn calc_sha512sum(&self) -> io::Result<String> {
let mut hasher = sha2::Sha512::new();
let mut file = std::fs::File::open(&self.path)?;
copy_with_buf(&mut file, &mut hasher, HASH_BUF_SIZE)?;
Ok(hex::encode(hasher.finalize().to_vec()))
}
fn check_hash<F>(&self, name: &str, expected: &str, hash_fn: F) -> bool
where
F: Fn(&Self) -> io::Result<String>,
{
match hash_fn(self) {
Ok(actual) => {
if actual == expected {
log::debug!(path:?=self.path; "{} matched", name);
true
} else {
log::error!(path:?=self.path, expected, actual; "{} mismatch", name);
false
}
}
Err(e) => {
log::error!(path:?=self.path, e:err; "failed to calculate {}", name);
false
}
}
}
pub fn check(&self) -> bool {
if !self.path.exists() {
log::trace!(path:?=self.path; "file not exists, skip checking");
return true; }
if let Some(size) = self.size {
if let Ok(metadata) = self.path.metadata() {
if metadata.is_file() {
if metadata.len() != size as u64 {
log::error!(path:?=self.path, expected=size, actual=metadata.len(); "file size mismatch");
return false;
}
}
}
}
if let Some(md5sum) = &self.md5sum {
return self.check_hash("md5sum", md5sum, Self::calc_md5sum);
}
if let Some(sha256sum) = &self.sha256sum {
return self.check_hash("sha256sum", sha256sum, Self::calc_sha256sum);
}
if let Some(sha512sum) = &self.sha512sum {
return self.check_hash("sha512sum", sha512sum, Self::calc_sha512sum);
}
log::warn!(path:?=self.path; "no supported checksum algorithm, skip checking");
true
}
}
#[derive(Default, Debug, Builder)]
#[builder(setter(into), default)]
pub struct RelativeFileAttr {
pub path: RelativePathBuf,
pub size: Option<usize>,
pub md5sum: Option<String>,
pub sha256sum: Option<String>,
pub sha512sum: Option<String>,
}
impl RelativeFileAttr {
pub fn into_absolute<P>(self, base_dir: P) -> FileAttr
where
P: AsRef<Path>,
{
FileAttr {
path: self.path.to_path(base_dir),
size: self.size,
md5sum: self.md5sum,
sha256sum: self.sha256sum,
sha512sum: self.sha512sum,
}
}
}