BungeeMain / src / main / java / com / lifeknight / relaymcbungeemain / network / MessageServerHandler.java
MessageServerHandler.java
Raw
package com.lifeknight.relaymcbungeemain.network;

import com.google.common.io.Files;
import com.lifeknight.relaymcbungeemain.Main;
import com.lifeknight.relaymcbungeemain.utilities.Server;
import com.lifeknight.relaymcbungeemain.utilities.Utilities;
import com.lifeknight.relayutils.RelayUtils;
import com.lifeknight.relayutils.basic.Miscellaneous;
import com.lifeknight.relayutils.basic.Text;
import com.lifeknight.relayutils.game.MainGame;
import com.lifeknight.relayutils.network.Command;
import com.lifeknight.relayutils.network.Message;
import com.lifeknight.relayutils.network.Recipient;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;

import java.io.File;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.TimeoutException;

public class MessageServerHandler extends ChannelInboundHandlerAdapter {
    private static final UUID CLIENT_KEY = UUID.fromString("ec573093-e335-4af5-8a9b-bd97baf11b10");
    public static final Map<String, MessageServerHandler> CHANNELS = new HashMap<>();

    private long lastPing = System.currentTimeMillis();
    private boolean hasAccess = false;
    private String serverName = null;
    private boolean updatedPlugin = false;
    private ChannelHandlerContext channelHandlerContext;

    public void sendMessage(Message message) {
        if (RelayUtils.messageLog) {
            Main.info("Sending message: %s | %s", message.getRecipient(), message.getCommand());
        }
        this.channelHandlerContext.writeAndFlush(message);
    }

