Merge pull request #3006 from Multiverse/ben/mv5/config-migrate

Implement worlds.yml config migrate
This commit is contained in:
Ben Woo 2023-09-10 14:04:03 +08:00 committed by GitHub
commit 323145ebf0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 484 additions and 13 deletions

View File

@ -35,7 +35,9 @@ public abstract class GenericConfigHandle<C extends ConfigurationSection> {
*/
public Try<Void> load() {
return Try.run(() -> {
migrateConfig();
if (!config.getKeys(false).isEmpty()) {
migrateConfig();
}
setUpNodes();
});
}

View File

@ -0,0 +1,30 @@
package com.onarandombox.MultiverseCore.configuration.migration;
import co.aikar.commands.ACFUtil;
import com.dumptruckman.minecraft.util.Logging;
import org.bukkit.configuration.ConfigurationSection;
/**
* Single migrator action that converts a string value to a long.
*/
public class LongMigratorAction implements MigratorAction {
public static LongMigratorAction of(String path) {
return new LongMigratorAction(path);
}
private final String path;
LongMigratorAction(String path) {
this.path = path;
}
/**
* {@inheritDoc}
*/
@Override
public void migrate(ConfigurationSection config) {
config.set(path, ACFUtil.parseLong(config.getString(path)));
Logging.info("Converted %s to long %s", path, config.getLong(path));
}
}

View File

@ -37,8 +37,8 @@ public class MoveMigratorAction implements MigratorAction {
public void migrate(ConfigurationSection config) {
Optional.ofNullable(config.get(fromPath))
.ifPresent(value -> {
config.set(toPath, value);
config.set(fromPath, null);
config.set(toPath, value);
Logging.config("Moved path %s to %s", fromPath, toPath);
});
}

View File

@ -0,0 +1,29 @@
package com.onarandombox.MultiverseCore.configuration.migration;
import com.dumptruckman.minecraft.util.Logging;
import org.bukkit.configuration.ConfigurationSection;
/**
* Single migrator action changes a string value of "null" to an empty string.
*/
public class NullStringMigratorAction implements MigratorAction {
public static NullStringMigratorAction of(String path) {
return new NullStringMigratorAction(path);
}
private final String path;
protected NullStringMigratorAction(String path) {
this.path = path;
}
/**
* {@inheritDoc}
*/
@Override
public void migrate(ConfigurationSection config) {
config.set(path, "null".equals(config.getString(path)) ? "" : config.getString(path));
Logging.info("Converted %s to %s", path, config.getString(path));
}
}

View File

@ -0,0 +1,88 @@
package com.onarandombox.MultiverseCore.worldnew.config;
import com.onarandombox.MultiverseCore.configuration.migration.MigratorAction;
import io.vavr.control.Try;
import org.bukkit.ChatColor;
import org.bukkit.configuration.ConfigurationSection;
import java.util.concurrent.atomic.AtomicReference;
class LegacyAliasMigrator implements MigratorAction {
@Override
public void migrate(ConfigurationSection config) {
AtomicReference<String> alias = new AtomicReference<>(config.getString("alias", ""));
if (alias.get().isEmpty()) return;
String color = config.getString("color", "");
String style = config.getString("style", "");
Try.of(() -> Enum.valueOf(EnglishChatColor.class, color.toUpperCase()))
.map(c -> c.color)
.onSuccess(c -> {
if (c != ChatColor.WHITE) {
alias.set("&" + c.getChar() + alias.get());
}
});
Try.of(() -> Enum.valueOf(EnglishChatStyle.class, style.toUpperCase()))
.map(c -> c.color)
.onSuccess(s -> {
if (s != null) {
alias.set("&" + s.getChar() + alias.get());
}
});
config.set("alias", alias.get());
config.set("color", null);
config.set("style", null);
}
private enum EnglishChatColor {
// BEGIN CHECKSTYLE-SUPPRESSION: JavadocVariable
AQUA(ChatColor.AQUA),
BLACK(ChatColor.BLACK),
BLUE(ChatColor.BLUE),
DARKAQUA(ChatColor.DARK_AQUA),
DARKBLUE(ChatColor.DARK_BLUE),
DARKGRAY(ChatColor.DARK_GRAY),
DARKGREEN(ChatColor.DARK_GREEN),
DARKPURPLE(ChatColor.DARK_PURPLE),
DARKRED(ChatColor.DARK_RED),
GOLD(ChatColor.GOLD),
GRAY(ChatColor.GRAY),
GREEN(ChatColor.GREEN),
LIGHTPURPLE(ChatColor.LIGHT_PURPLE),
RED(ChatColor.RED),
YELLOW(ChatColor.YELLOW),
WHITE(ChatColor.WHITE);
// END CHECKSTYLE-SUPPRESSION: JavadocVariable
private final ChatColor color;
//private final String text;
EnglishChatColor(ChatColor color) {
this.color = color;
}
}
private enum EnglishChatStyle {
// BEGIN CHECKSTYLE-SUPPRESSION: JavadocVariable
/**
* No style.
*/
NORMAL(null),
MAGIC(ChatColor.MAGIC),
BOLD(ChatColor.BOLD),
STRIKETHROUGH(ChatColor.STRIKETHROUGH),
UNDERLINE(ChatColor.UNDERLINE),
ITALIC(ChatColor.ITALIC);
// END CHECKSTYLE-SUPPRESSION: JavadocVariable
private final ChatColor color;
EnglishChatStyle(ChatColor color) {
this.color = color;
}
}
}

View File

@ -2,6 +2,13 @@ package com.onarandombox.MultiverseCore.worldnew.config;
import com.dumptruckman.minecraft.util.Logging;
import com.onarandombox.MultiverseCore.configuration.handle.ConfigurationSectionHandle;
import com.onarandombox.MultiverseCore.configuration.migration.BooleanMigratorAction;
import com.onarandombox.MultiverseCore.configuration.migration.ConfigMigrator;
import com.onarandombox.MultiverseCore.configuration.migration.IntegerMigratorAction;
import com.onarandombox.MultiverseCore.configuration.migration.LongMigratorAction;
import com.onarandombox.MultiverseCore.configuration.migration.MoveMigratorAction;
import com.onarandombox.MultiverseCore.configuration.migration.NullStringMigratorAction;
import com.onarandombox.MultiverseCore.configuration.migration.VersionMigrator;
import com.onarandombox.MultiverseCore.world.configuration.AllowedPortalType;
import com.onarandombox.MultiverseCore.worldnew.LoadedMultiverseWorld;
import io.vavr.control.Try;
@ -29,14 +36,68 @@ public final class WorldConfig {
public WorldConfig(@NotNull String worldName, @NotNull final ConfigurationSection configSection) {
this.worldName = worldName;
this.configNodes = new WorldConfigNodes();
// TODO: Config migration and version
this.configHandle = ConfigurationSectionHandle.builder(configSection)
.logger(Logging.getLogger())
.nodes(configNodes.getNodes())
.migrator(ConfigMigrator.builder(configNodes.VERSION)
.addVersionMigrator(initialVersionMigrator())
.build())
.build();
load();
}
private VersionMigrator initialVersionMigrator() {
return VersionMigrator.builder(1.0)
.addAction(MoveMigratorAction.of("adjustSpawn", "adjust-spawn"))
.addAction(BooleanMigratorAction.of("adjust-spawn"))
.addAction(MoveMigratorAction.of("allowFlight", "allow-flight"))
.addAction(BooleanMigratorAction.of("allow-flight"))
.addAction(MoveMigratorAction.of("allowWeather", "allow-weather"))
.addAction(BooleanMigratorAction.of("allow-weather"))
.addAction(MoveMigratorAction.of("autoHeal", "auto-heal"))
.addAction(BooleanMigratorAction.of("auto-heal"))
.addAction(MoveMigratorAction.of("autoLoad", "auto-load"))
.addAction(BooleanMigratorAction.of("auto-load"))
.addAction(MoveMigratorAction.of("bedRespawn", "bed-respawn"))
.addAction(BooleanMigratorAction.of("bed-respawn"))
//.addAction(MoveMigratorAction.of("difficulty", "difficulty"))
.addAction(MoveMigratorAction.of("entryfee.amount", "entry-fee.amount"))
.addAction(MoveMigratorAction.of("entryfee.currency", "entry-fee.currency"))
//.addAction(MoveMigratorAction.of("environment", "environment"))
.addAction(MoveMigratorAction.of("gameMode", "gamemode"))
//.addAction(MoveMigratorAction.of("generator", "generator"))
.addAction(NullStringMigratorAction.of("generator"))
//.addAction(MoveMigratorAction.of("hidden", "hidden"))
.addAction(BooleanMigratorAction.of("hidden"))
//.addAction(MoveMigratorAction.of("hunger", "hunger"))
.addAction(BooleanMigratorAction.of("hunger"))
.addAction(MoveMigratorAction.of("keepSpawnInMemory", "keep-spawn-in-memory"))
.addAction(BooleanMigratorAction.of("keep-spawn-in-memory"))
.addAction(MoveMigratorAction.of("playerLimit", "player-limit"))
.addAction(IntegerMigratorAction.of("player-limit"))
.addAction(MoveMigratorAction.of("portalForm", "portal-form"))
//.addAction(MoveMigratorAction.of("pvp", "pvp"))
.addAction(BooleanMigratorAction.of("pvp"))
.addAction(MoveMigratorAction.of("respawnWorld", "respawn-world"))
//.addAction(MoveMigratorAction.of("scale", "scale"))
//.addAction(MoveMigratorAction.of("seed", "seed"))
.addAction(LongMigratorAction.of("seed"))
.addAction(MoveMigratorAction.of("spawnLocation", "spawn-location"))
//.addAction(MoveMigratorAction.of("spawning.animals.spawn", "spawning.animals.spawn"))
.addAction(BooleanMigratorAction.of("spawning.animals.spawn"))
.addAction(MoveMigratorAction.of("spawning.animals.amount", "spawning.animals.tick-rate"))
.addAction(IntegerMigratorAction.of("spawning.animals.tick-rate"))
//.addAction(MoveMigratorAction.of("spawning.animals.exceptions", "spawning.animals.exceptions"))
//.addAction(MoveMigratorAction.of("spawning.monsters.spawn", "spawning.monsters.spawn"))
.addAction(BooleanMigratorAction.of("spawning.monsters.spawn"))
.addAction(MoveMigratorAction.of("spawning.monsters.amount", "spawning.monsters.tick-rate"))
.addAction(IntegerMigratorAction.of("spawning.monsters.tick-rate"))
//.addAction(MoveMigratorAction.of("spawning.monsters.exceptions", "spawning.monsters.exceptions"))
.addAction(MoveMigratorAction.of("worldBlacklist", "world-blacklist"))
.addAction(new LegacyAliasMigrator())
.build();
}
public Try<Void> load() {
return configHandle.load();
}

View File

@ -1,5 +1,6 @@
package com.onarandombox.MultiverseCore.worldnew.config;
import com.onarandombox.MultiverseCore.config.MVCoreConfig;
import com.onarandombox.MultiverseCore.configuration.node.ConfigNode;
import com.onarandombox.MultiverseCore.configuration.node.Node;
import com.onarandombox.MultiverseCore.configuration.node.NodeGroup;
@ -18,6 +19,8 @@ import java.util.List;
* Represents nodes in a world configuration.
*/
public class WorldConfigNodes {
static final double CONFIG_VERSION = 1.0;
private final NodeGroup nodes = new NodeGroup();
LoadedMultiverseWorld world = null;
@ -235,5 +238,8 @@ public class WorldConfigNodes {
.name("world-blacklist")
.build());
// TODO: Migrate color and style into alias
public final ConfigNode<Double> VERSION = node(ConfigNode.builder("version", Double.class)
.defaultValue(CONFIG_VERSION)
.name(null)
.build());
}

View File

@ -1,5 +1,6 @@
package com.onarandombox.MultiverseCore.worldnew.config;
import com.dumptruckman.minecraft.util.Logging;
import com.onarandombox.MultiverseCore.MultiverseCore;
import io.vavr.Tuple2;
import io.vavr.control.Try;
@ -13,6 +14,7 @@ import org.jvnet.hk2.annotations.Service;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
@ -55,14 +57,54 @@ public final class WorldsConfigManager {
* @throws InvalidConfigurationException If the file is not a valid YAML file.
*/
private void loadWorldYmlFile() throws IOException, InvalidConfigurationException {
if (!worldConfigFile.exists() && !worldConfigFile.createNewFile()) {
boolean exists = worldConfigFile.exists();
if (!exists && !worldConfigFile.createNewFile()) {
throw new IllegalStateException("Could not create worlds.yml config file");
}
if (exists) {
migrateRemoveOldConfigSerializable();
}
worldsConfig = new YamlConfiguration();
worldsConfig.load(worldConfigFile);
}
private void migrateRemoveOldConfigSerializable() {
Try.of(() -> Files.readString(worldConfigFile.toPath()))
.mapTry(configData -> {
if (!configData.contains("==: MVWorld")) {
throw new ConfigMigratedException();
}
return configData.replace(" ==: MVWorld\n", "")
.replace(" ==: MVSpawnSettings\n", "")
.replace(" ==: MVSpawnSubSettings\n", "")
.replace(" ==: MVEntryFee\n", "");
})
.andThenTry(configData -> Files.writeString(worldConfigFile.toPath(), configData))
.andThenTry(() -> {
YamlConfiguration config = YamlConfiguration.loadConfiguration(worldConfigFile);
List<ConfigurationSection> worlds = config.getConfigurationSection("worlds")
.getKeys(false)
.stream()
.map(worldName -> config.getConfigurationSection("worlds." + worldName))
.toList();
config.set("worlds", null);
for (ConfigurationSection world : worlds) {
config.createSection(world.getName(), world.getValues(true));
}
config.save(worldConfigFile);
})
.onFailure(e -> {
if (e instanceof ConfigMigratedException) {
Logging.fine("Config already migrated");
return;
}
Logging.warning("Failed to migrate old worlds.yml file: %s", e.getMessage());
e.printStackTrace();
});
}
/**
* Parses the worlds.yml file and creates a WorldConfig for each world in the file if it doesn't already exist.
*
@ -158,4 +200,10 @@ public final class WorldsConfigManager {
return worldsConfig.isConfigurationSection(worldName)
? worldsConfig.getConfigurationSection(worldName) : worldsConfig.createSection(worldName);
}
private final class ConfigMigratedException extends RuntimeException {
private ConfigMigratedException() {
super("Config migrated");
}
}
}

View File

@ -2,6 +2,7 @@ package org.mvplugins.multiverse.core.world
import com.onarandombox.MultiverseCore.worldnew.config.SpawnLocation
import com.onarandombox.MultiverseCore.worldnew.config.WorldsConfigManager
import org.bukkit.World.Environment
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertTrue
import org.mvplugins.multiverse.core.TestWithMockBukkit
@ -24,8 +25,6 @@ class WorldConfigMangerTest : TestWithMockBukkit() {
worldConfigManager = multiverseCore.getService(WorldsConfigManager::class.java).takeIf { it != null } ?: run {
throw IllegalStateException("WorldsConfigManager is not available as a service") }
assertTrue(worldConfigManager.load().isSuccess)
}
@Test
@ -35,32 +34,51 @@ class WorldConfigMangerTest : TestWithMockBukkit() {
@Test
fun `Old world config is migrated`() {
// TODO: When logic is implemented, check that the old config is migrated
val oldConfig = getResourceAsText("/old_worlds.yml")
assertNotNull(oldConfig)
File(Path.of(multiverseCore.dataFolder.absolutePath, "worlds2.yml").absolutePathString()).writeText(oldConfig)
assertTrue(worldConfigManager.load().isSuccess)
assertTrue(worldConfigManager.save().isSuccess)
val endWorldConfig = worldConfigManager.getWorldConfig("world_the_end")
assertNotNull(endWorldConfig)
assertEquals("&aworld the end", endWorldConfig.alias)
assertEquals(Environment.THE_END, endWorldConfig.environment)
val worldConfig = worldConfigManager.getWorldConfig("world")
assertNotNull(worldConfig)
assertEquals(-5176596003035866649, worldConfig.seed)
assertEquals(listOf("test"), worldConfig.worldBlacklist)
}
@Test
fun `Add a new world to config`() {
assertTrue(worldConfigManager.load().isSuccess)
val worldConfig = worldConfigManager.addWorldConfig("newworld")
worldConfigManager.save()
assertTrue(worldConfigManager.save().isSuccess)
compareConfigFile("worlds2.yml", "/newworld_worlds.yml")
}
@Test
fun `Updating existing world properties`() {
assertTrue(worldConfigManager.load().isSuccess)
val worldConfig = worldConfigManager.getWorldConfig("world")
assertNotNull(worldConfig)
worldConfig.setProperty("adjust-spawn", true)
worldConfig.setProperty("alias", "newalias")
worldConfig.setProperty("spawn-location", SpawnLocation(-64.0, 64.0, 48.0))
worldConfigManager.save()
compareConfigFile("worlds2.yml", "/properties_worlds.yml")
assertTrue(worldConfigManager.save().isSuccess)
}
@Test
fun `Delete world section from config`() {
assertTrue(worldConfigManager.load().isSuccess)
worldConfigManager.deleteWorldConfig("world")
worldConfigManager.save()
assertTrue(worldConfigManager.save().isSuccess)
compareConfigFile("worlds2.yml", "/delete_worlds.yml")
}

View File

@ -34,6 +34,7 @@ world:
tick-rate: -1
exceptions: []
world-blacklist: []
version: 1.0
world_nether:
adjust-spawn: false
alias: ''
@ -70,3 +71,4 @@ world_nether:
tick-rate: -1
exceptions: []
world-blacklist: []
version: 1.0

View File

@ -34,3 +34,4 @@ world_nether:
tick-rate: -1
exceptions: []
world-blacklist: []
version: 1.0

View File

@ -0,0 +1,85 @@
world_the_end:
adjust-spawn: false
alias: &aworld the end
allow-flight: false
allow-weather: true
auto-heal: true
auto-load: true
bed-respawn: true
difficulty: NORMAL
entry-fee:
amount: 0.0
currency: AIR
environment: THE_END
gamemode: SURVIVAL
generator: ''
hidden: false
hunger: true
keep-spawn-in-memory: true
player-limit: -1
portal-form: ALL
pvp: true
respawn-world: ''
scale: 16.0
seed: -5176596003035866649
spawn-location:
==: MVSpawnLocation
x: 0.0
y: 65.0
z: 0.0
pitch: 0.0
yaw: 0.0
spawning:
animals:
spawn: true
tick-rate: -1
exceptions: []
monsters:
spawn: true
tick-rate: -1
exceptions: []
world-blacklist: []
version: 1.0
world:
adjust-spawn: false
alias: ''
allow-flight: false
allow-weather: true
auto-heal: true
auto-load: true
bed-respawn: true
difficulty: NORMAL
entry-fee:
amount: 0.0
currency: AIR
environment: NORMAL
gamemode: SURVIVAL
generator: ''
hidden: false
hunger: true
keep-spawn-in-memory: true
player-limit: -1
portal-form: ALL
pvp: true
respawn-world: ''
scale: 1.0
seed: -5176596003035866649
spawn-location:
==: MVSpawnLocation
x: -64.0
y: 64.0
z: 48.0
pitch: 0.0
yaw: 0.0
spawning:
animals:
spawn: true
tick-rate: -1
exceptions: []
monsters:
spawn: true
tick-rate: -1
exceptions: []
world-blacklist:
- test
version: 1.0

View File

@ -34,6 +34,7 @@ world:
tick-rate: -1
exceptions: []
world-blacklist: []
version: 1.0
world_nether:
adjust-spawn: false
alias: ''
@ -70,6 +71,7 @@ world_nether:
tick-rate: -1
exceptions: []
world-blacklist: []
version: 1.0
newworld:
adjust-spawn: false
alias: ''
@ -106,3 +108,4 @@ newworld:
tick-rate: -1
exceptions: []
world-blacklist: []
version: 1.0

View File

@ -0,0 +1,96 @@
worlds:
world_the_end:
==: MVWorld
hidden: 'false'
alias: world the end
color: GREEN
style: NORMAL
pvp: 'true'
scale: '16.0'
respawnWorld: ''
allowWeather: 'true'
difficulty: NORMAL
spawning:
==: MVSpawnSettings
animals:
==: MVSpawnSubSettings
spawn: 'true'
spawnrate: '-1'
exceptions: []
monsters:
==: MVSpawnSubSettings
spawn: 'true'
spawnrate: '-1'
exceptions: []
entryfee:
==: MVEntryFee
amount: '0.0'
hunger: 'true'
autoHeal: 'true'
adjustSpawn: 'true'
portalForm: ALL
gameMode: SURVIVAL
keepSpawnInMemory: 'true'
spawnLocation:
==: MVSpawnLocation
x: 0.0
y: 65.0
z: 0.0
pitch: 0.0
yaw: 0.0
autoLoad: 'true'
bedRespawn: 'true'
worldBlacklist: []
environment: THE_END
seed: '-5176596003035866649'
generator: 'null'
playerLimit: '-1'
allowFlight: 'true'
world:
==: MVWorld
hidden: 'false'
alias: world
color: WHITE
style: NORMAL
pvp: 'true'
scale: '1.0'
respawnWorld: ''
allowWeather: 'true'
difficulty: NORMAL
spawning:
==: MVSpawnSettings
animals:
==: MVSpawnSubSettings
spawn: 'true'
spawnrate: '-1'
exceptions: []
monsters:
==: MVSpawnSubSettings
spawn: 'true'
spawnrate: '-1'
exceptions: []
entryfee:
==: MVEntryFee
amount: '0.0'
hunger: 'true'
autoHeal: 'true'
adjustSpawn: 'true'
portalForm: ALL
gameMode: SURVIVAL
keepSpawnInMemory: 'true'
spawnLocation:
==: MVSpawnLocation
x: -64.0
y: 64.0
z: 48.0
pitch: 0.0
yaw: 0.0
autoLoad: 'true'
bedRespawn: 'true'
worldBlacklist:
- test
environment: NORMAL
seed: '-5176596003035866649'
generator: 'null'
playerLimit: '-1'
allowFlight: 'true'

View File

@ -39,6 +39,7 @@ world:
tick-rate: -1
exceptions: []
world-blacklist: []
version: 1.0
world_nether:
adjust-spawn: false
alias: ''
@ -75,3 +76,4 @@ world_nether:
tick-rate: -1
exceptions: []
world-blacklist: []
version: 1.0