Some checks are pending
Tests & Quality Checks / Test on Python 3.11 (push) Waiting to run
Tests & Quality Checks / Test on Python 3.12 (push) Waiting to run
Tests & Quality Checks / Test on Python 3.11-1 (push) Waiting to run
Tests & Quality Checks / Test on Python 3.12-1 (push) Waiting to run
Tests & Quality Checks / Test on Python 3.10 (push) Waiting to run
Tests & Quality Checks / Test on Python 3.11-2 (push) Waiting to run
Tests & Quality Checks / Test on Python 3.12-2 (push) Waiting to run
Tests & Quality Checks / Build Artifacts (push) Blocked by required conditions
Tests & Quality Checks / Build Artifacts-1 (push) Blocked by required conditions
306 lines
10 KiB
Markdown
306 lines
10 KiB
Markdown
# Architecture Guide
|
|
|
|
## Related Docs
|
|
|
|
- [Translations Guide (i18n)](TRANSLATIONS_GUIDE.md)
|
|
|
|
## High-Level Design
|
|
|
|
```
|
|
┌──────────────────────────────────────────────────────────┐
|
|
│ Application Layer (UI) │
|
|
│ ┌────────────────────────────────────────────────────────┤
|
|
│ │ QMainWindow (main_window.py) │
|
|
│ │ ├─ QWebEngineView (web app container) │
|
|
│ │ └─ DragInterceptor (drag handling) │
|
|
│ └────────────────────────────────────────────────────────┘
|
|
│ ↓ Events (dragEnterEvent, etc.)
|
|
├──────────────────────────────────────────────────────────┤
|
|
│ Business Logic Layer (Core) │
|
|
│ ┌────────────────────────────────────────────────────────┤
|
|
│ │ PathValidator (security & validation) │
|
|
│ │ DragInterceptor (drag/drop conversion) │
|
|
│ │ Config (configuration management) │
|
|
│ └────────────────────────────────────────────────────────┘
|
|
│ ↓ Platform APIs
|
|
├──────────────────────────────────────────────────────────┤
|
|
│ Platform Layer (OS Integration) │
|
|
│ ┌────────────────────────────────────────────────────────┤
|
|
│ │ QUrl (Windows: CF_HDROP, macOS: NSFilenamesPboardType)│
|
|
│ │ QMimeData (cross-platform mime data) │
|
|
│ │ QDrag (native drag-drop implementation) │
|
|
│ └────────────────────────────────────────────────────────┘
|
|
```
|
|
|
|
## Module Organization
|
|
|
|
### `src/webdrop_bridge/core/`
|
|
|
|
**Purpose**: Core business logic, independent of UI
|
|
|
|
**Key Components:**
|
|
|
|
- `validator.py`: Path validation against whitelist with security checks
|
|
- `drag_interceptor.py`: Drag event handling and native drag operations
|
|
- `config_manager.py`: Configuration loading from files and caching
|
|
- `url_converter.py`: Azure Blob Storage URL → local path conversion
|
|
- `updater.py`: Update checking via Forgejo API
|
|
|
|
**Dependencies**: None (only stdlib + pathlib)
|
|
|
|
### `src/webdrop_bridge/ui/`
|
|
|
|
**Purpose**: User interface components using Qt/PySide6
|
|
|
|
**Key Components:**
|
|
|
|
- `main_window.py`: Main application window with web engine integration
|
|
- `restricted_web_view.py`: Hardened QWebEngineView with security policies
|
|
- `settings_dialog.py`: Settings UI for configuration
|
|
- `update_manager_ui.py`: Update checking and notification UI
|
|
- `bridge_script_intercept.js`: JavaScript drag interception and WebChannel bridge for Qt communication
|
|
- `download_interceptor.js`: Download handling for web content
|
|
|
|
**Dependencies**: PySide6, core/
|
|
|
|
### `src/webdrop_bridge/utils/`
|
|
|
|
**Purpose**: Shared utilities and helpers
|
|
|
|
**Key Components:**
|
|
|
|
- `logging.py`: Logging configuration (console + file with rotation)
|
|
|
|
**Dependencies**: stdlib only
|
|
|
|
## Data Flow
|
|
|
|
### Drag-and-Drop Operation
|
|
|
|
```
|
|
User in Web App (browser)
|
|
↓
|
|
[dragstart event] → bridge_script_intercept.js detects drag
|
|
├─ Checks if content is convertible (file path or Azure URL)
|
|
├─ Calls window.bridge.start_file_drag(url)
|
|
└─ preventDefault() → Blocks normal browser drag
|
|
|
|
↓
|
|
JavaScript → QWebChannel Bridge
|
|
↓
|
|
_DragBridge.start_file_drag(path_text) [main_window.py]
|
|
├─ Defers execution via QTimer (drag manager safety)
|
|
└─ Calls DragInterceptor.handle_drag()
|
|
|
|
↓
|
|
DragInterceptor.handle_drag() [core/drag_interceptor.py]
|
|
├─ Check if Azure URL: Use URLConverter → local path
|
|
├─ Else: Treat as direct file path
|
|
└─ Validate with PathValidator
|
|
|
|
↓
|
|
PathValidator.validate(path)
|
|
├─ Resolve to absolute path
|
|
├─ Check file exists (if configured)
|
|
├─ Check is regular file (not directory)
|
|
└─ Check path within allowed_roots (whitelist)
|
|
|
|
↓
|
|
If valid:
|
|
→ Create QUrl.fromLocalFile(path)
|
|
→ Create QMimeData with file URL
|
|
→ QDrag.exec(Qt.CopyAction) → Native file drag
|
|
→ Emit drag_started signal
|
|
↓
|
|
If invalid:
|
|
→ Emit drag_failed signal with error
|
|
→ Log validation error
|
|
↓
|
|
OS receives native file drag
|
|
↓
|
|
Target application (InDesign/Word) receives file handle
|
|
```
|
|
|
|
**Key Components in Data Flow:**
|
|
|
|
1. **bridge_script_intercept.js**: Opens a WebChannel to Qt's _DragBridge
|
|
2. **_DragBridge**: Exposes `start_file_drag()` slot to JavaScript
|
|
3. **DragInterceptor**: Handles validation and native drag creation
|
|
4. **URLConverter**: Maps Azure Blob Storage URLs to local paths via config
|
|
5. **PathValidator**: Security-critical validation against whitelist
|
|
|
|
## Security Model
|
|
|
|
### Path Validation Strategy
|
|
|
|
1. **Whitelist-based**: Only allow configured root directories
|
|
2. **Absolute path resolution**: Prevent directory traversal attacks
|
|
3. **Symlink handling**: Resolve to real path before checking
|
|
4. **File existence check**: Don't drag non-existent files
|
|
5. **Type verification**: Only allow regular files
|
|
|
|
```python
|
|
# Example allowed roots
|
|
ALLOWED_ROOTS = [
|
|
Path("Z:/"), # Network share
|
|
Path("C:/Users/Public"), # Windows public folder
|
|
]
|
|
|
|
# Example validation
|
|
path = Path(r"Z:\projects\file.psd")
|
|
→ Resolve: Z:\projects\file.psd
|
|
→ Check: starts with Z:\? YES
|
|
→ Check: file exists? YES
|
|
→ Check: is regular file? YES
|
|
→ Result: ALLOW
|
|
```
|
|
|
|
### Web Engine Security
|
|
|
|
- **LocalContentCanAccessFileUrls**: Enabled (required for drag)
|
|
- **LocalContentCanAccessRemoteUrls**: Disabled (prevent phishing)
|
|
- **Certificate validation**: Enforced for HTTPS
|
|
|
|
## Performance Considerations
|
|
|
|
### Drag Operation Timing
|
|
|
|
```
|
|
Event Time (ms) Critical Path
|
|
─────────────────────────────────────
|
|
dragenter <1 ✓ Must be fast
|
|
validation <10 ✓ Keep fast
|
|
QUrl creation <1
|
|
QDrag.exec() <50 ✓ Start quickly
|
|
─────────────────────────
|
|
TOTAL <50 ✓ Imperceptible
|
|
```
|
|
|
|
### Memory Management
|
|
|
|
- **Web engine**: ~100-150 MB baseline
|
|
- **Drag interceptor**: Minimal overhead
|
|
- **Logging**: Rotated to prevent growth
|
|
- **File cache**: None (on-demand validation)
|
|
|
|
## Testing Strategy
|
|
|
|
### Unit Testing
|
|
|
|
```
|
|
validator.py
|
|
├─ test_is_allowed()
|
|
├─ test_is_valid_file()
|
|
└─ test_symlink_handling()
|
|
|
|
config.py
|
|
├─ test_load_from_env()
|
|
├─ test_default_values()
|
|
└─ test_validation()
|
|
|
|
drag_interceptor.py
|
|
├─ test_dragEnterEvent()
|
|
├─ test_invalid_path_rejected()
|
|
└─ test_file_drag_created()
|
|
```
|
|
|
|
### Integration Testing
|
|
|
|
```
|
|
end_to_end.py
|
|
├─ test_app_startup()
|
|
├─ test_webapp_loads()
|
|
├─ test_complete_drag_workflow()
|
|
└─ test_invalid_path_handling()
|
|
```
|
|
|
|
## Extension Points
|
|
|
|
### Adding New Handlers
|
|
|
|
```python
|
|
# Future: Support for additional file types or sources
|
|
class HandlerRegistry:
|
|
def register(self, source_type: str, handler: Handler):
|
|
self.handlers[source_type] = handler
|
|
|
|
def handle(self, data: QMimeData) -> Optional[QMimeData]:
|
|
for handler in self.handlers.values():
|
|
if handler.can_handle(data):
|
|
return handler.handle(data)
|
|
return None
|
|
```
|
|
|
|
### Custom Validators
|
|
|
|
```python
|
|
# Future: Pluggable validation rules
|
|
class ValidatorPlugin(ABC):
|
|
@abstractmethod
|
|
def validate(self, path: Path) -> bool:
|
|
pass
|
|
|
|
class FileSizeValidator(ValidatorPlugin):
|
|
def __init__(self, max_size: int):
|
|
self.max_size = max_size
|
|
|
|
def validate(self, path: Path) -> bool:
|
|
return path.stat().st_size <= self.max_size
|
|
```
|
|
|
|
## Deployment Architecture
|
|
|
|
### Single-File Executable
|
|
|
|
```
|
|
WebDropBridge.exe (built with PyInstaller)
|
|
├─ Python runtime
|
|
├─ PySide6 libraries
|
|
├─ Qt libraries
|
|
├─ Embedded web app (index.html)
|
|
└─ Bundled resources (icons, etc.)
|
|
|
|
Size: ~100-120 MB
|
|
Startup: <1 second
|
|
```
|
|
|
|
### Portable Deployment
|
|
|
|
- Single executable: No installation required
|
|
- No registry entries: Clean removal
|
|
- Relative paths: Can run from any directory
|
|
- Configuration: `.env` file per instance
|
|
|
|
## Platform Differences
|
|
|
|
### Windows
|
|
|
|
- **Drag Format**: CF_HDROP (file paths)
|
|
- **File URLs**: `file:///C:/path/to/file`
|
|
- **Paths**: Backslash `\` (converted to forward `/`)
|
|
- **Permissions**: Admin privileges not required
|
|
|
|
### macOS
|
|
|
|
- **Drag Format**: NSFilenamesPboardType (same as Windows, different implementation)
|
|
- **File URLs**: `file:///Users/username/path/to/file`
|
|
- **Paths**: Forward slash `/` (native)
|
|
- **Permissions**: May require accessibility permissions
|
|
|
|
## Update Manager
|
|
|
|
The `UpdateManager` class checks for new releases using the Forgejo API. It caches results and only signals updates for newer versions. See `src/webdrop_bridge/core/updater.py` for implementation.
|
|
|
|
## Release Flow
|
|
|
|
- Checks for new releases on startup or user request
|
|
- Parses release notes and assets
|
|
- Notifies UI if update is available
|
|
|
|
## Integration Test Strategy
|
|
|
|
Integration tests verify workflows across modules. The update workflow is covered in [tests/integration/test_update_flow.py](../tests/integration/test_update_flow.py).
|
|
|
|
---
|
|
|
|
For more details, see [DEVELOPMENT_PLAN.md](DEVELOPMENT_PLAN.md)
|