Coverage for openhcs/textual_tui/services/file_browser_service.py: 0.0%

75 statements  

« prev     ^ index     » next       coverage.py v7.10.3, created at 2025-08-14 05:57 +0000

1"""File browser service for FileManager integration.""" 

2 

3import datetime 

4from dataclasses import dataclass 

5from pathlib import Path 

6from typing import List, Optional 

7from enum import Enum 

8 

9from openhcs.constants.constants import Backend 

10from openhcs.io.filemanager import FileManager 

11 

12 

13class SelectionMode(Enum): 

14 """File selection mode.""" 

15 FILES_ONLY = "files" 

16 DIRECTORIES_ONLY = "directories" 

17 BOTH = "both" 

18 

19 

20@dataclass 

21class FileItem: 

22 """File or directory item.""" 

23 name: str 

24 path: Path 

25 is_dir: bool 

26 size: Optional[int] = None 

27 mtime: Optional[float] = None 

28 

29 @property 

30 def display_size(self) -> str: 

31 """Get formatted size string.""" 

32 if self.size is None or self.is_dir: 

33 return "" 

34 

35 size = self.size 

36 if size < 1024: 

37 return f"{size} B" 

38 

39 for unit in ['KB', 'MB', 'GB', 'TB']: 

40 size /= 1024.0 

41 if size < 1024.0: 

42 return f"{size:.1f} {unit}" 

43 return f"{size:.1f} PB" 

44 

45 @property 

46 def display_mtime(self) -> str: 

47 """Get formatted modification time.""" 

48 if self.mtime is None: 

49 return "" 

50 try: 

51 return datetime.datetime.fromtimestamp(self.mtime).strftime('%Y-%m-%d %H:%M') 

52 except (ValueError, OSError): 

53 return "" 

54 

55 

56class FileBrowserService: 

57 """Service for file browser operations using FileManager.""" 

58 

59 def __init__(self, file_manager: FileManager): 

60 self.file_manager = file_manager 

61 

62 def load_directory(self, path: Path, backend: Backend) -> List[FileItem]: 

63 """Load directory contents using FileManager.""" 

64 # Convert Backend enum to string for FileManager (evidence: file_browser.py line 110) 

65 backend_str = backend.value 

66 

67 try: 

68 entries = self.file_manager.list_dir(path, backend_str) 

69 items = [] 

70 

71 for name in entries: 

72 item_path = path / name 

73 try: 

74 is_dir = self.file_manager.is_dir(item_path, backend_str) 

75 except (NotADirectoryError, FileNotFoundError): 

76 # Path is a file, not a directory, or doesn't exist (broken symlink) 

77 is_dir = False 

78 items.append(FileItem(name=name, path=item_path, is_dir=is_dir)) 

79 

80 # Sort: directories first, then files, alphabetically 

81 return sorted(items, key=lambda x: (not x.is_dir, x.name.lower())) 

82 

83 except Exception as e: 

84 # Log error but return empty list for robustness 

85 import logging 

86 logger = logging.getLogger(__name__) 

87 logger.error(f"Failed to load directory {path}: {e}") 

88 return [] 

89 

90 def can_select_item(self, item: FileItem, selection_mode: SelectionMode) -> bool: 

91 """Check if item can be selected based on mode.""" 

92 if selection_mode == SelectionMode.FILES_ONLY: 

93 return not item.is_dir 

94 elif selection_mode == SelectionMode.DIRECTORIES_ONLY: 

95 return item.is_dir 

96 else: # SelectionMode.BOTH 

97 return True 

98 

99 def filter_items(self, items: List[FileItem], 

100 show_hidden: bool = False, 

101 extensions: Optional[List[str]] = None) -> List[FileItem]: 

102 """Filter items based on criteria.""" 

103 filtered = [] 

104 

105 for item in items: 

106 # Hidden files filter 

107 if not show_hidden and item.name.startswith('.'): 

108 continue 

109 

110 # Extension filter (only for files) 

111 if extensions and not item.is_dir: 

112 if not any(item.name.lower().endswith(ext.lower()) for ext in extensions): 

113 continue 

114 

115 filtered.append(item) 

116 

117 return filtered