    public void sendMessage(Command command, Object... arguments) {
        this.sendMessage(new Message(command, arguments));
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        this.lastPing = System.currentTimeMillis();
        Message message = (Message) msg;

        String[] arguments = message.getArgumentsStringArray();
        if (message.getCommandCommand() == Command.TAKE_KEY) {
            UUID uuid = UUID.fromString(arguments[0]);
            if (!uuid.equals(CLIENT_KEY)) {
                ctx.close();
                Main.warn("Closed connection; incorrect key was provided.");
            } else {
                this.serverName = message.getSender();
                this.hasAccess = true;

                this.sendMessage(Command.ACCESS_GRANTED);

                if (arguments.length > 1) {
                    String ip = arguments[1];
                    Server server = Server.getServerFromIP(ip);

                    if (server != null) {
                        this.serverName = server.getName();
                        if (!this.serverName.isEmpty()) {
                            this.sendMessage(Command.NAME, server.getName());
                        }
                    } else {
                        server = Server.getServer(this.serverName);
                        if (!this.serverName.equalsIgnoreCase("paper")) {
                            Utilities.createNewServerInfo(this.serverName, ip);
                        }
                    }

                    if (arguments.length > 2) {
                        long bytes = Long.parseLong(arguments[2]);
                        int gigaBytes = (int) (bytes / 1000000000L);
                        if (server != null) {
                            server.setMemoryGB(gigaBytes);
                        }
                    }
                }

                Main.info("Authorized connection: %s", this.serverName);

                CHANNELS.put(this.serverName, this);
                if (this.hasServer()) {
                    this.getServer().setAvailableForPlayers(true);
                }
                this.getPluginVersion();
            }
        } else if (message.getCommandCommand() == Command.PLUGIN) {
            if (message.hasArguments()) {
                if (arguments[0].equals("version")) {
                    this.checkVersion(arguments[1]);
                }
            }
        }
        if (this.hasServer()) {
            this.getServer().setMessageClientActive(true);
        }
        Main.onMessageReceived(message);
    }

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        this.channelHandlerContext = ctx;
        if (RelayUtils.log) {
            Main.info("Channel active: %s", ctx.channel().remoteAddress());
        }
        Main.schedule(() -> {
            if (!this.hasAccess) {
                if (ctx.channel().remoteAddress() != null) {
                    Main.info("Closed connection with %s; no key was sent.", ctx.channel().remoteAddress());
                }
                ctx.close();
            }
        }, 5);
        if (this.hasServer()) {
            this.getServer().setMessageClientActive(true);
        }
    }

    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        if (this.hasServer()) {
            this.getServer().setMessageClientActive(false);
        }
        if (this.serverName != null) {
            Main.info("Connection lost: %s", this.serverName);
        }
        CHANNELS.remove(this.serverName);
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        if (cause instanceof TimeoutException) {
            if (this.serverName != null) {
                Main.info("Timed out: %s", this.serverName);
            }
        } else {
            Main.error("Exception caught (%s): %s\n %s\n[%s]", this.serverName, cause.getMessage(), ctx.channel().remoteAddress(), Text.separateBy(Miscellaneous.fromAndTo(cause.getStackTrace(), 0, 5), "\n"));
        }
    }

    public static void sendMessageToServer(Message message) throws NullPointerException {
        MessageServerHandler messageServerHandler = CHANNELS.get(message.getRecipient());
        if (messageServerHandler == null) {
            Main.error("Tried to send message, could not find channel: %s", message.getRecipient());
        } else {
            messageServerHandler.sendMessage(message);
        }
    }

    public static void broadcastMessage(Message message) {
        for (MessageServerHandler messageServerHandler : CHANNELS.values()) {
            if (!messageServerHandler.serverName.equalsIgnoreCase(message.getSender())) {
                messageServerHandler.sendMessage(message);
            }
        }
    }

    public static void broadcastMessage(Command command, Object... arguments) {
        broadcastMessage(new Message(Recipient.NONE, command, arguments));
    }

    public void getPluginVersion() {
        this.sendMessage(Command.PLUGIN, "version");
    }

    public void updatePlugin(File plugin) {
        try {
            byte[] data = Files.toByteArray(plugin);
            Byte[] result = new Byte[data.length];

            for (int i = 0; i < data.length; ++i) {
                result[i] = data[i];
            }

            this.sendMessage(Command.PLUGIN, "set", plugin.getName(), Text.toCSV(result));
            this.updatedPlugin = true;
        } catch (Exception exception) {
            Main.info("An error occurred while trying to update the plugin of %s: %s", this.serverName, exception.getMessage());
        }
    }

    public void checkVersion(String pluginFileName) {
        if (this.hasServer()) {
            File pluginFile = Main.getPlugin(this.getServer());
            if (!pluginFileName.isEmpty()) {
                for (MainGame value : MainGame.values()) {
                    if (Text.comparablyContains(pluginFileName, value)) {
                        if (this.getServer().getMainGame() != value) {
                            this.updatePlugin(pluginFile);
                            return;
                        }
                    }
                }
            }

            if (pluginFile != null && !pluginFile.getName().equals(pluginFileName)) {
                if (Utilities.parseVersion(pluginFile.getName()).isGreater(Utilities.parseVersion(pluginFileName)) && !this.updatedPlugin) {
                    this.updatePlugin(pluginFile);
                    Main.info("Updating plugin for %s. (%s vs %s)", this.serverName, pluginFile.getName(), pluginFileName);
                } else if (!this.updatedPlugin) {
                    Main.info("%s is outdated! (%s)", pluginFile.getName(), pluginFileName);
                }
            }
        }
    }

    public Server getServer() {
        return Server.getServer(this.serverName);
    }

    public boolean hasServer() {
        return this.getServer() != null;
    }

    public long getLastPing() {
        return this.lastPing;
    }

    public void closeConnection() {
        if (this.hasServer()) {
            this.getServer().setMessageClientActive(false);
        }
        if (this.channelHandlerContext != null) {
            this.channelHandlerContext.close();
            try {
                this.channelInactive(this.channelHandlerContext);
            } catch (Exception e) {
                Main.error("An error occurred while deliberately closing connection: %s", e.getMessage());
            }
        }
    }
}