webdrop-bridge/docs/ARCHITECTURE.md

8.5 KiB

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
# 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

# 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

# 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.


For more details, see DEVELOPMENT_PLAN.md