Performance-Verbesserung: Asynchrone Batch-Verarbeitung und Progressbars
Änderungen: - XML-Batch-Processing in separaten Thread verlagert (XmlBatchProcessingThread) • Verhindert UI-Freezing bei vielen XML-Dateien • Hash-Berechnung, Duplikatserkennung und Dateikopieren asynchron • Live Progress-Updates an Hauptthread - Progressbar für XML-Batch-Verarbeitung in Statusbar • Zeigt "X/Y Dateien" während Verarbeitung • Aktueller Dateiname in Statusbar-Text • Automatisches Verstecken nach Abschluss - Progressbar für Transformationen in Statusbar hinzugefügt • Zeigt "X/Y Jobs" während Transformation • Live-Update nach jedem abgeschlossenen Job • Funktioniert bei Erfolg und Fehlern - Zusammenfassungsdialog am Ende statt einzelner Erfolgsdialoge Technische Details: - Neue Thread-Klasse mit Signalen für Progress-Updates - Progressbar-Management-Methoden für beide Operationen - Signal-Handler angepasst für Live-Updates - Statistik-Sammlung für detaillierten Zusammenfassungsdialog UX-Verbesserungen: - UI bleibt während Verarbeitung reaktionsfähig - Benutzer sieht Gesamtfortschritt in Echtzeit - Kein wiederholtes Klicken auf OK-Dialoge mehr - Transparente Anzeige laufender Operationen 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
+416
-79
@@ -123,6 +123,262 @@ class XmlHashCalculatorThread(QThread):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
class XmlBatchProcessingThread(QThread):
|
||||||
|
"""
|
||||||
|
Thread für die asynchrone Batch-Verarbeitung von mehreren XML-Dateien.
|
||||||
|
Verarbeitet XML-Dateien mit Hash-Berechnung, Duplikatserkennung und Dateikopieren.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Signale für die Kommunikation mit dem Haupt-Thread
|
||||||
|
progress_update = Signal(int, int, str) # current, total, current_file_name
|
||||||
|
file_processed = Signal(dict) # result dictionary
|
||||||
|
processing_finished = Signal(dict) # stats dictionary
|
||||||
|
error_occurred = Signal(str) # error_message
|
||||||
|
|
||||||
|
def __init__(self, xml_files: list, selected_xsl_nodes: list, project_dir: Path, pdf_project):
|
||||||
|
"""
|
||||||
|
Initialisiert den Batch-Verarbeitungs-Thread.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
xml_files: Liste von Pfaden zu XML-Dateien
|
||||||
|
selected_xsl_nodes: Liste der ausgewählten XSL-Knoten
|
||||||
|
project_dir: Pfad zum Projekt-Verzeichnis
|
||||||
|
pdf_project: ProjectData-Objekt
|
||||||
|
"""
|
||||||
|
super().__init__()
|
||||||
|
self.xml_files = xml_files
|
||||||
|
self.selected_xsl_nodes = selected_xsl_nodes
|
||||||
|
self.project_dir = project_dir
|
||||||
|
self.pdf_project = pdf_project
|
||||||
|
|
||||||
|
# Statistiken
|
||||||
|
self.stats = {
|
||||||
|
"total": len(xml_files),
|
||||||
|
"processed": 0,
|
||||||
|
"new_added": 0,
|
||||||
|
"existing_added": 0,
|
||||||
|
"already_assigned": 0,
|
||||||
|
"cancelled": 0,
|
||||||
|
"errors": 0,
|
||||||
|
"error_messages": [],
|
||||||
|
"renamed_files": [],
|
||||||
|
}
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
"""
|
||||||
|
Führt die Batch-Verarbeitung aller XML-Dateien aus.
|
||||||
|
"""
|
||||||
|
logger.info(f"Starte Batch-Verarbeitung für {len(self.xml_files)} XML-Dateien")
|
||||||
|
|
||||||
|
for i, xml_file_path in enumerate(self.xml_files):
|
||||||
|
try:
|
||||||
|
# Sende Progress-Update
|
||||||
|
self.progress_update.emit(i + 1, len(self.xml_files), xml_file_path.name)
|
||||||
|
|
||||||
|
# Prüfe ob die Datei existiert
|
||||||
|
if not xml_file_path.exists():
|
||||||
|
self.stats["errors"] += 1
|
||||||
|
self.stats["error_messages"].append(f"{xml_file_path.name}: Datei existiert nicht")
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Verarbeite die XML-Datei
|
||||||
|
result = self._process_xml_file(xml_file_path)
|
||||||
|
|
||||||
|
# Aktualisiere Statistiken
|
||||||
|
self._update_stats(result)
|
||||||
|
|
||||||
|
# Sende Ergebnis
|
||||||
|
self.file_processed.emit(result)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
error_msg = f"Fehler bei {xml_file_path.name}: {str(e)}"
|
||||||
|
logger.error(error_msg)
|
||||||
|
self.stats["errors"] += 1
|
||||||
|
self.stats["error_messages"].append(error_msg)
|
||||||
|
|
||||||
|
# Sende Abschluss-Signal mit Statistiken
|
||||||
|
self.processing_finished.emit(self.stats)
|
||||||
|
logger.info(f"Batch-Verarbeitung abgeschlossen: {self.stats['processed']}/{self.stats['total']} verarbeitet")
|
||||||
|
|
||||||
|
def _process_xml_file(self, xml_file_path: Path) -> dict:
|
||||||
|
"""
|
||||||
|
Verarbeitet eine einzelne XML-Datei.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
xml_file_path: Pfad zur XML-Datei
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict: Ergebnis-Dictionary mit Status
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# 1. Hash berechnen
|
||||||
|
file_hash = self._calculate_hash_for_file(xml_file_path)
|
||||||
|
if not file_hash:
|
||||||
|
logger.warning(f"Hash-Berechnung für {xml_file_path} fehlgeschlagen")
|
||||||
|
|
||||||
|
# 2. Prüfe auf Hash-Duplikat
|
||||||
|
existing_xml = self._find_xml_file_by_hash(file_hash) if file_hash else None
|
||||||
|
|
||||||
|
if existing_xml:
|
||||||
|
# Hash-Match: Ordne vorhandene Datei zu
|
||||||
|
return self._assign_existing_xml_to_nodes(existing_xml)
|
||||||
|
else:
|
||||||
|
# Keine Duplikate: Verarbeite als neue Datei
|
||||||
|
return self._process_new_xml_file(xml_file_path, file_hash)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
return {"status": "error", "error_msg": str(e)}
|
||||||
|
|
||||||
|
def _calculate_hash_for_file(self, file_path: Path) -> str | None:
|
||||||
|
"""Berechnet blake2b Hash für eine Datei."""
|
||||||
|
try:
|
||||||
|
if not file_path.exists():
|
||||||
|
return None
|
||||||
|
|
||||||
|
with open(file_path, "rb") as f:
|
||||||
|
file_content = f.read()
|
||||||
|
hash_obj = hashlib.blake2b(file_content)
|
||||||
|
hash_hex = hash_obj.hexdigest()
|
||||||
|
|
||||||
|
return f"blake2b:{hash_hex}"
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Fehler beim Berechnen des Hash für {file_path}: {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
def _find_xml_file_by_hash(self, hash_value: str) -> XmlFile | None:
|
||||||
|
"""Sucht eine XML-Datei anhand ihres Hash-Werts."""
|
||||||
|
if not hash_value or not self.pdf_project.nodes:
|
||||||
|
return None
|
||||||
|
|
||||||
|
def search_recursive(nodes):
|
||||||
|
for node in nodes:
|
||||||
|
if isinstance(node, XslFile) and node.xmls:
|
||||||
|
for xml_file in node.xmls:
|
||||||
|
if xml_file.hashsum == hash_value:
|
||||||
|
return xml_file
|
||||||
|
elif isinstance(node, TreeNode) and node.children:
|
||||||
|
found = search_recursive(node.children)
|
||||||
|
if found:
|
||||||
|
return found
|
||||||
|
return None
|
||||||
|
|
||||||
|
return search_recursive(self.pdf_project.nodes)
|
||||||
|
|
||||||
|
def _assign_existing_xml_to_nodes(self, existing_xml: XmlFile) -> dict:
|
||||||
|
"""Ordnet eine vorhandene XML-Datei den Knoten zu."""
|
||||||
|
try:
|
||||||
|
added_count = 0
|
||||||
|
|
||||||
|
for xsl_node in self.selected_xsl_nodes:
|
||||||
|
already_assigned = any(xml_file.xml == existing_xml.xml for xml_file in xsl_node.xmls)
|
||||||
|
|
||||||
|
if not already_assigned:
|
||||||
|
new_xml_ref = XmlFile(xml=existing_xml.xml, hashsum=existing_xml.hashsum)
|
||||||
|
xsl_node.xmls.append(new_xml_ref)
|
||||||
|
added_count += 1
|
||||||
|
|
||||||
|
if added_count > 0:
|
||||||
|
return {
|
||||||
|
"status": "existing_added",
|
||||||
|
"added_count": added_count,
|
||||||
|
"existing_file": existing_xml.xml.name,
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
return {"status": "already_assigned", "added_count": 0, "existing_file": existing_xml.xml.name}
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
return {"status": "error", "error_msg": str(e)}
|
||||||
|
|
||||||
|
def _process_new_xml_file(self, xml_file_path: Path, file_hash: str | None) -> dict:
|
||||||
|
"""Verarbeitet eine neue XML-Datei."""
|
||||||
|
try:
|
||||||
|
# Erstelle xml-Ordner
|
||||||
|
xml_dir = self.project_dir / "xml"
|
||||||
|
xml_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
# Bestimme Ziel-Pfad
|
||||||
|
target_xml_path = xml_dir / xml_file_path.name
|
||||||
|
|
||||||
|
# Prüfe auf Namenskonflikte und generiere ggf. alternativen Namen
|
||||||
|
original_name = xml_file_path.name
|
||||||
|
counter = 1
|
||||||
|
while target_xml_path.exists() or self._is_filename_used_in_project(Path("xml") / target_xml_path.name):
|
||||||
|
# Generiere alternativen Namen
|
||||||
|
stem = xml_file_path.stem
|
||||||
|
suffix = xml_file_path.suffix
|
||||||
|
target_xml_path = xml_dir / f"{stem}_{counter}{suffix}"
|
||||||
|
counter += 1
|
||||||
|
|
||||||
|
# Sicherheit: Maximal 1000 Versuche
|
||||||
|
if counter > 1000:
|
||||||
|
return {"status": "error", "error_msg": "Konnte keinen eindeutigen Dateinamen finden"}
|
||||||
|
|
||||||
|
# Kopiere Datei
|
||||||
|
shutil.copy2(xml_file_path, target_xml_path)
|
||||||
|
|
||||||
|
# Erstelle relatives Path
|
||||||
|
relative_xml_path = Path("xml") / target_xml_path.name
|
||||||
|
|
||||||
|
# Füge zu XSL-Knoten hinzu
|
||||||
|
added_count = 0
|
||||||
|
for xsl_node in self.selected_xsl_nodes:
|
||||||
|
existing_xml = any(xml_file.xml == relative_xml_path for xml_file in xsl_node.xmls)
|
||||||
|
|
||||||
|
if not existing_xml:
|
||||||
|
new_xml_file = XmlFile(xml=relative_xml_path, hashsum=file_hash)
|
||||||
|
xsl_node.xmls.append(new_xml_file)
|
||||||
|
added_count += 1
|
||||||
|
|
||||||
|
if added_count > 0:
|
||||||
|
return {
|
||||||
|
"status": "new_added",
|
||||||
|
"added_count": added_count,
|
||||||
|
"new_file": target_xml_path.name,
|
||||||
|
"renamed_from": original_name if target_xml_path.name != original_name else None,
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
return {"status": "already_assigned", "added_count": 0, "new_file": target_xml_path.name}
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
return {"status": "error", "error_msg": str(e)}
|
||||||
|
|
||||||
|
def _is_filename_used_in_project(self, filename: Path) -> bool:
|
||||||
|
"""Prüft ob ein Dateiname bereits im Projekt verwendet wird."""
|
||||||
|
if not self.pdf_project.nodes:
|
||||||
|
return False
|
||||||
|
|
||||||
|
def search_recursive(nodes):
|
||||||
|
for node in nodes:
|
||||||
|
if isinstance(node, XslFile) and node.xmls:
|
||||||
|
for xml_file in node.xmls:
|
||||||
|
if xml_file.xml == filename:
|
||||||
|
return True
|
||||||
|
elif isinstance(node, TreeNode) and node.children:
|
||||||
|
if search_recursive(node.children):
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
return search_recursive(self.pdf_project.nodes)
|
||||||
|
|
||||||
|
def _update_stats(self, result: dict):
|
||||||
|
"""Aktualisiert die Statistiken."""
|
||||||
|
self.stats["processed"] += 1
|
||||||
|
|
||||||
|
status = result.get("status")
|
||||||
|
if status == "new_added":
|
||||||
|
self.stats["new_added"] += 1
|
||||||
|
if result.get("renamed_from"):
|
||||||
|
self.stats["renamed_files"].append(f"{result['renamed_from']} → {result['new_file']}")
|
||||||
|
elif status == "existing_added":
|
||||||
|
self.stats["existing_added"] += 1
|
||||||
|
elif status == "already_assigned":
|
||||||
|
self.stats["already_assigned"] += 1
|
||||||
|
elif status == "error":
|
||||||
|
self.stats["errors"] += 1
|
||||||
|
self.stats["error_messages"].append(result.get("error_msg", "Unbekannter Fehler"))
|
||||||
|
|
||||||
|
|
||||||
class TransformationThread(QThread):
|
class TransformationThread(QThread):
|
||||||
"""
|
"""
|
||||||
Thread für die asynchrone Ausführung von Transformations-Jobs.
|
Thread für die asynchrone Ausführung von Transformations-Jobs.
|
||||||
@@ -235,6 +491,15 @@ class MainWindow(QMainWindow):
|
|||||||
# Transformations-Thread
|
# Transformations-Thread
|
||||||
self.transformation_thread = None
|
self.transformation_thread = None
|
||||||
|
|
||||||
|
# Batch-Processing-Thread für XML-Dateien
|
||||||
|
self.batch_processing_thread = None
|
||||||
|
|
||||||
|
# Progressbar für Batch-Verarbeitung in Statusbar
|
||||||
|
self.batch_progress_bar = None
|
||||||
|
|
||||||
|
# Progressbar für Transformationen in Statusbar
|
||||||
|
self.transformation_progress_bar = None
|
||||||
|
|
||||||
# Mapping: xml_file_path_str → QTreeWidgetItem (für Progress Bar und Icon Updates)
|
# Mapping: xml_file_path_str → QTreeWidgetItem (für Progress Bar und Icon Updates)
|
||||||
self.xml_item_map = {}
|
self.xml_item_map = {}
|
||||||
|
|
||||||
@@ -2440,8 +2705,8 @@ class MainWindow(QMainWindow):
|
|||||||
|
|
||||||
def _handle_multiple_xml_files_drop(self, xml_files: list):
|
def _handle_multiple_xml_files_drop(self, xml_files: list):
|
||||||
"""
|
"""
|
||||||
Verarbeitet mehrere XML-Dateien, die per Drag&Drop hinzugefügt wurden.
|
Verarbeitet mehrere XML-Dateien asynchron per Drag&Drop.
|
||||||
Unterstützt das "Alle XML-Dateien zuordnen" Feature.
|
Zeigt einen Dialog zur Auswahl der XSL-Knoten und startet dann die Batch-Verarbeitung im Hintergrund.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
xml_files: Liste von Pfaden zu XML-Dateien
|
xml_files: Liste von Pfaden zu XML-Dateien
|
||||||
@@ -2455,102 +2720,162 @@ class MainWindow(QMainWindow):
|
|||||||
QMessageBox.warning(self, "Warnung", "Keine Projekt-Nodes verfügbar für die Zuordnung.")
|
QMessageBox.warning(self, "Warnung", "Keine Projekt-Nodes verfügbar für die Zuordnung.")
|
||||||
return
|
return
|
||||||
|
|
||||||
# Variablen für "Alle zuordnen" Feature
|
# Zeige Dialog für die erste Datei
|
||||||
apply_to_all = False
|
|
||||||
cached_selected_nodes = None
|
|
||||||
|
|
||||||
# Statistiken für Zusammenfassung
|
|
||||||
stats = {
|
|
||||||
"total": len(xml_files),
|
|
||||||
"processed": 0,
|
|
||||||
"new_added": 0,
|
|
||||||
"existing_added": 0,
|
|
||||||
"already_assigned": 0,
|
|
||||||
"cancelled": 0,
|
|
||||||
"errors": 0,
|
|
||||||
"error_messages": [],
|
|
||||||
"renamed_files": [],
|
|
||||||
}
|
|
||||||
|
|
||||||
# Verarbeite jede XML-Datei
|
|
||||||
for i, xml_file_path in enumerate(xml_files):
|
|
||||||
logger.debug(f"Verarbeite XML-Datei {i+1}/{len(xml_files)}: {xml_file_path}")
|
|
||||||
|
|
||||||
# Prüfe ob die Datei existiert
|
|
||||||
if not xml_file_path.exists():
|
|
||||||
stats["errors"] += 1
|
|
||||||
stats["error_messages"].append(f"{xml_file_path.name}: Datei existiert nicht")
|
|
||||||
continue
|
|
||||||
|
|
||||||
# Wenn "Alle zuordnen" aktiv ist und wir bereits eine Auswahl haben
|
|
||||||
if apply_to_all and cached_selected_nodes:
|
|
||||||
# Verwende die gecachte Auswahl
|
|
||||||
result = self._assign_xml_to_xsl_nodes(xml_file_path, cached_selected_nodes)
|
|
||||||
self._update_stats(stats, result)
|
|
||||||
continue
|
|
||||||
|
|
||||||
# Zeige den Dialog für die erste Datei oder wenn "Alle zuordnen" nicht aktiv ist
|
|
||||||
dialog = XmlToXslAssignDialog(
|
dialog = XmlToXslAssignDialog(
|
||||||
parent=self, xml_file_path=xml_file_path, project_nodes=self.pdf_project.nodes
|
parent=self, xml_file_path=xml_files[0], project_nodes=self.pdf_project.nodes
|
||||||
)
|
)
|
||||||
|
|
||||||
if dialog.exec() == XmlToXslAssignDialog.DialogCode.Accepted:
|
if dialog.exec() != XmlToXslAssignDialog.DialogCode.Accepted:
|
||||||
|
logger.debug("Dialog abgebrochen - keine Dateien verarbeitet")
|
||||||
|
return
|
||||||
|
|
||||||
# Hole die ausgewählten XSL-Knoten
|
# Hole die ausgewählten XSL-Knoten
|
||||||
selected_xsl_nodes = dialog.get_selected_xsl_nodes()
|
selected_xsl_nodes = dialog.get_selected_xsl_nodes()
|
||||||
|
|
||||||
if selected_xsl_nodes:
|
if not selected_xsl_nodes:
|
||||||
# Verarbeite die Zuordnung
|
logger.warning("Keine XSL-Knoten ausgewählt")
|
||||||
result = self._assign_xml_to_xsl_nodes(xml_file_path, selected_xsl_nodes)
|
return
|
||||||
self._update_stats(stats, result)
|
|
||||||
|
|
||||||
# Prüfe ob "Alle zuordnen" aktiviert wurde
|
# Prüfe ob "Alle zuordnen" aktiviert wurde
|
||||||
if dialog.is_apply_to_all() and i < len(xml_files) - 1:
|
apply_to_all = dialog.is_apply_to_all()
|
||||||
# Es gibt noch weitere Dateien und User möchte alle zuordnen
|
|
||||||
apply_to_all = True
|
# Bestimme welche Dateien verarbeitet werden sollen
|
||||||
cached_selected_nodes = selected_xsl_nodes
|
files_to_process = xml_files if apply_to_all else [xml_files[0]]
|
||||||
logger.info(
|
|
||||||
f"'Alle zuordnen' aktiviert - {len(xml_files) - i - 1} weitere Datei(en) werden automatisch zugeordnet"
|
# Stoppe vorherigen Batch-Thread falls noch aktiv
|
||||||
|
if self.batch_processing_thread and self.batch_processing_thread.isRunning():
|
||||||
|
self.batch_processing_thread.quit()
|
||||||
|
self.batch_processing_thread.wait()
|
||||||
|
|
||||||
|
# Erstelle und starte neuen Batch-Verarbeitungs-Thread
|
||||||
|
self.batch_processing_thread = XmlBatchProcessingThread(
|
||||||
|
xml_files=files_to_process,
|
||||||
|
selected_xsl_nodes=selected_xsl_nodes,
|
||||||
|
project_dir=Path(self.project.project_dir),
|
||||||
|
pdf_project=self.pdf_project,
|
||||||
)
|
)
|
||||||
else:
|
|
||||||
logger.warning("Keine XSL-Knoten ausgewählt")
|
# Verbinde Signale
|
||||||
else:
|
self.batch_processing_thread.progress_update.connect(self._on_batch_progress_update)
|
||||||
# Dialog wurde abgebrochen - beende die Verarbeitung
|
self.batch_processing_thread.processing_finished.connect(self._on_batch_processing_finished)
|
||||||
stats["cancelled"] = len(xml_files) - i
|
|
||||||
logger.debug(f"Dialog abgebrochen - {stats['cancelled']} Datei(en) übersprungen")
|
# Zeige Progressbar
|
||||||
break
|
self._show_batch_progress_bar(len(files_to_process))
|
||||||
|
|
||||||
|
# Starte Thread
|
||||||
|
self.batch_processing_thread.start()
|
||||||
|
|
||||||
|
logger.info(
|
||||||
|
f"Batch-Verarbeitung von {len(files_to_process)} Datei(en) gestartet (apply_to_all={apply_to_all})"
|
||||||
|
)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
error_msg = f"Fehler beim Starten der Batch-Verarbeitung: {str(e)}"
|
||||||
|
logger.error(error_msg)
|
||||||
|
QMessageBox.critical(self, "Fehler", error_msg)
|
||||||
|
|
||||||
|
def _show_batch_progress_bar(self, total_files: int):
|
||||||
|
"""
|
||||||
|
Zeigt einen Progressbar in der Statusbar für die Batch-Verarbeitung.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
total_files: Gesamtanzahl der zu verarbeitenden Dateien
|
||||||
|
"""
|
||||||
|
if self.batch_progress_bar is None:
|
||||||
|
self.batch_progress_bar = QProgressBar()
|
||||||
|
self.batch_progress_bar.setMaximumHeight(20)
|
||||||
|
self.batch_progress_bar.setMaximumWidth(300)
|
||||||
|
|
||||||
|
self.batch_progress_bar.setMinimum(0)
|
||||||
|
self.batch_progress_bar.setMaximum(total_files)
|
||||||
|
self.batch_progress_bar.setValue(0)
|
||||||
|
self.batch_progress_bar.setFormat("%v/%m Dateien")
|
||||||
|
|
||||||
|
# Füge Progressbar zur Statusbar hinzu
|
||||||
|
self.statusBar().addPermanentWidget(self.batch_progress_bar)
|
||||||
|
self.batch_progress_bar.show()
|
||||||
|
|
||||||
|
def _hide_batch_progress_bar(self):
|
||||||
|
"""Versteckt und entfernt den Progressbar aus der Statusbar."""
|
||||||
|
if self.batch_progress_bar:
|
||||||
|
self.statusBar().removeWidget(self.batch_progress_bar)
|
||||||
|
self.batch_progress_bar.hide()
|
||||||
|
|
||||||
|
def _on_batch_progress_update(self, current: int, total: int, current_file: str):
|
||||||
|
"""
|
||||||
|
Wird aufgerufen wenn der Batch-Thread einen Fortschritt meldet.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
current: Aktuelle Dateinummer
|
||||||
|
total: Gesamtanzahl der Dateien
|
||||||
|
current_file: Name der aktuellen Datei
|
||||||
|
"""
|
||||||
|
if self.batch_progress_bar:
|
||||||
|
self.batch_progress_bar.setValue(current)
|
||||||
|
|
||||||
|
self.statusBar().showMessage(f"Verarbeite: {current_file} ({current}/{total})")
|
||||||
|
|
||||||
|
def _on_batch_processing_finished(self, stats: dict):
|
||||||
|
"""
|
||||||
|
Wird aufgerufen wenn die Batch-Verarbeitung abgeschlossen ist.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
stats: Statistik-Dictionary mit Verarbeitungsergebnissen
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# Verstecke Progressbar
|
||||||
|
self._hide_batch_progress_bar()
|
||||||
|
|
||||||
|
# Speichere Projekt-Einstellungen
|
||||||
|
if stats["processed"] > 0:
|
||||||
|
self._save_project_settings()
|
||||||
|
|
||||||
|
# Aktualisiere Tree
|
||||||
|
self._load_nodes_to_tree()
|
||||||
|
|
||||||
# Zeige Zusammenfassungsdialog
|
# Zeige Zusammenfassungsdialog
|
||||||
self._show_drop_summary_dialog(stats)
|
self._show_drop_summary_dialog(stats)
|
||||||
|
|
||||||
except Exception as e:
|
# Statusbar-Nachricht
|
||||||
error_msg = f"Fehler beim Verarbeiten der XML-Dateien: {str(e)}"
|
self.statusBar().showMessage(
|
||||||
logger.error(error_msg)
|
f"Batch-Verarbeitung abgeschlossen: {stats['processed']}/{stats['total']} Dateien", 5000
|
||||||
QMessageBox.critical(self, "Fehler", error_msg)
|
)
|
||||||
|
|
||||||
def _update_stats(self, stats: dict, result: dict):
|
except Exception as e:
|
||||||
|
logger.error(f"Fehler beim Abschließen der Batch-Verarbeitung: {e}")
|
||||||
|
|
||||||
|
def _show_transformation_progress_bar(self, total_jobs: int):
|
||||||
"""
|
"""
|
||||||
Aktualisiert die Statistiken basierend auf dem Verarbeitungsergebnis.
|
Zeigt einen Progressbar in der Statusbar für Transformationen.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
stats: Statistik-Dictionary
|
total_jobs: Gesamtanzahl der Transformations-Jobs
|
||||||
result: Ergebnis von _assign_xml_to_xsl_nodes
|
|
||||||
"""
|
"""
|
||||||
stats["processed"] += 1
|
if self.transformation_progress_bar is None:
|
||||||
|
self.transformation_progress_bar = QProgressBar()
|
||||||
|
self.transformation_progress_bar.setMaximumHeight(20)
|
||||||
|
self.transformation_progress_bar.setMaximumWidth(300)
|
||||||
|
|
||||||
status = result.get("status")
|
self.transformation_progress_bar.setMinimum(0)
|
||||||
if status == "new_added":
|
self.transformation_progress_bar.setMaximum(total_jobs)
|
||||||
stats["new_added"] += 1
|
self.transformation_progress_bar.setValue(0)
|
||||||
if result.get("renamed_from"):
|
self.transformation_progress_bar.setFormat("%v/%m Jobs")
|
||||||
stats["renamed_files"].append(f"{result['renamed_from']} → {result['new_file']}")
|
|
||||||
elif status == "existing_added":
|
# Füge Progressbar zur Statusbar hinzu
|
||||||
stats["existing_added"] += 1
|
self.statusBar().addPermanentWidget(self.transformation_progress_bar)
|
||||||
elif status == "already_assigned":
|
self.transformation_progress_bar.show()
|
||||||
stats["already_assigned"] += 1
|
|
||||||
elif status == "cancelled":
|
def _hide_transformation_progress_bar(self):
|
||||||
stats["cancelled"] += 1
|
"""Versteckt und entfernt den Transformation-Progressbar aus der Statusbar."""
|
||||||
elif status == "error":
|
if self.transformation_progress_bar:
|
||||||
stats["errors"] += 1
|
self.statusBar().removeWidget(self.transformation_progress_bar)
|
||||||
stats["error_messages"].append(result.get("error_msg", "Unbekannter Fehler"))
|
self.transformation_progress_bar.hide()
|
||||||
|
|
||||||
|
def _update_transformation_progress(self):
|
||||||
|
"""Aktualisiert den Transformation-Progressbar um einen Schritt."""
|
||||||
|
if self.transformation_progress_bar:
|
||||||
|
current_value = self.transformation_progress_bar.value()
|
||||||
|
self.transformation_progress_bar.setValue(current_value + 1)
|
||||||
|
|
||||||
def _show_drop_summary_dialog(self, stats: dict):
|
def _show_drop_summary_dialog(self, stats: dict):
|
||||||
"""
|
"""
|
||||||
@@ -3609,6 +3934,9 @@ class MainWindow(QMainWindow):
|
|||||||
self.transformation_thread.job_error.connect(self._on_transformation_job_error)
|
self.transformation_thread.job_error.connect(self._on_transformation_job_error)
|
||||||
self.transformation_thread.all_jobs_finished.connect(self._on_all_transformations_finished)
|
self.transformation_thread.all_jobs_finished.connect(self._on_all_transformations_finished)
|
||||||
|
|
||||||
|
# Zeige Progressbar
|
||||||
|
self._show_transformation_progress_bar(len(jobs))
|
||||||
|
|
||||||
# Starte Thread
|
# Starte Thread
|
||||||
self.transformation_thread.start()
|
self.transformation_thread.start()
|
||||||
|
|
||||||
@@ -3679,6 +4007,9 @@ class MainWindow(QMainWindow):
|
|||||||
Args:
|
Args:
|
||||||
result: Ergebnis-Dictionary
|
result: Ergebnis-Dictionary
|
||||||
"""
|
"""
|
||||||
|
# Aktualisiere Transformation-Progressbar
|
||||||
|
self._update_transformation_progress()
|
||||||
|
|
||||||
xml_file = result.get("xml_file", "?")
|
xml_file = result.get("xml_file", "?")
|
||||||
success = result.get("success", False)
|
success = result.get("success", False)
|
||||||
duration = result.get("duration", 0)
|
duration = result.get("duration", 0)
|
||||||
@@ -3734,6 +4065,9 @@ class MainWindow(QMainWindow):
|
|||||||
xsl_id_str: XSL-ID als String
|
xsl_id_str: XSL-ID als String
|
||||||
error_message: Fehlermeldung
|
error_message: Fehlermeldung
|
||||||
"""
|
"""
|
||||||
|
# Aktualisiere Transformation-Progressbar
|
||||||
|
self._update_transformation_progress()
|
||||||
|
|
||||||
logger.error(f"Transformation-Fehler bei {xml_file_name} (XSL-ID: {xsl_id_str}): {error_message}")
|
logger.error(f"Transformation-Fehler bei {xml_file_name} (XSL-ID: {xsl_id_str}): {error_message}")
|
||||||
QMessageBox.critical(self, "Fehler", f"Fehler bei {xml_file_name}:\n{error_message}")
|
QMessageBox.critical(self, "Fehler", f"Fehler bei {xml_file_name}:\n{error_message}")
|
||||||
|
|
||||||
@@ -3754,6 +4088,9 @@ class MainWindow(QMainWindow):
|
|||||||
"""
|
"""
|
||||||
logger.info(f"Alle Transformationen abgeschlossen: {successful_count}/{total_count} erfolgreich")
|
logger.info(f"Alle Transformationen abgeschlossen: {successful_count}/{total_count} erfolgreich")
|
||||||
|
|
||||||
|
# Verstecke Transformation-Progressbar
|
||||||
|
self._hide_transformation_progress_bar()
|
||||||
|
|
||||||
# Aktualisiere Diff-PDF-Anzahl und Icons in allen Knoten
|
# Aktualisiere Diff-PDF-Anzahl und Icons in allen Knoten
|
||||||
self._update_all_diff_pdf_counts()
|
self._update_all_diff_pdf_counts()
|
||||||
self._update_diff_icons_for_existing_pdfs()
|
self._update_diff_icons_for_existing_pdfs()
|
||||||
|
|||||||
Reference in New Issue
Block a user