V2.9: Integrierte PDF-Vorschau und editierbarer Dateiname im Verarbeitungslauf
Neu im Tab "Verarbeitungslauf": - Integrierte PDF-Vorschau der Quelldatei mit Lazy Rendering (Seite 1 sofort, weitere Seiten on-demand), Cache pro Selektion, "latest preview request wins" - Editierbarer KI-Dateinamenvorschlag mit Live-Validierung, Dirty-State-Dialog bei Zeilen-/Tabwechsel, Schließen und Laufstart, atomare FS+DB-Transaktion inkl. Rollback und Fingerprint-basierter Konfliktauflösung Architektur: - Neuer Application-Use-Case ManualFileRenameUseCase und Outbound-Port TargetFileRenamePort mit Filesystem-Adapter - Neuer GuiManualFileRenamePort, verdrahtet im Bootstrap - GuiBatchRunResultRow um correctedFileName erweitert - GuiBatchRunTab auf SplitPane-Layout (60/40) umgebaut, Detail-Panel mit KI-Begründung, FileNameEditorPane und PdfPreviewPane - Spike-Code (PdfViewerSpike) entfernt, produktive Implementierung ersetzt Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
+106
@@ -0,0 +1,106 @@
|
||||
package de.gecheckt.pdf.umbenenner.adapter.out.targetfolder;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.AtomicMoveNotSupportedException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.util.Objects;
|
||||
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import de.gecheckt.pdf.umbenenner.application.port.out.TargetFileRenameFailureFileNotFound;
|
||||
import de.gecheckt.pdf.umbenenner.application.port.out.TargetFileRenameFailureTargetExists;
|
||||
import de.gecheckt.pdf.umbenenner.application.port.out.TargetFileRenamePort;
|
||||
import de.gecheckt.pdf.umbenenner.application.port.out.TargetFileRenameResult;
|
||||
import de.gecheckt.pdf.umbenenner.application.port.out.TargetFileRenameSuccess;
|
||||
import de.gecheckt.pdf.umbenenner.application.port.out.TargetFileRenameTechnicalFailure;
|
||||
|
||||
/**
|
||||
* Filesystem-basierte Implementierung von {@link TargetFileRenamePort}.
|
||||
* <p>
|
||||
* Benennt eine bestehende Datei im konfigurierten Zielordner um, indem sie
|
||||
* {@link Files#move} mit {@link StandardCopyOption#ATOMIC_MOVE} verwendet. Wird
|
||||
* {@link AtomicMoveNotSupportedException} geworfen (z. B. auf Netzlaufwerken), erfolgt
|
||||
* ein automatischer Rückfall auf einen nicht-atomaren {@code Files.move}-Aufruf.
|
||||
* <p>
|
||||
* <strong>Architekturgrenze:</strong> Alle NIO-Operationen ({@code Path}, {@code Files})
|
||||
* sind ausschließlich in dieser Klasse gekapselt. Der Port
|
||||
* {@link TargetFileRenamePort} enthält keine Dateisystem-Typen.
|
||||
*/
|
||||
public class FilesystemTargetFileRenameAdapter implements TargetFileRenamePort {
|
||||
|
||||
private static final Logger LOG = LogManager.getLogger(FilesystemTargetFileRenameAdapter.class);
|
||||
|
||||
private final Path targetFolder;
|
||||
|
||||
/**
|
||||
* Erstellt den Adapter für den angegebenen Zielordner.
|
||||
*
|
||||
* @param targetFolder Pfad des Zielordners; darf nicht null sein
|
||||
* @throws NullPointerException wenn {@code targetFolder} null ist
|
||||
*/
|
||||
public FilesystemTargetFileRenameAdapter(Path targetFolder) {
|
||||
this.targetFolder = Objects.requireNonNull(targetFolder, "targetFolder darf nicht null sein");
|
||||
}
|
||||
|
||||
/**
|
||||
* Benennt eine bestehende Datei im Zielordner von {@code oldFileName} zu
|
||||
* {@code newFileName} um.
|
||||
* <p>
|
||||
* Ablauf:
|
||||
* <ol>
|
||||
* <li>Prüft, ob {@code oldFileName} im Zielordner vorhanden ist; falls nicht,
|
||||
* wird {@link TargetFileRenameFailureFileNotFound} zurückgegeben.</li>
|
||||
* <li>Prüft, ob {@code newFileName} bereits durch eine andere Datei belegt ist;
|
||||
* falls ja, wird {@link TargetFileRenameFailureTargetExists} zurückgegeben.</li>
|
||||
* <li>Versucht {@link StandardCopyOption#ATOMIC_MOVE}; bei
|
||||
* {@link AtomicMoveNotSupportedException} (z. B. Netzlaufwerk) erfolgt ein
|
||||
* Rückfall auf einen normalen Verschiebeaufruf ohne Atomic-Flag.</li>
|
||||
* <li>Bei Erfolg: {@link TargetFileRenameSuccess}.</li>
|
||||
* <li>Bei anderen {@link IOException}: {@link TargetFileRenameTechnicalFailure}
|
||||
* mit deutschem Fehlertext.</li>
|
||||
* </ol>
|
||||
*
|
||||
* @param oldFileName der aktuell im Zielordner vorhandene Dateiname (ohne Pfad);
|
||||
* darf nicht null sein
|
||||
* @param newFileName der gewünschte neue Dateiname (ohne Pfad); darf nicht null sein
|
||||
* @return das Ergebnis der Umbenennung; nie null
|
||||
*/
|
||||
@Override
|
||||
public TargetFileRenameResult rename(String oldFileName, String newFileName) {
|
||||
Objects.requireNonNull(oldFileName, "oldFileName darf nicht null sein");
|
||||
Objects.requireNonNull(newFileName, "newFileName darf nicht null sein");
|
||||
|
||||
Path oldPath = targetFolder.resolve(oldFileName);
|
||||
Path newPath = targetFolder.resolve(newFileName);
|
||||
|
||||
if (Files.notExists(oldPath)) {
|
||||
LOG.warn("Umbenennung verweigert: Quelldatei nicht vorhanden: '{}'", oldPath);
|
||||
return new TargetFileRenameFailureFileNotFound(oldFileName);
|
||||
}
|
||||
|
||||
if (Files.exists(newPath) && !oldPath.equals(newPath)) {
|
||||
LOG.warn("Umbenennung verweigert: Zieldatei bereits vorhanden: '{}'", newPath);
|
||||
return new TargetFileRenameFailureTargetExists(newFileName);
|
||||
}
|
||||
|
||||
try {
|
||||
try {
|
||||
Files.move(oldPath, newPath, StandardCopyOption.ATOMIC_MOVE);
|
||||
} catch (AtomicMoveNotSupportedException atomicEx) {
|
||||
LOG.warn("Atomares Verschieben nicht unterstützt (z. B. Netzlaufwerk) für '{}' → '{}'. " +
|
||||
"Rückfall auf normales Verschieben.", oldPath, newPath);
|
||||
Files.move(oldPath, newPath);
|
||||
}
|
||||
LOG.info("Datei erfolgreich umbenannt: '{}' → '{}'", oldFileName, newFileName);
|
||||
return new TargetFileRenameSuccess();
|
||||
} catch (IOException e) {
|
||||
String message = "Technischer Fehler beim Umbenennen von '" + oldFileName
|
||||
+ "' zu '" + newFileName + "': " + e.getMessage();
|
||||
LOG.error(message, e);
|
||||
return new TargetFileRenameTechnicalFailure(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
+3
@@ -6,6 +6,9 @@
|
||||
* <li>{@link de.gecheckt.pdf.umbenenner.adapter.out.targetfolder.FilesystemTargetFolderAdapter}
|
||||
* — Filesystem-based implementation of
|
||||
* {@link de.gecheckt.pdf.umbenenner.application.port.out.TargetFolderPort}.</li>
|
||||
* <li>{@link de.gecheckt.pdf.umbenenner.adapter.out.targetfolder.FilesystemTargetFileRenameAdapter}
|
||||
* — Filesystem-based implementation of
|
||||
* {@link de.gecheckt.pdf.umbenenner.application.port.out.TargetFileRenamePort}.</li>
|
||||
* </ul>
|
||||
* <p>
|
||||
* <strong>Duplicate resolution:</strong> Given a base name such as
|
||||
|
||||
Reference in New Issue
Block a user