PruefManager und Daten aus P41 übertragen

This commit is contained in:
2025-12-02 20:55:51 +01:00
parent 788bac2a23
commit 1881af93f8
25 changed files with 2567 additions and 295 deletions

97
modules/Dateipruefer.py Normal file
View File

@@ -0,0 +1,97 @@
import os
from enum import Enum, auto
# -------------------------------
# ENUMS
# -------------------------------
class LeererPfadModus(Enum):#legt die modi fest, die für Dateipfade möglich sind
VERBOTEN = auto() #ein leeres Eingabefeld stellt einen Fehler dar
NUTZE_STANDARD = auto() #ein leeres Eingabefeld fordert zur Entscheidung auf: nutze Standard oder brich ab
TEMPORAER_ERLAUBT = auto() #ein leeres Eingabefeld fordert zur Entscheidung auf: arbeite temporär oder brich ab.
class DateiEntscheidung(Enum):#legt die Modi fest, wie mit bestehenden Dateien umgegangen werden soll (hat das das QGSFile-Objekt schon selbst?)
ERSETZEN = auto()#Ergebnis der Nutzerentscheidung: bestehende Datei ersetzen
ANHAENGEN = auto()#Ergebnis der Nutzerentscheidung: an bestehende Datei anhängen
ABBRECHEN = auto()#bricht den Vorgang ab. (muss das eine definierte Option sein? oder geht das auch mit einem normalen Abbruch-Button)
# -------------------------------
# RÜCKGABEOBJEKT
# -------------------------------
#Das Dateiprüfergebnis wird an den Prüfmanager übergeben. Alle GUI-Abfragen werden im Prüfmanager behandelt.
class DateipruefErgebnis:
#Definition der Parameter und Festlegung auf den Parametertyp,bzw den Standardwert
def __init__(self, erfolgreich: bool, pfad: str = None, temporär: bool = False,
entscheidung: DateiEntscheidung = None, fehler: list = None):
self.erfolgreich = erfolgreich
self.pfad = pfad
self.temporär = temporär
self.entscheidung = entscheidung
self.fehler = fehler or []
def __repr__(self):
return (f"DateipruefErgebnis(erfolgreich={self.erfolgreich}, "
f"pfad={repr(self.pfad)}, temporär={self.temporär}, "
f"entscheidung={repr(self.entscheidung)}, fehler={repr(self.fehler)})")
# -------------------------------
# DATEIPRÜFER
# -------------------------------
class Dateipruefer:
def pruefe(self, pfad: str,
leer_modus: LeererPfadModus,
standardname: str = None,
plugin_pfad: str = None,
vorhandene_datei_entscheidung: DateiEntscheidung = None) -> DateipruefErgebnis: #Rückgabetypannotation; "Die Funktion "pruefe" gibt ein Objekt vom Typ "DateipruefErgebnis" zurück
# 1. Prüfe, ob das Eingabefeld leer ist
if not pfad or pfad.strip() == "":#wenn der angegebene Pfad leer oder ungültig ist:
if leer_modus == LeererPfadModus.VERBOTEN: #wenn der Modus "verboten" vorgegeben ist, gib zurück, dass der Test fehlgeschlagen ist
return DateipruefErgebnis(
erfolgreich=False,
fehler=["Kein Pfad angegeben."]
)
elif leer_modus == LeererPfadModus.NUTZE_STANDARD:#wenn der Modus "Nutze_Standard" vorgegeben ist...
if not plugin_pfad or not standardname:#wenn kein gültiger Pluginpfad angegeben ist oder die Standarddatei fehlt...
return DateipruefErgebnis(
erfolgreich=False,
fehler=["Standardpfad oder -name fehlen."]#..gib zurück, dass der Test fehlgeschlagen ist
)
pfad = os.path.join(plugin_pfad, standardname)#...wenn es Standarddatei und Pluginpfad gibt...setze sie zum Pfad zusammen...
elif leer_modus == LeererPfadModus.TEMPORAER_ERLAUBT:#wenn der Modus "temporär" vorgegeben ist,...
return DateipruefErgebnis(#...gib zurück, dass das Prüfergebnis erfolgreich ist (Entscheidung, ob temporör gearbeitet werden soll oder nicht, kommt woanders)
erfolgreich=True,
pfad=None
)
# 2. Existiert die Datei bereits?
if os.path.exists(pfad):#wenn die Datei vorhanden ist...
if not vorhandene_datei_entscheidung:#aber noch keine Entscheidung getroffen ist...
return DateipruefErgebnis(
erfolgreich=True,#ist die Prüfung erfolgreich, aber es muss noch eine Entscheidung verlangt werden
pfad=pfad,
entscheidung=None,
fehler=["Datei existiert bereits Entscheidung ausstehend."]
)
if vorhandene_datei_entscheidung == DateiEntscheidung.ABBRECHEN:
return DateipruefErgebnis(#...der Nutzer aber abgebrochen hat...
erfolgreich=False,#ist die Prüfung fehlgeschlagen ISSUE: ergibt das Sinn?
pfad=pfad,
fehler=["Benutzer hat abgebrochen."]
)
return DateipruefErgebnis(
erfolgreich=True,
pfad=pfad,
entscheidung=vorhandene_datei_entscheidung
)
# 3. Pfad gültig und Datei nicht vorhanden
#wenn alle Varianten NICHT zutreffen, weil ein gültiger Pfad eingegeben wurde und die Datei noch nicht vorhanden ist:
return DateipruefErgebnis(
erfolgreich=True,
pfad=pfad
)

