Add initial project structure and documentation
- Created architecture documentation outlining high-level design, module organization, data flow, security model, performance considerations, testing strategy, and deployment architecture. - Added pyproject.toml for project metadata and dependencies management. - Introduced requirements files for development and production dependencies. - Set up testing configuration with pytest and tox. - Established basic directory structure for source code and tests, including __init__.py files. - Implemented a sample web application (index.html) for drag-and-drop functionality. - Configured VS Code workspace settings for Python development.
This commit is contained in:
commit
61aa33633c
34 changed files with 5342 additions and 0 deletions
291
docs/ARCHITECTURE.md
Normal file
291
docs/ARCHITECTURE.md
Normal file
|
|
@ -0,0 +1,291 @@
|
|||
# Architecture Guide
|
||||
|
||||
## 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
|
||||
- `drag_interceptor.py`: Drag event handling and conversion
|
||||
- `config.py`: Configuration management
|
||||
- `errors.py`: Custom exception classes
|
||||
|
||||
**Dependencies**: None (only stdlib + pathlib)
|
||||
|
||||
### `src/webdrop_bridge/ui/`
|
||||
|
||||
**Purpose**: User interface components using Qt/PySide6
|
||||
|
||||
**Key Components:**
|
||||
|
||||
- `main_window.py`: Main application window
|
||||
- `widgets.py`: Reusable custom widgets
|
||||
- `styles.py`: UI styling and themes
|
||||
|
||||
**Dependencies**: PySide6, core/
|
||||
|
||||
### `src/webdrop_bridge/utils/`
|
||||
|
||||
**Purpose**: Shared utilities and helpers
|
||||
|
||||
**Key Components:**
|
||||
|
||||
- `logging.py`: Logging configuration
|
||||
- `constants.py`: Application constants
|
||||
- `helpers.py`: General-purpose helper functions
|
||||
|
||||
**Dependencies**: stdlib only
|
||||
|
||||
## Data Flow
|
||||
|
||||
### Drag-and-Drop Operation
|
||||
|
||||
```
|
||||
User in Web App
|
||||
↓
|
||||
[dragstart event] → JavaScript sets dataTransfer.text = "Z:\path\file.txt"
|
||||
↓
|
||||
[dragend event] → Drag leaves WebEngine widget
|
||||
↓
|
||||
DragInterceptor.dragEnterEvent() triggered
|
||||
↓
|
||||
Extract text from QMimeData
|
||||
↓
|
||||
PathValidator.is_valid_file(path)
|
||||
├─ is_allowed(path) → Check whitelist
|
||||
└─ path.exists() and path.is_file() → File system check
|
||||
↓
|
||||
If valid:
|
||||
→ Create QUrl.fromLocalFile(path)
|
||||
→ Create new QMimeData with URLs
|
||||
→ QDrag.exec() → Native file drag
|
||||
↓
|
||||
If invalid:
|
||||
→ event.ignore()
|
||||
→ Log warning
|
||||
↓
|
||||
OS receives native file drag
|
||||
↓
|
||||
InDesign/Word receives file handle
|
||||
```
|
||||
|
||||
## 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
|
||||
|
||||
## Monitoring & Debugging
|
||||
|
||||
### Debug Logging
|
||||
|
||||
```python
|
||||
# Enable debug logging
|
||||
LOG_LEVEL=DEBUG
|
||||
|
||||
# Output
|
||||
2026-01-28 14:32:15 - webdrop_bridge - DEBUG - DragInterceptor: dragEnterEvent triggered
|
||||
2026-01-28 14:32:15 - webdrop_bridge - DEBUG - PathValidator: Checking Z:\file.psd
|
||||
2026-01-28 14:32:15 - webdrop_bridge - INFO - File dragged: Z:\file.psd
|
||||
```
|
||||
|
||||
### Performance Profiling
|
||||
|
||||
```python
|
||||
import cProfile
|
||||
import pstats
|
||||
|
||||
profiler = cProfile.Profile()
|
||||
profiler.enable()
|
||||
# ... drag operation ...
|
||||
profiler.disable()
|
||||
stats = pstats.Stats(profiler)
|
||||
stats.print_stats()
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
For more details, see [DEVELOPMENT_PLAN.md](DEVELOPMENT_PLAN.md)
|
||||
Loading…
Add table
Add a link
Reference in a new issue