114 lines
6.1 KiB
Markdown
114 lines
6.1 KiB
Markdown
|
|
---
|
|||
|
|
type: Architecture Overview
|
|||
|
|
title: Architektur-Überblick
|
|||
|
|
description: PySide6-Mixin-Architektur, UI-Pattern (generierte UI vs. Implementierung), Thread-Modell und Konfigurationssystem von DocuMentor.
|
|||
|
|
tags: [architektur, pyside6, mixins, threads, konfiguration]
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
# Architektur-Überblick
|
|||
|
|
|
|||
|
|
## Technologie-Stack
|
|||
|
|
|
|||
|
|
| Schicht | Technologie | Zweck |
|
|||
|
|
|---------|------------|------|
|
|||
|
|
| GUI | PySide6 (Qt 6) | Native Desktop-Oberfläche |
|
|||
|
|
| Konfiguration | Pydantic + pydantic-settings | Typsichere Datenmodelle, JSON/YAML-Persistenz |
|
|||
|
|
| Datenverarbeitung | Polars + ConnectorX | PostgreSQL-Abfragen, DataFrames |
|
|||
|
|
| XSL-Parsing | lxml | XSL-Abhängigkeitsgraph |
|
|||
|
|
| Hashing | hashlib (blake2b) | Duplikatserkennung |
|
|||
|
|
| Build | PyInstaller + WiX/Inno Setup | Windows-Distribution |
|
|||
|
|
| Paketverwaltung | uv | Dependency-Management |
|
|||
|
|
|
|||
|
|
## Einstiegspunkt
|
|||
|
|
|
|||
|
|
Die Anwendung startet in `src/main.py`:
|
|||
|
|
1. Logging konfigurieren (Datei + Konsole, 24h Auto-Cleanup)
|
|||
|
|
2. `QApplication` erstellen, App-Icon setzen
|
|||
|
|
3. `MainWindow` instanziieren und anzeigen
|
|||
|
|
4. Bei fehlender Tool-Konfiguration `AppSettingsDlg` modal öffnen
|
|||
|
|
5. Qt-Event-Loop starten
|
|||
|
|
|
|||
|
|
## MainWindow – Mixin-Architektur
|
|||
|
|
|
|||
|
|
`MainWindow` (`src/ui/MainWindow.py`) ist die zentrale Klasse und erbt von mehreren Mixins, um Funktionalität zu trennen:
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
MainWindow
|
|||
|
|
├── IconRefreshMixin (src/icons.py) – Theme-Wechsel → Icons neu einfärben
|
|||
|
|
├── QMainWindow (PySide6) – Qt-Basisfenster
|
|||
|
|
├── TreeManagerMixin (mixins/tree_manager.py) – Baum-Widget, Kontextmenüs, Node-Operationen
|
|||
|
|
├── PdfViewerMixin (mixins/pdf_viewer.py) – PDF-Rendering, Alpha-Blending, Zoom
|
|||
|
|
├── WorkerPoolMixin (mixins/worker_pool.py) – Saxon/FOP Worker-Pool Lebenszyklus
|
|||
|
|
├── DatabaseMixin (mixins/database.py) – PostgreSQL-Abfragen, Obsolete-Erkennung
|
|||
|
|
├── DragDropMixin (mixins/drag_drop.py) – Drag-and-Drop für XML-Dateien
|
|||
|
|
├── HashCalculationMixin (mixins/hash_calculation.py) – blake2b-Hash-Berechnung, Duplikatserkennung
|
|||
|
|
└── TransformationMixin (mixins/transformation.py) – XSL-Transformations-Ausführung
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Jedes Mixin erwartet bestimmte Attribute (`self.ui`, `self.project`, `self.pdf_project`) und Methoden von anderen Mixins. Die Mixins sind in `src/ui/mixins/__init__.py` gebündelt.
|
|||
|
|
|
|||
|
|
### Mixin-Abhängigkeiten
|
|||
|
|
|
|||
|
|
`TransformationMixin` benötigt Methoden aus `WorkerPoolMixin` (`_initialize_saxon_worker_pool`, `_shutdown_saxon_worker_pool`) und `TreeManagerMixin` (`_collect_parent_params`, `_create_centered_progress_bar`). `HashCalculationMixin` und `DragDropMixin` benötigen `_save_project_settings()` und `_load_nodes_to_tree()` von MainWindow.
|
|||
|
|
|
|||
|
|
## UI-Pattern: Generierte UI vs. Implementierung
|
|||
|
|
|
|||
|
|
DocuMentor folgt einem strikten PySide6-Pattern:
|
|||
|
|
|
|||
|
|
1. **UI-Definitionsdateien** (`*_ui.py`, z.B. `MainWinddow_ui.py`): Aus `.ui`-Dateien von einer VS Code Extension generiert. Enthalten nur Widget-Layout-Definitionen als Klasse (`Ui_MainWindow`). **Nicht manuell bearbeiten.**
|
|||
|
|
|
|||
|
|
2. **Implementierungsdateien** (ohne `_ui`-Suffix, z.B. `MainWindow.py`): Importieren die UI-Klasse, instanziieren sie als `self.ui` und fügen Business-Logik hinzu.
|
|||
|
|
|
|||
|
|
```python
|
|||
|
|
class JavaVmConfigDialog(QDialog):
|
|||
|
|
def __init__(self, parent=None):
|
|||
|
|
super().__init__(parent)
|
|||
|
|
self.ui = Ui_JavaVmConfigDialog()
|
|||
|
|
self.ui.setupUi(self)
|
|||
|
|
# Signale NACH setupUi() verbinden
|
|||
|
|
self.ui.browseButton.clicked.connect(self._browse_file)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**Regeln:**
|
|||
|
|
- UI-Klassen niemals direkt erben, nur als `self.ui`-Member verwenden
|
|||
|
|
- Alle Widgets über `self.ui.widgetName` zugreifen
|
|||
|
|
- Signal-Verbindungen immer nach `setupUi()`
|
|||
|
|
|
|||
|
|
## Thread-Modell
|
|||
|
|
|
|||
|
|
Asynchrone Operationen verwenden `QThread`-Subklassen aus `src/ui/threads.py`:
|
|||
|
|
|
|||
|
|
| Thread-Klasse | Zweck | Signale |
|
|||
|
|
|---------------|------|---------|
|
|||
|
|
| `XmlHashCalculatorThread` | blake2b-Hash-Berechnung für XML-Dateien | `hash_calculated`, `calculation_finished`, `error_occurred` |
|
|||
|
|
| `XmlBatchProcessingThread` | Batch-Verarbeitung: Hash + Duplikatserkennung + Dateikopieren | `progress_update`, `file_processed`, `processing_finished` |
|
|||
|
|
| `TransformationThread` | Ausführung der XSL-Transformations-Pipeline | Fortschritts- und Abschluss-Signale |
|
|||
|
|
| `DatabaseQueryThread` | Asynchrone PostgreSQL-Abfragen (in `mixins/database.py`) | `query_completed` (DataFrame), `query_failed` |
|
|||
|
|
|
|||
|
|
**Wichtig:** Threads immer mit `.start()` starten, nie `.run()` direkt aufrufen.
|
|||
|
|
|
|||
|
|
## Konfigurationssystem
|
|||
|
|
|
|||
|
|
Die Konfiguration ist in [Datenmodelle & Konfiguration](../data-models.md) detailliert beschrieben. Zusammenfassung:
|
|||
|
|
|
|||
|
|
- **`AppSettings`** (`src/conf.py`): Globales Singleton (`app_settings`), gespeichert als JSON. Enthält Tool-Listen, UI-Zustand, Worker-Pool-Einstellungen.
|
|||
|
|
- **`ProjectData`** (`src/conf.py`): Projektspezifisch, gespeichert als `project.yaml` im Projektverzeichnis. Enthält die Baumstruktur (`TreeNode → XslFile → XmlFile`).
|
|||
|
|
|
|||
|
|
## Icon-System
|
|||
|
|
|
|||
|
|
Icons werden über `src/icons.py` verwaltet:
|
|||
|
|
- Feather Icons aus dem Qt-Ressource-System (`:/icons/<name>.svg`)
|
|||
|
|
- Einmaliges Rendern bei 64px, Qt skaliert für 16/24/32px
|
|||
|
|
- Theme-reactiv: Färbung nach `QPalette.WindowText`, Cache-Schlüssel enthält Theme-Farbe
|
|||
|
|
- `IconRefreshMixin` erlaubt Theme-Wechsel zur Laufzeit ohne manuellen Cache-Reset
|
|||
|
|
|
|||
|
|
## XSL-Abhängigkeitsgraph
|
|||
|
|
|
|||
|
|
`src/xsl_dependencies.py` (`XslDependencyGraph`) parst XSL-Dateien mit lxml und baut einen Abhängigkeitsgraph für `<xsl:import/>` und `<xsl:include/>`. Der Graph wird über Datei-mtime automatisch invalidiert. Visualisiert wird er im Dialog `src/ui/XslDependencyDialog.py` via vis.js.
|
|||
|
|
|
|||
|
|
Der Abhängigkeitsgraph ist auch für die [Transformations-Pipeline](../workflows/transformation-pipeline.md) relevant: wenn eine importierte XSL-Datei neuer ist als das Output-PDF, gilt die Transformation als veraltet.
|
|||
|
|
|
|||
|
|
## Obsolete-Erkennung
|
|||
|
|
|
|||
|
|
`src/obsolete_detector.py` enthält reine Analyselogik (ohne Qt-Abhängigkeit) zum Finden veralteter `XslFile`-Einträge nach Datenbank-Import. Wird vom `DatabaseMixin` nach dem DB-Laden aufgerufen und im `ObsoleteEntriesDialog` (`src/ui/ObsoleteEntriesDialog.py`) angezeigt.
|