use super::Formatter;
use crate::scanner::{FileNode, TreeStats};
use anyhow::Result;
use sha2::{Digest, Sha256};
use std::io::Write;
use std::path::Path;
pub struct DigestFormatter;
impl Default for DigestFormatter {
fn default() -> Self {
Self::new()
}
}
impl DigestFormatter {
pub fn new() -> Self {
Self
}
fn calculate_tree_hash(&self, nodes: &[FileNode]) -> String {
let mut hasher = Sha256::new();
for node in nodes {
hasher.update(node.depth.to_le_bytes());
hasher.update(
node.path
.file_name()
.unwrap_or_default()
.to_string_lossy()
.as_bytes(),
);
hasher.update([if node.is_dir { 1 } else { 0 }]);
hasher.update(node.size.to_le_bytes());
hasher.update(node.permissions.to_le_bytes());
}
let result = hasher.finalize();
hex::encode(&result[..8])
}
}
impl Formatter for DigestFormatter {
fn format(
&self,
writer: &mut dyn Write,
nodes: &[FileNode],
stats: &TreeStats,
_root_path: &Path,
) -> Result<()> {
let tree_hash = self.calculate_tree_hash(nodes);
write!(
writer,
"HASH: {} F:{} D:{} S:{:x}",
tree_hash, stats.total_files, stats.total_dirs, stats.total_size,
)?;
if !stats.file_types.is_empty() {
let mut types: Vec<_> = stats.file_types.iter().collect();
types.sort_by(|a, b| b.1.cmp(a.1));
let types_str: Vec<String> = types
.iter()
.take(5)
.map(|(ext, count)| format!("{}:{}", ext, count))
.collect();
write!(writer, " TYPES: {}", types_str.join(" "))?;
}
writeln!(writer)?;
Ok(())
}
}