use std::collections::{HashMap, HashSet};
use std::fs;
use std::path::Path;
use crate::store::disk::decompress_node;
use crate::tree::{Hash, Node};
use super::error::{MarkSourceId, VacuumError};
use super::inventory::{NodeFile, StoreInventory};
pub type StoreMarks = HashMap<Hash, u64>;
pub fn walk_declared(
inventory: &StoreInventory,
store_dir: &Path,
root: Hash,
source: &MarkSourceId,
marks: &mut StoreMarks,
verified: &mut usize,
) -> Result<(), VacuumError> {
let mut stack = vec![root];
while let Some(hash) = stack.pop() {
if marks.contains_key(&hash) {
continue;
}
let Some(file) = inventory.nodes.get(&hash) else {
return Err(VacuumError::UnresolvableRoot {
source: source.clone(),
root,
missing: hash,
});
};
let node = read_verified(store_dir, file, hash)?;
*verified += 1;
push_children(&node, &mut stack);
marks.insert(hash, file.compressed_len);
}
Ok(())
}
#[derive(Debug)]
pub enum ProbeOutcome {
Resolved(HashSet<Hash>),
Missing(Hash),
}
pub fn probe_store(
inventory: &StoreInventory,
store_dir: &Path,
root: Hash,
marks: &StoreMarks,
verified: &mut usize,
) -> Result<ProbeOutcome, VacuumError> {
let mut resolved = HashSet::new();
let mut stack = vec![root];
while let Some(hash) = stack.pop() {
if resolved.contains(&hash) {
continue;
}
if marks.contains_key(&hash) {
resolved.insert(hash);
continue;
}
let Some(file) = inventory.nodes.get(&hash) else {
return Ok(ProbeOutcome::Missing(hash));
};
let node = read_verified(store_dir, file, hash)?;
*verified += 1;
push_children(&node, &mut stack);
resolved.insert(hash);
}
Ok(ProbeOutcome::Resolved(resolved))
}
pub fn commit_marks(inventory: &StoreInventory, resolved: &HashSet<Hash>, marks: &mut StoreMarks) {
for hash in resolved {
if let Some(file) = inventory.nodes.get(hash) {
marks.insert(*hash, file.compressed_len);
}
}
}
fn push_children(node: &Node, stack: &mut Vec<Hash>) {
if let Node::Internal(internal) = node {
stack.extend(
internal
.children()
.iter()
.map(|(_lower_bound, child_hash)| *child_hash),
);
}
}
fn read_verified(store_dir: &Path, file: &NodeFile, expected: Hash) -> Result<Node, VacuumError> {
let mismatch = || VacuumError::NodeHashMismatch {
store: store_dir.to_path_buf(),
path: file.path.clone(),
};
let compressed = fs::read(&file.path).map_err(|error| VacuumError::Io {
path: file.path.clone(),
error,
})?;
let serialised = decompress_node(&compressed).map_err(|_error| mismatch())?;
let node = Node::deserialise(&serialised).map_err(|_error| mismatch())?;
let (canonical, computed) = node.serialise_and_hash();
if computed != expected || canonical != serialised {
return Err(mismatch());
}
Ok(node)
}