/* * This file is part of Libelula Minecraft Edition Project. * * Libelula Minecraft Edition is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Libelula Minecraft Edition is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Libelula Minecraft Edition. * If not, see <http://www.gnu.org/licenses/>. * */ package me.libelula.libelulalogger; import java.io.File; import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.TreeMap; import java.util.TreeSet; import org.bukkit.ChatColor; import org.bukkit.Material; import org.bukkit.World; import org.bukkit.command.CommandSender; import org.bukkit.configuration.ConfigurationSection; /** * Class Configuration of the plugin. * * @author Diego Lucio D'Onofrio <ddonofrio@member.fsf.org> * @version 1.0 */ public class Configuration { public enum logType { INTERNAL, EXTERNAL, BOTH; } private static class ValidationMessage { protected enum MessageType { INFO, ERROR, WARNING } protected TreeMap<String, MessageType> messages; public ValidationMessage() { this.messages = new TreeMap<>(); } } private static class Value { protected String dbDirectory; protected int maxDBsizeInDisk; protected int maxEventsInRam; protected List<String> ignoredMaterialsNames; protected TreeSet<String> ignoredPlayerNames; protected List<String> ignoredWorldsNames; protected boolean logOnlyModifiedBlocks; protected String wgRegionPolicy; protected boolean signsLogToFile; protected boolean signsIgnoreEmptyInLog; protected logType signTargetLog; protected String signsExternalLogFileName; protected boolean chestLogToFile; protected logType chestTargetLog; protected String chestExternalLogFileName; } private static class Cache { protected TreeSet<Material> ignoredMaterials; protected TreeSet<World> ignoredWorlds; protected boolean wgRegionPolicyIsDefault; public Cache() { ignoredMaterials = new TreeSet<>(); ignoredWorlds = new TreeSet<>(); wgRegionPolicyIsDefault = false; } } static class WorldComparator implements Comparator<World> { @Override public int compare(World w2, World w1) { return (w1.getUID().compareTo(w2.getUID())); } } private final LibelulaLogger plugin; private Value values; private Cache cache; public Configuration(LibelulaLogger plugin) { this.plugin = plugin; values = loadValues(plugin.getConfig().getDefaults()); cache = new Cache(); cache.ignoredMaterials = new TreeSet<>(); cache.ignoredWorlds = new TreeSet<>(new WorldComparator()); } public void reload() { plugin.reloadConfig(); Value loadedValues = loadValues(plugin.getConfig()); this.values = validatedConfigChanges(values, loadedValues); updateCache(); } public void load() { Value oldValues = loadValues(plugin.getConfig().getDefaults()); plugin.reloadConfig(); Value loadedValues = loadValues(plugin.getConfig()); this.values = validatedConfigChanges(oldValues, loadedValues); updateCache(); } private void updateCache() { this.cache.ignoredMaterials.clear(); for (String mat : values.ignoredMaterialsNames) { this.cache.ignoredMaterials.add(getValidMaterial(mat)); } this.cache.ignoredWorlds.clear(); for (String worldName : values.ignoredWorldsNames) { this.cache.ignoredWorlds.add(plugin.getServer().getWorld(worldName)); } if (values.wgRegionPolicy.equals("EVER")) { cache.wgRegionPolicyIsDefault = true; } else { cache.wgRegionPolicyIsDefault = false; } } private void reportAndClear(ValidationMessage results, CommandSender sender) { for (Map.Entry<String, ValidationMessage.MessageType> entry : results.messages.entrySet()) { switch (entry.getValue()) { case INFO: sender.sendMessage(ChatColor.GREEN + entry.getKey()); break; case WARNING: sender.sendMessage(ChatColor.YELLOW + entry.getKey()); break; case ERROR: sender.sendMessage(ChatColor.RED + entry.getKey()); break; } } results.messages.clear(); } private void reportAndClear(ValidationMessage results) { for (Map.Entry<String, ValidationMessage.MessageType> entry : results.messages.entrySet()) { switch (entry.getValue()) { case INFO: plugin.logInfo(entry.getKey()); break; case WARNING: plugin.logWarning(entry.getKey()); break; case ERROR: plugin.logSevere(entry.getKey()); break; } } results.messages.clear(); } private Value validatedConfigChanges(Value oldValues, Value newValues) { ValidationMessage results = new ValidationMessage(); if (!oldValues.dbDirectory.equals(newValues.dbDirectory)) { if (!validateDbDirectory(newValues, results)) { newValues.dbDirectory = oldValues.dbDirectory; reportAndClear(results); } } if (oldValues.ignoredMaterialsNames != newValues.ignoredMaterialsNames) { TreeSet<Material> ignoredMaterials = validateIgnoredMaterials(newValues, results); newValues.ignoredMaterialsNames.clear(); for (Material mat : ignoredMaterials) { newValues.ignoredMaterialsNames.add(mat.name()); } reportAndClear(results); } if (oldValues.ignoredPlayerNames != newValues.ignoredPlayerNames) { newValues.ignoredPlayerNames = validateIgnoredPlayers(newValues, results); reportAndClear(results); } if (oldValues.ignoredWorldsNames != newValues.ignoredWorldsNames) { TreeSet<World> ignoredWorlds = validateIgnoredWorlds(newValues, results); newValues.ignoredWorldsNames.clear(); for (World world : ignoredWorlds) { newValues.ignoredWorldsNames.add(world.getName()); } reportAndClear(results); } if (oldValues.maxDBsizeInDisk != newValues.maxDBsizeInDisk) { if (!validateMaxDiskDBsize(newValues, results)) { newValues.maxDBsizeInDisk = oldValues.maxDBsizeInDisk; reportAndClear(results); } } if (oldValues.maxEventsInRam != newValues.maxEventsInRam) { if (!validateMaxEventsInRam(newValues, results)) { newValues.maxEventsInRam = oldValues.maxEventsInRam; reportAndClear(results); } } if (!oldValues.wgRegionPolicy.equals(newValues.wgRegionPolicy)) { if (!validateWgRegionsPolicy(newValues, results)) { newValues.wgRegionPolicy = oldValues.wgRegionPolicy; reportAndClear(results); } else { newValues.wgRegionPolicy = newValues.wgRegionPolicy.toUpperCase(); } } return newValues; } private static boolean validateDbDirectory(Value newValues, ValidationMessage results) { File dbDirectoryFile = new File(newValues.dbDirectory); if (!dbDirectoryFile.mkdirs() && !dbDirectoryFile.isDirectory()) { results.messages.put("Unable to create configured database-directory: " .concat(newValues.dbDirectory), ValidationMessage.MessageType.WARNING); return false; } if (!dbDirectoryFile.canRead() || !dbDirectoryFile.canWrite()) { results.messages.put("Unable to perform IO operation into configured database-directory: " .concat(newValues.dbDirectory), ValidationMessage.MessageType.WARNING); return false; } return true; } private static TreeSet<Material> validateIgnoredMaterials(Value newValues, ValidationMessage results) { TreeSet<Material> ignoredMaterials = new TreeSet<>(); for (String matName : newValues.ignoredMaterialsNames) { Material mat = getValidMaterial(matName); if (mat == null) { results.messages.put("Invalid ignored-materials configured: ".concat(matName), ValidationMessage.MessageType.WARNING); } else { if (!ignoredMaterials.add(mat)) { results.messages.put("Duplicated material in ignored-materials: ".concat(matName), ValidationMessage.MessageType.WARNING); } } } return ignoredMaterials; } private TreeSet<String> validateIgnoredPlayers(Value newValues, ValidationMessage results) { TreeSet<String> names = new TreeSet<>(); for (String playerName : newValues.ignoredPlayerNames) { if (!plugin.getServer().getOfflinePlayer(playerName).hasPlayedBefore()) { results.messages.put("ignored-players contains a player which never played: ".concat(playerName), ValidationMessage.MessageType.WARNING); } if (!names.add(playerName)) { results.messages.put("Ignoring duplicated name: ".concat(playerName), ValidationMessage.MessageType.WARNING); } } return names; } private TreeSet<World> validateIgnoredWorlds(Value newValues, ValidationMessage results) { TreeSet<World> ignoredWorlds = new TreeSet<>(new WorldComparator()); for (String worldName : newValues.ignoredWorldsNames) { World ignoredWorld = plugin.getServer().getWorld(worldName); if (ignoredWorld == null) { results.messages.put("ignored-worlds has a configured world which not exists: ".concat(worldName), ValidationMessage.MessageType.WARNING); } else { if (!ignoredWorlds.add(ignoredWorld)) { results.messages.put("ignored-worlds has a duplicated world: ".concat(worldName), ValidationMessage.MessageType.WARNING); } } } return ignoredWorlds; } private static boolean validateMaxDiskDBsize(Value newValues, ValidationMessage results) { if (newValues.maxDBsizeInDisk < 512) { results.messages.put("max-disk-db-size-mb cannot be less than 512MB, wrong configured value: " + newValues.maxDBsizeInDisk, ValidationMessage.MessageType.WARNING); return false; } return true; } private static boolean validateMaxEventsInRam(Value newValues, ValidationMessage results) { if (newValues.maxEventsInRam < 512 || newValues.maxEventsInRam > 2048) { results.messages.put("Ignoring max-events-in-ram out of range value: " + newValues.maxEventsInRam, ValidationMessage.MessageType.WARNING); results.messages.put("max-events-in-ram should be between 512 and 2048.", ValidationMessage.MessageType.INFO); return false; } return true; } private static boolean validateWgRegionsPolicy(Value newValues, ValidationMessage results) { switch (newValues.wgRegionPolicy.toUpperCase()) { case "EVER": case "NEVER": case "DIFFERS": break; default: results.messages.put("Invalid value for worldguard-regions-policy: " + newValues.maxEventsInRam, ValidationMessage.MessageType.WARNING); results.messages.put("Allowed values are: EVER, NEVER and DIFFERS", ValidationMessage.MessageType.INFO); return false; } return true; } private Value loadValues(ConfigurationSection cs) { Value resultValues; resultValues = new Value(); resultValues.dbDirectory = cs.getString("db-engine.database-directory"); resultValues.maxDBsizeInDisk = cs.getInt("db-engine.max-disk-db-size-mb"); resultValues.maxEventsInRam = cs.getInt("db-engine.max-events-in-ram"); resultValues.ignoredMaterialsNames = cs.getStringList("log-policy.ignored-materials"); resultValues.ignoredPlayerNames = new TreeSet<>(); for (String playerName : cs.getStringList("log-policy.ignored-players")) { resultValues.ignoredPlayerNames.add(playerName); } resultValues.ignoredWorldsNames = cs.getStringList("log-policy.ignored-worlds"); resultValues.logOnlyModifiedBlocks = cs.getBoolean("log-policy.only-modified-blocks"); resultValues.wgRegionPolicy = cs.getString("log-policy.worldguard-regions-policy"); resultValues.signsLogToFile = cs.getBoolean("log-to-file.signs.active"); resultValues.signsIgnoreEmptyInLog = cs.getBoolean("log-to-file.signs.ignore-empty"); resultValues.chestLogToFile = cs.getBoolean("log-to-file.chest.active"); String auxString = cs.getString("log-to-file.signs.target-log"); switch (auxString.toUpperCase()) { case "EXTERNAL": resultValues.signTargetLog = logType.EXTERNAL; break; case "INTERNAL": resultValues.signTargetLog = logType.INTERNAL; break; default: resultValues.signTargetLog = logType.BOTH; break; } auxString = cs.getString("log-to-file.chest.target-log"); switch (auxString.toUpperCase()) { case "EXTERNAL": resultValues.chestTargetLog = logType.EXTERNAL; break; case "INTERNAL": resultValues.chestTargetLog = logType.INTERNAL; break; default: resultValues.chestTargetLog = logType.BOTH; break; } resultValues.signsExternalLogFileName = cs.getString("log-to-file.signs.external-log-filename"); resultValues.chestExternalLogFileName = cs.getString("log-to-file.chest.external-log-filename"); return resultValues; } public boolean logChestToFile() { return values.chestLogToFile; } public logType getLogTypeForChest() { return values.chestTargetLog; } public String getChestExternalLogFileName() { return values.chestExternalLogFileName; } public boolean logSignsToFile() { return values.signsLogToFile; } public boolean ignoreEmptySigns() { return values.signsIgnoreEmptyInLog; } public logType getLogTypeForSigns() { return values.signTargetLog; } public String getSignsExternalLogFileName() { return values.signsExternalLogFileName; } public static Material getValidMaterial(String stringMaterial) { Material m; if (stringMaterial.contains(":") && stringMaterial.split(":").length == 2) { try { int i = Integer.parseInt(stringMaterial.split(":")[1]); if (i > 255 || i < 0) { return null; } } catch (Exception ex) { return null; } stringMaterial = stringMaterial.split(":")[0]; } try { int id = Integer.parseInt(stringMaterial); m = Material.getMaterial(id); } catch (NumberFormatException e) { m = Material.matchMaterial(stringMaterial); } return m; } @Override public String toString() { return "database-directory: " + values.dbDirectory + "|" + "max-disk-db-size-mb: " + values.maxDBsizeInDisk + "|" + "max-events-in-ram: " + values.maxEventsInRam + "|" + "ignored-materials: " + values.ignoredMaterialsNames.toString() + "|" + "ignored-players: " + values.ignoredPlayerNames.toString() + "|" + "ignored-worlds: " + values.ignoredWorldsNames.toString() + "|" + "only-modified-blocks: " + (values.logOnlyModifiedBlocks ? "True" : "False") + "|" + "worldguard-regions-policy: " + values.wgRegionPolicy + "|" + "chest-log-to-file: " + (values.chestLogToFile ? "True" : "False") + "|" + "chest-target-log: " + values.chestTargetLog.toString() + "|" + "chest-external-log-filename: " + values.chestExternalLogFileName + "|" + "signs-log-to-file: " + (values.signsLogToFile ? "True" : "False") + "|" + "signs-ignore-empty-in-log: " + (values.signsIgnoreEmptyInLog ? "True" : "False") + "|" + "signs-target-log: " + values.signTargetLog.toString() + "|" + "signs-external-log-filename: " + values.signsExternalLogFileName; } public String getdbDirectory() { return values.dbDirectory; } public int getMaxDiskDBsizeMB() { return values.maxDBsizeInDisk; } public int getMaxEventsInRAM() { return values.maxEventsInRam; } public TreeSet<Material> getIgnoredMaterials() { return cache.ignoredMaterials; } public TreeSet<String> getIgnoredPlayerNames() { return values.ignoredPlayerNames; } public TreeSet<World> getIgnoredWorlds() { return cache.ignoredWorlds; } public boolean getFlagValue(String flagName) { boolean result; switch (flagName) { case "only-modified-blocks": result = values.logOnlyModifiedBlocks; break; default: result = false; } return result; } public boolean wgRegionPolicyIsDefault() { return cache.wgRegionPolicyIsDefault; } public String getWgRegionPolicy() { return values.wgRegionPolicy; } public void setValue(String key, String value, CommandSender sender) { Value newvalues = new Value(); ValidationMessage results = new ValidationMessage(); switch (key.toLowerCase()) { case "database-directory": newvalues.dbDirectory = value; if (validateDbDirectory(newvalues, results)) { values.dbDirectory = value; } break; case "max-disk-db-size-mb": try { newvalues.maxDBsizeInDisk = Integer.parseInt(value); if (validateMaxDiskDBsize(newvalues, results)) { values.maxDBsizeInDisk = newvalues.maxDBsizeInDisk; } } catch (Exception ex) { sender.sendMessage(ChatColor.RED + "This value must be a integer number."); } break; case "max-events-in-ram": try { newvalues.maxEventsInRam = Integer.parseInt(value); if (validateMaxEventsInRam(newvalues, results)) { values.maxEventsInRam = newvalues.maxEventsInRam; } } catch (Exception ex) { sender.sendMessage(ChatColor.RED + "This value must be a integer number."); } break; case "ignored-materials": newvalues.ignoredMaterialsNames = new ArrayList<>(); newvalues.ignoredMaterialsNames.addAll(Arrays.asList(value.split(","))); TreeSet<Material> ignoredMats = validateIgnoredMaterials(newvalues, results); values.ignoredMaterialsNames.clear(); for (Material mat : ignoredMats) { values.ignoredMaterialsNames.add(mat.name()); } break; case "ignored-players": newvalues.ignoredPlayerNames = new TreeSet<>(); newvalues.ignoredPlayerNames.addAll(Arrays.asList(value.split(","))); values.ignoredPlayerNames = validateIgnoredPlayers(newvalues, results); break; case "ignored-worlds": newvalues.ignoredWorldsNames = new ArrayList<>(); newvalues.ignoredWorldsNames.addAll(Arrays.asList(value.split(","))); TreeSet<World> worlds = validateIgnoredWorlds(newvalues, results); values.ignoredWorldsNames.clear(); for (Iterator<World> it = worlds.iterator(); it.hasNext();) { World w = it.next(); values.ignoredWorldsNames.add(w.getName()); } break; case "only-modified-blocks": switch (value.toLowerCase()) { case "true": values.logOnlyModifiedBlocks = true; break; case "false": values.logOnlyModifiedBlocks = false; break; default: sender.sendMessage(ChatColor.RED + "Only True or False are possible values for this key."); } break; case "worldguard-regions-policy": newvalues.wgRegionPolicy = value; if (validateWgRegionsPolicy(newvalues, results)) { values.wgRegionPolicy = value.toUpperCase(); } break; case "signs-log-to-file": if (validateBoolean(value)) { values.signsLogToFile = toBoolean(value); } else { sender.sendMessage(ChatColor.RED + "Only True or False are possible values for this key."); } break; case "chest-log-to-file": if (validateBoolean(value)) { values.signsLogToFile = toBoolean(value); } else { sender.sendMessage(ChatColor.RED + "Only True or False are possible values for this key."); } break; case "signs-ignore-empty-in-log": if (validateBoolean(value)) { values.signsIgnoreEmptyInLog = toBoolean(value); } else { sender.sendMessage(ChatColor.RED + "Only True or False are possible values for this key."); } break; case "chest-target-log": switch (value.toUpperCase()) { case "INTERNAL": values.chestTargetLog = logType.INTERNAL; break; case "EXTERNAL": values.chestTargetLog = logType.EXTERNAL; break; case "BOTH": values.chestTargetLog = logType.BOTH; break; default: sender.sendMessage(ChatColor.RED + "Only INTERNAL, EXTERNAL or BOTH are possible values for this key."); } break; case "signs-target-log": switch (value.toUpperCase()) { case "INTERNAL": values.signTargetLog = logType.INTERNAL; break; case "EXTERNAL": values.signTargetLog = logType.EXTERNAL; break; case "BOTH": values.signTargetLog = logType.BOTH; break; default: sender.sendMessage(ChatColor.RED + "Only INTERNAL, EXTERNAL or BOTH are possible values for this key."); } break; case "signs-external-log-filename": values.signsExternalLogFileName = value; break; case "chest-external-log-filename": values.signsExternalLogFileName = value; break; default: sender.sendMessage(ChatColor.RED + "The configuration key \"" + key + "\" does not exists."); } reportAndClear(results, sender); updateCache(); } private boolean toBoolean(String value) { if (value.equalsIgnoreCase("true")) { return true; } else { return false; } } private boolean validateBoolean(String value) { if (value.equalsIgnoreCase("true") || value.equalsIgnoreCase("false")) { return true; } else { return false; } } public void delValue(String key, CommandSender sender) { Value defaultValues = loadValues(plugin.getConfig().getDefaults()); switch (key.toLowerCase()) { case "database-directory": values.dbDirectory = defaultValues.dbDirectory; break; case "max-disk-db-size-mb": values.maxDBsizeInDisk = defaultValues.maxDBsizeInDisk; break; case "max-events-in-ram": values.maxEventsInRam = defaultValues.maxEventsInRam; break; case "ignored-materials": values.ignoredMaterialsNames = defaultValues.ignoredMaterialsNames; break; case "ignored-players": values.ignoredPlayerNames = defaultValues.ignoredPlayerNames; break; case "ignored-worlds": values.ignoredWorldsNames = defaultValues.ignoredWorldsNames; break; case "only-modified-blocks": values.logOnlyModifiedBlocks = defaultValues.logOnlyModifiedBlocks; break; case "worldguard-regions-policy": values.wgRegionPolicy = defaultValues.wgRegionPolicy; break; case "chest-log-to-file": values.chestLogToFile = defaultValues.chestLogToFile; break; case "chest-target-log": values.chestTargetLog = defaultValues.chestTargetLog; break; case "chest-external-log-filename": values.chestExternalLogFileName = defaultValues.chestExternalLogFileName; break; case "signs-log-to-file": values.signsLogToFile = defaultValues.signsLogToFile; break; case "signs-ignore-empty-in-log": values.signsIgnoreEmptyInLog = defaultValues.signsIgnoreEmptyInLog; break; case "signs-target-log": values.signTargetLog = defaultValues.signTargetLog; break; case "signs-external-log-filename": values.signsExternalLogFileName = defaultValues.signsExternalLogFileName; break; default: sender.sendMessage(ChatColor.RED + "The configuration key \"" + key + "\" does not exists."); return; } updateCache(); } public void persistConfiguration() { List<String> ignoredPlayer = new ArrayList<>(); ignoredPlayer.addAll(values.ignoredPlayerNames); plugin.getConfig().set("db-engine.database-directory", values.dbDirectory); plugin.getConfig().set("db-engine.max-disk-db-size-mb", values.maxDBsizeInDisk); plugin.getConfig().set("db-engine.max-events-in-ram", values.maxEventsInRam); plugin.getConfig().set("log-policy.ignored-materials", values.ignoredMaterialsNames); plugin.getConfig().set("log-policy.ignored-players", ignoredPlayer); plugin.getConfig().set("log-policy.ignored-worlds", values.ignoredWorldsNames); plugin.getConfig().set("log-policy.only-modified-blocks", values.logOnlyModifiedBlocks); plugin.getConfig().set("log-policy.worldguard-regions-policy", values.wgRegionPolicy); plugin.getConfig().set("log-to-file.chest.active", values.chestLogToFile); plugin.getConfig().set("log-to-file.chest.target-log", values.chestTargetLog.toString()); plugin.getConfig().set("log-to-file.chest.external-log-filename", values.chestExternalLogFileName); plugin.getConfig().set("log-to-file.signs.active", values.signsLogToFile); plugin.getConfig().set("log-to-file.signs.ignore-empty", values.signsIgnoreEmptyInLog); plugin.getConfig().set("log-to-file.signs.target-log", values.signTargetLog.toString()); plugin.getConfig().set("log-to-file.signs.external-log-filename", values.signsExternalLogFileName); plugin.saveConfig(); } }