aboutsummaryrefslogtreecommitdiff
path: root/src/main/kotlin/gui
diff options
context:
space:
mode:
authorLinnea Gräf <nea@nea.moe>2025-09-28 19:48:11 +0200
committerLinnea Gräf <nea@nea.moe>2025-10-13 18:26:42 +0200
commit9b6c8f21f86385c993c0e34ba4b31348cae200cd (patch)
treed310ffaf3e98af40c64d9895771026dd7ee4c56d /src/main/kotlin/gui
parente8a42a056cb0209f8bbeb35dd74f42e5c2d8bd62 (diff)
downloadFirmament-9b6c8f21f86385c993c0e34ba4b31348cae200cd.tar.gz
Firmament-9b6c8f21f86385c993c0e34ba4b31348cae200cd.tar.bz2
Firmament-9b6c8f21f86385c993c0e34ba4b31348cae200cd.zip
fix: improve config backups
Diffstat (limited to 'src/main/kotlin/gui')
-rw-r--r--src/main/kotlin/gui/config/storage/ConfigLoadContext.kt21
-rw-r--r--src/main/kotlin/gui/config/storage/FirmamentConfigLoader.kt32
-rw-r--r--src/main/kotlin/gui/config/storage/FirstLevelSplitJsonFolder.kt41
-rw-r--r--src/main/kotlin/gui/config/storage/LegacyImporter.kt6
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")
}
}