diff options
| author | Linnea Gräf <nea@nea.moe> | 2025-09-14 16:37:57 +0200 |
|---|---|---|
| committer | Linnea Gräf <nea@nea.moe> | 2025-09-14 16:37:57 +0200 |
| commit | 9abe9f46f04f188037687adb2740b32220ad21b2 (patch) | |
| tree | 48dbd9cdf48c59853310c0b2e9bc59801522400e /src/main/kotlin/gui/config | |
| parent | 2851c1d6834fafdaeb009dce2a3485df1388907e (diff) | |
| download | Firmament-9abe9f46f04f188037687adb2740b32220ad21b2.tar.gz Firmament-9abe9f46f04f188037687adb2740b32220ad21b2.tar.bz2 Firmament-9abe9f46f04f188037687adb2740b32220ad21b2.zip | |
snapshot
Diffstat (limited to 'src/main/kotlin/gui/config')
18 files changed, 443 insertions, 285 deletions
diff --git a/src/main/kotlin/gui/config/AllConfigsGui.kt b/src/main/kotlin/gui/config/AllConfigsGui.kt index f9ffd2d..0add10f 100644 --- a/src/main/kotlin/gui/config/AllConfigsGui.kt +++ b/src/main/kotlin/gui/config/AllConfigsGui.kt @@ -10,9 +10,11 @@ import moe.nea.firmament.commands.get import moe.nea.firmament.commands.thenArgument import moe.nea.firmament.commands.thenExecute import moe.nea.firmament.events.CommandEvent +import moe.nea.firmament.util.data.ManagedConfig import moe.nea.firmament.util.MC import moe.nea.firmament.util.MoulConfigUtils import moe.nea.firmament.util.ScreenUtil.setScreenLater +import moe.nea.firmament.util.data.Config object AllConfigsGui { // @@ -21,6 +23,7 @@ object AllConfigsGui { // RepoManager.Config // ) + FeatureManager.allFeatures.mapNotNull { it.config } + @Config object ConfigConfig : ManagedConfig("configconfig", Category.META) { val enableYacl by toggle("enable-yacl") { false } val enableMoulConfig by toggle("enable-moulconfig") { true } diff --git a/src/main/kotlin/gui/config/BooleanHandler.kt b/src/main/kotlin/gui/config/BooleanHandler.kt index 8592777..b954401 100644 --- a/src/main/kotlin/gui/config/BooleanHandler.kt +++ b/src/main/kotlin/gui/config/BooleanHandler.kt @@ -9,6 +9,7 @@ import kotlinx.serialization.json.JsonElement import kotlinx.serialization.json.JsonPrimitive import kotlinx.serialization.json.boolean import kotlinx.serialization.json.jsonPrimitive +import moe.nea.firmament.util.data.ManagedConfig class BooleanHandler(val config: ManagedConfig) : ManagedConfig.OptionHandler<Boolean> { override fun toJson(element: Boolean): JsonElement? { @@ -29,7 +30,7 @@ class BooleanHandler(val config: ManagedConfig) : ManagedConfig.OptionHandler<Bo override fun set(newValue: Boolean) { opt.set(newValue) - config.save() + config.markDirty() } }, 200) )) diff --git a/src/main/kotlin/gui/config/ChoiceHandler.kt b/src/main/kotlin/gui/config/ChoiceHandler.kt index 2ea3efc..9c3dda2 100644 --- a/src/main/kotlin/gui/config/ChoiceHandler.kt +++ b/src/main/kotlin/gui/config/ChoiceHandler.kt @@ -9,6 +9,7 @@ import kotlinx.serialization.json.JsonElement import kotlin.jvm.optionals.getOrNull import net.minecraft.util.StringIdentifiable import moe.nea.firmament.gui.CheckboxComponent +import moe.nea.firmament.util.data.ManagedConfig import moe.nea.firmament.util.ErrorUtil import moe.nea.firmament.util.json.KJsonOps diff --git a/src/main/kotlin/gui/config/ClickHandler.kt b/src/main/kotlin/gui/config/ClickHandler.kt index fa1c621..9ea83aa 100644 --- a/src/main/kotlin/gui/config/ClickHandler.kt +++ b/src/main/kotlin/gui/config/ClickHandler.kt @@ -5,6 +5,7 @@ package moe.nea.firmament.gui.config import io.github.notenoughupdates.moulconfig.gui.component.TextComponent import kotlinx.serialization.json.JsonElement import moe.nea.firmament.gui.FirmButtonComponent +import moe.nea.firmament.util.data.ManagedConfig class ClickHandler(val config: ManagedConfig, val runnable: () -> Unit) : ManagedConfig.OptionHandler<Unit> { override fun toJson(element: Unit): JsonElement? { diff --git a/src/main/kotlin/gui/config/ColourHandler.kt b/src/main/kotlin/gui/config/ColourHandler.kt index 83ce8ac..33daa6d 100644 --- a/src/main/kotlin/gui/config/ColourHandler.kt +++ b/src/main/kotlin/gui/config/ColourHandler.kt @@ -10,6 +10,7 @@ import kotlinx.serialization.encoding.Decoder import kotlinx.serialization.encoding.Encoder import kotlinx.serialization.json.Json import kotlinx.serialization.json.JsonElement +import moe.nea.firmament.util.data.ManagedConfig class ColourHandler(val config: ManagedConfig) : ManagedConfig.OptionHandler<ChromaColour> { @@ -73,7 +74,7 @@ class ColourHandler(val config: ManagedConfig) : opt.value.toLegacyString(), { opt.value = ChromaColour.forLegacyString(it) - config.save() + config.markDirty() }, { } ) diff --git a/src/main/kotlin/gui/config/DurationHandler.kt b/src/main/kotlin/gui/config/DurationHandler.kt index 32bec25..4800bf6 100644 --- a/src/main/kotlin/gui/config/DurationHandler.kt +++ b/src/main/kotlin/gui/config/DurationHandler.kt @@ -12,6 +12,7 @@ import kotlinx.serialization.json.JsonElement import kotlinx.serialization.json.JsonPrimitive import kotlinx.serialization.json.jsonPrimitive import kotlinx.serialization.json.long +import moe.nea.firmament.util.data.ManagedConfig import kotlin.time.Duration import kotlin.time.DurationUnit import kotlin.time.toDuration diff --git a/src/main/kotlin/gui/config/HudMetaHandler.kt b/src/main/kotlin/gui/config/HudMetaHandler.kt index 0637351..dcf5d8f 100644 --- a/src/main/kotlin/gui/config/HudMetaHandler.kt +++ b/src/main/kotlin/gui/config/HudMetaHandler.kt @@ -10,6 +10,7 @@ import net.minecraft.text.MutableText import net.minecraft.text.Text import moe.nea.firmament.Firmament import moe.nea.firmament.gui.FirmButtonComponent +import moe.nea.firmament.util.data.ManagedConfig import moe.nea.firmament.jarvis.JarvisIntegration import moe.nea.firmament.util.MC diff --git a/src/main/kotlin/gui/config/IntegerHandler.kt b/src/main/kotlin/gui/config/IntegerHandler.kt index fd10447..3e7be57 100644 --- a/src/main/kotlin/gui/config/IntegerHandler.kt +++ b/src/main/kotlin/gui/config/IntegerHandler.kt @@ -12,6 +12,7 @@ import kotlinx.serialization.json.JsonElement import kotlinx.serialization.json.JsonPrimitive import kotlinx.serialization.json.int import kotlinx.serialization.json.jsonPrimitive +import moe.nea.firmament.util.data.ManagedConfig import moe.nea.firmament.util.FirmFormatters class IntegerHandler(val config: ManagedConfig, val min: Int, val max: Int) : ManagedConfig.OptionHandler<Int> { diff --git a/src/main/kotlin/gui/config/KeyBindingHandler.kt b/src/main/kotlin/gui/config/KeyBindingHandler.kt index 14a4b32..a5e626d 100644 --- a/src/main/kotlin/gui/config/KeyBindingHandler.kt +++ b/src/main/kotlin/gui/config/KeyBindingHandler.kt @@ -11,6 +11,7 @@ import kotlinx.serialization.json.JsonElement import kotlinx.serialization.json.decodeFromJsonElement import kotlinx.serialization.json.encodeToJsonElement import moe.nea.firmament.gui.FirmButtonComponent +import moe.nea.firmament.util.data.ManagedConfig import moe.nea.firmament.keybindings.FirmamentKeyBindings import moe.nea.firmament.keybindings.SavedKeyBinding @@ -35,7 +36,7 @@ class KeyBindingHandler(val name: String, val managedConfig: ManagedConfig) : { opt.value }, { opt.value = it - opt.element.save() + opt.element.markDirty() }, { button.blur() }, { button.requestFocus() } diff --git a/src/main/kotlin/gui/config/ManagedConfig.kt b/src/main/kotlin/gui/config/ManagedConfig.kt deleted file mode 100644 index 90e58d0..0000000 --- a/src/main/kotlin/gui/config/ManagedConfig.kt +++ /dev/null @@ -1,270 +0,0 @@ -package moe.nea.firmament.gui.config - -import com.mojang.serialization.Codec -import io.github.notenoughupdates.moulconfig.ChromaColour -import io.github.notenoughupdates.moulconfig.gui.CloseEventListener -import io.github.notenoughupdates.moulconfig.gui.GuiContext -import io.github.notenoughupdates.moulconfig.gui.component.CenterComponent -import io.github.notenoughupdates.moulconfig.gui.component.ColumnComponent -import io.github.notenoughupdates.moulconfig.gui.component.PanelComponent -import io.github.notenoughupdates.moulconfig.gui.component.RowComponent -import io.github.notenoughupdates.moulconfig.gui.component.ScrollPanelComponent -import io.github.notenoughupdates.moulconfig.gui.component.TextComponent -import io.github.notenoughupdates.moulconfig.platform.MoulConfigScreenComponent -import moe.nea.jarvis.api.Point -import org.joml.Vector2i -import org.lwjgl.glfw.GLFW -import kotlinx.serialization.encodeToString -import kotlinx.serialization.json.JsonElement -import kotlinx.serialization.json.JsonObject -import kotlin.io.path.createDirectories -import kotlin.io.path.readText -import kotlin.io.path.writeText -import kotlin.time.Duration -import net.minecraft.client.gui.screen.Screen -import net.minecraft.text.Text -import net.minecraft.util.StringIdentifiable -import moe.nea.firmament.Firmament -import moe.nea.firmament.gui.FirmButtonComponent -import moe.nea.firmament.keybindings.GenericInputButton -import moe.nea.firmament.keybindings.InputModifiers -import moe.nea.firmament.keybindings.SavedKeyBinding -import moe.nea.firmament.util.ScreenUtil.setScreenLater -import moe.nea.firmament.util.collections.InstanceList - -abstract class ManagedConfig( - override val name: String, - val category: Category, - // TODO: allow vararg secondaryCategories: Category, -) : ManagedConfigElement() { - enum class Category { - // Böse Kategorie, nicht benutzten lol - MISC, - CHAT, - INVENTORY, - ITEMS, - MINING, - GARDEN, - EVENTS, - INTEGRATIONS, - META, - DEV, - ; - - val labelText: Text = Text.translatable("firmament.config.category.${name.lowercase()}") - val description: Text = Text.translatable("firmament.config.category.${name.lowercase()}.description") - val configs: MutableList<ManagedConfig> = mutableListOf() - } - - companion object { - val allManagedConfigs = InstanceList<ManagedConfig>("ManagedConfig") - } - - interface OptionHandler<T : Any> { - fun initOption(opt: ManagedOption<T>) {} - fun toJson(element: T): JsonElement? - fun fromJson(element: JsonElement): T - fun emitGuiElements(opt: ManagedOption<T>, guiAppender: GuiAppender) - } - - init { - allManagedConfigs.getAll().forEach { - require(it.name != name) { "Duplicate name '$name' used for config" } - } - allManagedConfigs.add(this) - category.configs.add(this) - } - - // TODO: warn if two files use the same config file name :( - val file = Firmament.CONFIG_DIR.resolve("$name.json") - val data: JsonObject by lazy { - try { - Firmament.json.decodeFromString( - file.readText() - ) - } catch (e: Exception) { - Firmament.logger.info("Could not read config $name. Loading empty config.") - JsonObject(mutableMapOf()) - } - } - - fun save() { - val data = JsonObject(allOptions.mapNotNull { (key, value) -> - value.toJson()?.let { - key to it - } - }.toMap()) - file.parent.createDirectories() - file.writeText(Firmament.json.encodeToString(data)) - } - - - val allOptions = mutableMapOf<String, ManagedOption<*>>() - val sortedOptions = mutableListOf<ManagedOption<*>>() - - private var latestGuiAppender: GuiAppender? = null - - protected fun <T : Any> option( - propertyName: String, - default: () -> T, - handler: OptionHandler<T> - ): ManagedOption<T> { - if (propertyName in allOptions) error("Cannot register the same name twice") - return ManagedOption(this, propertyName, default, handler).also { - it.handler.initOption(it) - it.load(data) - allOptions[propertyName] = it - sortedOptions.add(it) - } - } - - protected fun toggle(propertyName: String, default: () -> Boolean): ManagedOption<Boolean> { - return option(propertyName, default, BooleanHandler(this)) - } - - protected fun colour(propertyName: String, default: () -> ChromaColour): ManagedOption<ChromaColour> { - return option(propertyName, default, ColourHandler(this)) - } - - protected fun <E> choice( - propertyName: String, - enumClass: Class<E>, - default: () -> E - ): ManagedOption<E> where E : Enum<E>, E : StringIdentifiable { - return option(propertyName, default, ChoiceHandler(enumClass, enumClass.enumConstants.toList())) - } - - protected inline fun <reified E> choice( - propertyName: String, - noinline default: () -> E - ): ManagedOption<E> where E : Enum<E>, E : StringIdentifiable { - return choice(propertyName, E::class.java, default) - } - - private fun <E> createStringIdentifiable(x: () -> Array<out E>): Codec<E> where E : Enum<E>, E : StringIdentifiable { - return StringIdentifiable.createCodec { x() } - } - - // TODO: wait on https://youtrack.jetbrains.com/issue/KT-73434 -// protected inline fun <reified E> choice( -// propertyName: String, -// noinline default: () -> E -// ): ManagedOption<E> where E : Enum<E>, E : StringIdentifiable { -// return choice( -// propertyName, -// enumEntries<E>().toList(), -// StringIdentifiable.createCodec { enumValues<E>() }, -// EnumRenderer.default(), -// default -// ) -// } - open fun onChange(option: ManagedOption<*>) { - } - - protected fun duration( - propertyName: String, - min: Duration, - max: Duration, - default: () -> Duration, - ): ManagedOption<Duration> { - return option(propertyName, default, DurationHandler(this, min, max)) - } - - - protected fun position( - propertyName: String, - width: Int, - height: Int, - default: () -> Vector2i, - ): ManagedOption<HudMeta> { - val label = Text.translatable("firmament.config.${name}.${propertyName}") - return option(propertyName, { - val p = default() - HudMeta(HudPosition(p.x(), p.y(), 1F), Firmament.identifier(propertyName), label, width, height) - }, HudMetaHandler(this, propertyName, label, width, height)) - } - - protected fun keyBinding( - propertyName: String, - default: () -> Int, - ): ManagedOption<SavedKeyBinding> = keyBindingWithOutDefaultModifiers(propertyName) { - SavedKeyBinding.keyWithoutMods(default()) - } - - protected fun keyBindingWithOutDefaultModifiers( - propertyName: String, - default: () -> SavedKeyBinding, - ): ManagedOption<SavedKeyBinding> { - return option(propertyName, default, KeyBindingHandler("firmament.config.${name}.${propertyName}", this)) - } - - protected fun keyBindingWithDefaultUnbound( - propertyName: String, - ): ManagedOption<SavedKeyBinding> { - return keyBindingWithOutDefaultModifiers(propertyName) { SavedKeyBinding.unbound() } - } - - protected fun integer( - propertyName: String, - min: Int, - max: Int, - default: () -> Int, - ): ManagedOption<Int> { - return option(propertyName, default, IntegerHandler(this, min, max)) - } - - protected fun button(propertyName: String, runnable: () -> Unit): ManagedOption<Unit> { - return option(propertyName, { }, ClickHandler(this, runnable)) - } - - protected fun string(propertyName: String, default: () -> String): ManagedOption<String> { - return option(propertyName, default, StringHandler(this)) - } - - - fun reloadGui() { - latestGuiAppender?.reloadables?.forEach { it() } - } - - val translationKey get() = "firmament.config.${name}" - val labelText: Text = Text.translatable(translationKey) - - fun getConfigEditor(parent: Screen? = null): Screen { - var screen: Screen? = null - val guiapp = GuiAppender(400) { requireNotNull(screen) { "Screen Accessor called too early" } } - latestGuiAppender = guiapp - guiapp.appendFullRow( - RowComponent( - FirmButtonComponent(TextComponent("←")) { - if (parent != null) { - save() - setScreenLater(parent) - } else { - AllConfigsGui.showAllGuis() - } - } - )) - sortedOptions.forEach { it.appendToGui(guiapp) } - guiapp.reloadables.forEach { it() } - val component = CenterComponent( - PanelComponent( - ScrollPanelComponent(400, 300, ColumnComponent(guiapp.panel)), - 10, - PanelComponent.DefaultBackgroundRenderer.VANILLA - ) - ) - screen = object : MoulConfigScreenComponent(Text.empty(), GuiContext(component), parent) { - override fun close() { - if (guiContext.onBeforeClose() == CloseEventListener.CloseAction.NO_OBJECTIONS_TO_CLOSE) { - client!!.setScreen(parent) - } - } - } - return screen - } - - fun showConfigEditor(parent: Screen? = null) { - setScreenLater(getConfigEditor(parent)) - } - -} diff --git a/src/main/kotlin/gui/config/ManagedConfigElement.kt b/src/main/kotlin/gui/config/ManagedConfigElement.kt deleted file mode 100644 index 28cd6b8..0000000 --- a/src/main/kotlin/gui/config/ManagedConfigElement.kt +++ /dev/null @@ -1,8 +0,0 @@ - - -package moe.nea.firmament.gui.config - -abstract class ManagedConfigElement { - abstract val name: String - -} diff --git a/src/main/kotlin/gui/config/ManagedOption.kt b/src/main/kotlin/gui/config/ManagedOption.kt index 830086c..888b3f1 100644 --- a/src/main/kotlin/gui/config/ManagedOption.kt +++ b/src/main/kotlin/gui/config/ManagedOption.kt @@ -3,6 +3,7 @@ package moe.nea.firmament.gui.config import io.github.notenoughupdates.moulconfig.observer.GetSetter import kotlinx.serialization.json.JsonElement import kotlinx.serialization.json.JsonObject +import moe.nea.firmament.util.data.ManagedConfig import kotlin.properties.ReadWriteProperty import kotlin.reflect.KProperty import net.minecraft.text.Text @@ -27,11 +28,11 @@ class ManagedOption<T : Any>( val descriptionTranslationKey = "firmament.config.${element.name}.${propertyName}.description" val labelDescription: Text = Text.translatable(descriptionTranslationKey) - private var actualValue: T? = null + var _actualValue: T? = null var value: T - get() = actualValue ?: error("Lateinit variable not initialized") + get() = _actualValue ?: error("Lateinit variable not initialized") set(value) { - actualValue = value + _actualValue = value element.onChange(this) } diff --git a/src/main/kotlin/gui/config/StringHandler.kt b/src/main/kotlin/gui/config/StringHandler.kt index a326abb..da14d4b 100644 --- a/src/main/kotlin/gui/config/StringHandler.kt +++ b/src/main/kotlin/gui/config/StringHandler.kt @@ -7,6 +7,7 @@ import io.github.notenoughupdates.moulconfig.observer.GetSetter import kotlinx.serialization.json.JsonElement import kotlinx.serialization.json.JsonPrimitive import kotlinx.serialization.json.jsonPrimitive +import moe.nea.firmament.util.data.ManagedConfig import net.minecraft.text.Text class StringHandler(val config: ManagedConfig) : ManagedConfig.OptionHandler<String> { @@ -25,7 +26,7 @@ class StringHandler(val config: ManagedConfig) : ManagedConfig.OptionHandler<Str object : GetSetter<String> by opt { override fun set(newValue: String) { opt.set(newValue) - config.save() + config.markDirty() } }, 130, diff --git a/src/main/kotlin/gui/config/storage/ConfigLoadContext.kt b/src/main/kotlin/gui/config/storage/ConfigLoadContext.kt new file mode 100644 index 0000000..59afaa1 --- /dev/null +++ b/src/main/kotlin/gui/config/storage/ConfigLoadContext.kt @@ -0,0 +1,65 @@ +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.Path +import kotlin.io.path.createParentDirectories +import kotlin.io.path.writeText +import moe.nea.firmament.Firmament + +data class ConfigLoadContext( + val loadId: String, +) : AutoCloseable { + val logFile = Path("logs") + .resolve(Firmament.MOD_ID) + .resolve("config-$loadId.log") + .toAbsolutePath() + val logBuffer = StringBuilder() + + var shouldSaveLogBuffer = false + fun markShouldSaveLogBuffer() { + shouldSaveLogBuffer = true + } + + fun logDebug(message: String) { + logBuffer.append("[DEBUG] ").append(message).appendLine() + } + + fun logInfo(message: String) { + Firmament.logger.info("[ConfigUpgrade] $message") + logBuffer.append("[INFO] ").append(message).appendLine() + } + + fun logError(message: String, exception: Throwable) { + markShouldSaveLogBuffer() + Firmament.logger.error("[ConfigUpgrade] $message", exception) + logBuffer.append("[ERROR] ").append(message).appendLine() + PrintWriter(StringBuilderWriter(logBuffer)).use { + exception.printStackTrace(it) + } + logBuffer.appendLine() + } + + fun logError(message: String) { + markShouldSaveLogBuffer() + Firmament.logger.error("[ConfigUpgrade] $message") + logBuffer.append("[ERROR] ").append(message).appendLine() + } + + fun ensureWritable(path: Path) { + path.createParentDirectories() + } + + override fun close() { + logInfo("Closing out config load.") + if (shouldSaveLogBuffer) { + try { + ensureWritable(logFile) + logFile.writeText(logBuffer.toString()) + } catch (ex: Exception) { + logError("Could not save config load log", ex) + } + } + } +} diff --git a/src/main/kotlin/gui/config/storage/ConfigStorageClass.kt b/src/main/kotlin/gui/config/storage/ConfigStorageClass.kt new file mode 100644 index 0000000..8258fe7 --- /dev/null +++ b/src/main/kotlin/gui/config/storage/ConfigStorageClass.kt @@ -0,0 +1,8 @@ +package moe.nea.firmament.gui.config.storage + +enum class ConfigStorageClass { // TODO: make this encode type info somehow + PROFILE, + STORAGE, + CONFIG, +} + diff --git a/src/main/kotlin/gui/config/storage/FirmamentConfigLoader.kt b/src/main/kotlin/gui/config/storage/FirmamentConfigLoader.kt new file mode 100644 index 0000000..22cba2c --- /dev/null +++ b/src/main/kotlin/gui/config/storage/FirmamentConfigLoader.kt @@ -0,0 +1,204 @@ +package moe.nea.firmament.gui.config.storage + +import java.util.UUID +import kotlinx.serialization.json.JsonElement +import kotlinx.serialization.json.JsonObject +import kotlinx.serialization.json.buildJsonObject +import kotlinx.serialization.json.jsonObject +import kotlin.io.path.Path +import kotlin.io.path.exists +import kotlin.io.path.forEachDirectoryEntry +import kotlin.io.path.isDirectory +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.data.IConfigProvider +import moe.nea.firmament.util.data.IDataHolder +import moe.nea.firmament.util.data.ProfileKeyedConfig +import moe.nea.firmament.util.json.intoGson +import moe.nea.firmament.util.json.intoKotlinJson + +object FirmamentConfigLoader { + val currentConfigVersion = 1000 + val configFolder = Path("config/firmament") + .toAbsolutePath() + val storageFolder = configFolder.resolve("storage") + val profilePath = configFolder.resolve("profiles") + val tagLines = listOf( + "<- your config version here", + "I'm a teapot", + "mail.example.com ESMTP", + "Apples" + ) + val configVersionFile = configFolder.resolve("config.version") + + fun loadConfig() { + if (configFolder.exists()) { + if (!configVersionFile.exists()) { + LegacyImporter.importFromLegacy() + } + updateConfigs() + } + + ConfigLoadContext("load-${System.currentTimeMillis()}").use { loadContext -> + val configData = FirstLevelSplitJsonFolder(loadContext, configFolder).load() + loadConfigFromData(configData, Unit, ConfigStorageClass.CONFIG) + val storageData = FirstLevelSplitJsonFolder(loadContext, storageFolder).load() + loadConfigFromData(storageData, Unit, ConfigStorageClass.STORAGE) + val profileData = + profilePath.listDirectoryEntries() + .filter { it.isDirectory() } + .associate { + UUID.fromString(it.name) to FirstLevelSplitJsonFolder(loadContext, it).load() + } + profileData.forEach { (key, value) -> + loadConfigFromData(value, key, ConfigStorageClass.PROFILE) + } + } + } + + fun <T> loadConfigFromData( + configData: JsonObject, + key: T, + storageClass: ConfigStorageClass + ) { + for (holder in allConfigs) { + if (holder.storageClass == storageClass) { + (holder as IDataHolder<T>).loadFrom(key, configData) + } + } + } + + fun <T> collectConfigFromData( + key: T, + storageClass: ConfigStorageClass, + ): JsonObject { + var json = JsonObject(mapOf()) + for (holder in allConfigs) { + if (holder.storageClass == storageClass) { + json = mergeJson(json, (holder as IDataHolder<T>).saveTo(key)) + } + } + return json + } + + fun <T> saveStorage( + storageClass: ConfigStorageClass, + key: T, + firstLevelSplitJsonFolder: FirstLevelSplitJsonFolder, + ) { + firstLevelSplitJsonFolder.save( + collectConfigFromData(key, storageClass) + ) + } + + fun collectAllProfileIds(): Set<UUID> { + return allConfigs + .filter { it.storageClass == ConfigStorageClass.PROFILE } + .flatMapTo(mutableSetOf()) { + (it as ProfileKeyedConfig<*>).keys() + } + } + + fun saveAll() { + ConfigLoadContext("load-${System.currentTimeMillis()}").use { context -> + saveStorage( + ConfigStorageClass.CONFIG, + Unit, + FirstLevelSplitJsonFolder(context, configFolder) + ) + saveStorage( + ConfigStorageClass.STORAGE, + Unit, + FirstLevelSplitJsonFolder(context, storageFolder) + ) + collectAllProfileIds().forEach { profileId -> + saveStorage( + ConfigStorageClass.PROFILE, + profileId, + FirstLevelSplitJsonFolder(context, profilePath.resolve(profileId.toString())) + ) + } + } + } + + fun mergeJson(a: JsonObject, b: JsonObject): JsonObject { + fun mergeInner(a: JsonElement?, b: JsonElement?): JsonElement { + if (a == null) + return b!! + if (b == null) + return a + a as JsonObject + b as JsonObject + return buildJsonObject { + (a.keys + b.keys) + .forEach { + put(it, mergeInner(a[it], b[it])) + } + } + } + return mergeInner(a, b) as JsonObject + } + + val allConfigs: List<IDataHolder<*>> = IConfigProvider.providers.allValidInstances.flatMap { it.configs } + + fun updateConfigs() { + val startVersion = configVersionFile.readText() + .substringBefore(' ') + .trim() + .toInt() + ConfigLoadContext("update-from-$startVersion-to-$currentConfigVersion-${System.currentTimeMillis()}") + .use { loadContext -> + updateOneConfig( + loadContext, + startVersion, + ConfigStorageClass.CONFIG, + FirstLevelSplitJsonFolder(loadContext, configFolder) + ) + updateOneConfig( + loadContext, + startVersion, + ConfigStorageClass.STORAGE, + FirstLevelSplitJsonFolder(loadContext, storageFolder) + ) + profilePath.forEachDirectoryEntry { + updateOneConfig( + loadContext, + startVersion, + ConfigStorageClass.PROFILE, + FirstLevelSplitJsonFolder(loadContext, it) + ) + } + configVersionFile.writeText("$currentConfigVersion ${tagLines.random()}") + } + } + + private fun updateOneConfig( + loadContext: ConfigLoadContext, + startVersion: Int, + storageClass: ConfigStorageClass, + firstLevelSplitJsonFolder: FirstLevelSplitJsonFolder + ) { + loadContext.logInfo("Starting upgrade from at ${firstLevelSplitJsonFolder.folder} ($storageClass) to $startVersion") + var data = firstLevelSplitJsonFolder.load() + for (nextVersion in (startVersion + 1)..currentConfigVersion) { + data = updateOneConfigOnce(nextVersion, storageClass, data) + } + firstLevelSplitJsonFolder.save(data) + } + + private fun updateOneConfigOnce( + nextVersion: Int, + storageClass: ConfigStorageClass, + data: JsonObject + ): JsonObject { + return ConfigFixEvent.publish(ConfigFixEvent(storageClass, nextVersion, data.intoGson().asJsonObject)) + .data.intoKotlinJson().jsonObject + } + + fun markDirty(holder: IDataHolder<*>) { + TODO("Not yet implemented") + } + +} diff --git a/src/main/kotlin/gui/config/storage/FirstLevelSplitJsonFolder.kt b/src/main/kotlin/gui/config/storage/FirstLevelSplitJsonFolder.kt new file mode 100644 index 0000000..ff544d5 --- /dev/null +++ b/src/main/kotlin/gui/config/storage/FirstLevelSplitJsonFolder.kt @@ -0,0 +1,83 @@ +@file:OptIn(ExperimentalSerializationApi::class) + +package moe.nea.firmament.gui.config.storage + +import java.nio.file.Path +import kotlinx.serialization.ExperimentalSerializationApi +import kotlinx.serialization.json.JsonElement +import kotlinx.serialization.json.JsonObject +import kotlinx.serialization.json.decodeFromStream +import kotlinx.serialization.json.encodeToStream +import kotlin.io.path.deleteExisting +import kotlin.io.path.inputStream +import kotlin.io.path.listDirectoryEntries +import kotlin.io.path.nameWithoutExtension +import kotlin.io.path.outputStream +import moe.nea.firmament.Firmament + +class FirstLevelSplitJsonFolder( + val context: ConfigLoadContext, + val folder: Path +) { + 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!") } + } + + fun loadIndividualFile(path: Path): Pair<String, JsonElement>? { + context.logInfo("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) + null + } + } + + fun save(value: JsonObject) { + context.logInfo("Saving FLSJF to $folder") + context.logDebug("Current value:\n$value") + val entries = folder.listDirectoryEntries("*.json") + .toMutableList() + for ((name, element) in value) { + val path = saveIndividualFile(name, element) + if (path != null) { + entries.remove(path) + } + } + if (entries.isNotEmpty()) { + context.logInfo("Deleting additional files.") + for (path in entries) { + context.logInfo("Deleting $path") +// context.backup(path) + try { + path.deleteExisting() + } catch (ex: Exception) { + context.logError("Could not delete $path", ex) + } + } + } + context.logInfo("FLSJF to $folder - Voller Erfolg!") + } + + fun saveIndividualFile(name: String, element: JsonElement): Path? { + try { + context.logInfo("Saving partial file with name $name") + val path = folder.resolve("$name.json") + context.ensureWritable(path) + path.outputStream().use { + Firmament.json.encodeToStream(JsonElement.serializer(), element, it) + } + return path + } catch (ex: Exception) { + context.logError("Could not save $name with value $element", ex) + return null + } + } +} diff --git a/src/main/kotlin/gui/config/storage/LegacyImporter.kt b/src/main/kotlin/gui/config/storage/LegacyImporter.kt new file mode 100644 index 0000000..8915c17 --- /dev/null +++ b/src/main/kotlin/gui/config/storage/LegacyImporter.kt @@ -0,0 +1,63 @@ +package moe.nea.firmament.gui.config.storage + +import java.nio.file.Path +import javax.xml.namespace.QName +import kotlin.io.path.Path +import kotlin.io.path.copyTo +import kotlin.io.path.copyToRecursively +import kotlin.io.path.createDirectories +import kotlin.io.path.createParentDirectories +import kotlin.io.path.exists +import kotlin.io.path.forEachDirectoryEntry +import kotlin.io.path.listDirectoryEntries +import kotlin.io.path.moveTo +import kotlin.io.path.name +import kotlin.io.path.nameWithoutExtension +import kotlin.io.path.writeText +import moe.nea.firmament.gui.config.storage.FirmamentConfigLoader.configFolder +import moe.nea.firmament.gui.config.storage.FirmamentConfigLoader.configVersionFile +import moe.nea.firmament.gui.config.storage.FirmamentConfigLoader.storageFolder + +object LegacyImporter { + val legacyConfigVersion = 995 + val backupPath = configFolder.resolveSibling("firmament-legacy-config-${System.currentTimeMillis()}") + + fun copyIf(from: Path, to: Path) { + if (from.exists()) { + to.createParentDirectories() + from.copyTo(to) + } + } + + fun importFromLegacy() { + configFolder.moveTo(backupPath) + configFolder.createDirectories() + + copyIf( + backupPath.resolve("inventory-buttons.json"), + storageFolder.resolve("inventory-buttons.json") + ) + + backupPath.listDirectoryEntries("*.json") + .forEach { path -> + val name = path.name + if (name == "inventory-buttons.json") + return@forEach + path.copyTo(configFolder.resolve(name)) + } + + backupPath.resolve("profiles") + .forEachDirectoryEntry { category -> + category.forEachDirectoryEntry { profile -> + copyIf( + profile, + FirmamentConfigLoader.profilePath + .resolve(profile.nameWithoutExtension) + .resolve(category.name + ".json") + ) + } + } + + configVersionFile.writeText(legacyConfigVersion.toString()) + } +} |
