Refactor: Code-Duplikation reduziert und Dead Code entfernt

- blake2b-Hash-Berechnung in zentrale Utility-Funktion extrahiert (src/utils.py) mit chunk-basiertem Hashing für bessere RAM-Effizienz
- _transform_all_xml_files und _transform_all_xml_files_force zu einer Methode mit force-Parameter zusammengeführt
- Project-Lookup-Methoden (getXsl, getJavaVm, etc.) über gemeinsame _lookup()-Hilfsmethode konsolidiert
- Duplizierte XML-Sammel-Methoden entfernt, Set-basierte Duplikatsprüfung eingeführt
- Ungenutzte Imports, Dead Code und wirkungslose Ausdrücke entfernt

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-08 20:21:02 +01:00
parent 6fe61b9a42
commit cb90f9e483
10 changed files with 99 additions and 288 deletions
+12 -24
View File
@@ -104,41 +104,29 @@ class Project(BaseModel):
postgre_sql_db_id: int = Field(..., description="ID der PostgreSQL Datenbank", gt=0) postgre_sql_db_id: int = Field(..., description="ID der PostgreSQL Datenbank", gt=0)
fop_config_dir: Path | None = Field(None, description="Optionaler Pfad zum Apache FOP Config-Verzeichnis") fop_config_dir: Path | None = Field(None, description="Optionaler Pfad zum Apache FOP Config-Verzeichnis")
def getXsl(self) -> str: @staticmethod
global app_settings def _lookup(collection, item_id: int, attr: str) -> str:
value = [x.name for x in app_settings.xsl_dirs if x.id == self.xsl_dir_id] """Sucht einen Wert in einer Konfigurationsliste anhand der ID."""
value = [getattr(x, attr) for x in collection if x.id == item_id]
return value[0] if value else ""
return value[0] if len(value) else "" def getXsl(self) -> str:
return self._lookup(app_settings.xsl_dirs, self.xsl_dir_id, "name")
def getJavaVm(self) -> str: def getJavaVm(self) -> str:
global app_settings return self._lookup(app_settings.java_vms, self.java_vm_id, "version")
value = [x.version for x in app_settings.java_vms if x.id == self.java_vm_id]
return value[0] if len(value) else ""
def getSaxon(self) -> str: def getSaxon(self) -> str:
global app_settings return self._lookup(app_settings.saxon_jars, self.saxon_jar_id, "version")
value = [x.version for x in app_settings.saxon_jars if x.id == self.saxon_jar_id]
return value[0] if len(value) else ""
def getApacheFop(self) -> str: def getApacheFop(self) -> str:
global app_settings return self._lookup(app_settings.apache_fops, self.apache_fop_id, "version")
value = [x.version for x in app_settings.apache_fops if x.id == self.apache_fop_id]
return value[0] if len(value) else ""
def getDiffPdf(self) -> str: def getDiffPdf(self) -> str:
global app_settings return self._lookup(app_settings.diff_pdfs, self.diff_pdf_id, "version")
value = [x.version for x in app_settings.diff_pdfs if x.id == self.diff_pdf_id]
return value[0] if len(value) else ""
def getPostgreSqlDb(self) -> str: def getPostgreSqlDb(self) -> str:
global app_settings return self._lookup(app_settings.postgresql_dbs, self.postgre_sql_db_id, "name")
value = [x.name for x in app_settings.postgresql_dbs if x.id == self.postgre_sql_db_id]
return value[0] if len(value) else ""
class AppSettings(BaseSettings): class AppSettings(BaseSettings):
+1 -4
View File
@@ -11,7 +11,6 @@ import threading
import time import time
import psutil import psutil
from pathlib import Path from pathlib import Path
from queue import Queue
from typing import Optional from typing import Optional
import tempfile import tempfile
@@ -196,10 +195,8 @@ class FopWorkerPool:
self.fop_config_file = fop_config_file self.fop_config_file = fop_config_file
self.log_dir = log_dir self.log_dir = log_dir
# Worker-Prozesse und Queues # Worker-Prozesse
self.workers: list[subprocess.Popen] = [] self.workers: list[subprocess.Popen] = []
self.job_queue: Queue = Queue()
self.result_queue: Queue = Queue()
self.worker_locks: list[threading.Lock] = [] self.worker_locks: list[threading.Lock] = []
# Temporäres Verzeichnis für kompilierte Java-Klasse # Temporäres Verzeichnis für kompilierte Java-Klasse
+1 -4
View File
@@ -11,7 +11,6 @@ import threading
import time import time
import psutil import psutil
from pathlib import Path from pathlib import Path
from queue import Queue
from typing import Optional from typing import Optional
import tempfile import tempfile
@@ -207,10 +206,8 @@ class SaxonWorkerPool:
self.classpath_cache = classpath_cache self.classpath_cache = classpath_cache
self.log_dir = log_dir self.log_dir = log_dir
# Worker-Prozesse und Queues # Worker-Prozesse
self.workers: list[subprocess.Popen] = [] self.workers: list[subprocess.Popen] = []
self.job_queue: Queue = Queue()
self.result_queue: Queue = Queue()
self.worker_locks: list[threading.Lock] = [] self.worker_locks: list[threading.Lock] = []
# Temporäres Verzeichnis für kompilierte Java-Klasse # Temporäres Verzeichnis für kompilierte Java-Klasse
+1 -4
View File
@@ -12,7 +12,6 @@ import threading
import time import time
import psutil import psutil
from pathlib import Path from pathlib import Path
from queue import Queue
from typing import Optional from typing import Optional
import tempfile import tempfile
@@ -188,10 +187,8 @@ class SaxonWorkerPoolS9Api:
self.classpath_cache = classpath_cache self.classpath_cache = classpath_cache
self.log_dir = log_dir self.log_dir = log_dir
# Worker-Prozesse und Queues # Worker-Prozesse
self.workers: list[subprocess.Popen] = [] self.workers: list[subprocess.Popen] = []
self.job_queue: Queue = Queue()
self.result_queue: Queue = Queue()
self.worker_locks: list[threading.Lock] = [] self.worker_locks: list[threading.Lock] = []
# Temporäres Verzeichnis für kompilierte Java-Klasse # Temporäres Verzeichnis für kompilierte Java-Klasse
-47
View File
@@ -291,50 +291,3 @@ class PdfProjectDlg(QDialog):
self.project_data = project_data self.project_data = project_data
self._load_project_data() self._load_project_data()
# Convenience-Funktionen für einfache Verwendung
def create_project_dialog(parent=None):
"""
Erstellt einen neuen Projekt-Dialog für ein neues Projekt.
Args:
parent: Übergeordnetes Widget
Returns:
PdfProjectDlg: Der Dialog
"""
return PdfProjectDlg(parent)
def edit_project_dialog(parent=None, project_data=None):
"""
Erstellt einen Projekt-Dialog zum Bearbeiten eines bestehenden Projekts.
Args:
parent: Übergeordnetes Widget
project_data: Bestehende Projektdaten
Returns:
PdfProjectDlg: Der Dialog
"""
return PdfProjectDlg(parent, project_data)
def show_project_dialog(parent=None, project_data=None):
"""
Zeigt einen Projekt-Dialog an und gibt die Ergebnisse zurück.
Args:
parent: Übergeordnetes Widget
project_data: Bestehende Projektdaten (optional)
Returns:
tuple: (accepted: bool, project_data: dict)
"""
dialog = PdfProjectDlg(parent, project_data)
result = dialog.exec()
if result == QDialog.DialogCode.Accepted:
return True, dialog.get_project_data()
else:
return False, None
-3
View File
@@ -12,12 +12,9 @@ from PySide6.QtWidgets import (
QGroupBox, QGroupBox,
QLabel, QLabel,
QPushButton, QPushButton,
QTextEdit,
QTabWidget, QTabWidget,
QWidget, QWidget,
) )
from PySide6.QtCore import Qt
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
+13 -85
View File
@@ -5,9 +5,8 @@ Dieses Mixin enthält alle Methoden zur blake2b-Hash-Berechnung,
XML-Datei-Zuordnung und Duplikatserkennung für das MainWindow. XML-Datei-Zuordnung und Duplikatserkennung für das MainWindow.
""" """
import time
import hashlib
import shutil import shutil
import time
import logging import logging
from pathlib import Path from pathlib import Path
from typing import List from typing import List
@@ -17,6 +16,7 @@ from PySide6.QtWidgets import QMessageBox
from conf import TreeNode, XslFile, XmlFile from conf import TreeNode, XslFile, XmlFile
from ui.XmlToXslAssignDialog import XmlToXslAssignDialog from ui.XmlToXslAssignDialog import XmlToXslAssignDialog
from ui.threads import XmlHashCalculatorThread from ui.threads import XmlHashCalculatorThread
from utils import calculate_blake2b_hash
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -185,14 +185,14 @@ class HashCalculationMixin:
nodes: Liste der zu durchsuchenden Nodes nodes: Liste der zu durchsuchenden Nodes
xml_files: Liste zum Sammeln der XML-Dateien xml_files: Liste zum Sammeln der XML-Dateien
""" """
seen_paths = {xf.xml for xf in xml_files}
for node in nodes: for node in nodes:
if isinstance(node, XslFile) and node.xmls: if isinstance(node, XslFile) and node.xmls:
# Füge alle XML-Dateien dieser XSL-Datei hinzu
for xml_file in node.xmls: for xml_file in node.xmls:
if xml_file not in xml_files: # Vermeide Duplikate if xml_file.xml not in seen_paths:
xml_files.append(xml_file) xml_files.append(xml_file)
seen_paths.add(xml_file.xml)
elif isinstance(node, TreeNode) and node.children: elif isinstance(node, TreeNode) and node.children:
# Rekursiv in Kinder-Nodes suchen
self._collect_xml_files_recursive(node.children, xml_files) self._collect_xml_files_recursive(node.children, xml_files)
def _on_hash_calculated(self, xml_file: XmlFile, hash_value: str): def _on_hash_calculated(self, xml_file: XmlFile, hash_value: str):
@@ -253,68 +253,22 @@ class HashCalculationMixin:
logger.debug(f"Hash bereits vorhanden für {xml_file.xml}: {xml_file.hashsum}") logger.debug(f"Hash bereits vorhanden für {xml_file.xml}: {xml_file.hashsum}")
return return
# Prüfe ob Projekt verfügbar ist
if not self.project or not self.project.project_dir: if not self.project or not self.project.project_dir:
logger.warning("Kein Projekt-Verzeichnis für Hash-Berechnung verfügbar") logger.warning("Kein Projekt-Verzeichnis für Hash-Berechnung verfügbar")
return return
xml_file_path = Path(self.project.project_dir) / xml_file.xml xml_file_path = Path(self.project.project_dir) / xml_file.xml
hash_value = calculate_blake2b_hash(xml_file_path)
if not xml_file_path.exists(): if hash_value:
logger.warning(f"XML-Datei nicht gefunden: {xml_file_path}") xml_file.hashsum = hash_value
return logger.debug(f"Hash berechnet für {xml_file.xml}: {xml_file.hashsum}")
# 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: except Exception as e:
logger.error(f"Fehler beim Berechnen des Hash für {xml_file.xml}: {e}") logger.error(f"Fehler beim Berechnen des Hash für {xml_file.xml}: {e}")
def _get_all_project_xml_files(self) -> List[XmlFile]: def _get_all_project_xml_files(self) -> List[XmlFile]:
""" """Sammelt alle XmlFile-Objekte aus dem gesamten Projekt."""
Sammelt alle XmlFile-Objekte aus dem gesamten Projekt für Hash-Vergleiche. return self._collect_all_xml_files()
Returns:
List[XmlFile]: Liste aller XML-Dateien im Projekt
"""
xml_files = []
try:
if self.pdf_project and self.pdf_project.nodes:
self._collect_xml_files_for_hash_comparison(self.pdf_project.nodes, xml_files)
logger.debug(f"Hash-Vergleich: {len(xml_files)} XML-Dateien im Projekt gefunden")
return xml_files
except Exception as e:
logger.error(f"Fehler beim Sammeln der XML-Dateien für Hash-Vergleich: {e}")
return []
def _collect_xml_files_for_hash_comparison(self, nodes, xml_files: List[XmlFile]):
"""
Sammelt rekursiv alle XML-Dateien aus den Nodes für Hash-Vergleiche.
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:
# Vermeide Duplikate basierend auf Pfad
if not any(existing.xml == xml_file.xml for existing in xml_files):
xml_files.append(xml_file)
elif isinstance(node, TreeNode) and node.children:
# Rekursiv in Kinder-Nodes suchen
self._collect_xml_files_for_hash_comparison(node.children, xml_files)
def _find_xml_file_by_hash(self, target_hash: str) -> XmlFile | None: def _find_xml_file_by_hash(self, target_hash: str) -> XmlFile | None:
""" """
@@ -410,34 +364,8 @@ class HashCalculationMixin:
return True # Im Zweifelsfall annehmen, dass der Name verwendet wird return True # Im Zweifelsfall annehmen, dass der Name verwendet wird
def _calculate_hash_for_file(self, file_path: Path) -> str | None: def _calculate_hash_for_file(self, file_path: Path) -> str | None:
""" """Berechnet synchron den blake2b-Hash für eine Datei."""
Berechnet synchron den blake2b-Hash für eine Datei. return calculate_blake2b_hash(file_path)
Args:
file_path: Pfad zur Datei
Returns:
str|None: Hash-Wert mit blake2b: Präfix oder None bei Fehler
"""
try:
if not file_path.exists():
logger.warning(f"Datei für Hash-Berechnung 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()
# Hash mit Präfix zurückgeben
hash_value = f"blake2b:{hash_hex}"
logger.debug(f"Hash berechnet für {file_path}: {hash_value}")
return hash_value
except Exception as e:
logger.error(f"Fehler beim Berechnen des Hash für {file_path}: {e}")
return None
def _assign_existing_xml_to_nodes(self, existing_xml: XmlFile, selected_xsl_nodes: list): def _assign_existing_xml_to_nodes(self, existing_xml: XmlFile, selected_xsl_nodes: list):
""" """
+28 -73
View File
@@ -579,9 +579,6 @@ class TransformationMixin:
# Progress Bar anzeigen # Progress Bar anzeigen
map_key = f"{xml_file_name}|{xsl_id_str}" map_key = f"{xml_file_name}|{xsl_id_str}"
if map_key not in self.xml_item_map and self.xml_item_map:
# Zeige erste Keys zur Diagnose
list(self.xml_item_map.keys())[:3]
logger.info(f"Suche TreeWidget-Item für: '{map_key}'") logger.info(f"Suche TreeWidget-Item für: '{map_key}'")
logger.info(f"Map hat {len(self.xml_item_map)} Einträge") logger.info(f"Map hat {len(self.xml_item_map)} Einträge")
tree_item = self.xml_item_map.get(map_key) tree_item = self.xml_item_map.get(map_key)
@@ -741,10 +738,12 @@ class TransformationMixin:
f"{successful_count} von {total_count} Transformationen erfolgreich\n{failed_count} fehlgeschlagen\n\nGesamtdauer: {duration_str}", f"{successful_count} von {total_count} Transformationen erfolgreich\n{failed_count} fehlgeschlagen\n\nGesamtdauer: {duration_str}",
) )
def _transform_all_xml_files(self): def _transform_all_xml_files(self, force: bool = False):
""" """
Transformiert ALLE XML-Dateien in allen TreeNodes des TreeWidgets. Transformiert ALLE XML-Dateien in allen TreeNodes des TreeWidgets.
Nur Dateien, die nicht up-to-date sind, werden transformiert.
Args:
force: Wenn True, werden alle Dateien unabhängig vom Änderungsstatus neu transformiert.
""" """
try: try:
if not self.project or not self.pdf_project: if not self.project or not self.pdf_project:
@@ -781,87 +780,43 @@ class TransformationMixin:
return return
# Frage Benutzer um Bestätigung # Frage Benutzer um Bestätigung
if force:
title = "Alle XML-Dateien neu transformieren (force)"
message = (
f"Möchten Sie wirklich ALLE {len(all_jobs)} XML-Dateien NEU transformieren?\n\n"
f"⚠ WARNUNG: Im Force-Modus werden alle Dateien unabhängig von ihrem Status neu verarbeitet!\n"
f"Dies kann längere Zeit in Anspruch nehmen."
)
default_button = QMessageBox.StandardButton.No
else:
title = "Alle XML-Dateien transformieren"
message = (
f"Möchten Sie wirklich alle {len(all_jobs)} XML-Dateien transformieren?\n\n"
f"Nur Dateien mit Änderungen werden verarbeitet."
)
default_button = QMessageBox.StandardButton.Yes
reply = QMessageBox.question( reply = QMessageBox.question(
self, self,
"Alle XML-Dateien transformieren", title,
f"Möchten Sie wirklich alle {len(all_jobs)} XML-Dateien transformieren?\n\n" message,
f"Nur Dateien mit Änderungen werden verarbeitet.",
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No, QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
QMessageBox.StandardButton.Yes, default_button,
) )
if reply != QMessageBox.StandardButton.Yes: if reply != QMessageBox.StandardButton.Yes:
logger.info("Transformation abgebrochen durch Benutzer") logger.info(f"{'Force-' if force else ''}Transformation abgebrochen durch Benutzer")
return return
logger.info(f"Starte Transformation für alle {len(all_jobs)} XML-Dateien (nicht-force)") logger.info(f"Starte {'Force-' if force else ''}Transformation für alle {len(all_jobs)} XML-Dateien")
# Starte Transformation in separatem Thread # Starte Transformation in separatem Thread
self._start_transformation(all_jobs, force=False) self._start_transformation(all_jobs, force=force)
except Exception as e: except Exception as e:
logger.error(f"Fehler beim Transformieren aller XML-Dateien: {e}") logger.error(f"Fehler beim Transformieren aller XML-Dateien: {e}")
QMessageBox.critical(self, "Fehler", f"Fehler beim Starten der Transformation: {str(e)}") QMessageBox.critical(self, "Fehler", f"Fehler beim Starten der Transformation: {str(e)}")
def _transform_all_xml_files_force(self): def _transform_all_xml_files_force(self):
""" """Transformiert ALLE XML-Dateien im Force-Modus."""
Transformiert ALLE XML-Dateien in allen TreeNodes des TreeWidgets (force). self._transform_all_xml_files(force=True)
Alle Dateien werden unabhängig vom Änderungsstatus neu transformiert.
"""
try:
if not self.project or not self.pdf_project:
QMessageBox.warning(self, "Fehler", "Kein Projekt geladen")
return
# Sammle alle XSL/XML-Paare aus allen Root-Nodes
all_jobs = []
root = self.ui.treeWidget.invisibleRootItem()
for i in range(root.childCount()):
root_item = root.child(i)
root_node = root_item.data(0, Qt.ItemDataRole.UserRole)
if isinstance(root_node, TreeNode):
# Sammle alle XSL/XML-Paare rekursiv
xsl_xml_pairs = self._collect_all_xsl_xml_pairs_recursive(root_node, root_item)
# Erstelle TransformationJobs
for xsl_file_obj, xml_file_obj, xsl_file_item in xsl_xml_pairs:
job = self._create_transformation_job(xsl_file_obj, xml_file_obj, xsl_file_item)
if job:
all_jobs.append(job)
elif isinstance(root_node, XslFile):
# Direkt XslFile als Root-Element
for xml_file_obj in root_node.xmls:
job = self._create_transformation_job(root_node, xml_file_obj, root_item)
if job:
all_jobs.append(job)
if not all_jobs:
QMessageBox.information(self, "Info", "Keine XML-Dateien zum Transformieren gefunden")
return
# Frage Benutzer um Bestätigung (mit Warnung wegen force)
reply = QMessageBox.question(
self,
"Alle XML-Dateien neu transformieren (force)",
f"Möchten Sie wirklich ALLE {len(all_jobs)} XML-Dateien NEU transformieren?\n\n"
f"⚠ WARNUNG: Im Force-Modus werden alle Dateien unabhängig von ihrem Status neu verarbeitet!\n"
f"Dies kann längere Zeit in Anspruch nehmen.",
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
QMessageBox.StandardButton.No,
)
if reply != QMessageBox.StandardButton.Yes:
logger.info("Force-Transformation abgebrochen durch Benutzer")
return
logger.info(f"Starte Force-Transformation für alle {len(all_jobs)} XML-Dateien")
# Starte Transformation in separatem Thread (mit force=True)
self._start_transformation(all_jobs, force=True)
except Exception as e:
logger.error(f"Fehler beim Force-Transformieren aller XML-Dateien: {e}")
QMessageBox.critical(self, "Fehler", f"Fehler beim Starten der Force-Transformation: {str(e)}")
+5 -44
View File
@@ -7,16 +7,14 @@ Dieses Modul enthält alle QThread-Klassen, die für Hintergrundoperationen verw
- TransformationThread: Ausführung von XSL-Transformationen - TransformationThread: Ausführung von XSL-Transformationen
""" """
import hashlib
import logging import logging
import shutil import shutil
from pathlib import Path from pathlib import Path
from typing import List
from PySide6.QtCore import QThread, Signal from PySide6.QtCore import QThread, Signal
from conf import TreeNode, XslFile, XmlFile from conf import TreeNode, XslFile, XmlFile
from transform import TransformationJob from transform import TransformationJob
from utils import calculate_blake2b_hash
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -32,7 +30,7 @@ class XmlHashCalculatorThread(QThread):
calculation_finished = Signal(int, int) # processed_count, total_count calculation_finished = Signal(int, int) # processed_count, total_count
error_occurred = Signal(str, str) # xml_file_path, error_message error_occurred = Signal(str, str) # xml_file_path, error_message
def __init__(self, project_dir: Path, xml_files: List[XmlFile]): def __init__(self, project_dir: Path, xml_files: list[XmlFile]):
""" """
Initialisiert den Hash-Berechnungs-Thread. Initialisiert den Hash-Berechnungs-Thread.
@@ -81,32 +79,8 @@ class XmlHashCalculatorThread(QThread):
logger.info(f"Hash-Berechnung abgeschlossen: {self.processed_count}/{len(self.xml_files)} verarbeitet") logger.info(f"Hash-Berechnung abgeschlossen: {self.processed_count}/{len(self.xml_files)} verarbeitet")
def _calculate_blake2b_hash(self, file_path: Path) -> str | None: def _calculate_blake2b_hash(self, file_path: Path) -> str | None:
""" """Berechnet den blake2b-Hash einer XML-Datei."""
Berechnet den blake2b-Hash einer XML-Datei. return calculate_blake2b_hash(file_path)
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 XmlBatchProcessingThread(QThread): class XmlBatchProcessingThread(QThread):
@@ -217,20 +191,7 @@ class XmlBatchProcessingThread(QThread):
def _calculate_hash_for_file(self, file_path: Path) -> str | None: def _calculate_hash_for_file(self, file_path: Path) -> str | None:
"""Berechnet blake2b Hash für eine Datei.""" """Berechnet blake2b Hash für eine Datei."""
try: return calculate_blake2b_hash(file_path)
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: def _find_xml_file_by_hash(self, hash_value: str) -> XmlFile | None:
"""Sucht eine XML-Datei anhand ihres Hash-Werts.""" """Sucht eine XML-Datei anhand ihres Hash-Werts."""
+38
View File
@@ -0,0 +1,38 @@
"""
Gemeinsame Utility-Funktionen für DocuMentor.
"""
import hashlib
import logging
from pathlib import Path
logger = logging.getLogger(__name__)
HASH_PREFIX = "blake2b"
def calculate_blake2b_hash(file_path: Path) -> str | None:
"""
Berechnet den blake2b-Hash einer Datei.
Args:
file_path: Pfad zur Datei
Returns:
str: Hash-Wert mit "blake2b:" Präfix oder None bei Fehler
"""
try:
if not file_path.exists():
logger.warning(f"Datei für Hash-Berechnung nicht gefunden: {file_path}")
return None
hash_obj = hashlib.blake2b()
with open(file_path, "rb") as f:
for chunk in iter(lambda: f.read(8192), b""):
hash_obj.update(chunk)
return f"{HASH_PREFIX}:{hash_obj.hexdigest()}"
except Exception as e:
logger.error(f"Fehler beim Berechnen des Hash für {file_path}: {e}")
return None