Update on working process

swadhin biswas committed 1 years ago
f1cf876
1 parent fb9342e
commit f1cf876
23 files changed
+3935 -103
β–Ό.github/workflows/release.yml
+6-4
35
35
36
36
- name: Run tests
37
37
run: |
38
- python -m pytest tests/ -v --cov=laxyfile --cov-report=xml
38
+ python -m pytest tests/ -v --cov=laxyfile --cov-report=xml --cov-report=term-missing
39
39
40
40
- name: Upload coverage to Codecov
41
- uses: codecov/codecov-action@v3
41
+ uses: codecov/codecov-action@v4
42
42
with:
43
+ token: ${{ secrets.CODECOV_TOKEN }}
43
44
file: ./coverage.xml
44
- flags: unittests
45
- name: codecov-umbrella
45
+ flags: release,${{ matrix.os }},python${{ matrix.python-version }}
46
+ name: codecov-release-${{ matrix.os }}-py${{ matrix.python-version }}
46
47
fail_ci_if_error: false
48
+ verbose: true
47
49
48
50
build-packages:
49
51
needs: build-and-test
50
52
β–Ό.github/workflows/test.yml
+11-5
41
41
42
42
- name: Test with pytest
43
43
run: |
44
- python -m pytest tests/ -v --cov=laxyfile --cov-report=xml --cov-report=term-missing
44
+ python -m pytest tests/ -v --cov=laxyfile --cov-report=xml --cov-report=term-missing --cov-report=html
45
+ env:
46
+ COVERAGE_FILE: .coverage.${{ matrix.os }}.${{ matrix.python-version }}
45
47
46
- - name: Upload coverage to Codecov
47
- uses: codecov/codecov-action@v3
48
+ - name: Upload coverage reports to Codecov
49
+ uses: codecov/codecov-action@v4
48
50
with:
51
+ token: ${{ secrets.CODECOV_TOKEN }}
49
52
file: ./coverage.xml
50
- flags: unittests
51
- name: codecov-umbrella
53
+ flags: unittests,${{ matrix.os }},python${{ matrix.python-version }}
54
+ name: codecov-${{ matrix.os }}-py${{ matrix.python-version }}
52
55
fail_ci_if_error: false
56
+ verbose: true
57
+ env:
58
+ CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
53
59
54
60
security-scan:
55
61
runs-on: ubuntu-latest
56
62
β–ΌCODECOV_INTEGRATION_SUMMARY.md
+208-0
1
+# πŸ“Š Codecov Integration Summary
2
+
3
+## βœ… Complete Codecov Integration for LaxyFile
4
+
5
+The LaxyFile project now has **comprehensive Codecov integration** set up for automated code coverage reporting and monitoring.
6
+
7
+## 🎯 What's Been Implemented
8
+
9
+### 1. **Configuration Files**
10
+
11
+#### `codecov.yml` - Main Configuration
12
+
13
+- **Coverage targets**: 80% project, 75% patch coverage
14
+- **Component-based coverage** with individual targets for each module
15
+- **Intelligent ignoring** of test files, docs, and build artifacts
16
+- **Advanced reporting** with detailed comments and annotations
17
+
18
+#### `pytest.ini` - Enhanced Test Configuration
19
+
20
+- **Branch coverage** enabled for comprehensive analysis
21
+- **Parallel coverage** support for multi-process testing
22
+- **XML and HTML** report generation
23
+- **Smart exclusions** for uncoverable code patterns
24
+
25
+### 2. **GitHub Actions Integration**
26
+
27
+#### Test Workflow (`.github/workflows/test.yml`)
28
+
29
+- **Multi-platform coverage**: Ubuntu, macOS, Windows
30
+- **Multi-version testing**: Python 3.8-3.12
31
+- **Detailed flags**: OS and Python version specific
32
+- **Automatic upload** to Codecov with proper tokens
33
+
34
+#### Release Workflow (`.github/workflows/release.yml`)
35
+
36
+- **Pre-release coverage** validation
37
+- **Release-specific** coverage reporting
38
+- **Quality gates** before deployment
39
+
40
+### 3. **Coverage Targets by Component**
41
+
42
+| Component | Target | Rationale |
43
+| ------------------- | ------ | -------------------------------- |
44
+| **Core Components** | 85% | Critical functionality |
45
+| **File Operations** | 85% | Data integrity critical |
46
+| **Utilities** | 85% | Shared functionality |
47
+| **AI Components** | 80% | Complex external dependencies |
48
+| **Plugin System** | 80% | Dynamic loading complexity |
49
+| **UI Components** | 75% | Visual components harder to test |
50
+| **Preview System** | 75% | Media handling complexity |
51
+
52
+### 4. **Documentation & Tools**
53
+
54
+#### `docs/codecov-setup.md`
55
+
56
+- **Complete setup guide** for new contributors
57
+- **Troubleshooting section** for common issues
58
+- **Best practices** for writing testable code
59
+- **Monitoring guidelines** for maintaining coverage
60
+
61
+#### `scripts/run_coverage.py`
62
+
63
+- **Local coverage testing** script
64
+- **Comprehensive reporting** with HTML and XML output
65
+- **Coverage threshold validation** (70% minimum, 80% target)
66
+- **Easy-to-use** command-line interface
67
+
68
+### 5. **Visual Integration**
69
+
70
+#### README Badges
71
+
72
+```markdown
73
+[![codecov](https://codecov.io/gh/swadhinbiswas/laxyfile/branch/main/graph/badge.svg)](https://codecov.io/gh/swadhinbiswas/laxyfile)
74
+```
75
+
76
+#### Status Checks
77
+
78
+- **Automatic PR comments** with coverage analysis
79
+- **GitHub status checks** for coverage requirements
80
+- **Trend analysis** and historical tracking
81
+
82
+## πŸš€ Setup Instructions
83
+
84
+### For Repository Owner
85
+
86
+1. **Sign up for Codecov**: Visit [codecov.io](https://codecov.io)
87
+2. **Add repository**: Connect `swadhinbiswas/laxyfile`
88
+3. **Get upload token**: Copy from Codecov dashboard
89
+4. **Add GitHub secret**:
90
+ - Go to Settings β†’ Secrets β†’ Actions
91
+ - Add `CODECOV_TOKEN` with your upload token
92
+
93
+### For Contributors
94
+
95
+1. **Run tests locally**:
96
+
97
+ ```bash
98
+ python scripts/run_coverage.py
99
+ ```
100
+
101
+2. **View coverage report**:
102
+
103
+ ```bash
104
+ open htmlcov/index.html
105
+ ```
106
+
107
+3. **Check coverage before PR**:
108
+ ```bash
109
+ pytest --cov=laxyfile --cov-report=term-missing
110
+ ```
111
+
112
+## πŸ“ˆ Coverage Monitoring
113
+
114
+### Current Status
115
+
116
+- **Overall Target**: 80% project coverage
117
+- **Patch Target**: 75% for new code
118
+- **Component Targets**: 75-85% based on criticality
119
+- **Quality Gates**: Automatic PR status checks
120
+
121
+### Reporting Features
122
+
123
+- **Line-by-line analysis** of covered/uncovered code
124
+- **Pull request comments** with detailed coverage changes
125
+- **Historical trends** and coverage evolution
126
+- **Component breakdown** for focused improvement
127
+
128
+### Exclusions
129
+
130
+The following are intelligently excluded from coverage:
131
+
132
+- Test files (`tests/`)
133
+- Documentation (`docs/`)
134
+- Build scripts (`scripts/`)
135
+- Package metadata (`setup.py`, `__main__.py`)
136
+- Generated files (`build/`, `dist/`)
137
+- Type checking imports (`if TYPE_CHECKING:`)
138
+
139
+## 🎯 Benefits Achieved
140
+
141
+### βœ… **Automated Quality Gates**
142
+
143
+- Coverage requirements enforced on every PR
144
+- Prevents coverage regression
145
+- Maintains code quality standards
146
+
147
+### βœ… **Comprehensive Reporting**
148
+
149
+- Multi-platform coverage analysis
150
+- Component-specific tracking
151
+- Historical trend monitoring
152
+
153
+### βœ… **Developer Experience**
154
+
155
+- Clear coverage feedback on PRs
156
+- Local testing tools provided
157
+- Detailed setup documentation
158
+
159
+### βœ… **CI/CD Integration**
160
+
161
+- Seamless GitHub Actions integration
162
+- Release quality validation
163
+- Automated badge updates
164
+
165
+## πŸ”§ Usage Examples
166
+
167
+### Local Coverage Testing
168
+
169
+```bash
170
+# Run comprehensive coverage analysis
171
+python scripts/run_coverage.py
172
+
173
+# Quick coverage check
174
+pytest --cov=laxyfile --cov-report=term-missing
175
+
176
+# Generate HTML report
177
+coverage html && open htmlcov/index.html
178
+```
179
+
180
+### GitHub Integration
181
+
182
+- **Automatic**: Coverage uploaded on every push/PR
183
+- **PR Comments**: Detailed analysis in pull request comments
184
+- **Status Checks**: Pass/fail based on coverage thresholds
185
+- **Badge Updates**: README badge reflects current coverage
186
+
187
+### Codecov Dashboard
188
+
189
+- **Project Overview**: [codecov.io/gh/swadhinbiswas/laxyfile](https://codecov.io/gh/swadhinbiswas/laxyfile)
190
+- **Coverage Trends**: Historical analysis and graphs
191
+- **File Browser**: Line-by-line coverage exploration
192
+- **Component Analysis**: Module-specific coverage breakdown
193
+
194
+## πŸŽ‰ Integration Complete!
195
+
196
+LaxyFile now has **enterprise-grade code coverage integration** with:
197
+
198
+βœ… **Automated coverage reporting** on every commit
199
+βœ… **Multi-platform and multi-version** coverage analysis
200
+βœ… **Component-based coverage targets** for focused quality
201
+βœ… **Pull request integration** with detailed feedback
202
+βœ… **Local development tools** for easy testing
203
+βœ… **Comprehensive documentation** for contributors
204
+βœ… **Visual status indicators** with badges and checks
205
+
206
+The integration ensures LaxyFile maintains high code quality and comprehensive test coverage across all components, platforms, and Python versions. Contributors get immediate feedback on coverage impact, and maintainers have detailed insights into code quality trends.
207
+
208
+**Ready for production use!** πŸš€
0
209
β–ΌREADME.md
+6-0
1
1
# πŸš€ LaxyFile - Advanced Terminal File Manager
2
2
3
+[![Tests](https://github.com/swadhinbiswas/laxyfile/actions/workflows/test.yml/badge.svg)](https://github.com/swadhinbiswas/laxyfile/actions/workflows/test.yml)
4
+[![codecov](https://codecov.io/gh/swadhinbiswas/laxyfile/branch/main/graph/badge.svg)](https://codecov.io/gh/swadhinbiswas/laxyfile)
5
+[![PyPI version](https://badge.fury.io/py/laxyfile.svg)](https://badge.fury.io/py/laxyfile)
6
+[![Python 3.8+](https://img.shields.io/badge/python-3.8+-blue.svg)](https://www.python.org/downloads/)
7
+[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
8
+
3
9
**A beautiful, AI-powered terminal file manager inspired by Superfile with advanced media support and intelligent automation**
4
10
5
11
## ✨ Features
6
12
β–Όcodecov.yml
+119-0
1
+# Codecov configuration for LaxyFile
2
+# https://docs.codecov.com/docs/codecov-yaml
3
+
4
+coverage:
5
+ precision: 2
6
+ round: down
7
+ range: "70...100"
8
+
9
+ status:
10
+ project:
11
+ default:
12
+ target: 80%
13
+ threshold: 1%
14
+ base: auto
15
+ branches:
16
+ - main
17
+ - develop
18
+ patch:
19
+ default:
20
+ target: 75%
21
+ threshold: 2%
22
+
23
+ ignore:
24
+ - "tests/"
25
+ - "docs/"
26
+ - "scripts/"
27
+ - "packaging/"
28
+ - "build/"
29
+ - "dist/"
30
+ - "*.egg-info/"
31
+ - "setup.py"
32
+ - "conftest.py"
33
+ - "**/__pycache__/"
34
+ - "**/*.pyc"
35
+ - "**/test_*.py"
36
+ - "laxyfile/__main__.py" # Entry point, hard to test
37
+ - "laxyfile/main.py" # Main CLI, integration tested
38
+
39
+comment:
40
+ layout: "reach,diff,flags,tree,footer"
41
+ behavior: default
42
+ require_changes: false
43
+ require_base: no
44
+ require_head: yes
45
+
46
+github_checks:
47
+ annotations: true
48
+
49
+flags:
50
+ unittests:
51
+ paths:
52
+ - laxyfile/
53
+ carryforward: true
54
+ integration:
55
+ paths:
56
+ - laxyfile/
57
+ carryforward: true
58
+ performance:
59
+ paths:
60
+ - laxyfile/
61
+ carryforward: false
62
+
63
+component_management:
64
+ default_rules:
65
+ statuses:
66
+ - type: project
67
+ target: 80%
68
+ - type: patch
69
+ target: 75%
70
+ individual_components:
71
+ - component_id: core
72
+ name: Core Components
73
+ paths:
74
+ - laxyfile/core/
75
+ statuses:
76
+ - type: project
77
+ target: 85%
78
+ - component_id: ui
79
+ name: UI Components
80
+ paths:
81
+ - laxyfile/ui/
82
+ statuses:
83
+ - type: project
84
+ target: 75%
85
+ - component_id: ai
86
+ name: AI Components
87
+ paths:
88
+ - laxyfile/ai/
89
+ statuses:
90
+ - type: project
91
+ target: 80%
92
+ - component_id: operations
93
+ name: File Operations
94
+ paths:
95
+ - laxyfile/operations/
96
+ statuses:
97
+ - type: project
98
+ target: 85%
99
+ - component_id: preview
100
+ name: Preview System
101
+ paths:
102
+ - laxyfile/preview/
103
+ statuses:
104
+ - type: project
105
+ target: 75%
106
+ - component_id: plugins
107
+ name: Plugin System
108
+ paths:
109
+ - laxyfile/plugins/
110
+ statuses:
111
+ - type: project
112
+ target: 80%
113
+ - component_id: utils
114
+ name: Utilities
115
+ paths:
116
+ - laxyfile/utils/
117
+ statuses:
118
+ - type: project
119
+ target: 85%
0
120
β–Όdocs/codecov-setup.md
+200-0
1
+# Codecov Integration Setup Guide
2
+
3
+This guide explains how to set up and configure Codecov for LaxyFile's code coverage reporting.
4
+
5
+## Overview
6
+
7
+Codecov is integrated into LaxyFile's CI/CD pipeline to provide:
8
+
9
+- **Automated coverage reporting** for all pull requests and commits
10
+- **Coverage trends** and historical data
11
+- **Component-based coverage** tracking for different parts of the codebase
12
+- **Coverage status checks** to maintain code quality
13
+
14
+## Setup Steps
15
+
16
+### 1. Codecov Account Setup
17
+
18
+1. **Sign up for Codecov**: Go to [codecov.io](https://codecov.io) and sign up with your GitHub account
19
+2. **Add Repository**: Add the `swadhinbiswas/laxyfile` repository to your Codecov dashboard
20
+3. **Get Upload Token**: Copy the repository upload token from Codecov dashboard
21
+
22
+### 2. GitHub Secrets Configuration
23
+
24
+Add the following secrets to your GitHub repository:
25
+
26
+1. Go to **Settings** β†’ **Secrets and variables** β†’ **Actions**
27
+2. Add new repository secret:
28
+ - **Name**: `CODECOV_TOKEN`
29
+ - **Value**: Your Codecov upload token from step 1.3
30
+
31
+### 3. Configuration Files
32
+
33
+The following files are already configured in the repository:
34
+
35
+#### `codecov.yml` - Main Configuration
36
+
37
+```yaml
38
+coverage:
39
+ precision: 2
40
+ round: down
41
+ range: "70...100"
42
+
43
+ status:
44
+ project:
45
+ default:
46
+ target: 80% # Overall project coverage target
47
+ threshold: 1% # Allow 1% decrease
48
+ patch:
49
+ default:
50
+ target: 75% # New code coverage target
51
+ threshold: 2% # Allow 2% decrease for patches
52
+```
53
+
54
+#### Component Coverage Targets
55
+
56
+- **Core Components**: 85% coverage target
57
+- **File Operations**: 85% coverage target
58
+- **Utilities**: 85% coverage target
59
+- **AI Components**: 80% coverage target
60
+- **Plugin System**: 80% coverage target
61
+- **UI Components**: 75% coverage target
62
+- **Preview System**: 75% coverage target
63
+
64
+### 4. GitHub Actions Integration
65
+
66
+Coverage is automatically collected and uploaded in two workflows:
67
+
68
+#### Test Workflow (`.github/workflows/test.yml`)
69
+
70
+- Runs on every push and pull request
71
+- Tests across multiple OS and Python versions
72
+- Uploads coverage with detailed flags
73
+
74
+#### Release Workflow (`.github/workflows/release.yml`)
75
+
76
+- Runs on tagged releases
77
+- Ensures coverage before deployment
78
+- Uploads release-specific coverage data
79
+
80
+## Coverage Reports
81
+
82
+### Viewing Coverage
83
+
84
+1. **Codecov Dashboard**: Visit your repository on codecov.io
85
+2. **GitHub PR Comments**: Codecov automatically comments on pull requests
86
+3. **GitHub Status Checks**: Coverage status appears in PR checks
87
+4. **Local HTML Reports**: Generated in `htmlcov/` directory after running tests
88
+
89
+### Coverage Flags
90
+
91
+The integration uses several flags to organize coverage data:
92
+
93
+- `unittests`: Unit test coverage
94
+- `integration`: Integration test coverage
95
+- `performance`: Performance test coverage
96
+- `release`: Release build coverage
97
+- OS flags: `ubuntu-latest`, `macos-latest`, `windows-latest`
98
+- Python version flags: `python3.8`, `python3.9`, etc.
99
+
100
+### Understanding Coverage Reports
101
+
102
+#### Project Coverage
103
+
104
+- **Overall percentage** of code covered by tests
105
+- **Trend analysis** showing coverage changes over time
106
+- **File-by-file breakdown** of coverage percentages
107
+
108
+#### Patch Coverage
109
+
110
+- **New code coverage** for pull requests
111
+- **Line-by-line analysis** of what's covered
112
+- **Suggestions** for improving coverage
113
+
114
+#### Component Coverage
115
+
116
+- **Module-specific targets** for different parts of the codebase
117
+- **Individual status checks** for each component
118
+- **Focused improvement** recommendations
119
+
120
+## Best Practices
121
+
122
+### Writing Tests for Coverage
123
+
124
+1. **Aim for 80%+ overall coverage**
125
+2. **Focus on critical paths** first
126
+3. **Test edge cases** and error conditions
127
+4. **Mock external dependencies** appropriately
128
+
129
+### Improving Coverage
130
+
131
+1. **Identify uncovered lines** using coverage reports
132
+2. **Add tests for missing scenarios**
133
+3. **Remove dead code** that can't be covered
134
+4. **Use `# pragma: no cover`** for intentionally uncovered code
135
+
136
+### Coverage Exclusions
137
+
138
+The following are excluded from coverage:
139
+
140
+- Test files (`tests/`)
141
+- Documentation (`docs/`)
142
+- Build scripts (`scripts/`)
143
+- Package metadata (`setup.py`, `__main__.py`)
144
+- Generated files (`build/`, `dist/`)
145
+
146
+## Troubleshooting
147
+
148
+### Common Issues
149
+
150
+#### Coverage Not Uploading
151
+
152
+- Check that `CODECOV_TOKEN` is set correctly
153
+- Verify the coverage.xml file is generated
154
+- Check GitHub Actions logs for upload errors
155
+
156
+#### Low Coverage Warnings
157
+
158
+- Review uncovered lines in Codecov dashboard
159
+- Add tests for critical uncovered code
160
+- Consider adjusting coverage targets if appropriate
161
+
162
+#### Status Check Failures
163
+
164
+- Check if coverage dropped below thresholds
165
+- Review the specific component that failed
166
+- Add tests to bring coverage back up
167
+
168
+### Getting Help
169
+
170
+1. **Codecov Documentation**: [docs.codecov.com](https://docs.codecov.com)
171
+2. **GitHub Issues**: Report integration issues
172
+3. **Coverage Reports**: Use detailed reports to identify gaps
173
+
174
+## Monitoring and Maintenance
175
+
176
+### Regular Tasks
177
+
178
+1. **Review coverage trends** monthly
179
+2. **Update coverage targets** as codebase matures
180
+3. **Clean up excluded files** list periodically
181
+4. **Monitor component coverage** for balance
182
+
183
+### Coverage Goals
184
+
185
+- **Short-term**: Maintain 80%+ overall coverage
186
+- **Medium-term**: Achieve 85%+ for core components
187
+- **Long-term**: Establish 90%+ coverage for critical paths
188
+
189
+## Integration Benefits
190
+
191
+With Codecov properly configured, you get:
192
+
193
+βœ… **Automated coverage reporting** on every commit
194
+βœ… **Pull request coverage analysis** with detailed feedback
195
+βœ… **Historical coverage tracking** and trend analysis
196
+βœ… **Component-based coverage goals** for focused improvement
197
+βœ… **Integration with GitHub status checks** for quality gates
198
+βœ… **Visual coverage reports** with line-by-line analysis
199
+
200
+This ensures LaxyFile maintains high code quality and comprehensive test coverage across all components and platforms.
0
201
β–Όlaxyfile/core/component_initializer.py
+604-0
1
+"""
2
+ComponentInitializer - Manages proper initialization order and dependency resolution
3
+
4
+This module ensures that components are initialized in the correct order with proper
5
+dependency management and error handling.
6
+"""
7
+
8
+import time
9
+from typing import List, Dict, Any, Optional, Callable
10
+from dataclasses import dataclass, field
11
+from datetime import datetime
12
+from pathlib import Path
13
+
14
+from .file_manager_service import FileManagerService
15
+from .config import Config
16
+from ..utils.logger import Logger
17
+
18
+
19
+@dataclass
20
+class InitializationState:
21
+ """Tracks initialization status and component health"""
22
+ file_manager_ready: bool = False
23
+ ui_components_ready: bool = False
24
+ initialization_errors: List[str] = field(default_factory=list)
25
+ last_initialization_attempt: Optional[datetime] = None
26
+ component_status: Dict[str, bool] = field(default_factory=dict)
27
+ initialization_time: float = 0.0
28
+
29
+
30
+@dataclass
31
+class ComponentHealth:
32
+ """Tracks individual component health status"""
33
+ component_name: str
34
+ is_healthy: bool
35
+ last_check: datetime
36
+ error_message: Optional[str] = None
37
+ dependency_status: Dict[str, bool] = field(default_factory=dict)
38
+ initialization_time: float = 0.0
39
+
40
+
41
+class ComponentInitializer:
42
+ """Manages proper initialization order and dependency resolution"""
43
+
44
+ def __init__(self, config: Optional[Config] = None):
45
+ self.config = config or Config()
46
+ self.logger = Logger()
47
+ self.state = InitializationState()
48
+ self.component_health: Dict[str, ComponentHealth] = {}
49
+ self.initialization_callbacks: Dict[str, List[Callable]] = {}
50
+
51
+ # Component dependencies mapping
52
+ self.dependencies = {
53
+ 'file_manager': [],
54
+ 'theme_manager': ['file_manager'],
55
+ 'panel_manager': ['file_manager', 'theme_manager'],
56
+ 'media_viewer': ['file_manager'],
57
+ 'ai_assistant': ['file_manager'],
58
+ 'performance_optimizer': [],
59
+ 'animation_optimizer': [],
60
+ 'hotkey_manager': [],
61
+ 'ui_components': ['file_manager', 'theme_manager', 'panel_manager']
62
+ }
63
+
64
+ def initialize_core_services(self) -> bool:
65
+ """Initialize core services (file manager and essential services)
66
+
67
+ Returns:
68
+ bool: True if core services initialized successfully
69
+ """
70
+ start_time = time.time()
71
+ self.state.last_initialization_attempt = datetime.now()
72
+
73
+ try:
74
+ self.logger.info("Starting core services initialization")
75
+
76
+ # Initialize file manager service first
77
+ if not self._initialize_file_manager():
78
+ return False
79
+
80
+ # Initiiguration-dependent services
81
+ if not self._initialize_config_services():
82
+ return False
83
+
84
+ self.state.file_manager_ready = True
85
+ self.state.initialization_time = time.time() - start_time
86
+
87
+ self.logger.info(f"Core services initialized successfully in {self.state.initialization_time:.3f}s")
88
+ return True
89
+
90
+ except Exception as e:
91
+ error_msg = f"Core services initialization failed: {e}"
92
+ self.logger.error(error_msg)
93
+ self.state.initialization_errors.append(error_msg)
94
+ return False
95
+
96
+ def initialize_ui_components(self) -> bool:
97
+ """Initialize UI components after core services are ready
98
+
99
+ Returns:
100
+ bool: True if UI components initialized successfully
101
+ """
102
+ if not self.state.file_manager_ready:
103
+ error_msg = "Cannot initialize UI components: core services not ready"
104
+ self.logger.error(error_msg)
105
+ self.state.initialization_errors.append(error_msg)
106
+ return False
107
+
108
+ start_time = time.time()
109
+
110
+ try:
111
+ self.logger.info("Starting UI components initialization")
112
+
113
+ # Initialize theme manager
114
+ if not self._initialize_theme_manager():
115
+ return False
116
+
117
+ # Initialize panel manager
118
+ if not self._initialize_panel_manager():
119
+ return False
120
+
121
+ # Initialize media viewer
122
+ if not self._initialize_media_viewer():
123
+ return False
124
+
125
+ # Initialize other UI components
126
+ if not self._initialize_other_ui_components():
127
+ return False
128
+
129
+ self.state.ui_components_ready = True
130
+ ui_init_time = time.time() - start_time
131
+
132
+ self.logger.info(f"UI components initialized successfully in {ui_init_time:.3f}s")
133
+ return True
134
+
135
+ except Exception as e:
136
+ error_msg = f"UI components initialization failed: {e}"
137
+ self.logger.error(error_msg)
138
+ self.state.initialization_errors.append(error_msg)
139
+ return False
140
+
141
+ def verify_dependencies(self) -> List[str]:
142
+ """Check all components have required dependencies
143
+
144
+ Returns:
145
+ List of missing dependencies or empty list if all satisfied
146
+ """
147
+ missing_dependencies = []
148
+
149
+ try:
150
+ # Check file manager service
151
+ file_manager_service = FileManagerService.get_instance()
152
+ if not file_manager_service.is_healthy():
153
+ missing_dependencies.append("file_manager_service")
154
+
155
+ # Check each component's dependencies
156
+ for component, deps in self.dependencies.items():
157
+ component_health = self.component_health.get(component)
158
+ if component_health and not component_health.is_healthy:
159
+ continue # Skip unhealthy components
160
+
161
+ for dep in deps:
162
+ if not self._check_dependency_available(dep):
163
+ missing_dependencies.append(f"{component} -> {dep}")
164
+
165
+ return missing_dependencies
166
+
167
+ except Exception as e:
168
+ self.logger.error(f"Error verifying dependencies: {e}")
169
+ return [f"dependency_check_error: {e}"]
170
+
171
+ def get_initialization_status(self) -> Dict[str, Any]:
172
+ """Get detailed initialization status
173
+
174
+ Returns:
175
+ Dict containing initialization status information
176
+ """
177
+ return {
178
+ 'core_services_ready': self.state.file_manager_ready,
179
+ 'ui_components_ready': self.state.ui_components_ready,
180
+ 'initialization_errors': self.state.initialization_errors,
181
+ 'last_attempt': self.state.last_initialization_attempt,
182
+ 'initialization_time': self.state.initialization_time,
183
+ 'component_status': self.state.component_status,
184
+ 'missing_dependencies': self.verify_dependencies(),
185
+ 'component_health': {
186
+ name: {
187
+ 'healthy': health.is_healthy,
188
+ 'last_check': health.last_check,
189
+ 'error': health.error_message,
190
+ 'dependencies': health.dependency_status
191
+ }
192
+ for name, health in self.component_health.items()
193
+ }
194
+ }
195
+
196
+ def register_initialization_callback(self, component: str, callback: Callable) -> None:
197
+ """Register a callback to be called when a component is initialized
198
+
199
+ Args:
200
+ component: Component name
201
+ callback: Callback function to call
202
+ """
203
+ if component not in self.initialization_callbacks:
204
+ self.initialization_callbacks[component] = []
205
+ self.initialization_callbacks[component].append(callback)
206
+
207
+ def _initialize_file_manager(self) -> bool:
208
+ """Initialize the file manager service"""
209
+ try:
210
+ start_time = time.time()
211
+
212
+ file_manager_service = FileManagerService.get_instance()
213
+
214
+ # Determine if we should use advanced file manager
215
+ use_advanced = self.config.get('performance.use_advanced_file_manager', False)
216
+
217
+ success = file_manager_service.initialize(self.config, use_advanced)
218
+
219
+ init_time = time.time() - start_time
220
+
221
+ # Update component health
222
+ self.component_health['file_manager'] = ComponentHealth(
223
+ component_name='file_manager',
224
+ is_healthy=success and file_manager_service.is_healthy(),
225
+ last_check=datetime.now(),
226
+ error_message=None if success else "Initialization failed",
227
+ initialization_time=init_time
228
+ )
229
+
230
+ self.state.component_status['file_manager'] = success
231
+
232
+ if success:
233
+ self.logger.info(f"File manager initialized in {init_time:.3f}s")
234
+ self._call_initialization_callbacks('file_manager')
235
+ else:
236
+ self.logger.error("File manager initialization failed")
237
+
238
+ return success
239
+
240
+ except Exception as e:
241
+ error_msg = f"File manager initialization error: {e}"
242
+ self.logger.error(error_msg)
243
+ self.state.initialization_errors.append(error_msg)
244
+
245
+ self.component_health['file_manager'] = ComponentHealth(
246
+ component_name='file_manager',
247
+ is_healthy=False,
248
+ last_check=datetime.now(),
249
+ error_message=str(e)
250
+ )
251
+
252
+ return False
253
+
254
+ def _initialize_config_services(self) -> bool:
255
+ """Initialize configuration-dependent services"""
256
+ try:
257
+ # Initialize performance optimizer if enabled
258
+ if self.config.get('performance.enable_optimizer', True):
259
+ if not self._initialize_performance_optimizer():
260
+ self.logger.warning("Performance optimizer initialization failed")
261
+
262
+ # Initialize other core services as needed
263
+ return True
264
+
265
+ except Exception as e:
266
+ self.logger.error(f"Config services initialization error: {e}")
267
+ return False
268
+
269
+ def _initialize_theme_manager(self) -> bool:
270
+ """Initialize theme manager"""
271
+ try:
272
+ start_time = time.time()
273
+
274
+ # Import here to avoid circular imports
275
+ from ..ui.theme import ThemeManager
276
+
277
+ theme_manager = ThemeManager(self.config)
278
+ init_time = time.time() - start_time
279
+
280
+ self.component_health['theme_manager'] = ComponentHealth(
281
+ component_name='theme_manager',
282
+ is_healthy=True,
283
+ last_check=datetime.now(),
284
+ initialization_time=init_time,
285
+ dependency_status={'file_manager': True}
286
+ )
287
+
288
+ self.state.component_status['theme_manager'] = True
289
+ self.logger.info(f"Theme manager initialized in {init_time:.3f}s")
290
+ self._call_initialization_callbacks('theme_manager')
291
+
292
+ return True
293
+
294
+ except Exception as e:
295
+ error_msg = f"Theme manager initialization error: {e}"
296
+ self.logger.error(error_msg)
297
+ self.state.initialization_errors.append(error_msg)
298
+
299
+ self.component_health['theme_manager'] = ComponentHealth(
300
+ component_name='theme_manager',
301
+ is_healthy=False,
302
+ last_check=datetime.now(),
303
+ error_message=str(e)
304
+ )
305
+
306
+ return False
307
+
308
+ def _initialize_panel_manager(self) -> bool:
309
+ """Initialize panel manager"""
310
+ try:
311
+ start_time = time.time()
312
+
313
+ # Import here to avoid circular imports
314
+ from ..ui.panels import PanelManager
315
+ from ..ui.theme import ThemeManager
316
+
317
+ # Get theme manager (should be initialized by now)
318
+ theme_manager = ThemeManager(self.config)
319
+ panel_manager = PanelManager(theme_manager)
320
+
321
+ init_time = time.time() - start_time
322
+
323
+ self.component_health['panel_manager'] = ComponentHealth(
324
+ component_name='panel_manager',
325
+ is_healthy=True,
326
+ last_check=datetime.now(),
327
+ initialization_time=init_time,
328
+ dependency_status={
329
+ 'file_manager': True,
330
+ 'theme_manager': True
331
+ }
332
+ )
333
+
334
+ self.state.component_status['panel_manager'] = True
335
+ self.logger.info(f"Panel manager initialized in {init_time:.3f}s")
336
+ self._call_initialization_callbacks('panel_manager')
337
+
338
+ return True
339
+
340
+ except Exception as e:
341
+ error_msg = f"Panel manager initialization error: {e}"
342
+ self.logger.error(error_msg)
343
+ self.state.initialization_errors.append(error_msg)
344
+
345
+ self.component_health['panel_manager'] = ComponentHealth(
346
+ component_name='panel_manager',
347
+ is_healthy=False,
348
+ last_check=datetime.now(),
349
+ error_message=str(e)
350
+ )
351
+
352
+ return False
353
+
354
+ def _initialize_media_viewer(self) -> bool:
355
+ """Initialize media viewer"""
356
+ try:
357
+ start_time = time.time()
358
+
359
+ # Import here to avoid circular imports
360
+ from ..ui.image_viewer import MediaViewer
361
+ from rich.console import Console
362
+
363
+ console = Console()
364
+ media_viewer = MediaViewer(console)
365
+
366
+ init_time = time.time() - start_time
367
+
368
+ self.component_health['media_viewer'] = ComponentHealth(
369
+ component_name='media_viewer',
370
+ is_healthy=True,
371
+ last_check=datetime.now(),
372
+ initialization_time=init_time,
373
+ dependency_status={'file_manager': True}
374
+ )
375
+
376
+ self.state.component_status['media_viewer'] = True
377
+ self.logger.info(f"Media viewer initialized in {init_time:.3f}s")
378
+ self._call_initialization_callbacks('media_viewer')
379
+
380
+ return True
381
+
382
+ except Exception as e:
383
+ error_msg = f"Media viewer initialization error: {e}"
384
+ self.logger.error(error_msg)
385
+ self.state.initialization_errors.append(error_msg)
386
+
387
+ self.component_health['media_viewer'] = ComponentHealth(
388
+ component_name='media_viewer',
389
+ is_healthy=False,
390
+ last_check=datetime.now(),
391
+ error_message=str(e)
392
+ )
393
+
394
+ return False
395
+
396
+ def _initialize_performance_optimizer(self) -> bool:
397
+ """Initialize performance optimizer"""
398
+ try:
399
+ start_time = time.time()
400
+
401
+ # Import here to avoid circular imports
402
+ from ..core.performance_optimizer import PerformanceOptimizer, PerformanceConfig
403
+
404
+ perf_config = PerformanceConfig(
405
+ max_concurrent_operations=self.config.get('performance.max_concurrent_operations', 10),
406
+ chunk_size=self.config.get('performance.chunk_size', 100),
407
+ memory_threshold_mb=self.config.get('performance.memory_threshold_mb', 500),
408
+ lazy_loading_threshold=self.config.get('performance.lazy_loading_threshold', 1000),
409
+ background_processing=self.config.get('performance.background_processing', True),
410
+ use_threading=self.config.get('performance.use_threading', True),
411
+ max_worker_threads=self.config.get('performance.max_worker_threads', 4)
412
+ )
413
+
414
+ performance_optimizer = PerformanceOptimizer(perf_config)
415
+ init_time = time.time() - start_time
416
+
417
+ self.component_health['performance_optimizer'] = ComponentHealth(
418
+ component_name='performance_optimizer',
419
+ is_healthy=True,
420
+ last_check=datetime.now(),
421
+ initialization_time=init_time
422
+ )
423
+
424
+ self.state.component_status['performance_optimizer'] = True
425
+ self.logger.info(f"Performance optimizer initialized in {init_time:.3f}s")
426
+ self._call_initialization_callbacks('performance_optimizer')
427
+
428
+ return True
429
+
430
+ except Exception as e:
431
+ error_msg = f"Performance optimizer initialization error: {e}"
432
+ self.logger.error(error_msg)
433
+ self.state.initialization_errors.append(error_msg)
434
+
435
+ self.component_health['performance_optimizer'] = ComponentHealth(
436
+ component_name='performance_optimizer',
437
+ is_healthy=False,
438
+ last_check=datetime.now(),
439
+ error_message=str(e)
440
+ )
441
+
442
+ return False
443
+
444
+ def _initialize_other_ui_components(self) -> bool:
445
+ """Initialize other UI components"""
446
+ try:
447
+ # Initialize hotkey manager
448
+ if not self._initialize_hotkey_manager():
449
+ self.logger.warning("Hotkey manager initialization failed")
450
+
451
+ # Initialize AI assistant if enabled
452
+ if self.config.get('ai.enable_assistant', False):
453
+ if not self._initialize_ai_assistant():
454
+ self.logger.warning("AI assistant initialization failed")
455
+
456
+ return True
457
+
458
+ except Exception as e:
459
+ self.logger.error(f"Other UI components initialization error: {e}")
460
+ return False
461
+
462
+ def _initialize_hotkey_manager(self) -> bool:
463
+ """Initialize hotkey manager"""
464
+ try:
465
+ start_time = time.time()
466
+
467
+ # Import here to avoid circular imports
468
+ from ..utils.hotkeys import HotkeyManager
469
+
470
+ hotkey_manager = HotkeyManager()
471
+ init_time = time.time() - start_time
472
+
473
+ self.component_health['hotkey_manager'] = ComponentHealth(
474
+ component_name='hotkey_manager',
475
+ is_healthy=True,
476
+ last_check=datetime.now(),
477
+ initialization_time=init_time
478
+ )
479
+
480
+ self.state.component_status['hotkey_manager'] = True
481
+ self.logger.info(f"Hotkey manager initialized in {init_time:.3f}s")
482
+ self._call_initialization_callbacks('hotkey_manager')
483
+
484
+ return True
485
+
486
+ except Exception as e:
487
+ error_msg = f"Hotkey manager initialization error: {e}"
488
+ self.logger.error(error_msg)
489
+
490
+ self.component_health['hotkey_manager'] = ComponentHealth(
491
+ component_name='hotkey_manager',
492
+ is_healthy=False,
493
+ last_check=datetime.now(),
494
+ error_message=str(e)
495
+ )
496
+
497
+ return False
498
+
499
+ def _initialize_ai_assistant(self) -> bool:
500
+ """Initialize AI assistant"""
501
+ try:
502
+ start_time = time.time()
503
+
504
+ # Import here to avoid circular imports
505
+ from ..ai.assistant import AdvancedAIAssistant
506
+
507
+ ai_assistant = AdvancedAIAssistant(self.config)
508
+ init_time = time.time() - start_time
509
+
510
+ self.component_health['ai_assistant'] = ComponentHealth(
511
+ component_name='ai_assistant',
512
+ is_healthy=True,
513
+ last_check=datetime.now(),
514
+ initialization_time=init_time,
515
+ dependency_status={'file_manager': True}
516
+ )
517
+
518
+ self.state.component_status['ai_assistant'] = True
519
+ self.logger.info(f"AI assistant initialized in {init_time:.3f}s")
520
+ self._call_initialization_callbacks('ai_assistant')
521
+
522
+ return True
523
+
524
+ except Exception as e:
525
+ error_msg = f"AI assistant initialization error: {e}"
526
+ self.logger.error(error_msg)
527
+
528
+ self.component_health['ai_assistant'] = ComponentHealth(
529
+ component_name='ai_assistant',
530
+ is_healthy=False,
531
+ last_check=datetime.now(),
532
+ error_message=str(e)
533
+ )
534
+
535
+ return False
536
+
537
+ def _check_dependency_available(self, dependency: str) -> bool:
538
+ """Check if a dependency is available and healthy"""
539
+ if dependency == 'file_manager':
540
+ file_manager_service = FileManagerService.get_instance()
541
+ return file_manager_service.is_healthy()
542
+
543
+ component_health = self.component_health.get(dependency)
544
+ return component_health is not None and component_health.is_healthy
545
+
546
+ def _call_initialization_callbacks(self, component: str) -> None:
547
+ """Call initialization callbacks for a component"""
548
+ callbacks = self.initialization_callbacks.get(component, [])
549
+ for callback in callbacks:
550
+ try:
551
+ callback()
552
+ except Exception as e:
553
+ self.logger.error(f"Error in initialization callback for {component}: {e}")
554
+
555
+ def health_check(self) -> bool:
556
+ """Perform a comprehensive health check of all components
557
+
558
+ Returns:
559
+ bool: True if all components are healthy
560
+ """
561
+ try:
562
+ all_healthy = True
563
+
564
+ # Check file manager service
565
+ file_manager_service = FileManagerService.get_instance()
566
+ if not file_manager_service.is_healthy():
567
+ all_healthy = False
568
+ self.logger.warning("File manager service is not healthy")
569
+
570
+ # Check each component
571
+ for component_name, health in self.component_health.items():
572
+ # Update health status
573
+ try:
574
+ # Perform component-specific health checks here
575
+ health.last_check = datetime.now()
576
+ # For now, assume components remain healthy once initialized
577
+ # In a real implementation, you'd add specific health checks
578
+
579
+ except Exception as e:
580
+ health.is_healthy = False
581
+ health.error_message = str(e)
582
+ all_healthy = False
583
+ self.logger.warning(f"Component {component_name} health check failed: {e}")
584
+
585
+ return all_healthy
586
+
587
+ except Exception as e:
588
+ self.logger.error(f"Health check error: {e}")
589
+ return False
590
+
591
+ def cleanup(self) -> None:
592
+ """Clean up resources and reset state"""
593
+ try:
594
+ self.logger.info("Cleaning up component initializer")
595
+
596
+ # Clear callbacks
597
+ self.initialization_callbacks.clear()
598
+
599
+ # Reset state
600
+ self.state = InitializationState()
601
+ self.component_health.clear()
602
+
603
+ except Exception as e:
604
+ self.logger.error(f"Cleanup error: {e}")
0
605
β–Όlaxyfile/core/error_handling_mixin.py
+558-0
1
+"""
2
+ErrorHandlingMixin - Provides consistent error handling across all components
3
+
4
+This mixin provides standardized error handling, recovery mechanisms, and fallback
5
+behaviors for components that interact with the file manager.
6
+"""
7
+
8
+import time
9
+import functools
10
+from typing import Any, Optional, Callable, Dict, List, Union
11
+from pathlib import Path
12
+from datetime import datetime, timedelta
13
+
14
+from .file_manager_service import FileManagerService
15
+from ..utils.logger import Logger
16
+
17
+
18
+class ErrorHandlingMixin:
19
+ """Mixin providing consistent error handling across all components"""
20
+
21
+ def __init__(self):
22
+ """Initialize error handling mixin"""
23
+ if not hasattr(self, 'logger'):
24
+ self.logger = Logger()
25
+
26
+ # Error tracking
27
+ self._error_count = 0
28
+ self._last_error_time: Optional[datetime] = None
29
+ self._error_history: List[Dict[str, Any]] = []
30
+ self._max_error_history = 50
31
+
32
+ # Recovery settings
33
+ self._max_retry_attempts = 3
34
+ self._retry_delay_base = 1.0 # Base delay in seconds
35
+ self._recovery_timeout = 30.0 # Max time to spend on recovery
36
+ self._exponential_backoff_max = 10.0 # Max backoff delay
37
+
38
+ # Automatic recovery settings
39
+ self._auto_recovery_enabled = True
40
+ self._recovery_attempts = 0
41
+ self._max_recovery_attempts = 5
42
+ self._last_recovery_attempt: Optional[datetime] = None
43
+ self._recovery_cooldown = timedelta(minutes=1) # Wait between recovery attempts
44
+
45
+ # User notification callbacks
46
+ self._notification_callbacks: List[Callable] = []
47
+ self._recovery_status_callbacks: List[Callable] = []
48
+
49
+ # Fallback cache
50
+ self._fallback_cache: Dict[str, Any] = {}
51
+ self._cache_ttl = timedelta(minutes=5)
52
+
53
+ def safe_file_operation(self, operation: Callable, *args,
54
+ fallback_value: Any = None,
55
+ cache_key: Optional[str] = None,
56
+ max_retries: Optional[int] = None,
57
+ **kwargs) -> Optional[Any]:
58
+ """Execute file operations with comprehensive error handling
59
+
60
+ Args:
61
+ operation: The operation function to execute
62
+ *args: Arguments for the operation
63
+ fallback_value: Value to return if operation fails
64
+ cache_key: Key for caching results/fallbacks
65
+ max_retries: Maximum retry attempts (overrides default)
66
+ **kwargs: Keyword arguments for the operation
67
+
68
+ Returns:
69
+ Operation result or fallback value on error
70
+ """
71
+ operation_name = getattr(operation, '__name__', str(operation))
72
+ max_retries = max_retries or self._max_retry_attempts
73
+
74
+ # Check cache first if cache_key provided
75
+ if cache_key:
76
+ cached_result = self._get_cached_result(cache_key)
77
+ if cached_result is not None:
78
+ return cached_result
79
+
80
+ for attempt in range(max_retries + 1):
81
+ try:
82
+ start_time = time.time()
83
+
84
+ # Execute the operation
85
+ result = operation(*args, **kwargs)
86
+
87
+ # Cache successful result
88
+ if cache_key and result is not None:
89
+ self._cache_result(cache_key, result)
90
+
91
+ # Log successful recovery if this was a retry
92
+ if attempt > 0:
93
+ self.logger.info(f"Operation {operation_name} succeeded on attempt {attempt + 1}")
94
+
95
+ return result
96
+
97
+ except Exception as e:
98
+ error_info = {
99
+ 'operation': operation_name,
100
+ 'attempt': attempt + 1,
101
+ 'error': str(e),
102
+ 'error_type': type(e).__name__,
103
+ 'timestamp': datetime.now(),
104
+ 'args': str(args)[:100], # Truncate for logging
105
+ 'kwargs': str(kwargs)[:100]
106
+ }
107
+
108
+ self._record_error(error_info)
109
+
110
+ # If this is the last attempt, handle final failure
111
+ if attempt >= max_retries:
112
+ return self._handle_final_failure(
113
+ operation_name, e, fallback_value, cache_key
114
+ )
115
+
116
+ # Attempt recovery before retry
117
+ if not self._attempt_recovery(operation_name, e, attempt):
118
+ # Recovery failed, try fallback
119
+ return self._handle_final_failure(
120
+ operation_name, e, fallback_value, cache_key
121
+ )
122
+
123
+ # Wait before retry with exponential backoff
124
+ retry_delay = self._retry_delay_base * (2 ** attempt)
125
+ time.sleep(min(retry_delay, 5.0)) # Cap at 5 seconds
126
+
127
+ return fallback_value
128
+
129
+ def handle_file_manager_error(self, error: Exception, context: str = "") -> None:
130
+ """Standardized file manager error handling
131
+
132
+ Args:
133
+ error: The exception that occurred
134
+ context: Additional context about where the error occurred
135
+ """
136
+ error_type = type(error).__name__
137
+ error_msg = str(error)
138
+
139
+ # Log the error with context
140
+ if context:
141
+ self.logger.error(f"File manager error in {context}: {error_type}: {error_msg}")
142
+ else:
143
+ self.logger.error(f"File manager error: {error_type}: {error_msg}")
144
+
145
+ # Check if this is a critical file manager error
146
+ critical_errors = [
147
+ 'AttributeError', # Usually 'NoneType' object has no attribute
148
+ 'TypeError', # File manager is None
149
+ 'ImportError', # Module loading issues
150
+ ]
151
+
152
+ if error_type in critical_errors and 'NoneType' in error_msg:
153
+ self.logger.critical("Critical file manager error detected - attempting recovery")
154
+ self._attempt_file_manager_recovery()
155
+
156
+ # Update error statistics
157
+ self._error_count += 1
158
+ self._last_error_time = datetime.now()
159
+
160
+ def get_fallback_behavior(self, operation_type: str, **kwargs) -> Any:
161
+ """Provide fallback behavior when file manager is unavailable
162
+
163
+ Args:
164
+ operation_type: Type of operation that failed
165
+ **kwargs: Additional context for fallback behavior
166
+
167
+ Returns:
168
+ Appropriate fallback value or behavior
169
+ """
170
+ fallback_behaviors = {
171
+ 'list_directory': self._fallback_list_directory,
172
+ 'get_file_info': self._fallback_get_file_info,
173
+ 'file_operations': self._fallback_file_operations,
174
+ 'preview': self._fallback_preview,
175
+ 'search': self._fallback_search
176
+ }
177
+
178
+ fallback_func = fallback_behaviors.get(operation_type)
179
+ if fallback_func:
180
+ try:
181
+ return fallback_func(**kwargs)
182
+ except Exception as e:
183
+ self.logger.error(f"Fallback behavior failed for {operation_type}: {e}")
184
+
185
+ return self._get_generic_fallback(operation_type)
186
+
187
+ def is_file_manager_available(self) -> bool:
188
+ """Check if file manager is available and healthy
189
+
190
+ Returns:
191
+ bool: True if file manager is available and working
192
+ """
193
+ try:
194
+ service = FileManagerService.get_instance()
195
+ return service.is_healthy()
196
+ except Exception as e:
197
+ self.logger.error(f"Error checking file manager availability: {e}")
198
+ return False
199
+
200
+ def ensure_file_manager_available(self) -> bool:
201
+ """Ensure file manager is available, attempt recovery if needed
202
+
203
+ Returns:
204
+ bool: True if file manager is now available
205
+ """
206
+ if self.is_file_manager_available():
207
+ return True
208
+
209
+ self.logger.warning("File manager not available, attempting recovery")
210
+ return self._attempt_file_manager_recovery()
211
+
212
+ def get_error_statistics(self) -> Dict[str, Any]:
213
+ """Get error statistics for monitoring and debugging
214
+
215
+ Returns:
216
+ Dict containing error statistics
217
+ """
218
+ return {
219
+ 'total_errors': self._error_count,
220
+ 'last_error_time': self._last_error_time,
221
+ 'recent_errors': self._error_history[-10:], # Last 10 errors
222
+ 'error_rate': self._calculate_error_rate(),
223
+ 'file_manager_available': self.is_file_manager_available()
224
+ }
225
+
226
+ def clear_error_history(self) -> None:
227
+ """Clear error history and reset statistics"""
228
+ self._error_count = 0
229
+ self._last_error_time = None
230
+ self._error_history.clear()
231
+ self._fallback_cache.clear()
232
+ self._recovery_attempts = 0
233
+ self._last_recovery_attempt = None
234
+ self.logger.info("Error history cleared")
235
+
236
+ def register_notification_callback(self, callback: Callable[[str, str], None]) -> None:
237
+ """Register a callback for user notifications
238
+
239
+ Args:
240
+ callback: Function that takes (message, level) parameters
241
+ """
242
+ if callback not in self._notification_callbacks:
243
+ self._notification_callbacks.append(callback)
244
+
245
+ def register_recovery_status_callback(self, callback: Callable[[Dict[str, Any]], None]) -> None:
246
+ """Register a callback for recovery status updates
247
+
248
+ Args:
249
+ callback: Function that takes recovery status dict
250
+ """
251
+ if callback not in self._recovery_status_callbacks:
252
+ self._recovery_status_callbacks.append(callback)
253
+
254
+ def enable_auto_recovery(self, enabled: bool = True) -> None:
255
+ """Enable or disable automatic recovery
256
+
257
+ Args:
258
+ enabled: Whether to enable automatic recovery
259
+ """
260
+ self._auto_recovery_enabled = enabled
261
+ self.logger.info(f"Automatic recovery {'enabled' if enabled else 'disabled'}")
262
+
263
+ def trigger_manual_recovery(self) -> bool:
264
+ """Manually trigger recovery process
265
+
266
+ Returns:
267
+ bool: True if recovery was successful
268
+ """
269
+ self.logger.info("Manual recovery triggered")
270
+ self._notify_users("Attempting manual recovery...", "info")
271
+
272
+ success = self._attempt_file_manager_recovery_with_backoff()
273
+
274
+ if success:
275
+ self._notify_users("Recovery successful!", "success")
276
+ else:
277
+ self._notify_users("Recovery failed. Please restart the application.", "error")
278
+
279
+ return success
280
+
281
+ def _record_error(self, error_info: Dict[str, Any]) -> None:
282
+ """Record error information for tracking and analysis"""
283
+ self._error_history.append(error_info)
284
+
285
+ # Limit history size
286
+ if len(self._error_history) > self._max_error_history:
287
+ self._error_history = self._error_history[-self._max_error_history:]
288
+
289
+ self._error_count += 1
290
+ self._last_error_time = error_info['timestamp']
291
+
292
+ def _attempt_recovery(self, operation_name: str, error: Exception, attempt: int) -> bool:
293
+ """Attempt to recover from an error
294
+
295
+ Args:
296
+ operation_name: Name of the failed operation
297
+ error: The exception that occurred
298
+ attempt: Current attempt number
299
+
300
+ Returns:
301
+ bool: True if recovery was successful
302
+ """
303
+ try:
304
+ self.logger.info(f"Attempting recovery for {operation_name} (attempt {attempt + 1})")
305
+
306
+ # Check if this is a file manager related error
307
+ if self._is_file_manager_error(error):
308
+ return self._attempt_file_manager_recovery()
309
+
310
+ # Generic recovery attempts
311
+ time.sleep(0.1) # Brief pause
312
+ return True
313
+
314
+ except Exception as recovery_error:
315
+ self.logger.error(f"Recovery attempt failed: {recovery_error}")
316
+ return False
317
+
318
+ def _attempt_file_manager_recovery(self) -> bool:
319
+ """Attempt to recover the file manager service
320
+
321
+ Returns:
322
+ bool: True if recovery was successful
323
+ """
324
+ try:
325
+ self.logger.info("Attempting file manager recovery")
326
+
327
+ service = FileManagerService.get_instance()
328
+
329
+ # Try reinitialization
330
+ if service.reinitialize(force=True):
331
+ self.logger.info("File manager recovery successful")
332
+ return True
333
+
334
+ # If reinitialize failed, try getting a new instance
335
+ FileManagerService.reset()
336
+ new_service = FileManagerService.get_instance()
337
+
338
+ if new_service.ensure_initialized():
339
+ self.logger.info("File manager recovery successful with new instance")
340
+ return True
341
+
342
+ self.logger.error("File manager recovery failed")
343
+ return False
344
+
345
+ except Exception as e:
346
+ self.logger.error(f"File manager recovery error: {e}")
347
+ return False
348
+
349
+ def _handle_final_failure(self, operation_name: str, error: Exception,
350
+ fallback_value: Any, cache_key: Optional[str]) -> Any:
351
+ """Handle final failure after all retries exhausted
352
+
353
+ Args:
354
+ operation_name: Name of the failed operation
355
+ error: The final exception
356
+ fallback_value: Fallback value to return
357
+ cache_key: Cache key for storing fallback
358
+
359
+ Returns:
360
+ Fallback value or cached result
361
+ """
362
+ self.handle_file_manager_error(error, operation_name)
363
+
364
+ # Try to get a cached fallback
365
+ if cache_key:
366
+ cached_fallback = self._get_cached_fallback(cache_key)
367
+ if cached_fallback is not None:
368
+ self.logger.info(f"Using cached fallback for {operation_name}")
369
+ return cached_fallback
370
+
371
+ # Use provided fallback or generate one
372
+ if fallback_value is not None:
373
+ result = fallback_value
374
+ else:
375
+ result = self.get_fallback_behavior(operation_name)
376
+
377
+ # Cache the fallback for future use
378
+ if cache_key and result is not None:
379
+ self._cache_fallback(cache_key, result)
380
+
381
+ return result
382
+
383
+ def _is_file_manager_error(self, error: Exception) -> bool:
384
+ """Check if an error is related to file manager issues"""
385
+ error_msg = str(error).lower()
386
+ error_type = type(error).__name__
387
+
388
+ file_manager_indicators = [
389
+ 'nonetype',
390
+ 'file_manager',
391
+ 'list_directory',
392
+ 'has no attribute',
393
+ 'not initialized'
394
+ ]
395
+
396
+ return (error_type in ['AttributeError', 'TypeError'] or
397
+ any(indicator in error_msg for indicator in file_manager_indicators))
398
+
399
+ def _get_cached_result(self, cache_key: str) -> Optional[Any]:
400
+ """Get cached result if available and not expired"""
401
+ if cache_key not in self._fallback_cache:
402
+ return None
403
+
404
+ cached_data = self._fallback_cache[cache_key]
405
+ if datetime.now() - cached_data['timestamp'] > self._cache_ttl:
406
+ del self._fallback_cache[cache_key]
407
+ return None
408
+
409
+ return cached_data['value']
410
+
411
+ def _cache_result(self, cache_key: str, result: Any) -> None:
412
+ """Cache a successful result"""
413
+ self._fallback_cache[cache_key] = {
414
+ 'value': result,
415
+ 'timestamp': datetime.now(),
416
+ 'type': 'result'
417
+ }
418
+
419
+ def _get_cached_fallback(self, cache_key: str) -> Optional[Any]:
420
+ """Get cached fallback if available"""
421
+ fallback_key = f"fallback_{cache_key}"
422
+ return self._get_cached_result(fallback_key)
423
+
424
+ def _cache_fallback(self, cache_key: str, fallback: Any) -> None:
425
+ """Cache a fallback value"""
426
+ fallback_key = f"fallback_{cache_key}"
427
+ self._fallback_cache[fallback_key] = {
428
+ 'value': fallback,
429
+ 'timestamp': datetime.now(),
430
+ 'type': 'fallback'
431
+ }
432
+
433
+ def _calculate_error_rate(self) -> float:
434
+ """Calculate recent error rate"""
435
+ if not self._error_history:
436
+ return 0.0
437
+
438
+ # Calculate errors in the last hour
439
+ one_hour_ago = datetime.now() - timedelta(hours=1)
440
+ recent_errors = [
441
+ error for error in self._error_history
442
+ if error['timestamp'] > one_hour_ago
443
+ ]
444
+
445
+ return len(recent_errors) / 60.0 # Errors per minute
446
+
447
+ # Fallback behavior implementations
448
+
449
+ def _fallback_list_directory(self, path: Optional[Path] = None, **kwargs) -> List[Any]:
450
+ """Fallback behavior for directory listing"""
451
+ if path is None:
452
+ path = Path.cwd()
453
+
454
+ try:
455
+ # Try basic Python directory listing as fallback
456
+ items = []
457
+ if path.exists() and path.is_dir():
458
+ for item in path.iterdir():
459
+ # Create minimal file info
460
+ items.append({
461
+ 'name': item.name,
462
+ 'path': item,
463
+ 'is_dir': item.is_dir(),
464
+ 'is_file': item.is_file(),
465
+ 'size': item.stat().st_size if item.is_file() else 0,
466
+ 'modified': item.stat().st_mtime
467
+ })
468
+ return items
469
+ except Exception as e:
470
+ self.logger.error(f"Fallback directory listing failed: {e}")
471
+ return []
472
+
473
+ def _fallback_get_file_info(self, path: Optional[Path] = None, **kwargs) -> Optional[Dict[str, Any]]:
474
+ """Fallback behavior for getting file information"""
475
+ if path is None:
476
+ return None
477
+
478
+ try:
479
+ if path.exists():
480
+ stat_info = path.stat()
481
+ return {
482
+ 'name': path.name,
483
+ 'path': path,
484
+ 'is_dir': path.is_dir(),
485
+ 'is_file': path.is_file(),
486
+ 'size': stat_info.st_size,
487
+ 'modified': stat_info.st_mtime,
488
+ 'file_type': 'directory' if path.is_dir() else 'file'
489
+ }
490
+ except Exception as e:
491
+ self.logger.error(f"Fallback file info failed: {e}")
492
+
493
+ return None
494
+
495
+ def _fallback_file_operations(self, **kwargs) -> bool:
496
+ """Fallback behavior for file operations"""
497
+ self.logger.warning("File operations not available - file manager unavailable")
498
+ return False
499
+
500
+ def _fallback_preview(self, **kwargs) -> str:
501
+ """Fallback behavior for file preview"""
502
+ return "Preview not available - file manager unavailable"
503
+
504
+ def _fallback_search(self, **kwargs) -> List[Any]:
505
+ """Fallback behavior for file search"""
506
+ return []
507
+
508
+ def _get_generic_fallback(self, operation_type: str) -> Any:
509
+ """Get generic fallback for unknown operation types"""
510
+ fallback_map = {
511
+ 'list': [],
512
+ 'get': None,
513
+ 'search': [],
514
+ 'operation': False,
515
+ 'preview': "Not available",
516
+ 'info': None
517
+ }
518
+
519
+ # Try to match operation type to fallback
520
+ for key, value in fallback_map.items():
521
+ if key in operation_type.lower():
522
+ return value
523
+
524
+ return None
525
+
526
+
527
+def safe_file_operation(fallback_value: Any = None, cache_key: Optional[str] = None,
528
+ max_retries: int = 3):
529
+ """Decorator for safe file operations with error handling
530
+
531
+ Args:
532
+ fallback_value: Value to return on failure
533
+ cache_key: Key for caching results
534
+ max_retries: Maximum retry attempts
535
+ """
536
+ def decorator(func: Callable) -> Callable:
537
+ @functools.wraps(func)
538
+ def wrapper(self, *args, **kwargs):
539
+ if not isinstance(self, ErrorHandlingMixin):
540
+ # If not using mixin, create temporary error handler
541
+ error_handler = ErrorHandlingMixin()
542
+ return error_handler.safe_file_operation(
543
+ func, self, *args,
544
+ fallback_value=fallback_value,
545
+ cache_key=cache_key,
546
+ max_retries=max_retries,
547
+ **kwargs
548
+ )
549
+ else:
550
+ return self.safe_file_operation(
551
+ func, self, *args,
552
+ fallback_value=fallback_value,
553
+ cache_key=cache_key,
554
+ max_retries=max_retries,
555
+ **kwargs
556
+ )
557
+ return wrapper
558
+ return decorator
0
559
β–Όlaxyfile/core/file_manager_service.py
+312-0
1
+"""
2
+FileManagerService - Thread-safe singleton service for file management
3
+
4
+This module provides a centralized, thread-safe file manager service that ensures
5
+the file manager is always available when needed and handles initialization errors gracefully.
6
+"""
7
+
8
+import threading
9
+import time
10
+from pathlib import Path
11
+from typing import Optional, Dict, Any, List
12
+from datetime import datetime
13
+
14
+from .file_manager import FileManager
15
+from .advanced_file_manager import AdvancedFileManager
16
+from .config import Config
17
+from ..utils.logger import Logger
18
+
19
+
20
+class FileManagerService:
21
+ """Thread-safe singleton service for file management"""
22
+
23
+ _instance: Optional['FileManagerService'] = None
24
+ _lock = threading.Lock()
25
+ _initialized = False
26
+
27
+ def __init__(self):
28
+ """Private constructor - use get_instance() instead"""
29
+ if FileManagerService._instance is not None:
30
+ raise RuntimeError("Use FileManagerService.get_instance() instead of direct instantiation")
31
+
32
+ self.logger = Logger()
33
+ self._file_manager: Optional[FileManager] = None
34
+ self._advanced_file_manager: Optional[AdvancedFileManager] = None
35
+ self._config: Optional[Config] = None
36
+ self._initialization_lock = threading.Lock()
37
+ self._initialization_attempts = 0
38
+ self._last_initialization_attempt: Optional[datetime] = None
39
+ self._initialization_error: Optional[str] = None
40
+ self._use_advanced = False
41
+
42
+ @classmethod
43
+ def get_instance(cls) -> 'FileManagerService':
44
+ """Get the singleton instance with thread-safe lazy initialization"""
45
+ if cls._instance is None:
46
+ with cls._lock:
47
+ if cls._instance is None:
48
+ cls._instance = cls()
49
+ return cls._instance
50
+
51
+ def initialize(self, config: Optional[Config] = None, use_advanced: bool = False) -> bool:
52
+ """Initialize the file manager service
53
+
54
+ Args:
55
+ config: Configuration object (optional)
56
+ use_advanced: Whether to use AdvancedFileManager (default: False)
57
+
58
+ Returns:
59
+ bool: True if initialization successful, False otherwise
60
+ """
61
+ with self._initialization_lock:
62
+ try:
63
+ self._config = config or Config()
64
+ self._use_advanced = use_advanced
65
+ self._initialization_attempts += 1
66
+ self._last_initialization_attempt = datetime.now()
67
+
68
+ if use_advanced:
69
+ self._advanced_file_manager = AdvancedFileManager(self._config)
70
+ # Initialize async components if available
71
+ import asyncio
72
+ try:
73
+ loop = asyncio.get_event_loop()
74
+ if loop.is_running():
75
+ # Schedule initialization in the background
76
+ asyncio.create_task(self._advanced_file_manager.initialize())
77
+ else:
78
+ # Run initialization synchronously
79
+ loop.run_until_complete(self._advanced_file_manager.initialize())
80
+ except RuntimeError:
81
+ # No event loop, skip async initialization
82
+ self.logger.warning("No event loop available for advanced file manager initialization")
83
+ else:
84
+ self._file_manager = FileManager()
85
+
86
+ FileManagerService._initialized = True
87
+ self._initialization_error = None
88
+ self.logger.info(f"FileManagerService initialized successfully (advanced={use_advanced})")
89
+ return True
90
+
91
+ except Exception as e:
92
+ self._initialization_error = str(e)
93
+ self.logger.error(f"Failed to initialize FileManagerService: {e}")
94
+ return False
95
+
96
+ def ensure_initialized(self) -> bool:
97
+ """Ensure the file manager is properly initialized
98
+
99
+ Returns:
100
+ bool: True if file manager is available, False otherwise
101
+ """
102
+ if not FileManagerService._initialized or (not self._file_manager and not self._advanced_file_manager):
103
+ return self.initialize()
104
+ return True
105
+
106
+ def reinitialize(self, force: bool = False) -> bool:
107
+ """Attempt to reinitialize the file manager
108
+
109
+ Args:
110
+ force: Force reinitialization even if already initialized
111
+
112
+ Returns:
113
+ bool: True if reinitialization successful, False otherwise
114
+ """
115
+ if not force and self.is_healthy():
116
+ return True
117
+
118
+ with self._initialization_lock:
119
+ try:
120
+ # Reset state
121
+ FileManagerService._initialized = False
122
+ self._file_manager = None
123
+ self._advanced_file_manager = None
124
+
125
+ # Attempt reinitialization
126
+ return self.initialize(self._config, self._use_advanced)
127
+
128
+ except Exception as e:
129
+ self.logger.error(f"Failed to reinitialize FileManagerService: {e}")
130
+ return False
131
+
132
+ def get_file_manager(self) -> Optional[FileManager]:
133
+ """Get the file manager instance with automatic initialization
134
+
135
+ Returns:
136
+ FileManager instance or None if initialization failed
137
+ """
138
+ if not self.ensure_initialized():
139
+ return None
140
+
141
+ if self._use_advanced:
142
+ return self._advanced_file_manager
143
+ else:
144
+ return self._file_manager
145
+
146
+ def is_healthy(self) -> bool:
147
+ """Check if the file manager service is healthy
148
+
149
+ Returns:
150
+ bool: True if service is healthy and ready to use
151
+ """
152
+ try:
153
+ if not FileManagerService._initialized:
154
+ return False
155
+
156
+ file_manager = self._advanced_file_manager if self._use_advanced else self._file_manager
157
+ if file_manager is None:
158
+ return False
159
+
160
+ # Test basic functionality
161
+ if hasattr(file_manager, 'list_directory'):
162
+ # Try to list current directory as a health check
163
+ test_path = Path.cwd()
164
+ if self._use_advanced:
165
+ # For advanced file manager, we need to handle async
166
+ import asyncio
167
+ try:
168
+ loop = asyncio.get_event_loop()
169
+ if loop.is_running():
170
+ # Can't test async in running loop, assume healthy
171
+ return True
172
+ else:
173
+ loop.run_until_complete(file_manager.list_directory(test_path))
174
+ except RuntimeError:
175
+ # No event loop, assume healthy if object exists
176
+ return True
177
+ else:
178
+ file_manager.list_directory(test_path)
179
+ return True
180
+
181
+ return False
182
+
183
+ except Exception as e:
184
+ self.logger.warning(f"Health check failed: {e}")
185
+ return False
186
+
187
+ def get_status(self) -> Dict[str, Any]:
188
+ """Get detailed status information about the service
189
+
190
+ Returns:
191
+ Dict containing status information
192
+ """
193
+ return {
194
+ 'initialized': FileManagerService._initialized,
195
+ 'healthy': self.is_healthy(),
196
+ 'use_advanced': self._use_advanced,
197
+ 'initialization_attempts': self._initialization_attempts,
198
+ 'last_initialization_attempt': self._last_initialization_attempt,
199
+ 'initialization_error': self._initialization_error,
200
+ 'has_file_manager': self._file_manager is not None,
201
+ 'has_advanced_file_manager': self._advanced_file_manager is not None
202
+ }
203
+
204
+ def list_directory(self, path: Path, show_hidden: bool = False, **kwargs):
205
+ """Safe wrapper for list_directory operation
206
+
207
+ Args:
208
+ path: Directory path to list
209
+ show_hidden: Whether to show hidden files
210
+ **kwargs: Additional arguments for advanced file manager
211
+
212
+ Returns:
213
+ List of file information or empty list on error
214
+ """
215
+ try:
216
+ file_manager = self.get_file_manager()
217
+ if file_manager is None:
218
+ self.logger.error("File manager not available for list_directory")
219
+ return []
220
+
221
+ if self._use_advanced:
222
+ # Handle async call for advanced file manager
223
+ import asyncio
224
+ try:
225
+ loop = asyncio.get_event_loop()
226
+ if loop.is_running():
227
+ # Create a task for async execution
228
+ task = asyncio.create_task(
229
+ file_manager.list_directory(path, show_hidden, **kwargs)
230
+ )
231
+ # For now, return empty list and log warning
232
+ # In a real implementation, this would need proper async handling
233
+ self.logger.warning("Async list_directory called from sync context")
234
+ return []
235
+ else:
236
+ return loop.run_until_complete(
237
+ file_manager.list_directory(path, show_hidden, **kwargs)
238
+ )
239
+ except RuntimeError:
240
+ # Fallback to basic file manager
241
+ if self._file_manager is None:
242
+ self._file_manager = FileManager()
243
+ return self._file_manager.list_directory(path, show_hidden)
244
+ else:
245
+ return file_manager.list_directory(path, show_hidden)
246
+
247
+ except Exception as e:
248
+ self.logger.error(f"Error in list_directory: {e}")
249
+ return []
250
+
251
+ def safe_operation(self, operation_name: str, operation_func, *args, **kwargs):
252
+ """Execute a file operation safely with error handling
253
+
254
+ Args:
255
+ operation_name: Name of the operation for logging
256
+ operation_func: Function to execute
257
+ *args: Arguments for the operation
258
+ **kwargs: Keyword arguments for the operation
259
+
260
+ Returns:
261
+ Operation result or None on error
262
+ """
263
+ try:
264
+ file_manager = self.get_file_manager()
265
+ if file_manager is None:
266
+ self.logger.error(f"File manager not available for {operation_name}")
267
+ return None
268
+
269
+ if not hasattr(file_manager, operation_func.__name__):
270
+ self.logger.error(f"Operation {operation_func.__name__} not available on file manager")
271
+ return None
272
+
273
+ return operation_func(*args, **kwargs)
274
+
275
+ except Exception as e:
276
+ self.logger.error(f"Error in {operation_name}: {e}")
277
+
278
+ # Attempt recovery
279
+ if self.reinitialize():
280
+ try:
281
+ file_manager = self.get_file_manager()
282
+ if file_manager and hasattr(file_manager, operation_func.__name__):
283
+ return operation_func(*args, **kwargs)
284
+ except Exception as recovery_error:
285
+ self.logger.error(f"Recovery attempt failed for {operation_name}: {recovery_error}")
286
+
287
+ return None
288
+
289
+ @classmethod
290
+ def reset(cls):
291
+ """Reset the singleton instance (mainly for testing)"""
292
+ with cls._lock:
293
+ cls._instance = None
294
+ cls._initialized = False
295
+
296
+
297
+# Convenience functions for common operations
298
+def get_file_manager_service() -> FileManagerService:
299
+ """Get the file manager service instance"""
300
+ return FileManagerService.get_instance()
301
+
302
+
303
+def safe_list_directory(path: Path, show_hidden: bool = False, **kwargs):
304
+ """Safely list directory contents"""
305
+ service = get_file_manager_service()
306
+ return service.list_directory(path, show_hidden, **kwargs)
307
+
308
+
309
+def ensure_file_manager_available() -> bool:
310
+ """Ensure file manager is available and healthy"""
311
+ service = get_file_manager_service()
312
+ return service.ensure_initialized() and service.is_healthy()
0
313
β–Όlaxyfile/core/initialization_models.py
+510-0
1
+"""
2
+Initialization Models - Data models for tracking initialization status and component health
3
+
4
+This module provides data models for tracking the initialization state and health
5
+of various components in the LaxyFile application.
6
+"""
7
+
8
+from dataclasses import dataclass, field
9
+from typing import Dict, List, Optional, Any
10
+from datetime import datetime
11
+from enum import Enum
12
+
13
+
14
+class ComponentStatus(Enum):
15
+ """Enumeration of component status states"""
16
+ NOT_INITIALIZED = "not_initialized"
17
+ INITIALIZING = "initializing"
18
+ INITIALIZED = "initialized"
19
+ HEALTHY = "healthy"
20
+ UNHEALTHY = "unhealthy"
21
+ FAILED = "failed"
22
+ RECOVERING = "recovering"
23
+
24
+
25
+class InitializationPhase(Enum):
26
+ """Enumeration of initialization phases"""
27
+ NOT_STARTED = "not_started"
28
+ CORE_SERVICES = "core_services"
29
+ UI_COMPONENTS = "ui_components"
30
+ INTEGRATION = "integration"
31
+ COMPLETED = "completed"
32
+ FAILED = "failed"
33
+
34
+
35
+@dataclass
36
+class InitializationState:
37
+ """Tracks overall initialization status and component health"""
38
+
39
+ # Phase tracking
40
+ current_phase: InitializationPhase = InitializationPhase.NOT_STARTED
41
+ file_manager_ready: bool = False
42
+ ui_components_ready: bool = False
43
+ integration_complete: bool = False
44
+
45
+ # Error tracking
46
+ initialization_errors: List[str] = field(default_factory=list)
47
+ last_initialization_attempt: Optional[datetime] = None
48
+ initialization_start_time: Optional[datetime] = None
49
+ initialization_end_time: Optional[datetime] = None
50
+
51
+ # Component status tracking
52
+ component_status: Dict[str, ComponentStatus] = field(default_factory=dict)
53
+
54
+ # Performance metrics
55
+ initialization_time: float = 0.0
56
+ total_components: int = 0
57
+ successful_components: int = 0
58
+ failed_components: int = 0
59
+
60
+ # Recovery tracking
61
+ recovery_attempts: int = 0
62
+ last_recovery_attempt: Optional[datetime] = None
63
+ recovery_successful: bool = False
64
+
65
+ def get_initialization_progress(self) -> float:
66
+ """Get initialization progress as a percentage
67
+
68
+ Returns:
69
+ float: Progress percentage (0.0 to 100.0)
70
+ """
71
+ if self.total_components == 0:
72
+ return 0.0
73
+
74
+ return (self.successful_components / self.total_components) * 100.0
75
+
76
+ def is_initialization_complete(self) -> bool:
77
+ """Check if initialization is complete
78
+
79
+ Returns:
80
+ bool: True if initialization is complete and successful
81
+ """
82
+ return (self.current_phase == InitializationPhase.COMPLETED and
83
+ self.file_manager_ready and
84
+ self.ui_components_ready and
85
+ self.integration_complete)
86
+
87
+ def has_critical_errors(self) -> bool:
88
+ """Check if there are critical initialization errors
89
+
90
+ Returns:
91
+ bool: True if there are critical errors
92
+ """
93
+ critical_keywords = ['file_manager', 'critical', 'fatal', 'core']
94
+ return any(
95
+ any(keyword in error.lower() for keyword in critical_keywords)
96
+ for error in self.initialization_errors
97
+ )
98
+
99
+ def get_failed_components(self) -> List[str]:
100
+ """Get list of components that failed to initialize
101
+
102
+ Returns:
103
+ List of component names that failed
104
+ """
105
+ return [
106
+ component for component, status in self.component_status.items()
107
+ if status == ComponentStatus.FAILED
108
+ ]
109
+
110
+ def get_healthy_components(self) -> List[str]:
111
+ """Get list of healthy components
112
+
113
+ Returns:
114
+ List of component names that are healthy
115
+ """
116
+ return [
117
+ component for component, status ionent_status.items()
118
+ if status == ComponentStatus.HEALTHY
119
+ ]
120
+
121
+ def add_error(self, error_message: str) -> None:
122
+ """Add an initialization error
123
+
124
+ Args:
125
+ error_message: Error message to add
126
+ """
127
+ self.initialization_errors.append(error_message)
128
+
129
+ # Update failed components count
130
+ if not any('recovery' in error_message.lower() for _ in [None]):
131
+ self.failed_components += 1
132
+
133
+ def set_component_status(self, component: str, status: ComponentStatus) -> None:
134
+ """Set the status of a component
135
+
136
+ Args:
137
+ component: Component name
138
+ status: New status
139
+ """
140
+ old_status = self.component_status.get(component)
141
+ self.component_status[component] = status
142
+
143
+ # Update counters
144
+ if old_status != status:
145
+ if status == ComponentStatus.HEALTHY:
146
+ if old_status != ComponentStatus.HEALTHY:
147
+ self.successful_components += 1
148
+ if old_status == ComponentStatus.FAILED:
149
+ self.failed_components -= 1
150
+ elif status == ComponentStatus.FAILED:
151
+ if old_status != ComponentStatus.FAILED:
152
+ self.failed_components += 1
153
+ if old_status == ComponentStatus.HEALTHY:
154
+ self.successful_components -= 1
155
+
156
+ def to_dict(self) -> Dict[str, Any]:
157
+ """Convert to dictionary for serialization
158
+
159
+ Returns:
160
+ Dict representation of the initialization state
161
+ """
162
+ return {
163
+ 'current_phase': self.current_phase.value,
164
+ 'file_manager_ready': self.file_manager_ready,
165
+ 'ui_components_ready': self.ui_components_ready,
166
+ 'integration_complete': self.integration_complete,
167
+ 'initialization_errors': self.initialization_errors,
168
+ 'last_initialization_attempt': self.last_initialization_attempt.isoformat() if self.last_initialization_attempt else None,
169
+ 'initialization_time': self.initialization_time,
170
+ 'total_components': self.total_components,
171
+ 'successful_components': self.successful_components,
172
+ 'failed_components': self.failed_components,
173
+ 'progress_percentage': self.get_initialization_progress(),
174
+ 'is_complete': self.is_initialization_complete(),
175
+ 'has_critical_errors': self.has_critical_errors(),
176
+ 'component_status': {k: v.value for k, v in self.component_status.items()},
177
+ 'recovery_attempts': self.recovery_attempts,
178
+ 'recovery_successful': self.recovery_successful
179
+ }
180
+
181
+
182
+@dataclass
183
+class ComponentHealth:
184
+ """Tracks individual component health status and metrics"""
185
+
186
+ # Basic information
187
+ component_name: str
188
+ status: ComponentStatus = ComponentStatus.NOT_INITIALIZED
189
+ is_healthy: bool = False
190
+
191
+ # Timing information
192
+ last_check: datetime = field(default_factory=datetime.now)
193
+ initialization_time: float = 0.0
194
+ last_successful_operation: Optional[datetime] = None
195
+ last_failed_operation: Optional[datetime] = None
196
+
197
+ # Error tracking
198
+ error_message: Optional[str] = None
199
+ error_count: int = 0
200
+ last_error_time: Optional[datetime] = None
201
+ error_history: List[Dict[str, Any]] = field(default_factory=list)
202
+
203
+ # Dependency tracking
204
+ dependency_status: Dict[str, bool] = field(default_factory=dict)
205
+ required_dependencies: List[str] = field(default_factory=list)
206
+ optional_dependencies: List[str] = field(default_factory=list)
207
+
208
+ # Performance metrics
209
+ average_response_time: float = 0.0
210
+ operation_count: int = 0
211
+ success_rate: float = 100.0
212
+
213
+ # Recovery tracking
214
+ recovery_attempts: int = 0
215
+ last_recovery_attempt: Optional[datetime] = None
216
+ recovery_successful: bool = False
217
+
218
+ def update_health_status(self) -> None:
219
+ """Update the health status based on current metrics"""
220
+ try:
221
+ # Check if all required dependencies are available
222
+ all_deps_available = all(
223
+ self.dependency_status.get(dep, False)
224
+ for dep in self.required_dependencies
225
+ )
226
+
227
+ # Determine health based on various factors
228
+ if self.status == ComponentStatus.FAILED:
229
+ self.is_healthy = False
230
+ elif self.status == ComponentStatus.NOT_INITIALIZED:
231
+ self.is_healthy = False
232
+ elif not all_deps_available:
233
+ self.is_healthy = False
234
+ elif self.error_count > 10: # Too many errors
235
+ self.is_healthy = False
236
+ elif self.success_rate < 50.0: # Low success rate
237
+ self.is_healthy = False
238
+ else:
239
+ self.is_healthy = True
240
+ self.status = ComponentStatus.HEALTHY
241
+
242
+ self.last_check = datetime.now()
243
+
244
+ except Exception as e:
245
+ self.is_healthy = False
246
+ self.error_message = f"Health check failed: {e}"
247
+
248
+ def record_operation(self, success: bool, response_time: float = 0.0,
249
+ error_message: Optional[str] = None) -> None:
250
+ """Record the result of an operation
251
+
252
+ Args:
253
+ success: Whether the operation was successful
254
+ response_time: Time taken for the operation
255
+ error_message: Error message if operation failed
256
+ """
257
+ self.operation_count += 1
258
+
259
+ if success:
260
+ self.last_successful_operation = datetime.now()
261
+
262
+ # Update average response time
263
+ if self.operation_count == 1:
264
+ self.average_response_time = response_time
265
+ else:
266
+ self.average_response_time = (
267
+ (self.average_response_time * (self.operation_count - 1) + response_time) /
268
+ self.operation_count
269
+ )
270
+ else:
271
+ self.error_count += 1
272
+ self.last_failed_operation = datetime.now()
273
+ self.last_error_time = datetime.now()
274
+
275
+ if error_message:
276
+ self.error_message = error_message
277
+ self.error_history.append({
278
+ 'timestamp': datetime.now(),
279
+ 'error': error_message,
280
+ 'operation_count': self.operation_count
281
+ })
282
+
283
+ # Limit error history size
284
+ if len(self.error_history) > 50:
285
+ self.error_history = self.error_history[-50:]
286
+
287
+ # Update success rate
288
+ successful_operations = self.operation_count - self.error_count
289
+ self.success_rate = (successful_operations / self.operation_count) * 100.0
290
+
291
+ # Update health status
292
+ self.update_health_status()
293
+
294
+ def record_dependency_status(self, dependency: str, available: bool) -> None:
295
+ """Record the status of a dependency
296
+
297
+ Args:
298
+ dependency: Name of the dependency
299
+ available: Whether the dependency is available
300
+ """
301
+ self.dependency_status[dependency] = available
302
+ self.update_health_status()
303
+
304
+ def get_dependency_health(self) -> Dict[str, Any]:
305
+ """Get detailed dependency health information
306
+
307
+ Returns:
308
+ Dict containing dependency health information
309
+ """
310
+ return {
311
+ 'required_dependencies': {
312
+ dep: self.dependency_status.get(dep, False)
313
+ for dep in self.required_dependencies
314
+ },
315
+ 'optional_dependencies': {
316
+ dep: self.dependency_status.get(dep, False)
317
+ for dep in self.optional_dependencies
318
+ },
319
+ 'all_required_available': all(
320
+ self.dependency_status.get(dep, False)
321
+ for dep in self.required_dependencies
322
+ ),
323
+ 'missing_required': [
324
+ dep for dep in self.required_dependencies
325
+ if not self.dependency_status.get(dep, False)
326
+ ]
327
+ }
328
+
329
+ def attempt_recovery(self) -> bool:
330
+ """Attempt to recover the component
331
+
332
+ Returns:
333
+ bool: True if recovery was successful
334
+ """
335
+ self.recovery_attempts += 1
336
+ self.last_recovery_attempt = datetime.now()
337
+
338
+ try:
339
+ # Basic recovery logic - reset error state
340
+ if self.error_count > 0:
341
+ self.error_count = max(0, self.error_count - 5) # Reduce error count
342
+ self.error_message = None
343
+
344
+ # Update status
345
+ if self.error_count == 0:
346
+ self.status = ComponentStatus.HEALTHY
347
+ self.recovery_successful = True
348
+ return True
349
+
350
+ return False
351
+
352
+ except Exception as e:
353
+ self.error_message = f"Recovery failed: {e}"
354
+ self.recovery_successful = False
355
+ return False
356
+
357
+ def get_performance_metrics(self) -> Dict[str, Any]:
358
+ """Get performance metrics for the component
359
+
360
+ Returns:
361
+ Dict containing performance metrics
362
+ """
363
+ return {
364
+ 'operation_count': self.operation_count,
365
+ 'error_count': self.error_count,
366
+ 'success_rate': self.success_rate,
367
+ 'average_response_time': self.average_response_time,
368
+ 'initialization_time': self.initialization_time,
369
+ 'last_successful_operation': self.last_successful_operation,
370
+ 'last_failed_operation': self.last_failed_operation,
371
+ 'recovery_attempts': self.recovery_attempts,
372
+ 'recovery_successful': self.recovery_successful
373
+ }
374
+
375
+ def to_dict(self) -> Dict[str, Any]:
376
+ """Convert to dictionary for serialization
377
+
378
+ Returns:
379
+ Dict representation of the component health
380
+ """
381
+ return {
382
+ 'component_name': self.component_name,
383
+ 'status': self.status.value,
384
+ 'is_healthy': self.is_healthy,
385
+ 'last_check': self.last_check.isoformat(),
386
+ 'initialization_time': self.initialization_time,
387
+ 'error_message': self.error_message,
388
+ 'error_count': self.error_count,
389
+ 'dependency_health': self.get_dependency_health(),
390
+ 'performance_metrics': self.get_performance_metrics(),
391
+ 'recent_errors': self.error_history[-5:] if self.error_history else []
392
+ }
393
+
394
+
395
+@dataclass
396
+class SystemHealth:
397
+ """Overall system health status combining all components"""
398
+
399
+ # Overall status
400
+ is_healthy: bool = False
401
+ health_score: float = 0.0 # 0-100 score
402
+ last_health_check: datetime = field(default_factory=datetime.now)
403
+
404
+ # Component tracking
405
+ total_components: int = 0
406
+ healthy_components: int = 0
407
+ unhealthy_components: int = 0
408
+ failed_components: int = 0
409
+
410
+ # Critical systems
411
+ file_manager_healthy: bool = False
412
+ ui_system_healthy: bool = False
413
+ core_services_healthy: bool = False
414
+
415
+ # Performance metrics
416
+ average_response_time: float = 0.0
417
+ total_operations: int = 0
418
+ total_errors: int = 0
419
+ overall_success_rate: float = 100.0
420
+
421
+ def update_from_components(self, components: Dict[str, ComponentHealth]) -> None:
422
+ """Update system health from component health data
423
+
424
+ Args:
425
+ components: Dictionary of component health objects
426
+ """
427
+ self.total_components = len(components)
428
+ self.healthy_components = sum(1 for c in components.values() if c.is_healthy)
429
+ self.unhealthy_components = self.total_components - self.healthy_components
430
+ self.failed_components = sum(
431
+ 1 for c in components.values()
432
+ if c.status == ComponentStatus.FAILED
433
+ )
434
+
435
+ # Check critical systems
436
+ self.file_manager_healthy = components.get('file_manager', ComponentHealth('file_manager')).is_healthy
437
+ self.ui_system_healthy = all(
438
+ components.get(comp, ComponentHealth(comp)).is_healthy
439
+ for comp in ['theme_manager', 'panel_manager', 'media_viewer']
440
+ )
441
+ self.core_services_healthy = all(
442
+ components.get(comp, ComponentHealth(comp)).is_healthy
443
+ for comp in ['file_manager', 'config']
444
+ )
445
+
446
+ # Calculate health score
447
+ if self.total_components > 0:
448
+ base_score = (self.healthy_components / self.total_components) * 100
449
+
450
+ # Apply penalties for critical system failures
451
+ if not self.file_manager_healthy:
452
+ base_score *= 0.5 # 50% penalty for file manager issues
453
+ if not self.core_services_healthy:
454
+ base_score *= 0.7 # 30% penalty for core service issues
455
+
456
+ self.health_score = max(0.0, min(100.0, base_score))
457
+ else:
458
+ self.health_score = 0.0
459
+
460
+ # Overall health determination
461
+ self.is_healthy = (
462
+ self.health_score >= 80.0 and
463
+ self.file_manager_healthy and
464
+ self.core_services_healthy
465
+ )
466
+
467
+ # Update performance metrics
468
+ if components:
469
+ total_ops = sum(c.operation_count for c in components.values())
470
+ total_errors = sum(c.error_count for c in components.values())
471
+
472
+ self.total_operations = total_ops
473
+ self.total_errors = total_errors
474
+
475
+ if total_ops > 0:
476
+ self.overall_success_rate = ((total_ops - total_errors) / total_ops) * 100.0
477
+ self.average_response_time = sum(
478
+ c.average_response_time * c.operation_count for c in components.values()
479
+ ) / total_ops
480
+
481
+ self.last_health_check = datetime.now()
482
+
483
+ def get_health_summary(self) -> Dict[str, Any]:
484
+ """Get a summary of system health
485
+
486
+ Returns:
487
+ Dict containing health summary
488
+ """
489
+ return {
490
+ 'is_healthy': self.is_healthy,
491
+ 'health_score': self.health_score,
492
+ 'last_check': self.last_health_check.isoformat(),
493
+ 'component_summary': {
494
+ 'total': self.total_components,
495
+ 'healthy': self.healthy_components,
496
+ 'unhealthy': self.unhealthy_components,
497
+ 'failed': self.failed_components
498
+ },
499
+ 'critical_systems': {
500
+ 'file_manager': self.file_manager_healthy,
501
+ 'ui_system': self.ui_system_healthy,
502
+ 'core_services': self.core_services_healthy
503
+ },
504
+ 'performance': {
505
+ 'total_operations': self.total_operations,
506
+ 'total_errors': self.total_errors,
507
+ 'success_rate': self.overall_success_rate,
508
+ 'average_response_time': self.average_response_time
509
+ }
510
+ }
0
511
β–Όlaxyfile/integration/component_integration.py
+8-1
169
169
async def _initialize_preview_system(self) -> bool:
170
170
"""Initialize preview system"""
171
171
try:
172
+ # Ensure file manager service is available first
173
+ from ..core.file_manager_service import FileManagerService
174
+ file_manager_service = FileManagerService.get_instance()
175
+
176
+ if not file_manager_service.ensure_initialized():
177
+ self.logger.warning("File manager service not available during preview system initialization")
178
+
172
179
# Create preview config from main config
173
180
from ..preview.preview_system import PreviewConfig
174
181
preview_config = PreviewConfig()
189
196
initialized=True,
190
197
healthy=True,
191
198
last_check=time.time(),
192
- dependencies=['config']
199
+ dependencies=['config', 'file_manager']
193
200
)
194
201
return True
195
202
except Exception as e:
196
203
β–Όlaxyfile/main.py
+186-60
34
34
35
35
from .core.file_manager import FileManager
36
36
from .core.config import Config
37
+from .core.component_initializer import ComponentInitializer
38
+from .core.file_manager_service import FileManagerService
39
+from .core.error_handling_mixin import ErrorHandlingMixin
37
40
from .core.performance_optimizer import PerformanceOptimizer, PerformanceConfig
38
41
from .core.startup_optimizer import StartupOptimizer, StartupConfig
39
42
from .ui.theme import ThemeManager
153
156
pass
154
157
return None
155
158
156
-class LaxyFileApp:
157
- """Modern file manager with Superfile-like interface"""
159
+class LaxyFileApp(ErrorHandlingMixin):
160
+ """Modern file manager with Superfile-like interface and robust error handling"""
158
161
159
162
def __init__(self):
163
+ # Initialize error handling mixin first
164
+ super().__init__()
165
+
160
166
# Initialize console with optimized settings for performance
161
167
self.console = Console(
162
168
force_terminal=True,
170
176
_environ=os.environ # Cache environment for performance
171
177
)
172
178
173
- # Core components - lazy initialization for faster startup
179
+ # Core configuration
174
180
self.config = Config()
175
- self.theme_manager = None # Lazy init
176
- self.file_manager = None # Lazy init
177
- self.panel_manager = None # Lazy init
178
- self.media_viewer = None # Lazy init
179
- self.ai_assistant = None # Lazy init
181
+
182
+ # Initialize component initializer for proper startup sequence
183
+ self.component_initializer = ComponentInitializer(self.config)
184
+
185
+ # Initialize core services first
186
+ if not self.component_initializer.initialize_core_services():
187
+ self.logger.error("Failed to initialize core services")
188
+ # Continue with degraded functionality
189
+
190
+ # Core components - will be properly initialized by ComponentInitializer
191
+ self.theme_manager = None
192
+ self.file_manager_service = FileManagerService.get_instance()
193
+ self.panel_manager = None
194
+ self.media_viewer = None
195
+ self.ai_assistant = None
180
196
181
197
# Initialize navigation performance systems
182
198
self.navigation_throttle = NavigationThrottle()
183
199
self.fast_nav_manager = FastNavigationManager()
184
200
self.keyboard_debouncer = KeyboardDebouncer()
185
201
202
+ # Display optimization
203
+ self._display_dirty = False
204
+ self._last_navigation_time = 0
205
+ self._navigation_debounce_delay = 0.05 # 50ms debounce
206
+ self._cached_file_lists = {}
207
+ self._cache_expiry = 2.0 # Cache for 2 seconds
208
+
186
209
# Initialize hotkey manager
187
210
self.hotkey_manager = HotkeyManager()
188
- self.logger = Logger()
189
211
190
212
# Performance optimizations
191
213
self._component_cache = {}
214
236
if self.config.config.remember_session:
215
237
self._restore_session()
216
238
239
+ # Initialize UI components after core services
240
+ if not self.component_initializer.initialize_ui_components():
241
+ self.logger.warning("Some UI components failed to initialize")
242
+ # Continue with available components
243
+
217
244
# Setup layout
218
245
self.layout = self.create_layout()
219
246
221
248
signal.signal(signal.SIGINT, self._signal_handler)
222
249
signal.signal(signal.SIGTERM, self._signal_handler)
223
250
251
+ # Mark startup as complete
252
+ self._startup_complete = True
253
+ self.logger.info("LaxyFile application initialized successfully")
254
+
224
255
def _get_theme_manager(self):
225
256
"""Lazy initialization of theme manager"""
226
257
if self.theme_manager is None:
229
260
return self.theme_manager
230
261
231
262
def _get_file_manager(self):
232
- """Lazy initialization of file manager"""
233
- if self.file_manager is None:
234
- self.file_manager = FileManager()
235
- self._component_cache['file_manager'] = self.file_manager
236
- return self.file_manager
263
+ """Get file manager through the service"""
264
+ # Use the file manager service instead of direct instantiation
265
+ file_manager = self.file_manager_service.get_file_manager()
266
+ if file_manager is None:
267
+ self.logger.error("File manager not available from service")
268
+ # Return a fallback or handle gracefully
269
+ return None
270
+
271
+ self._component_cache['file_manager'] = file_manager
272
+ return file_manager
273
+
274
+ @property
275
+ def file_manager(self):
276
+ """Property to access file manager through service with error handling"""
277
+ return self._get_file_manager()
237
278
238
279
def _get_panel_manager(self):
239
280
"""Lazy initialization of panel manager"""
299
340
self._component_cache['animation_optimizer'] = self.animation_optimizer
300
341
return self.animation_optimizer
301
342
343
+ def _get_cached_file_list(self, panel_name: str) -> List[Any]:
344
+ """Get cached file list to prevent excessive directory reads during navigation"""
345
+ current_path = self.current_path[panel_name]
346
+ cache_key = f"{panel_name}_{current_path}_{self._get_panel_manager().show_hidden}"
347
+ current_time = time.time()
348
+
349
+ # Check if we have a valid cached result
350
+ if cache_key in self._cached_file_lists:
351
+ cached_data = self._cached_file_lists[cache_key]
352
+ if current_time - cached_data['timestamp'] < self._cache_expiry:
353
+ return cached_data['files']
354
+
355
+ # Cache miss or expired - get fresh file list
356
+ files = self.safe_file_operation(
357
+ operation=lambda: self.file_manager_service.list_directory(
358
+ current_path,
359
+ self._get_panel_manager().show_hidden
360
+ ),
361
+ fallback_value=[],
362
+ cache_key=f"cached_list_{cache_key}",
363
+ max_retries=1
364
+ )
365
+
366
+ # Cache the result
367
+ self._cached_file_lists[cache_key] = {
368
+ 'files': files,
369
+ 'timestamp': current_time
370
+ }
371
+
372
+ # Limit cache size to prevent memory issues
373
+ if len(self._cached_file_lists) > 10:
374
+ # Remove oldest entries
375
+ oldest_key = min(self._cached_file_lists.keys(),
376
+ key=lambda k: self._cached_file_lists[k]['timestamp'])
377
+ del self._cached_file_lists[oldest_key]
378
+
379
+ return files
380
+
302
381
def _complete_startup(self):
303
382
"""Complete startup initialization after first use"""
304
383
if not self._startup_complete:
430
509
def create_file_panel(self, panel_name: str, path: Path, is_active: bool) -> Panel:
431
510
"""Create beautiful file panel with modern styling"""
432
511
try:
433
- files = self.file_manager.list_directory(path, self.panel_manager.show_hidden)
512
+ # Use safe file operation to get directory listing
513
+ files = self.safe_file_operation(
514
+ operation=lambda: self.file_manager_service.list_directory(path, self._get_panel_manager().show_hidden),
515
+ fallback_value=[],
516
+ cache_key=f"panel_{panel_name}_{path}",
517
+ max_retries=2
518
+ )
434
519
current_selection = self.panel_manager.current_selection.get(panel_name, 0)
435
520
selected_files = self.panel_manager.selected_files.get(panel_name, set())
436
521
616
701
def create_preview_panel(self) -> Panel:
617
702
"""Create preview panel for current file"""
618
703
try:
619
- files = self.file_manager.list_directory(
620
- self.current_path[self.active_panel],
621
- self.panel_manager.show_hidden
704
+ # Use safe file operation to get directory listing
705
+ files = self.safe_file_operation(
706
+ operation=lambda: self.file_manager_service.list_directory(
707
+ self.current_path[self.active_panel],
708
+ self._get_panel_manager().show_hidden
709
+ ),
710
+ fallback_value=[],
711
+ cache_key=f"preview_{self.active_panel}_{self.current_path[self.active_panel]}",
712
+ max_retries=1
622
713
)
623
714
current_file = self.panel_manager.get_current_file(self.active_panel, files)
624
715
635
726
636
727
# Use media viewer for preview with even larger dimensions for better quality
637
728
self.logger.debug(f"Creating preview for: {current_file.path}")
638
- return self.media_viewer.create_preview_panel(current_file.path, 60, 40)
729
+ media_viewer = self._get_media_viewer()
730
+ if media_viewer is None:
731
+ return Panel(
732
+ Align.center(Text("Media viewer unavailable", style="yellow")),
733
+ title="⚠️ Preview Unavailable",
734
+ border_style="yellow",
735
+ box=ROUNDED
736
+ )
737
+ return media_viewer.create_preview_panel(current_file.path, 60, 40)
639
738
640
739
except Exception as e:
641
740
self.logger.error(f"Preview panel creation error: {e}")
649
748
def create_footer(self) -> Panel:
650
749
"""Create modern footer with status and shortcuts"""
651
750
try:
652
- # Status information
653
- left_files = self.file_manager.list_directory(
654
- self.current_path["left"],
655
- self.panel_manager.show_hidden
751
+ # Status information using safe file operations
752
+ left_files = self.safe_file_operation(
753
+ operation=lambda: self.file_manager_service.list_directory(
754
+ self.current_path["left"],
755
+ self._get_panel_manager().show_hidden
756
+ ),
757
+ fallback_value=[],
758
+ cache_key=f"footer_left_{self.current_path['left']}",
759
+ max_retries=1
656
760
)
657
- right_files = self.file_manager.list_directory(
658
- self.current_path["right"],
659
- self.panel_manager.show_hidden
761
+ right_files = self.safe_file_operation(
762
+ operation=lambda: self.file_manager_service.list_directory(
763
+ self.current_path["right"],
764
+ self._get_panel_manager().show_hidden
765
+ ),
766
+ fallback_value=[],
767
+ cache_key=f"footer_right_{self.current_path['right']}",
768
+ max_retries=1
660
769
)
661
770
662
771
left_selected = len(self.panel_manager.selected_files.get("left", set()))
794
903
try:
795
904
hotkeys = self.config.get_hotkeys()
796
905
797
- # Ultra-fast navigation - process instantly without caching delays
906
+ # Optimized navigation with caching to prevent flicker
798
907
# WASD Navigation (as requested)
799
908
if key in ['s', 'S', 'j', 'J', 'DOWN']:
800
- # Down navigation - instant processing
801
- files = self.file_manager.list_directory(
802
- self.current_path[self.active_panel],
803
- self.panel_manager.show_hidden
804
- )
909
+ # Down navigation - use cached file list to prevent flicker
910
+ files = self._get_cached_file_list(self.active_panel)
805
911
self.panel_manager.navigate_down(self.active_panel, len(files))
806
- # Force immediate display update for ultra-fast response
807
-
912
+ self._display_dirty = True
808
913
809
914
elif key in ['w', 'W', 'k', 'K', 'UP']:
810
- # Up navigation - instant processing
915
+ # Up navigation - instant processing without file system calls
811
916
self.panel_manager.navigate_up(self.active_panel)
917
+ self._display_dirty = True
812
918
813
919
814
920
elif key in ['a', 'A', 'h', 'H', 'LEFT']:
833
939
834
940
835
941
elif key in ['G']:
836
- files = self.file_manager.list_directory(self.current_path[self.active_panel], self.panel_manager.show_hidden)
942
+ files = self._get_cached_file_list(self.active_panel)
837
943
self.panel_manager.navigate_to_bottom(self.active_panel, len(files))
944
+ self._display_dirty = True
838
945
839
946
840
947
elif key == 'PAGE_UP':
843
950
844
951
845
952
elif key == 'PAGE_DOWN':
846
- # Page down navigation - jump by 10 items
847
- files = self.file_manager.list_directory(self.current_path[self.active_panel], self.panel_manager.show_hidden)
953
+ # Page down navigation - jump by 10 items using cached file list
954
+ files = self._get_cached_file_list(self.active_panel)
848
955
self.panel_manager.navigate_page_down(self.active_panel, len(files), 10)
956
+ self._display_dirty = True
849
957
850
958
# File operations - reassigned since WASD are now navigation
851
959
elif key in ['c', 'C']:
863
971
elif key in ['e', 'E']:
864
972
self.handle_edit()
865
973
elif key in ['z', 'Z']: # New key for select all since 'a' is now left
866
- files = self.file_manager.list_directory(self.current_path[self.active_panel], self.panel_manager.show_hidden)
974
+ files = self._get_cached_file_list(self.active_panel)
867
975
self.panel_manager.select_all(self.active_panel, files)
976
+ self._display_dirty = True
868
977
869
978
# Quick navigation
870
979
elif key == '~':
1259
1368
1260
1369
if current_file and current_file.is_file:
1261
1370
# Use media viewer for full-screen display
1262
- panel = self.media_viewer.display_file(current_file.path, 100, 40)
1263
- self.console.print(panel)
1264
- self.console.input("[dim cyan]Press Enter to continue...[/dim cyan]")
1371
+ media_viewer = self._get_media_viewer()
1372
+ if media_viewer is not None:
1373
+ panel = media_viewer.display_file(current_file.path, 100, 40)
1374
+ self.console.print(panel)
1375
+ self.console.input("[dim cyan]Press Enter to continue...[/dim cyan]")
1376
+ else:
1377
+ self.show_message("❌ Media viewer unavailable", "red")
1265
1378
1266
1379
except Exception as e:
1267
1380
self.show_message(f"❌ View error: {e}", "red")
1388
1501
pass
1389
1502
1390
1503
async def _async_run(self):
1391
- """Ultra-fast async main application loop optimized for navigation with stable display"""
1504
+ """Optimized async main application loop with anti-flicker navigation"""
1392
1505
try:
1393
- # Initial display setup - populate panels before starting main loop
1394
- self.update_display_immediately()
1395
-
1396
-
1506
+ # Initial display setup
1507
+ self.update_display()
1397
1508
1398
1509
with Live(
1399
1510
self.layout,
1400
1511
console=self.console,
1401
1512
screen=True,
1402
- refresh_per_second=60, # Reduced from 120 to prevent window jumping
1513
+ refresh_per_second=30, # Reduced to 30 FPS to prevent flicker
1403
1514
auto_refresh=False
1404
1515
) as live:
1405
1516
1517
+ last_key_time = 0
1518
+ pending_refresh = False
1519
+
1406
1520
while self.running:
1407
- # Handle input first for maximum responsiveness
1521
+ current_time = time.time()
1408
1522
key = self.key_handler.get_key_input()
1523
+
1409
1524
if key:
1410
- # Process the key without showing it on screen
1525
+ # Process the key
1411
1526
await self.handle_key(key)
1412
-
1413
- # Update display content
1414
- self.update_display()
1415
-
1416
- # Single refresh after key processing to prevent flicker
1527
+ last_key_time = current_time
1528
+ pending_refresh = True
1529
+
1530
+ # For navigation keys, use debounced update to prevent flicker
1531
+ if key in ['UP', 'DOWN', 'w', 'W', 's', 'S', 'k', 'K', 'j', 'J']:
1532
+ # Mark display as dirty but don't refresh immediately
1533
+ self._display_dirty = True
1534
+ else:
1535
+ # For non-navigation keys, update immediately
1536
+ self.update_display()
1537
+ live.refresh()
1538
+ pending_refresh = False
1539
+
1540
+ # Debounced refresh for navigation keys to prevent flicker
1541
+ if pending_refresh and (current_time - last_key_time) > 0.05: # 50ms debounce
1542
+ if self._display_dirty:
1543
+ self.update_display()
1544
+ self._display_dirty = False
1417
1545
live.refresh()
1546
+ pending_refresh = False
1418
1547
1419
- # Slightly longer delay to prevent excessive refreshing
1420
- await asyncio.sleep(0.016) # ~60 FPS to match refresh rate
1421
- else:
1422
- # No input - minimal delay without refresh to prevent jumping
1423
- await asyncio.sleep(0.016)
1548
+ # Minimal sleep to prevent CPU spinning
1549
+ await asyncio.sleep(0.01) # 10ms sleep
1424
1550
1425
1551
except KeyboardInterrupt:
1426
1552
self.quit()
1427
1553
β–Όlaxyfile/preview/preview_system.py
+61-3
18
18
19
19
from ..core.types import OperationResult
20
20
from ..core.exceptions import PreviewError
21
+from ..core.file_manager_service import FileManagerService
22
+from ..core.error_handling_mixin import ErrorHandlingMixin
21
23
from ..utils.logger import Logger
22
24
from .terminal_media import terminal_image_renderer, terminal_video_renderer
23
25
384
386
return self._create_error_result(file_path, str(e))
385
387
386
388
387
-class AdvancedPreviewSystem:
389
+class AdvancedPreviewSystem(ErrorHandlingMixin):
388
390
"""Advanced file preview system with multiple format support"""
389
391
390
392
def __init__(self, config: Optional[PreviewConfig] = None):
393
+ super().__init__()
391
394
self.config = config or PreviewConfig()
392
395
self.logger = Logger()
393
396
self.cache = PreviewCache() if self.config.cache_enabled else None
394
397
398
+ # Get file manager service
399
+ self.file_manager_service = FileManagerService.get_instance()
400
+
395
401
# Initialize renderers
396
402
self.renderers: List[BasePreviewRenderer] = []
397
403
self._initialize_renderers()
423
429
self.logger.error(f"Failed to initialize AdvancedPreviewSystem: {e}")
424
430
raise
425
431
432
+ def _safe_file_exists(self, file_path: Path) -> bool:
433
+ """Safely check if file exists using file manager service"""
434
+ try:
435
+ # Ensure file manager service is available
436
+ if not self.file_manager_service.ensure_initialized():
437
+ # Fallback to direct file system check
438
+ return file_path.exists()
439
+
440
+ # For file existence, we can use direct path check
441
+ # but we could also use file manager service for consistency
442
+ return file_path.exists() and file_path.is_file()
443
+
444
+ except Exception as e:
445
+ self.handle_file_manager_error(e, "_safe_file_exists")
446
+ return False
447
+
448
+ def _safe_get_file_info(self, file_path: Path) -> Optional[Dict[str, Any]]:
449
+ """Safely get file information using file manager service"""
450
+ try:
451
+ # Ensure file manager service is available
452
+ if not self.file_manager_service.ensure_initialized():
453
+ # Fallback to direct file system access
454
+ try:
455
+ stat = file_path.stat()
456
+ return {
457
+ 'size': stat.st_size,
458
+ 'modified': stat.st_mtime,
459
+ 'is_file': file_path.is_file(),
460
+ 'is_dir': file_path.is_dir()
461
+ }
462
+ except Exception:
463
+ return None
464
+
465
+ # Use file manager service to get file info
466
+ # For now, fallback to direct access since file manager service
467
+ # doesn't have a specific get_file_info method
468
+ try:
469
+ stat = file_path.stat()
470
+ return {
471
+ 'size': stat.st_size,
472
+ 'modified': stat.st_mtime,
473
+ 'is_file': file_path.is_file(),
474
+ 'is_dir': file_path.is_dir()
475
+ }
476
+ except Exception:
477
+ return None
478
+
479
+ except Exception as e:
480
+ self.handle_file_manager_error(e, "_safe_get_file_info")
481
+ return None
482
+
426
483
def _initialize_renderers(self):
427
484
"""Initialize preview renderers in priority order"""
428
485
# Import here to avoid circular imports
443
500
force_refresh: bool = False) -> PreviewResult:
444
501
"""Generate preview for a file"""
445
502
try:
446
- if not file_path.exists():
503
+ # Use safe file operation to check if file exists
504
+ if not self._safe_file_exists(file_path):
447
505
return PreviewResult(
448
506
file_path=file_path,
449
507
preview_type=PreviewType.UNKNOWN,
450
508
content="",
451
509
success=False,
452
- error_message="File does not exist"
510
+ error_message="File does not exist or is not accessible"
453
511
)
454
512
455
513
# Check cache first
456
514
β–Όlaxyfile/ui/enhanced_panels.py
+17-2
20
20
21
21
from ..core.types import PanelData, EnhancedFileInfo, SortType
22
22
from ..core.interfaces import UIManagerInterface
23
+from ..core.file_manager_service import FileManagerService
24
+from ..core.error_handling_mixin import ErrorHandlingMixin
23
25
from .theme import ThemeManager
24
26
from ..utils.logger import Logger
25
27
26
28
27
-class EnhancedPanelRenderer:
29
+class EnhancedPanelRenderer(ErrorHandlingMixin):
28
30
"""Enhanced panel rendering with advanced visual features"""
29
31
30
32
def __init__(self, theme_manager: ThemeManager, console: Console):
33
+ super().__init__()
31
34
self.theme_manager = theme_manager
32
35
self.console = console
33
36
self.logger = Logger()
34
37
38
+ # Get file manager service
39
+ self.file_manager_service = FileManagerService.get_instance()
40
+
35
41
# Visual settings
36
42
self.show_file_icons = True
37
43
self.show_file_sizes = True
431
437
432
438
def render_preview_panel(self, file_path: Optional[Path], content: str = "",
433
439
preview_type: str = "none") -> Panel:
434
- """Render preview panel with file content"""
440
+ """Render preview panel with file content using safe file operations"""
435
441
try:
436
442
theme = self.theme_manager.get_current_theme()
437
443
443
449
box=ROUNDED
444
450
)
445
451
452
+ # Check if file manager service is available
453
+ if not self.file_manager_service.ensure_initialized():
454
+ return Panel(
455
+ Align.center(Text("Preview unavailable - File manager loading...", style="yellow")),
456
+ title="⚠️ Loading Preview",
457
+ border_style="yellow",
458
+ box=ROUNDED
459
+ )
460
+
446
461
# Create preview content based on type
447
462
if preview_type == "text":
448
463
preview_content = Text(content[:1000]) # Limit content
449
464
β–Όlaxyfile/ui/panels.py
+91-26
10
10
from rich.align import Align
11
11
12
12
from ..core.file_manager import FileManager, FileInfo
13
+from ..core.file_manager_service import FileManagerService
14
+from ..core.error_handling_mixin import ErrorHandlingMixin
13
15
from ..ui.theme import ThemeManager
14
16
15
-class PanelManager:
16
- """Manages file browser panels"""
17
+class PanelManager(ErrorHandlingMixin):
18
+ """Manages file browser panels with robust error handling"""
17
19
18
20
def __init__(self, theme_manager: ThemeManager):
21
+ super().__init__()
19
22
self.theme_manager = theme_manager
20
- self.file_manager = FileManager()
23
+ self.file_manager_service = FileManagerService.get_instance()
21
24
self.current_selection: Dict[str, int] = {"left": 0, "right": 0}
22
25
self.selected_files: Dict[str, Set[Path]] = {"left": set(), "right": set()}
23
26
self.show_hidden = False
24
27
self.current_paths: Dict[str, Path] = {}
25
28
29
+ # Ensure file manager is initialized
30
+ if not self.file_manager_service.ensure_initialized():
31
+ self.logger.warning("File manager service not available during PanelManager initialization")
32
+
26
33
def render_panel(self, path: Path, panel_name: str, is_active: bool = False) -> Panel:
27
- """Render a file browser panel"""
34
+ """Render a file browser panel with robust error handling"""
28
35
try:
29
- files = self.file_manager.list_directory(path, self.show_hidden)
36
+ # Use safe file operation to get directory listing
37
+ files = self.safe_file_operation(
38
+ operation=lambda: self.file_manager_service.list_directory(path, self.show_hidden),
39
+ fallback_value=[],
40
+ cache_key=f"list_dir_{path}_{self.show_hidden}",
41
+ max_retries=2
42
+ )
30
43
44
+ # Handle empty directory or error case
31
45
if not files:
32
- empty_content = Align.center(
33
- Text("Empty directory", style=self.theme_manager.get_style("info"))
34
- )
35
- border_style = "bright_blue" if is_active else self.theme_manager.get_style("border")
46
+ # Check if this is an error case or truly empty directory
47
+ if not self.file_manager_service.is_healthy():
48
+ error_content = Align.center(
49
+ Text("File manager unavailable - Loading...", style="yellow")
50
+ )
51
+ border_style = "yellow"
52
+ title = "⚠️ Loading..."
53
+ else:
54
+ error_content = Align.center(
55
+ Text("Empty directory", style="dim cyan")
56
+ )
57
+ border_style = "bright_blue" if is_active else self.theme_manager.get_style("border")
58
+ title = f"πŸ“ {path.name or str(path)}"
59
+
36
60
return Panel(
37
- empty_content,
38
- title=f"πŸ“ {path.name or str(path)}",
61
+ error_content,
62
+ title=title,
39
63
border_style=border_style
40
64
)
41
65
58
82
self.current_selection[panel_name] = current_selection
59
83
60
84
for i, file_info in enumerate(files):
85
+ # Handle different file info formats (dict vs FileInfo object)
86
+ if isinstance(file_info, dict):
87
+ # Fallback format from error handling
88
+ name = file_info.get('name', 'Unknown')
89
+ is_dir = file_info.get('is_dir', False)
90
+ size = file_info.get('size', 0)
91
+ modified = file_info.get('modified', 0)
92
+ file_path = file_info.get('path', path / name)
93
+ file_type = file_info.get('file_type', 'unknown')
94
+ else:
95
+ # Normal FileInfo object
96
+ name = file_info.name
97
+ is_dir = file_info.is_dir
98
+ size = file_info.size
99
+ modified = file_info.modified
100
+ file_path = file_info.path
101
+ file_type = getattr(file_info, 'file_type', 'unknown')
102
+
61
103
# Selection indicator
62
104
if i == current_selection and is_active:
63
105
selector = "β–Ά"
65
107
elif i == current_selection:
66
108
selector = "β–Ί"
67
109
name_style = "bold white"
68
- elif file_info.path in self.selected_files.get(panel_name, set()):
110
+ elif file_path in self.selected_files.get(panel_name, set()):
69
111
selector = "●"
70
112
name_style = "bold yellow"
71
113
else:
72
114
selector = " "
73
- name_style = self.theme_manager.get_file_style(file_info.file_type)
115
+ name_style = self.theme_manager.get_file_style(file_type, False, False) if hasattr(self.theme_manager, 'get_file_style') else "white"
74
116
75
117
# Icon
76
- icon = self.theme_manager.get_file_icon(
77
- file_info.file_type,
78
- file_info.is_dir
79
- )
118
+ if hasattr(self.theme_manager, 'get_file_icon'):
119
+ icon = self.theme_manager.get_file_icon(file_type, name, is_dir, False)
120
+ else:
121
+ icon = "πŸ“" if is_dir else "πŸ“„"
80
122
81
123
# Name with style
82
- name_text = Text(file_info.name, style=name_style)
124
+ name_text = Text(name, style=name_style)
83
125
84
126
# Size
85
- if file_info.is_dir:
127
+ if is_dir:
86
128
size_text = Text("<DIR>", style="dim cyan")
87
129
else:
88
- size_text = Text(file_info.get_size_formatted(), style="dim")
130
+ # Format size
131
+ if hasattr(file_info, 'get_size_formatted') and not isinstance(file_info, dict):
132
+ size_text = Text(file_info.get_size_formatted(), style="dim")
133
+ else:
134
+ # Manual size formatting
135
+ if size < 1024:
136
+ size_str = f"{size}B"
137
+ elif size < 1024 * 1024:
138
+ size_str = f"{size/1024:.1f}K"
139
+ elif size < 1024 * 1024 * 1024:
140
+ size_str = f"{size/(1024*1024):.1f}M"
141
+ else:
142
+ size_str = f"{size/(1024*1024*1024):.1f}G"
143
+ size_text = Text(size_str, style="dim")
89
144
90
145
# Modified time
91
- modified_text = Text(
92
- file_info.get_modified_formatted(),
93
- style="dim"
94
- )
146
+ if hasattr(file_info, 'get_modified_formatted') and not isinstance(file_info, dict):
147
+ modified_text = Text(file_info.get_modified_formatted(), style="dim")
148
+ else:
149
+ # Manual time formatting
150
+ try:
151
+ import time as time_module
152
+ if isinstance(modified, (int, float)):
153
+ modified_str = time_module.strftime("%m/%d %H:%M", time_module.localtime(modified))
154
+ else:
155
+ modified_str = str(modified)[:16] if modified else "??/??"
156
+ except:
157
+ modified_str = "??/??"
158
+ modified_text = Text(modified_str, style="dim")
95
159
96
160
table.add_row(
97
161
selector,
119
183
)
120
184
121
185
except Exception as e:
186
+ self.handle_file_manager_error(e, "render_panel")
122
187
error_content = Align.center(
123
- Text(f"Error: {str(e)}", style=self.theme_manager.get_style("error"))
188
+ Text(f"Panel Error: {str(e)}", style="red")
124
189
)
125
190
return Panel(
126
191
error_content,
127
192
title=f"❌ Error",
128
- border_style=self.theme_manager.get_style("error")
193
+ border_style="red"
129
194
)
130
195
131
196
def navigate_up(self, panel_name: str):
132
197
β–Όpytest.ini
+15-1
36
36
*/__pycache__/*
37
37
*/venv/*
38
38
*/env/*
39
+ */build/*
40
+ */dist/*
39
41
setup.py
42
+ laxyfile/__main__.py
43
+
44
+branch = True
45
+parallel = True
40
46
41
47
[coverage:report]
42
48
exclude_lines =
50
56
if __name__ == .__main__.:
51
57
class .*\bProtocol\):
52
58
@(abc\.)?abstractmethod
59
+ # Type checking imports
60
+ if TYPE_CHECKING:
53
61
54
62
show_missing = True
55
63
precision = 2
64
+skip_covered = False
65
+skip_empty = True
56
66
57
67
[coverage:html]
58
-directory = htmlcov
68
+directory = htmlcov
69
+title = LaxyFile Coverage Report
70
+
71
+[coverage:xml]
72
+output = coverage.xml
59
73
β–Όrequirements-dev.txt
+1-1
64
64
line-profiler>=4.1.0
65
65
66
66
# Debugging
67
-pdb++>=0.10.3
67
+# pdbpp>=0.10.3 # Optional: enhanced Python debugger (pip install pdbpp)
68
68
ipdb>=0.13.13
69
69
pudb>=2022.1.3
70
70
71
71
β–Όscripts/run_coverage.py
+105-0
1
+#!/usr/bin/env python3
2
+"""
3
+Coverage Testing Script
4
+
5
+Run comprehensive coverage tests locally and generate reports.
6
+"""
7
+
8
+import os
9
+import sys
10
+import subprocess
11
+from pathlib import Path
12
+
13
+
14
+def run_command(cmd, description):
15
+ """Run a command and handle errors"""
16
+ print(f"πŸ”„ {description}...")
17
+ try:
18
+ result = subprocess.run(cmd, shell=True, check=True, capture_output=True, text=True)
19
+ print(f"βœ… {description} completed")
20
+ return result
21
+ except subprocess.CalledProcessError as e:
22
+ print(f"❌ {description} failed:")
23
+ print(f" Command: {cmd}")
24
+ print(f" Error: {e.stderr}")
25
+ return None
26
+
27
+
28
+def main():
29
+ """Main coverage testing function"""
30
+ project_root = Path(__file__).parent.parent
31
+ os.chdir(project_root)
32
+
33
+ print("πŸ§ͺ LaxyFile Coverage Testing")
34
+ print("=" * 50)
35
+
36
+ # Clean previous coverage data
37
+ run_command("coverage erase", "Cleaning previous coverage data")
38
+
39
+ # Run unit tests with coverage
40
+ result = run_command(
41
+ "python -m pytest tests/unit/ -v --cov=laxyfile --cov-append --cov-report=term-missing",
42
+ "Running unit tests with coverage"
43
+ )
44
+ if not result:
45
+ return 1
46
+
47
+ # Run integration tests with coverage
48
+ result = run_command(
49
+ "python -m pytest tests/integration/ -v --cov=laxyfile --cov-append --cov-report=term-missing",
50
+ "Running integration tests with coverage"
51
+ )
52
+ if not result:
53
+ return 1
54
+
55
+ # Run e2e tests with coverage
56
+ result = run_command(
57
+ "python -m pytest tests/e2e/ -v --cov=laxyfile --cov-append --cov-report=term-missing",
58
+ "Running e2e tests with coverage"
59
+ )
60
+ if not result:
61
+ return 1
62
+
63
+ # Generate HTML report
64
+ run_command("coverage html", "Generating HTML coverage report")
65
+
66
+ # Generate XML report for Codecov
67
+ run_command("coverage xml", "Generating XML coverage report")
68
+
69
+ # Show coverage summary
70
+ result = run_command("coverage report", "Generating coverage summary")
71
+
72
+ print("\n" + "=" * 50)
73
+ print("πŸ“Š Coverage Report Generated!")
74
+ print("=" * 50)
75
+ print("πŸ“ HTML Report: htmlcov/index.html")
76
+ print("πŸ“„ XML Report: coverage.xml")
77
+ print("🌐 Open HTML report: file://" + str(project_root / "htmlcov" / "index.html"))
78
+
79
+ # Check if coverage meets minimum threshold
80
+ if result and result.stdout:
81
+ lines = result.stdout.split('\n')
82
+ for line in lines:
83
+ if 'TOTAL' in line and '%' in line:
84
+ # Extract percentage
85
+ parts = line.split()
86
+ for part in parts:
87
+ if part.endswith('%'):
88
+ coverage_pct = int(part[:-1])
89
+ print(f"\nπŸ“ˆ Total Coverage: {coverage_pct}%")
90
+
91
+ if coverage_pct >= 80:
92
+ print("βœ… Coverage meets target (80%+)")
93
+ return 0
94
+ elif coverage_pct >= 70:
95
+ print("⚠️ Coverage below target but acceptable (70-79%)")
96
+ return 0
97
+ else:
98
+ print("❌ Coverage below minimum threshold (70%)")
99
+ return 1
100
+
101
+ return 0
102
+
103
+
104
+if __name__ == "__main__":
105
+ sys.exit(main())
0
106
β–Όtest_error_fixes.py
+290-0
1
+#!/usr/bin/env python3
2
+"""
3
+Test script to verify the LaxyFile error fixes are working correctly.
4
+
5
+This script tests the FileManagerService, ComponentInitializer, and ErrorHandlingMixin
6
+to ensure they properly handle the NoneType errors that were occurring.
7
+"""
8
+
9
+import sys
10
+import os
11
+from pathlib import Path
12
+import time
13
+import threading
14
+
15
+# Add the project root to Python path
16
+project_root = Path(__file__).parent
17
+sys.path.insert(0, str(project_root))
18
+
19
+def test_file_manager_service():
20
+ """Test FileManagerService singleton and initialization"""
21
+ print("πŸ§ͺ Testing FileManagerService...")
22
+
23
+ try:
24
+ from laxyfile.core.file_manager_service import FileManagerService
25
+
26
+ # Test singleton behavior
27
+ service1 = FileManagerService.get_instance()
28
+ service2 = FileManagerService.get_instance()
29
+
30
+ assert service1 is service2, "FileManagerService should be a singleton"
31
+ print("βœ… Singleton behavior working correctly")
32
+
33
+ # Test initialization
34
+ success = service1.ensure_initialized()
35
+ print(f"βœ… Initialization: {'Success' if success else 'Failed'}")
36
+
37
+ # Test health check
38
+ is_healthy = service1.is_healthy()
39
+ print(f"βœ… Health check: {'Healthy' if is_healthy else 'Unhealthy'}")
40
+
41
+ # Test directory listing
42
+ current_dir = Path.cwd()
43
+ files = service1.list_directory(current_dir)
44
+ print(f"βœ… Directory listing: Found {len(files)} items in {current_dir}")
45
+
46
+ # Test status
47
+ status = service1.get_status()
48
+ print(f"βœ… Service status: {status}")
49
+
50
+ return True
51
+
52
+ except Exception as e:
53
+ print(f"❌ FileManagerService test failed: {e}")
54
+ return False
55
+
56
+def test_component_initializer():
57
+ """Test ComponentInitializer proper initialization order"""
58
+ print("\nπŸ§ͺ Testing ComponentInitializer...")
59
+
60
+ try:
61
+ from laxyfile.core.component_initializer import ComponentInitializer
62
+ from laxyfile.core.config import Config
63
+
64
+ config = Config()
65
+ initializer = ComponentInitializer(config)
66
+
67
+ # Test core services initialization
68
+ core_success = initializer.initialize_core_services()
69
+ print(f"βœ… Core services initization: {'Success' if core_success else 'Failed'}")
70
+
71
+ # Test UI components initialization
72
+ ui_success = initializer.initialize_ui_components()
73
+ print(f"βœ… UI components initialization: {'Success' if ui_success else 'Failed'}")
74
+
75
+ # Test dependency verification
76
+ missing_deps = initializer.verify_dependencies()
77
+ print(f"βœ… Dependency verification: {len(missing_deps)} missing dependencies")
78
+ if missing_deps:
79
+ print(f" Missing: {missing_deps}")
80
+
81
+ # Test initialization status
82
+ status = initializer.get_initialization_status()
83
+ print(f"βœ… Initialization status: {status}")
84
+
85
+ # Test health check
86
+ health = initializer.health_check()
87
+ print(f"βœ… Health check: {'Healthy' if health else 'Unhealthy'}")
88
+
89
+ return True
90
+
91
+ except Exception as e:
92
+ print(f"❌ ComponentInitializer test failed: {e}")
93
+ return False
94
+
95
+def test_error_handling_mixin():
96
+ """Test ErrorHandlingMixin error handling and recovery"""
97
+ print("\nπŸ§ͺ Testing ErrorHandlingMixin...")
98
+
99
+ try:
100
+ from laxyfile.core.error_handling_mixin import ErrorHandlingMixin
101
+
102
+ class TestComponent(ErrorHandlingMixin):
103
+ def __init__(self):
104
+ super().__init__()
105
+ self.call_count = 0
106
+
107
+ def test_operation(self):
108
+ self.call_count += 1
109
+ if self.call_count < 3:
110
+ raise Exception("Simulated error")
111
+ return "Success"
112
+
113
+ def failing_operation(self):
114
+ raise Exception("Always fails")
115
+
116
+ component = TestComponent()
117
+
118
+ # Test safe operation with retry
119
+ result = component.safe_file_operation(
120
+ operation=component.test_operation,
121
+ fallback_value="Fallback",
122
+ max_retries=3
123
+ )
124
+ print(f"βœ… Safe operation with retry: {result}")
125
+
126
+ # Test safe operation with fallback
127
+ result = component.safe_file_operation(
128
+ operation=component.failing_operation,
129
+ fallback_value="Fallback used",
130
+ max_retries=1
131
+ )
132
+ print(f"βœ… Safe operation with fallback: {result}")
133
+
134
+ # Test error statistics
135
+ stats = component.get_error_statistics()
136
+ print(f"βœ… Error statistics: {stats}")
137
+
138
+ # Test file manager availability check
139
+ available = component.is_file_manager_available()
140
+ print(f"βœ… File manager availability: {'Available' if available else 'Unavailable'}")
141
+
142
+ return True
143
+
144
+ except Exception as e:
145
+ print(f"❌ ErrorHandlingMixin test failed: {e}")
146
+ return False
147
+
148
+def test_panel_manager_integration():
149
+ """Test PanelManager integration with FileManagerService"""
150
+ print("\nπŸ§ͺ Testing PanelManager integration...")
151
+
152
+ try:
153
+ from laxyfile.ui.panels import PanelManager
154
+ from laxyfile.ui.theme import ThemeManager
155
+ from laxyfile.core.config import Config
156
+
157
+ config = Config()
158
+ theme_manager = ThemeManager(config)
159
+ panel_manager = PanelManager(theme_manager)
160
+
161
+ # Test panel rendering
162
+ current_dir = Path.cwd()
163
+ panel = panel_manager.render_panel(current_dir, "test", is_active=True)
164
+
165
+ print(f"βœ… Panel rendering: Successfully created panel for {current_dir}")
166
+ print(f" Panel type: {type(panel)}")
167
+
168
+ # Test error handling in panel manager
169
+ non_existent_path = Path("/non/existent/path")
170
+ error_panel = panel_manager.render_panel(non_existent_path, "error_test", is_active=False)
171
+
172
+ print(f"βœ… Error handling: Successfully handled non-existent path")
173
+ print(f" Error panel type: {type(error_panel)}")
174
+
175
+ return True
176
+
177
+ except Exception as e:
178
+ print(f"❌ PanelManager integration test failed: {e}")
179
+ return False
180
+
181
+def test_thread_safety():
182
+ """Test thread safety of FileManagerService"""
183
+ print("\nπŸ§ͺ Testing thread safety...")
184
+
185
+ try:
186
+ from laxyfile.core.file_manager_service import FileManagerService
187
+
188
+ results = []
189
+ errors = []
190
+
191
+ def worker_thread(thread_id):
192
+ try:
193
+ service = FileManagerService.get_instance()
194
+ current_dir = Path.cwd()
195
+ files = service.list_directory(current_dir)
196
+ results.append((thread_id, len(files)))
197
+ except Exception as e:
198
+ errors.append((thread_id, str(e)))
199
+
200
+ # Create multiple threads
201
+ threads = []
202
+ for i in range(5):
203
+ thread = threading.Thread(target=worker_thread, args=(i,))
204
+ threads.append(thread)
205
+ thread.start()
206
+
207
+ # Wait for all threads to complete
208
+ for thread in threads:
209
+ thread.join()
210
+
211
+ print(f"βœ… Thread safety: {len(results)} successful operations, {len(errors)} errors")
212
+ if errors:
213
+ print(f" Errors: {errors}")
214
+
215
+ return len(errors) == 0
216
+
217
+ except Exception as e:
218
+ print(f"❌ Thread safety test failed: {e}")
219
+ return False
220
+
221
+def test_recovery_mechanisms():
222
+ """Test automatic recovery mechanisms"""
223
+ print("\nπŸ§ͺ Testing recovery mechanisms...")
224
+
225
+ try:
226
+ from laxyfile.core.file_manager_service import FileManagerService
227
+
228
+ service = FileManagerService.get_instance()
229
+
230
+ # Test reinitialization
231
+ reinit_success = service.reinitialize(force=True)
232
+ print(f"βœ… Reinitialization: {'Success' if reinit_success else 'Failed'}")
233
+
234
+ # Test health after reinitialization
235
+ is_healthy = service.is_healthy()
236
+ print(f"βœ… Health after reinit: {'Healthy' if is_healthy else 'Unhealthy'}")
237
+
238
+ # Test service reset and recovery
239
+ FileManagerService.reset()
240
+ new_service = FileManagerService.get_instance()
241
+ recovery_success = new_service.ensure_initialized()
242
+ print(f"βœ… Service recovery: {'Success' if recovery_success else 'Failed'}")
243
+
244
+ return True
245
+
246
+ except Exception as e:
247
+ print(f"❌ Recovery mechanisms test failed: {e}")
248
+ return False
249
+
250
+def main():
251
+ """Run all tests"""
252
+ print("πŸš€ Starting LaxyFile Error Fixes Test Suite")
253
+ print("=" * 50)
254
+
255
+ tests = [
256
+ test_file_manager_service,
257
+ test_component_initializer,
258
+ test_error_handling_mixin,
259
+ test_panel_manager_integration,
260
+ test_thread_safety,
261
+ test_recovery_mechanisms
262
+ ]
263
+
264
+ passed = 0
265
+ failed = 0
266
+
267
+ for test in tests:
268
+ try:
269
+ if test():
270
+ passed += 1
271
+ else:
272
+ failed += 1
273
+ except Exception as e:
274
+ print(f"❌ Test {test.__name__} crashed: {e}")
275
+ failed += 1
276
+
277
+ time.sleep(0.1) # Brief pause between tests
278
+
279
+ print("\n" + "=" * 50)
280
+ print(f"πŸ“Š Test Results: {passed} passed, {failed} failed")
281
+
282
+ if failed == 0:
283
+ print("πŸŽ‰ All tests passed! Error fixes are working correctly.")
284
+ return 0
285
+ else:
286
+ print("⚠️ Some tests failed. Please check the error messages above.")
287
+ return 1
288
+
289
+if __name__ == "__main__":
290
+ sys.exit(main())
0
291
β–Όtest_flicker_fix.py
+178-0
1
+#!/usr/bin/env python3
2
+"""
3
+Test script to verify the flicker fix for LaxyFile navigation
4
+"""
5
+
6
+import sys
7
+import time
8
+from pathlib import Path
9
+
10
+# Add the laxyfile package to the path
11
+sys.path.insert(0, str(Path(__file__).parent))
12
+
13
+def test_navigation_performance():
14
+ """Test that navigation doesn't cause excessive file system calls"""
15
+ print("πŸ§ͺ Testing Navigation Performance and Anti-Flicker...")
16
+
17
+ try:
18
+ from laxyfile.main import LaxyFileApp
19
+
20
+ # Create LaxyFile app
21
+ app = LaxyFileApp()
22
+ print(" βœ… LaxyFile app created successfully")
23
+
24
+ # Test cached file list functionality
25
+ print(" πŸ” Testing cached file list functionality...")
26
+
27
+ # First call should populate cache
28
+ start_time = time.time()
29
+ files1 = app._get_cached_file_list("left")
30
+ first_call_time = time.time() - start_time
31
+
32
+ # Second call should use cache (much faster)
33
+ start_time = time.time()
34
+ files2 = app._get_cached_file_list("left")
35
+ second_call_time = time.time() - start_time
36
+
37
+ print(f" πŸ“Š First call: {first_call_time:.4f}s, Second call: {second_call_time:.4f}s")
38
+
39
+ if second_call_time < first_call_time:
40
+ print(" βœ… Caching is working - second call is faster")
41
+ else:
42
+ print(" ⚠️ Caching may not be working optimally")
43
+
44
+ # Test that both calls return the same data
45
+ if len(files1) == len(files2):
46
+ print(f" βœ… Cache consistency verified - both calls returned {len(files1)} files")
47
+ else:
48
+ print(f" ❌ Cache inconsistency - first: {len(files1)}, second: {len(files2)}")
49
+ return False
50
+
51
+ # Test display dirty flag functionality
52
+ print(" πŸ” Testing display dirty flag...")
53
+
54
+ app._display_dirty = False
55
+ print(f" πŸ“Š Initial display dirty state: {app._display_dirty}")
56
+
57
+ # Simulate navigation that should set dirty flag
58
+ app._display_dirty = True
59
+ print(f" πŸ“Š After navigation simulation: {app._display_dirty}")
60
+
61
+ if app._display_dirty:
62
+ print(" βœ… Display dirty flag working correctly")
63
+ else:
64
+ print(" ❌ Display dirty flag not working")
65
+ return False
66
+
67
+ # Test debounce settings
68
+ print(" πŸ” Testing debounce configuration...")
69
+
70
+ debounce_delay = app._navigation_debounce_delay
71
+ cache_expiry = app._cache_expiry
72
+
73
+ print(f" πŸ“Š Navigation debounce delay: {debounce_delay}s")
74
+ print(f" πŸ“Š Cache expiry time: {cache_expiry}s")
75
+
76
+ if debounce_delay > 0 and cache_expiry > 0:
77
+ print(" βœ… Anti-flicker settings configured correctly")
78
+ else:
79
+ print(" ❌ Anti-flicker settings not configured properly")
80
+ return False
81
+
82
+ print(" βœ… Navigation performance test completed successfully")
83
+ return True
84
+
85
+ except Exception as e:
86
+ print(f" ❌ Navigation performance test failed: {e}")
87
+ import traceback
88
+ traceback.print_exc()
89
+ return False
90
+
91
+def test_display_optimization():
92
+ """Test display optimization features"""
93
+ print("\nπŸ§ͺ Testing Display Optimization...")
94
+
95
+ try:
96
+ from laxyfile.main import LaxyFileApp
97
+
98
+ # Create LaxyFile app
99
+ app = LaxyFileApp()
100
+ print(" βœ… LaxyFile app created successfully")
101
+
102
+ # Test that cached file lists are properly managed
103
+ print(" πŸ” Testing cache management...")
104
+
105
+ # Fill cache with multiple entries
106
+ for i in range(5):
107
+ cache_key = f"test_panel_{i}"
108
+ app.current_path["left"] = Path.cwd() # Use current directory
109
+ files = app._get_cached_file_list("left")
110
+ print(f" πŸ“Š Cache entry {i}: {len(files)} files")
111
+
112
+ cache_size = len(app._cached_file_lists)
113
+ print(f" πŸ“Š Total cache entries: {cache_size}")
114
+
115
+ if cache_size > 0:
116
+ print(" βœ… File list caching is working")
117
+ else:
118
+ print(" ❌ File list caching not working")
119
+ return False
120
+
121
+ # Test cache expiry mechanism
122
+ print(" πŸ” Testing cache expiry...")
123
+
124
+ # Manually expire a cache entry
125
+ if app._cached_file_lists:
126
+ first_key = list(app._cached_file_lists.keys())[0]
127
+ app._cached_file_lists[first_key]['timestamp'] = time.time() - app._cache_expiry - 1
128
+ print(" πŸ“Š Manually expired one cache entry")
129
+
130
+ # Request files again - should refresh expired entry
131
+ files = app._get_cached_file_list("left")
132
+ print(f" πŸ“Š Files after cache expiry test: {len(files)}")
133
+
134
+ print(" βœ… Display optimization test completed successfully")
135
+ return True
136
+
137
+ except Exception as e:
138
+ print(f" ❌ Display optimization test failed: {e}")
139
+ import traceback
140
+ traceback.print_exc()
141
+ return False
142
+
143
+def main():
144
+ """Run all anti-flicker tests"""
145
+ print("πŸš€ Starting LaxyFile Anti-Flicker Tests\n")
146
+
147
+ test_results = []
148
+
149
+ # Test navigation performance
150
+ result1 = test_navigation_performance()
151
+ test_results.append(result1)
152
+
153
+ # Test display optimization
154
+ result2 = test_display_optimization()
155
+ test_results.append(result2)
156
+
157
+ # Summary
158
+ print(f"\nπŸ“Š Test Results:")
159
+ print(f" βœ… Passed: {sum(test_results)}")
160
+ print(f" ❌ Failed: {len(test_results) - sum(test_results)}")
161
+
162
+ if all(test_results):
163
+ print("\nπŸŽ‰ All tests passed! Anti-flicker optimizations are working correctly.")
164
+ print("\nπŸ“‹ Flicker Fixes Applied:")
165
+ print(" β€’ Implemented navigation debouncing (50ms delay)")
166
+ print(" β€’ Added file list caching to prevent excessive directory reads")
167
+ print(" β€’ Reduced refresh rate from 60 FPS to 30 FPS")
168
+ print(" β€’ Added display dirty flag for optimized updates")
169
+ print(" β€’ Cached file operations for navigation keys")
170
+ print(" β€’ Optimized async main loop with proper timing")
171
+ return 0
172
+ else:
173
+ print("\nπŸ’₯ Some tests failed. Please check the error messages above.")
174
+ return 1
175
+
176
+if __name__ == "__main__":
177
+ exit_code = main()
178
+ sys.exit(exit_code)
0
179
β–Όtest_media_viewer_fix.py
+145-0
1
+#!/usr/bin/env python3
2
+"""
3
+Test script to verify the media viewer NoneType error fix
4
+"""
5
+
6
+import sys
7
+from pathlib import Path
8
+
9
+# Add the laxyfile package to the path
10
+sys.path.insert(0, str(Path(__file__).parent))
11
+
12
+def test_media_viewer_error_handling():
13
+ """Test that media viewer handles initialization errors gracefully"""
14
+ print("πŸ§ͺ Testing Media Viewer Error Handling...")
15
+
16
+ try:
17
+ from laxyfile.main import LaxyFileApp
18
+
19
+ # Create LaxyFile app
20
+ app = LaxyFileApp()
21
+ print(" βœ… LaxyFile app created successfully")
22
+
23
+ # Test create_preview_panel method - this should not crash even if media viewer is None
24
+ print(" πŸ” Testing create_preview_panel method...")
25
+
26
+ try:
27
+ preview_panel = app.create_preview_panel()
28
+ print(" βœ… Preview panel created successfully without crashing")
29
+
30
+ # Check if the panel has content (even if it's an error message)
31
+ if hasattr(preview_panel, 'renderable'):
32
+ print(" βœ… Preview panel has renderable content")
33
+ else:
34
+ print(" βœ… Preview panel created (may be fallback content)")
35
+
36
+ except Exception as e:
37
+ print(f" ❌ Preview panel creation failed: {e}")
38
+ return False
39
+
40
+ # Test that media viewer lazy initialization works
41
+ print(" πŸ” Testing media viewer lazy initialization...")
42
+
43
+ try:
44
+ media_viewer = app._get_media_viewer()
45
+ if media_viewer is not None:
46
+ print(" βœ… Media viewer initialized successfully")
47
+ else:
48
+ print(" ⚠️ Media viewer is None (expected if dependencies missing)")
49
+
50
+ except Exception as e:
51
+ print(f" ❌ Media viewer initialization failed: {e}")
52
+ return False
53
+
54
+ print(" βœ… Media viewer error handling test completed successfully")
55
+ return True
56
+
57
+ except Exception as e:
58
+ print(f" ❌ Media viewer test failed: {e}")
59
+ import traceback
60
+ traceback.print_exc()
61
+ return False
62
+
63
+def test_file_manager_service_integration():
64
+ """Test that file manager service integration works correctly"""
65
+ print("\nπŸ§ͺ Testing File Manager Service Integration...")
66
+
67
+ try:
68
+ from laxyfile.main import LaxyFileApp
69
+ from laxyfile.core.file_manager_service import FileManagerService
70
+
71
+ # Reset file manager service to test initialization
72
+ FileManagerService.reset()
73
+
74
+ # Create LaxyFile app
75
+ app = LaxyFileApp()
76
+ print(" βœ… LaxyFile app created successfully")
77
+
78
+ # Test file manager property access
79
+ print(" πŸ” Testing file manager property access...")
80
+
81
+ try:
82
+ file_manager = app.file_manager
83
+ if file_manager is not None:
84
+ print(" βœ… File manager accessed successfully through property")
85
+ else:
86
+ print(" ⚠️ File manager is None (may need initialization)")
87
+
88
+ except Exception as e:
89
+ print(f" ❌ File manager property access failed: {e}")
90
+ return False
91
+
92
+ # Test safe file operations
93
+ print(" πŸ” Testing safe file operations...")
94
+
95
+ try:
96
+ # This should not crash even if file manager is not available
97
+ test_result = app.safe_file_operation(
98
+ operation=lambda: [],
99
+ fallback_value=[],
100
+ cache_key="test_operation"
101
+ )
102
+ print(" βœ… Safe file operation completed successfully")
103
+
104
+ except Exception as e:
105
+ print(f" ❌ Safe file operation failed: {e}")
106
+ return False
107
+
108
+ print(" βœ… File manager service integration test completed successfully")
109
+ return True
110
+
111
+ except Exception as e:
112
+ print(f" ❌ File manager service integration test failed: {e}")
113
+ import traceback
114
+ traceback.print_exc()
115
+ return False
116
+
117
+def main():
118
+ """Run all tests"""
119
+ print("πŸš€ Starting LaxyFile Media Viewer Error Fix Tests\n")
120
+
121
+ test_results = []
122
+
123
+ # Test media viewer error handling
124
+ result1 = test_media_viewer_error_handling()
125
+ test_results.append(result1)
126
+
127
+ # Test file manager service integration
128
+ result2 = test_file_manager_service_integration()
129
+ test_results.append(result2)
130
+
131
+ # Summary
132
+ print(f"\nπŸ“Š Test Results:")
133
+ print(f" βœ… Passed: {sum(test_results)}")
134
+ print(f" ❌ Failed: {len(test_results) - sum(test_results)}")
135
+
136
+ if all(test_results):
137
+ print("\nπŸŽ‰ All tests passed! Media viewer and file manager error handling is working correctly.")
138
+ return 0
139
+ else:
140
+ print("\nπŸ’₯ Some tests failed. Please check the error messages above.")
141
+ return 1
142
+
143
+if __name__ == "__main__":
144
+ exit_code = main()
145
+ sys.exit(exit_code)
0
146
β–Όtest_nonetype_fix.py
+176-0
1
+#!/usr/bin/env python3
2
+"""
3
+Specific test to verify the NoneType error fix.
4
+
5
+This test simulates the original error condition and verifies it's been resolved.
6
+"""
7
+
8
+import sys
9
+from pathlib import Path
10
+
11
+# Add the project root to Python path
12
+project_root = Path(__file__).parent
13
+sys.path.insert(0, str(project_root))
14
+
15
+def test_original_nonetype_error():
16
+ """Test that the original 'NoneType' object has no attribute 'list_directory' error is fixed"""
17
+ print("πŸ§ͺ Testing original NoneType error fix...")
18
+
19
+ try:
20
+ # This simulates the original error scenario where file_manager was None
21
+ from laxyfile.ui.panels import PanelManager
22
+ from laxyfile.ui.theme import ThemeManager
23
+ from laxyfile.core.config import Config
24
+
25
+ # Initialize components
26
+ config = Config()
27
+ theme_manager = ThemeManager(config)
28
+ panel_manager = PanelManager(theme_manager)
29
+
30
+ # This would previously cause: 'NoneType' object has no attribute 'list_directory'
31
+ current_dir = Path.cwd()
32
+ panel = panel_manager.render_panel(current_dir, "test", is_active=True)
33
+
34
+ print("βœ… Successfully created panel without NoneType error")
35
+ print(f" Panel created for: {current_dir}")
36
+ print(f" Panel type: {type(panel)}")
37
+
38
+ # Test with multiple rapid calls (stress test)
39
+ for i in range(5):
40
+ panel = panel_manager.render_panel(current_dir, f"test_{i}", is_active=(i % 2 == 0))
41
+
42
+ print("βœ… Multiple rapid panel creations successful")
43
+
44
+ return True
45
+
46
+ except AttributeError as e:
47
+ if "'NoneType' object has no attribute" in str(e):
48
+ print(f"❌ Original NoneType error still occurs: {e}")
49
+ return False
50
+ else:
51
+ print(f"❌ Different AttributeError: {e}")
52
+ return False
53
+ except Exception as e:
54
+ print(f"❌ Unexpected error: {e}")
55
+ return False
56
+
57
+def test_file_manager_service_robustness():
58
+ """Test FileManagerService robustness under various conditions"""
59
+ print("\nπŸ§ͺ Testing FileManagerService robustness...")
60
+
61
+ try:
62
+ from laxyfile.core.file_manager_service import FileManagerService
63
+
64
+ # Test getting service multiple times
65
+ services = [FileManagerService.get_instance() for _ in range(10)]
66
+
67
+ # Verify all are the same instance
68
+ assert all(service is services[0] for service in services), "All instances should be the same"
69
+ print("βœ… Singleton consistency verified")
70
+
71
+ # Test directory listing with various paths
72
+ service = services[0]
73
+
74
+ # Test current directory
75
+ current_files = service.list_directory(Path.cwd())
76
+ print(f"βœ… Current directory: {len(current_files)} files")
77
+
78
+ # Test parent directory
79
+ parent_files = service.list_directory(Path.cwd().parent)
80
+ print(f"βœ… Parent directory: {len(parent_files)} files")
81
+
82
+ # Test non-existent directory (should return empty list, not crash)
83
+ non_existent_files = service.list_directory(Path("/non/existent/path"))
84
+ print(f"βœ… Non-existent directory handled: {len(non_existent_files)} files")
85
+
86
+ # Test health status
87
+ is_healthy = service.is_healthy()
88
+ print(f"βœ… Service health: {'Healthy' if is_healthy else 'Unhealthy'}")
89
+
90
+ return True
91
+
92
+ except Exception as e:
93
+ print(f"❌ FileManagerService robustness test failed: {e}")
94
+ return False
95
+
96
+def test_error_recovery():
97
+ """Test error recovery mechanisms"""
98
+ print("\nπŸ§ͺ Testing error recovery...")
99
+
100
+ try:
101
+ from laxyfile.core.error_handling_mixin import ErrorHandlingMixin
102
+ from laxyfile.core.file_manager_service import FileManagerService
103
+
104
+ class TestComponent(ErrorHandlingMixin):
105
+ def __init__(self):
106
+ super().__init__()
107
+
108
+ def test_file_operation(self):
109
+ # This should use the FileManagerService safely
110
+ service = FileManagerService.get_instance()
111
+ return service.list_directory(Path.cwd())
112
+
113
+ component = TestComponent()
114
+
115
+ # Test safe file operation
116
+ result = component.safe_file_operation(
117
+ operation=component.test_file_operation,
118
+ fallback_value=[],
119
+ max_retries=2
120
+ )
121
+
122
+ print(f"βœ… Safe file operation: {len(result)} files retrieved")
123
+
124
+ # Test file manager availability
125
+ available = component.is_file_manager_available()
126
+ print(f"βœ… File manager availability: {'Available' if available else 'Unavailable'}")
127
+
128
+ # Test recovery attempt
129
+ recovery_success = component.ensure_file_manager_available()
130
+ print(f"βœ… Recovery mechanism: {'Success' if recovery_success else 'Failed'}")
131
+
132
+ return True
133
+
134
+ except Exception as e:
135
+ print(f"❌ Error recovery test failed: {e}")
136
+ return False
137
+
138
+def main():
139
+ """Run the NoneType error fix verification tests"""
140
+ print("πŸš€ LaxyFile NoneType Error Fix Verification")
141
+ print("=" * 50)
142
+
143
+ tests = [
144
+ test_original_nonetype_error,
145
+ test_file_manager_service_robustness,
146
+ test_error_recovery
147
+ ]
148
+
149
+ passed = 0
150
+ failed = 0
151
+
152
+ for test in tests:
153
+ try:
154
+ if test():
155
+ passed += 1
156
+ else:
157
+ failed += 1
158
+ except Exception as e:
159
+ print(f"❌ Test {test.__name__} crashed: {e}")
160
+ failed += 1
161
+
162
+ print("\n" + "=" * 50)
163
+ print(f"πŸ“Š Test Results: {passed} passed, {failed} failed")
164
+
165
+ if failed == 0:
166
+ print("πŸŽ‰ SUCCESS: The original NoneType error has been fixed!")
167
+ print(" - FileManagerService provides robust singleton access")
168
+ print(" - ErrorHandlingMixin provides fallback behavior")
169
+ print(" - PanelManager safely handles file manager operations")
170
+ return 0
171
+ else:
172
+ print("⚠️ Some tests failed. The fix may not be complete.")
173
+ return 1
174
+
175
+if __name__ == "__main__":
176
+ sys.exit(main())
0
177
β–Όtest_preview_fix.py
+128-0
1
+#!/usr/bin/env python3
2
+"""
3
+Test script to verify the preview system NoneType error fix
4
+"""
5
+
6
+import asyncio
7
+import sys
8
+from pathlib import Path
9
+
10
+# Add the laxyfile package to the path
11
+sys.path.insert(0, str(Path(__file__).parent))
12
+
13
+async def test_preview_system_error_handling():
14
+ """Test that preview system handles file manager errors gracefully"""
15
+ print("πŸ§ͺ Testing Preview System Error Handling...")
16
+
17
+ try:
18
+ from laxyfile.preview.preview_system import AdvancedPreviewSystem, PreviewConfig
19
+ from laxyfile.core.file_manager_service import FileManagerService
20
+
21
+ # Reset file manager service to simulate uninitialized state
22
+ FileManagerService.reset()
23
+
24
+ # Create preview system
25
+ config = PreviewConfig()
26
+ preview_system = AdvancedPreviewSystem(config)
27
+
28
+ # Initialize preview system
29
+ await preview_system.initialize()
30
+ print(" βœ… Preview system initialized successfully")
31
+
32
+ # Test preview generation with a file that exists
33
+ test_file = Path(__file__)
34
+ print(f" πŸ” Testing preview generation for: {test_file.name}")
35
+
36
+ # This should not crash even if file manager is not properly initialized
37
+ preview_result = await preview_system.generate_preview(test_file)
38
+
39
+ if preview_result.success:
40
+ print(f" βœ… Preview generated successfully: {len(preview_result.content)} characters")
41
+ else:
42
+ print(f" ⚠️ Preview generation failed (expected): {preview_result.error_message}")
43
+
44
+ # Test with non-existent file
45
+ fake_file = Path("non_existent_file.txt")
46
+ print(f" πŸ” Testing preview generation for non-existent file: {fake_file.name}")
47
+
48
+ preview_result = await preview_system.generate_preview(fake_file)
49
+
50
+ if not preview_result.success:
51
+ print(f" βœ… Non-existent file handled gracefully: {preview_result.error_message}")
52
+ else:
53
+ print(f" ⚠️ Unexpected success for non-existent file")
54
+
55
+ print(" βœ… Preview system error handling test completed successfully")
56
+ return True
57
+
58
+ except Exception as e:
59
+ print(f" ❌ Preview system test failed: {e}")
60
+ import traceback
61
+ traceback.print_exc()
62
+ return False
63
+
64
+async def test_enhanced_panels_error_handling():
65
+ """Test that enhanced panels handle file manager errors gracefully"""
66
+ print("\nπŸ§ͺ Testing Enhanced Panels Error Handling...")
67
+
68
+ try:
69
+ from laxyfile.ui.enhanced_panels import EnhancedPanelRenderer
70
+ from laxyfile.ui.theme import ThemeManager
71
+ from laxyfile.core.config import Config
72
+ from rich.console import Console
73
+
74
+ # Create components
75
+ console = Console()
76
+ config = Config()
77
+ theme_manager = ThemeManager(config)
78
+ panel_renderer = EnhancedPanelRenderer(theme_manager, console)
79
+
80
+ print(" βœ… Enhanced panel renderer created successfully")
81
+
82
+ # Test preview panel rendering with no file
83
+ preview_panel = panel_renderer.render_preview_panel(None)
84
+ print(" βœ… Preview panel rendered successfully with no file")
85
+
86
+ # Test preview panel rendering with a file
87
+ test_file = Path(__file__)
88
+ preview_panel = panel_renderer.render_preview_panel(test_file, "Test content", "text")
89
+ print(" βœ… Preview panel rendered successfully with file")
90
+
91
+ print(" βœ… Enhanced panels error handling test completed successfully")
92
+ return True
93
+
94
+ except Exception as e:
95
+ print(f" ❌ Enhanced panels test failed: {e}")
96
+ import traceback
97
+ traceback.print_exc()
98
+ return False
99
+
100
+async def main():
101
+ """Run all tests"""
102
+ print("πŸš€ Starting LaxyFile Preview System Error Fix Tests\n")
103
+
104
+ test_results = []
105
+
106
+ # Test preview system
107
+ result1 = await test_preview_system_error_handling()
108
+ test_results.append(result1)
109
+
110
+ # Test enhanced panels
111
+ result2 = await test_enhanced_panels_error_handling()
112
+ test_results.append(result2)
113
+
114
+ # Summary
115
+ print(f"\nπŸ“Š Test Results:")
116
+ print(f" βœ… Passed: {sum(test_results)}")
117
+ print(f" ❌ Failed: {len(test_results) - sum(test_results)}")
118
+
119
+ if all(test_results):
120
+ print("\nπŸŽ‰ All tests passed! Preview system error handling is working correctly.")
121
+ return 0
122
+ else:
123
+ print("\nπŸ’₯ Some tests failed. Please check the error messages above.")
124
+ return 1
125
+
126
+if __name__ == "__main__":
127
+ exit_code = asyncio.run(main())
128
+ sys.exit(exit_code)
0
129