1use crate::category::FileCategory;
2use std::path::{Path, PathBuf};
3use std::time::SystemTime;
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
7pub struct NodeId(pub u32);
8
9#[derive(Debug, Clone)]
11pub struct TreeNode {
12 pub name: String,
14 pub kind: NodeKind,
16 pub total_size: u64,
18 pub file_count: u64,
20 pub parent: Option<NodeId>,
22 pub depth: u16,
24}
25
26#[derive(Debug, Clone)]
28pub enum NodeKind {
29 File {
30 size: u64,
32 category: FileCategory,
34 modified: Option<SystemTime>,
36 },
37 Directory {
38 children: Vec<NodeId>,
40 },
41}
42
43#[derive(Debug)]
45pub struct FileTree {
46 nodes: Vec<TreeNode>,
48 root: NodeId,
50 scan_root: PathBuf,
52}
53
54impl FileTree {
55 pub fn node(&self, id: NodeId) -> &TreeNode {
57 debug_assert!(
58 (id.0 as usize) < self.nodes.len(),
59 "invalid NodeId: {} (tree has {} nodes)",
60 id.0,
61 self.nodes.len()
62 );
63 &self.nodes[id.0 as usize]
64 }
65
66 pub fn root(&self) -> NodeId {
68 self.root
69 }
70
71 pub fn scan_root(&self) -> &Path {
73 &self.scan_root
74 }
75
76 pub fn len(&self) -> usize {
78 self.nodes.len()
79 }
80
81 pub fn is_empty(&self) -> bool {
83 self.nodes.is_empty()
84 }
85
86 pub fn children(&self, id: NodeId) -> &[NodeId] {
88 match &self.node(id).kind {
89 NodeKind::Directory { children } => children,
90 NodeKind::File { .. } => &[],
91 }
92 }
93
94 pub fn path(&self, id: NodeId) -> PathBuf {
99 let mut components = Vec::new();
100 let mut current = id;
101 loop {
102 let node = self.node(current);
103 match node.parent {
104 Some(parent) => {
105 components.push(node.name.as_str());
106 current = parent;
107 }
108 None => break, }
110 }
111 components.reverse();
112 let mut path = PathBuf::new();
113 for c in components {
114 path.push(c);
115 }
116 path
117 }
118}
119
120pub fn build_file_tree(dir_node: &super::DirNode, scan_root: PathBuf) -> FileTree {
126 let mut nodes = Vec::new();
127 let root_name = scan_root
128 .file_name()
129 .map(|n| n.to_string_lossy().into_owned())
130 .unwrap_or_else(|| scan_root.to_string_lossy().into_owned());
131
132 build_recursive(dir_node, &root_name, None, 0, &mut nodes);
133
134 let root = NodeId(0);
135 FileTree {
136 nodes,
137 root,
138 scan_root,
139 }
140}
141
142fn build_recursive(
143 dir_node: &super::DirNode,
144 name: &str,
145 parent: Option<NodeId>,
146 depth: u16,
147 nodes: &mut Vec<TreeNode>,
148) -> NodeId {
149 let my_id = NodeId(nodes.len() as u32);
150
151 if dir_node.children.is_empty() {
152 let category = FileCategory::from_path(Path::new(name));
154 nodes.push(TreeNode {
155 name: name.to_string(),
156 kind: NodeKind::File {
157 size: dir_node.file_size,
158 category,
159 modified: None,
160 },
161 total_size: dir_node.file_size,
162 file_count: 1,
163 parent,
164 depth,
165 });
166 return my_id;
167 }
168
169 nodes.push(TreeNode {
171 name: name.to_string(),
172 kind: NodeKind::Directory {
173 children: Vec::new(),
174 },
175 total_size: 0,
176 file_count: 0,
177 parent,
178 depth,
179 });
180
181 let mut child_ids: Vec<NodeId> = Vec::with_capacity(dir_node.children.len());
182 let mut total_size = dir_node.file_size; let mut file_count: u64 = dir_node.file_count as u64;
184
185 for (child_name, child_node) in &dir_node.children {
186 let child_id = build_recursive(child_node, child_name, Some(my_id), depth + 1, nodes);
187 let child = &nodes[child_id.0 as usize];
188 total_size += child.total_size;
189 file_count += child.file_count;
190 child_ids.push(child_id);
191 }
192
193 child_ids.sort_by(|a, b| {
195 let sa = nodes[b.0 as usize].total_size;
196 let sb = nodes[a.0 as usize].total_size;
197 sa.cmp(&sb)
198 });
199
200 let node = &mut nodes[my_id.0 as usize];
202 node.kind = NodeKind::Directory {
203 children: child_ids,
204 };
205 node.total_size = total_size;
206 node.file_count = file_count;
207
208 my_id
209}
210
211#[cfg(test)]
212#[path = "arena_tests.rs"]
213mod arena_tests;