Refactor: MainWindow in 7 Mixins aufgeteilt (80% Code-Reduktion)
MainWindow.py von 5025 auf 983 Zeilen reduziert durch Extraktion in: - TreeManagerMixin: Baumstruktur-Verwaltung (~1136 Zeilen) - PdfViewerMixin: PDF-Anzeige und Rendering - WorkerPoolMixin: Saxon/FOP Worker-Pool-Verwaltung - DatabaseMixin: PostgreSQL-Operationen - DragDropMixin: Drag-and-Drop für XML-Dateien - HashCalculationMixin: blake2b Hash-Berechnung - TransformationMixin: XSL-Transformationen Zusätzlich Thread-Klassen in threads.py ausgelagert. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,655 @@
|
||||
"""
|
||||
HashCalculationMixin - Mixin für Hash-Berechnung und XML-Dateiverwaltung.
|
||||
|
||||
Dieses Mixin enthält alle Methoden zur blake2b-Hash-Berechnung,
|
||||
XML-Datei-Zuordnung und Duplikatserkennung für das MainWindow.
|
||||
"""
|
||||
|
||||
import time
|
||||
import hashlib
|
||||
import shutil
|
||||
import logging
|
||||
from pathlib import Path
|
||||
from typing import List
|
||||
|
||||
from PySide6.QtWidgets import QMessageBox
|
||||
|
||||
from conf import TreeNode, XslFile, XmlFile
|
||||
from ui.XmlToXslAssignDialog import XmlToXslAssignDialog
|
||||
from ui.threads import XmlHashCalculatorThread
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class HashCalculationMixin:
|
||||
"""
|
||||
Mixin für Hash-Berechnung und XML-Dateiverwaltung.
|
||||
|
||||
Dieses Mixin wird von MainWindow verwendet und erwartet folgende Attribute:
|
||||
- self.project: Das aktuelle Projekt
|
||||
- self.pdf_project: Die Projekt-Daten (ProjectData)
|
||||
- self.hash_calculator_thread: Thread für Hash-Berechnung
|
||||
- self._save_project_settings(): Methode zum Speichern der Projekt-Einstellungen
|
||||
- self._load_nodes_to_tree(): Methode zum Laden der Nodes in den Tree
|
||||
"""
|
||||
|
||||
def _handle_xml_file_drop(self, xml_file_path: Path):
|
||||
"""
|
||||
Verarbeitet eine einzelne XML-Datei, die per Drag&Drop hinzugefügt wurde.
|
||||
DEPRECATED: Diese Methode wird durch _handle_multiple_xml_files_drop ersetzt.
|
||||
|
||||
Args:
|
||||
xml_file_path: Pfad zur XML-Datei
|
||||
"""
|
||||
try:
|
||||
logger.debug(f"Verarbeite XML-Datei: {xml_file_path}")
|
||||
|
||||
# Prüfe ob die Datei existiert
|
||||
if not xml_file_path.exists():
|
||||
QMessageBox.critical(self, "Fehler", f"Die XML-Datei existiert nicht:\n{xml_file_path}")
|
||||
return
|
||||
|
||||
# Prüfe ob Projekt-Nodes verfügbar sind
|
||||
if not self.pdf_project or not self.pdf_project.nodes:
|
||||
QMessageBox.warning(self, "Warnung", "Keine Projekt-Nodes verfügbar für die Zuordnung.")
|
||||
return
|
||||
|
||||
# Öffne den Dialog zur Zuordnung zu XSL-Knoten
|
||||
dialog = XmlToXslAssignDialog(
|
||||
parent=self, xml_file_path=xml_file_path, project_nodes=self.pdf_project.nodes
|
||||
)
|
||||
|
||||
if dialog.exec() == XmlToXslAssignDialog.DialogCode.Accepted:
|
||||
# Hole die ausgewählten XSL-Knoten
|
||||
selected_xsl_nodes = dialog.get_selected_xsl_nodes()
|
||||
|
||||
if selected_xsl_nodes:
|
||||
# Verarbeite die Zuordnung
|
||||
self._assign_xml_to_xsl_nodes(xml_file_path, selected_xsl_nodes)
|
||||
else:
|
||||
logger.warning("Keine XSL-Knoten ausgewählt")
|
||||
else:
|
||||
logger.debug("Dialog abgebrochen")
|
||||
|
||||
except Exception as e:
|
||||
error_msg = f"Fehler beim Verarbeiten der XML-Datei '{xml_file_path}': {str(e)}"
|
||||
logger.error(error_msg)
|
||||
QMessageBox.critical(self, "Fehler", error_msg)
|
||||
|
||||
def _assign_xml_to_xsl_nodes(self, xml_file_path: Path, selected_xsl_nodes: list):
|
||||
"""
|
||||
Ordnet eine XML-Datei den ausgewählten XSL-Knoten zu.
|
||||
Implementiert Hash-basierte Duplikatserkennung und intelligente Dateinamen-Verwaltung.
|
||||
|
||||
Args:
|
||||
xml_file_path: Pfad zur XML-Datei
|
||||
selected_xsl_nodes: Liste der ausgewählten XSL-Knoten
|
||||
|
||||
Returns:
|
||||
dict: Statistiken über die Verarbeitung
|
||||
"""
|
||||
try:
|
||||
logger.info(f"Ordne XML-Datei '{xml_file_path.name}' zu {len(selected_xsl_nodes)} XSL-Knoten zu")
|
||||
|
||||
# 1. Hash für die neue XML-Datei 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 ob eine XML-Datei mit gleichem Hash bereits im Projekt existiert
|
||||
existing_xml = self._find_xml_file_by_hash(file_hash) if file_hash else None
|
||||
|
||||
if existing_xml:
|
||||
# 3. Hash-Match gefunden: Ordne vorhandene XML-Datei zu
|
||||
logger.info(f"Hash-Duplikat gefunden: {existing_xml.xml} hat gleichen Hash wie {xml_file_path.name}")
|
||||
return self._assign_existing_xml_to_nodes(existing_xml, selected_xsl_nodes)
|
||||
else:
|
||||
# 4. Kein Hash-Match: Verarbeite als neue XML-Datei
|
||||
logger.info(f"Keine Hash-Duplikate gefunden für {xml_file_path.name}, verarbeite als neue Datei")
|
||||
return self._process_new_xml_file(xml_file_path, selected_xsl_nodes, file_hash)
|
||||
|
||||
except Exception as e:
|
||||
error_msg = f"Fehler beim Zuordnen der XML-Datei: {str(e)}"
|
||||
logger.error(error_msg)
|
||||
return {"status": "error", "error_msg": 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")
|
||||
|
||||
# Prüfe ob Projekt verfügbar ist
|
||||
if not self.project or not self.project.project_dir:
|
||||
logger.warning("Kein Projekt-Verzeichnis für Hash-Berechnung verfügbar")
|
||||
return
|
||||
|
||||
# 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
|
||||
|
||||
# Prüfe ob Projekt verfügbar ist
|
||||
if not self.project or not self.project.project_dir:
|
||||
logger.warning("Kein Projekt-Verzeichnis für Hash-Berechnung verfügbar")
|
||||
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 _get_all_project_xml_files(self) -> List[XmlFile]:
|
||||
"""
|
||||
Sammelt alle XmlFile-Objekte aus dem gesamten Projekt für Hash-Vergleiche.
|
||||
|
||||
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:
|
||||
"""
|
||||
Sucht eine XML-Datei mit dem angegebenen Hash im gesamten Projekt.
|
||||
|
||||
Args:
|
||||
target_hash: Der zu suchende Hash-Wert (mit blake2b: Präfix)
|
||||
|
||||
Returns:
|
||||
XmlFile|None: Die gefundene XML-Datei oder None
|
||||
"""
|
||||
try:
|
||||
if not target_hash:
|
||||
return None
|
||||
|
||||
all_xml_files = self._get_all_project_xml_files()
|
||||
|
||||
for xml_file in all_xml_files:
|
||||
if xml_file.hashsum == target_hash:
|
||||
logger.debug(f"Hash-Match gefunden: {xml_file.xml} hat Hash {target_hash}")
|
||||
return xml_file
|
||||
|
||||
logger.debug(f"Kein Hash-Match für {target_hash} gefunden")
|
||||
return None
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Fehler bei Hash-Suche für {target_hash}: {e}")
|
||||
return None
|
||||
|
||||
def _generate_alternative_filename(self, original_path: Path, xml_dir: Path) -> Path:
|
||||
"""
|
||||
Generiert alternative Dateinamen im Format: datei_1.xml, datei_2.xml, ...
|
||||
|
||||
Args:
|
||||
original_path: Ursprünglicher Dateipfad
|
||||
xml_dir: Ziel-XML-Verzeichnis
|
||||
|
||||
Returns:
|
||||
Path: Pfad mit alternativem Dateinamen
|
||||
"""
|
||||
try:
|
||||
base_name = original_path.stem # "datei"
|
||||
extension = original_path.suffix # ".xml"
|
||||
|
||||
# Sammle einmalig alle verwendeten Dateinamen (Performance-Optimierung)
|
||||
all_xml_files = self._get_all_project_xml_files()
|
||||
used_names = {xml_file.xml.name for xml_file in all_xml_files}
|
||||
|
||||
counter = 1
|
||||
while True:
|
||||
new_name = f"{base_name}_{counter}{extension}"
|
||||
new_path = xml_dir / new_name
|
||||
|
||||
# Prüfe sowohl physische Existenz als auch Verwendung im Projekt (optimierter Set-Lookup)
|
||||
if not new_path.exists() and new_name not in used_names:
|
||||
logger.debug(f"Alternativer Dateiname generiert: {new_name}")
|
||||
return new_path
|
||||
|
||||
counter += 1
|
||||
|
||||
# Sicherheitsgrenze um Endlosschleifen zu vermeiden
|
||||
if counter > 1000:
|
||||
raise Exception("Zu viele alternative Dateinamen generiert")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Fehler beim Generieren alternativer Dateinamen für {original_path}: {e}")
|
||||
# Fallback: Zeitstempel verwenden
|
||||
timestamp = int(time.time())
|
||||
fallback_name = f"{original_path.stem}_{timestamp}{original_path.suffix}"
|
||||
return xml_dir / fallback_name
|
||||
|
||||
def _is_filename_used_in_project(self, relative_xml_path: Path) -> bool:
|
||||
"""
|
||||
Prüft ob ein relativer XML-Dateipfad bereits im Projekt verwendet wird.
|
||||
|
||||
Args:
|
||||
relative_xml_path: Relativer Pfad zur XML-Datei (z.B. xml/datei_1.xml)
|
||||
|
||||
Returns:
|
||||
bool: True wenn der Dateiname bereits verwendet wird
|
||||
"""
|
||||
try:
|
||||
all_xml_files = self._get_all_project_xml_files()
|
||||
|
||||
for xml_file in all_xml_files:
|
||||
if xml_file.xml == relative_xml_path:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Fehler beim Prüfen der Dateiname-Verwendung für {relative_xml_path}: {e}")
|
||||
return True # Im Zweifelsfall annehmen, dass der Name verwendet wird
|
||||
|
||||
def _calculate_hash_for_file(self, file_path: Path) -> str | None:
|
||||
"""
|
||||
Berechnet synchron den blake2b-Hash für eine Datei.
|
||||
|
||||
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):
|
||||
"""
|
||||
Ordnet eine bereits vorhandene XML-Datei (basierend auf Hash-Match) den XSL-Knoten zu.
|
||||
|
||||
Args:
|
||||
existing_xml: Die bereits vorhandene XML-Datei
|
||||
selected_xsl_nodes: Liste der ausgewählten XSL-Knoten
|
||||
|
||||
Returns:
|
||||
dict: Statistiken mit 'status', 'added_count', 'existing_file'
|
||||
"""
|
||||
try:
|
||||
added_count = 0
|
||||
|
||||
for xsl_node in selected_xsl_nodes:
|
||||
# Prüfe ob diese XML-Datei bereits in der XslFile-Node vorhanden ist
|
||||
already_assigned = any(xml_file.xml == existing_xml.xml for xml_file in xsl_node.xmls)
|
||||
|
||||
if not already_assigned:
|
||||
# Erstelle neue XmlFile-Referenz mit gleichem Pfad und Hash
|
||||
new_xml_ref = XmlFile(xml=existing_xml.xml, hashsum=existing_xml.hashsum)
|
||||
xsl_node.xmls.append(new_xml_ref)
|
||||
added_count += 1
|
||||
logger.info(f"Vorhandene XML-Datei '{existing_xml.xml}' zu XSL-Node '{xsl_node.bez}' zugeordnet")
|
||||
else:
|
||||
logger.debug(f"XML-Datei '{existing_xml.xml}' bereits in XSL-Node '{xsl_node.bez}' vorhanden")
|
||||
|
||||
if added_count > 0:
|
||||
# Speichere die aktualisierten Projekt-Einstellungen
|
||||
self._save_project_settings()
|
||||
|
||||
# Aktualisiere das TreeWidget
|
||||
self._load_nodes_to_tree()
|
||||
|
||||
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:
|
||||
error_msg = f"Fehler beim Zuordnen der vorhandenen XML-Datei: {str(e)}"
|
||||
logger.error(error_msg)
|
||||
return {"status": "error", "error_msg": error_msg}
|
||||
|
||||
def _process_new_xml_file(self, xml_file_path: Path, selected_xsl_nodes: list, file_hash: str | None):
|
||||
"""
|
||||
Verarbeitet eine neue XML-Datei (kein Hash-Match gefunden).
|
||||
|
||||
Args:
|
||||
xml_file_path: Pfad zur neuen XML-Datei
|
||||
selected_xsl_nodes: Liste der ausgewählten XSL-Knoten
|
||||
file_hash: Berechneter Hash der Datei
|
||||
|
||||
Returns:
|
||||
dict: Statistiken mit 'status', 'added_count', 'new_file', 'renamed_from'
|
||||
"""
|
||||
try:
|
||||
# Prüfe ob Projekt verfügbar ist
|
||||
if not self.project or not self.project.project_dir:
|
||||
logger.error("Kein Projekt-Verzeichnis für neue XML-Datei verfügbar")
|
||||
return {"status": "error", "error_msg": "Kein Projekt-Verzeichnis verfügbar."}
|
||||
|
||||
# Erstelle xml-Ordner im Projekt-Verzeichnis falls er nicht existiert
|
||||
xml_dir = Path(self.project.project_dir) / "xml"
|
||||
xml_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# Bestimme den Ziel-Pfad in xml-Ordner
|
||||
target_xml_path = xml_dir / xml_file_path.name
|
||||
|
||||
# Prüfe ob eine Datei mit gleichem Namen bereits existiert
|
||||
if target_xml_path.exists() or self._is_filename_used_in_project(Path("xml") / xml_file_path.name):
|
||||
# Generiere alternative Dateinamen
|
||||
alternative_paths = []
|
||||
for i in range(1, 6): # Generiere bis zu 5 Alternativen
|
||||
alt_path = self._generate_alternative_filename(xml_file_path, xml_dir)
|
||||
if alt_path not in alternative_paths:
|
||||
alternative_paths.append(alt_path)
|
||||
|
||||
# Zeige Dialog zur Auswahl des Dateinamens
|
||||
selected_path = self._show_filename_selection_dialog(xml_file_path.name, alternative_paths)
|
||||
|
||||
if not selected_path:
|
||||
# Benutzer hat abgebrochen
|
||||
return {"status": "cancelled", "added_count": 0}
|
||||
|
||||
target_xml_path = selected_path
|
||||
|
||||
# Kopiere die XML-Datei in den xml-Ordner
|
||||
shutil.copy2(xml_file_path, target_xml_path)
|
||||
logger.info(f"XML-Datei kopiert: {xml_file_path} -> {target_xml_path}")
|
||||
|
||||
# Erstelle relatives Path zur XML-Datei (relativ zum Projekt-Verzeichnis)
|
||||
relative_xml_path = Path("xml") / target_xml_path.name
|
||||
|
||||
# Füge die XML-Datei zu allen ausgewählten XSL-Knoten hinzu
|
||||
added_count = 0
|
||||
for xsl_node in selected_xsl_nodes:
|
||||
# Prüfe ob diese XML-Datei bereits in der XslFile-Node vorhanden ist
|
||||
existing_xml = None
|
||||
for xml_file in xsl_node.xmls:
|
||||
if xml_file.xml == relative_xml_path:
|
||||
existing_xml = xml_file
|
||||
break
|
||||
|
||||
if not existing_xml:
|
||||
# Erstelle neues XmlFile-Objekt mit Hash
|
||||
new_xml_file = XmlFile(xml=relative_xml_path, hashsum=file_hash)
|
||||
xsl_node.xmls.append(new_xml_file)
|
||||
added_count += 1
|
||||
logger.info(f"XML-Datei '{target_xml_path.name}' zu XSL-Node '{xsl_node.bez}' hinzugefügt")
|
||||
else:
|
||||
logger.debug(f"XML-Datei '{target_xml_path.name}' bereits in XSL-Node '{xsl_node.bez}' vorhanden")
|
||||
|
||||
if added_count > 0:
|
||||
# Speichere die aktualisierten Projekt-Einstellungen
|
||||
self._save_project_settings()
|
||||
|
||||
# Aktualisiere das TreeWidget
|
||||
self._load_nodes_to_tree()
|
||||
|
||||
return {
|
||||
"status": "new_added",
|
||||
"added_count": added_count,
|
||||
"new_file": target_xml_path.name,
|
||||
"renamed_from": xml_file_path.name if target_xml_path.name != xml_file_path.name else None,
|
||||
}
|
||||
else:
|
||||
return {
|
||||
"status": "already_assigned",
|
||||
"added_count": 0,
|
||||
"new_file": target_xml_path.name,
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
error_msg = f"Fehler beim Verarbeiten der neuen XML-Datei: {str(e)}"
|
||||
logger.error(error_msg)
|
||||
return {"status": "error", "error_msg": error_msg}
|
||||
|
||||
def _show_filename_selection_dialog(self, original_name: str, alternative_paths: List[Path]) -> Path | None:
|
||||
"""
|
||||
Zeigt einen Dialog zur Auswahl eines alternativen Dateinamens.
|
||||
|
||||
Args:
|
||||
original_name: Ursprünglicher Dateiname
|
||||
alternative_paths: Liste alternativer Pfade
|
||||
|
||||
Returns:
|
||||
Path|None: Ausgewählter Pfad oder None bei Abbruch
|
||||
"""
|
||||
try:
|
||||
from PySide6.QtWidgets import (
|
||||
QDialog,
|
||||
QVBoxLayout,
|
||||
QLabel,
|
||||
QRadioButton,
|
||||
QButtonGroup,
|
||||
QPushButton,
|
||||
QHBoxLayout,
|
||||
)
|
||||
|
||||
dialog = QDialog(self)
|
||||
dialog.setWindowTitle("Dateiname auswählen")
|
||||
dialog.setModal(True)
|
||||
dialog.resize(400, 300)
|
||||
|
||||
layout = QVBoxLayout(dialog)
|
||||
|
||||
# Erklärungstext
|
||||
info_label = QLabel(
|
||||
f"Eine Datei mit dem Namen '{original_name}' existiert bereits.\n\n"
|
||||
"Bitte wählen Sie einen alternativen Dateinamen:"
|
||||
)
|
||||
layout.addWidget(info_label)
|
||||
|
||||
# Radio-Buttons für alternative Namen
|
||||
button_group = QButtonGroup(dialog)
|
||||
radio_buttons = []
|
||||
|
||||
for i, alt_path in enumerate(alternative_paths):
|
||||
radio_button = QRadioButton(alt_path.name)
|
||||
if i == 0: # Ersten als Standard auswählen
|
||||
radio_button.setChecked(True)
|
||||
button_group.addButton(radio_button, i)
|
||||
radio_buttons.append(radio_button)
|
||||
layout.addWidget(radio_button)
|
||||
|
||||
# Buttons
|
||||
button_layout = QHBoxLayout()
|
||||
ok_button = QPushButton("OK")
|
||||
cancel_button = QPushButton("Abbrechen")
|
||||
|
||||
button_layout.addWidget(ok_button)
|
||||
button_layout.addWidget(cancel_button)
|
||||
layout.addLayout(button_layout)
|
||||
|
||||
# Event-Handler
|
||||
ok_button.clicked.connect(dialog.accept)
|
||||
cancel_button.clicked.connect(dialog.reject)
|
||||
|
||||
# Dialog anzeigen
|
||||
if dialog.exec() == QDialog.DialogCode.Accepted:
|
||||
selected_id = button_group.checkedId()
|
||||
if 0 <= selected_id < len(alternative_paths):
|
||||
return alternative_paths[selected_id]
|
||||
|
||||
return None
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Fehler beim Anzeigen des Dateiname-Auswahl-Dialogs: {e}")
|
||||
# Fallback: Ersten alternativen Namen verwenden
|
||||
return alternative_paths[0] if alternative_paths else None
|
||||
Reference in New Issue
Block a user