From 4e22585faf5041f3548e8a4f1b860b33f10abcb8 Mon Sep 17 00:00:00 2001 From: Burning_TNT <88144530+burningtnt@users.noreply.github.com> Date: Mon, 8 Jan 2024 20:39:55 +0800 Subject: [PATCH] Build configuration DFU system. (#2589) * Build configuration DFU system. * Remove redundant object allocation. * Fix * Code cleanup * Remove unnecessary GSON usage. * Fix compiletime unchecked warning. --- .../jackhuang/hmcl/setting/ConfigHolder.java | 5 +- .../hmcl/setting/ConfigUpgrader.java | 152 ++++++++++-------- 2 files changed, 88 insertions(+), 69 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/setting/ConfigHolder.java b/HMCL/src/main/java/org/jackhuang/hmcl/setting/ConfigHolder.java index 33c8e8d53..2a3db2e7e 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/setting/ConfigHolder.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/setting/ConfigHolder.java @@ -17,7 +17,6 @@ */ package org.jackhuang.hmcl.setting; -import com.google.gson.Gson; import com.google.gson.JsonParseException; import org.jackhuang.hmcl.Metadata; import org.jackhuang.hmcl.util.InvocationDispatcher; @@ -28,7 +27,6 @@ import org.jackhuang.hmcl.util.platform.OperatingSystem; import java.io.IOException; import java.nio.file.*; -import java.util.Map; import java.util.logging.Level; import static org.jackhuang.hmcl.util.Logging.LOG; @@ -167,8 +165,7 @@ public final class ConfigHolder { if (deserialized == null) { LOG.info("Config is empty"); } else { - Map raw = new Gson().fromJson(content, Map.class); - ConfigUpgrader.upgradeConfig(deserialized, raw); + ConfigUpgrader.upgradeConfig(deserialized, content); return deserialized; } } catch (JsonParseException e) { diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/setting/ConfigUpgrader.java b/HMCL/src/main/java/org/jackhuang/hmcl/setting/ConfigUpgrader.java index 9e519b092..8176575c0 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/setting/ConfigUpgrader.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/setting/ConfigUpgrader.java @@ -17,96 +17,118 @@ */ package org.jackhuang.hmcl.setting; +import com.google.gson.Gson; +import org.jackhuang.hmcl.util.Lang; +import org.jackhuang.hmcl.util.Pair; import org.jackhuang.hmcl.util.StringUtils; + +import java.util.Collections; import java.util.HashMap; +import java.util.List; import java.util.Map; +import java.util.function.BiConsumer; +import java.util.logging.Level; import static org.jackhuang.hmcl.util.Lang.tryCast; +import static org.jackhuang.hmcl.util.Logging.LOG; final class ConfigUpgrader { - private static final int VERSION = 0; - private ConfigUpgrader() { } + private static final int CURRENT_VERSION = 1; + /** - * This method is for the compatibility with old HMCL 3.x as well as HMCL 2.x. + * This method is for the compatibility with old HMCL versions. * * @param deserialized deserialized config settings - * @param rawJson raw json structure of the config settings without modification - * @return true if config version is upgraded + * @param rawContent raw json content of the config settings without modification */ - static boolean upgradeConfig(Config deserialized, Map rawJson) { - boolean upgraded; - if (deserialized.getConfigVersion() < VERSION) { - deserialized.setConfigVersion(VERSION); - // TODO: Add upgrade code here. - upgraded = true; - } else { - upgraded = false; + static void upgradeConfig(Config deserialized, String rawContent) { + if (deserialized.getConfigVersion() == CURRENT_VERSION) { + return; } - upgradeV2(deserialized, rawJson); - upgradeV3(deserialized, rawJson); + int configVersion = deserialized.getConfigVersion(); + if (configVersion > CURRENT_VERSION) { + LOG.log(Level.WARNING, String.format("Current HMCL only support the configuration version up to %d. However, the version now is %d.", CURRENT_VERSION, configVersion)); + return; + } - return upgraded; + LOG.log(Level.INFO, String.format("Updating configuration from %d to %d.", configVersion, CURRENT_VERSION)); + Map unmodifiableRawJson = Collections.unmodifiableMap(new Gson().>fromJson(rawContent, Map.class)); + for (Map.Entry>> dfu : collectDFU()) { + if (configVersion < dfu.getKey()) { + dfu.getValue().accept(deserialized, unmodifiableRawJson); + configVersion = dfu.getKey(); + } + } + + deserialized.setConfigVersion(CURRENT_VERSION); } /** - * Upgrade configuration of HMCL 2.x + *

Initialize the dfu of HMCL. Feel free to use lambda as all the lambda here would not be initialized unless HMCL needs to update the configuration. + * For each item in this list, it should be a Map.Entry.

* - * @param deserialized deserialized config settings - * @param rawJson raw json structure of the config settings without modification - */ - private static void upgradeV2(Config deserialized, Map rawJson) { - // Convert OfflineAccounts whose stored uuid is important. - tryCast(rawJson.get("auth"), Map.class).ifPresent(auth -> { - tryCast(auth.get("offline"), Map.class).ifPresent(offline -> { - String selected = rawJson.containsKey("selectedAccount") ? null - : tryCast(offline.get("IAuthenticator_UserName"), String.class).orElse(null); - - tryCast(offline.get("uuidMap"), Map.class).ifPresent(uuidMap -> { - ((Map) uuidMap).forEach((key, value) -> { - Map storage = new HashMap<>(); - storage.put("type", "offline"); - storage.put("username", key); - storage.put("uuid", value); - if (key.equals(selected)) { - storage.put("selected", true); - } - deserialized.getAccountStorages().add(storage); - }); - }); - }); - }); - } - - /** - * Upgrade configuration of HMCL earlier than 3.1.70 + *

The key should be a version number. All the configuration with a version number which is less than the specific one will be applied to this upgrader.

+ *

The value should be the upgrader. The configuration which is waited to being processed, and the raw unmodifiable value of the json from the configuration file.

+ *

The return value should a target version number of this item.

* - * @param deserialized deserialized config settings - * @param rawJson raw json structure of the config settings without modification + *

The last item must return CURRENT_VERSION, as the config file should always being updated to the latest version.

*/ - private static void upgradeV3(Config deserialized, Map rawJson) { - if (!rawJson.containsKey("commonDirType")) - deserialized.setCommonDirType(deserialized.getCommonDirectory().equals(Settings.getDefaultCommonDirectory()) ? EnumCommonDirectory.DEFAULT : EnumCommonDirectory.CUSTOM); - if (!rawJson.containsKey("backgroundType")) - deserialized.setBackgroundImageType(StringUtils.isNotBlank(deserialized.getBackgroundImage()) ? EnumBackgroundImage.CUSTOM : EnumBackgroundImage.DEFAULT); - if (!rawJson.containsKey("hasProxy")) - deserialized.setHasProxy(StringUtils.isNotBlank(deserialized.getProxyHost())); - if (!rawJson.containsKey("hasProxyAuth")) - deserialized.setHasProxyAuth(StringUtils.isNotBlank(deserialized.getProxyUser())); + private static List>>> collectDFU() { + List>>> dfu = Lang.immutableListOf( + Pair.pair(1, (deserialized, rawJson) -> { + // Upgrade configuration of HMCL 2.x: Convert OfflineAccounts whose stored uuid is important. + tryCast(rawJson.get("auth"), Map.class).ifPresent(auth -> { + tryCast(auth.get("offline"), Map.class).ifPresent(offline -> { + String selected = rawJson.containsKey("selectedAccount") ? null + : tryCast(offline.get("IAuthenticator_UserName"), String.class).orElse(null); - if (!rawJson.containsKey("downloadType")) { - tryCast(rawJson.get("downloadtype"), Number.class) - .map(Number::intValue) - .ifPresent(id -> { - if (id == 0) { - deserialized.setDownloadType("mojang"); - } else if (id == 1) { - deserialized.setDownloadType("bmclapi"); - } + tryCast(offline.get("uuidMap"), Map.class).ifPresent(uuidMap -> { + ((Map) uuidMap).forEach((key, value) -> { + Map storage = new HashMap<>(); + storage.put("type", "offline"); + storage.put("username", key); + storage.put("uuid", value); + if (key.equals(selected)) { + storage.put("selected", true); + } + deserialized.getAccountStorages().add(storage); + }); + }); + }); }); + + // Upgrade configuration of HMCL earlier than 3.1.70 + if (!rawJson.containsKey("commonDirType")) + deserialized.setCommonDirType(deserialized.getCommonDirectory().equals(Settings.getDefaultCommonDirectory()) ? EnumCommonDirectory.DEFAULT : EnumCommonDirectory.CUSTOM); + if (!rawJson.containsKey("backgroundType")) + deserialized.setBackgroundImageType(StringUtils.isNotBlank(deserialized.getBackgroundImage()) ? EnumBackgroundImage.CUSTOM : EnumBackgroundImage.DEFAULT); + if (!rawJson.containsKey("hasProxy")) + deserialized.setHasProxy(StringUtils.isNotBlank(deserialized.getProxyHost())); + if (!rawJson.containsKey("hasProxyAuth")) + deserialized.setHasProxyAuth(StringUtils.isNotBlank(deserialized.getProxyUser())); + + if (!rawJson.containsKey("downloadType")) { + tryCast(rawJson.get("downloadtype"), Number.class) + .map(Number::intValue) + .ifPresent(id -> { + if (id == 0) { + deserialized.setDownloadType("mojang"); + } else if (id == 1) { + deserialized.setDownloadType("bmclapi"); + } + }); + } + }) + ); + + if (dfu.get(dfu.size() - 1).getKey() != CURRENT_VERSION) { + throw new IllegalStateException("The last dfu must adapt all the config version below CURRENT_VERSION"); } + + return dfu; } }