com.kyne.webby.rtk.modules.WebbyRTKModule.java Source code

Java tutorial

Introduction

Here is the source code for com.kyne.webby.rtk.modules.WebbyRTKModule.java

Source

/*
 * Copyright KyneSilverhide 2010-2011
 * 
 * This program 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.
 * 
 * This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package com.kyne.webby.rtk.modules;

import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.logging.Handler;
import java.util.logging.Logger;

import org.apache.commons.io.IOUtils;

import com.drdanick.McRKit.ToolkitAction;
import com.drdanick.McRKit.ToolkitEvent;
import com.drdanick.McRKit.Wrapper;
import com.drdanick.McRKit.api.RTKInterface;
import com.drdanick.McRKit.api.RTKInterface.CommandType;
import com.drdanick.McRKit.api.RTKInterfaceException;
import com.drdanick.McRKit.api.RTKListener;
import com.drdanick.McRKit.module.Module;
import com.drdanick.McRKit.module.ModuleLoader;
import com.drdanick.McRKit.module.ModuleMetadata;
import com.esotericsoftware.yamlbeans.YamlReader;
import com.esotericsoftware.yamlbeans.YamlWriter;
import com.kyne.webby.commons.BackupUtils;
import com.kyne.webby.commons.LogHelper;
import com.kyne.webby.commons.WebbyYAMLParser;
import com.kyne.webby.commons.protocol.ServerInfos;
import com.kyne.webby.commons.protocol.ServerStatus;
import com.kyne.webby.rtk.dto.User;
import com.kyne.webby.rtk.helper.WebbyFormatter;
import com.kyne.webby.rtk.web.WebServer;

public class WebbyRTKModule extends Module implements RTKListener {

    // Interface to Bukkit Plugin and RemoteToolkit
    private BukkitInterface bukkitInterface;
    private RTKInterface rtkInterface;

    // WebServer that handles HTTP requests
    private WebServer webServer;

    // Configuration file from BukkitWebby module
    private Map<String, Object> pluginConf;

    // Defined users
    private final List<User> definedUsers = new ArrayList<User>();

    public WebbyRTKModule(final ModuleMetadata moduleMetadata, final ModuleLoader moduleLoader,
            final ClassLoader classLoader) {
        super(moduleMetadata, moduleLoader, classLoader, ToolkitEvent.ON_TOOLKIT_START, ToolkitEvent.NULL_EVENT);

        //Fix formatting on logger (Based on MilkAdmin)
        final Logger rootlog = Logger.getLogger("");
        for (final Handler h : rootlog.getHandlers()) { //remove all handlers
            h.setFormatter(new WebbyFormatter());
        }
        LogHelper.initLogger("WebbyRTKModule", "Minecraft");
    }

    @Override
    protected void onEnable() {
        try {
            // Init configurations
            this.initRTKModule();

            // Init and start webserver
            final int webPort = WebbyYAMLParser.getInt("webby.port", this.pluginConf, 25567);
            this.webServer = new WebServer(this, webPort);
            this.webServer.start();
        } catch (final Exception e) {
            LogHelper.error("The WebServer couldn't be initialized due to the following error :", e);
            e.printStackTrace();
        }
    }

    @SuppressWarnings("unchecked")
    private void initRTKModule() {
        final File configFile = new File("plugins/BukkitWebby/config.yml");

        // Check if file exists
        if (!configFile.exists()) {
            LogHelper.warn(
                    "Config.yml can't be found. If this is the first start, it's normal, and we will create it for you.");
            // Create the file
            FileWriter writer = null;
            try {
                writer = new FileWriter(configFile);
                writer.write(this.getConfigFileContent());
            } catch (final IOException ex) {
                LogHelper.error("Can't write the new new configuration file", ex);
            } finally {
                IOUtils.closeQuietly(writer);
            }
        }

        FileReader fileReader = null;
        try {
            fileReader = new FileReader(configFile);
            final YamlReader reader = new YamlReader(fileReader);
            this.pluginConf = (Map<String, Object>) reader.read();
        } catch (final Exception e) {
            LogHelper.error("Unable to read configuration file", e);
        } finally {
            IOUtils.closeQuietly(fileReader);
        }

        //Check for new values
        final Object avatars = WebbyYAMLParser.getObject("webby.show_avatars", this.pluginConf, null);
        if (avatars == null) {
            LogHelper.info("Updating configuration file with value 'show_avatars'");
            final Map<String, Object> webbyConf = (Map<String, Object>) this.pluginConf.get("webby");
            webbyConf.put("show_avatars", "true");
            FileWriter fileWriter = null;
            try {
                fileWriter = new FileWriter(configFile);
                YamlWriter writer = new YamlWriter(fileWriter);
                writer.write(pluginConf);
                writer.close();
            } catch (Exception e) {
                LogHelper.error("Unable to update the configuration file", e);
            } finally {
                IOUtils.closeQuietly(fileWriter);
            }
        }

        // Extract users
        final Map<String, Object> users = (Map<String, Object>) WebbyYAMLParser.getObject("webby.users",
                this.pluginConf, null);
        for (final Object userObjects : users.values()) {
            final Map<String, Object> userValues = (Map<String, Object>) userObjects;
            final User user = new User((String) userValues.get("login"), (String) userValues.get("password"));
            this.definedUsers.add(user);
        }
        if (this.definedUsers.size() == 0) {
            LogHelper.error(
                    "No user found in the configuration file. Nobody will be able to authenticate to BukkitWebby !!");
        }
    }

    /**
     * Check if the given login and password match one of the defined users.
     */
    public boolean checkAuthentification(final String inputLogin, final String inputPassword) {
        final String userLogin = inputLogin == null ? "" : inputLogin;
        final String userPassword = inputPassword == null ? "" : inputPassword;
        for (final User user : this.definedUsers) {
            if (userLogin.equals(user.getLogin()) && userPassword.equals(user.getPassword())) {
                return true;
            }
        }
        return false;
    }

    /**
     * Get the content of a new, default configuration file
     */
    private String getConfigFileContent() {
        final String YAMLINDENT = "    "; //Can't use \t (tabulations) in YAML files
        final StringBuilder sBuilder = new StringBuilder();
        sBuilder.append("#BukkitWebby configuration file (DON'T USE TABULATIONS OR THIS FILE WON'T BE READABLE)\n")
                .append("#-------------------------------------------------------------------------------------\n")
                .append("\n").append("#Webby options\n").append("webby:\n")
                .append(YAMLINDENT
                        + "#Webserver port used to listen to web connections (Ex: http://localhost:25567/login) (default = 25567) MUST BE DIFFERENT THAN ALL OTHER PORTS \n")
                .append(YAMLINDENT + "port: 25567\n")
                .append(YAMLINDENT
                        + "#Local port used by the Webby RTKModule to communicate with the Webby Bukkit Plugin (default = 25564). MUST BE DIFFERENT THAN ALL OTHER PORTS \n")
                .append(YAMLINDENT + "localPort : 25564\n").append(YAMLINDENT + "#Administration login\n")
                .append(YAMLINDENT + "users: \n")
                .append(YAMLINDENT + YAMLINDENT + "admin: #Simple name, only used to split each user \n")
                .append(YAMLINDENT + YAMLINDENT + YAMLINDENT + "login: admin\n")
                .append(YAMLINDENT + YAMLINDENT + YAMLINDENT + "password: admin\n")
                .append(YAMLINDENT
                        + "#Timeout in minutes before a user has to relog after inactivity (default = 10 minutes)\n")
                .append(YAMLINDENT + "sessionTimeout: 10\n")
                .append(YAMLINDENT
                        + "#Show user avatars next to their name, using Minotar.net service (requires Internet access) \n")
                .append(YAMLINDENT + "show_avatars: true\n")
                .append(YAMLINDENT
                        + "#Switch the backup. If set to BUKKIT, each world is expected to have its own directory. if set to SMP, a unique directory called 'your_world_name' should exists.\n")
                .append(YAMLINDENT + "backupMode: BUKKIT\n")
                .append(YAMLINDENT
                        + "#Switch the log. If set to OLD, Bukkit Webby will use the server.log file. If set to NEW (default), it will look into logs/latest.log\n")
                .append(YAMLINDENT + "logMode: NEW\n").append("\n").append("rtk:\n")
                .append(YAMLINDENT + "#RemoteToolkit port (Same as in remote.properties, default = 25561)\n")
                .append(YAMLINDENT + "port: 25561\n")
                .append(YAMLINDENT + "#Host. You shouldn't have to change this. (Default = localhost) \n")
                .append(YAMLINDENT + "host: localhost\n")
                .append(YAMLINDENT + "#RemoteToolkit login (See the rtoolkit.sh or .bat files, default = user)\n")
                .append(YAMLINDENT + "login: user\n")
                .append(YAMLINDENT
                        + "#RemoteToolkit password  (See the rtoolkit.sh or .bat files, default = pass)\n")
                .append(YAMLINDENT + "password: pass\n");
        return sBuilder.toString();
    }

    @Override
    protected void onDisable() {
        this.webServer.stopServer();
        LogHelper.info("Webserver has been successfully disabled");
    }

    @Override
    public void onRTKStringReceived(final String message) {
        if (message.equals("RTK_TIMEOUT")) {
            LogHelper.warn(
                    "Remote Toolkit is not responding. If this happen more than once, there probably is something wrong with your config.yml");
        } else {
            LogHelper.info("RemoteToolkit : " + message);
        }
    }

    /**
     * Handle the given command name (it can be send to the RTK wrapper or to the bukkit server itself)
     * @param command the command name (see RTK CommandType)
     * @throws IOException
     */
    public void handleCommand(final String command) throws IOException {
        LogHelper.debug("Sendng command " + command);
        try {
            // Bukkit commands
            if ("RELOAD".equalsIgnoreCase(command)) {
                this.getBukkitInterface().reloadServer();
            } else if ("STOP".equalsIgnoreCase(command)) {
                this.getRtkInterface().executeCommand(CommandType.DISABLE_RESTARTS, null);
                this.getBukkitInterface().stopServer();
            } else if ("SAVE".equalsIgnoreCase(command)) {
                this.getBukkitInterface().saveAllWorlds();
                LogHelper.info("All worlds have been saved");
            }
            // RTK commands
            else {
                //Call to RTK Interface (no login/password required)
                if ("RESTART".equalsIgnoreCase(command)) {
                    this.bukkitInterface.setLastKnownStatus(ServerStatus.RESTARTING);
                }
                Wrapper.getInterface().performAction(ToolkitAction.valueOf(command), null);
            }
        } catch (final IllegalArgumentException e) {
            LogHelper.error("Unsupported command " + command + " will be ignored", e);
        } catch (final RTKInterfaceException e) {
            LogHelper.error("RTK error : unable to disable the wrapper 'restarts on stop'", e);
        }
    }

    public void backupServer(final boolean notifyForRestore, final String defaultWorldName) throws IOException {
        this.getBukkitInterface().backupServer(notifyForRestore, defaultWorldName);
    }

    public boolean restoreBackup(final String fileName) throws IOException {
        if (fileName == null) {
            LogHelper.error("Unable to restore backup : fileName is null !");
            return false;
        } else {
            // Checks
            final File backupRoot = new File("Backups/");
            final File backupFile = new File(backupRoot, fileName);
            if (!backupFile.exists()) {
                LogHelper.error(
                        "Backup file " + backupFile + " can't be found. Unable to restore the requested backup");
                return false;
            }

            // Restore backup
            boolean success = true;
            try {
                final File serverRoot = new File(".");
                BackupUtils.extractZip(backupFile, serverRoot);
            } catch (final Exception e) {
                success = false;
                LogHelper.error("An error occured while restoring the backup. Don't panic, you can still "
                        + "try manually by unzipping the file " + fileName + " to yout server root", e);
                return false;
            }
            if (success) {
                LogHelper.info("Backup has been successfully restored. Restarting server");
            }
            return success;
        }
    }

    public void handleBukkitCommand(final String command) throws IOException {
        this.getBukkitInterface().dispatchCommand(command);
    }

    public boolean pingServer() throws IOException {
        return this.getBukkitInterface().pingServer();
    }

    public ServerStatus askForServerStatus() {
        try {
            return this.getBukkitInterface().getServerStatus();
        } catch (final IOException e) {
            LogHelper.error("Unable to interact with Bukkit", e);
            return ServerStatus.OFF;
        }
    }

    public ServerInfos askForServerInfos() {
        try {
            return this.bukkitInterface.getServerInfos();
        } catch (final IOException e) {
            LogHelper.error("Unable to interact with Bukkit", e);
            return null;
        }
    }

    public List<String> readConsoleLog(final LogMode logMode) {
        final List<String> logLines = new ArrayList<String>();
        String line = "";

        File logFile = null;
        if (logMode == LogMode.NEW) {
            logFile = new File("logs/latest.log");
        } else if (logMode == LogMode.OLD) {
            logFile = new File("server.log");
        } else {
            throw new UnsupportedOperationException("Unsupported log mode " + logMode);
        }

        if (!logFile.exists()) {
            LogHelper.error("Unable to find the log file at " + logFile.getAbsolutePath());
            return Arrays.asList("Unable to find the log file");
        }

        RandomAccessFile randomFile = null;
        try {
            randomFile = new RandomAccessFile(logFile, "r");
            final long linesToRead = 100;
            final long fileLength = randomFile.length();
            long startPosition = fileLength - (linesToRead * 100);
            if (startPosition < 0) {
                startPosition = 0;
            }
            randomFile.seek(startPosition);
            while ((line = randomFile.readLine()) != null) {
                logLines.add(line.replace("[0;30;22m", "").replace("[0;34;22m", "").replace("[0;32;22m", "")
                        .replace("[0;36;22m", "").replace("[0;31;22m", "").replace("[0;35;22m", "")
                        .replace("[0;33;22m", "").replace("[0;37;22m", "").replace("[0;30;1m", "")
                        .replace("[0;34;1m", "").replace("[0;32;1m", "").replace("[0;36;1m", "")
                        .replace("[0;31;1m", "").replace("[0;35;1m", "").replace("[0;33;1m", "")
                        .replace("[0;37;1m", "").replace("[m", "").replace("[5m", "").replace("[21m", "")
                        .replace("[9m", "").replace("[4m", "").replace("[3m", "").replace("[0;39m", "")
                        .replace("[0m", ""));

            }
        } catch (final IOException e) {
            LogHelper.error("Unable to read server.log", e);
        } finally {
            IOUtils.closeQuietly(randomFile);
        }
        return logLines;
    }

    public Map<String, Object> getPluginConf() {
        return this.pluginConf;
    }

    public void setPluginConf(final Map<String, Object> pluginConf) {
        this.pluginConf = pluginConf;
    }

    private BukkitInterface getBukkitInterface() throws IOException {
        if (this.bukkitInterface == null) {
            this.bukkitInterface = new BukkitInterface(
                    WebbyYAMLParser.getInt("webby.localPort", this.pluginConf, 25564));
        }
        return this.bukkitInterface;
    }

    private RTKInterface getRtkInterface() throws RTKInterfaceException {
        if (this.rtkInterface == null) {
            //Init RTKInterface
            final int rtkPort = WebbyYAMLParser.getInt("rtk.port", this.pluginConf, 25561);
            final String host = WebbyYAMLParser.getString("rtk.host", this.pluginConf, "localhost");
            final String user = WebbyYAMLParser.getString("rtk.login", this.pluginConf, "user");
            final String password = WebbyYAMLParser.getString("rtk.password", this.pluginConf, "pass");
            this.rtkInterface = RTKInterface.createRTKInterface(rtkPort, host, user, password);
        }
        return this.rtkInterface;
    }

    public enum LogMode {
        OLD, NEW;
    }
}