51
modules/Pruefmanager.py Normal file
View File

@@ -0,0 +1,51 @@
from PyQt5.QtWidgets import QMessageBox, QFileDialog
from Dateipruefer import DateiEntscheidung
class PruefManager:
def __init__(self, iface=None, plugin_pfad=None):
self.iface = iface
self.plugin_pfad = plugin_pfad
def frage_datei_ersetzen_oder_anhaengen(self, pfad: str) -> DateiEntscheidung:
"""Fragt den Nutzer, ob die vorhandene Datei ersetzt, angehängt oder abgebrochen werden soll."""
msg = QMessageBox()
msg.setIcon(QMessageBox.Question)
msg.setWindowTitle("Datei existiert")
msg.setText(f"Die Datei '{pfad}' existiert bereits.\nWas möchtest du tun?")
msg.setStandardButtons(QMessageBox.Yes | QMessageBox.No | QMessageBox.Cancel)
msg.setDefaultButton(QMessageBox.Yes)
msg.button(QMessageBox.Yes).setText("Ersetzen")
msg.button(QMessageBox.No).setText("Anhängen")
msg.button(QMessageBox.Cancel).setText("Abbrechen")
result = msg.exec_()
if result == QMessageBox.Yes:
return DateiEntscheidung.ERSETZEN
elif result == QMessageBox.No:
return DateiEntscheidung.ANHAENGEN
else:
return DateiEntscheidung.ABBRECHEN
def frage_temporär_verwenden(self) -> bool:
"""Fragt den Nutzer, ob mit temporären Layern gearbeitet werden soll."""
msg = QMessageBox()
msg.setIcon(QMessageBox.Question)
msg.setWindowTitle("Temporäre Layer")
msg.setText("Kein Speicherpfad wurde angegeben.\nMit temporären Layern fortfahren?")
msg.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
msg.setDefaultButton(QMessageBox.Yes)
result = msg.exec_()
return result == QMessageBox.Yes
def waehle_dateipfad(self, titel="Speicherort wählen", filter="GeoPackage (*.gpkg)") -> str:
"""Öffnet einen QFileDialog zur Dateiauswahl."""
pfad, _ = QFileDialog.getSaveFileName(
parent=None,
caption=titel,
directory=self.plugin_pfad or "",
filter=filter
)
return pfad

0
modules/__init__py Normal file
View File

94
modules/linkpruefer.py Normal file
View File

