use std::collections::HashMap;
use std::fs::File;
use std::path::Path;
use crate::manifest::ArtifactKind;
use crate::{Error, Result};
pub fn hash_file(path: &Path) -> Result<String> {
let file = File::open(path).map_err(|e| Error::io("open", path, e))?;
let mut hasher = blake3::Hasher::new();
hasher
.update_reader(file)
.map_err(|e| Error::io("read", path, e))?;
Ok(hasher.finalize().to_hex().to_string())
}
pub fn hash_bytes(bytes: &[u8]) -> String {
blake3::hash(bytes).to_hex().to_string()
}
#[derive(Debug, Clone)]
pub struct CanonicalArtifact {
pub source_path: String,
pub text_path: String,
pub text_hash: String,
pub converter_id: String,
pub converter_version: String,
pub artifact_kind: Option<ArtifactKind>,
pub warnings: Vec<String>,
}
#[derive(Debug, Default)]
pub struct DedupIndex {
by_hash: HashMap<String, CanonicalArtifact>,
}
impl DedupIndex {
pub fn insert(&mut self, source_hash: &str, canonical: CanonicalArtifact) {
self.by_hash
.entry(source_hash.to_string())
.or_insert(canonical);
}
pub fn replace(&mut self, source_hash: &str, canonical: CanonicalArtifact) {
self.by_hash.insert(source_hash.to_string(), canonical);
}
pub fn get(&self, source_hash: &str) -> Option<&CanonicalArtifact> {
self.by_hash.get(source_hash)
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::io::Write;
#[test]
fn hash_bytes_matches_known_vector() {
assert_eq!(
hash_bytes(b""),
"af1349b9f5f9a1a6a0404dea36dcc9499bcb25c9adc112b7cc9a93cae41f3262"
);
}
#[test]
fn hash_file_matches_hash_bytes() {
let mut file = tempfile::NamedTempFile::new().unwrap();
file.write_all(b"mirror me").unwrap();
file.flush().unwrap();
assert_eq!(hash_file(file.path()).unwrap(), hash_bytes(b"mirror me"));
}
#[test]
fn dedup_index_keeps_first_insert() {
let mut index = DedupIndex::default();
let first = CanonicalArtifact {
source_path: "a.txt".to_string(),
text_path: "a.txt.txt".to_string(),
text_hash: "aa".to_string(),
converter_id: "text-passthrough".to_string(),
converter_version: "1.0.0".to_string(),
artifact_kind: Some(ArtifactKind::Text),
warnings: Vec::new(),
};
let mut second = first.clone();
second.source_path = "b.txt".to_string();
index.insert("h1", first);
index.insert("h1", second.clone());
assert_eq!(index.get("h1").unwrap().source_path, "a.txt");
assert!(index.get("h2").is_none());
index.replace("h1", second);
assert_eq!(index.get("h1").unwrap().source_path, "b.txt");
}
}