use crate::category::FileCategory;
use serde::Serialize;
use std::collections::HashMap;
use std::path::Path;
#[derive(Debug)]
pub struct DirNode {
pub children: HashMap<String, DirNode>,
pub file_size: u64,
pub file_count: usize,
}
#[derive(Debug, Serialize)]
pub struct EChartsItemStyle {
pub color: FileCategory,
}
#[derive(Debug, Serialize)]
pub struct EChartsNode {
pub name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub value: Option<u64>,
#[serde(skip_serializing_if = "Vec::is_empty")]
pub children: Vec<EChartsNode>,
#[serde(rename = "itemStyle", skip_serializing_if = "Option::is_none")]
pub item_style: Option<EChartsItemStyle>,
}
impl DirNode {
pub fn new() -> Self {
Self {
children: HashMap::new(),
file_size: 0,
file_count: 0,
}
}
pub fn insert(&mut self, path_components: &[&str], size: u64) {
if path_components.is_empty() {
return;
}
if path_components.len() == 1 {
let leaf = self
.children
.entry(path_components[0].to_string())
.or_default();
leaf.file_size += size;
leaf.file_count += 1;
} else {
let child = self
.children
.entry(path_components[0].to_string())
.or_default();
child.insert(&path_components[1..], size);
}
}
pub fn total_size(&self) -> u64 {
let children_size: u64 = self.children.values().map(|c| c.total_size()).sum();
self.file_size + children_size
}
pub fn total_file_count(&self) -> usize {
let children_count: usize = self.children.values().map(|c| c.total_file_count()).sum();
self.file_count + children_count
}
pub fn to_echarts(&self, name: &str) -> EChartsNode {
if self.children.is_empty() {
let category = FileCategory::from_path(Path::new(name));
return EChartsNode {
name: name.to_string(),
value: Some(self.file_size),
children: Vec::new(),
item_style: Some(EChartsItemStyle { color: category }),
};
}
let mut children: Vec<EChartsNode> = self
.children
.iter()
.map(|(child_name, child_node)| child_node.to_echarts(child_name))
.collect();
children.sort_by(|a, b| {
let size_a = echarts_subtree_size(a);
let size_b = echarts_subtree_size(b);
size_b.cmp(&size_a)
});
if children.len() == 1 && !children[0].children.is_empty() {
let child = children.remove(0);
let collapsed_name = format!("{}/{}", name, child.name);
return EChartsNode {
name: collapsed_name,
value: child.value,
children: child.children,
item_style: None,
};
}
EChartsNode {
name: name.to_string(),
value: None,
children,
item_style: None,
}
}
}
impl Default for DirNode {
fn default() -> Self {
Self::new()
}
}
fn echarts_subtree_size(node: &EChartsNode) -> u64 {
if let Some(val) = node.value {
val
} else {
node.children.iter().map(echarts_subtree_size).sum()
}
}
pub fn split_path(path: &str) -> Vec<&str> {
path.split(['/', '\\']).filter(|s| !s.is_empty()).collect()
}
pub fn build_tree(files: &[(&str, u64)]) -> DirNode {
let mut root = DirNode::new();
for (path, size) in files {
let components = split_path(path);
if !components.is_empty() {
root.insert(&components, *size);
}
}
root
}