From 254655abdb4c0797afe41a3a79047704235acbee Mon Sep 17 00:00:00 2001 From: ljacqu Date: Tue, 22 Nov 2016 21:16:56 +0100 Subject: [PATCH] #411 Migrate settings to new structure (work in progress) - Work in progress: config.yml cannot be loaded after migration --- .../initialization/SettingsProvider.java | 3 +- .../process/login/ProcessSyncPlayerLogin.java | 16 +-- .../fr/xephi/authme/settings/Settings.java | 2 +- .../settings/SettingsMigrationService.java | 57 ++++++++++- .../settings/commandconfig/Command.java | 17 ++++ .../settings/commandconfig/CommandConfig.java | 8 +- .../commandconfig/CommandManager.java | 27 ++++- .../commandconfig/CommandsMigrater.java | 98 +++++++++++++++++++ .../properties/RegistrationSettings.java | 23 ----- 9 files changed, 206 insertions(+), 45 deletions(-) create mode 100644 src/main/java/fr/xephi/authme/settings/commandconfig/CommandsMigrater.java diff --git a/src/main/java/fr/xephi/authme/initialization/SettingsProvider.java b/src/main/java/fr/xephi/authme/initialization/SettingsProvider.java index 5d98cac30..f9c8638af 100644 --- a/src/main/java/fr/xephi/authme/initialization/SettingsProvider.java +++ b/src/main/java/fr/xephi/authme/initialization/SettingsProvider.java @@ -20,6 +20,8 @@ public class SettingsProvider implements Provider { @Inject @DataFolder private File dataFolder; + @Inject + private SettingsMigrationService migrationService; SettingsProvider() { } @@ -36,7 +38,6 @@ public class SettingsProvider implements Provider { FileUtils.create(configFile); } PropertyResource resource = new YamlFileResource(configFile); - SettingsMigrationService migrationService = new SettingsMigrationService(dataFolder); ConfigurationData configurationData = AuthMeSettingsRetriever.buildConfigurationData(); return new Settings(dataFolder, resource, migrationService, configurationData); } diff --git a/src/main/java/fr/xephi/authme/process/login/ProcessSyncPlayerLogin.java b/src/main/java/fr/xephi/authme/process/login/ProcessSyncPlayerLogin.java index 2488be8a1..9c73725e4 100644 --- a/src/main/java/fr/xephi/authme/process/login/ProcessSyncPlayerLogin.java +++ b/src/main/java/fr/xephi/authme/process/login/ProcessSyncPlayerLogin.java @@ -11,6 +11,7 @@ import fr.xephi.authme.listener.PlayerListener; import fr.xephi.authme.process.ProcessService; import fr.xephi.authme.process.SynchronousProcess; import fr.xephi.authme.service.BungeeService; +import fr.xephi.authme.settings.commandconfig.CommandManager; import fr.xephi.authme.settings.properties.RegistrationSettings; import fr.xephi.authme.service.BukkitService; import fr.xephi.authme.service.TeleportationService; @@ -49,6 +50,9 @@ public class ProcessSyncPlayerLogin implements SynchronousProcess { @Inject private DataSource dataSource; + @Inject + private CommandManager commandManager; + ProcessSyncPlayerLogin() { } @@ -60,16 +64,6 @@ public class ProcessSyncPlayerLogin implements SynchronousProcess { } } - private void forceCommands(Player player) { - for (String command : service.getProperty(RegistrationSettings.FORCE_COMMANDS)) { - player.performCommand(command.replace("%p", player.getName())); - } - for (String command : service.getProperty(RegistrationSettings.FORCE_COMMANDS_AS_CONSOLE)) { - Bukkit.getServer().dispatchCommand( - Bukkit.getServer().getConsoleSender(), command.replace("%p", player.getName())); - } - } - public void processPlayerLogin(Player player) { final String name = player.getName().toLowerCase(); @@ -124,7 +118,7 @@ public class ProcessSyncPlayerLogin implements SynchronousProcess { } // Login is now finished; we can force all commands - forceCommands(player); + commandManager.runCommandsOnLogin(player); // Send Bungee stuff. The service will check if it is enabled or not. bungeeService.connectPlayer(player); diff --git a/src/main/java/fr/xephi/authme/settings/Settings.java b/src/main/java/fr/xephi/authme/settings/Settings.java index fb4c06246..5bf9d0030 100644 --- a/src/main/java/fr/xephi/authme/settings/Settings.java +++ b/src/main/java/fr/xephi/authme/settings/Settings.java @@ -68,7 +68,7 @@ public class Settings extends SettingsManager { private void loadSettingsFromFiles() { passwordEmailMessage = readFile("email.html"); recoveryCodeEmailMessage = readFile("recovery_code_email.html"); - welcomeMessage = readFile("welcome.txt").split("\n"); + welcomeMessage = readFile("welcome.txt").split("\\n"); } @Override diff --git a/src/main/java/fr/xephi/authme/settings/SettingsMigrationService.java b/src/main/java/fr/xephi/authme/settings/SettingsMigrationService.java index 954116a4a..07d3531e6 100644 --- a/src/main/java/fr/xephi/authme/settings/SettingsMigrationService.java +++ b/src/main/java/fr/xephi/authme/settings/SettingsMigrationService.java @@ -2,16 +2,20 @@ package fr.xephi.authme.settings; import com.github.authme.configme.migration.PlainMigrationService; import com.github.authme.configme.properties.Property; +import com.github.authme.configme.properties.StringListProperty; import com.github.authme.configme.resource.PropertyResource; import com.google.common.base.Objects; import fr.xephi.authme.ConsoleLogger; +import fr.xephi.authme.initialization.DataFolder; import fr.xephi.authme.output.LogLevel; import fr.xephi.authme.settings.properties.PluginSettings; import fr.xephi.authme.settings.properties.SecuritySettings; +import javax.inject.Inject; import java.io.File; import java.io.FileWriter; import java.io.IOException; +import java.util.Collections; import java.util.List; import static com.github.authme.configme.properties.PropertyInitializer.newListProperty; @@ -28,10 +32,20 @@ import static fr.xephi.authme.settings.properties.RestrictionSettings.FORCE_SPAW */ public class SettingsMigrationService extends PlainMigrationService { - private final File pluginFolder; + @Inject + @DataFolder + private File pluginFolder; - public SettingsMigrationService(File pluginFolder) { - this.pluginFolder = pluginFolder; + // Stores old commands that need to be migrated to the new commands configuration + // We need to store it in here for retrieval when we build the CommandConfig. Retrieving it from the config.yml is + // not possible since this migration service may trigger config.yml to be resaved. As the old command settings + // don't exist in the code anymore, as soon as config.yml is resaved we lose this information. + private List onLoginCommands = Collections.emptyList(); + private List onLoginConsoleCommands = Collections.emptyList(); + private List onRegisterCommands = Collections.emptyList(); + private List onRegisterConsoleCommands = Collections.emptyList(); + + SettingsMigrationService() { } @Override @@ -42,6 +56,8 @@ public class SettingsMigrationService extends PlainMigrationService { changes = true; } + gatherOldCommandSettings(resource); + // Note ljacqu 20160211: Concatenating migration methods with | instead of the usual || // ensures that all migrations will be performed return changes @@ -59,7 +75,9 @@ public class SettingsMigrationService extends PlainMigrationService { "Converter.Rakamak.newPasswordHash", "Hooks.chestshop", "Hooks.legacyChestshop", "Hooks.notifications", "Passpartu", "Performances", "settings.restrictions.enablePasswordVerifier", "Xenoforo.predefinedSalt", "VeryGames", "settings.restrictions.allowAllCommandsIfRegistrationIsOptional", "DataSource.mySQLWebsite", - "Hooks.customAttributes", "Security.stop.kickPlayersBeforeStopping"}; + "Hooks.customAttributes", "Security.stop.kickPlayersBeforeStopping", + "settings.restrictions.keepCollisionsDisabled", "settings.forceCommands", "settings.forceCommandsAsConsole", + "settings.forceRegisterCommands", "settings.forceRegisterCommandsAsConsole"}; for (String deprecatedPath : deprecatedProperties) { if (resource.contains(deprecatedPath)) { return true; @@ -68,6 +86,37 @@ public class SettingsMigrationService extends PlainMigrationService { return false; } + // ---------------- + // Forced commands relocation (from config.yml to commands.yml) + // ---------------- + private void gatherOldCommandSettings(PropertyResource resource) { + onLoginCommands = getStringList(resource, "settings.forceCommands"); + onLoginConsoleCommands = getStringList(resource, "settings.forceCommandsAsConsole"); + onRegisterCommands = getStringList(resource, "settings.forceRegisterCommands"); + onRegisterConsoleCommands = getStringList(resource, "settings.forceRegisterCommandsAsConsole"); + } + + private List getStringList(PropertyResource resource, String path) { + List entries = new StringListProperty(path).getFromResource(resource); + return entries == null ? Collections.emptyList() : entries; + } + + public List getOnLoginCommands() { + return onLoginCommands; + } + + public List getOnLoginConsoleCommands() { + return onLoginConsoleCommands; + } + + public List getOnRegisterCommands() { + return onRegisterCommands; + } + + public List getOnRegisterConsoleCommands() { + return onRegisterConsoleCommands; + } + // -------- // Specific migrations // -------- diff --git a/src/main/java/fr/xephi/authme/settings/commandconfig/Command.java b/src/main/java/fr/xephi/authme/settings/commandconfig/Command.java index 38bb59913..5cb50998a 100644 --- a/src/main/java/fr/xephi/authme/settings/commandconfig/Command.java +++ b/src/main/java/fr/xephi/authme/settings/commandconfig/Command.java @@ -10,6 +10,23 @@ public class Command { /** The executor of the command. */ private Executor executor = Executor.PLAYER; + /** + * Default constructor (for bean mapping). + */ + public Command() { + } + + /** + * Constructor. + * + * @param command the command + * @param executor the executor of the command + */ + public Command(String command, Executor executor) { + this.command = command; + this.executor = executor; + } + public String getCommand() { return command; } diff --git a/src/main/java/fr/xephi/authme/settings/commandconfig/CommandConfig.java b/src/main/java/fr/xephi/authme/settings/commandconfig/CommandConfig.java index 00d35e2de..53246cdb3 100644 --- a/src/main/java/fr/xephi/authme/settings/commandconfig/CommandConfig.java +++ b/src/main/java/fr/xephi/authme/settings/commandconfig/CommandConfig.java @@ -1,6 +1,6 @@ package fr.xephi.authme.settings.commandconfig; -import java.util.Collections; +import java.util.HashMap; import java.util.Map; /** @@ -10,9 +10,9 @@ import java.util.Map; */ public class CommandConfig { - private Map onJoin = Collections.emptyMap(); - private Map onLogin = Collections.emptyMap(); - private Map onRegister = Collections.emptyMap(); + private Map onJoin = new HashMap<>(); + private Map onLogin = new HashMap<>(); + private Map onRegister = new HashMap<>(); public Map getOnJoin() { return onJoin; diff --git a/src/main/java/fr/xephi/authme/settings/commandconfig/CommandManager.java b/src/main/java/fr/xephi/authme/settings/commandconfig/CommandManager.java index ebfcce2a0..b37e01eb7 100644 --- a/src/main/java/fr/xephi/authme/settings/commandconfig/CommandManager.java +++ b/src/main/java/fr/xephi/authme/settings/commandconfig/CommandManager.java @@ -2,9 +2,11 @@ package fr.xephi.authme.settings.commandconfig; import com.github.authme.configme.SettingsManager; import com.github.authme.configme.resource.YamlFileResource; +import fr.xephi.authme.ConsoleLogger; import fr.xephi.authme.initialization.DataFolder; import fr.xephi.authme.initialization.Reloadable; import fr.xephi.authme.service.BukkitService; +import fr.xephi.authme.settings.Settings; import fr.xephi.authme.util.FileUtils; import org.bukkit.entity.Player; @@ -27,6 +29,12 @@ public class CommandManager implements Reloadable { @Inject private BukkitService bukkitService; + @Inject + private CommandsMigrater commandsMigrater; + + @Inject + private Settings settings; + CommandManager() { } @@ -39,6 +47,10 @@ public class CommandManager implements Reloadable { executeCommands(player, commandConfig.getOnRegister()); } + public void runCommandsOnLogin(Player player) { + executeCommands(player, commandConfig.getOnLogin()); + } + private void executeCommands(Player player, Map commands) { for (Command command : commands.values()) { final String execution = command.getCommand().replace("%p", player.getName()); @@ -58,7 +70,20 @@ public class CommandManager implements Reloadable { SettingsManager settingsManager = new SettingsManager( new YamlFileResource(file), null, CommandSettingsHolder.class); - commandConfig = settingsManager.getProperty(CommandSettingsHolder.COMMANDS); + CommandConfig commandConfig = settingsManager.getProperty(CommandSettingsHolder.COMMANDS); + + if (commandsMigrater.transformOldCommands(commandConfig)) { + ConsoleLogger.warning("Old setting properties (such as settings.forceCommands) were found. " + + "They have been moved to commands.yml"); + settingsManager.setProperty(CommandSettingsHolder.COMMANDS, commandConfig); + settingsManager.save(); + settingsManager.reload(); + settings.save(); + settings.reload(); + + commandConfig = settingsManager.getProperty(CommandSettingsHolder.COMMANDS); + } + this.commandConfig = commandConfig; } diff --git a/src/main/java/fr/xephi/authme/settings/commandconfig/CommandsMigrater.java b/src/main/java/fr/xephi/authme/settings/commandconfig/CommandsMigrater.java new file mode 100644 index 000000000..5defb4c00 --- /dev/null +++ b/src/main/java/fr/xephi/authme/settings/commandconfig/CommandsMigrater.java @@ -0,0 +1,98 @@ +package fr.xephi.authme.settings.commandconfig; + +import fr.xephi.authme.ConsoleLogger; +import fr.xephi.authme.settings.SettingsMigrationService; +import fr.xephi.authme.util.RandomStringUtils; + +import javax.inject.Inject; +import java.util.List; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; + +/** + * Migrates the commands from their old location, in config.yml, to the dedicated commands configuration file. + */ +class CommandsMigrater { + + @Inject + private SettingsMigrationService settingsMigrationService; + + CommandsMigrater() { + } + + boolean transformOldCommands(CommandConfig commandConfig) { + boolean didMoveCommands = false; + for (MigratableCommandSection section : MigratableCommandSection.values()) { + didMoveCommands |= section.convertCommands(settingsMigrationService, commandConfig); + } + return didMoveCommands; + } + + /** + * Enum defining the forced command settings that should be moved from config.yml to the new commands.yml file. + */ + private enum MigratableCommandSection { + + ON_JOIN( + SettingsMigrationService::getOnLoginCommands, + Executor.PLAYER, + CommandConfig::getOnJoin), + + ON_JOIN_CONSOLE( + SettingsMigrationService::getOnLoginConsoleCommands, + Executor.CONSOLE, + CommandConfig::getOnJoin), + + ON_REGISTER( + SettingsMigrationService::getOnRegisterCommands, + Executor.PLAYER, + CommandConfig::getOnRegister), + + ON_REGISTER_CONSOLE( + SettingsMigrationService::getOnRegisterConsoleCommands, + Executor.CONSOLE, + CommandConfig::getOnRegister); + + private final Function> legacyCommandsGetter; + private final Executor executor; + private final Function> commandMapGetter; + + /** + * Constructor. + * + * @param legacyCommandsGetter getter on MigrationService to get the deprecated command entries + * @param executor the executor of the commands + * @param commandMapGetter the getter for the commands map in the new settings structure to add the old + * settings to after conversion + */ + MigratableCommandSection(Function> legacyCommandsGetter, + Executor executor, + Function> commandMapGetter) { + this.legacyCommandsGetter = legacyCommandsGetter; + this.executor = executor; + this.commandMapGetter = commandMapGetter; + } + + /** + * Adds the commands from the sections' settings migration service to the appropriate place in the new + * command config object. + * + * @param settingsMigrationService settings migration service to read old commands from + * @param commandConfig command config object to add converted commands to + * @return true if there were commands to migrate, false otherwise + */ + boolean convertCommands(SettingsMigrationService settingsMigrationService, CommandConfig commandConfig) { + List commands = legacyCommandsGetter.apply(settingsMigrationService).stream() + .map(cmd -> new Command(cmd, executor)).collect(Collectors.toList()); + + if (commands.isEmpty()) { + return false; + } + Map commandMap = commandMapGetter.apply(commandConfig); + commands.forEach(cmd -> commandMap.put(RandomStringUtils.generate(10), cmd)); + ConsoleLogger.info("Migrated " + commands.size() + " of type " + this); + return true; + } + } +} diff --git a/src/main/java/fr/xephi/authme/settings/properties/RegistrationSettings.java b/src/main/java/fr/xephi/authme/settings/properties/RegistrationSettings.java index bf250e1f4..a3de56b82 100644 --- a/src/main/java/fr/xephi/authme/settings/properties/RegistrationSettings.java +++ b/src/main/java/fr/xephi/authme/settings/properties/RegistrationSettings.java @@ -4,9 +4,6 @@ import com.github.authme.configme.Comment; import com.github.authme.configme.SettingsHolder; import com.github.authme.configme.properties.Property; -import java.util.List; - -import static com.github.authme.configme.properties.PropertyInitializer.newListProperty; import static com.github.authme.configme.properties.PropertyInitializer.newProperty; public class RegistrationSettings implements SettingsHolder { @@ -48,26 +45,6 @@ public class RegistrationSettings implements SettingsHolder { public static final Property FORCE_LOGIN_AFTER_REGISTER = newProperty("settings.registration.forceLoginAfterRegister", false); - @Comment("Force these commands after /login, without any '/', use %p to replace with player name") - public static final Property> FORCE_COMMANDS = - newListProperty("settings.forceCommands"); - - @Comment({ - "Force these commands after /login as service console, without any '/'.", - "Use %p to replace with player name"}) - public static final Property> FORCE_COMMANDS_AS_CONSOLE = - newListProperty("settings.forceCommandsAsConsole"); - - @Comment("Force these commands after /register, without any '/', use %p to replace with player name") - public static final Property> FORCE_REGISTER_COMMANDS = - newListProperty("settings.forceRegisterCommands"); - - @Comment({ - "Force these commands after /register as a server console, without any '/'.", - "Use %p to replace with player name"}) - public static final Property> FORCE_REGISTER_COMMANDS_AS_CONSOLE = - newListProperty("settings.forceRegisterCommandsAsConsole"); - @Comment({ "Enable to display the welcome message (welcome.txt) after a login", "You can use colors in this welcome.txt + some replaced strings:",