//! Stable sha256 of a path's bytes.

use std::fs::File;
use std::io::Read;

use camino::Utf8PathBuf;
use sha2::{Digest, Sha256};

use crate::ScanError;

const BUF_SIZE: usize = 65_536;

/// Hash a file's contents. Returns hex-encoded lowercase sha256.
#[allow(clippy::missing_errors_doc)]
pub fn sha256_file(path: &Utf8PathBuf) -> Result<String, ScanError> {
    let mut f = File::open(path.as_std_path()).map_err(|e| ScanError::Io {
        path: path.clone(),
        source: e,
    })?;
    let mut hasher = Sha256::new();
    let mut buf = vec![0u8; BUF_SIZE];
    loop {
        let n = f.read(&mut buf).map_err(|e| ScanError::Io {
            path: path.clone(),
            source: e,
        })?;
        if n == 0 {
            break;
        }
        hasher.update(&buf[..n]);
    }
    Ok(hex::encode(hasher.finalize()))
}

#[cfg(test)]
mod tests {
    use std::io::Write;

    use super::*;

    #[test]
    fn known_sha256_of_empty_file() {
        let dir = tempfile::tempdir().expect("tempdir");
        let p = dir.path().join("empty.bin");
        File::create(&p)
            .expect("create")
            .write_all(b"")
            .expect("write");
        let utf8: Utf8PathBuf = p.try_into().expect("utf8");
        let h = sha256_file(&utf8).expect("hash");
        assert_eq!(
            h,
            "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
        );
    }
}
