SonarQube: fix alle BLOCKER- und CRITICAL-Issues (S3252, S2479, S1186, S1192, S2699, S5783, S3776)
- S3252: GuiStatusRefreshTimeline nutzt Animation.INDEFINITE statt Timeline.INDEFINITE - S2479: Narrow-No-Break-Space (U+202F) in GuiTooltipTexts durch normales Leerzeichen ersetzt - S1186: 134 leere Stub-Methoden in 18 Test- und Produktionsdateien kommentiert - S1192: ~49 duplizierte String-Literale in ~25 Klassen als Konstanten extrahiert - S2699: fehlende Assertions in SqliteSchemaInitializationAdapterTest und FilesystemTargetFolderAdapterTest ergaenzt - S5783: Lambda-geprufte Ausnahme in SqliteSchemaInitializationAdapterTest in private Hilfsmethode extrahiert - S3776: kognitive Komplexitaet in 8 Methoden durch Methodenextraktion auf unter 15 gesenkt (EarlyLogDirectoryInitializer, CliArgumentParser, GuiConfigurationEditorWorkspace, GuiHistoryTab x2, GuiBatchRunTab x2, DefaultManualFileCopyUseCase) - Kompilierungsfehler behoben: private-Modifier in CorrectionOutcome-Interface entfernt, selbstreferenzielle Konstante in ModelCatalogResult korrigiert Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+34
-27
@@ -124,6 +124,12 @@ import javafx.stage.Window;
|
||||
* Thread via {@code Platform.runLater}.
|
||||
*/
|
||||
public final class GuiConfigurationEditorWorkspace {
|
||||
private static final String NO_PROMPT_PATH_MSG = "Kein Prompt-Pfad konfiguriert.";
|
||||
private static final String OPERATION_VALIDATE = "Validierung";
|
||||
private static final String PROPERTIES_FILTER_EXT = "*.properties";
|
||||
private static final String PROPERTIES_FILTER_DESC = "Properties-Dateien";
|
||||
|
||||
|
||||
|
||||
private static final Logger LOG = LogManager.getLogger(GuiConfigurationEditorWorkspace.class);
|
||||
private static final String WELCOME_TEXT =
|
||||
@@ -985,7 +991,7 @@ public final class GuiConfigurationEditorWorkspace {
|
||||
Window owner = root.getScene() == null ? null : root.getScene().getWindow();
|
||||
FileChooser fileChooser = new FileChooser();
|
||||
fileChooser.setTitle("Konfiguration öffnen");
|
||||
fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter("Properties-Dateien", "*.properties"));
|
||||
fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter(PROPERTIES_FILTER_DESC, PROPERTIES_FILTER_EXT));
|
||||
if (owner != null && editorState.hasLoadedFileSnapshot()) {
|
||||
Path currentPath = editorState.loadedFileSnapshot().orElseThrow().filePath();
|
||||
Path parent = currentPath.getParent();
|
||||
@@ -1055,7 +1061,7 @@ public final class GuiConfigurationEditorWorkspace {
|
||||
FileChooser fileChooser = saveFileChooserFactory.get();
|
||||
fileChooser.setTitle("Konfiguration speichern");
|
||||
fileChooser.getExtensionFilters().add(
|
||||
new FileChooser.ExtensionFilter("Properties-Dateien", "*.properties"));
|
||||
new FileChooser.ExtensionFilter(PROPERTIES_FILTER_DESC, PROPERTIES_FILTER_EXT));
|
||||
|
||||
// Propose the default path relative to the working directory.
|
||||
Path proposedDir = DEFAULT_SAVE_PATH.getParent();
|
||||
@@ -1504,7 +1510,7 @@ public final class GuiConfigurationEditorWorkspace {
|
||||
FileChooser fileChooser = saveFileChooserFactory.get();
|
||||
fileChooser.setTitle("Konfiguration speichern");
|
||||
fileChooser.getExtensionFilters().add(
|
||||
new FileChooser.ExtensionFilter("Properties-Dateien", "*.properties"));
|
||||
new FileChooser.ExtensionFilter(PROPERTIES_FILTER_DESC, PROPERTIES_FILTER_EXT));
|
||||
java.io.File proposedDirFile = DEFAULT_SAVE_PATH.getParent().toAbsolutePath().toFile();
|
||||
if (proposedDirFile.exists()) {
|
||||
fileChooser.setInitialDirectory(proposedDirFile);
|
||||
@@ -1604,13 +1610,13 @@ public final class GuiConfigurationEditorWorkspace {
|
||||
@Override
|
||||
public de.gecheckt.pdf.umbenenner.application.port.out.PromptLoadingResult loadCurrentPrompt() {
|
||||
return new de.gecheckt.pdf.umbenenner.application.port.out.PromptLoadingFailure(
|
||||
"NO_PATH", "Kein Prompt-Pfad konfiguriert.");
|
||||
"NO_PATH", NO_PROMPT_PATH_MSG);
|
||||
}
|
||||
|
||||
@Override
|
||||
public de.gecheckt.pdf.umbenenner.application.port.out.PromptSaveResult save(String content) {
|
||||
return new de.gecheckt.pdf.umbenenner.application.port.out.PromptSaveResult.WriteFailed(
|
||||
"Kein Prompt-Pfad konfiguriert.", null);
|
||||
NO_PROMPT_PATH_MSG, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -1619,7 +1625,7 @@ public final class GuiConfigurationEditorWorkspace {
|
||||
de.gecheckt.pdf.umbenenner.application.validation.technicaltest
|
||||
.CorrectionSuggestion.CreatePromptFile suggestion) {
|
||||
return new de.gecheckt.pdf.umbenenner.application.validation.technicaltest
|
||||
.CorrectionOutcome.NotAttempted(suggestion, "Kein Prompt-Pfad konfiguriert.");
|
||||
.CorrectionOutcome.NotAttempted(suggestion, NO_PROMPT_PATH_MSG);
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -1696,26 +1702,27 @@ public final class GuiConfigurationEditorWorkspace {
|
||||
// Tab-Wechsel-Schutz: Beim Wechsel weg vom Verarbeitungslauf-Tab prüfen ob
|
||||
// der Dateiname-Editor ungespeicherte Änderungen hat.
|
||||
// Gleiches gilt für den Prompt-Tab.
|
||||
tabPane.getSelectionModel().selectedItemProperty().addListener((obs, oldTab, newTab) -> {
|
||||
if (oldTab == null || newTab == null) {
|
||||
return;
|
||||
tabPane.getSelectionModel().selectedItemProperty().addListener(
|
||||
(obs, oldTab, newTab) -> handleTabSwitch(oldTab, newTab));
|
||||
}
|
||||
|
||||
private void handleTabSwitch(javafx.scene.control.Tab oldTab, javafx.scene.control.Tab newTab) {
|
||||
if (oldTab == null || newTab == null) {
|
||||
return;
|
||||
}
|
||||
if (oldTab == batchRunTab.tab() && batchRunTab.hasUnsavedFilenameEdits()) {
|
||||
boolean shouldDiscard = batchRunTab.confirmDiscardUnsavedFilenameEdits();
|
||||
if (!shouldDiscard) {
|
||||
Platform.runLater(() -> tabPane.getSelectionModel().select(oldTab));
|
||||
}
|
||||
if (oldTab == batchRunTab.tab() && batchRunTab.hasUnsavedFilenameEdits()) {
|
||||
// Selektion kurz unterdrücken um Rekursion zu vermeiden
|
||||
boolean shouldDiscard = batchRunTab.confirmDiscardUnsavedFilenameEdits();
|
||||
if (!shouldDiscard) {
|
||||
// Zurück zum Verarbeitungslauf-Tab
|
||||
Platform.runLater(() -> tabPane.getSelectionModel().select(oldTab));
|
||||
}
|
||||
} else if (oldTab == promptEditorTab.tab() && promptEditorTab.hasDirtyContent()) {
|
||||
boolean shouldDiscard = promptEditorTab.confirmDiscardIfDirty();
|
||||
if (!shouldDiscard) {
|
||||
Platform.runLater(() -> tabPane.getSelectionModel().select(oldTab));
|
||||
} else {
|
||||
promptEditorTab.discardChanges();
|
||||
}
|
||||
} else if (oldTab == promptEditorTab.tab() && promptEditorTab.hasDirtyContent()) {
|
||||
boolean shouldDiscard = promptEditorTab.confirmDiscardIfDirty();
|
||||
if (!shouldDiscard) {
|
||||
Platform.runLater(() -> tabPane.getSelectionModel().select(oldTab));
|
||||
} else {
|
||||
promptEditorTab.discardChanges();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void configureActionBar() {
|
||||
@@ -2610,7 +2617,7 @@ public final class GuiConfigurationEditorWorkspace {
|
||||
|
||||
for (EditorValidationFinding finding : report.findings()) {
|
||||
GuiMessageSeverity severity = toGuiSeverity(finding.severity());
|
||||
messages.add(GuiMessageEntry.of(severity, finding.message(), "Validierung"));
|
||||
messages.add(GuiMessageEntry.of(severity, finding.message(), OPERATION_VALIDATE));
|
||||
if (finding.hasFieldKey()) {
|
||||
fieldFindings.add(new GuiFieldFinding(finding.fieldKey().orElseThrow(),
|
||||
severity, finding.message()));
|
||||
@@ -2619,7 +2626,7 @@ public final class GuiConfigurationEditorWorkspace {
|
||||
|
||||
// Replace validation-related entries; preserve model-catalog messages (from coordinator)
|
||||
pendingMessages.removeIf(m -> m.source().isPresent()
|
||||
&& "Validierung".equals(m.source().get()));
|
||||
&& OPERATION_VALIDATE.equals(m.source().get()));
|
||||
pendingMessages.addAll(messages);
|
||||
|
||||
pendingFieldFindings.clear();
|
||||
@@ -2675,7 +2682,7 @@ public final class GuiConfigurationEditorWorkspace {
|
||||
// Drop silent auto-validation entries so the central message area is not flooded
|
||||
// by keystroke-level background checks; explicit action entries always accumulate.
|
||||
pendingMessages.removeIf(m -> m.source().isPresent()
|
||||
&& "Validierung".equals(m.source().get()));
|
||||
&& OPERATION_VALIDATE.equals(m.source().get()));
|
||||
|
||||
// Append a timestamped confirmation plus each concrete finding as its own entry.
|
||||
int findingCount = report.findings().size();
|
||||
|
||||
+12
-8
@@ -51,6 +51,10 @@ import javafx.application.Platform;
|
||||
* {@code Platform.runLater}.
|
||||
*/
|
||||
public final class GuiModelCatalogCoordinator {
|
||||
private static final String LOG_MODEL_FETCH_FMT = "GUI-Modellabruf: {} (Provider: {})";
|
||||
private static final String OPERATION_MODELLABRUF = "Modellabruf";
|
||||
|
||||
|
||||
|
||||
private static final Logger LOG = LogManager.getLogger(GuiModelCatalogCoordinator.class);
|
||||
|
||||
@@ -203,7 +207,7 @@ public final class GuiModelCatalogCoordinator {
|
||||
String previousManualValue) {
|
||||
// Remove any previous message entries from an earlier retrieval so messages do not
|
||||
// accumulate across repeated triggers of the same retrieval action.
|
||||
pendingMessages.removeIf(msg -> "Modellabruf".equals(msg.source().orElse("")));
|
||||
pendingMessages.removeIf(msg -> OPERATION_MODELLABRUF.equals(msg.source().orElse("")));
|
||||
|
||||
String displayName = displayNameFor(family);
|
||||
|
||||
@@ -213,28 +217,28 @@ public final class GuiModelCatalogCoordinator {
|
||||
container.applyModelList(models, previousManualValue);
|
||||
String message = "Modellliste für " + displayName + " geladen ("
|
||||
+ models.size() + " " + (models.size() == 1 ? "Eintrag" : "Einträge") + ").";
|
||||
pendingMessages.add(GuiMessageEntry.of(GuiMessageSeverity.INFO, message, "Modellabruf"));
|
||||
LOG.info("GUI-Modellabruf: {} (Provider: {})", message, family.getIdentifier());
|
||||
pendingMessages.add(GuiMessageEntry.of(GuiMessageSeverity.INFO, message, OPERATION_MODELLABRUF));
|
||||
LOG.info(LOG_MODEL_FETCH_FMT, message, family.getIdentifier());
|
||||
}
|
||||
case ModelCatalogResult.EmptyList emptyList -> {
|
||||
container.applyManualFallback(GuiModelSource.LIST_UNAVAILABLE_MANUAL_INPUT);
|
||||
String message = "Provider " + displayName
|
||||
+ " liefert aktuell keine Modelle. Manuelle Eingabe aktiv.";
|
||||
pendingMessages.add(GuiMessageEntry.of(GuiMessageSeverity.HINT, message, "Modellabruf"));
|
||||
LOG.warn("GUI-Modellabruf: {} (Provider: {})", message, family.getIdentifier());
|
||||
pendingMessages.add(GuiMessageEntry.of(GuiMessageSeverity.HINT, message, OPERATION_MODELLABRUF));
|
||||
LOG.warn(LOG_MODEL_FETCH_FMT, message, family.getIdentifier());
|
||||
}
|
||||
case ModelCatalogResult.IncompleteConfiguration incomplete -> {
|
||||
container.applyManualFallback(GuiModelSource.LIST_UNAVAILABLE_MANUAL_INPUT);
|
||||
String message = "Modellliste nicht abrufbar: " + incomplete.missingReason()
|
||||
+ ". Manuelle Eingabe aktiv.";
|
||||
pendingMessages.add(GuiMessageEntry.of(GuiMessageSeverity.WARNING, message, "Modellabruf"));
|
||||
LOG.warn("GUI-Modellabruf: {} (Provider: {})", message, family.getIdentifier());
|
||||
pendingMessages.add(GuiMessageEntry.of(GuiMessageSeverity.WARNING, message, OPERATION_MODELLABRUF));
|
||||
LOG.warn(LOG_MODEL_FETCH_FMT, message, family.getIdentifier());
|
||||
}
|
||||
case ModelCatalogResult.TechnicalFailure failure -> {
|
||||
container.applyManualFallback(GuiModelSource.LIST_FAILED_MANUAL_INPUT);
|
||||
String message = "Modellliste nicht abrufbar (" + failure.errorCategory()
|
||||
+ "). Manuelle Eingabe aktiv.";
|
||||
pendingMessages.add(GuiMessageEntry.of(GuiMessageSeverity.ERROR, message, "Modellabruf"));
|
||||
pendingMessages.add(GuiMessageEntry.of(GuiMessageSeverity.ERROR, message, OPERATION_MODELLABRUF));
|
||||
LOG.warn("GUI-Modellabruf: {} Detail: {} (Provider: {})",
|
||||
message, failure.errorDetail(), family.getIdentifier());
|
||||
}
|
||||
|
||||
+6
-3
@@ -54,6 +54,9 @@ import javafx.scene.layout.VBox;
|
||||
* Hintergrund-Worker-Thread ({@code gui-scheduler-control}) ausgeführt.
|
||||
*/
|
||||
public final class GuiSchedulerTab {
|
||||
private static final String HEADER_LABEL_STYLE = "-fx-font-size: 14px; -fx-font-weight: bold; -fx-text-fill: #7f8c8d;";
|
||||
|
||||
|
||||
|
||||
private static final Logger LOG = LogManager.getLogger(GuiSchedulerTab.class);
|
||||
|
||||
@@ -177,7 +180,7 @@ public final class GuiSchedulerTab {
|
||||
}
|
||||
|
||||
private VBox buildControlArea() {
|
||||
statusLabel.setStyle("-fx-font-size: 14px; -fx-font-weight: bold; -fx-text-fill: #7f8c8d;");
|
||||
statusLabel.setStyle(HEADER_LABEL_STYLE);
|
||||
|
||||
stopButton.setDisable(true);
|
||||
HBox buttonBox = new HBox(10, startButton, stopButton);
|
||||
@@ -248,7 +251,7 @@ public final class GuiSchedulerTab {
|
||||
switch (status.state()) {
|
||||
case STOPPED -> {
|
||||
statusLabel.setText("○ Gestoppt");
|
||||
statusLabel.setStyle("-fx-font-size: 14px; -fx-font-weight: bold; -fx-text-fill: #7f8c8d;");
|
||||
statusLabel.setStyle(HEADER_LABEL_STYLE);
|
||||
}
|
||||
case STARTING -> {
|
||||
statusLabel.setText("⟳ Wird gestartet…");
|
||||
@@ -264,7 +267,7 @@ public final class GuiSchedulerTab {
|
||||
}
|
||||
case STOPPING_BATCH_ACTIVE -> {
|
||||
statusLabel.setText("○ Gestoppt – aktueller Lauf läuft noch");
|
||||
statusLabel.setStyle("-fx-font-size: 14px; -fx-font-weight: bold; -fx-text-fill: #7f8c8d;");
|
||||
statusLabel.setStyle(HEADER_LABEL_STYLE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+9
-6
@@ -101,6 +101,9 @@ public record GuiStartupContext(
|
||||
Optional<String> applicationContextError,
|
||||
Optional<SchedulerControlUseCase> schedulerControlUseCase,
|
||||
Optional<ConfigurationFileLockPort> configurationFileLockPort) {
|
||||
private static final String NO_PROMPT_PORT_MSG = "Kein Prompt-Editor-Port in diesem Startkontext verfügbar.";
|
||||
private static final String NO_PORT_MSG = "Kein Port in diesem Startkontext.";
|
||||
|
||||
|
||||
/**
|
||||
* Creates a fully wired startup context.
|
||||
@@ -524,21 +527,21 @@ public record GuiStartupContext(
|
||||
createDirectory(de.gecheckt.pdf.umbenenner.application.validation.technicaltest
|
||||
.CorrectionSuggestion.CreateDirectory suggestion) {
|
||||
return new de.gecheckt.pdf.umbenenner.application.validation.technicaltest
|
||||
.CorrectionOutcome.NotAttempted(suggestion, "Kein Port in diesem Startkontext.");
|
||||
.CorrectionOutcome.NotAttempted(suggestion, NO_PORT_MSG);
|
||||
}
|
||||
@Override
|
||||
public de.gecheckt.pdf.umbenenner.application.validation.technicaltest.CorrectionOutcome
|
||||
createPromptFile(de.gecheckt.pdf.umbenenner.application.validation.technicaltest
|
||||
.CorrectionSuggestion.CreatePromptFile suggestion) {
|
||||
return new de.gecheckt.pdf.umbenenner.application.validation.technicaltest
|
||||
.CorrectionOutcome.NotAttempted(suggestion, "Kein Port in diesem Startkontext.");
|
||||
.CorrectionOutcome.NotAttempted(suggestion, NO_PORT_MSG);
|
||||
}
|
||||
@Override
|
||||
public de.gecheckt.pdf.umbenenner.application.validation.technicaltest.CorrectionOutcome
|
||||
prepareSqlitePath(de.gecheckt.pdf.umbenenner.application.validation.technicaltest
|
||||
.CorrectionSuggestion.PrepareSqlitePath suggestion) {
|
||||
return new de.gecheckt.pdf.umbenenner.application.validation.technicaltest
|
||||
.CorrectionOutcome.NotAttempted(suggestion, "Kein Port in diesem Startkontext.");
|
||||
.CorrectionOutcome.NotAttempted(suggestion, NO_PORT_MSG);
|
||||
}
|
||||
};
|
||||
CorrectionExecutionService noOpCorrectionService = new CorrectionExecutionService(noOpResourceCreationPort);
|
||||
@@ -599,13 +602,13 @@ public record GuiStartupContext(
|
||||
@Override
|
||||
public de.gecheckt.pdf.umbenenner.application.port.out.PromptLoadingResult loadCurrentPrompt() {
|
||||
return new de.gecheckt.pdf.umbenenner.application.port.out.PromptLoadingFailure(
|
||||
"NO_OP", "Kein Prompt-Editor-Port in diesem Startkontext verfügbar.");
|
||||
"NO_OP", NO_PROMPT_PORT_MSG);
|
||||
}
|
||||
|
||||
@Override
|
||||
public de.gecheckt.pdf.umbenenner.application.port.out.PromptSaveResult save(String content) {
|
||||
return new de.gecheckt.pdf.umbenenner.application.port.out.PromptSaveResult.WriteFailed(
|
||||
"Kein Prompt-Editor-Port in diesem Startkontext verfügbar.", null);
|
||||
NO_PROMPT_PORT_MSG, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -615,7 +618,7 @@ public record GuiStartupContext(
|
||||
.CorrectionSuggestion.CreatePromptFile suggestion) {
|
||||
return new de.gecheckt.pdf.umbenenner.application.validation.technicaltest
|
||||
.CorrectionOutcome.NotAttempted(
|
||||
suggestion, "Kein Prompt-Editor-Port in diesem Startkontext verfügbar.");
|
||||
suggestion, NO_PROMPT_PORT_MSG);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
+6
-3
@@ -29,6 +29,9 @@ import javafx.scene.layout.Region;
|
||||
* Die Klasse selbst erzwingt dies nicht; der Aufrufer trägt die Verantwortung.
|
||||
*/
|
||||
public final class GuiStatusBar {
|
||||
private static final String LABEL_STYLE = "-fx-font-size: 11px; -fx-text-fill: #555555;";
|
||||
|
||||
|
||||
|
||||
/** Anzeigetext wenn keine Konfiguration geladen ist. */
|
||||
static final String KEIN_PROFIL_TEXT = "Kein Profil geladen";
|
||||
@@ -58,16 +61,16 @@ public final class GuiStatusBar {
|
||||
|
||||
// Linkes Segment: Versionsanzeige
|
||||
this.versionLabel = new Label(VERSION_PREFIX + this.applicationVersion);
|
||||
this.versionLabel.setStyle("-fx-font-size: 11px; -fx-text-fill: #555555;");
|
||||
this.versionLabel.setStyle(LABEL_STYLE);
|
||||
|
||||
// Mittleres Segment: Provider und Modell
|
||||
this.providerLabel = new Label(KEIN_PROFIL_TEXT);
|
||||
this.providerLabel.setStyle("-fx-font-size: 11px; -fx-text-fill: #555555;");
|
||||
this.providerLabel.setStyle(LABEL_STYLE);
|
||||
this.providerLabel.setAlignment(Pos.CENTER);
|
||||
|
||||
// Rechtes Segment: Konfigurationspfad
|
||||
this.configPathLabel = new Label(KEIN_PROFIL_TEXT);
|
||||
this.configPathLabel.setStyle("-fx-font-size: 11px; -fx-text-fill: #555555;");
|
||||
this.configPathLabel.setStyle(LABEL_STYLE);
|
||||
this.configPathLabel.setAlignment(Pos.CENTER_RIGHT);
|
||||
|
||||
// Abstandhalter zwischen den Segmenten
|
||||
|
||||
+2
-1
@@ -4,6 +4,7 @@ import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
|
||||
import de.gecheckt.pdf.umbenenner.application.port.in.SchedulerControlUseCase;
|
||||
import javafx.animation.Animation;
|
||||
import javafx.animation.KeyFrame;
|
||||
import javafx.animation.Timeline;
|
||||
import javafx.util.Duration;
|
||||
@@ -42,7 +43,7 @@ public final class GuiStatusRefreshTimeline {
|
||||
Objects.requireNonNull(onRefresh, "onRefresh must not be null");
|
||||
this.timeline = new Timeline(
|
||||
new KeyFrame(Duration.seconds(1), e -> onRefresh.run()));
|
||||
this.timeline.setCycleCount(Timeline.INDEFINITE);
|
||||
this.timeline.setCycleCount(Animation.INDEFINITE);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+1
-1
@@ -87,7 +87,7 @@ public final class GuiTooltipTexts {
|
||||
|
||||
/** Tooltip für das Eingabefeld „Basis-URL". */
|
||||
public static final String PROVIDER_BASIS_URL =
|
||||
"Basis-URL des KI-Dienstes (z. B. https://api.openai.com/v1).";
|
||||
"Basis-URL des KI-Dienstes (z. B. https://api.openai.com/v1).";
|
||||
|
||||
/** Tooltip für das Eingabefeld „Timeout". */
|
||||
public static final String PROVIDER_TIMEOUT =
|
||||
|
||||
+7
-4
@@ -63,6 +63,9 @@ import javafx.scene.control.Alert;
|
||||
* </ol>
|
||||
*/
|
||||
public final class GuiBatchRunCoordinator {
|
||||
private static final String CONFIG_FILE_NOT_NULL = "configFilePath must not be null";
|
||||
|
||||
|
||||
|
||||
private static final Logger LOG = LogManager.getLogger(GuiBatchRunCoordinator.class);
|
||||
private static final String WORKER_THREAD_NAME = "gui-batch-run";
|
||||
@@ -353,7 +356,7 @@ public final class GuiBatchRunCoordinator {
|
||||
* @throws NullPointerException if {@code configFilePath} is {@code null}
|
||||
*/
|
||||
public boolean start(Path configFilePath) {
|
||||
Objects.requireNonNull(configFilePath, "configFilePath must not be null");
|
||||
Objects.requireNonNull(configFilePath, CONFIG_FILE_NOT_NULL);
|
||||
if (isRunning()) {
|
||||
return false;
|
||||
}
|
||||
@@ -379,7 +382,7 @@ public final class GuiBatchRunCoordinator {
|
||||
*/
|
||||
public boolean startMiniRun(Path configFilePath,
|
||||
Set<DocumentFingerprint> fingerprintFilter) {
|
||||
Objects.requireNonNull(configFilePath, "configFilePath must not be null");
|
||||
Objects.requireNonNull(configFilePath, CONFIG_FILE_NOT_NULL);
|
||||
Objects.requireNonNull(fingerprintFilter, "fingerprintFilter must not be null");
|
||||
if (isRunning()) {
|
||||
return false;
|
||||
@@ -411,7 +414,7 @@ public final class GuiBatchRunCoordinator {
|
||||
*/
|
||||
public boolean startReprocessing(Path configFilePath,
|
||||
Set<DocumentFingerprint> fingerprintFilter) {
|
||||
Objects.requireNonNull(configFilePath, "configFilePath must not be null");
|
||||
Objects.requireNonNull(configFilePath, CONFIG_FILE_NOT_NULL);
|
||||
Objects.requireNonNull(fingerprintFilter, "fingerprintFilter must not be null");
|
||||
if (isRunning()) {
|
||||
return false;
|
||||
@@ -452,7 +455,7 @@ public final class GuiBatchRunCoordinator {
|
||||
* @throws NullPointerException if any argument is {@code null}
|
||||
*/
|
||||
public boolean startReset(Path configFilePath, Set<DocumentFingerprint> fingerprints) {
|
||||
Objects.requireNonNull(configFilePath, "configFilePath must not be null");
|
||||
Objects.requireNonNull(configFilePath, CONFIG_FILE_NOT_NULL);
|
||||
Objects.requireNonNull(fingerprints, "fingerprints must not be null");
|
||||
if (isRunning()) {
|
||||
return false;
|
||||
|
||||
+78
-68
@@ -111,6 +111,11 @@ import javafx.scene.layout.VBox;
|
||||
* dafür, Hintergrundereignisse vor dem Callback auf den FX-Thread zu übertragen.
|
||||
*/
|
||||
public final class GuiBatchRunTab {
|
||||
private static final String COPY_FAILED_LOG = "Manuelle Dateikopie fehlgeschlagen: {}";
|
||||
private static final String RENAME_FAILED_LOG = "Manuelle Dateiumbenennung fehlgeschlagen: {}";
|
||||
private static final String DIRTY_STATE_MSG = "Dateiname-Editor: Ungespeicherte Änderungen";
|
||||
|
||||
|
||||
|
||||
private static final Logger LOG = LogManager.getLogger(GuiBatchRunTab.class);
|
||||
|
||||
@@ -820,7 +825,7 @@ public final class GuiBatchRunTab {
|
||||
return;
|
||||
}
|
||||
fileNameEditor.discardChanges();
|
||||
LOG.debug("Dateiname-Editor: Ungespeicherte Änderung – Benutzer hat verworfen");
|
||||
LOG.debug(DIRTY_STATE_MSG);
|
||||
}
|
||||
|
||||
// Neue Zeile laden
|
||||
@@ -957,55 +962,55 @@ public final class GuiBatchRunTab {
|
||||
*/
|
||||
private void handleCopyResult(ManualFileCopyResult result, GuiBatchRunResultRow row) {
|
||||
switch (result) {
|
||||
case ManualFileCopySuccess success -> {
|
||||
LOG.info("Manuelle Dateikopie erfolgreich: {} → {} (Suffix: {})",
|
||||
row.originalFileName(), success.appliedFileName(),
|
||||
success.conflictSuffixApplied());
|
||||
GuiBatchRunResultRow updatedRow = buildSuccessRowAfterCopy(row, success.appliedFileName());
|
||||
currentlySelectedRow = updatedRow;
|
||||
fileNameEditor.clearDirtyState();
|
||||
upsertResultRowByFingerprint(updatedRow);
|
||||
String targetFolder = targetFolderSupplier.get().orElse("");
|
||||
fileNameEditor.loadSelection(updatedRow, targetFolder);
|
||||
String msg = "Datei kopiert und gespeichert: " + success.appliedFileName();
|
||||
if (success.conflictSuffixApplied()) {
|
||||
msg += " (Suffix wegen Namenskonflikt angehängt)";
|
||||
}
|
||||
showMessage(msg);
|
||||
refreshAggregateCountersFromItems();
|
||||
}
|
||||
case ManualFileCopyNoOpIdenticalTarget noOp -> {
|
||||
LOG.info("Manuelle Dateikopie: identische Zieldatei {} bereits vorhanden – kein Schreibvorgang.",
|
||||
noOp.existingFileName());
|
||||
GuiBatchRunResultRow updatedRow = buildSuccessRowAfterCopy(row, noOp.existingFileName());
|
||||
currentlySelectedRow = updatedRow;
|
||||
fileNameEditor.clearDirtyState();
|
||||
upsertResultRowByFingerprint(updatedRow);
|
||||
String targetFolder = targetFolderSupplier.get().orElse("");
|
||||
fileNameEditor.loadSelection(updatedRow, targetFolder);
|
||||
showMessage("Identische Datei bereits vorhanden – Status auf SUCCESS gesetzt");
|
||||
refreshAggregateCountersFromItems();
|
||||
}
|
||||
case ManualFileCopySuccess success -> applyCopySuccess(success, row);
|
||||
case ManualFileCopyNoOpIdenticalTarget noOp -> applyCopyNoOpIdentical(noOp, row);
|
||||
case ManualFileCopyDocumentNotFound notFound -> {
|
||||
LOG.warn("Manuelle Dateikopie fehlgeschlagen: {}", notFound.reason());
|
||||
LOG.warn(COPY_FAILED_LOG, notFound.reason());
|
||||
showMessage("Fehler: Dokument nicht gefunden – " + notFound.reason());
|
||||
}
|
||||
case ManualFileCopyInvalidState invalidState -> {
|
||||
LOG.warn("Manuelle Dateikopie fehlgeschlagen: {}", invalidState.reason());
|
||||
LOG.warn(COPY_FAILED_LOG, invalidState.reason());
|
||||
showMessage("Fehler: Ungültiger Dokumentstatus – " + invalidState.reason());
|
||||
}
|
||||
case ManualFileCopyFileSystemFailure fsFail -> {
|
||||
LOG.warn("Manuelle Dateikopie fehlgeschlagen: {}", fsFail.message());
|
||||
LOG.warn(COPY_FAILED_LOG, fsFail.message());
|
||||
showMessage("Dateisystemfehler: " + fsFail.message());
|
||||
}
|
||||
case ManualFileCopyPersistenceFailure persistFail -> {
|
||||
LOG.warn("Manuelle Dateikopie fehlgeschlagen: {}", persistFail.message());
|
||||
showMessage("Persistenzfehler (Zielkopie wurde zurückgerollt): "
|
||||
+ persistFail.message());
|
||||
LOG.warn(COPY_FAILED_LOG, persistFail.message());
|
||||
showMessage("Persistenzfehler (Zielkopie wurde zurückgerollt): " + persistFail.message());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void applyCopySuccess(ManualFileCopySuccess success, GuiBatchRunResultRow row) {
|
||||
LOG.info("Manuelle Dateikopie erfolgreich: {} → {} (Suffix: {})",
|
||||
row.originalFileName(), success.appliedFileName(), success.conflictSuffixApplied());
|
||||
GuiBatchRunResultRow updatedRow = buildSuccessRowAfterCopy(row, success.appliedFileName());
|
||||
currentlySelectedRow = updatedRow;
|
||||
fileNameEditor.clearDirtyState();
|
||||
upsertResultRowByFingerprint(updatedRow);
|
||||
fileNameEditor.loadSelection(updatedRow, targetFolderSupplier.get().orElse(""));
|
||||
String msg = "Datei kopiert und gespeichert: " + success.appliedFileName();
|
||||
if (success.conflictSuffixApplied()) {
|
||||
msg += " (Suffix wegen Namenskonflikt angehängt)";
|
||||
}
|
||||
showMessage(msg);
|
||||
refreshAggregateCountersFromItems();
|
||||
}
|
||||
|
||||
private void applyCopyNoOpIdentical(ManualFileCopyNoOpIdenticalTarget noOp, GuiBatchRunResultRow row) {
|
||||
LOG.info("Manuelle Dateikopie: identische Zieldatei {} bereits vorhanden – kein Schreibvorgang.",
|
||||
noOp.existingFileName());
|
||||
GuiBatchRunResultRow updatedRow = buildSuccessRowAfterCopy(row, noOp.existingFileName());
|
||||
currentlySelectedRow = updatedRow;
|
||||
fileNameEditor.clearDirtyState();
|
||||
upsertResultRowByFingerprint(updatedRow);
|
||||
fileNameEditor.loadSelection(updatedRow, targetFolderSupplier.get().orElse(""));
|
||||
showMessage("Identische Datei bereits vorhanden – Status auf SUCCESS gesetzt");
|
||||
refreshAggregateCountersFromItems();
|
||||
}
|
||||
|
||||
/**
|
||||
* Baut eine neue Zeilen-Sicht für ein Dokument, das per manueller Dateikopie auf
|
||||
* {@code SUCCESS} gehoben wurde. Status, korrigierter Dateiname und das Zurücksetzen
|
||||
@@ -1105,24 +1110,24 @@ public final class GuiBatchRunTab {
|
||||
noOp.existingFileName());
|
||||
}
|
||||
case ManualFileRenameDocumentNotFound notFound -> {
|
||||
LOG.warn("Manuelle Dateiumbenennung fehlgeschlagen: {}", notFound.reason());
|
||||
LOG.warn(RENAME_FAILED_LOG, notFound.reason());
|
||||
showMessage("Fehler: Dokument nicht gefunden – " + notFound.reason());
|
||||
}
|
||||
case ManualFileRenameInvalidState invalidState -> {
|
||||
LOG.warn("Manuelle Dateiumbenennung fehlgeschlagen: {}", invalidState.reason());
|
||||
LOG.warn(RENAME_FAILED_LOG, invalidState.reason());
|
||||
showMessage("Fehler: Ungültiger Dokumentstatus – " + invalidState.reason());
|
||||
}
|
||||
case ManualFileRenameSourceFileMissing sourceMissing -> {
|
||||
LOG.warn("Manuelle Dateiumbenennung fehlgeschlagen: {}",
|
||||
LOG.warn(RENAME_FAILED_LOG,
|
||||
sourceMissing.expectedFileName());
|
||||
showMessage("Zieldatei nicht gefunden – Umbenennung nicht möglich");
|
||||
}
|
||||
case ManualFileRenameFileSystemFailure fsFail -> {
|
||||
LOG.warn("Manuelle Dateiumbenennung fehlgeschlagen: {}", fsFail.message());
|
||||
LOG.warn(RENAME_FAILED_LOG, fsFail.message());
|
||||
showMessage("Dateisystemfehler: " + fsFail.message());
|
||||
}
|
||||
case ManualFileRenamePersistenceFailure persistFail -> {
|
||||
LOG.warn("Manuelle Dateiumbenennung fehlgeschlagen: {}", persistFail.message());
|
||||
LOG.warn(RENAME_FAILED_LOG, persistFail.message());
|
||||
showMessage("Persistenzfehler (Dateisystem wurde zurückgerollt): "
|
||||
+ persistFail.message());
|
||||
}
|
||||
@@ -1263,7 +1268,7 @@ public final class GuiBatchRunTab {
|
||||
return;
|
||||
}
|
||||
fileNameEditor.discardChanges();
|
||||
LOG.debug("Dateiname-Editor: Ungespeicherte Änderung – Benutzer hat verworfen");
|
||||
LOG.debug(DIRTY_STATE_MSG);
|
||||
}
|
||||
if (!savedConfigurationReadyCheck.getAsBoolean()) {
|
||||
showMessage(NO_SAVED_CONFIGURATION_HINT);
|
||||
@@ -1317,7 +1322,7 @@ public final class GuiBatchRunTab {
|
||||
return;
|
||||
}
|
||||
fileNameEditor.discardChanges();
|
||||
LOG.debug("Dateiname-Editor: Ungespeicherte Änderung – Benutzer hat verworfen");
|
||||
LOG.debug(DIRTY_STATE_MSG);
|
||||
}
|
||||
if (!savedConfigurationReadyCheck.getAsBoolean()) {
|
||||
showMessage(NO_SAVED_CONFIGURATION_HINT);
|
||||
@@ -1562,35 +1567,12 @@ public final class GuiBatchRunTab {
|
||||
return builder.toString();
|
||||
}
|
||||
if (row.status() == DocumentCompletionStatus.SKIPPED_ALREADY_PROCESSED) {
|
||||
builder.append('\n');
|
||||
row.historicalContext().ifPresentOrElse(ctx -> {
|
||||
ctx.lastSuccessInstant().ifPresentOrElse(
|
||||
instant -> builder.append("Bereits erfolgreich verarbeitet am ")
|
||||
.append(DETAIL_DATE_FORMAT.format(
|
||||
instant.atZone(ZoneId.systemDefault())))
|
||||
.append('.'),
|
||||
() -> builder.append("Bereits erfolgreich verarbeitet."));
|
||||
ctx.lastTargetFileName().ifPresent(name ->
|
||||
builder.append('\n').append("Zieldatei: ").append(name).append('.'));
|
||||
}, () -> builder.append("Bereits erfolgreich verarbeitet."));
|
||||
return builder.toString();
|
||||
return appendSkippedAlreadyProcessed(builder, row);
|
||||
}
|
||||
if (row.status() == DocumentCompletionStatus.SKIPPED_FINAL_FAILURE) {
|
||||
builder.append('\n');
|
||||
row.historicalContext().ifPresentOrElse(ctx ->
|
||||
ctx.lastFailureInstant().ifPresentOrElse(
|
||||
instant -> builder.append("Endg\u00fcltig fehlgeschlagen am ")
|
||||
.append(DETAIL_DATE_FORMAT.format(
|
||||
instant.atZone(ZoneId.systemDefault())))
|
||||
.append(". Erneute Verarbeitung nur nach Reset m\u00f6glich."),
|
||||
() -> builder.append(
|
||||
"Endg\u00fcltig fehlgeschlagen. Erneute Verarbeitung nur nach Reset m\u00f6glich.")),
|
||||
() -> builder.append(
|
||||
"Endg\u00fcltig fehlgeschlagen. Erneute Verarbeitung nur nach Reset m\u00f6glich."));
|
||||
return builder.toString();
|
||||
return appendSkippedFinalFailure(builder, row);
|
||||
}
|
||||
if (row.status() == DocumentCompletionStatus.FAILED_PERMANENT) {
|
||||
// Erweiterter Erkl\u00e4rungstext gem\u00e4\u00df Spezifikation #51 \u2013 dauerhaft fehlgeschlagen
|
||||
builder.append('\n').append(ProcessingStatusPresentation.DETAIL_TEXT_FAILED_PERMANENT);
|
||||
row.aiFailureMessage().ifPresent(msg ->
|
||||
builder.append("\n\nFehlerdetail: ")
|
||||
@@ -1611,6 +1593,34 @@ public final class GuiBatchRunTab {
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
private static String appendSkippedAlreadyProcessed(StringBuilder builder, GuiBatchRunResultRow row) {
|
||||
builder.append('\n');
|
||||
row.historicalContext().ifPresentOrElse(ctx -> {
|
||||
ctx.lastSuccessInstant().ifPresentOrElse(
|
||||
instant -> builder.append("Bereits erfolgreich verarbeitet am ")
|
||||
.append(DETAIL_DATE_FORMAT.format(instant.atZone(ZoneId.systemDefault())))
|
||||
.append('.'),
|
||||
() -> builder.append("Bereits erfolgreich verarbeitet."));
|
||||
ctx.lastTargetFileName().ifPresent(name ->
|
||||
builder.append('\n').append("Zieldatei: ").append(name).append('.'));
|
||||
}, () -> builder.append("Bereits erfolgreich verarbeitet."));
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
private static String appendSkippedFinalFailure(StringBuilder builder, GuiBatchRunResultRow row) {
|
||||
builder.append('\n');
|
||||
row.historicalContext().ifPresentOrElse(ctx ->
|
||||
ctx.lastFailureInstant().ifPresentOrElse(
|
||||
instant -> builder.append("Endg\u00fcltig fehlgeschlagen am ")
|
||||
.append(DETAIL_DATE_FORMAT.format(instant.atZone(ZoneId.systemDefault())))
|
||||
.append(". Erneute Verarbeitung nur nach Reset m\u00f6glich."),
|
||||
() -> builder.append(
|
||||
"Endg\u00fcltig fehlgeschlagen. Erneute Verarbeitung nur nach Reset m\u00f6glich.")),
|
||||
() -> builder.append(
|
||||
"Endg\u00fcltig fehlgeschlagen. Erneute Verarbeitung nur nach Reset m\u00f6glich."));
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
private static GuiBatchRunLaunchOutcome rejectingMiniLaunch(
|
||||
Path p, Set<DocumentFingerprint> f,
|
||||
de.gecheckt.pdf.umbenenner.application.port.in.BatchRunProgressObserver o,
|
||||
|
||||
+9
-6
@@ -20,6 +20,9 @@ import de.gecheckt.pdf.umbenenner.domain.model.ProcessingStatus;
|
||||
* Alle Methoden sind statisch.
|
||||
*/
|
||||
public final class ProcessingStatusPresentation {
|
||||
private static final String STATUS_NOT_NULL = "status darf nicht null sein";
|
||||
|
||||
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Icons (Unicode-Zeichen, zuverlässig darstellbar unter Windows 10+)
|
||||
@@ -166,7 +169,7 @@ public final class ProcessingStatusPresentation {
|
||||
* @throws NullPointerException wenn {@code status} {@code null} ist
|
||||
*/
|
||||
public static String iconFor(DocumentCompletionStatus status) {
|
||||
Objects.requireNonNull(status, "status darf nicht null sein");
|
||||
Objects.requireNonNull(status, STATUS_NOT_NULL);
|
||||
return switch (status) {
|
||||
case SUCCESS -> ICON_SUCCESS;
|
||||
case FAILED_RETRYABLE -> ICON_FAILED_RETRYABLE;
|
||||
@@ -187,7 +190,7 @@ public final class ProcessingStatusPresentation {
|
||||
* @throws NullPointerException wenn {@code status} {@code null} ist
|
||||
*/
|
||||
public static String cssColorFor(DocumentCompletionStatus status) {
|
||||
Objects.requireNonNull(status, "status darf nicht null sein");
|
||||
Objects.requireNonNull(status, STATUS_NOT_NULL);
|
||||
return switch (status) {
|
||||
case SUCCESS -> COLOR_SUCCESS;
|
||||
case FAILED_RETRYABLE -> COLOR_FAILED_RETRYABLE;
|
||||
@@ -205,7 +208,7 @@ public final class ProcessingStatusPresentation {
|
||||
* @throws NullPointerException wenn {@code status} {@code null} ist
|
||||
*/
|
||||
public static String tooltipFor(DocumentCompletionStatus status) {
|
||||
Objects.requireNonNull(status, "status darf nicht null sein");
|
||||
Objects.requireNonNull(status, STATUS_NOT_NULL);
|
||||
return switch (status) {
|
||||
case SUCCESS -> TOOLTIP_SUCCESS;
|
||||
case FAILED_RETRYABLE -> TOOLTIP_FAILED_RETRYABLE;
|
||||
@@ -224,7 +227,7 @@ public final class ProcessingStatusPresentation {
|
||||
* @throws NullPointerException wenn {@code status} {@code null} ist
|
||||
*/
|
||||
public static String summaryCategoryFor(DocumentCompletionStatus status) {
|
||||
Objects.requireNonNull(status, "status darf nicht null sein");
|
||||
Objects.requireNonNull(status, STATUS_NOT_NULL);
|
||||
return switch (status) {
|
||||
case SUCCESS -> SUMMARY_CATEGORY_SUCCESS;
|
||||
case FAILED_RETRYABLE -> SUMMARY_CATEGORY_FAILED_RETRYABLE;
|
||||
@@ -243,7 +246,7 @@ public final class ProcessingStatusPresentation {
|
||||
* @throws NullPointerException wenn {@code status} {@code null} ist
|
||||
*/
|
||||
public static StatusVisuals visualsFor(DocumentCompletionStatus status) {
|
||||
Objects.requireNonNull(status, "status darf nicht null sein");
|
||||
Objects.requireNonNull(status, STATUS_NOT_NULL);
|
||||
return new StatusVisuals(
|
||||
iconFor(status),
|
||||
cssColorFor(status),
|
||||
@@ -264,7 +267,7 @@ public final class ProcessingStatusPresentation {
|
||||
* @throws NullPointerException wenn {@code status} {@code null} ist
|
||||
*/
|
||||
public static String displayTextFor(ProcessingStatus status) {
|
||||
Objects.requireNonNull(status, "status darf nicht null sein");
|
||||
Objects.requireNonNull(status, STATUS_NOT_NULL);
|
||||
return switch (status) {
|
||||
case SUCCESS -> "✓ Erfolgreich";
|
||||
case FAILED_RETRYABLE -> "↻ Temporärer Fehler";
|
||||
|
||||
+51
-40
@@ -87,6 +87,11 @@ import javafx.scene.layout.VBox;
|
||||
* Verarbeitungslaufs deaktiviert.
|
||||
*/
|
||||
public final class GuiHistoryTab {
|
||||
private static final String BOLD_STYLE = "-fx-font-weight: bold;";
|
||||
private static final String NO_ERROR_DETAILS_MSG = "Keine Fehlerdetails gespeichert.";
|
||||
private static final String NO_CONFIG_LOADED_MSG = "Keine Konfiguration geladen.";
|
||||
|
||||
|
||||
|
||||
private static final Logger LOG = LogManager.getLogger(GuiHistoryTab.class);
|
||||
|
||||
@@ -421,20 +426,20 @@ public final class GuiHistoryTab {
|
||||
addDetailRow(5, "Aktualisiert:", detailUpdatedLabel);
|
||||
|
||||
Label detailTitle = new Label("Dokument-Details");
|
||||
detailTitle.setStyle("-fx-font-weight: bold;");
|
||||
detailTitle.setStyle(BOLD_STYLE);
|
||||
|
||||
// Versuche-Tabelle
|
||||
buildAttemptsTable();
|
||||
Label attemptsTitle = new Label("Verarbeitungsversuche");
|
||||
attemptsTitle.setStyle("-fx-font-weight: bold;");
|
||||
attemptsTitle.setStyle(BOLD_STYLE);
|
||||
|
||||
// Fehlerursache (aus letztem Fehler-Versuch)
|
||||
failureArea.setEditable(false);
|
||||
failureArea.setWrapText(true);
|
||||
failureArea.setPrefRowCount(3);
|
||||
failureArea.setPromptText("Keine Fehlerdetails gespeichert.");
|
||||
failureArea.setPromptText(NO_ERROR_DETAILS_MSG);
|
||||
Label failureTitle = new Label("Fehlerursache (letzter Fehler-Versuch)");
|
||||
failureTitle.setStyle("-fx-font-weight: bold;");
|
||||
failureTitle.setStyle(BOLD_STYLE);
|
||||
|
||||
failureArea.setTooltip(new Tooltip(GuiTooltipTexts.VERLAUF_FAILURE_AREA));
|
||||
|
||||
@@ -445,7 +450,7 @@ public final class GuiHistoryTab {
|
||||
reasoningArea.setText(DETAIL_PLACEHOLDER);
|
||||
reasoningArea.setTooltip(new Tooltip(GuiTooltipTexts.VERLAUF_REASONING_AREA));
|
||||
Label reasoningTitle = new Label("KI-Begründung (ausgewählter Versuch)");
|
||||
reasoningTitle.setStyle("-fx-font-weight: bold;");
|
||||
reasoningTitle.setStyle(BOLD_STYLE);
|
||||
|
||||
VBox rightPane = new VBox(8,
|
||||
detailTitle, detailGrid,
|
||||
@@ -579,7 +584,7 @@ public final class GuiHistoryTab {
|
||||
Path configPath = configPathSupplier.get();
|
||||
if (configPath == null) {
|
||||
statusBarLabel.setText("Keine Konfiguration geladen – bitte zuerst eine Konfigurationsdatei öffnen.");
|
||||
overviewTable.setPlaceholder(new Label("Keine Konfiguration geladen."));
|
||||
overviewTable.setPlaceholder(new Label(NO_CONFIG_LOADED_MSG));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -666,7 +671,7 @@ public final class GuiHistoryTab {
|
||||
|
||||
Path configPath = configPathSupplier.get();
|
||||
if (configPath == null) {
|
||||
showInfo("Keine Konfiguration geladen.");
|
||||
showInfo(NO_CONFIG_LOADED_MSG);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -674,28 +679,10 @@ public final class GuiHistoryTab {
|
||||
.filter(r -> r.overallStatus() == ProcessingStatus.SUCCESS)
|
||||
.count();
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("Setzt den Status auf READY_FOR_AI zurück.\n");
|
||||
sb.append("Fehlerzähler und letzter Fehlerzeitpunkt werden gelöscht.\n");
|
||||
sb.append("Die Versuchshistorie bleibt vollständig erhalten.\n\n");
|
||||
if (selectedItems.size() == 1) {
|
||||
sb.append("Quelldatei: ").append(selectedItems.get(0).sourceFileName());
|
||||
} else {
|
||||
sb.append(selectedItems.size()).append(" Einträge werden zurückgesetzt.");
|
||||
}
|
||||
if (successCount > 0) {
|
||||
sb.append("\n\nHinweis: ").append(successCount)
|
||||
.append(" der ausgewählten Einträge ")
|
||||
.append(successCount == 1 ? "hat" : "haben")
|
||||
.append(" Status \"Erfolgreich\". ")
|
||||
.append(successCount == 1 ? "Dieser Eintrag wird" : "Diese Einträge werden")
|
||||
.append(" erneut verarbeitet.");
|
||||
}
|
||||
|
||||
Alert confirm = new Alert(Alert.AlertType.CONFIRMATION);
|
||||
confirm.setTitle("Status zurücksetzen");
|
||||
confirm.setHeaderText("Status zurücksetzen?");
|
||||
confirm.setContentText(sb.toString());
|
||||
confirm.setContentText(buildResetConfirmationText(selectedItems, successCount));
|
||||
Optional<ButtonType> choice = confirm.showAndWait();
|
||||
if (choice.isEmpty() || choice.get() != ButtonType.OK) return;
|
||||
|
||||
@@ -729,6 +716,27 @@ public final class GuiHistoryTab {
|
||||
});
|
||||
}
|
||||
|
||||
private static String buildResetConfirmationText(List<DocumentHistoryRow> selectedItems, long successCount) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("Setzt den Status auf READY_FOR_AI zurück.\n");
|
||||
sb.append("Fehlerzähler und letzter Fehlerzeitpunkt werden gelöscht.\n");
|
||||
sb.append("Die Versuchshistorie bleibt vollständig erhalten.\n\n");
|
||||
if (selectedItems.size() == 1) {
|
||||
sb.append("Quelldatei: ").append(selectedItems.get(0).sourceFileName());
|
||||
} else {
|
||||
sb.append(selectedItems.size()).append(" Einträge werden zurückgesetzt.");
|
||||
}
|
||||
if (successCount > 0) {
|
||||
sb.append("\n\nHinweis: ").append(successCount)
|
||||
.append(" der ausgewählten Einträge ")
|
||||
.append(successCount == 1 ? "hat" : "haben")
|
||||
.append(" Status \"Erfolgreich\". ")
|
||||
.append(successCount == 1 ? "Dieser Eintrag wird" : "Diese Einträge werden")
|
||||
.append(" erneut verarbeitet.");
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private void handleDeleteAction() {
|
||||
if (runningCheck.getAsBoolean()) {
|
||||
showInfo(LAUF_AKTIV_HINWEIS);
|
||||
@@ -741,7 +749,7 @@ public final class GuiHistoryTab {
|
||||
|
||||
Path configPath = configPathSupplier.get();
|
||||
if (configPath == null) {
|
||||
showInfo("Keine Konfiguration geladen.");
|
||||
showInfo(NO_CONFIG_LOADED_MSG);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -818,23 +826,26 @@ public final class GuiHistoryTab {
|
||||
// Fehlerursache aus letztem Fehler-Versuch anzeigen
|
||||
showLastFailureMessage(result.attempts(), record.overallStatus());
|
||||
|
||||
// Neuesten Versuch selektieren und Begründung anzeigen
|
||||
if (!result.attempts().isEmpty()) {
|
||||
ProcessingAttempt last = result.attempts().get(result.attempts().size() - 1);
|
||||
selectLatestAttemptAndShowReasoning(result.attempts());
|
||||
attemptsTable.getSelectionModel().selectedItemProperty().addListener(
|
||||
(obs, old, attempt) -> onAttemptSelected(attempt));
|
||||
}
|
||||
|
||||
private void selectLatestAttemptAndShowReasoning(java.util.List<ProcessingAttempt> attempts) {
|
||||
if (!attempts.isEmpty()) {
|
||||
ProcessingAttempt last = attempts.get(attempts.size() - 1);
|
||||
attemptsTable.getSelectionModel().select(last);
|
||||
showReasoning(last);
|
||||
} else {
|
||||
reasoningArea.setText("");
|
||||
reasoningArea.setPromptText(NO_REASONING_TEXT);
|
||||
}
|
||||
}
|
||||
|
||||
// KI-Begründung bei Versuchs-Selektion aktualisieren
|
||||
attemptsTable.getSelectionModel().selectedItemProperty().addListener(
|
||||
(obs, old, attempt) -> {
|
||||
if (attempt != null) {
|
||||
showReasoning(attempt);
|
||||
}
|
||||
});
|
||||
private void onAttemptSelected(ProcessingAttempt attempt) {
|
||||
if (attempt != null) {
|
||||
showReasoning(attempt);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -865,7 +876,7 @@ public final class GuiHistoryTab {
|
||||
|
||||
failureArea.setText(failureMessage != null
|
||||
? AiFailureMessageTranslator.translate(failureMessage) : "");
|
||||
failureArea.setPromptText("Keine Fehlerdetails gespeichert.");
|
||||
failureArea.setPromptText(NO_ERROR_DETAILS_MSG);
|
||||
}
|
||||
|
||||
private void showReasoning(ProcessingAttempt attempt) {
|
||||
@@ -883,7 +894,7 @@ public final class GuiHistoryTab {
|
||||
clearDetailFields();
|
||||
attemptsItems.clear();
|
||||
failureArea.setText("");
|
||||
failureArea.setPromptText("Keine Fehlerdetails gespeichert.");
|
||||
failureArea.setPromptText(NO_ERROR_DETAILS_MSG);
|
||||
reasoningArea.setText(DETAIL_PLACEHOLDER);
|
||||
}
|
||||
|
||||
@@ -906,7 +917,7 @@ public final class GuiHistoryTab {
|
||||
|
||||
private void addDetailRow(int row, String labelText, Label valueLabel) {
|
||||
Label label = new Label(labelText);
|
||||
label.setStyle("-fx-font-weight: bold;");
|
||||
label.setStyle(BOLD_STYLE);
|
||||
valueLabel.setMaxWidth(Double.MAX_VALUE);
|
||||
GridPane.setHgrow(valueLabel, Priority.ALWAYS);
|
||||
detailGrid.add(label, 0, row);
|
||||
|
||||
+36
-12
@@ -119,9 +119,15 @@ class GuiBatchRunCoordinatorMiniRunTest {
|
||||
void startReset_invokesResetPortAndDispatchesResult() {
|
||||
AtomicReference<ResetDocumentStatusResult> captured = new AtomicReference<>();
|
||||
GuiBatchRunCoordinator.Listener listener = new GuiBatchRunCoordinator.Listener() {
|
||||
@Override public void onRunStarted(RunId runId, int totalCandidates) { }
|
||||
@Override public void onDocumentCompleted(GuiBatchRunResultRow row) { }
|
||||
@Override public void onRunEnded(RunSummary summary, GuiBatchRunLaunchOutcome outcome) { }
|
||||
@Override public void onRunStarted(RunId runId, int totalCandidates) {
|
||||
// intentionally empty
|
||||
}
|
||||
@Override public void onDocumentCompleted(GuiBatchRunResultRow row) {
|
||||
// intentionally empty
|
||||
}
|
||||
@Override public void onRunEnded(RunSummary summary, GuiBatchRunLaunchOutcome outcome) {
|
||||
// intentionally empty
|
||||
}
|
||||
@Override public void onResetCompleted(ResetDocumentStatusResult result) {
|
||||
captured.set(result);
|
||||
}
|
||||
@@ -170,9 +176,15 @@ class GuiBatchRunCoordinatorMiniRunTest {
|
||||
void startReset_portThrowsException_mapsToAllFailures() {
|
||||
AtomicReference<ResetDocumentStatusResult> captured = new AtomicReference<>();
|
||||
GuiBatchRunCoordinator.Listener listener = new GuiBatchRunCoordinator.Listener() {
|
||||
@Override public void onRunStarted(RunId runId, int totalCandidates) { }
|
||||
@Override public void onDocumentCompleted(GuiBatchRunResultRow row) { }
|
||||
@Override public void onRunEnded(RunSummary summary, GuiBatchRunLaunchOutcome outcome) { }
|
||||
@Override public void onRunStarted(RunId runId, int totalCandidates) {
|
||||
// intentionally empty
|
||||
}
|
||||
@Override public void onDocumentCompleted(GuiBatchRunResultRow row) {
|
||||
// intentionally empty
|
||||
}
|
||||
@Override public void onRunEnded(RunSummary summary, GuiBatchRunLaunchOutcome outcome) {
|
||||
// intentionally empty
|
||||
}
|
||||
@Override public void onResetCompleted(ResetDocumentStatusResult result) {
|
||||
captured.set(result);
|
||||
}
|
||||
@@ -198,9 +210,15 @@ class GuiBatchRunCoordinatorMiniRunTest {
|
||||
void listenerDefaultOnResetCompleted_doesNotThrow() {
|
||||
// Verify the default implementation is safe to call.
|
||||
GuiBatchRunCoordinator.Listener listener = new GuiBatchRunCoordinator.Listener() {
|
||||
@Override public void onRunStarted(RunId runId, int totalCandidates) { }
|
||||
@Override public void onDocumentCompleted(GuiBatchRunResultRow row) { }
|
||||
@Override public void onRunEnded(RunSummary summary, GuiBatchRunLaunchOutcome outcome) { }
|
||||
@Override public void onRunStarted(RunId runId, int totalCandidates) {
|
||||
// intentionally empty
|
||||
}
|
||||
@Override public void onDocumentCompleted(GuiBatchRunResultRow row) {
|
||||
// intentionally empty
|
||||
}
|
||||
@Override public void onRunEnded(RunSummary summary, GuiBatchRunLaunchOutcome outcome) {
|
||||
// intentionally empty
|
||||
}
|
||||
};
|
||||
listener.onResetCompleted(new ResetDocumentStatusResult(0, Set.of(), Map.of()));
|
||||
}
|
||||
@@ -223,9 +241,15 @@ class GuiBatchRunCoordinatorMiniRunTest {
|
||||
|
||||
private static GuiBatchRunCoordinator.Listener noOpListener() {
|
||||
return new GuiBatchRunCoordinator.Listener() {
|
||||
@Override public void onRunStarted(RunId runId, int totalCandidates) { }
|
||||
@Override public void onDocumentCompleted(GuiBatchRunResultRow row) { }
|
||||
@Override public void onRunEnded(RunSummary summary, GuiBatchRunLaunchOutcome outcome) { }
|
||||
@Override public void onRunStarted(RunId runId, int totalCandidates) {
|
||||
// intentionally empty
|
||||
}
|
||||
@Override public void onDocumentCompleted(GuiBatchRunResultRow row) {
|
||||
// intentionally empty
|
||||
}
|
||||
@Override public void onRunEnded(RunSummary summary, GuiBatchRunLaunchOutcome outcome) {
|
||||
// intentionally empty
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
+21
-7
@@ -247,8 +247,12 @@ class GuiBatchRunCoordinatorTest {
|
||||
GuiBatchRunCoordinator coordinator = new GuiBatchRunCoordinator(
|
||||
launcher, syncThreadFactory(), syncDispatcher(),
|
||||
new GuiBatchRunCoordinator.Listener() {
|
||||
@Override public void onRunStarted(RunId runId, int totalCandidates) { }
|
||||
@Override public void onDocumentCompleted(GuiBatchRunResultRow row) { }
|
||||
@Override public void onRunStarted(RunId runId, int totalCandidates) {
|
||||
// intentionally empty
|
||||
}
|
||||
@Override public void onDocumentCompleted(GuiBatchRunResultRow row) {
|
||||
// intentionally empty
|
||||
}
|
||||
@Override public void onRunEnded(RunSummary summary, GuiBatchRunLaunchOutcome outcome) {
|
||||
captured.set(outcome);
|
||||
}
|
||||
@@ -270,8 +274,12 @@ class GuiBatchRunCoordinatorTest {
|
||||
GuiBatchRunCoordinator coordinator = new GuiBatchRunCoordinator(
|
||||
launcher, syncThreadFactory(), syncDispatcher(),
|
||||
new GuiBatchRunCoordinator.Listener() {
|
||||
@Override public void onRunStarted(RunId runId, int totalCandidates) { }
|
||||
@Override public void onDocumentCompleted(GuiBatchRunResultRow row) { }
|
||||
@Override public void onRunStarted(RunId runId, int totalCandidates) {
|
||||
// intentionally empty
|
||||
}
|
||||
@Override public void onDocumentCompleted(GuiBatchRunResultRow row) {
|
||||
// intentionally empty
|
||||
}
|
||||
@Override public void onRunEnded(RunSummary summary, GuiBatchRunLaunchOutcome outcome) {
|
||||
captured.set(outcome);
|
||||
}
|
||||
@@ -322,9 +330,15 @@ class GuiBatchRunCoordinatorTest {
|
||||
|
||||
private static GuiBatchRunCoordinator.Listener noOpListener() {
|
||||
return new GuiBatchRunCoordinator.Listener() {
|
||||
@Override public void onRunStarted(RunId runId, int totalCandidates) { }
|
||||
@Override public void onDocumentCompleted(GuiBatchRunResultRow row) { }
|
||||
@Override public void onRunEnded(RunSummary summary, GuiBatchRunLaunchOutcome outcome) { }
|
||||
@Override public void onRunStarted(RunId runId, int totalCandidates) {
|
||||
// intentionally empty
|
||||
}
|
||||
@Override public void onDocumentCompleted(GuiBatchRunResultRow row) {
|
||||
// intentionally empty
|
||||
}
|
||||
@Override public void onRunEnded(RunSummary summary, GuiBatchRunLaunchOutcome outcome) {
|
||||
// intentionally empty
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user