Die XML-Dateien haben nun hashsummen in Projekt-Datei
This commit is contained in:
+268
-1
@@ -3,8 +3,12 @@ import os
|
||||
import time
|
||||
import polars as pl
|
||||
import shutil
|
||||
import hashlib
|
||||
import logging
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
from typing import List
|
||||
|
||||
from PySide6.QtCore import Qt, QSize
|
||||
from PySide6.QtCore import Qt, QSize, QThread, Signal
|
||||
from PySide6.QtGui import QCursor, QPixmap, QPainter, QAction, QIcon, QDragEnterEvent, QDropEvent
|
||||
from PySide6.QtWidgets import QLabel, QMainWindow, QApplication, QStyleFactory, QMenu, QTreeWidgetItem, QMessageBox, QFileDialog
|
||||
from PySide6.QtPdf import QPdfDocument
|
||||
@@ -19,6 +23,96 @@ from conf import app_settings, Project, ProjectData, TreeNode, XslFile, XmlFile
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class XmlHashCalculatorThread(QThread):
|
||||
"""
|
||||
Thread für die asynchrone Berechnung von blake2b-Hash-Werten für XML-Dateien.
|
||||
"""
|
||||
|
||||
# Signale für die Kommunikation mit dem Haupt-Thread
|
||||
hash_calculated = Signal(object, str) # xml_file_object, hash_value
|
||||
calculation_finished = Signal(int, int) # processed_count, total_count
|
||||
error_occurred = Signal(str, str) # xml_file_path, error_message
|
||||
|
||||
def __init__(self, project_dir: Path, xml_files: List[XmlFile]):
|
||||
"""
|
||||
Initialisiert den Hash-Berechnungs-Thread.
|
||||
|
||||
Args:
|
||||
project_dir: Pfad zum Projekt-Verzeichnis
|
||||
xml_files: Liste der XmlFile-Objekte für die Hash-Berechnung
|
||||
"""
|
||||
super().__init__()
|
||||
self.project_dir = project_dir
|
||||
self.xml_files = xml_files
|
||||
self.processed_count = 0
|
||||
|
||||
def run(self):
|
||||
"""
|
||||
Führt die Hash-Berechnung für alle XML-Dateien aus.
|
||||
"""
|
||||
logger.info(f"Starte Hash-Berechnung für {len(self.xml_files)} XML-Dateien")
|
||||
|
||||
for xml_file in self.xml_files:
|
||||
try:
|
||||
# Prüfe ob hashsum bereits vorhanden ist
|
||||
if xml_file.hashsum:
|
||||
logger.debug(f"Hash bereits vorhanden für {xml_file.xml}: {xml_file.hashsum}")
|
||||
self.processed_count += 1
|
||||
continue
|
||||
|
||||
# Berechne Hash für die XML-Datei
|
||||
xml_file_path = self.project_dir / xml_file.xml
|
||||
hash_value = self._calculate_blake2b_hash(xml_file_path)
|
||||
|
||||
if hash_value:
|
||||
# Sende Signal mit berechnetem Hash
|
||||
self.hash_calculated.emit(xml_file, hash_value)
|
||||
logger.debug(f"Hash berechnet für {xml_file.xml}: {hash_value}")
|
||||
|
||||
self.processed_count += 1
|
||||
|
||||
except Exception as e:
|
||||
error_msg = f"Fehler bei Hash-Berechnung für {xml_file.xml}: {str(e)}"
|
||||
logger.error(error_msg)
|
||||
self.error_occurred.emit(str(xml_file.xml), error_msg)
|
||||
self.processed_count += 1
|
||||
|
||||
# Sende Abschluss-Signal
|
||||
self.calculation_finished.emit(self.processed_count, len(self.xml_files))
|
||||
logger.info(f"Hash-Berechnung abgeschlossen: {self.processed_count}/{len(self.xml_files)} verarbeitet")
|
||||
|
||||
def _calculate_blake2b_hash(self, file_path: Path) -> str | None:
|
||||
"""
|
||||
Berechnet den blake2b-Hash einer XML-Datei.
|
||||
|
||||
Args:
|
||||
file_path: Pfad zur XML-Datei
|
||||
|
||||
Returns:
|
||||
str: Hash-Wert mit "blake2b:" Präfix oder None bei Fehler
|
||||
"""
|
||||
try:
|
||||
if not file_path.exists():
|
||||
logger.warning(f"XML-Datei nicht gefunden: {file_path}")
|
||||
return None
|
||||
|
||||
# Datei binär lesen und Hash berechnen
|
||||
with open(file_path, 'rb') as f:
|
||||
file_content = f.read()
|
||||
hash_obj = hashlib.blake2b(file_content)
|
||||
hash_hex = hash_obj.hexdigest()
|
||||
|
||||
# Präfix hinzufügen
|
||||
return f"blake2b:{hash_hex}"
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Fehler beim Berechnen des Hash für {file_path}: {e}")
|
||||
return None
|
||||
|
||||
|
||||
class MainWindow(QMainWindow):
|
||||
def __init__(self, parent=None):
|
||||
"""
|
||||
@@ -65,6 +159,9 @@ class MainWindow(QMainWindow):
|
||||
# Das aktuelle ProjectData
|
||||
self.pdf_project = None
|
||||
|
||||
# Hash-Berechnungs-Thread
|
||||
self.hash_calculator_thread = None
|
||||
|
||||
# Theme-Menü initialisieren
|
||||
self._setup_theme_menu()
|
||||
|
||||
@@ -181,6 +278,9 @@ class MainWindow(QMainWindow):
|
||||
# Lade die Nodes in das TreeWidget
|
||||
self._load_nodes_to_tree()
|
||||
|
||||
# Starte Hash-Berechnung für alle XML-Dateien
|
||||
self._start_xml_hash_calculation()
|
||||
|
||||
except Exception as e:
|
||||
print(f"Fehler beim Laden des Projekts '{project.name}': {e}")
|
||||
# Fallback: Erstelle Standard-Einstellungen
|
||||
@@ -1227,6 +1327,9 @@ class MainWindow(QMainWindow):
|
||||
|
||||
print(f"XML-Datei '{xml_file_path.name}' zu XslFile-Node '{xsl_node.bez}' hinzugefügt")
|
||||
|
||||
# Berechne Hash für die neue XML-Datei
|
||||
self._calculate_hash_for_xml_file(new_xml_file)
|
||||
|
||||
# Speichere die aktualisierten Projekt-Einstellungen
|
||||
self._save_project_settings()
|
||||
|
||||
@@ -1974,6 +2077,10 @@ class MainWindow(QMainWindow):
|
||||
if not existing_xml:
|
||||
# Erstelle neues XmlFile-Objekt und füge es zur XslFile-Node hinzu
|
||||
new_xml_file = XmlFile(xml=relative_xml_path)
|
||||
|
||||
# Berechne Hash für die neue XML-Datei
|
||||
self._calculate_hash_for_xml_file(new_xml_file)
|
||||
|
||||
xsl_node.xmls.append(new_xml_file)
|
||||
added_count += 1
|
||||
print(f"XML-Datei '{xml_file_path.name}' zu XslFile-Node '{xsl_node.bez}' hinzugefügt")
|
||||
@@ -2005,7 +2112,167 @@ class MainWindow(QMainWindow):
|
||||
print(error_msg)
|
||||
QMessageBox.critical(self, "Fehler", error_msg)
|
||||
|
||||
def _start_xml_hash_calculation(self):
|
||||
"""
|
||||
Startet die asynchrone Hash-Berechnung für alle XML-Dateien im Projekt.
|
||||
"""
|
||||
try:
|
||||
if not hasattr(self, 'pdf_project') or not self.pdf_project:
|
||||
logger.debug("Keine Projekt-Einstellungen verfügbar für Hash-Berechnung")
|
||||
return
|
||||
|
||||
# Sammle alle XML-Dateien aus dem Projekt
|
||||
xml_files = self._collect_all_xml_files()
|
||||
|
||||
if not xml_files:
|
||||
logger.debug("Keine XML-Dateien für Hash-Berechnung gefunden")
|
||||
return
|
||||
|
||||
logger.info(f"Starte Hash-Berechnung für {len(xml_files)} XML-Dateien")
|
||||
|
||||
# Stoppe vorherigen Thread falls noch aktiv
|
||||
if self.hash_calculator_thread and self.hash_calculator_thread.isRunning():
|
||||
self.hash_calculator_thread.quit()
|
||||
self.hash_calculator_thread.wait()
|
||||
|
||||
# Erstelle und starte neuen Hash-Berechnungs-Thread
|
||||
self.hash_calculator_thread = XmlHashCalculatorThread(
|
||||
project_dir=Path(self.project.project_dir),
|
||||
xml_files=xml_files
|
||||
)
|
||||
|
||||
# Verbinde Signale
|
||||
self.hash_calculator_thread.hash_calculated.connect(self._on_hash_calculated)
|
||||
self.hash_calculator_thread.calculation_finished.connect(self._on_hash_calculation_finished)
|
||||
self.hash_calculator_thread.error_occurred.connect(self._on_hash_calculation_error)
|
||||
|
||||
# Starte Thread
|
||||
self.hash_calculator_thread.start()
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Fehler beim Starten der Hash-Berechnung: {e}")
|
||||
|
||||
def _collect_all_xml_files(self) -> List[XmlFile]:
|
||||
"""
|
||||
Sammelt alle XmlFile-Objekte aus der Projektstruktur.
|
||||
|
||||
Returns:
|
||||
List[XmlFile]: Liste aller gefundenen XML-Dateien
|
||||
"""
|
||||
xml_files = []
|
||||
|
||||
try:
|
||||
if self.pdf_project and self.pdf_project.nodes:
|
||||
self._collect_xml_files_recursive(self.pdf_project.nodes, xml_files)
|
||||
|
||||
logger.debug(f"Gesammelt: {len(xml_files)} XML-Dateien")
|
||||
return xml_files
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Fehler beim Sammeln der XML-Dateien: {e}")
|
||||
return []
|
||||
|
||||
def _collect_xml_files_recursive(self, nodes, xml_files: List[XmlFile]):
|
||||
"""
|
||||
Sammelt rekursiv alle XML-Dateien aus den Nodes.
|
||||
|
||||
Args:
|
||||
nodes: Liste der zu durchsuchenden Nodes
|
||||
xml_files: Liste zum Sammeln der XML-Dateien
|
||||
"""
|
||||
for node in nodes:
|
||||
if isinstance(node, XslFile) and node.xmls:
|
||||
# Füge alle XML-Dateien dieser XSL-Datei hinzu
|
||||
for xml_file in node.xmls:
|
||||
if xml_file not in xml_files: # Vermeide Duplikate
|
||||
xml_files.append(xml_file)
|
||||
elif isinstance(node, TreeNode) and node.children:
|
||||
# Rekursiv in Kinder-Nodes suchen
|
||||
self._collect_xml_files_recursive(node.children, xml_files)
|
||||
|
||||
def _on_hash_calculated(self, xml_file: XmlFile, hash_value: str):
|
||||
"""
|
||||
Wird aufgerufen, wenn ein Hash-Wert berechnet wurde.
|
||||
|
||||
Args:
|
||||
xml_file: Das XmlFile-Objekt
|
||||
hash_value: Der berechnete Hash-Wert mit Präfix
|
||||
"""
|
||||
try:
|
||||
# Setze den Hash-Wert
|
||||
xml_file.hashsum = hash_value
|
||||
logger.debug(f"Hash gesetzt für {xml_file.xml}: {hash_value}")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Fehler beim Setzen des Hash-Werts: {e}")
|
||||
|
||||
def _on_hash_calculation_finished(self, processed_count: int, total_count: int):
|
||||
"""
|
||||
Wird aufgerufen, wenn die Hash-Berechnung abgeschlossen ist.
|
||||
|
||||
Args:
|
||||
processed_count: Anzahl der verarbeiteten Dateien
|
||||
total_count: Gesamtanzahl der Dateien
|
||||
"""
|
||||
try:
|
||||
logger.info(f"Hash-Berechnung abgeschlossen: {processed_count}/{total_count} Dateien verarbeitet")
|
||||
|
||||
# Speichere die aktualisierten Projekt-Einstellungen
|
||||
if processed_count > 0:
|
||||
self._save_project_settings()
|
||||
logger.info("Projekt-Einstellungen mit neuen Hash-Werten gespeichert")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Fehler beim Abschließen der Hash-Berechnung: {e}")
|
||||
|
||||
def _on_hash_calculation_error(self, xml_file_path: str, error_message: str):
|
||||
"""
|
||||
Wird aufgerufen, wenn ein Fehler bei der Hash-Berechnung auftritt.
|
||||
|
||||
Args:
|
||||
xml_file_path: Pfad zur XML-Datei
|
||||
error_message: Fehlermeldung
|
||||
"""
|
||||
logger.warning(f"Hash-Berechnungsfehler für {xml_file_path}: {error_message}")
|
||||
|
||||
def _calculate_hash_for_xml_file(self, xml_file: XmlFile):
|
||||
"""
|
||||
Berechnet synchron den Hash für eine einzelne XML-Datei.
|
||||
Wird verwendet beim Hinzufügen neuer XML-Dateien.
|
||||
|
||||
Args:
|
||||
xml_file: Das XmlFile-Objekt
|
||||
"""
|
||||
try:
|
||||
if xml_file.hashsum:
|
||||
logger.debug(f"Hash bereits vorhanden für {xml_file.xml}: {xml_file.hashsum}")
|
||||
return
|
||||
|
||||
xml_file_path = Path(self.project.project_dir) / xml_file.xml
|
||||
|
||||
if not xml_file_path.exists():
|
||||
logger.warning(f"XML-Datei nicht gefunden: {xml_file_path}")
|
||||
return
|
||||
|
||||
# Datei binär lesen und Hash berechnen
|
||||
with open(xml_file_path, 'rb') as f:
|
||||
file_content = f.read()
|
||||
hash_obj = hashlib.blake2b(file_content)
|
||||
hash_hex = hash_obj.hexdigest()
|
||||
|
||||
# Hash mit Präfix setzen
|
||||
xml_file.hashsum = f"blake2b:{hash_hex}"
|
||||
logger.debug(f"Hash berechnet für {xml_file.xml}: {xml_file.hashsum}")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Fehler beim Berechnen des Hash für {xml_file.xml}: {e}")
|
||||
|
||||
def closeEvent(self, event):
|
||||
"""Wird beim Schließen der Anwendung aufgerufen."""
|
||||
# Stoppe Hash-Berechnungs-Thread falls noch aktiv
|
||||
if hasattr(self, 'hash_calculator_thread') and self.hash_calculator_thread and self.hash_calculator_thread.isRunning():
|
||||
self.hash_calculator_thread.quit()
|
||||
self.hash_calculator_thread.wait()
|
||||
|
||||
# PDF-Dokumente schließen ist bei QtPdf automatisch durch Garbage Collection
|
||||
super().closeEvent(event)
|
||||
|
||||
Reference in New Issue
Block a user