apt-mirror-check 1.0.0

Check errors for apt mirror
Documentation
use derive_builder::Builder;
use relative_path::RelativePathBuf;
use sha2::Digest;
use std::io::{self, Read, Write};
use std::path::{Path, PathBuf};

/// file with attributes
pub struct FileAttr {
    pub path: PathBuf,
    size: Option<usize>,
    md5sum: Option<String>,
    sha256sum: Option<String>,
    sha512sum: Option<String>,
}

/// 类似于 std::io::copy,但是可以指定缓冲区大小。
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(()), // EOF
            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; // will be downloaded by apt-mirror
        }

        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);
        }

        // no checksum found
        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,
        }
    }
}