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
« prev ^ index » next coverage.py v7.10.3, created at 2025-08-14 05:57 +0000
1"""File browser service for FileManager integration."""
3import datetime
4from dataclasses import dataclass
5from pathlib import Path
6from typing import List, Optional
7from enum import Enum
9from openhcs.constants.constants import Backend
10from openhcs.io.filemanager import FileManager
13class SelectionMode(Enum):
14 """File selection mode."""
15 FILES_ONLY = "files"
16 DIRECTORIES_ONLY = "directories"
17 BOTH = "both"
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
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 ""
35 size = self.size
36 if size < 1024:
37 return f"{size} B"
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"
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 ""
56class FileBrowserService:
57 """Service for file browser operations using FileManager."""
59 def __init__(self, file_manager: FileManager):
60 self.file_manager = file_manager
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
67 try:
68 entries = self.file_manager.list_dir(path, backend_str)
69 items = []
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))
80 # Sort: directories first, then files, alphabetically
81 return sorted(items, key=lambda x: (not x.is_dir, x.name.lower()))
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 []
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
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 = []
105 for item in items:
106 # Hidden files filter
107 if not show_hidden and item.name.startswith('.'):
108 continue
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
115 filtered.append(item)
117 return filtered