diff options
author | Linnea Gräf <nea@nea.moe> | 2023-09-16 13:19:12 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-09-16 13:19:12 +0200 |
commit | 83b5cd3c93bab32f16365c6b6810362f4de5598e (patch) | |
tree | f9f612f021ef7f4283d74312edfaca30badc6749 /src/main | |
parent | 024ba52fb69b6cd44b4e31542867f802de656f15 (diff) | |
download | skyhanni-83b5cd3c93bab32f16365c6b6810362f4de5598e.tar.gz skyhanni-83b5cd3c93bab32f16365c6b6810362f4de5598e.tar.bz2 skyhanni-83b5cd3c93bab32f16365c6b6810362f4de5598e.zip |
Config migration and also more atomic config saves (#420)
Config migration and also more atomic config saves #420
Diffstat (limited to 'src/main')
5 files changed, 114 insertions, 10 deletions
diff --git a/src/main/java/at/hannibal2/skyhanni/config/ConfigManager.kt b/src/main/java/at/hannibal2/skyhanni/config/ConfigManager.kt index a882af5d4..23e9b6261 100644 --- a/src/main/java/at/hannibal2/skyhanni/config/ConfigManager.kt +++ b/src/main/java/at/hannibal2/skyhanni/config/ConfigManager.kt @@ -10,6 +10,7 @@ import at.hannibal2.skyhanni.utils.NEUInternalName import at.hannibal2.skyhanni.utils.NEUInternalName.Companion.asInternalName import at.hannibal2.skyhanni.utils.NEUItems import com.google.gson.GsonBuilder +import com.google.gson.JsonObject import com.google.gson.TypeAdapter import com.google.gson.stream.JsonReader import com.google.gson.stream.JsonWriter @@ -100,10 +101,6 @@ class ConfigManager { configFile = File(configDirectory, "config.json") sackFile = File(configDirectory, "sacks.json") - fixedRateTimer(name = "skyhanni-config-auto-save", period = 60_000L, initialDelay = 60_000L) { - saveConfig("auto-save-60s") - } - logger.log("Trying to load config from $configFile") if (configFile!!.exists()) { @@ -119,8 +116,10 @@ class ConfigManager { logger.log("load-config-now") + val jsonObject = gson.fromJson(builder.toString(), JsonObject::class.java) + val newJsonObject = ConfigUpdaterMigrator.fixConfig(jsonObject) features = gson.fromJson( - builder.toString(), + newJsonObject, Features::class.java ) logger.log("Loaded config from file") @@ -166,6 +165,10 @@ class ConfigManager { saveConfig("blank config") } + fixedRateTimer(name = "skyhanni-config-auto-save", period = 60_000L, initialDelay = 60_000L) { + saveConfig("auto-save-60s") + } + if (!::sackData.isInitialized) { logger.log("Creating blank sack data and saving") sackData = SackData() @@ -201,11 +204,14 @@ class ConfigManager { try { logger.log("Saving config file") file.parentFile.mkdirs() - file.createNewFile() - BufferedWriter(OutputStreamWriter(FileOutputStream(file), StandardCharsets.UTF_8)).use { writer -> + val unit = file.parentFile.resolve("config.json.write") + unit.createNewFile() + BufferedWriter(OutputStreamWriter(FileOutputStream(unit), StandardCharsets.UTF_8)).use { writer -> // TODO remove old "hidden" area writer.write(gson.toJson(SkyHanniMod.feature)) } + // Perform move — which is atomic, unlike writing — after writing is done. + unit.renameTo(file) } catch (e: IOException) { logger.log("Could not save config file to $file") e.printStackTrace() diff --git a/src/main/java/at/hannibal2/skyhanni/config/ConfigUpdaterMigrator.kt b/src/main/java/at/hannibal2/skyhanni/config/ConfigUpdaterMigrator.kt new file mode 100644 index 000000000..6b590a389 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/config/ConfigUpdaterMigrator.kt @@ -0,0 +1,85 @@ +package at.hannibal2.skyhanni.config + +import at.hannibal2.skyhanni.events.LorenzEvent +import at.hannibal2.skyhanni.utils.LorenzLogger +import com.google.gson.JsonElement +import com.google.gson.JsonObject +import com.google.gson.JsonPrimitive + +object ConfigUpdaterMigrator { + val logger = LorenzLogger("ConfigMigration") + val configVersion = 0 + fun JsonElement.at(chain: List<String>, init: Boolean): JsonElement? { + if (chain.isEmpty()) return this + if (this !is JsonObject) return null + var obj = this.get(chain.first()) + if (obj == null && init) { + obj = JsonObject() + this.add(chain.first(), obj) + } + return obj?.at(chain.drop(1), init) + } + + + data class ConfigFixEvent( + val old: JsonObject, + val new: JsonObject, + val oldVersion: Int, + var movesPerformed: Int, + ) : LorenzEvent() { + fun move(since: Int, oldPath: String, newPath: String, transform: (JsonElement) -> JsonElement = { it }) { + if (since <= oldVersion) { + logger.log("Skipping move from $oldPath to $newPath ($since <= $oldVersion)") + return + } + val op = oldPath.split(".") + val np = newPath.split(".") + val oldElem = old.at(op, false) + if (oldElem == null) { + logger.log("Skipping move from $oldPath to $newPath ($oldPath not present)") + return + } + val x = new.at(np.dropLast(1), true) + if (x !is JsonObject) { + logger.log("Catastrophic: element at path $old could not be relocated to $new, since another element already inhabits that path") + return + } + movesPerformed++ + x.add(np.last(), transform(oldElem)) + logger.log("Moved element from $oldPath to $newPath") + } + } + + fun merge(a: JsonObject, b: JsonObject): Int { + var c = 0 + b.entrySet().forEach { + val e = a.get(it.key) + val n = it.value + if (e is JsonObject && n is JsonObject) { + c += merge(e, n) + } else { + if (e != null) { + logger.log("Encountered destructive merge. Erasing $e in favour of $n.") + c++ + } + a.add(it.key, n) + } + } + return c + } + + fun fixConfig(config: JsonObject): JsonObject { + val lV = (config.get("lastVersion") as? JsonPrimitive) + ?.takeIf { it.isNumber }?.asInt ?: -1 + if (lV == configVersion) return config + logger.log("Starting config transformation from $lV to $configVersion") + val migration = ConfigFixEvent(config, JsonObject().also { + it.add("lastVersion", JsonPrimitive(configVersion)) + }, lV, 0).also { it.postAndCatch() } + logger.log("Transformations scheduled: ${migration.new}") + val mergesPerformed = merge(migration.old, migration.new) + logger.log("Migration done with $mergesPerformed merges and ${migration.movesPerformed} moves performed") + return migration.old + } + +}
\ No newline at end of file diff --git a/src/main/java/at/hannibal2/skyhanni/config/Features.java b/src/main/java/at/hannibal2/skyhanni/config/Features.java index 541b6e72b..149f66020 100644 --- a/src/main/java/at/hannibal2/skyhanni/config/Features.java +++ b/src/main/java/at/hannibal2/skyhanni/config/Features.java @@ -136,4 +136,8 @@ public class Features extends Config { @Expose public Storage storage = new Storage(); + + @Expose + public int lastVersion = ConfigUpdaterMigrator.INSTANCE.getConfigVersion(); + } diff --git a/src/main/java/at/hannibal2/skyhanni/config/features/AshfangConfig.java b/src/main/java/at/hannibal2/skyhanni/config/features/AshfangConfig.java index 575a1987d..9cffc4ed8 100644 --- a/src/main/java/at/hannibal2/skyhanni/config/features/AshfangConfig.java +++ b/src/main/java/at/hannibal2/skyhanni/config/features/AshfangConfig.java @@ -13,7 +13,7 @@ public class AshfangConfig { @ConfigOption(name = "Freeze", desc = "Show the cooldown for how long Ashfang blocks your abilities.") @ConfigEditorBoolean @FeatureToggle - public boolean freezeCooldown = false; + public boolean brrrrItsSoColdCooldownCooldown = false; @Expose public Position freezeCooldownPos = new Position(10, 10, false, true); diff --git a/src/main/java/at/hannibal2/skyhanni/features/nether/ashfang/AshfangFreezeCooldown.kt b/src/main/java/at/hannibal2/skyhanni/features/nether/ashfang/AshfangFreezeCooldown.kt index 0bccfe254..264b443eb 100644 --- a/src/main/java/at/hannibal2/skyhanni/features/nether/ashfang/AshfangFreezeCooldown.kt +++ b/src/main/java/at/hannibal2/skyhanni/features/nether/ashfang/AshfangFreezeCooldown.kt @@ -1,6 +1,7 @@ package at.hannibal2.skyhanni.features.nether.ashfang import at.hannibal2.skyhanni.SkyHanniMod +import at.hannibal2.skyhanni.config.ConfigUpdaterMigrator import at.hannibal2.skyhanni.events.GuiRenderEvent import at.hannibal2.skyhanni.events.LorenzChatEvent import at.hannibal2.skyhanni.features.damageindicator.BossType @@ -34,12 +35,20 @@ class AshfangFreezeCooldown { val remainingLong = maxDuration - duration if (remainingLong > 0) { var format = TimeUtils.formatDuration(remainingLong, showMilliSeconds = true) - SkyHanniMod.feature.ashfang.freezeCooldownPos.renderString("§cAshfang Freeze: §a$format", posLabel = "Ashfang Freeze Cooldown") + SkyHanniMod.feature.ashfang.freezeCooldownPos.renderString( + "§cAshfang Freeze: §a$format", + posLabel = "Ashfang Freeze Cooldown" + ) } } + @SubscribeEvent + fun onConfigFix(event: ConfigUpdaterMigrator.ConfigFixEvent) { + event.move(0, "ashfang.freezeCooldown", "ashfang.brrrrItsSoColdCooldownCooldown") + } + private fun isEnabled(): Boolean { - return LorenzUtils.inSkyBlock && SkyHanniMod.feature.ashfang.freezeCooldown && + return LorenzUtils.inSkyBlock && SkyHanniMod.feature.ashfang.brrrrItsSoColdCooldownCooldown && DamageIndicatorManager.isBossSpawned(BossType.NETHER_ASHFANG) } }
\ No newline at end of file |