M10 bis AP-003

This commit is contained in:
2026-04-20 13:07:19 +02:00
parent 20b847d821
commit 01414fc732
16 changed files with 1139 additions and 184 deletions
@@ -1,17 +1,28 @@
package de.gecheckt.pdf.umbenenner.bootstrap;
import java.io.IOException;
import java.io.StringReader;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.Instant;
import java.util.Objects;
import java.util.Optional;
import java.util.Properties;
import java.util.UUID;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import de.gecheckt.pdf.umbenenner.adapter.in.gui.GuiAdapter;
import de.gecheckt.pdf.umbenenner.adapter.in.gui.GuiConfigurationEditorWorkspace;
import de.gecheckt.pdf.umbenenner.adapter.in.gui.GuiConfigurationFileLoader;
import de.gecheckt.pdf.umbenenner.adapter.in.gui.GuiConfigurationLoadException;
import de.gecheckt.pdf.umbenenner.adapter.in.gui.GuiStartupContext;
import de.gecheckt.pdf.umbenenner.adapter.in.gui.editor.GuiConfigurationEditorState;
import de.gecheckt.pdf.umbenenner.adapter.in.gui.editor.GuiConfigurationEditorStateFactory;
import de.gecheckt.pdf.umbenenner.adapter.in.gui.editor.GuiConfigurationFileSnapshot;
import de.gecheckt.pdf.umbenenner.bootstrap.startup.StartupArguments;
import de.gecheckt.pdf.umbenenner.bootstrap.startup.StartupMode;
@@ -518,25 +529,11 @@ public class BootstrapRunner {
* @return exit code: 0 for normal GUI shutdown, 1 for any GUI startup failure
*/
private int startGuiMode(Optional<String> configPathOverride) {
Optional<String> startupNotice = Optional.empty();
if (configPathOverride.isPresent()) {
Path configPath = Paths.get(configPathOverride.get());
if (Files.exists(configPath)) {
LOG.info("GUI startup: configuration file confirmed at: {}", configPath.toAbsolutePath());
} else {
LOG.error("GUI startup: --config path not found: {}. Starting GUI without configuration override.",
configPath.toAbsolutePath());
startupNotice = Optional.of(
"Konfigurationsdatei nicht gefunden: " + configPath.toAbsolutePath()
+ "\nDie GUI startet ohne Konfigurationsdatei.");
}
}
GuiStartupContext startupContext = buildGuiStartupContext(configPathOverride);
LOG.info("GUI startup: launching GUI adapter.");
try {
GuiAdapter guiAdapter = guiAdapterFactory.create();
guiAdapter.start(startupNotice);
guiAdapter.start(startupContext);
LOG.info("GUI adapter terminated normally.");
return 0;
} catch (Exception e) {
@@ -615,6 +612,83 @@ public class BootstrapRunner {
.orElse(DEFAULT_CONFIG_PATH);
}
private GuiStartupContext buildGuiStartupContext(Optional<String> configPathOverride) {
GuiConfigurationFileLoader loader = this::loadGuiConfigurationState;
if (configPathOverride.isEmpty()) {
return new GuiStartupContext(
GuiConfigurationEditorStateFactory.createBlankStartState(),
Optional.empty(),
loader);
}
Path configPath = Paths.get(configPathOverride.get());
if (!Files.exists(configPath)) {
LOG.error("GUI startup: --config path not found: {}. Starting GUI without configuration override.",
configPath.toAbsolutePath());
return new GuiStartupContext(
GuiConfigurationEditorStateFactory.createBlankStartState(),
Optional.of("Konfigurationsdatei nicht gefunden: " + configPath.toAbsolutePath()
+ "\nDie GUI startet ohne Konfigurationsdatei."),
loader);
}
LOG.info("GUI startup: configuration file confirmed at: {}", configPath.toAbsolutePath());
try {
GuiConfigurationEditorState loadedState = loadGuiConfigurationState(configPath);
return new GuiStartupContext(loadedState, Optional.empty(), loader);
} catch (GuiConfigurationLoadException e) {
LOG.error("GUI startup: configuration could not be loaded, starting without it: {}",
e.getMessage(), e);
return new GuiStartupContext(
GuiConfigurationEditorStateFactory.createBlankStartState(),
Optional.of("Konfiguration konnte nicht geladen werden: " + e.getMessage()),
loader);
}
}
private GuiConfigurationEditorState loadGuiConfigurationState(Path configFilePath) {
try {
boolean legacyDetected = detectedLegacyConfiguration(configFilePath);
migrateConfigurationIfNeeded(configFilePath);
GuiConfigurationFileSnapshot snapshot = new GuiConfigurationFileSnapshot(
configFilePath,
readPropertiesSnapshot(configFilePath));
Optional<String> migrationMessage = legacyDetected
? Optional.of("Konfiguration wurde aus einer Legacy-Datei übernommen.")
: Optional.empty();
return GuiConfigurationEditorStateFactory.fromPropertiesSnapshot(snapshot, migrationMessage);
} catch (ConfigurationLoadingException e) {
throw new GuiConfigurationLoadException("Failed to load configuration from " + configFilePath, e);
}
}
private boolean detectedLegacyConfiguration(Path configFilePath) {
try {
String content = Files.readString(configFilePath, StandardCharsets.UTF_8);
Properties props = new Properties();
props.load(new StringReader(content.replace("\\", "\\\\")));
boolean hasLegacyKey = props.containsKey("api.baseUrl")
|| props.containsKey("api.model")
|| props.containsKey("api.timeoutSeconds")
|| props.containsKey("api.key");
return hasLegacyKey && !props.containsKey("ai.provider.active");
} catch (IOException e) {
throw new GuiConfigurationLoadException("Failed to inspect legacy configuration at " + configFilePath, e);
}
}
private Properties readPropertiesSnapshot(Path configFilePath) {
try {
String content = Files.readString(configFilePath, StandardCharsets.UTF_8);
Properties props = new Properties();
props.load(new StringReader(content.replace("\\", "\\\\")));
return props;
} catch (IOException e) {
throw new GuiConfigurationLoadException("Failed to create snapshot for " + configFilePath, e);
}
}
/**
* Runs the legacy configuration migration step against the effective configuration path.
* <p>