1use crate::category::FileCategory;
2use serde::Serialize;
3use std::collections::HashMap;
4use std::path::Path;
5
6#[derive(Debug)]
11pub struct DirNode {
12 pub children: HashMap<String, DirNode>,
13 pub file_size: u64,
15 pub file_count: usize,
17}
18
19#[derive(Debug, Serialize)]
21pub struct EChartsItemStyle {
22 pub color: FileCategory,
23}
24
25#[derive(Debug, Serialize)]
27pub struct EChartsNode {
28 pub name: String,
29 #[serde(skip_serializing_if = "Option::is_none")]
30 pub value: Option<u64>,
31 #[serde(skip_serializing_if = "Vec::is_empty")]
32 pub children: Vec<EChartsNode>,
33 #[serde(rename = "itemStyle", skip_serializing_if = "Option::is_none")]
34 pub item_style: Option<EChartsItemStyle>,
35}
36
37impl DirNode {
38 pub fn new() -> Self {
39 Self {
40 children: HashMap::new(),
41 file_size: 0,
42 file_count: 0,
43 }
44 }
45
46 pub fn insert(&mut self, path_components: &[&str], size: u64) {
48 if path_components.is_empty() {
49 return;
50 }
51
52 if path_components.len() == 1 {
53 let leaf = self
55 .children
56 .entry(path_components[0].to_string())
57 .or_default();
58 leaf.file_size += size;
59 leaf.file_count += 1;
60 } else {
61 let child = self
63 .children
64 .entry(path_components[0].to_string())
65 .or_default();
66 child.insert(&path_components[1..], size);
67 }
68 }
69
70 pub fn total_size(&self) -> u64 {
72 let children_size: u64 = self.children.values().map(|c| c.total_size()).sum();
73 self.file_size + children_size
74 }
75
76 pub fn total_file_count(&self) -> usize {
78 let children_count: usize = self.children.values().map(|c| c.total_file_count()).sum();
79 self.file_count + children_count
80 }
81
82 pub fn to_echarts(&self, name: &str) -> EChartsNode {
87 if self.children.is_empty() {
88 let category = FileCategory::from_path(Path::new(name));
90 return EChartsNode {
91 name: name.to_string(),
92 value: Some(self.file_size),
93 children: Vec::new(),
94 item_style: Some(EChartsItemStyle { color: category }),
95 };
96 }
97
98 let mut children: Vec<EChartsNode> = self
99 .children
100 .iter()
101 .map(|(child_name, child_node)| child_node.to_echarts(child_name))
102 .collect();
103
104 children.sort_by(|a, b| {
106 let size_a = echarts_subtree_size(a);
107 let size_b = echarts_subtree_size(b);
108 size_b.cmp(&size_a)
109 });
110
111 if children.len() == 1 && !children[0].children.is_empty() {
113 let child = children.remove(0);
114 let collapsed_name = format!("{}/{}", name, child.name);
115 return EChartsNode {
116 name: collapsed_name,
117 value: child.value,
118 children: child.children,
119 item_style: None,
120 };
121 }
122
123 EChartsNode {
124 name: name.to_string(),
125 value: None,
126 children,
127 item_style: None,
128 }
129 }
130}
131
132impl Default for DirNode {
133 fn default() -> Self {
134 Self::new()
135 }
136}
137
138fn echarts_subtree_size(node: &EChartsNode) -> u64 {
140 if let Some(val) = node.value {
141 val
142 } else {
143 node.children.iter().map(echarts_subtree_size).sum()
144 }
145}
146
147pub fn split_path(path: &str) -> Vec<&str> {
149 path.split(['/', '\\']).filter(|s| !s.is_empty()).collect()
150}
151
152pub fn build_tree(files: &[(&str, u64)]) -> DirNode {
154 let mut root = DirNode::new();
155 for (path, size) in files {
156 let components = split_path(path);
157 if !components.is_empty() {
158 root.insert(&components, *size);
159 }
160 }
161 root
162}