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