creeper 1.0.0-alpha.9

Minecraft Package Manager
use std::{
    fmt::Display,
    fs::File,
    io::{BufReader, Read},
    path::Path,
    str::FromStr,
};

use anyhow::anyhow;
use const_hex::ToHexExt;
use ring::digest::{Algorithm, Context, SHA1_FOR_LEGACY_USE_ONLY, SHA256};
use tokio::task::spawn_blocking;
use tracing::debug;

pub async fn blake3(file: impl AsRef<Path>) -> anyhow::Result<String> {
    fn calc(file: impl AsRef<Path>) -> anyhow::Result<String> {
        let reader = File::open(file)?;
        let mut hasher = blake3::Hasher::new();
        hasher.update_reader(reader)?;
        let hash = hasher.finalize().to_hex().to_string();
        Ok(hash)
    }
    let file = file.as_ref().to_owned();
    spawn_blocking(|| calc(file)).await?
}

pub async fn sha1(file: impl AsRef<Path>) -> anyhow::Result<String> {
    let file = file.as_ref().to_owned();
    spawn_blocking(|| ring(file, &SHA1_FOR_LEGACY_USE_ONLY)).await?
}

pub async fn sha256(file: impl AsRef<Path>) -> anyhow::Result<String> {
    let file = file.as_ref().to_owned();
    spawn_blocking(|| ring(file, &SHA256)).await?
}

fn ring(file: impl AsRef<Path>, algorithm: &'static Algorithm) -> anyhow::Result<String> {
    let mut reader = BufReader::new(File::open(file)?);
    let mut ctx = Context::new(algorithm);
    let mut buf = [0u8; 4096];
    loop {
        let n = reader.read(&mut buf)?;
        if n == 0 {
            break;
        }
        ctx.update(&buf[..n]);
    }
    let digest = ctx.finish();
    Ok(digest.encode_hex())
}

#[derive(Clone, Hash)]
pub struct Checksum {
    pub function: HashFunc,
    pub hex_hash: String,
}

impl Checksum {
    pub fn blake3(hex_hash: String) -> Self {
        Self {
            function: HashFunc::Blake3,
            hex_hash,
        }
    }

    pub fn sha1(hex_hash: String) -> Self {
        Self {
            function: HashFunc::Sha1,
            hex_hash,
        }
    }

    pub fn sha256(hex_hash: String) -> Self {
        Self {
            function: HashFunc::Sha256,
            hex_hash,
        }
    }

    pub async fn check(&self, file: impl AsRef<Path>) -> anyhow::Result<bool> {
        let hash = self.function.calc(file).await?;
        Ok(self.hex_hash == hash)
    }
}

impl Display for Checksum {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}={}", self.function, self.hex_hash)
    }
}

#[derive(Clone, Copy, PartialEq, Eq, Hash)]
pub enum HashFunc {
    Blake3,
    Sha1,
    Sha256,
}

impl HashFunc {
    pub async fn calc(&self, file: impl AsRef<Path>) -> anyhow::Result<String> {
        let file = file.as_ref();

        let sum = match self {
            HashFunc::Blake3 => blake3(file).await,
            HashFunc::Sha1 => sha1(file).await,
            HashFunc::Sha256 => sha256(file).await,
        }?;

        debug!("calculated {} {} = {}", self, file.display(), sum);
        Ok(sum)
    }
}

impl Display for HashFunc {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        let name = match self {
            HashFunc::Blake3 => "blake3",
            HashFunc::Sha1 => "sha1",
            HashFunc::Sha256 => "sha256",
        };
        write!(f, "{name}")
    }
}

impl FromStr for HashFunc {
    type Err = anyhow::Error;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s {
            "blake3" => Ok(Self::Blake3),
            "sha1" => Ok(Self::Sha1),
            "sha256" => Ok(Self::Sha256),
            _ => Err(anyhow!("unknown hash function: {s}")),
        }
    }
}