@@ -0,0 +1,94 @@
# Importiert den Event-Loop und URL-Objekte aus der PyQt-Bibliothek von QGIS
from qgis.PyQt.QtCore import QEventLoop, QUrl
# Importiert den NetworkAccessManager aus dem QGIS Core-Modul
from qgis.core import QgsNetworkAccessManager
# Importiert das QNetworkRequest-Objekt für HTTP-Anfragen
from qgis.PyQt.QtNetwork import QNetworkRequest
# Importiert die Klasse für das Ergebnisobjekt der Prüfung
from pruef_ergebnis import PruefErgebnis
# Definiert die Klasse zum Prüfen von Links
class Linkpruefer:
"""Prüft den Link mit QgsNetworkAccessManager und klassifiziert Anbieter nach Attribut."""
# Statische Zuordnung möglicher Anbietertypen als Konstanten
ANBIETER_TYPEN: dict[str, str] = {
"REST": "REST",
"WFS": "WFS",
"WMS": "WMS",
"OGR": "OGR"
}
# Konstruktor zum Initialisieren der Instanz
def __init__(self, link: str, anbieter: str):
# Speichert den übergebenen Link als Instanzvariable
self.link = link
# Speichert den Anbietertyp, bereinigt und in Großbuchstaben (auch wenn leer oder None)
self.anbieter = anbieter.upper().strip() if anbieter else ""
# Erstellt einen neuen NetworkAccessManager für Netzwerkverbindungen
self.network_manager = QgsNetworkAccessManager()
# Methode zur Klassifizierung des Anbieters und der Quelle
def klassifiziere_anbieter(self):
# Bestimmt den Typ auf Basis der vorgegebenen Konstante oder nimmt den Rohwert
typ = self.ANBIETER_TYPEN.get(self.anbieter, self.anbieter)
# Unterscheidet zwischen "remote" (http/https) oder "local" (Dateipfad)
quelle = "remote" if self.link.startswith(("http://", "https://")) else "local"
# Gibt Typ und Quelle als Dictionary zurück
return {
"typ": typ,
"quelle": quelle
}
# Prüft die Erreichbarkeit und Plausibilität des Links
def pruefe_link(self):
# Initialisiert Listen für Fehler und Warnungen
fehler = []
warnungen = []
# Prüft, ob ein Link übergeben wurde
if not self.link:
fehler.append("Link fehlt.")
return PruefErgebnis(False, fehler=fehler, warnungen=warnungen)
# Prüft, ob ein Anbieter angegeben ist
if not self.anbieter or not self.anbieter.strip():
fehler.append("Anbieter muss gesetzt werden und darf nicht leer sein.")
# Prüfung für Remote-Links (http/https)
if self.link.startswith(("http://", "https://")):
# Erstellt eine HTTP-Anfrage mit dem Link
request = QNetworkRequest(QUrl(self.link))
# Startet eine HEAD-Anfrage über den NetworkManager
reply = self.network_manager.head(request)
# Wartet synchron auf die Netzwerkanwort (Event Loop)
loop = QEventLoop()
reply.finished.connect(loop.quit)
loop.exec_()
# Prüft auf Netzwerkfehler
if reply.error():
fehler.append(f"Verbindungsfehler: {reply.errorString()}")
else:
# Holt den HTTP-Statuscode aus der Antwort
status = reply.attribute(reply.HttpStatusCodeAttribute)
# Prüft, ob der Status außerhalb des Erfolgsbereichs liegt
if status is None or status < 200 or status >= 400:
fehler.append(f"Link nicht erreichbar: HTTP {status}")
# Räumt die Antwort auf (Vermeidung von Speicherlecks)
reply.deleteLater()
else:
# Plausibilitäts-Check für lokale Links (Dateien), prüft auf Dateiendung
if "." not in self.link.split("/")[-1]:
warnungen.append("Der lokale Link sieht ungewöhnlich aus.")
# Gibt das Ergebnisobjekt mit allen gesammelten Informationen zurück
return PruefErgebnis(len(fehler) == 0, daten=self.klassifiziere_anbieter(), fehler=fehler, warnungen=warnungen)
# Führt die Linkprüfung als externe Methode aus
def ausfuehren(self):
# Gibt das Ergebnis der Prüf-Methode zurück
return self.pruefe_link()

11
modules/pruef_ergebnis Normal file
View File

@@ -0,0 +1,11 @@
# Klasse zur Definition eines Pruefergebnis-Objekts, das in allen Prüfern verwendet werden kann
class PruefErgebnis:
def __init__(self, erfolgreich: bool, daten=None, fehler=None, warnungen=None):
self.erfolgreich = erfolgreich
self.daten = daten or {}
self.fehler = fehler or []
self.warnungen = warnungen or []
def __repr__(self):
return (f"PruefErgebnis(erfolgreich={self.erfolgreich}, "
f"daten={self.daten}, fehler={self.fehler}, warnungen={self.warnungen})")

11
modules/pruef_ergebnis.py Normal file
View File

@@ -0,0 +1,11 @@
# Klasse zur Definition eines Pruefergebnis-Objekts, das in allen Prüfern verwendet werden kann
class PruefErgebnis:
def __init__(self, erfolgreich: bool, daten=None, fehler=None, warnungen=None):
self.erfolgreich = erfolgreich
self.daten = daten or {}
self.fehler = fehler or []
self.warnungen = warnungen or []
def __repr__(self):
return (f"PruefErgebnis(erfolgreich={self.erfolgreich}, "
f"daten={self.daten}, fehler={self.fehler}, warnungen={self.warnungen})")

45
modules/stilpruefer.py Normal file
View File

@@ -0,0 +1,45 @@
import os
from pruef_ergebnis import PruefErgebnis
class Stilpruefer:
"""
Prüft, ob ein angegebener Stilpfad gültig und nutzbar ist.
- Wenn kein Stil angegeben ist, gilt die Prüfung als erfolgreich.
- Wenn angegeben:
* Datei muss existieren
* Dateiendung muss '.qml' sein
"""
def pruefe(self, stilpfad: str) -> PruefErgebnis:
# kein Stil angegeben -> erfolgreich, keine Warnung
if not stilpfad or stilpfad.strip() == "":
return PruefErgebnis(
erfolgreich=True,
daten={"stil": None},
warnungen=["Kein Stil angegeben."]
)
fehler = []
warnungen = []
# Prüfung: Datei existiert?
if not os.path.exists(stilpfad):
fehler.append(f"Stildatei nicht gefunden: {stilpfad}")
# Prüfung: Endung .qml?
elif not stilpfad.lower().endswith(".qml"):
fehler.append(f"Ungültige Dateiendung für Stil: {stilpfad}")
else:
# Hinweis: alle Checks bestanden
return PruefErgebnis(
erfolgreich=True,
daten={"stil": stilpfad}
)
return PruefErgebnis(
erfolgreich=False if fehler else True,
daten={"stil": stilpfad},
fehler=fehler,
warnungen=warnungen
)