M10 vollständig abgeschlossen (AP-004 bis AP-007)
- AP-004: Speichern und Speichern unter mit .bak-Rotation, normalisierte .properties-Ausgabe, API-Key-Erhaltung bei leerem Feld - AP-005: Dirty-State aus Editorzustand, Fenstertitel- und Header-Marker, Schutzdialog (Speichern/Verwerfen/Abbrechen) vor Neu/Öffnen/Schließen inkl. Close-Request-Handler - AP-006: Vollständige Editoroberfläche mit allen Konfigurationswerten, native Pfad-Picker für Quell-/Zielordner, SQLite- und Prompt-Datei, Files.exists-Pfadprüfung auf Worker-Thread verlagert - AP-007: Integrations- und Regressionstests für alle zentralen Bedienpfade, Writer-Threading-Contract dokumentiert und getestet Hexagonale Architektur, Threadingmodell und Naming-Regel durchgehend eingehalten. Keine Vorgriffe auf M11/M12. Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
+937
-21
File diff suppressed because it is too large
Load Diff
+34
@@ -0,0 +1,34 @@
|
||||
package de.gecheckt.pdf.umbenenner.adapter.in.gui;
|
||||
|
||||
import java.nio.file.Path;
|
||||
|
||||
import de.gecheckt.pdf.umbenenner.adapter.in.gui.editor.GuiConfigurationValues;
|
||||
|
||||
/**
|
||||
* Writes a normalized {@code .properties} configuration file from the current editor values.
|
||||
* <p>
|
||||
* The interface allows Bootstrap to provide the concrete file-writing, backup and
|
||||
* normalization logic while the GUI only deals with editor values and target paths.
|
||||
* Implementations must follow the backup schema defined for this application:
|
||||
* {@code <filename>.bak}, and on collision {@code <filename>.bak.1}, {@code .bak.2}, ...
|
||||
* Existing backups are never overwritten.
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface GuiConfigurationFileWriter {
|
||||
|
||||
/**
|
||||
* Writes the given configuration values to the specified target path as a normalized
|
||||
* {@code .properties} file.
|
||||
* <p>
|
||||
* When {@code targetPath} already exists on disk, the implementation must create a
|
||||
* {@code .bak} backup of the existing file before overwriting it. The caller is
|
||||
* responsible for obtaining user confirmation before invoking this method.
|
||||
*
|
||||
* @param values the current editor values to serialize; must not be {@code null}
|
||||
* @param targetPath the target file path to write to; must not be {@code null}
|
||||
* @return the result of the write operation, including any API-key preservation note;
|
||||
* never {@code null}
|
||||
* @throws GuiConfigurationWriteException if the file cannot be written
|
||||
*/
|
||||
GuiConfigurationSaveResult write(GuiConfigurationValues values, Path targetPath);
|
||||
}
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
package de.gecheckt.pdf.umbenenner.adapter.in.gui;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Carries the outcome of a successful configuration file write operation.
|
||||
* <p>
|
||||
* The result separates the written file path from supplementary observations such as
|
||||
* API-key preservation events. This allows the GUI to update its header and editor
|
||||
* state without inspecting the written file again, and to forward the preservation
|
||||
* flag to later warning display logic without mixing that concern into the write
|
||||
* implementation itself.
|
||||
*
|
||||
* @param savedPath the path to which the file was written; never {@code null}
|
||||
* @param apiKeyPreservedForProvider identifier of the provider whose API key was silently
|
||||
* preserved because the GUI field was left empty while
|
||||
* the existing property value was non-empty; {@code null}
|
||||
* when no preservation occurred
|
||||
*/
|
||||
public record GuiConfigurationSaveResult(Path savedPath, String apiKeyPreservedForProvider) {
|
||||
|
||||
/**
|
||||
* Creates a save result.
|
||||
*
|
||||
* @param savedPath the path that was written; must not be {@code null}
|
||||
* @param apiKeyPreservedForProvider provider identifier when key was preserved; may be {@code null}
|
||||
*/
|
||||
public GuiConfigurationSaveResult {
|
||||
Objects.requireNonNull(savedPath, "savedPath must not be null");
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a save result with no API-key preservation event.
|
||||
*
|
||||
* @param savedPath the path that was written; must not be {@code null}
|
||||
* @return a result without an API-key preservation note
|
||||
*/
|
||||
public static GuiConfigurationSaveResult saved(Path savedPath) {
|
||||
return new GuiConfigurationSaveResult(savedPath, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a save result that records an API-key preservation event.
|
||||
*
|
||||
* @param savedPath the path that was written; must not be {@code null}
|
||||
* @param providerIdentifier the provider for which the key was preserved;
|
||||
* must not be {@code null}
|
||||
* @return a result carrying the preservation note for later display
|
||||
*/
|
||||
public static GuiConfigurationSaveResult savedWithPreservedKey(Path savedPath,
|
||||
String providerIdentifier) {
|
||||
Objects.requireNonNull(providerIdentifier, "providerIdentifier must not be null");
|
||||
return new GuiConfigurationSaveResult(savedPath, providerIdentifier);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether an API-key preservation event occurred during this write operation.
|
||||
*
|
||||
* @return {@code true} when at least one provider API key was silently preserved
|
||||
*/
|
||||
public boolean hasApiKeyPreservationNote() {
|
||||
return apiKeyPreservedForProvider != null;
|
||||
}
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
package de.gecheckt.pdf.umbenenner.adapter.in.gui;
|
||||
|
||||
/**
|
||||
* Thrown when a configuration file cannot be written by the GUI file writer.
|
||||
* <p>
|
||||
* This exception wraps low-level I/O failures so that the GUI layer does not have
|
||||
* to handle raw {@link java.io.IOException} instances directly.
|
||||
*/
|
||||
public class GuiConfigurationWriteException extends RuntimeException {
|
||||
|
||||
/**
|
||||
* Creates an exception with the given message.
|
||||
*
|
||||
* @param message the error description; must not be {@code null}
|
||||
*/
|
||||
public GuiConfigurationWriteException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an exception with the given message and cause.
|
||||
*
|
||||
* @param message the error description; must not be {@code null}
|
||||
* @param cause the underlying cause; may be {@code null}
|
||||
*/
|
||||
public GuiConfigurationWriteException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
||||
+10
-5
@@ -9,13 +9,14 @@ import de.gecheckt.pdf.umbenenner.adapter.in.gui.editor.GuiConfigurationEditorSt
|
||||
/**
|
||||
* Immutable startup data for the GUI adapter.
|
||||
* <p>
|
||||
* Carries the initial editor state, the optional startup notice and the file-loading callback
|
||||
* that the workspace uses for native open actions.
|
||||
* Carries the initial editor state, the optional startup notice, the file-loading callback
|
||||
* and the file-writing callback that the workspace uses for native save actions.
|
||||
*/
|
||||
public record GuiStartupContext(
|
||||
GuiConfigurationEditorState initialState,
|
||||
Optional<String> startupNotice,
|
||||
GuiConfigurationFileLoader configurationFileLoader) {
|
||||
GuiConfigurationFileLoader configurationFileLoader,
|
||||
GuiConfigurationFileWriter configurationFileWriter) {
|
||||
|
||||
/**
|
||||
* Creates a startup context.
|
||||
@@ -23,16 +24,19 @@ public record GuiStartupContext(
|
||||
* @param initialState initial editor state; must not be {@code null}
|
||||
* @param startupNotice optional startup notice; {@code null} becomes empty
|
||||
* @param configurationFileLoader file-loading callback; must not be {@code null}
|
||||
* @param configurationFileWriter file-writing callback; must not be {@code null}
|
||||
*/
|
||||
public GuiStartupContext {
|
||||
initialState = Objects.requireNonNull(initialState, "initialState must not be null");
|
||||
startupNotice = startupNotice == null ? Optional.empty() : startupNotice;
|
||||
configurationFileLoader = Objects.requireNonNull(configurationFileLoader,
|
||||
"configurationFileLoader must not be null");
|
||||
configurationFileWriter = Objects.requireNonNull(configurationFileWriter,
|
||||
"configurationFileWriter must not be null");
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a blank startup context with no loader side effects.
|
||||
* Creates a blank startup context with no loader or writer side effects.
|
||||
*
|
||||
* @param startupNotice optional startup notice; {@code null} becomes empty
|
||||
* @return a startup context for the unloaded editor start
|
||||
@@ -41,6 +45,7 @@ public record GuiStartupContext(
|
||||
return new GuiStartupContext(
|
||||
GuiConfigurationEditorStateFactory.createBlankStartState(),
|
||||
startupNotice,
|
||||
configFilePath -> GuiConfigurationEditorStateFactory.createBlankStartState());
|
||||
configFilePath -> GuiConfigurationEditorStateFactory.createBlankStartState(),
|
||||
(values, path) -> GuiConfigurationSaveResult.saved(path));
|
||||
}
|
||||
}
|
||||
|
||||
+87
@@ -0,0 +1,87 @@
|
||||
package de.gecheckt.pdf.umbenenner.adapter.in.gui;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* Mediates the three-way protection dialog before any action that would discard unsaved changes.
|
||||
* <p>
|
||||
* The guard asks the user whether to save, discard or cancel the requested action.
|
||||
* The dialog interaction is injected via a {@link Function} so the guard can be tested
|
||||
* without a running JavaFX scene by substituting the real dialog with a stub.
|
||||
*
|
||||
* <p>Usage:
|
||||
* <ol>
|
||||
* <li>Obtain an instance from the workspace.</li>
|
||||
* <li>Call {@link #askAndProceed(String, Runnable, Runnable)} with the intended follow-up action.</li>
|
||||
* <li>The guard shows the dialog when the editor is dirty and runs the follow-up only when
|
||||
* it is safe to proceed.</li>
|
||||
* </ol>
|
||||
*/
|
||||
public final class GuiUnsavedChangesGuard {
|
||||
|
||||
/**
|
||||
* The possible responses the user can give to the protection dialog.
|
||||
*/
|
||||
public enum Choice {
|
||||
/** Save the current changes and then continue with the requested action. */
|
||||
SAVE,
|
||||
/** Discard all unsaved changes and continue with the requested action. */
|
||||
DISCARD,
|
||||
/** Cancel the requested action; no state change is performed. */
|
||||
CANCEL
|
||||
}
|
||||
|
||||
/**
|
||||
* Supplies the user's choice for a given trigger label.
|
||||
* <p>
|
||||
* In production the function shows a modal dialog; in tests it can be replaced with a stub.
|
||||
*/
|
||||
private Function<String, Choice> dialogSupplier;
|
||||
|
||||
/**
|
||||
* Creates a guard that delegates the dialog interaction to the supplied function.
|
||||
*
|
||||
* @param dialogSupplier function that maps a trigger label to the user's choice; must not be {@code null}
|
||||
*/
|
||||
public GuiUnsavedChangesGuard(Function<String, Choice> dialogSupplier) {
|
||||
this.dialogSupplier = dialogSupplier;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces the dialog supplier at runtime.
|
||||
* <p>
|
||||
* Package-private so tests can inject stubs without exposing setter to external callers.
|
||||
*
|
||||
* @param dialogSupplier the replacement function; must not be {@code null}
|
||||
*/
|
||||
void setDialogSupplier(Function<String, Choice> dialogSupplier) {
|
||||
this.dialogSupplier = dialogSupplier;
|
||||
}
|
||||
|
||||
/**
|
||||
* Asks the user how to handle unsaved changes before the named action and invokes the
|
||||
* appropriate callback.
|
||||
*
|
||||
* <ul>
|
||||
* <li>{@link Choice#SAVE} → {@code onSave} is called; the caller must invoke
|
||||
* {@code onProceed} itself after a successful save.</li>
|
||||
* <li>{@link Choice#DISCARD} → {@code onProceed} is called immediately.</li>
|
||||
* <li>{@link Choice#CANCEL} → neither callback is called.</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param triggerLabel a short label identifying the triggering action (e.g. "Neu", "Öffnen");
|
||||
* used to give the dialog context; must not be {@code null}
|
||||
* @param onProceed action to run when the user chose discard; must not be {@code null}
|
||||
* @param onSave action to run when the user chose save; must not be {@code null}
|
||||
*/
|
||||
public void askAndProceed(String triggerLabel, Runnable onProceed, Runnable onSave) {
|
||||
Choice choice = dialogSupplier.apply(triggerLabel);
|
||||
switch (choice) {
|
||||
case SAVE -> onSave.run();
|
||||
case DISCARD -> onProceed.run();
|
||||
case CANCEL -> {
|
||||
// No action – caller keeps the current state.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
package de.gecheckt.pdf.umbenenner.adapter.in.gui;
|
||||
|
||||
import de.gecheckt.pdf.umbenenner.adapter.in.gui.editor.GuiChangeState;
|
||||
import de.gecheckt.pdf.umbenenner.adapter.in.gui.editor.GuiConfigurationEditorState;
|
||||
|
||||
/**
|
||||
* Formats the window title string for the PDF-Umbenenner GUI editor.
|
||||
* <p>
|
||||
* The title reflects the current editor state: whether a file is loaded and whether the
|
||||
* editor contains unsaved changes. The application name and the separator are kept in
|
||||
* one place so every part of the GUI uses the same formatting convention.
|
||||
*
|
||||
* <ul>
|
||||
* <li>Clean state with loaded file: {@code "PDF-Umbenenner — <filename>"}</li>
|
||||
* <li>Clean state without file (new configuration): {@code "PDF-Umbenenner — Neue Konfiguration"}</li>
|
||||
* <li>Dirty state: the same formats with a leading {@code "* "} prefix</li>
|
||||
* </ul>
|
||||
*/
|
||||
public final class GuiWindowTitleFormatter {
|
||||
|
||||
/** The application name shown in every window title variant. */
|
||||
static final String APPLICATION_NAME = "PDF-Umbenenner";
|
||||
|
||||
/** Separator placed between the application name and the context section. */
|
||||
static final String SEPARATOR = " \u2014 ";
|
||||
|
||||
/** Prefix added to the title when the editor contains unsaved changes. */
|
||||
static final String DIRTY_PREFIX = "* ";
|
||||
|
||||
/** Context label used when no file has been loaded yet. */
|
||||
static final String NEW_CONFIGURATION_LABEL = "Neue Konfiguration";
|
||||
|
||||
private GuiWindowTitleFormatter() {
|
||||
// Utility class.
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats the window title for the given editor state.
|
||||
*
|
||||
* @param editorState the current editor state; must not be {@code null}
|
||||
* @return the formatted window title string; never {@code null}
|
||||
*/
|
||||
public static String format(GuiConfigurationEditorState editorState) {
|
||||
String contextPart = buildContextPart(editorState);
|
||||
String base = APPLICATION_NAME + SEPARATOR + contextPart;
|
||||
if (editorState.changeState() == GuiChangeState.DIRTY) {
|
||||
return DIRTY_PREFIX + base;
|
||||
}
|
||||
return base;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the context portion of the title (the part after the separator).
|
||||
*
|
||||
* @param editorState the current editor state; must not be {@code null}
|
||||
* @return the context string; never {@code null}
|
||||
*/
|
||||
private static String buildContextPart(GuiConfigurationEditorState editorState) {
|
||||
if (editorState.isNewConfiguration()) {
|
||||
return NEW_CONFIGURATION_LABEL;
|
||||
}
|
||||
String fullPath = editorState.loadedFileSnapshot()
|
||||
.map(snapshot -> snapshot.filePath().getFileName())
|
||||
.map(Object::toString)
|
||||
.orElse(NEW_CONFIGURATION_LABEL);
|
||||
return fullPath;
|
||||
}
|
||||
}
|
||||
+18
-4
@@ -13,11 +13,15 @@ import org.apache.logging.log4j.Logger;
|
||||
* The application starts the editor shell in a clean, unloaded state unless Bootstrap
|
||||
* has provided a preloaded startup context. The visible editor surface is delegated to
|
||||
* {@link GuiConfigurationEditorWorkspace}.
|
||||
*
|
||||
* <p>The window title is kept in sync with the workspace's dirty state via the
|
||||
* {@code titleUpdateListener} hook. The close-request handler is installed through
|
||||
* {@link GuiConfigurationEditorWorkspace#installCloseRequestHandler(Stage)} so that
|
||||
* unsaved changes are protected when the user tries to close the window.
|
||||
*/
|
||||
public class PdfUmbenennerGuiApplication extends Application {
|
||||
|
||||
private static final Logger LOG = LogManager.getLogger(PdfUmbenennerGuiApplication.class);
|
||||
private static final String WINDOW_TITLE = "PDF-Umbenenner";
|
||||
private static final double DEFAULT_WIDTH = 1100;
|
||||
private static final double DEFAULT_HEIGHT = 800;
|
||||
|
||||
@@ -30,6 +34,10 @@ public class PdfUmbenennerGuiApplication extends Application {
|
||||
|
||||
/**
|
||||
* Initializes and shows the primary stage.
|
||||
* <p>
|
||||
* Wires the workspace title-update listener to the stage title so any dirty-state change
|
||||
* causes an immediate window-title refresh. Also installs the close-request handler that
|
||||
* guards unsaved changes before the window is closed.
|
||||
*
|
||||
* @param primaryStage the primary stage provided by the JavaFX runtime; never {@code null}
|
||||
*/
|
||||
@@ -39,11 +47,17 @@ public class PdfUmbenennerGuiApplication extends Application {
|
||||
|
||||
GuiStartupContext startupContext = GuiStartupContextHolder.currentOrBlank();
|
||||
GuiConfigurationEditorWorkspace workspace = new GuiConfigurationEditorWorkspace(startupContext);
|
||||
Scene scene = new Scene(workspace.root(), DEFAULT_WIDTH, DEFAULT_HEIGHT);
|
||||
|
||||
primaryStage.setTitle(WINDOW_TITLE);
|
||||
// Wire the title-update listener so the stage title stays in sync with the dirty state.
|
||||
workspace.titleUpdateListener = primaryStage::setTitle;
|
||||
|
||||
Scene scene = new Scene(workspace.root(), DEFAULT_WIDTH, DEFAULT_HEIGHT);
|
||||
primaryStage.setTitle(GuiWindowTitleFormatter.format(workspace.editorState()));
|
||||
primaryStage.setScene(scene);
|
||||
primaryStage.setOnCloseRequest(event -> LOG.info("GUI: Fenster wird vom Benutzer geschlossen."));
|
||||
|
||||
// Install the close-request handler that protects unsaved changes.
|
||||
workspace.installCloseRequestHandler(primaryStage);
|
||||
|
||||
primaryStage.show();
|
||||
|
||||
LOG.info("GUI: Hauptfenster erfolgreich angezeigt.");
|
||||
|
||||
+120
@@ -0,0 +1,120 @@
|
||||
package de.gecheckt.pdf.umbenenner.adapter.in.gui.editor;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import de.gecheckt.pdf.umbenenner.application.config.provider.AiProviderFamily;
|
||||
|
||||
/**
|
||||
* Merges the current editor API-key values against the baseline values before a file
|
||||
* is written to disk.
|
||||
* <p>
|
||||
* The merge rule is:
|
||||
* <ul>
|
||||
* <li>When a provider's API-key field is non-empty in the current editor values, the
|
||||
* current value is kept unchanged.</li>
|
||||
* <li>When a provider's API-key field is empty in the current editor values but the
|
||||
* corresponding baseline field holds a non-empty value, the baseline value is
|
||||
* carried into the merged result so the key is not silently deleted from the
|
||||
* written file.</li>
|
||||
* <li>When both the current and the baseline value are empty, no preservation occurs
|
||||
* and the merged result also contains an empty value.</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>The result indicates which provider (if any) triggered a preservation event so the
|
||||
* GUI can display a warning via later validation layers without coupling the write path
|
||||
* to the warning display mechanism.
|
||||
*/
|
||||
public final class GuiApiKeyMerger {
|
||||
|
||||
private GuiApiKeyMerger() {
|
||||
// Utility class.
|
||||
}
|
||||
|
||||
/**
|
||||
* Merges the API-key values from the given editor state and returns both the merged
|
||||
* values and the first provider identifier for which a key was silently preserved.
|
||||
*
|
||||
* @param state the current editor state; must not be {@code null}
|
||||
* @return the merge result; never {@code null}
|
||||
*/
|
||||
public static MergeResult merge(GuiConfigurationEditorState state) {
|
||||
return merge(state.values(), state.baselineValues());
|
||||
}
|
||||
|
||||
/**
|
||||
* Merges the API-key values from the given current and baseline configuration values.
|
||||
*
|
||||
* @param current the current editor values; must not be {@code null}
|
||||
* @param baseline the baseline values to compare against; must not be {@code null}
|
||||
* @return the merge result; never {@code null}
|
||||
*/
|
||||
public static MergeResult merge(GuiConfigurationValues current, GuiConfigurationValues baseline) {
|
||||
Map<AiProviderFamily, GuiProviderConfigurationState> merged = new LinkedHashMap<>(
|
||||
current.providerConfigurations());
|
||||
|
||||
String preservedProvider = null;
|
||||
|
||||
for (AiProviderFamily family : AiProviderFamily.values()) {
|
||||
GuiProviderConfigurationState currentProvider = current.providerConfiguration(family);
|
||||
if (currentProvider == null) {
|
||||
continue;
|
||||
}
|
||||
String editorKey = currentProvider.apiKey().propertyValue();
|
||||
if (!editorKey.isBlank()) {
|
||||
continue;
|
||||
}
|
||||
GuiProviderConfigurationState baselineProvider = baseline.providerConfiguration(family);
|
||||
if (baselineProvider == null) {
|
||||
continue;
|
||||
}
|
||||
String baselineKey = baselineProvider.apiKey().propertyValue();
|
||||
if (baselineKey != null && !baselineKey.isBlank()) {
|
||||
merged.put(family, new GuiProviderConfigurationState(
|
||||
currentProvider.baseUrl(),
|
||||
currentProvider.model(),
|
||||
currentProvider.timeoutSeconds(),
|
||||
GuiProviderApiKeyState.unresolved(baselineKey)));
|
||||
if (preservedProvider == null) {
|
||||
preservedProvider = family.getIdentifier();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
GuiConfigurationValues mergedValues = new GuiConfigurationValues(
|
||||
current.sourceFolder(),
|
||||
current.targetFolder(),
|
||||
current.sqliteFile(),
|
||||
current.promptTemplateFile(),
|
||||
current.runtimeLockFile(),
|
||||
current.logDirectory(),
|
||||
current.logLevel(),
|
||||
current.maxRetriesTransient(),
|
||||
current.maxPages(),
|
||||
current.maxTextCharacters(),
|
||||
current.logAiSensitive(),
|
||||
current.activeProviderFamily(),
|
||||
merged);
|
||||
|
||||
return new MergeResult(mergedValues, preservedProvider);
|
||||
}
|
||||
|
||||
/**
|
||||
* Result of an API-key merge operation.
|
||||
*
|
||||
* @param values the merged configuration values; never {@code null}
|
||||
* @param preservedProviderIdentifier provider identifier when a key was preserved from
|
||||
* the baseline; {@code null} when no preservation occurred
|
||||
*/
|
||||
public record MergeResult(GuiConfigurationValues values, String preservedProviderIdentifier) {
|
||||
|
||||
/**
|
||||
* Returns whether at least one provider API key was silently preserved.
|
||||
*
|
||||
* @return {@code true} when a preservation event occurred
|
||||
*/
|
||||
public boolean hasPreservationNote() {
|
||||
return preservedProviderIdentifier != null;
|
||||
}
|
||||
}
|
||||
}
|
||||
+162
@@ -101,6 +101,168 @@ public record GuiConfigurationValues(
|
||||
logAiSensitive, providerFamily, providerConfigurations);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a copy with a different source-folder path.
|
||||
*
|
||||
* @param value new value; {@code null} becomes an empty string
|
||||
* @return a new configuration values object with the requested source folder
|
||||
*/
|
||||
public GuiConfigurationValues withSourceFolder(String value) {
|
||||
return new GuiConfigurationValues(value, targetFolder, sqliteFile, promptTemplateFile,
|
||||
runtimeLockFile, logDirectory, logLevel, maxRetriesTransient, maxPages, maxTextCharacters,
|
||||
logAiSensitive, activeProviderFamily, providerConfigurations);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a copy with a different target-folder path.
|
||||
*
|
||||
* @param value new value; {@code null} becomes an empty string
|
||||
* @return a new configuration values object with the requested target folder
|
||||
*/
|
||||
public GuiConfigurationValues withTargetFolder(String value) {
|
||||
return new GuiConfigurationValues(sourceFolder, value, sqliteFile, promptTemplateFile,
|
||||
runtimeLockFile, logDirectory, logLevel, maxRetriesTransient, maxPages, maxTextCharacters,
|
||||
logAiSensitive, activeProviderFamily, providerConfigurations);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a copy with a different SQLite file path.
|
||||
*
|
||||
* @param value new value; {@code null} becomes an empty string
|
||||
* @return a new configuration values object with the requested SQLite file path
|
||||
*/
|
||||
public GuiConfigurationValues withSqliteFile(String value) {
|
||||
return new GuiConfigurationValues(sourceFolder, targetFolder, value, promptTemplateFile,
|
||||
runtimeLockFile, logDirectory, logLevel, maxRetriesTransient, maxPages, maxTextCharacters,
|
||||
logAiSensitive, activeProviderFamily, providerConfigurations);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a copy with a different prompt-template file path.
|
||||
*
|
||||
* @param value new value; {@code null} becomes an empty string
|
||||
* @return a new configuration values object with the requested prompt-template file path
|
||||
*/
|
||||
public GuiConfigurationValues withPromptTemplateFile(String value) {
|
||||
return new GuiConfigurationValues(sourceFolder, targetFolder, sqliteFile, value,
|
||||
runtimeLockFile, logDirectory, logLevel, maxRetriesTransient, maxPages, maxTextCharacters,
|
||||
logAiSensitive, activeProviderFamily, providerConfigurations);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a copy with a different runtime lock file path.
|
||||
*
|
||||
* @param value new value; {@code null} becomes an empty string
|
||||
* @return a new configuration values object with the requested runtime lock file path
|
||||
*/
|
||||
public GuiConfigurationValues withRuntimeLockFile(String value) {
|
||||
return new GuiConfigurationValues(sourceFolder, targetFolder, sqliteFile, promptTemplateFile,
|
||||
value, logDirectory, logLevel, maxRetriesTransient, maxPages, maxTextCharacters,
|
||||
logAiSensitive, activeProviderFamily, providerConfigurations);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a copy with a different log directory path.
|
||||
*
|
||||
* @param value new value; {@code null} becomes an empty string
|
||||
* @return a new configuration values object with the requested log directory
|
||||
*/
|
||||
public GuiConfigurationValues withLogDirectory(String value) {
|
||||
return new GuiConfigurationValues(sourceFolder, targetFolder, sqliteFile, promptTemplateFile,
|
||||
runtimeLockFile, value, logLevel, maxRetriesTransient, maxPages, maxTextCharacters,
|
||||
logAiSensitive, activeProviderFamily, providerConfigurations);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a copy with a different log level.
|
||||
*
|
||||
* @param value new value; {@code null} becomes an empty string
|
||||
* @return a new configuration values object with the requested log level
|
||||
*/
|
||||
public GuiConfigurationValues withLogLevel(String value) {
|
||||
return new GuiConfigurationValues(sourceFolder, targetFolder, sqliteFile, promptTemplateFile,
|
||||
runtimeLockFile, logDirectory, value, maxRetriesTransient, maxPages, maxTextCharacters,
|
||||
logAiSensitive, activeProviderFamily, providerConfigurations);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a copy with a different transient-retry limit.
|
||||
*
|
||||
* @param value new value; {@code null} becomes an empty string
|
||||
* @return a new configuration values object with the requested retry limit
|
||||
*/
|
||||
public GuiConfigurationValues withMaxRetriesTransient(String value) {
|
||||
return new GuiConfigurationValues(sourceFolder, targetFolder, sqliteFile, promptTemplateFile,
|
||||
runtimeLockFile, logDirectory, logLevel, value, maxPages, maxTextCharacters,
|
||||
logAiSensitive, activeProviderFamily, providerConfigurations);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a copy with a different page limit.
|
||||
*
|
||||
* @param value new value; {@code null} becomes an empty string
|
||||
* @return a new configuration values object with the requested page limit
|
||||
*/
|
||||
public GuiConfigurationValues withMaxPages(String value) {
|
||||
return new GuiConfigurationValues(sourceFolder, targetFolder, sqliteFile, promptTemplateFile,
|
||||
runtimeLockFile, logDirectory, logLevel, maxRetriesTransient, value, maxTextCharacters,
|
||||
logAiSensitive, activeProviderFamily, providerConfigurations);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a copy with a different text-character limit.
|
||||
*
|
||||
* @param value new value; {@code null} becomes an empty string
|
||||
* @return a new configuration values object with the requested character limit
|
||||
*/
|
||||
public GuiConfigurationValues withMaxTextCharacters(String value) {
|
||||
return new GuiConfigurationValues(sourceFolder, targetFolder, sqliteFile, promptTemplateFile,
|
||||
runtimeLockFile, logDirectory, logLevel, maxRetriesTransient, maxPages, value,
|
||||
logAiSensitive, activeProviderFamily, providerConfigurations);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a copy with a different {@code log.ai.sensitive} value.
|
||||
*
|
||||
* @param value new raw boolean value; {@code null} becomes an empty string
|
||||
* @return a new configuration values object with the requested sensitive-log setting
|
||||
*/
|
||||
public GuiConfigurationValues withLogAiSensitive(String value) {
|
||||
return new GuiConfigurationValues(sourceFolder, targetFolder, sqliteFile, promptTemplateFile,
|
||||
runtimeLockFile, logDirectory, logLevel, maxRetriesTransient, maxPages, maxTextCharacters,
|
||||
value, activeProviderFamily, providerConfigurations);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a copy with a different provider-configurations map.
|
||||
*
|
||||
* @param providerConfigurations new provider map; must not be {@code null}
|
||||
* @return a new configuration values object with the requested provider configurations
|
||||
*/
|
||||
public GuiConfigurationValues withProviderConfigurations(
|
||||
Map<AiProviderFamily, GuiProviderConfigurationState> providerConfigurations) {
|
||||
return new GuiConfigurationValues(sourceFolder, targetFolder, sqliteFile, promptTemplateFile,
|
||||
runtimeLockFile, logDirectory, logLevel, maxRetriesTransient, maxPages, maxTextCharacters,
|
||||
logAiSensitive, activeProviderFamily, providerConfigurations);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a copy with a different configuration for one provider family.
|
||||
* <p>
|
||||
* All other provider-family entries are preserved unchanged.
|
||||
*
|
||||
* @param family the provider family to update; must not be {@code null}
|
||||
* @param state the new provider configuration state; must not be {@code null}
|
||||
* @return a new configuration values object with the updated provider configuration
|
||||
*/
|
||||
public GuiConfigurationValues withProviderConfiguration(AiProviderFamily family,
|
||||
GuiProviderConfigurationState state) {
|
||||
Map<AiProviderFamily, GuiProviderConfigurationState> updated =
|
||||
new java.util.LinkedHashMap<>(providerConfigurations);
|
||||
updated.put(family, state);
|
||||
return withProviderConfigurations(updated);
|
||||
}
|
||||
|
||||
private static String normalizeText(String value) {
|
||||
return value == null ? "" : value;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user