1use super::node::{NodeId, NodeState, TreeNode};
2use crate::model::filesystem::DirEntry;
3use crate::services::fs::FsManager;
4use std::collections::HashMap;
5use std::io;
6use std::path::{Path, PathBuf};
7use std::sync::Arc;
8
9#[derive(Debug)]
15pub struct FileTree {
16 root_path: PathBuf,
18 nodes: HashMap<NodeId, TreeNode>,
20 path_to_node: HashMap<PathBuf, NodeId>,
22 root_id: NodeId,
24 next_id: usize,
26 fs_manager: Arc<FsManager>,
28}
29
30impl FileTree {
31 pub async fn new(root_path: PathBuf, fs_manager: Arc<FsManager>) -> io::Result<Self> {
37 if !fs_manager.exists(&root_path).await {
39 return Err(io::Error::new(
40 io::ErrorKind::NotFound,
41 format!("Path does not exist: {:?}", root_path),
42 ));
43 }
44
45 if !fs_manager.is_dir(&root_path).await? {
46 return Err(io::Error::new(
47 io::ErrorKind::InvalidInput,
48 format!("Path is not a directory: {:?}", root_path),
49 ));
50 }
51
52 let root_entry = fs_manager.get_entry(&root_path).await?;
54
55 let root_id = NodeId(0);
57 let root_node = TreeNode::new(root_id, root_entry.clone(), None);
58
59 let mut nodes = HashMap::new();
60 nodes.insert(root_id, root_node);
61
62 let mut path_to_node = HashMap::new();
63 path_to_node.insert(root_path.clone(), root_id);
64
65 Ok(Self {
66 root_path,
67 nodes,
68 path_to_node,
69 root_id,
70 next_id: 1,
71 fs_manager,
72 })
73 }
74
75 pub fn root_id(&self) -> NodeId {
77 self.root_id
78 }
79
80 pub fn root_path(&self) -> &Path {
82 &self.root_path
83 }
84
85 pub fn get_node(&self, id: NodeId) -> Option<&TreeNode> {
87 self.nodes.get(&id)
88 }
89
90 fn get_node_mut(&mut self, id: NodeId) -> Option<&mut TreeNode> {
92 self.nodes.get_mut(&id)
93 }
94
95 pub fn get_node_by_path(&self, path: &Path) -> Option<&TreeNode> {
97 self.path_to_node
98 .get(path)
99 .and_then(|id| self.get_node(*id))
100 }
101
102 pub fn all_nodes(&self) -> impl Iterator<Item = &TreeNode> {
104 self.nodes.values()
105 }
106
107 pub async fn expand_node(&mut self, id: NodeId) -> io::Result<()> {
116 let node = self
118 .get_node(id)
119 .ok_or_else(|| io::Error::new(io::ErrorKind::NotFound, "Node not found"))?;
120
121 if !node.is_dir() {
122 return Err(io::Error::new(
123 io::ErrorKind::InvalidInput,
124 "Cannot expand a file node",
125 ));
126 }
127
128 if node.is_expanded() {
130 return Ok(());
131 }
132
133 if let Some(node) = self.get_node_mut(id) {
135 node.state = NodeState::Loading;
136 }
137
138 let path = self.get_node(id).unwrap().entry.path.clone();
140 let result = self.fs_manager.list_dir_with_metadata(path.clone()).await;
141
142 match result {
143 Ok(entries) => {
144 let mut sorted_entries = entries;
146 sorted_entries.sort_by(|a, b| match (a.is_dir(), b.is_dir()) {
147 (true, false) => std::cmp::Ordering::Less,
148 (false, true) => std::cmp::Ordering::Greater,
149 _ => a.name.to_lowercase().cmp(&b.name.to_lowercase()),
150 });
151
152 let mut child_ids = Vec::new();
154 for entry in sorted_entries {
155 let child_id = self.add_node(entry, Some(id));
156 child_ids.push(child_id);
157 }
158
159 if let Some(node) = self.get_node_mut(id) {
161 node.children = child_ids;
162 node.state = NodeState::Expanded;
163 }
164
165 Ok(())
166 }
167 Err(e) => {
168 if let Some(node) = self.get_node_mut(id) {
170 node.state = NodeState::Error(e.to_string());
171 }
172 Err(e)
173 }
174 }
175 }
176
177 pub fn collapse_node(&mut self, id: NodeId) {
182 if let Some(node) = self.get_node(id) {
183 if !node.is_dir() {
184 return;
185 }
186
187 let children_to_remove: Vec<NodeId> = node.children.clone();
189
190 for child_id in children_to_remove {
192 self.remove_node_recursive(child_id);
193 }
194 }
195
196 if let Some(node) = self.get_node_mut(id) {
198 node.children.clear();
199 node.state = NodeState::Collapsed;
200 }
201 }
202
203 pub async fn toggle_node(&mut self, id: NodeId) -> io::Result<()> {
205 let node = self
206 .get_node(id)
207 .ok_or_else(|| io::Error::new(io::ErrorKind::NotFound, "Node not found"))?;
208
209 if !node.is_dir() {
210 return Ok(());
211 }
212
213 if node.is_expanded() {
214 self.collapse_node(id);
215 Ok(())
216 } else {
217 self.expand_node(id).await
218 }
219 }
220
221 pub async fn refresh_node(&mut self, id: NodeId) -> io::Result<()> {
225 self.collapse_node(id);
227 self.expand_node(id).await
228 }
229
230 pub fn get_visible_nodes(&self) -> Vec<NodeId> {
235 let mut visible = Vec::new();
236 self.collect_visible_recursive(self.root_id, &mut visible);
237 visible
238 }
239
240 fn collect_visible_recursive(&self, id: NodeId, visible: &mut Vec<NodeId>) {
242 visible.push(id);
243
244 if let Some(node) = self.get_node(id) {
245 if node.is_expanded() {
246 for &child_id in &node.children {
247 self.collect_visible_recursive(child_id, visible);
248 }
249 }
250 }
251 }
252
253 pub fn get_ancestors(&self, id: NodeId) -> Vec<NodeId> {
255 let mut ancestors = Vec::new();
256 let mut current = Some(id);
257
258 while let Some(node_id) = current {
259 ancestors.push(node_id);
260 current = self.get_node(node_id).and_then(|n| n.parent);
261 }
262
263 ancestors.reverse();
264 ancestors
265 }
266
267 pub fn get_depth(&self, id: NodeId) -> usize {
269 self.get_ancestors(id).len() - 1
270 }
271
272 pub fn find_by_relative_path(&self, relative_path: &Path) -> Option<NodeId> {
274 let full_path = self.root_path.join(relative_path);
275 self.path_to_node.get(&full_path).copied()
276 }
277
278 fn add_node(&mut self, entry: DirEntry, parent: Option<NodeId>) -> NodeId {
280 let id = NodeId(self.next_id);
281 self.next_id += 1;
282
283 let node = TreeNode::new(id, entry.clone(), parent);
284 self.path_to_node.insert(entry.path.clone(), id);
285 self.nodes.insert(id, node);
286
287 id
288 }
289
290 fn remove_node_recursive(&mut self, id: NodeId) {
292 if let Some(node) = self.get_node(id) {
293 let children = node.children.clone();
294 let path = node.entry.path.clone();
295
296 for child_id in children {
298 self.remove_node_recursive(child_id);
299 }
300
301 self.path_to_node.remove(&path);
303
304 self.nodes.remove(&id);
306 }
307 }
308
309 pub fn node_count(&self) -> usize {
311 self.nodes.len()
312 }
313
314 pub async fn expand_to_path(&mut self, path: &Path) -> Option<NodeId> {
341 let relative_path = path.strip_prefix(&self.root_path).ok()?;
343
344 let mut current_id = self.root_id;
346
347 for component in relative_path.components() {
349 let component_str = component.as_os_str().to_str()?;
350
351 let node = self.get_node(current_id)?;
353 if node.is_dir() && !node.is_expanded() {
354 if let Err(e) = self.expand_node(current_id).await {
356 tracing::warn!("Failed to expand node during path traversal: {}", e);
357 return None;
358 }
359 }
360
361 let node = self.get_node(current_id)?;
363 let child_id = node
364 .children
365 .iter()
366 .find(|&&child_id| {
367 if let Some(child_node) = self.get_node(child_id) {
368 child_node.entry.name == component_str
369 } else {
370 false
371 }
372 })
373 .copied();
374
375 match child_id {
376 Some(id) => current_id = id,
377 None => {
378 tracing::warn!("Component '{}' not found in tree", component_str);
380 return None;
381 }
382 }
383 }
384
385 Some(current_id)
386 }
387}
388
389#[cfg(test)]
390mod tests {
391 use super::*;
392 use crate::model::filesystem::StdFileSystem;
393 use std::fs as std_fs;
394 use tempfile::TempDir;
395
396 async fn create_test_tree() -> (TempDir, FileTree) {
397 let temp_dir = TempDir::new().unwrap();
398 let temp_path = temp_dir.path();
399
400 std_fs::create_dir(temp_path.join("dir1")).unwrap();
411 std_fs::write(temp_path.join("dir1/file1.txt"), "content1").unwrap();
412 std_fs::write(temp_path.join("dir1/file2.txt"), "content2").unwrap();
413
414 std_fs::create_dir(temp_path.join("dir2")).unwrap();
415 std_fs::create_dir(temp_path.join("dir2/subdir")).unwrap();
416 std_fs::write(temp_path.join("dir2/subdir/file3.txt"), "content3").unwrap();
417
418 std_fs::write(temp_path.join("file4.txt"), "content4").unwrap();
419
420 let backend = Arc::new(StdFileSystem);
421 let manager = Arc::new(FsManager::new(backend));
422 let tree = FileTree::new(temp_path.to_path_buf(), manager)
423 .await
424 .unwrap();
425
426 (temp_dir, tree)
427 }
428
429 #[tokio::test]
430 async fn test_tree_creation() {
431 let (_temp_dir, tree) = create_test_tree().await;
432
433 assert_eq!(tree.node_count(), 1); let root = tree.get_node(tree.root_id()).unwrap();
435 assert!(root.is_collapsed());
436 assert_eq!(root.children.len(), 0);
437 }
438
439 #[tokio::test]
440 async fn test_expand_root() {
441 let (_temp_dir, mut tree) = create_test_tree().await;
442
443 tree.expand_node(tree.root_id()).await.unwrap();
444
445 let root = tree.get_node(tree.root_id()).unwrap();
446 assert!(root.is_expanded());
447 assert_eq!(root.children.len(), 3); assert_eq!(tree.node_count(), 4);
451 }
452
453 #[tokio::test]
454 async fn test_expand_nested() {
455 let (_temp_dir, mut tree) = create_test_tree().await;
456
457 tree.expand_node(tree.root_id()).await.unwrap();
459
460 let root = tree.get_node(tree.root_id()).unwrap();
462 let dir1_id = root.children[0]; tree.expand_node(dir1_id).await.unwrap();
465
466 let dir1 = tree.get_node(dir1_id).unwrap();
467 assert!(dir1.is_expanded());
468 assert_eq!(dir1.children.len(), 2); assert_eq!(tree.node_count(), 6);
472 }
473
474 #[tokio::test]
475 async fn test_collapse_node() {
476 let (_temp_dir, mut tree) = create_test_tree().await;
477
478 tree.expand_node(tree.root_id()).await.unwrap();
480 let root = tree.get_node(tree.root_id()).unwrap();
481 let dir1_id = root.children[0];
482 tree.expand_node(dir1_id).await.unwrap();
483
484 assert_eq!(tree.node_count(), 6);
485
486 tree.collapse_node(dir1_id);
488
489 let dir1 = tree.get_node(dir1_id).unwrap();
490 assert!(dir1.is_collapsed());
491 assert_eq!(dir1.children.len(), 0);
492
493 assert_eq!(tree.node_count(), 4);
495 }
496
497 #[tokio::test]
498 async fn test_toggle_node() {
499 let (_temp_dir, mut tree) = create_test_tree().await;
500
501 tree.expand_node(tree.root_id()).await.unwrap();
502 let root = tree.get_node(tree.root_id()).unwrap();
503 let dir1_id = root.children[0];
504
505 tree.toggle_node(dir1_id).await.unwrap();
507 assert!(tree.get_node(dir1_id).unwrap().is_expanded());
508
509 tree.toggle_node(dir1_id).await.unwrap();
511 assert!(tree.get_node(dir1_id).unwrap().is_collapsed());
512 }
513
514 #[tokio::test]
515 async fn test_get_visible_nodes() {
516 let (_temp_dir, mut tree) = create_test_tree().await;
517
518 let visible = tree.get_visible_nodes();
520 assert_eq!(visible.len(), 1);
521
522 tree.expand_node(tree.root_id()).await.unwrap();
524 let visible = tree.get_visible_nodes();
525 assert_eq!(visible.len(), 4); let root = tree.get_node(tree.root_id()).unwrap();
529 let dir1_id = root.children[0];
530 tree.expand_node(dir1_id).await.unwrap();
531
532 let visible = tree.get_visible_nodes();
533 assert_eq!(visible.len(), 6); }
535
536 #[tokio::test]
537 async fn test_get_ancestors() {
538 let (_temp_dir, mut tree) = create_test_tree().await;
539
540 tree.expand_node(tree.root_id()).await.unwrap();
541 let root = tree.get_node(tree.root_id()).unwrap();
542 let dir1_id = root.children[0];
543 tree.expand_node(dir1_id).await.unwrap();
544
545 let dir1 = tree.get_node(dir1_id).unwrap();
546 let file1_id = dir1.children[0];
547
548 let ancestors = tree.get_ancestors(file1_id);
549 assert_eq!(ancestors.len(), 3); assert_eq!(ancestors[0], tree.root_id());
551 assert_eq!(ancestors[1], dir1_id);
552 assert_eq!(ancestors[2], file1_id);
553 }
554
555 #[tokio::test]
556 async fn test_get_depth() {
557 let (_temp_dir, mut tree) = create_test_tree().await;
558
559 tree.expand_node(tree.root_id()).await.unwrap();
560 let root = tree.get_node(tree.root_id()).unwrap();
561 let dir1_id = root.children[0];
562 tree.expand_node(dir1_id).await.unwrap();
563
564 assert_eq!(tree.get_depth(tree.root_id()), 0);
565 assert_eq!(tree.get_depth(dir1_id), 1);
566
567 let dir1 = tree.get_node(dir1_id).unwrap();
568 let file1_id = dir1.children[0];
569 assert_eq!(tree.get_depth(file1_id), 2);
570 }
571
572 #[tokio::test]
573 async fn test_sorted_entries() {
574 let (_temp_dir, mut tree) = create_test_tree().await;
575
576 tree.expand_node(tree.root_id()).await.unwrap();
577
578 let root = tree.get_node(tree.root_id()).unwrap();
579 let children: Vec<_> = root
580 .children
581 .iter()
582 .map(|&id| tree.get_node(id).unwrap())
583 .collect();
584
585 assert!(children[0].is_dir());
587 assert!(children[1].is_dir());
588 assert!(children[2].is_file());
589
590 assert_eq!(children[0].entry.name, "dir1");
592 assert_eq!(children[1].entry.name, "dir2");
593 }
594
595 #[tokio::test]
596 async fn test_refresh_node() {
597 let temp_dir = TempDir::new().unwrap();
598 let temp_path = temp_dir.path();
599
600 std_fs::create_dir(temp_path.join("dir1")).unwrap();
601 std_fs::write(temp_path.join("dir1/file1.txt"), "content").unwrap();
602
603 let backend = Arc::new(StdFileSystem);
604 let manager = Arc::new(FsManager::new(backend));
605 let mut tree = FileTree::new(temp_path.to_path_buf(), manager)
606 .await
607 .unwrap();
608
609 tree.expand_node(tree.root_id()).await.unwrap();
611 let root = tree.get_node(tree.root_id()).unwrap();
612 let dir1_id = root.children[0];
613 tree.expand_node(dir1_id).await.unwrap();
614
615 let dir1 = tree.get_node(dir1_id).unwrap();
617 assert_eq!(dir1.children.len(), 1);
618
619 std_fs::write(temp_path.join("dir1/file2.txt"), "content2").unwrap();
621
622 tree.refresh_node(dir1_id).await.unwrap();
624
625 let dir1 = tree.get_node(dir1_id).unwrap();
627 assert_eq!(dir1.children.len(), 2);
628 }
629
630 #[tokio::test]
631 async fn test_find_by_relative_path() {
632 let (_temp_dir, mut tree) = create_test_tree().await;
633
634 tree.expand_node(tree.root_id()).await.unwrap();
635 let root = tree.get_node(tree.root_id()).unwrap();
636 let dir1_id = root.children[0];
637
638 let found_id = tree.find_by_relative_path(Path::new("dir1"));
639 assert_eq!(found_id, Some(dir1_id));
640
641 let not_found = tree.find_by_relative_path(Path::new("nonexistent"));
642 assert_eq!(not_found, None);
643 }
644
645 #[tokio::test]
646 async fn test_expand_to_path() {
647 let (_temp_dir, mut tree) = create_test_tree().await;
648 let root_path = tree.root_path().to_path_buf();
649
650 assert_eq!(tree.node_count(), 1);
652
653 let target_path = root_path.join("dir2/subdir/file3.txt");
655 let node_id = tree.expand_to_path(&target_path).await;
656
657 assert!(node_id.is_some(), "Should find the nested file");
658
659 let root = tree.get_node(tree.root_id()).unwrap();
661 assert!(root.is_expanded(), "Root should be expanded");
662
663 let dir2_node = root
665 .children
666 .iter()
667 .find_map(|&id| {
668 let node = tree.get_node(id)?;
669 if node.entry.name == "dir2" {
670 Some(node)
671 } else {
672 None
673 }
674 })
675 .expect("dir2 should exist");
676
677 assert!(dir2_node.is_expanded(), "dir2 should be expanded");
678
679 let subdir_node = dir2_node
681 .children
682 .iter()
683 .find_map(|&id| {
684 let node = tree.get_node(id)?;
685 if node.entry.name == "subdir" {
686 Some(node)
687 } else {
688 None
689 }
690 })
691 .expect("subdir should exist");
692
693 assert!(subdir_node.is_expanded(), "subdir should be expanded");
694
695 let target_node = tree.get_node(node_id.unwrap()).unwrap();
697 assert_eq!(target_node.entry.name, "file3.txt");
698 assert!(target_node.is_file());
699 }
700
701 #[tokio::test]
702 async fn test_expand_to_path_not_under_root() {
703 let (_temp_dir, mut tree) = create_test_tree().await;
704
705 let outside_path = PathBuf::from("/tmp/somefile.txt");
707 let result = tree.expand_to_path(&outside_path).await;
708
709 assert!(
710 result.is_none(),
711 "Should return None for paths outside root"
712 );
713 }
714
715 #[tokio::test]
716 async fn test_expand_to_path_nonexistent() {
717 let (_temp_dir, mut tree) = create_test_tree().await;
718 let root_path = tree.root_path().to_path_buf();
719
720 let nonexistent_path = root_path.join("dir1/nonexistent.txt");
722 let result = tree.expand_to_path(&nonexistent_path).await;
723
724 assert!(result.is_none(), "Should return None for nonexistent paths");
725 }
726}