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 async fn reload_expanded_node(&mut self, id: NodeId) -> io::Result<()> {
244 let expanded_paths = self.collect_expanded_descendant_paths(id);
245 self.refresh_node(id).await?;
246 for path in expanded_paths {
252 if let Some(target_id) = self.expand_to_path(&path).await {
253 if let Err(e) = self.expand_node(target_id).await {
254 tracing::warn!("Failed to re-expand {:?} after tree reload: {}", path, e);
255 }
256 }
257 }
258 Ok(())
259 }
260
261 fn collect_expanded_descendant_paths(&self, id: NodeId) -> Vec<PathBuf> {
265 let mut out = Vec::new();
266 if let Some(node) = self.get_node(id) {
267 for &child in &node.children {
268 self.collect_expanded_recursive(child, &mut out);
269 }
270 }
271 out
272 }
273
274 fn collect_expanded_recursive(&self, id: NodeId, out: &mut Vec<PathBuf>) {
275 if let Some(node) = self.get_node(id) {
276 if node.is_expanded() {
277 out.push(node.entry.path.clone());
278 for &child in &node.children {
279 self.collect_expanded_recursive(child, out);
280 }
281 }
282 }
283 }
284
285 pub fn get_visible_nodes(&self) -> Vec<NodeId> {
290 let mut visible = Vec::new();
291 self.collect_visible_recursive(self.root_id, &mut visible);
292 visible
293 }
294
295 fn collect_visible_recursive(&self, id: NodeId, visible: &mut Vec<NodeId>) {
297 visible.push(id);
298
299 if let Some(node) = self.get_node(id) {
300 if node.is_expanded() {
301 for &child_id in &node.children {
302 self.collect_visible_recursive(child_id, visible);
303 }
304 }
305 }
306 }
307
308 pub fn get_ancestors(&self, id: NodeId) -> Vec<NodeId> {
310 let mut ancestors = Vec::new();
311 let mut current = Some(id);
312
313 while let Some(node_id) = current {
314 ancestors.push(node_id);
315 current = self.get_node(node_id).and_then(|n| n.parent);
316 }
317
318 ancestors.reverse();
319 ancestors
320 }
321
322 pub fn get_depth(&self, id: NodeId) -> usize {
324 self.get_ancestors(id).len() - 1
325 }
326
327 pub fn find_by_relative_path(&self, relative_path: &Path) -> Option<NodeId> {
329 let full_path = self.root_path.join(relative_path);
330 self.path_to_node.get(&full_path).copied()
331 }
332
333 fn add_node(&mut self, entry: DirEntry, parent: Option<NodeId>) -> NodeId {
335 let id = NodeId(self.next_id);
336 self.next_id += 1;
337
338 let node = TreeNode::new(id, entry.clone(), parent);
339 self.path_to_node.insert(entry.path.clone(), id);
340 self.nodes.insert(id, node);
341
342 id
343 }
344
345 fn remove_node_recursive(&mut self, id: NodeId) {
347 if let Some(node) = self.get_node(id) {
348 let children = node.children.clone();
349 let path = node.entry.path.clone();
350
351 for child_id in children {
353 self.remove_node_recursive(child_id);
354 }
355
356 self.path_to_node.remove(&path);
358
359 self.nodes.remove(&id);
361 }
362 }
363
364 pub fn node_count(&self) -> usize {
366 self.nodes.len()
367 }
368
369 pub async fn expand_to_path(&mut self, path: &Path) -> Option<NodeId> {
396 let relative_path = path.strip_prefix(&self.root_path).ok()?;
398
399 let mut current_id = self.root_id;
401
402 for component in relative_path.components() {
404 let component_str = component.as_os_str().to_str()?;
405
406 let node = self.get_node(current_id)?;
408 if node.is_dir() && !node.is_expanded() {
409 if let Err(e) = self.expand_node(current_id).await {
411 tracing::warn!("Failed to expand node during path traversal: {}", e);
412 return None;
413 }
414 }
415
416 let node = self.get_node(current_id)?;
418 let child_id = node
419 .children
420 .iter()
421 .find(|&&child_id| {
422 if let Some(child_node) = self.get_node(child_id) {
423 child_node.entry.name == component_str
424 } else {
425 false
426 }
427 })
428 .copied();
429
430 match child_id {
431 Some(id) => current_id = id,
432 None => {
433 tracing::warn!("Component '{}' not found in tree", component_str);
435 return None;
436 }
437 }
438 }
439
440 Some(current_id)
441 }
442}
443
444#[cfg(test)]
445mod tests {
446 use super::*;
447 use crate::model::filesystem::StdFileSystem;
448 use std::fs as std_fs;
449 use tempfile::TempDir;
450
451 async fn create_test_tree() -> (TempDir, FileTree) {
452 let temp_dir = TempDir::new().unwrap();
453 let temp_path = temp_dir.path();
454
455 std_fs::create_dir(temp_path.join("dir1")).unwrap();
466 std_fs::write(temp_path.join("dir1/file1.txt"), "content1").unwrap();
467 std_fs::write(temp_path.join("dir1/file2.txt"), "content2").unwrap();
468
469 std_fs::create_dir(temp_path.join("dir2")).unwrap();
470 std_fs::create_dir(temp_path.join("dir2/subdir")).unwrap();
471 std_fs::write(temp_path.join("dir2/subdir/file3.txt"), "content3").unwrap();
472
473 std_fs::write(temp_path.join("file4.txt"), "content4").unwrap();
474
475 let backend = Arc::new(StdFileSystem);
476 let manager = Arc::new(FsManager::new(backend));
477 let tree = FileTree::new(temp_path.to_path_buf(), manager)
478 .await
479 .unwrap();
480
481 (temp_dir, tree)
482 }
483
484 #[tokio::test]
485 async fn test_tree_creation() {
486 let (_temp_dir, tree) = create_test_tree().await;
487
488 assert_eq!(tree.node_count(), 1); let root = tree.get_node(tree.root_id()).unwrap();
490 assert!(root.is_collapsed());
491 assert_eq!(root.children.len(), 0);
492 }
493
494 #[tokio::test]
495 async fn test_expand_root() {
496 let (_temp_dir, mut tree) = create_test_tree().await;
497
498 tree.expand_node(tree.root_id()).await.unwrap();
499
500 let root = tree.get_node(tree.root_id()).unwrap();
501 assert!(root.is_expanded());
502 assert_eq!(root.children.len(), 3); assert_eq!(tree.node_count(), 4);
506 }
507
508 #[tokio::test]
509 async fn test_expand_nested() {
510 let (_temp_dir, mut tree) = create_test_tree().await;
511
512 tree.expand_node(tree.root_id()).await.unwrap();
514
515 let root = tree.get_node(tree.root_id()).unwrap();
517 let dir1_id = root.children[0]; tree.expand_node(dir1_id).await.unwrap();
520
521 let dir1 = tree.get_node(dir1_id).unwrap();
522 assert!(dir1.is_expanded());
523 assert_eq!(dir1.children.len(), 2); assert_eq!(tree.node_count(), 6);
527 }
528
529 #[tokio::test]
530 async fn test_collapse_node() {
531 let (_temp_dir, mut tree) = create_test_tree().await;
532
533 tree.expand_node(tree.root_id()).await.unwrap();
535 let root = tree.get_node(tree.root_id()).unwrap();
536 let dir1_id = root.children[0];
537 tree.expand_node(dir1_id).await.unwrap();
538
539 assert_eq!(tree.node_count(), 6);
540
541 tree.collapse_node(dir1_id);
543
544 let dir1 = tree.get_node(dir1_id).unwrap();
545 assert!(dir1.is_collapsed());
546 assert_eq!(dir1.children.len(), 0);
547
548 assert_eq!(tree.node_count(), 4);
550 }
551
552 #[tokio::test]
553 async fn test_toggle_node() {
554 let (_temp_dir, mut tree) = create_test_tree().await;
555
556 tree.expand_node(tree.root_id()).await.unwrap();
557 let root = tree.get_node(tree.root_id()).unwrap();
558 let dir1_id = root.children[0];
559
560 tree.toggle_node(dir1_id).await.unwrap();
562 assert!(tree.get_node(dir1_id).unwrap().is_expanded());
563
564 tree.toggle_node(dir1_id).await.unwrap();
566 assert!(tree.get_node(dir1_id).unwrap().is_collapsed());
567 }
568
569 #[tokio::test]
570 async fn test_get_visible_nodes() {
571 let (_temp_dir, mut tree) = create_test_tree().await;
572
573 let visible = tree.get_visible_nodes();
575 assert_eq!(visible.len(), 1);
576
577 tree.expand_node(tree.root_id()).await.unwrap();
579 let visible = tree.get_visible_nodes();
580 assert_eq!(visible.len(), 4); let root = tree.get_node(tree.root_id()).unwrap();
584 let dir1_id = root.children[0];
585 tree.expand_node(dir1_id).await.unwrap();
586
587 let visible = tree.get_visible_nodes();
588 assert_eq!(visible.len(), 6); }
590
591 #[tokio::test]
592 async fn test_get_ancestors() {
593 let (_temp_dir, mut tree) = create_test_tree().await;
594
595 tree.expand_node(tree.root_id()).await.unwrap();
596 let root = tree.get_node(tree.root_id()).unwrap();
597 let dir1_id = root.children[0];
598 tree.expand_node(dir1_id).await.unwrap();
599
600 let dir1 = tree.get_node(dir1_id).unwrap();
601 let file1_id = dir1.children[0];
602
603 let ancestors = tree.get_ancestors(file1_id);
604 assert_eq!(ancestors.len(), 3); assert_eq!(ancestors[0], tree.root_id());
606 assert_eq!(ancestors[1], dir1_id);
607 assert_eq!(ancestors[2], file1_id);
608 }
609
610 #[tokio::test]
611 async fn test_get_depth() {
612 let (_temp_dir, mut tree) = create_test_tree().await;
613
614 tree.expand_node(tree.root_id()).await.unwrap();
615 let root = tree.get_node(tree.root_id()).unwrap();
616 let dir1_id = root.children[0];
617 tree.expand_node(dir1_id).await.unwrap();
618
619 assert_eq!(tree.get_depth(tree.root_id()), 0);
620 assert_eq!(tree.get_depth(dir1_id), 1);
621
622 let dir1 = tree.get_node(dir1_id).unwrap();
623 let file1_id = dir1.children[0];
624 assert_eq!(tree.get_depth(file1_id), 2);
625 }
626
627 #[tokio::test]
628 async fn test_sorted_entries() {
629 let (_temp_dir, mut tree) = create_test_tree().await;
630
631 tree.expand_node(tree.root_id()).await.unwrap();
632
633 let root = tree.get_node(tree.root_id()).unwrap();
634 let children: Vec<_> = root
635 .children
636 .iter()
637 .map(|&id| tree.get_node(id).unwrap())
638 .collect();
639
640 assert!(children[0].is_dir());
642 assert!(children[1].is_dir());
643 assert!(children[2].is_file());
644
645 assert_eq!(children[0].entry.name, "dir1");
647 assert_eq!(children[1].entry.name, "dir2");
648 }
649
650 #[tokio::test]
651 async fn test_refresh_node() {
652 let temp_dir = TempDir::new().unwrap();
653 let temp_path = temp_dir.path();
654
655 std_fs::create_dir(temp_path.join("dir1")).unwrap();
656 std_fs::write(temp_path.join("dir1/file1.txt"), "content").unwrap();
657
658 let backend = Arc::new(StdFileSystem);
659 let manager = Arc::new(FsManager::new(backend));
660 let mut tree = FileTree::new(temp_path.to_path_buf(), manager)
661 .await
662 .unwrap();
663
664 tree.expand_node(tree.root_id()).await.unwrap();
666 let root = tree.get_node(tree.root_id()).unwrap();
667 let dir1_id = root.children[0];
668 tree.expand_node(dir1_id).await.unwrap();
669
670 let dir1 = tree.get_node(dir1_id).unwrap();
672 assert_eq!(dir1.children.len(), 1);
673
674 std_fs::write(temp_path.join("dir1/file2.txt"), "content2").unwrap();
676
677 tree.refresh_node(dir1_id).await.unwrap();
679
680 let dir1 = tree.get_node(dir1_id).unwrap();
682 assert_eq!(dir1.children.len(), 2);
683 }
684
685 #[tokio::test]
686 async fn test_find_by_relative_path() {
687 let (_temp_dir, mut tree) = create_test_tree().await;
688
689 tree.expand_node(tree.root_id()).await.unwrap();
690 let root = tree.get_node(tree.root_id()).unwrap();
691 let dir1_id = root.children[0];
692
693 let found_id = tree.find_by_relative_path(Path::new("dir1"));
694 assert_eq!(found_id, Some(dir1_id));
695
696 let not_found = tree.find_by_relative_path(Path::new("nonexistent"));
697 assert_eq!(not_found, None);
698 }
699
700 #[tokio::test]
701 async fn test_expand_to_path() {
702 let (_temp_dir, mut tree) = create_test_tree().await;
703 let root_path = tree.root_path().to_path_buf();
704
705 assert_eq!(tree.node_count(), 1);
707
708 let target_path = root_path.join("dir2/subdir/file3.txt");
710 let node_id = tree.expand_to_path(&target_path).await;
711
712 assert!(node_id.is_some(), "Should find the nested file");
713
714 let root = tree.get_node(tree.root_id()).unwrap();
716 assert!(root.is_expanded(), "Root should be expanded");
717
718 let dir2_node = root
720 .children
721 .iter()
722 .find_map(|&id| {
723 let node = tree.get_node(id)?;
724 if node.entry.name == "dir2" {
725 Some(node)
726 } else {
727 None
728 }
729 })
730 .expect("dir2 should exist");
731
732 assert!(dir2_node.is_expanded(), "dir2 should be expanded");
733
734 let subdir_node = dir2_node
736 .children
737 .iter()
738 .find_map(|&id| {
739 let node = tree.get_node(id)?;
740 if node.entry.name == "subdir" {
741 Some(node)
742 } else {
743 None
744 }
745 })
746 .expect("subdir should exist");
747
748 assert!(subdir_node.is_expanded(), "subdir should be expanded");
749
750 let target_node = tree.get_node(node_id.unwrap()).unwrap();
752 assert_eq!(target_node.entry.name, "file3.txt");
753 assert!(target_node.is_file());
754 }
755
756 #[tokio::test]
757 async fn test_expand_to_path_not_under_root() {
758 let (_temp_dir, mut tree) = create_test_tree().await;
759
760 let outside_path = PathBuf::from("/tmp/somefile.txt");
762 let result = tree.expand_to_path(&outside_path).await;
763
764 assert!(
765 result.is_none(),
766 "Should return None for paths outside root"
767 );
768 }
769
770 #[tokio::test]
771 async fn test_expand_to_path_nonexistent() {
772 let (_temp_dir, mut tree) = create_test_tree().await;
773 let root_path = tree.root_path().to_path_buf();
774
775 let nonexistent_path = root_path.join("dir1/nonexistent.txt");
777 let result = tree.expand_to_path(&nonexistent_path).await;
778
779 assert!(result.is_none(), "Should return None for nonexistent paths");
780 }
781
782 }