diff options
| author | Linnea Gräf <nea@nea.moe> | 2025-09-28 19:48:11 +0200 |
|---|---|---|
| committer | Linnea Gräf <nea@nea.moe> | 2025-10-13 18:26:42 +0200 |
| commit | 9b6c8f21f86385c993c0e34ba4b31348cae200cd (patch) | |
| tree | d310ffaf3e98af40c64d9895771026dd7ee4c56d /src/main/kotlin/gui | |
| parent | e8a42a056cb0209f8bbeb35dd74f42e5c2d8bd62 (diff) | |
| download | Firmament-9b6c8f21f86385c993c0e34ba4b31348cae200cd.tar.gz Firmament-9b6c8f21f86385c993c0e34ba4b31348cae200cd.tar.bz2 Firmament-9b6c8f21f86385c993c0e34ba4b31348cae200cd.zip | |
fix: improve config backups
Diffstat (limited to 'src/main/kotlin/gui')
4 files changed, 83 insertions, 17 deletions
diff --git a/src/main/kotlin/gui/config/storage/ConfigLoadContext.kt b/src/main/kotlin/gui/config/storage/ConfigLoadContext.kt index 59ca71e..4a06ec6 100644 --- a/src/main/kotlin/gui/config/storage/ConfigLoadContext.kt +++ b/src/main/kotlin/gui/config/storage/ConfigLoadContext.kt @@ -3,7 +3,10 @@ package moe.nea.firmament.gui.config.storage import java.io.PrintWriter import java.nio.file.Path import org.apache.commons.io.output.StringBuilderWriter +import kotlin.io.path.ExperimentalPathApi +import kotlin.io.path.OnErrorResult import kotlin.io.path.Path +import kotlin.io.path.copyToRecursively import kotlin.io.path.createParentDirectories import kotlin.io.path.writeText import moe.nea.firmament.Firmament @@ -11,6 +14,9 @@ import moe.nea.firmament.Firmament data class ConfigLoadContext( val loadId: String, ) : AutoCloseable { + val backupPath = Path("backups").resolve(Firmament.MOD_ID) + .resolve("config-$loadId") + .toAbsolutePath() val logFile = Path("logs") .resolve(Firmament.MOD_ID) .resolve("config-$loadId.log") @@ -74,4 +80,19 @@ data class ConfigLoadContext( } } } + + @OptIn(ExperimentalPathApi::class) + fun createBackup(folder: Path, string: String) { + val backupDestination = backupPath.resolve("$string-${System.currentTimeMillis()}") + logError("Creating backup of $folder in $backupDestination") + folder.copyToRecursively( + backupDestination.createParentDirectories(), + onError = { source: Path, target: Path, exception: Exception -> + logError("Failed to copy subtree $source to $target", exception) + OnErrorResult.SKIP_SUBTREE + }, + followLinks = false, + overwrite = false + ) + } } diff --git a/src/main/kotlin/gui/config/storage/FirmamentConfigLoader.kt b/src/main/kotlin/gui/config/storage/FirmamentConfigLoader.kt index f8e3104..a408136 100644 --- a/src/main/kotlin/gui/config/storage/FirmamentConfigLoader.kt +++ b/src/main/kotlin/gui/config/storage/FirmamentConfigLoader.kt @@ -13,6 +13,7 @@ import kotlin.io.path.listDirectoryEntries import kotlin.io.path.name import kotlin.io.path.readText import kotlin.io.path.writeText +import moe.nea.firmament.util.SBData.NULL_UUID import moe.nea.firmament.util.data.IConfigProvider import moe.nea.firmament.util.data.IDataHolder import moe.nea.firmament.util.data.ProfileKeyedConfig @@ -46,12 +47,15 @@ object FirmamentConfigLoader { loadConfigFromData(configData, Unit, ConfigStorageClass.CONFIG) val storageData = FirstLevelSplitJsonFolder(loadContext, storageFolder).load() loadConfigFromData(storageData, Unit, ConfigStorageClass.STORAGE) - val profileData = - profilePath.listDirectoryEntries() - .filter { it.isDirectory() } - .associate { + var profileData = + profilePath.takeIf { it.exists() } + ?.listDirectoryEntries() + ?.filter { it.isDirectory() } + ?.associate { UUID.fromString(it.name) to FirstLevelSplitJsonFolder(loadContext, it).load() } + if (profileData.isNullOrEmpty()) + profileData = mapOf(NULL_UUID to JsonObject(mapOf())) profileData.forEach { (key, value) -> loadConfigFromData(value, key, ConfigStorageClass.PROFILE) } @@ -60,12 +64,17 @@ object FirmamentConfigLoader { fun <T> loadConfigFromData( configData: JsonObject, - key: T, + key: T?, storageClass: ConfigStorageClass ) { for (holder in allConfigs) { if (holder.storageClass == storageClass) { - (holder as IDataHolder<T>).loadFrom(key, configData) + val h = (holder as IDataHolder<T>) + if (key == null) { + h.explicitDefaultLoad() + } else { + h.loadFrom(key, configData) + } } } } @@ -120,6 +129,7 @@ object FirmamentConfigLoader { FirstLevelSplitJsonFolder(context, profilePath.resolve(profileId.toString())) ) } + writeConfigVersion() } } @@ -170,16 +180,24 @@ object FirmamentConfigLoader { FirstLevelSplitJsonFolder(loadContext, it) ) } - configVersionFile.writeText("$currentConfigVersion ${tagLines.random()}") + writeConfigVersion() } } + fun writeConfigVersion() { + configVersionFile.writeText("$currentConfigVersion ${tagLines.random()}") + } + private fun updateOneConfig( loadContext: ConfigLoadContext, startVersion: Int, storageClass: ConfigStorageClass, firstLevelSplitJsonFolder: FirstLevelSplitJsonFolder ) { + if (startVersion == currentConfigVersion) { + loadContext.logDebug("Skipping upgrade to ") + return + } loadContext.logInfo("Starting upgrade from at ${firstLevelSplitJsonFolder.folder} ($storageClass) to $startVersion") var data = firstLevelSplitJsonFolder.load() for (nextVersion in (startVersion + 1)..currentConfigVersion) { diff --git a/src/main/kotlin/gui/config/storage/FirstLevelSplitJsonFolder.kt b/src/main/kotlin/gui/config/storage/FirstLevelSplitJsonFolder.kt index ff544d5..3c672bf 100644 --- a/src/main/kotlin/gui/config/storage/FirstLevelSplitJsonFolder.kt +++ b/src/main/kotlin/gui/config/storage/FirstLevelSplitJsonFolder.kt @@ -8,7 +8,9 @@ import kotlinx.serialization.json.JsonElement import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.decodeFromStream import kotlinx.serialization.json.encodeToStream +import kotlin.io.path.createDirectories import kotlin.io.path.deleteExisting +import kotlin.io.path.exists import kotlin.io.path.inputStream import kotlin.io.path.listDirectoryEntries import kotlin.io.path.nameWithoutExtension @@ -19,23 +21,41 @@ class FirstLevelSplitJsonFolder( val context: ConfigLoadContext, val folder: Path ) { + + var hasCreatedBackup = false + + fun backup(cause: String) { + if (hasCreatedBackup) return + hasCreatedBackup = true + context.createBackup(folder, cause) + } + fun load(): JsonObject { context.logInfo("Loading FLSJF from $folder") - return folder.listDirectoryEntries("*.json") - .mapNotNull(::loadIndividualFile) - .toMap() - .let(::JsonObject) - .also { context.logInfo("FLSJF from $folder - Voller Erfolg!") } + if (!folder.exists()) + return JsonObject(mapOf()) + return try { + folder.listDirectoryEntries("*.json") + .mapNotNull(::loadIndividualFile) + .toMap() + .let(::JsonObject) + .also { context.logInfo("FLSJF from $folder - Voller Erfolg!") } + } catch (ex: Exception) { + context.logError("Could not load files from $folder", ex) + backup("failed-load") + JsonObject(mapOf()) + } } fun loadIndividualFile(path: Path): Pair<String, JsonElement>? { - context.logInfo("Loading partial file from $path") + context.logDebug("Loading partial file from $path") return try { path.inputStream().use { path.nameWithoutExtension to Firmament.json.decodeFromStream(JsonElement.serializer(), it) } } catch (ex: Exception) { context.logError("Could not load file from $path", ex) + backup("failed-load") null } } @@ -43,6 +63,10 @@ class FirstLevelSplitJsonFolder( fun save(value: JsonObject) { context.logInfo("Saving FLSJF to $folder") context.logDebug("Current value:\n$value") + if (!folder.exists()) { + context.logInfo("Creating folder $folder") + folder.createDirectories() + } val entries = folder.listDirectoryEntries("*.json") .toMutableList() for ((name, element) in value) { @@ -55,7 +79,7 @@ class FirstLevelSplitJsonFolder( context.logInfo("Deleting additional files.") for (path in entries) { context.logInfo("Deleting $path") -// context.backup(path) + backup("save-deletion") try { path.deleteExisting() } catch (ex: Exception) { @@ -68,7 +92,7 @@ class FirstLevelSplitJsonFolder( fun saveIndividualFile(name: String, element: JsonElement): Path? { try { - context.logInfo("Saving partial file with name $name") + context.logDebug("Saving partial file with name $name") val path = folder.resolve("$name.json") context.ensureWritable(path) path.outputStream().use { @@ -77,6 +101,7 @@ class FirstLevelSplitJsonFolder( return path } catch (ex: Exception) { context.logError("Could not save $name with value $element", ex) + backup("failed-save") return null } } diff --git a/src/main/kotlin/gui/config/storage/LegacyImporter.kt b/src/main/kotlin/gui/config/storage/LegacyImporter.kt index d06afcc..c1f8b90 100644 --- a/src/main/kotlin/gui/config/storage/LegacyImporter.kt +++ b/src/main/kotlin/gui/config/storage/LegacyImporter.kt @@ -32,6 +32,7 @@ object LegacyImporter { ) fun importFromLegacy() { + if (!configFolder.exists()) return configFolder.moveTo(backupPath) configFolder.createDirectories() @@ -50,7 +51,8 @@ object LegacyImporter { } backupPath.resolve("profiles") - .forEachDirectoryEntry { category -> + .takeIf { it.exists() } + ?.forEachDirectoryEntry { category -> category.forEachDirectoryEntry { profile -> copyIf( profile, @@ -61,6 +63,6 @@ object LegacyImporter { } } - configVersionFile.writeText(legacyConfigVersion.toString()) + configVersionFile.writeText("$legacyConfigVersion LEGACY") } } |
