1use std::path::{Path, PathBuf};
2
3use serde::{Deserialize, Serialize};
4
5use super::node::{NodeId, NodeKind, TreeNode};
6
7#[derive(Debug, Clone, Serialize, Deserialize)]
9pub struct DiskTree {
10 nodes: Vec<Option<TreeNode>>,
11 root_path: PathBuf,
12}
13
14impl DiskTree {
15 pub fn new(root_path: PathBuf) -> Self {
16 let root_name = root_path
17 .file_name()
18 .map(|s| s.to_string_lossy().to_string())
19 .unwrap_or_else(|| root_path.to_string_lossy().to_string());
20
21 let root_node = TreeNode::new(
22 NodeId::ROOT,
23 root_name,
24 NodeKind::Directory,
25 root_path.clone(),
26 None,
27 0,
28 );
29
30 Self {
31 nodes: vec![Some(root_node)],
32 root_path,
33 }
34 }
35
36 pub fn add_node(
38 &mut self,
39 name: String,
40 kind: NodeKind,
41 path: PathBuf,
42 parent: NodeId,
43 ) -> NodeId {
44 let parent_depth = self.get(parent).map(|n| n.depth).unwrap_or(0);
45 let id = NodeId(self.nodes.len());
46
47 let node = TreeNode::new(id, name, kind, path, Some(parent), parent_depth + 1);
48
49 self.nodes.push(Some(node));
50 if let Some(parent_node) = self.get_mut(parent) {
51 parent_node.children.push(id);
52 }
53
54 id
55 }
56
57 pub fn get(&self, id: NodeId) -> Option<&TreeNode> {
59 self.nodes.get(id.index()).and_then(|opt| opt.as_ref())
60 }
61
62 pub fn get_mut(&mut self, id: NodeId) -> Option<&mut TreeNode> {
64 self.nodes.get_mut(id.index()).and_then(|opt| opt.as_mut())
65 }
66
67 pub fn root(&self) -> &TreeNode {
69 self.nodes[0].as_ref().expect("Root node must exist")
70 }
71
72 pub fn root_path(&self) -> &Path {
74 &self.root_path
75 }
76
77 pub fn rebuild_paths(&mut self) {
80 if let Some(root) = self.nodes.get_mut(0).and_then(|o| o.as_mut()) {
82 root.path = self.root_path.clone();
83 root.is_expanded = true;
84 }
85
86 for i in 1..self.nodes.len() {
88 if let Some(node) = &self.nodes[i] {
89 let parent_path = node
90 .parent
91 .and_then(|pid| self.nodes.get(pid.index()))
92 .and_then(|o| o.as_ref())
93 .map(|p| p.path.clone());
94
95 if let Some(pp) = parent_path {
96 let name = self.nodes[i].as_ref().map(|n| n.name.clone());
97 if let (Some(node), Some(name)) = (self.nodes[i].as_mut(), name) {
98 node.path = pp.join(&name);
99 }
100 }
101 }
102 }
103 }
104
105 pub fn len(&self) -> usize {
107 self.nodes.len()
108 }
109
110 pub fn live_count(&self) -> usize {
112 self.nodes.iter().filter(|n| n.is_some()).count()
113 }
114
115 pub fn is_empty(&self) -> bool {
117 self.live_count() <= 1
118 }
119
120 pub fn set_size(&mut self, id: NodeId, size: u64) {
122 if let Some(node) = self.get_mut(id) {
123 node.size = size;
124 }
125 }
126
127 pub fn aggregate_sizes(&mut self) {
129 for i in (0..self.nodes.len()).rev() {
131 let node = match &self.nodes[i] {
132 Some(n) => n,
133 None => continue, };
135 if node.kind.is_directory() {
136 let children = node.children.clone();
137 let mut total_size = 0u64;
138 let mut total_files = 0u64;
139
140 for child_id in &children {
141 if let Some(child) = self.get(*child_id) {
142 total_size += child.size;
143 total_files += child.file_count;
144 }
145 }
146
147 if let Some(node) = self.get_mut(NodeId(i)) {
148 node.size = total_size;
149 node.file_count = total_files;
150 }
151 }
152 }
153 }
154
155 pub fn sort_by_size(&mut self) {
157 let sizes: Vec<u64> = self
159 .nodes
160 .iter()
161 .map(|n| n.as_ref().map(|n| n.size).unwrap_or(0))
162 .collect();
163
164 for node in self.nodes.iter_mut().flatten() {
166 node.children.sort_by(|a, b| {
167 let size_a = sizes.get(a.index()).copied().unwrap_or(0);
168 let size_b = sizes.get(b.index()).copied().unwrap_or(0);
169 size_b.cmp(&size_a)
170 });
171 }
172 }
173
174 pub fn toggle_expanded(&mut self, id: NodeId) {
176 if let Some(node) = self.get_mut(id)
177 && node.kind.is_directory()
178 {
179 node.is_expanded = !node.is_expanded;
180 }
181 }
182
183 pub fn set_expanded(&mut self, id: NodeId, expanded: bool) {
185 if let Some(node) = self.get_mut(id)
186 && node.kind.is_directory()
187 {
188 node.is_expanded = expanded;
189 }
190 }
191
192 pub fn visible_nodes(&self, root: NodeId) -> Vec<NodeId> {
194 let mut result = Vec::new();
195 self.collect_visible(root, &mut result);
196 result
197 }
198
199 fn collect_visible(&self, id: NodeId, result: &mut Vec<NodeId>) {
200 result.push(id);
201
202 if let Some(node) = self.get(id)
203 && node.is_expanded
204 {
205 for &child_id in &node.children {
206 self.collect_visible(child_id, result);
207 }
208 }
209 }
210
211 pub fn path_to_node(&self, id: NodeId) -> Vec<NodeId> {
213 let mut path = Vec::new();
214 let mut current = Some(id);
215
216 while let Some(node_id) = current {
217 path.push(node_id);
218 current = self.get(node_id).and_then(|n| n.parent);
219 }
220
221 path.reverse();
222 path
223 }
224
225 pub fn breadcrumbs(&self, id: NodeId) -> String {
227 let path = self.path_to_node(id);
228 path.iter()
229 .filter_map(|&id| self.get(id).map(|n| n.name.as_str()))
230 .collect::<Vec<_>>()
231 .join("/")
232 }
233
234 pub fn expand_to(&mut self, id: NodeId) {
236 let path = self.path_to_node(id);
237 for node_id in path {
238 self.set_expanded(node_id, true);
239 }
240 }
241
242 pub fn total_size(&self) -> u64 {
244 self.root().size
245 }
246
247 pub fn total_files(&self) -> u64 {
249 self.root().file_count
250 }
251
252 pub fn iter(&self) -> impl Iterator<Item = &TreeNode> {
254 self.nodes.iter().filter_map(|opt| opt.as_ref())
255 }
256
257 pub fn find_by_path(&self, path: &Path) -> Option<NodeId> {
259 for (i, node_opt) in self.nodes.iter().enumerate() {
260 if let Some(node) = node_opt
261 && node.path == path
262 {
263 return Some(NodeId(i));
264 }
265 }
266 None
267 }
268
269 fn collect_descendants(&self, id: NodeId, result: &mut Vec<NodeId>) {
271 if let Some(node) = self.get(id) {
272 for &child_id in &node.children {
273 result.push(child_id);
274 self.collect_descendants(child_id, result);
275 }
276 }
277 }
278
279 pub fn remove_node(&mut self, id: NodeId) -> u64 {
282 if id == NodeId::ROOT {
284 return 0;
285 }
286
287 let (size, file_count, parent_id) = match self.get(id) {
289 Some(node) => (node.size, node.file_count, node.parent),
290 None => return 0, };
292
293 if let Some(pid) = parent_id
295 && let Some(parent) = self.get_mut(pid)
296 {
297 parent.children.retain(|&c| c != id);
298 }
299
300 let mut to_remove = vec![id];
302 self.collect_descendants(id, &mut to_remove);
303
304 for nid in to_remove {
306 if let Some(slot) = self.nodes.get_mut(nid.index()) {
307 *slot = None;
308 }
309 }
310
311 let mut current = parent_id;
313 while let Some(nid) = current {
314 if let Some(node) = self.get_mut(nid) {
315 node.size = node.size.saturating_sub(size);
316 node.file_count = node.file_count.saturating_sub(file_count);
317 current = node.parent;
318 } else {
319 break;
320 }
321 }
322
323 size
324 }
325}
326
327#[cfg(test)]
328mod tests {
329 use super::*;
330
331 #[test]
332 fn test_tree_creation() {
333 let tree = DiskTree::new(PathBuf::from("/test"));
334 assert_eq!(tree.len(), 1);
335 assert_eq!(tree.root().name, "test");
336 }
337
338 #[test]
339 fn test_add_nodes() {
340 let mut tree = DiskTree::new(PathBuf::from("/test"));
341
342 let file_id = tree.add_node(
343 "file.txt".to_string(),
344 NodeKind::File,
345 PathBuf::from("/test/file.txt"),
346 NodeId::ROOT,
347 );
348
349 assert_eq!(tree.len(), 2);
350 assert_eq!(tree.get(file_id).unwrap().name, "file.txt");
351 assert_eq!(tree.root().children.len(), 1);
352 }
353
354 #[test]
355 fn test_remove_node_with_size_propagation() {
356 let mut tree = DiskTree::new(PathBuf::from("/test"));
357
358 let subdir_id = tree.add_node(
360 "subdir".to_string(),
361 NodeKind::Directory,
362 PathBuf::from("/test/subdir"),
363 NodeId::ROOT,
364 );
365
366 let file1_id = tree.add_node(
368 "file1.txt".to_string(),
369 NodeKind::File,
370 PathBuf::from("/test/subdir/file1.txt"),
371 subdir_id,
372 );
373 tree.set_size(file1_id, 1000);
374
375 let file2_id = tree.add_node(
376 "file2.txt".to_string(),
377 NodeKind::File,
378 PathBuf::from("/test/subdir/file2.txt"),
379 subdir_id,
380 );
381 tree.set_size(file2_id, 2000);
382
383 tree.aggregate_sizes();
385
386 assert_eq!(tree.root().size, 3000);
388 assert_eq!(tree.get(subdir_id).unwrap().size, 3000);
389
390 let freed = tree.remove_node(file1_id);
392 assert_eq!(freed, 1000);
393
394 assert_eq!(tree.root().size, 2000);
396 assert_eq!(tree.get(subdir_id).unwrap().size, 2000);
397
398 assert!(tree.get(file1_id).is_none());
400
401 assert!(tree.get(file2_id).is_some());
403 }
404
405 #[test]
406 fn test_remove_node_removes_descendants() {
407 let mut tree = DiskTree::new(PathBuf::from("/test"));
408
409 let subdir_id = tree.add_node(
411 "subdir".to_string(),
412 NodeKind::Directory,
413 PathBuf::from("/test/subdir"),
414 NodeId::ROOT,
415 );
416
417 let file1_id = tree.add_node(
419 "file1.txt".to_string(),
420 NodeKind::File,
421 PathBuf::from("/test/subdir/file1.txt"),
422 subdir_id,
423 );
424 tree.set_size(file1_id, 1000);
425
426 tree.aggregate_sizes();
427
428 let freed = tree.remove_node(subdir_id);
430 assert_eq!(freed, 1000);
431
432 assert!(tree.get(subdir_id).is_none());
434 assert!(tree.get(file1_id).is_none());
435
436 assert_eq!(tree.root().children.len(), 0);
438 assert_eq!(tree.root().size, 0);
439 }
440
441 #[test]
442 fn test_find_by_path() {
443 let mut tree = DiskTree::new(PathBuf::from("/test"));
444
445 let file_id = tree.add_node(
446 "file.txt".to_string(),
447 NodeKind::File,
448 PathBuf::from("/test/file.txt"),
449 NodeId::ROOT,
450 );
451
452 assert_eq!(
454 tree.find_by_path(&PathBuf::from("/test/file.txt")),
455 Some(file_id)
456 );
457
458 assert_eq!(
460 tree.find_by_path(&PathBuf::from("/test/nonexistent.txt")),
461 None
462 );
463 }
464}