From debfabff08c829cf2eadd4130f9f7573ea10e2ac Mon Sep 17 00:00:00 2001 From: Jordan Date: Wed, 1 May 2024 17:31:06 +0900 Subject: [PATCH] feat: introduce migrating config nodes to new locations (#2642) - Initial case of moving schematic limits from experimental to limits - Closes #2533 --- .../core/configuration/Config.java | 121 ++++++++++++------ .../core/configuration/Settings.java | 32 +++-- .../core/limit/FaweLimit.java | 10 ++ .../worldedit/command/SchematicCommands.java | 16 +-- 4 files changed, 123 insertions(+), 56 deletions(-) diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/configuration/Config.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/configuration/Config.java index bc300d584..c3f223dc2 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/configuration/Config.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/configuration/Config.java @@ -2,6 +2,7 @@ package com.fastasyncworldedit.core.configuration; import com.fastasyncworldedit.core.configuration.file.YamlConfiguration; import com.fastasyncworldedit.core.util.StringMan; +import com.sk89q.util.StringUtil; import com.sk89q.worldedit.internal.util.LogManagerCompat; import org.apache.logging.log4j.Logger; @@ -14,8 +15,10 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.lang.invoke.MethodHandles; import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Modifier; import java.lang.reflect.ParameterizedType; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; @@ -27,6 +30,9 @@ public class Config { private static final Logger LOGGER = LogManagerCompat.getLogger(); + private final Map removedKeyVals = new HashMap<>(); + private List existingMigrateNodes = null; + public Config() { save(new PrintWriter(new ByteArrayOutputStream(0)), getClass(), this, 0); } @@ -43,7 +49,8 @@ public class Config { try { return (T) field.get(instance); } catch (IllegalAccessException e) { - e.printStackTrace(); + LOGGER.error("Failed to get config option: {}", key, e); + return null; } } } @@ -67,6 +74,10 @@ public class Config { if (field.getAnnotation(Final.class) != null) { return; } + Migrate migrate = field.getAnnotation(Migrate.class); + if (existingMigrateNodes != null && migrate != null) { + existingMigrateNodes.add(migrate.value()); + } if (field.getType() == String.class && !(value instanceof String)) { value = value + ""; } @@ -74,17 +85,22 @@ public class Config { field.set(instance, value); return; } catch (Throwable e) { - e.printStackTrace(); + LOGGER.error("Failed to set config option: {}", key); } } } - LOGGER.error("Failed to set config option: {}: {} | {} | {}.yml", key, value, instance, root.getSimpleName()); + removedKeyVals.put(key, value); + LOGGER.error( + "Failed to set config option: {}: {} | {} | {}.yml. This is likely because it was removed.", + key, + value, + instance, + root.getSimpleName() + ); } public boolean load(File file) { - if (!file.exists()) { - return false; - } + existingMigrateNodes = new ArrayList<>(); YamlConfiguration yml = YamlConfiguration.loadConfiguration(file); for (String key : yml.getKeys(true)) { Object value = yml.get(key); @@ -93,6 +109,10 @@ public class Config { } set(key, value, getClass()); } + for (String node : existingMigrateNodes) { + removedKeyVals.remove(node); + } + existingMigrateNodes = null; return true; } @@ -113,7 +133,7 @@ public class Config { save(writer, getClass(), instance, 0); writer.close(); } catch (Throwable e) { - e.printStackTrace(); + LOGGER.error("Failed to save config file: {}", file, e); } } @@ -166,6 +186,19 @@ public class Config { } + /** + * Indicates that a field should be instantiated / created. + * + * @since TODO + */ + @Retention(RetentionPolicy.RUNTIME) + @Target({ElementType.FIELD}) + public @interface Migrate { + + String value(); + + } + @Ignore // This is not part of the config public static class ConfigBlock { @@ -222,7 +255,6 @@ public class Config { try { String CTRF = System.lineSeparator(); String spacing = StringMan.repeat(" ", indent); - HashMap, Object> instances = new HashMap<>(); for (Field field : clazz.getFields()) { if (field.getAnnotation(Ignore.class) != null) { continue; @@ -239,31 +271,14 @@ public class Config { } if (current == ConfigBlock.class) { current = (Class) ((ParameterizedType) (field.getGenericType())).getActualTypeArguments()[0]; - comment = current.getAnnotation(Comment.class); - if (comment != null) { - for (String commentLine : comment.value()) { - writer.write(spacing + "# " + commentLine + CTRF); - } - } - BlockName blockNames = current.getAnnotation(BlockName.class); - if (blockNames != null) { - writer.write(spacing + toNodeName(current.getSimpleName()) + ":" + CTRF); - ConfigBlock configBlock = (ConfigBlock) field.get(instance); - if (configBlock == null || configBlock.getInstances().isEmpty()) { - configBlock = new ConfigBlock(); - field.set(instance, configBlock); - for (String blockName : blockNames.value()) { - configBlock.put(blockName, current.getDeclaredConstructor().newInstance()); - } - } - // Save each instance - for (Map.Entry entry : ((Map) configBlock.getRaw()).entrySet()) { - String key = entry.getKey(); - writer.write(spacing + " " + toNodeName(key) + ":" + CTRF); - save(writer, current, entry.getValue(), indent + 4); - } - } + handleConfigBlockSave(writer, instance, indent, field, spacing, CTRF, current); continue; + } else if (!removedKeyVals.isEmpty()) { + Migrate migrate = field.getAnnotation(Migrate.class); + Object value; + if (migrate != null && (value = removedKeyVals.remove(migrate.value())) != null) { + field.set(instance, value); + } } Create create = field.getAnnotation(Create.class); if (create != null) { @@ -281,7 +296,6 @@ public class Config { writer.write(spacing + toNodeName(current.getSimpleName()) + ":" + CTRF); if (value == null) { field.set(instance, value = current.getDeclaredConstructor().newInstance()); - instances.put(current, value); } save(writer, current, value, indent + 2); } else { @@ -292,7 +306,42 @@ public class Config { } } } catch (Throwable e) { - e.printStackTrace(); + LOGGER.error("Failed to save config file", e); + } + } + + private void handleConfigBlockSave( + PrintWriter writer, + Object instance, + int indent, + Field field, + String spacing, + String CTRF, + Class current + ) throws IllegalAccessException, InstantiationException, InvocationTargetException, NoSuchMethodException { + Comment comment = current.getAnnotation(Comment.class); + if (comment != null) { + for (String commentLine : comment.value()) { + writer.write(spacing + "# " + commentLine + CTRF); + } + } + BlockName blockNames = current.getAnnotation(BlockName.class); + if (blockNames != null) { + writer.write(spacing + toNodeName(current.getSimpleName()) + ":" + CTRF); + ConfigBlock configBlock = (ConfigBlock) field.get(instance); + if (configBlock == null || configBlock.getInstances().isEmpty()) { + configBlock = new ConfigBlock<>(); + field.set(instance, configBlock); + for (String blockName : blockNames.value()) { + configBlock.put(blockName, current.getDeclaredConstructor().newInstance()); + } + } + // Save each instance + for (Map.Entry entry : configBlock.getRaw().entrySet()) { + String key = entry.getKey(); + writer.write(spacing + " " + toNodeName(key) + ":" + CTRF); + save(writer, current, entry.getValue(), indent + 4); + } } } @@ -311,7 +360,7 @@ public class Config { return field; } catch (Throwable ignored) { LOGGER.warn( - "Invalid config field: {} for {}", + "Invalid config field: {} for {}. It is possible this is because it has been removed.", StringMan.join(split, "."), toNodeName(instance.getClass().getSimpleName()) ); @@ -379,7 +428,7 @@ public class Config { return null; } } catch (Throwable e) { - e.printStackTrace(); + LOGGER.error("Failed retrieving instance for config node: {}", StringUtil.joinString(split, "."), e); } return null; } diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/configuration/Settings.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/configuration/Settings.java index 89f48b697..8c8077d66 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/configuration/Settings.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/configuration/Settings.java @@ -145,6 +145,14 @@ public class Settings extends Config { limit.MAX_HISTORY, newLimit.MAX_HISTORY_MB != -1 ? newLimit.MAX_HISTORY_MB : Integer.MAX_VALUE ); + limit.SCHEM_FILE_NUM_LIMIT = Math.max( + limit.SCHEM_FILE_NUM_LIMIT, + newLimit.SCHEM_FILE_NUM_LIMIT != -1 ? newLimit.SCHEM_FILE_NUM_LIMIT : Integer.MAX_VALUE + ); + limit.SCHEM_FILE_SIZE_LIMIT = Math.max( + limit.SCHEM_FILE_SIZE_LIMIT, + newLimit.SCHEM_FILE_SIZE_LIMIT != -1 ? newLimit.SCHEM_FILE_SIZE_LIMIT : Integer.MAX_VALUE + ); limit.MAX_EXPRESSION_MS = Math.max( limit.MAX_EXPRESSION_MS, newLimit.MAX_EXPRESSION_MS != -1 ? newLimit.MAX_EXPRESSION_MS : Integer.MAX_VALUE @@ -353,6 +361,18 @@ public class Settings extends Config { " - History on disk or memory will be deleted", }) public int MAX_HISTORY_MB = -1; + @Comment({ + "Sets a maximum limit (in kb) for the size of a player's schematics directory (per-player mode only)", + "Set to -1 to disable" + }) + @Migrate("experimental.per-player-file-size-limit") + public int SCHEM_FILE_SIZE_LIMIT = -1; + @Comment({ + "Sets a maximum limit for the amount of schematics in a player's schematics directory (per-player mode only)", + "Set to -1 to disable" + }) + @Migrate("experimental.per-player-file-num-limit") + public int SCHEM_FILE_NUM_LIMIT = -1; @Comment("Maximum time in milliseconds //calc can execute") public int MAX_EXPRESSION_MS = 50; @Comment({ @@ -615,18 +635,6 @@ public class Settings extends Config { }) public boolean ALLOW_TICK_FLUIDS = false; - @Comment({ - "Sets a maximum limit (in kb) for the size of a player's schematics directory (per-player mode only)", - "Set to -1 to disable" - }) - public int PER_PLAYER_FILE_SIZE_LIMIT = -1; - - @Comment({ - "Sets a maximum limit for the amount of schematics in a player's schematics directory (per-player mode only)", - "Set to -1 to disable" - }) - public int PER_PLAYER_FILE_NUM_LIMIT = -1; - } @Comment({"Web/HTTP connection related settings"}) diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/limit/FaweLimit.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/limit/FaweLimit.java index 00a47178c..e02e3abc9 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/limit/FaweLimit.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/limit/FaweLimit.java @@ -15,6 +15,8 @@ public class FaweLimit { public int MAX_BLOCKSTATES = 0; public int MAX_ENTITIES = 0; public int MAX_HISTORY = 0; + public int SCHEM_FILE_SIZE_LIMIT = 0; + public int SCHEM_FILE_NUM_LIMIT = 0; public int MAX_EXPRESSION_MS = 0; public int INVENTORY_MODE = Integer.MAX_VALUE; public int SPEED_REDUCTION = Integer.MAX_VALUE; @@ -111,6 +113,8 @@ public class FaweLimit { MAX.MAX_BLOCKSTATES = Integer.MAX_VALUE; MAX.MAX_ENTITIES = Integer.MAX_VALUE; MAX.MAX_HISTORY = Integer.MAX_VALUE; + MAX.SCHEM_FILE_NUM_LIMIT = Integer.MAX_VALUE; + MAX.SCHEM_FILE_SIZE_LIMIT = Integer.MAX_VALUE; MAX.MAX_EXPRESSION_MS = 50; MAX.FAST_PLACEMENT = true; MAX.CONFIRM_LARGE = true; @@ -237,6 +241,8 @@ public class FaweLimit { && MAX_BLOCKSTATES == Integer.MAX_VALUE && MAX_ENTITIES == Integer.MAX_VALUE && MAX_HISTORY == Integer.MAX_VALUE + && SCHEM_FILE_SIZE_LIMIT == Integer.MAX_VALUE + && SCHEM_FILE_NUM_LIMIT == Integer.MAX_VALUE && INVENTORY_MODE == 0 && SPEED_REDUCTION == 0 && FAST_PLACEMENT @@ -256,6 +262,8 @@ public class FaweLimit { MAX_FAILS = limit.MAX_FAILS; MAX_ITERATIONS = limit.MAX_ITERATIONS; MAX_HISTORY = limit.MAX_HISTORY; + SCHEM_FILE_NUM_LIMIT = limit.SCHEM_FILE_NUM_LIMIT; + SCHEM_FILE_SIZE_LIMIT = limit.SCHEM_FILE_SIZE_LIMIT; INVENTORY_MODE = limit.INVENTORY_MODE; SPEED_REDUCTION = limit.SPEED_REDUCTION; FAST_PLACEMENT = limit.FAST_PLACEMENT; @@ -279,6 +287,8 @@ public class FaweLimit { limit.MAX_FAILS = MAX_FAILS; limit.MAX_ITERATIONS = MAX_ITERATIONS; limit.MAX_HISTORY = MAX_HISTORY; + limit.SCHEM_FILE_SIZE_LIMIT = SCHEM_FILE_SIZE_LIMIT; + limit.SCHEM_FILE_NUM_LIMIT = SCHEM_FILE_NUM_LIMIT; limit.FAST_PLACEMENT = FAST_PLACEMENT; limit.CONFIRM_LARGE = CONFIRM_LARGE; limit.RESTRICT_HISTORY_TO_REGIONS = RESTRICT_HISTORY_TO_REGIONS; diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/command/SchematicCommands.java b/worldedit-core/src/main/java/com/sk89q/worldedit/command/SchematicCommands.java index 1158a7847..3bd62f02b 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/command/SchematicCommands.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/command/SchematicCommands.java @@ -700,10 +700,10 @@ public class SchematicCommands { String headerBytesElem = String.format("%.1fkb", totalBytes / 1000.0); - if (Settings.settings().PATHS.PER_PLAYER_SCHEMATICS && Settings.settings().EXPERIMENTAL.PER_PLAYER_FILE_SIZE_LIMIT > -1) { + if (Settings.settings().PATHS.PER_PLAYER_SCHEMATICS && actor.getLimit().SCHEM_FILE_SIZE_LIMIT > -1) { headerBytesElem += String.format( " / %dkb", - Settings.settings().EXPERIMENTAL.PER_PLAYER_FILE_SIZE_LIMIT + actor.getLimit().SCHEM_FILE_SIZE_LIMIT ); } @@ -837,7 +837,7 @@ public class SchematicCommands { //FAWE start boolean checkFilesize = Settings.settings().PATHS.PER_PLAYER_SCHEMATICS - && Settings.settings().EXPERIMENTAL.PER_PLAYER_FILE_SIZE_LIMIT > -1; + && actor.getLimit().SCHEM_FILE_SIZE_LIMIT > -1; double directorysizeKb = 0; String curFilepath = file.getAbsolutePath(); @@ -867,7 +867,7 @@ public class SchematicCommands { } - if (Settings.settings().PATHS.PER_PLAYER_SCHEMATICS && Settings.settings().EXPERIMENTAL.PER_PLAYER_FILE_NUM_LIMIT > -1) { + if (Settings.settings().PATHS.PER_PLAYER_SCHEMATICS && actor.getLimit().SCHEM_FILE_NUM_LIMIT > -1) { if (numFiles == -1) { numFiles = 0; @@ -880,7 +880,7 @@ public class SchematicCommands { } } } - int limit = Settings.settings().EXPERIMENTAL.PER_PLAYER_FILE_NUM_LIMIT; + int limit = actor.getLimit().SCHEM_FILE_NUM_LIMIT; if (numFiles >= limit) { TextComponent noSlotsErr = TextComponent.of( //TODO - to be moved into captions/translatablecomponents @@ -931,7 +931,7 @@ public class SchematicCommands { if (checkFilesize) { double curKb = filesizeKb + directorysizeKb; - int allocatedKb = Settings.settings().EXPERIMENTAL.PER_PLAYER_FILE_SIZE_LIMIT; + int allocatedKb = actor.getLimit().SCHEM_FILE_SIZE_LIMIT; if (overwrite) { curKb -= oldKbOverwritten; @@ -966,11 +966,11 @@ public class SchematicCommands { actor.print(kbRemainingNotif); } - if (Settings.settings().PATHS.PER_PLAYER_SCHEMATICS && Settings.settings().EXPERIMENTAL.PER_PLAYER_FILE_NUM_LIMIT > -1) { + if (Settings.settings().PATHS.PER_PLAYER_SCHEMATICS && actor.getLimit().SCHEM_FILE_NUM_LIMIT > -1) { TextComponent slotsRemainingNotif = TextComponent.of( //TODO - to be moved into captions/translatablecomponents - "You have " + (Settings.settings().EXPERIMENTAL.PER_PLAYER_FILE_NUM_LIMIT - numFiles) + "You have " + (actor.getLimit().SCHEM_FILE_NUM_LIMIT - numFiles) + " schematic file slots left.", TextColor.GRAY );