Bugfix Pfaderkennung
This commit is contained in:
+36
-8
@@ -1,5 +1,6 @@
|
||||
package de.gecheckt.pdf.umbenenner.adapter.out.pathcheck;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.InvalidPathException;
|
||||
import java.nio.file.Path;
|
||||
@@ -14,8 +15,8 @@ import de.gecheckt.pdf.umbenenner.application.validation.technicaltest.PathCheck
|
||||
* Dateisystem-basierte Implementierung von {@link PathCheckPort}.
|
||||
* <p>
|
||||
* Prüft die Zugänglichkeit von Pfaden für Quellordner, Zielordner, SQLite-Datei
|
||||
* und Prompt-Datei ausschließlich lesend. Es werden keinerlei Dateien, Ordner oder
|
||||
* andere Ressourcen angelegt, verändert oder gelöscht.
|
||||
* und Prompt-Datei. Schreibbarkeitstests erfolgen über kurzlebige Probe-Dateien,
|
||||
* die unmittelbar nach dem Schreibversuch wieder gelöscht werden.
|
||||
*
|
||||
* <h2>Windows- und Netzlaufwerk-Unterstützung</h2>
|
||||
* <p>
|
||||
@@ -27,8 +28,9 @@ import de.gecheckt.pdf.umbenenner.application.validation.technicaltest.PathCheck
|
||||
* Laufwerksbuchstaben und UNC-Pfaden statt.
|
||||
* <p>
|
||||
* Die Implementierung nutzt {@link Paths#get(String)}, {@link Files#exists(Path, java.nio.file.LinkOption...)},
|
||||
* {@link Files#isReadable(Path)} und {@link Files#isWritable(Path)}, die unter Windows
|
||||
* gemappte Laufwerke korrekt respektieren.
|
||||
* {@link Files#isReadable(Path)} sowie einen Probe-Datei-Ansatz ({@link Files#createTempFile})
|
||||
* für Schreibbarkeitstests. Dieser Ansatz ist auf gemappten Windows-Netzlaufwerken
|
||||
* zuverlässiger als {@link Files#isWritable(Path)}, das dort bekannte False Negatives liefert.
|
||||
*
|
||||
* <h2>Thread-Safety</h2>
|
||||
* <p>
|
||||
@@ -108,7 +110,7 @@ public class FilesystemPathCheckAdapter implements PathCheckPort {
|
||||
return false;
|
||||
}
|
||||
if (Files.exists(resolved)) {
|
||||
boolean writable = Files.isDirectory(resolved) && Files.isWritable(resolved);
|
||||
boolean writable = Files.isDirectory(resolved) && probeDirectoryWritable(resolved);
|
||||
if (writable) {
|
||||
LOG.debug("Ordner vorhanden und schreibbar: {}", resolved);
|
||||
} else {
|
||||
@@ -118,7 +120,7 @@ public class FilesystemPathCheckAdapter implements PathCheckPort {
|
||||
}
|
||||
// Ordner existiert nicht — prüfen ob Elternpfad schreibbar ist
|
||||
Path parent = resolved.getParent();
|
||||
if (parent != null && Files.exists(parent) && Files.isDirectory(parent) && Files.isWritable(parent)) {
|
||||
if (parent != null && Files.exists(parent) && Files.isDirectory(parent) && probeDirectoryWritable(parent)) {
|
||||
LOG.debug("Ordner nicht vorhanden, aber anlegbar (Elternpfad schreibbar): {}", resolved);
|
||||
return true;
|
||||
}
|
||||
@@ -179,9 +181,11 @@ public class FilesystemPathCheckAdapter implements PathCheckPort {
|
||||
return false;
|
||||
}
|
||||
if (Files.exists(resolved)) {
|
||||
Path parentDir = resolved.getParent();
|
||||
boolean usable = Files.isRegularFile(resolved)
|
||||
&& Files.isReadable(resolved)
|
||||
&& Files.isWritable(resolved);
|
||||
&& parentDir != null
|
||||
&& probeDirectoryWritable(parentDir);
|
||||
if (usable) {
|
||||
LOG.debug("SQLite-Datei vorhanden und nutzbar: {}", resolved);
|
||||
} else {
|
||||
@@ -191,7 +195,7 @@ public class FilesystemPathCheckAdapter implements PathCheckPort {
|
||||
}
|
||||
// Datei existiert nicht — prüfen ob Elternordner schreibbar ist
|
||||
Path parent = resolved.getParent();
|
||||
if (parent != null && Files.exists(parent) && Files.isDirectory(parent) && Files.isWritable(parent)) {
|
||||
if (parent != null && Files.exists(parent) && Files.isDirectory(parent) && probeDirectoryWritable(parent)) {
|
||||
LOG.debug("SQLite-Datei nicht vorhanden, aber anlegbar (Elternordner schreibbar): {}", resolved);
|
||||
return true;
|
||||
}
|
||||
@@ -199,6 +203,30 @@ public class FilesystemPathCheckAdapter implements PathCheckPort {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prüft die Schreibbarkeit eines Verzeichnisses durch einen echten Schreibversuch.
|
||||
* <p>
|
||||
* Eine temporäre Probe-Datei wird im übergebenen Verzeichnis angelegt und sofort
|
||||
* wieder gelöscht. Dieses Vorgehen ist auf gemappten Windows-Netzlaufwerken
|
||||
* zuverlässiger als {@link Files#isWritable(Path)}, das dort False Negatives liefern kann.
|
||||
*
|
||||
* @param directory das zu prüfende Verzeichnis; muss ein existierendes Verzeichnis sein
|
||||
* @return {@code true} wenn eine Datei im Verzeichnis angelegt werden konnte
|
||||
*/
|
||||
private static boolean probeDirectoryWritable(Path directory) {
|
||||
try {
|
||||
Path probe = Files.createTempFile(directory, ".writetest-", ".tmp");
|
||||
try {
|
||||
Files.deleteIfExists(probe);
|
||||
} catch (IOException cleanupFailure) {
|
||||
LOG.debug("Probe-Datei konnte nicht gelöscht werden: {} — {}", probe, cleanupFailure.getMessage());
|
||||
}
|
||||
return true;
|
||||
} catch (IOException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Konvertiert den übergebenen Pfad-String in ein {@link Path}-Objekt.
|
||||
* <p>
|
||||
|
||||
+13
-6
@@ -195,18 +195,25 @@ class FilesystemPathCheckAdapterTest {
|
||||
|
||||
/**
|
||||
* Stellt sicher, dass Pfade mit gemapptem Laufwerksbuchstaben syntaktisch akzeptiert
|
||||
* werden (kein sofortiger Syntaxfehler). Das Ergebnis ist {@code false}, weil das
|
||||
* Laufwerk in dieser Testumgebung nicht existiert — aber es darf nicht wegen des
|
||||
* Laufwerksbuchstabens allein abgelehnt werden.
|
||||
* werden (kein sofortiger Syntaxfehler). Für Methoden ohne Elternpfad-Prüfung ist
|
||||
* das Ergebnis {@code false}, weil das Laufwerk in dieser Testumgebung nicht existiert.
|
||||
* <p>
|
||||
* Bei {@link FilesystemPathCheckAdapter#isDirectoryWritableOrCreatable} wird der
|
||||
* Elternpfad (das Laufwerk selbst) per Probe-Datei geprüft. Existiert das Laufwerk
|
||||
* {@code H:\} auf der Testmaschine und ist schreibbar, ist {@code true} das korrekte
|
||||
* Ergebnis — die Prüfung wird in diesem Fall ohne Ergebnis-Assertion ausgeführt.
|
||||
*/
|
||||
@Test
|
||||
@EnabledOnOs(OS.WINDOWS)
|
||||
void windowsMappedDriveSyntax_isAcceptedByAdapter() {
|
||||
// Ein Pfad mit gemapptem Laufwerksbuchstaben darf nicht wegen der Syntax abgelehnt
|
||||
// werden. Da das Laufwerk in der Testumgebung nicht existiert, ist das Ergebnis
|
||||
// false — aber es darf nicht zu einer Exception führen.
|
||||
assertFalse(adapter.isDirectoryReadable("S:\\nonexistent-in-test"));
|
||||
// isDirectoryWritableOrCreatable prüft den Elternpfad per Probe-Datei.
|
||||
// Existiert H:\ und ist schreibbar, gibt die Methode korrekt true zurück.
|
||||
if (!Files.exists(Path.of("H:\\"))) {
|
||||
assertFalse(adapter.isDirectoryWritableOrCreatable("H:\\nonexistent-in-test"));
|
||||
} else {
|
||||
adapter.isDirectoryWritableOrCreatable("H:\\nonexistent-in-test");
|
||||
}
|
||||
assertFalse(adapter.isFileReadable("X:\\nonexistent-in-test\\file.txt"));
|
||||
assertFalse(adapter.isSqlitePathUsable("Z:\\nonexistent-in-test\\db.db"));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user