package net.glowstone.util;
import net.glowstone.GlowServer;
import org.apache.commons.lang.Validate;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.InvalidConfigurationException;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.util.FileUtil;
import org.yaml.snakeyaml.error.YAMLException;
import java.io.*;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import java.util.logging.Level;
/**
* Utilities for handling the server configuration files.
*/
public final class ServerConfig {
/**
* The directory configurations are stored in.
*/
private final File configDir;
/**
* The main configuration file.
*/
private final File configFile;
/**
* The actual configuration data.
*/
private final YamlConfiguration config = new YamlConfiguration();
/**
* Extra configuration files (help, permissions, commands).
*/
private final Map<String, YamlConfiguration> extraConfig = new HashMap<>();
/**
* Parameters with which the server is ran.
*/
private final Map<Key, Object> parameters;
/**
* Initialize a new ServerConfig and associated settings.
* @param configDir The config directory, or null for default.
* @param configFile The config file, or null for default.
* @param parameters The command-line parameters used as overrides.
*/
public ServerConfig(File configDir, File configFile, Map<Key, Object> parameters) {
Validate.notNull(configDir);
Validate.notNull(configFile);
Validate.notNull(parameters);
this.configDir = configDir;
this.configFile = configFile;
this.parameters = parameters;
config.options().indent(4);
}
////////////////////////////////////////////////////////////////////////////
// Modification
/**
* Save the configuration back to file.
*/
public void save() {
try {
config.save(configFile);
} catch (IOException e) {
GlowServer.logger.log(Level.SEVERE, "Failed to write config: " + configFile, e);
}
}
/**
* Change a configuration value at runtime.
* @see ServerConfig#save()
* @param key the config key to write the value to
* @param value value to write to config key
*/
public void set(Key key, Object value) {
config.set(key.path, value);
}
////////////////////////////////////////////////////////////////////////////
// Value getters
public String getString(Key key) {
if (parameters.containsKey(key)) {
return parameters.get(key).toString();
}
return config.getString(key.path, key.def.toString());
}
public int getInt(Key key) {
if (parameters.containsKey(key)) {
return (Integer) parameters.get(key);
}
return config.getInt(key.path, (Integer) key.def);
}
public boolean getBoolean(Key key) {
if (parameters.containsKey(key)) {
return (Boolean) parameters.get(key);
}
return config.getBoolean(key.path, (Boolean) key.def);
}
////////////////////////////////////////////////////////////////////////////
// Fancy stuff
public ConfigurationSection getConfigFile(Key key) {
String filename = getString(key);
if (extraConfig.containsKey(filename)) {
return extraConfig.get(filename);
}
final YamlConfiguration conf = new YamlConfiguration();
final File file = getFile(filename), migrateFrom = new File(key.def.toString());
// create file if it doesn't exist
if (!file.exists()) {
if (migrateFrom.exists()) {
FileUtil.copy(migrateFrom, file);
} else {
copyDefaults(key.def.toString(), file);
}
}
// read in config
try {
conf.load(file);
} catch (IOException e) {
GlowServer.logger.log(Level.SEVERE, "Failed to read config: " + file, e);
} catch (InvalidConfigurationException e) {
report(file, e);
}
extraConfig.put(filename, conf);
return conf;
}
public ConfigurationSection getWorlds() {
return config.getConfigurationSection("worlds");
}
public File getDirectory() {
return configDir;
}
public File getFile(String filename) {
return new File(configDir, filename);
}
////////////////////////////////////////////////////////////////////////////
// Load and internals
public void load() {
// load extra config files again next time they're needed
extraConfig.clear();
// create default file if needed
final boolean exists = configFile.exists();
if (!exists) {
// create config directory
if (!configDir.isDirectory() && !configDir.mkdirs()) {
GlowServer.logger.severe("Cannot create directory: " + configDir);
return;
}
copyDefaults("glowstone.yml", configFile);
}
// load config
try {
config.load(configFile);
} catch (IOException e) {
GlowServer.logger.log(Level.SEVERE, "Failed to read config: " + configFile, e);
} catch (InvalidConfigurationException e) {
report(configFile, e);
}
// if we just created defaults, attempt to migrate
if (!exists && migrate()) {
// save config, including any new defaults
try {
config.save(configFile);
} catch (IOException e) {
GlowServer.logger.log(Level.SEVERE, "Failed to write config: " + configFile, e);
return;
}
GlowServer.logger.info("Migrated configuration from CraftBukkit");
}
}
private void copyDefaults(String source, File dest) {
final URL resource = getClass().getClassLoader().getResource("defaults/" + source);
if (resource == null) {
GlowServer.logger.warning("Could not find default " + source + " on classpath");
return;
}
try (final InputStream in = resource.openStream();
final OutputStream out = new FileOutputStream(dest)) {
final byte[] buf = new byte[2048];
int len;
while ((len = in.read(buf)) > 0) {
out.write(buf, 0, len);
}
} catch (IOException e) {
GlowServer.logger.log(Level.WARNING, "Could not save default config: " + dest, e);
return;
}
GlowServer.logger.info("Created default config: " + dest);
}
private void report(File file, InvalidConfigurationException e) {
if (e.getCause() instanceof YAMLException) {
GlowServer.logger.severe("Config file " + file + " isn't valid! " + e.getCause());
} else if ((e.getCause() == null) || (e.getCause() instanceof ClassCastException)) {
GlowServer.logger.severe("Config file " + file + " isn't valid!");
} else {
GlowServer.logger.log(Level.SEVERE, "Cannot load " + file + ": " + e.getCause().getClass(), e);
}
}
private boolean migrate() {
boolean migrateStatus = false;
final File bukkitYml = new File("bukkit.yml");
if (bukkitYml.exists()) {
YamlConfiguration bukkit = new YamlConfiguration();
try {
bukkit.load(bukkitYml);
} catch (InvalidConfigurationException e) {
report(bukkitYml, e);
} catch (IOException e) {
GlowServer.logger.log(Level.WARNING, "Could not migrate from " + bukkitYml, e);
}
for (Key key : Key.values()) {
if (key.migrate == Migrate.BUKKIT && bukkit.contains(key.migratePath)) {
config.set(key.path, bukkit.get(key.migratePath));
migrateStatus = true;
}
}
config.set("aliases", bukkit.get("aliases"));
config.set("worlds", bukkit.get("worlds"));
}
final File serverProps = new File("server.properties");
if (serverProps.exists()) {
Properties props = new Properties();
try {
props.load(new FileInputStream(serverProps));
} catch (IOException e) {
GlowServer.logger.log(Level.WARNING, "Could not migrate from " + serverProps, e);
}
for (Key key : Key.values()) {
if (key.migrate == Migrate.PROPS && props.containsKey(key.migratePath)) {
String value = props.getProperty(key.migratePath);
if (key.def instanceof Integer) {
try {
config.set(key.path, Integer.parseInt(value));
} catch (NumberFormatException e) {
GlowServer.logger.log(Level.WARNING, "Could not migrate " + key.migratePath + " from " + serverProps, e);
continue;
}
} else if (key.def instanceof Boolean) {
config.set(key.path, Boolean.parseBoolean(value));
} else {
config.set(key.path, value);
}
migrateStatus = true;
}
}
}
return migrateStatus;
}
/**
* An enum containing configuration keys used by the server.
*/
public static enum Key {
// server
SERVER_IP("server.ip", "", Migrate.PROPS, "server-ip"),
SERVER_PORT("server.port", 25565, Migrate.PROPS, "server-port"),
SERVER_NAME("server.name", "Glowstone Server", Migrate.PROPS, "server-name"),
LOG_FILE("server.log-file", "logs/log-%D.txt"),
ONLINE_MODE("server.online-mode", true, Migrate.PROPS, "online-mode"),
MAX_PLAYERS("server.max-players", 20, Migrate.PROPS, "max-players"),
WHITELIST("server.whitelisted", false, Migrate.PROPS, "white-list"),
MOTD("server.motd", "Glowstone Server", Migrate.PROPS, "motd"),
SHUTDOWN_MESSAGE("server.shutdown-message", "Server shutting down.", Migrate.BUKKIT, "settings.shutdown-message"),
USE_JLINE("server.use-jline", true),
// folders
PLUGIN_FOLDER("folders.plugins", "plugins"),
UPDATE_FOLDER("folders.update", "update", Migrate.BUKKIT, "settings.update-folder"),
WORLD_FOLDER("folders.worlds", "worlds", Migrate.BUKKIT, "settings.world-container"),
// files
PERMISSIONS_FILE("files.permissions", "permissions.yml", Migrate.BUKKIT, "settings.permissions-file"),
COMMANDS_FILE("files.commands", "commands.yml"),
HELP_FILE("files.help", "help.yml"),
// advanced
CONNECTION_THROTTLE("advanced.connection-throttle", 4000, Migrate.BUKKIT, "settings.connection-throttle"),
//PING_PACKET_LIMIT("advanced.ping-packet-limit", 100, Migrate.BUKKIT, "settings.ping-packet-limit"),
PLAYER_IDLE_TIMEOUT("advanced.idle-timeout", 0, Migrate.PROPS, "player-idle-timeout"),
WARN_ON_OVERLOAD("advanced.warn-on-overload", true, Migrate.BUKKIT, "settings.warn-on-overload"),
EXACT_LOGIN_LOCATION("advanced.exact-login-location", false, Migrate.BUKKIT, "settings.use-exact-login-location"),
PLUGIN_PROFILING("advanced.plugin-profiling", false, Migrate.BUKKIT, "settings.plugin-profiling"),
WARNING_STATE("advanced.deprecated-verbose", "false", Migrate.BUKKIT, "settings.deprecated-verbose"),
COMPRESSION_THRESHOLD("advanced.compression-threshold", 256, Migrate.PROPS, "network-compression-threshold"),
PROXY_SUPPORT("advanced.proxy-support", false),
// query rcon etc
QUERY_ENABLED("extras.query-enabled", false, Migrate.PROPS, "enable-query"),
QUERY_PORT("extras.query-port", 25614, Migrate.PROPS, "query.port"),
QUERY_PLUGINS("extras.query-plugins", true, Migrate.BUKKIT, "settings.query-plugins"),
RCON_ENABLED("extras.rcon-enabled", false, Migrate.PROPS, "enable-rcon"),
RCON_PASSWORD("extras.rcon-password", "glowstone", Migrate.PROPS, "rcon.password"),
RCON_PORT("extras.rcon-port", 25575, Migrate.PROPS, "rcon.port"),
RCON_COLORS("extras.rcon-colors", true),
// level props
LEVEL_NAME("world.name", "world", Migrate.PROPS, "level-name"),
LEVEL_SEED("world.seed", "", Migrate.PROPS, "level-seed"),
LEVEL_TYPE("world.level-type", "DEFAULT", Migrate.PROPS, "level-type"),
SPAWN_RADIUS("world.spawn-radius", 16, Migrate.PROPS, "spawn-protection"),
VIEW_DISTANCE("world.view-distance", 8, Migrate.PROPS, "view-distance"),
GENERATE_STRUCTURES("world.gen-structures", true, Migrate.PROPS, "generate-structures"),
GENERATOR_SETTINGS("world.gen-settings", "", Migrate.PROPS, "generator-settings"),
ALLOW_NETHER("world.allow-nether", true, Migrate.PROPS, "allow-nether"),
ALLOW_END("world.allow-end", true, Migrate.BUKKIT, "settings.allow-end"),
PERSIST_SPAWN("world.keep-spawn-loaded", true),
// game props
GAMEMODE("game.gamemode", "SURVIVAL", Migrate.PROPS, "gamemode"),
FORCE_GAMEMODE("game.gamemode-force", "false", Migrate.PROPS, "force-gamemode"),
DIFFICULTY("game.difficulty", "NORMAL", Migrate.PROPS, "difficulty"),
HARDCORE("game.hardcore", false, Migrate.PROPS, "hardcore"),
PVP_ENABLED("game.pvp", true, Migrate.PROPS, "pvp"),
MAX_BUILD_HEIGHT("game.max-build-height", 256, Migrate.PROPS, "max-build-height"),
ANNOUNCE_ACHIEVEMENTS("game.announce-achievements", true, Migrate.PROPS, "announce-player-achievements"),
// server.properties keys
ALLOW_FLIGHT("game.allow-flight", false, Migrate.PROPS, "allow-flight"),
ENABLE_COMMAND_BLOCK("game.command-blocks", false, Migrate.PROPS, "enable-command-block"),
//OP_PERMISSION_LEVEL(null, Migrate.PROPS, "op-permission-level"),
RESOURCE_PACK("game.resource-pack", "", Migrate.PROPS, "resource-pack"),
RESOURCE_PACK_HASH("game.resource-pack-hash", "", Migrate.PROPS, "resource-pack-hash"),
SNOOPER_ENABLED("server.snooper-enabled", false, Migrate.PROPS, "snooper-enabled"),
// critters
SPAWN_MONSTERS("creatures.enable.monsters", true, Migrate.PROPS, "spawn-monsters"),
SPAWN_ANIMALS("creatures.enable.animals", true, Migrate.PROPS, "spawn-animals"),
SPAWN_NPCS("creatures.enable.npcs", true, Migrate.PROPS, "spawn-npcs"),
MONSTER_LIMIT("creatures.limit.monsters", 70, Migrate.BUKKIT, "spawn-limits.monsters"),
ANIMAL_LIMIT("creatures.limit.animals", 15, Migrate.BUKKIT, "spawn-limits.animals"),
WATER_ANIMAL_LIMIT("creatures.limit.water", 5, Migrate.BUKKIT, "spawn-limits.water-animals"),
AMBIENT_LIMIT("creatures.limit.ambient", 15, Migrate.BUKKIT, "spawn-limits.ambient"),
MONSTER_TICKS("creatures.ticks.monsters", 1, Migrate.BUKKIT, "ticks-per.monster-spawns"),
ANIMAL_TICKS("creatures.ticks.animal", 400, Migrate.BUKKIT, "ticks-per.animal-spawns"),
// database
DB_DRIVER("database.driver", "org.sqlite.JDBC", Migrate.BUKKIT, "database.driver"),
DB_URL("database.url", "jdbc:sqlite:config/database.db", Migrate.BUKKIT, "database.url"),
DB_USERNAME("database.username", "glowstone", Migrate.BUKKIT, "database.username"),
DB_PASSWORD("database.password", "nether", Migrate.BUKKIT, "database.password"),
DB_ISOLATION("database.isolation", "SERIALIZABLE", Migrate.BUKKIT, "database.isolation");
private final String path;
private final Object def;
private final Migrate migrate;
private final String migratePath;
private Key(String path, Object def) {
this(path, def, null, null);
}
private Key(String path, Object def, Migrate migrate, String migratePath) {
this.path = path;
this.def = def;
this.migrate = migrate;
this.migratePath = migratePath;
}
@Override
public String toString() {
return name() + "(" + path + ", " + def + ")";
}
}
private static enum Migrate {
BUKKIT, PROPS
}
}