diff --git a/bridge-protocol/build.gradle b/bridge-protocol/build.gradle index 44006f7..1e11dd2 100644 --- a/bridge-protocol/build.gradle +++ b/bridge-protocol/build.gradle @@ -1,5 +1,5 @@ group = 'asys' -version = '0.4-SNAPSHOT' +version = '0.5-SNAPSHOT' task jar(type: Jar, overwrite: true) { // не собирать jar diff --git a/bridge-protocol/src/main/java/asys/mcsmanager/packets/SC_ToggleSendMessages.java b/bridge-protocol/src/main/java/asys/mcsmanager/packets/SC_ToggleSendMessages.java new file mode 100644 index 0000000..258d337 --- /dev/null +++ b/bridge-protocol/src/main/java/asys/mcsmanager/packets/SC_ToggleSendMessages.java @@ -0,0 +1,32 @@ +/* + * DmitriyMX + * 2017-06-11 + */ +package asys.mcsmanager.packets; + +import io.netty.buffer.ByteBuf; + +public class SC_ToggleSendMessages extends Packet { + private boolean needSend; + + public SC_ToggleSendMessages() { + } + + public SC_ToggleSendMessages(boolean needSend) { + this.needSend = needSend; + } + + public boolean isNeedSend() { + return needSend; + } + + @Override + public void readSelfData(ByteBuf buffer) { + needSend = (buffer.readUnsignedByte() == 1); + } + + @Override + public void writeSelfData(ByteBuf buffer) { + buffer.writeByte(needSend ? 1 : 0); + } +} diff --git a/bridge/build.gradle b/bridge/build.gradle index 71e9af3..84e84cb 100644 --- a/bridge/build.gradle +++ b/bridge/build.gradle @@ -1,5 +1,5 @@ group = 'asys' -version = '0.7-SNAPSHOT' +version = '0.8-SNAPSHOT' repositories { maven { url 'https://hub.spigotmc.org/nexus/content/groups/public/' } diff --git a/bridge/src/main/java/asys/bridge/bukkit/BridgeBukkitImpl.java b/bridge/src/main/java/asys/bridge/bukkit/BridgeBukkitImpl.java new file mode 100644 index 0000000..be399a3 --- /dev/null +++ b/bridge/src/main/java/asys/bridge/bukkit/BridgeBukkitImpl.java @@ -0,0 +1,48 @@ +/* + * DmitriyMX + * 2017-05-17 + */ +package asys.bridge.bukkit; + +import asys.bridge.client.AbstractBridge; +import asys.bridge.client.IBridge; +import asys.bridge.client.IConfig; +import asys.bridge.client.ILogger; +import io.netty.channel.Channel; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.core.Logger; + +public class BridgeBukkitImpl extends AbstractBridge implements IBridge { + private BridgePlugin plugin; + private LoggerBukkitImpl logger; + private ConfigBukkitImpl configBukkit; + + public BridgeBukkitImpl(BridgePlugin plugin) { + this.plugin = plugin; + this.logger = new LoggerBukkitImpl(plugin.getLogger()); + this.configBukkit = new ConfigBukkitImpl(plugin.getConfig()); + } + + @Override + public ILogger getLogger() { + return logger; + } + + @Override + public IConfig getConfig() { + return configBukkit; + } + + @Override + public int getCountOnlinePlayers() { + return plugin.getServer().getOnlinePlayers().size(); + } + + @Override + public void dispatchCommand(String command) { + plugin.getServer().dispatchCommand( + plugin.getServer().getConsoleSender(), + command + ); + } +} diff --git a/bridge/src/main/java/asys/bridge/bukkit/BridgePlugin.java b/bridge/src/main/java/asys/bridge/bukkit/BridgePlugin.java index 5debbf8..4d73b8f 100644 --- a/bridge/src/main/java/asys/bridge/bukkit/BridgePlugin.java +++ b/bridge/src/main/java/asys/bridge/bukkit/BridgePlugin.java @@ -4,44 +4,26 @@ */ package asys.bridge.bukkit; -import asys.bridge.client.Client; -import asys.mcsmanager.packets.CS_Ping; -import io.netty.channel.Channel; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.core.Logger; import org.bukkit.command.Command; import org.bukkit.command.CommandSender; import org.bukkit.plugin.java.JavaPlugin; public class BridgePlugin extends JavaPlugin { - public static BridgePlugin INSTANCE; - private Client client; - private TaskTicker connectTicker, pingTicker; - private int tryConnect = 0; - private boolean needReconnect = true; + private BridgeBukkitImpl bridgeBukkit; @Override - public void onEnable() { - if (INSTANCE == null) { - INSTANCE = this; - saveDefaultConfig(); - - startReconnect(); - } + public void onLoad() { + saveDefaultConfig(); + this.bridgeBukkit = new BridgeBukkitImpl(this); + this.bridgeBukkit.startReconnect(); } @Override public void onDisable() { - setNeedReconnect(false); - stopReconnect(); - stopPing(); - - if (client.isConnected()) { - getLogger().info("Disconnect..."); - client.disconnect(); - } - - INSTANCE = null; + this.bridgeBukkit.setNeedReconnect(false); + this.bridgeBukkit.stopPing(); + this.bridgeBukkit.stopReconnect(); + this.bridgeBukkit.disconnect(); } @Override @@ -49,65 +31,4 @@ public class BridgePlugin extends JavaPlugin { sender.sendMessage("ASys Bridge by DmitriyMX"); return true; } - - public void startReconnect() { - client = new Client(); - connectTicker = new TaskTicker().setStepTimeMs(5000L); - connectTicker.setTask(() -> { - getLogger().info(String.format("Connect(%d) to Zond...", ++tryConnect)); - if (getConfig().getInt("mode") == 1) { - client.connect(getConfig().getString("host"), getConfig().getInt("port")); - } else { - client.connect("127.0.0.1", getConfig().getInt("port")); - } - if (client.isConnected()) { - stopReconnect(); - } else { - getLogger().warning( - String.format("Connection(%d) fail. Try reconnect...", tryConnect)); - } - }).start(); - } - - public void stopReconnect() { - if (connectTicker != null) { - connectTicker.stop(); - tryConnect = 0; - } - } - - public void startPing(Channel channel) { - pingTicker = new TaskTicker().setStepTimeMs(5000L); - pingTicker.setTask(() -> { - channel.write(new CS_Ping( - System.currentTimeMillis(), - 20.0D, //FIXME - getServer().getOnlinePlayers().size() - )); - if (channel.isWritable()) { - channel.flush(); - } else { - getLogger().warning("Lost connection!"); - channel.close(); - stopPing(); - - getLogger().warning("Try reconnect..."); - startReconnect(); - } - }).start(); - } - - public void stopPing() { - if (pingTicker != null) { - pingTicker.stop(); - } - } - - public boolean isNeedReconnect() { - return needReconnect; - } - - public void setNeedReconnect(boolean needReconnect) { - this.needReconnect = needReconnect; - } } diff --git a/bridge/src/main/java/asys/bridge/bukkit/ConfigBukkitImpl.java b/bridge/src/main/java/asys/bridge/bukkit/ConfigBukkitImpl.java new file mode 100644 index 0000000..be56291 --- /dev/null +++ b/bridge/src/main/java/asys/bridge/bukkit/ConfigBukkitImpl.java @@ -0,0 +1,26 @@ +/* + * DmitriyMX + * 2017-05-17 + */ +package asys.bridge.bukkit; + +import asys.bridge.client.IConfig; +import org.bukkit.configuration.file.FileConfiguration; + +public class ConfigBukkitImpl implements IConfig { + private FileConfiguration fileConfiguration; + + public ConfigBukkitImpl(FileConfiguration fileConfiguration) { + this.fileConfiguration = fileConfiguration; + } + + @Override + public String getString(String path) { + return fileConfiguration.getString(path); + } + + @Override + public int getInt(String path) { + return fileConfiguration.getInt(path); + } +} diff --git a/bridge/src/main/java/asys/bridge/bukkit/LoggerBukkitImpl.java b/bridge/src/main/java/asys/bridge/bukkit/LoggerBukkitImpl.java new file mode 100644 index 0000000..53945d3 --- /dev/null +++ b/bridge/src/main/java/asys/bridge/bukkit/LoggerBukkitImpl.java @@ -0,0 +1,32 @@ +/* + * DmitriyMX + * 2017-05-17 + */ +package asys.bridge.bukkit; + +import asys.bridge.client.ILogger; + +import java.util.logging.Logger; + +public class LoggerBukkitImpl implements ILogger { + private Logger logger; + + LoggerBukkitImpl(Logger logger) { + this.logger = logger; + } + + @Override + public void info(String string) { + logger.info(string); + } + + @Override + public void warn(String string) { + logger.warning(string); + } + + @Override + public void error(String string) { + logger.severe(string); + } +} diff --git a/bridge/src/main/java/asys/bridge/bukkit/TaskTicker.java b/bridge/src/main/java/asys/bridge/bukkit/TaskTicker.java index 3799467..02fa307 100644 --- a/bridge/src/main/java/asys/bridge/bukkit/TaskTicker.java +++ b/bridge/src/main/java/asys/bridge/bukkit/TaskTicker.java @@ -10,23 +10,23 @@ public class TaskTicker implements Runnable { private Thread thread; private boolean loop = false; - TaskTicker setTask(Runnable task) { + public TaskTicker setTask(Runnable task) { this.task = task; return this; } - TaskTicker setStepTimeMs(long stepTimeMs) { + public TaskTicker setStepTimeMs(long stepTimeMs) { this.stepTimeMs = stepTimeMs; return this; } - void start() { + public void start() { thread = new Thread(this, "TaskTicker"); loop = true; thread.start(); } - void stop() { + public void stop() { loop = false; if (thread != null) { thread.interrupt(); diff --git a/bridge/src/main/java/asys/bridge/client/AbstractBridge.java b/bridge/src/main/java/asys/bridge/client/AbstractBridge.java new file mode 100644 index 0000000..fb4b277 --- /dev/null +++ b/bridge/src/main/java/asys/bridge/client/AbstractBridge.java @@ -0,0 +1,86 @@ +/* + * DmitriyMX + * 2017-05-17 + */ +package asys.bridge.client; + +import asys.bridge.bukkit.TaskTicker; +import asys.mcsmanager.packets.CS_Ping; +import io.netty.channel.Channel; + +public abstract class AbstractBridge implements IBridge { + private Client client; + private TaskTicker connectTicker, pingTicker; + private int tryConnect = 0; + private boolean needReconnect = true; + + @Override + public void startReconnect() { + client = new Client(this); + connectTicker = new TaskTicker().setStepTimeMs(5000L); + connectTicker.setTask(() -> { + getLogger().info(String.format("Connect(%d) to Zond...", ++tryConnect)); + client.connect(getConfig().getString("host"), getConfig().getInt("port")); + if (client.isConnected()) { + stopReconnect(); + } else { + getLogger().warn(String.format("Connection(%d) fail. Try reconnect...", tryConnect)); + } + }).start(); + } + + @Override + public boolean isNeedReconnect() { + return needReconnect; + } + + @Override + public void setNeedReconnect(boolean value) { + this.needReconnect = value; + } + + @Override + public void stopReconnect() { + if (connectTicker != null) { + connectTicker.stop(); + tryConnect = 0; + } + } + + @Override + public void startPing(Channel channel) { + pingTicker = new TaskTicker().setStepTimeMs(5000L); + pingTicker.setTask(() -> { + channel.write(new CS_Ping( + System.currentTimeMillis(), + 20.0D, //FIXME fake tps + getCountOnlinePlayers() + )); + if (channel.isWritable()) { + channel.flush(); + } else { + getLogger().warn("Lost connection!"); + channel.close(); + stopPing(); + + getLogger().warn("Try reconnect..."); + startReconnect(); + } + }).start(); + } + + @Override + public void stopPing() { + if (pingTicker != null) { + pingTicker.stop(); + } + } + + @Override + public void disconnect() { + if (client.isConnected()) { + getLogger().info("Disconnect..."); + client.disconnect(); + } + } +} diff --git a/bridge/src/main/java/asys/bridge/client/Client.java b/bridge/src/main/java/asys/bridge/client/Client.java index df6fd0c..b9d354b 100644 --- a/bridge/src/main/java/asys/bridge/client/Client.java +++ b/bridge/src/main/java/asys/bridge/client/Client.java @@ -19,6 +19,11 @@ public class Client { private EventLoopGroup group; private Bootstrap bootstrap; private ChannelFuture channelFuture; + private IBridge bridge; + + public Client(IBridge bridge) { + this.bridge = bridge; + } public void connect(String host, int port) { if (group == null || bootstrap == null) { @@ -55,7 +60,7 @@ public class Client { new PacketEncoder(), new PacketDecoder(), new PacketHandler(), - new ClientPacketHandler() + new ClientPacketHandler(bridge) ); } }; diff --git a/bridge/src/main/java/asys/bridge/client/ClientPacketHandler.java b/bridge/src/main/java/asys/bridge/client/ClientPacketHandler.java index 2771d97..b2fe930 100644 --- a/bridge/src/main/java/asys/bridge/client/ClientPacketHandler.java +++ b/bridge/src/main/java/asys/bridge/client/ClientPacketHandler.java @@ -4,7 +4,6 @@ */ package asys.bridge.client; -import asys.bridge.bukkit.BridgePlugin; import asys.mcsmanager.packets.*; import com.google.common.collect.BiMap; import com.google.common.collect.ImmutableBiMap; @@ -18,22 +17,18 @@ import static asys.mcsmanager.packets.codec.Params.KNOWN_HANDLERS; import static asys.mcsmanager.packets.codec.Params.KNOWN_PACKETS; public class ClientPacketHandler extends ChannelInboundHandlerAdapter implements IPacketHandler { - private static final BiMap> handshakePackets = ImmutableBiMap.of( - 1, CS_Handshake.class, - 2, SC_HandshakeResult.class - ); - private static Map, IPacketHandler> handshakeHandlers; + private static Map, IPacketHandler> knownHandlers; - private static final BiMap> pingPackets = ImmutableBiMap.of( + private static final BiMap> knownPackets = ImmutableBiMap.of( 3, CS_Ping.class, - 4, CS_ConsoleMessage.class, 5, SC_Command.class ); + private IBridge bridge; - ClientPacketHandler() { - if (handshakeHandlers == null) { - handshakeHandlers = ImmutableMap.of( - SC_HandshakeResult.class, this, + ClientPacketHandler(IBridge bridge) { + this.bridge = bridge; + if (knownHandlers == null) { + knownHandlers = ImmutableMap.of( SC_Command.class, this ); } @@ -41,33 +36,18 @@ public class ClientPacketHandler extends ChannelInboundHandlerAdapter implements @Override public void channelActive(ChannelHandlerContext context) throws Exception { - BridgePlugin.INSTANCE.getLogger().info("channelActive"); - if (BridgePlugin.INSTANCE.getConfig().getInt("mode") == 1) { - context.channel().attr(KNOWN_PACKETS).set(handshakePackets); - context.channel().attr(KNOWN_HANDLERS).set(handshakeHandlers); - - - CS_Handshake packet = new CS_Handshake( - BridgePlugin.INSTANCE.getConfig().getString("clientId"), - BridgePlugin.INSTANCE.getConfig().getString("passcode")); - BridgePlugin.INSTANCE.getLogger().info("send Handshake packet..."); - context.channel().writeAndFlush(packet); - } else { - context.channel().attr(KNOWN_PACKETS).set(pingPackets); - BridgePlugin.INSTANCE.getLogger().info("Handshake: SKIP. Used Zond."); - BridgePlugin.INSTANCE.startPing(context.channel()); - } + bridge.getLogger().info("channelActive"); + context.channel().attr(KNOWN_PACKETS).set(knownPackets); + context.channel().attr(KNOWN_HANDLERS).set(knownHandlers); super.channelActive(context); } @Override public void channelInactive(ChannelHandlerContext context) throws Exception { - if (BridgePlugin.INSTANCE != null) { - BridgePlugin.INSTANCE.stopPing(); - if (BridgePlugin.INSTANCE.isNeedReconnect()) { - BridgePlugin.INSTANCE.getLogger().warning("Lost connection! Try reconnect..."); - BridgePlugin.INSTANCE.startReconnect(); - } + bridge.stopPing(); + if (bridge.isNeedReconnect()) { + bridge.getLogger().warn("Lost connection! Try reconnect..."); + bridge.startReconnect(); } context.channel().attr(KNOWN_PACKETS).remove(); context.channel().attr(KNOWN_HANDLERS).remove(); @@ -76,33 +56,14 @@ public class ClientPacketHandler extends ChannelInboundHandlerAdapter implements @Override public void handle(Packet packet, ChannelHandlerContext context) { - BridgePlugin.INSTANCE.getLogger().info("handle : " + packet.getClass().getSimpleName()); - if (packet instanceof SC_HandshakeResult) { - if (BridgePlugin.INSTANCE.getConfig().getInt("mode") == 1) { - handleHandshakeResult((SC_HandshakeResult) packet, context); - } - } else if (packet instanceof SC_Command) { + bridge.getLogger().info("handle : " + packet.getClass().getSimpleName()); + if (packet instanceof SC_Command) { handleCommand((SC_Command) packet); } } - private void handleHandshakeResult(SC_HandshakeResult packet, ChannelHandlerContext context) { - if (packet.getErrorCode() != 0) { - BridgePlugin.INSTANCE.getLogger().severe( - String.format("Handshake: #%d %s", packet.getErrorCode(), packet.getMessage())); - BridgePlugin.INSTANCE.setNeedReconnect(false); - } else { - context.channel().attr(KNOWN_PACKETS).set(pingPackets); - BridgePlugin.INSTANCE.getLogger().info("Handshake: OK"); - BridgePlugin.INSTANCE.startPing(context.channel()); - } - } - private void handleCommand(SC_Command packet) { - BridgePlugin.INSTANCE.getLogger().info("Command: " + packet.getCommand()); - BridgePlugin.INSTANCE.getServer().dispatchCommand( - BridgePlugin.INSTANCE.getServer().getConsoleSender(), - packet.getCommand() - ); + bridge.getLogger().info("Command: " + packet.getCommand()); + bridge.dispatchCommand(packet.getCommand()); } } diff --git a/bridge/src/main/java/asys/bridge/client/IBridge.java b/bridge/src/main/java/asys/bridge/client/IBridge.java new file mode 100644 index 0000000..3ded3e8 --- /dev/null +++ b/bridge/src/main/java/asys/bridge/client/IBridge.java @@ -0,0 +1,21 @@ +/* + * DmitriyMX + * 2017-05-17 + */ +package asys.bridge.client; + +import io.netty.channel.Channel; + +public interface IBridge { + ILogger getLogger(); + IConfig getConfig(); + int getCountOnlinePlayers(); + void dispatchCommand(String command); + void startReconnect(); + boolean isNeedReconnect(); + void setNeedReconnect(boolean value); + void stopReconnect(); + void startPing(Channel channel); + void stopPing(); + void disconnect(); +} diff --git a/bridge/src/main/java/asys/bridge/client/IConfig.java b/bridge/src/main/java/asys/bridge/client/IConfig.java new file mode 100644 index 0000000..fe1e084 --- /dev/null +++ b/bridge/src/main/java/asys/bridge/client/IConfig.java @@ -0,0 +1,10 @@ +/* + * DmitriyMX + * 2017-05-17 + */ +package asys.bridge.client; + +public interface IConfig { + String getString(String path); + int getInt(String path); +} diff --git a/bridge/src/main/java/asys/bridge/client/ILogger.java b/bridge/src/main/java/asys/bridge/client/ILogger.java new file mode 100644 index 0000000..5d62261 --- /dev/null +++ b/bridge/src/main/java/asys/bridge/client/ILogger.java @@ -0,0 +1,11 @@ +/* + * DmitriyMX + * 2017-05-17 + */ +package asys.bridge.client; + +public interface ILogger { + void info(String string); + void warn(String string); + void error(String string); +} diff --git a/mcserver-manager/build.gradle b/mcserver-manager/build.gradle index e47c984..3471b72 100644 --- a/mcserver-manager/build.gradle +++ b/mcserver-manager/build.gradle @@ -1,5 +1,5 @@ group = 'asys' -version = '0.8.13-SNAPSHOT' +version = '0.10.4-SNAPSHOT' apply plugin: 'osgi' @@ -8,6 +8,10 @@ configurations { compile.extendsFrom include } +compileJava { + dependsOn ':bridge-protocol:compileJava' +} + jar { manifest { name = 'ASys MC server manager' diff --git a/mcserver-manager/src/main/java/asys/mcsmanager/Activator.java b/mcserver-manager/src/main/java/asys/mcsmanager/Activator.java index 676eaff..ff05c31 100644 --- a/mcserver-manager/src/main/java/asys/mcsmanager/Activator.java +++ b/mcserver-manager/src/main/java/asys/mcsmanager/Activator.java @@ -29,9 +29,7 @@ public class Activator implements BundleActivator, ServiceListener { Config config = serviceConfigTracker.getService(); if (config == null) throw new RuntimeException("Service 'Config' is not avalable!"); - Manager manager = new Manager(); - - module = new MCSM_WebModule(manager); + module = new MCSM_WebModule(); logger.debug("Get service: {}", Webinterface.class); serviceTracker = new ServiceTracker<>(context, Webinterface.class, null); @@ -44,13 +42,13 @@ public class Activator implements BundleActivator, ServiceListener { String passcode = config.getString("asys.mcsmanager.passcode", "testpasscode"); logger.debug("Start server manager: {}:{}", host, port); serverManager = new asys.mcsmanager.server.Server(); - serverManager.start(host, port, passcode, manager); + serverManager.start(host, port, passcode); host = config.getString("asys.mcsmanager.webconsole.host", "127.0.0.1"); port = config.getInt("asys.mcsmanager.webconsole.port", 8770); logger.debug("Start webconsole server: {}:{}", host, port); webconsoleServer = new asys.mcsmanager.websocket.Server(); - webconsoleServer.start(host, port, manager); + webconsoleServer.start(host, port); serviceConfigTracker.close(); } @@ -82,7 +80,6 @@ public class Activator implements BundleActivator, ServiceListener { logger.debug("service not found =("); } } catch (InterruptedException ignore) { - // ignore } } } diff --git a/mcserver-manager/src/main/java/asys/mcsmanager/Linker.java b/mcserver-manager/src/main/java/asys/mcsmanager/Linker.java new file mode 100644 index 0000000..ddaa960 --- /dev/null +++ b/mcserver-manager/src/main/java/asys/mcsmanager/Linker.java @@ -0,0 +1,84 @@ +/* + * DmitriyMX + * 2017-05-01 + */ +package asys.mcsmanager; + +import asys.mcsmanager.packets.CS_ConsoleMessage; +import asys.mcsmanager.packets.SC_Command; +import asys.mcsmanager.packets.SC_ToggleSendMessages; +import io.netty.channel.Channel; +import io.netty.handler.codec.http.websocketx.TextWebSocketFrame; + +import java.util.*; + +public class Linker { + private static Linker instance = new Linker(); + private Map serverMap = new HashMap<>(); + private Map> webconsoleListeners = new HashMap<>(); + + public static Linker getInstance() { + return instance; + } + + private Linker(){ + } + + public void addServer(String serverName, Channel channel) { + this.serverMap.put(serverName, new ServerInfo(serverName, channel)); + } + + public boolean existsServer(String serverName) { + return this.serverMap.containsKey(serverName); + } + + public ServerInfo getServer(String serverName) { + return this.serverMap.get(serverName); + } + + public Set getServerList() { + return this.serverMap.keySet(); + } + + public void removeServer(String serverName) { + this.serverMap.remove(serverName); + if (this.webconsoleListeners.containsKey(serverName)) { + this.webconsoleListeners.get(serverName).forEach(Channel::close); + this.webconsoleListeners.remove(serverName); + } + } + + public void addWebconsoleListener(String serverName, Channel channel) { + List channels = this.webconsoleListeners.computeIfAbsent(serverName, v -> new ArrayList<>()); + if (channels.size() == 0) { + this.serverMap.get(serverName).getChannel().writeAndFlush(new SC_ToggleSendMessages(true)); + } + channels.add(channel); + } + + public void removeWebconsoleListener(String serverName, Channel channel) { + List channels = this.webconsoleListeners.get(serverName); + channels.remove(channel); + if (channels.size() == 0) { + this.serverMap.get(serverName).getChannel().writeAndFlush(new SC_ToggleSendMessages(false)); + } + } + + public void broadcastConsoleMessage(String serverName, CS_ConsoleMessage packet) { + if (this.webconsoleListeners.containsKey(serverName)) { + this.webconsoleListeners.get(serverName).forEach(channel -> { + channel.writeAndFlush(new TextWebSocketFrame(String.format( + "[L:%d] %s", + packet.getLevel(), + packet.getMessage() + ))); + }); + } + } + + public void sendCommand(String serverName, String command) { + if (this.serverMap.containsKey(serverName)) { + this.serverMap.get(serverName).getChannel().writeAndFlush(new SC_Command(command)); + } + } +} diff --git a/mcserver-manager/src/main/java/asys/mcsmanager/MCSM_WebModule.java b/mcserver-manager/src/main/java/asys/mcsmanager/MCSM_WebModule.java index 758a1cc..287240a 100644 --- a/mcserver-manager/src/main/java/asys/mcsmanager/MCSM_WebModule.java +++ b/mcserver-manager/src/main/java/asys/mcsmanager/MCSM_WebModule.java @@ -27,11 +27,6 @@ public class MCSM_WebModule extends WebModule { private final String MODULE_URL = "/"+MODULE_NAME; private final Pattern URL_PATTERN_JS = Pattern.compile(MODULE_URL+"/(\\w+)\\.js"); private final Pattern URL_PATTERN_CSS = Pattern.compile(MODULE_URL+"/(\\w+)\\.css"); - private Manager manager; - - MCSM_WebModule(Manager manager) { - this.manager = manager; - } @Override public String getName() { @@ -89,7 +84,7 @@ public class MCSM_WebModule extends WebModule { if (httpExchange.getRequestURI().getQuery() != null && !httpExchange.getRequestURI().getQuery().isEmpty()) { Map query = this.queryToMap(httpExchange.getRequestURI().getQuery()); - ServerInfo serverInfo = manager.getInfo(query.get("clientid")); + ServerInfo serverInfo = Linker.getInstance().getServer(query.get("clientid")); if (serverInfo == null) { this.sendJson(httpExchange, "{}"); } else { @@ -102,6 +97,6 @@ public class MCSM_WebModule extends WebModule { } private JsonElement serverList() { - return GSON.toJsonTree(manager.getServerList()); + return GSON.toJsonTree(Linker.getInstance().getServerList()); } } diff --git a/mcserver-manager/src/main/java/asys/mcsmanager/Manager.java b/mcserver-manager/src/main/java/asys/mcsmanager/Manager.java deleted file mode 100644 index 4f06e68..0000000 --- a/mcserver-manager/src/main/java/asys/mcsmanager/Manager.java +++ /dev/null @@ -1,96 +0,0 @@ -/* - * DmitriyMX - * 2017-05-01 - */ -package asys.mcsmanager; - -import asys.mcsmanager.packets.CS_ConsoleMessage; -import asys.mcsmanager.packets.CS_Ping; -import asys.mcsmanager.packets.SC_Command; -import io.netty.channel.Channel; -import io.netty.handler.codec.http.websocketx.TextWebSocketFrame; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.*; - -public class Manager { - private Logger logger = LoggerFactory.getLogger(Manager.class); - private Map serversMap = new HashMap<>(); - private Map serverChannels = new HashMap<>(); - private List webconsoleListener = new ArrayList<>(); - - /** - * Добавляем в список ClientID - * @param clientId id сервера. Чувствителен к регистру - * @return false, если сервер с таким id уже имеется - */ - public boolean addClientId(String clientId, Channel channel) { - if (serversMap.containsKey(clientId)) { - return false; - } - - logger.debug("addClientId: {}", clientId); - serversMap.put(clientId, new ServerInfo(clientId)); - serverChannels.put(clientId, channel); - return true; - } - - public void removeClientId(String clientId) { - if (clientId != null) { - serverChannels.remove(clientId); - } - } - - public boolean sendCommand(String clientId, String command) { -/* - if (serverChannels.containsKey(clientId)) { - serverChannels.get(clientId).writeAndFlush(new SC_Command(command)); - return true; - } else { - return false; - } -*/ - //FIXME временный костыль - Channel channel = serverChannels.entrySet().iterator().next().getValue(); - channel.writeAndFlush(new SC_Command(command)); - return true; - } - - /** - * Дополнить информация о сервере. - * Если сервер отсутствует в списке, информация будет потеряна. - * @param clientId id сервера. Чувствителен к регистру - * @param pingPacket - */ - public void putInfo(String clientId, CS_Ping pingPacket) { - if (!serversMap.containsKey(clientId)) return; - serversMap.get(clientId).putPing(pingPacket); - } - - public Set getServerList() { - return serversMap.keySet(); - } - - public ServerInfo getInfo(String clientId) { - return serversMap.get(clientId); - } - - public void addWebConsoleListener(Channel channel) { - this.webconsoleListener.add(channel); - } - - public void removeWebConsoleListener(Channel channel) { - this.webconsoleListener.remove(channel); - } - - public void sendBroadcastToWebConsoleListeners(CS_ConsoleMessage message) { - for (Channel channel : this.webconsoleListener) { - channel.writeAndFlush(new TextWebSocketFrame(String.format( - "[L:%d] %s", - message.getLevel(), - message.getMessage() - ))); - } - } -} diff --git a/mcserver-manager/src/main/java/asys/mcsmanager/ServerInfo.java b/mcserver-manager/src/main/java/asys/mcsmanager/ServerInfo.java index fd29a74..6e14ca8 100644 --- a/mcserver-manager/src/main/java/asys/mcsmanager/ServerInfo.java +++ b/mcserver-manager/src/main/java/asys/mcsmanager/ServerInfo.java @@ -5,6 +5,7 @@ package asys.mcsmanager; import asys.mcsmanager.packets.CS_Ping; +import io.netty.channel.Channel; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -14,18 +15,23 @@ import java.util.LinkedList; public class ServerInfo { private final Logger logger = LoggerFactory.getLogger(ServerInfo.class); private final String name; + private Channel channel; private int lastOnline; - private Deque pingDeque; + private Deque pingDeque = new LinkedList<>(); - public ServerInfo(String name) { + public ServerInfo(String name, Channel channel) { this.name = name; - this.pingDeque = new LinkedList<>(); + this.channel = channel; } public String getName() { return name; } + public Channel getChannel() { + return channel; + } + public int getLastOnline() { return lastOnline; } diff --git a/mcserver-manager/src/main/java/asys/mcsmanager/server/Server.java b/mcserver-manager/src/main/java/asys/mcsmanager/server/Server.java index f2743f5..e0d0f49 100644 --- a/mcserver-manager/src/main/java/asys/mcsmanager/server/Server.java +++ b/mcserver-manager/src/main/java/asys/mcsmanager/server/Server.java @@ -4,7 +4,6 @@ */ package asys.mcsmanager.server; -import asys.mcsmanager.Manager; import asys.mcsmanager.packets.CS_Ping; import asys.mcsmanager.packets.Packet; import asys.mcsmanager.packets.codec.PacketDecoder; @@ -25,11 +24,9 @@ public class Server { ); private EventLoopGroup bossGroup, workerGroup; static String passcode; - static Manager manager; - public void start(String host, int port, String passcode, Manager manager) { + public void start(String host, int port, String passcode) { Server.passcode = passcode; - Server.manager = manager; bossGroup = new NioEventLoopGroup(1); workerGroup = new NioEventLoopGroup(); diff --git a/mcserver-manager/src/main/java/asys/mcsmanager/server/ServerPacketHandler.java b/mcserver-manager/src/main/java/asys/mcsmanager/server/ServerPacketHandler.java index 3e81c14..205bac4 100644 --- a/mcserver-manager/src/main/java/asys/mcsmanager/server/ServerPacketHandler.java +++ b/mcserver-manager/src/main/java/asys/mcsmanager/server/ServerPacketHandler.java @@ -4,6 +4,7 @@ */ package asys.mcsmanager.server; +import asys.mcsmanager.Linker; import asys.mcsmanager.packets.*; import com.google.common.collect.BiMap; import com.google.common.collect.ImmutableBiMap; @@ -17,7 +18,6 @@ import java.util.Map; import static asys.mcsmanager.packets.codec.Params.KNOWN_HANDLERS; import static asys.mcsmanager.packets.codec.Params.KNOWN_PACKETS; -import static asys.mcsmanager.server.Server.manager; class ServerPacketHandler extends ChannelInboundHandlerAdapter implements IPacketHandler { private static final BiMap> handshakePackets = ImmutableBiMap.of( @@ -26,10 +26,11 @@ class ServerPacketHandler extends ChannelInboundHandlerAdapter implements IPacke ); private static Map, IPacketHandler> handshakeHandlers; - private static final BiMap> pingPackets = ImmutableBiMap.of( + private static final BiMap> knownPackets = ImmutableBiMap.of( 3, CS_Ping.class, 4, CS_ConsoleMessage.class, - 5, SC_Command.class + 5, SC_Command.class, + 6, SC_ToggleSendMessages.class ); private static Map, IPacketHandler> pingHandlers; @@ -59,7 +60,7 @@ class ServerPacketHandler extends ChannelInboundHandlerAdapter implements IPacke @Override public void channelInactive(ChannelHandlerContext context) throws Exception { - manager.removeClientId(context.channel().attr(CLIENTID).get()); + Linker.getInstance().removeServer(context.channel().attr(CLIENTID).get()); super.channelInactive(context); } @@ -70,7 +71,7 @@ class ServerPacketHandler extends ChannelInboundHandlerAdapter implements IPacke } else if (packet.getClass() == CS_Ping.class) { handleCSPing((CS_Ping) packet, context); } else if (packet.getClass() == CS_ConsoleMessage.class) { - handleCSConsoleMessage((CS_ConsoleMessage) packet); + handleCSConsoleMessage(context.channel().attr(CLIENTID).get(), (CS_ConsoleMessage) packet); } } @@ -79,32 +80,32 @@ class ServerPacketHandler extends ChannelInboundHandlerAdapter implements IPacke try { context.channel().writeAndFlush(HandshakeResult.INVALID_PASSCODE).sync().channel().close(); } catch (InterruptedException ignore) { - // ignore } return; } - if (!manager.addClientId(packet.getClientId(), context.channel())) { + if (Linker.getInstance().existsServer(packet.getClientId())) { try { context.channel().writeAndFlush(HandshakeResult.CLIENTID_EXISTS).sync().channel().close(); } catch (InterruptedException ignore) { - // ignore } return; + } else { + Linker.getInstance().addServer(packet.getClientId(), context.channel()); } context.channel().write(HandshakeResult.OK); context.channel().attr(CLIENTID).set(packet.getClientId()); - context.channel().attr(KNOWN_PACKETS).set(pingPackets); + context.channel().attr(KNOWN_PACKETS).set(knownPackets); context.channel().attr(KNOWN_HANDLERS).set(pingHandlers); context.channel().flush(); } private void handleCSPing(CS_Ping packet, ChannelHandlerContext context) { - manager.putInfo(context.channel().attr(CLIENTID).get(), packet); + Linker.getInstance().getServer(context.channel().attr(CLIENTID).get()).putPing(packet); } - private void handleCSConsoleMessage(CS_ConsoleMessage packet) { - manager.sendBroadcastToWebConsoleListeners(packet); + private void handleCSConsoleMessage(String serverName, CS_ConsoleMessage packet) { + Linker.getInstance().broadcastConsoleMessage(serverName, packet); } } diff --git a/mcserver-manager/src/main/java/asys/mcsmanager/websocket/FrameHandler.java b/mcserver-manager/src/main/java/asys/mcsmanager/websocket/FrameHandler.java index 1dbd305..9c8dde8 100644 --- a/mcserver-manager/src/main/java/asys/mcsmanager/websocket/FrameHandler.java +++ b/mcserver-manager/src/main/java/asys/mcsmanager/websocket/FrameHandler.java @@ -4,36 +4,35 @@ */ package asys.mcsmanager.websocket; +import asys.mcsmanager.Linker; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.SimpleChannelInboundHandler; import io.netty.handler.codec.http.websocketx.TextWebSocketFrame; import io.netty.handler.codec.http.websocketx.WebSocketFrame; +import io.netty.util.AttributeKey; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import static asys.mcsmanager.websocket.Server.manager; - public class FrameHandler extends SimpleChannelInboundHandler { + private static final AttributeKey WC_SERVERNAME = AttributeKey.valueOf("WC_SERVERNAME"); private final Logger logger = LoggerFactory.getLogger(FrameHandler.class); - @Override - public void channelActive(ChannelHandlerContext ctx) throws Exception { - manager.addWebConsoleListener(ctx.channel()); - super.channelActive(ctx); - } - @Override public void channelInactive(ChannelHandlerContext ctx) throws Exception { - manager.removeWebConsoleListener(ctx.channel()); + Linker.getInstance().removeWebconsoleListener(ctx.channel().attr(WC_SERVERNAME).get(), ctx.channel()); } @Override protected void channelRead0(ChannelHandlerContext ctx, WebSocketFrame frame) throws Exception { if (frame instanceof TextWebSocketFrame) { String requestText = ((TextWebSocketFrame)frame).text(); - if (requestText.startsWith(":")) { - //FIXME убрать костыли - manager.sendCommand(null, requestText.substring(1)); + if (requestText.startsWith("]")) { + String serverName = requestText.substring(1); + ctx.channel().attr(WC_SERVERNAME).set(serverName); + Linker.getInstance().addWebconsoleListener(serverName, ctx.channel()); + } else if (requestText.startsWith(":")) { + String command = requestText.substring(1); + Linker.getInstance().sendCommand(ctx.channel().attr(WC_SERVERNAME).get(), command); } } else { logger.warn("unsupport frame type: {}", frame.getClass().getName()); diff --git a/mcserver-manager/src/main/java/asys/mcsmanager/websocket/Server.java b/mcserver-manager/src/main/java/asys/mcsmanager/websocket/Server.java index a9b30c4..b73e5d1 100644 --- a/mcserver-manager/src/main/java/asys/mcsmanager/websocket/Server.java +++ b/mcserver-manager/src/main/java/asys/mcsmanager/websocket/Server.java @@ -4,7 +4,6 @@ */ package asys.mcsmanager.websocket; -import asys.mcsmanager.Manager; import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.ChannelInitializer; import io.netty.channel.EventLoopGroup; @@ -16,11 +15,9 @@ import io.netty.handler.codec.http.HttpServerCodec; import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler; public class Server { - static Manager manager; private EventLoopGroup bossGroup, workerGroup; - public void start(String host, int port, Manager manager) { - Server.manager = manager; + public void start(String host, int port) { bossGroup = new NioEventLoopGroup(1); workerGroup = new NioEventLoopGroup(); diff --git a/mcserver-manager/src/main/resources/components.js b/mcserver-manager/src/main/resources/components.js index 5e9e803..f07bea5 100644 --- a/mcserver-manager/src/main/resources/components.js +++ b/mcserver-manager/src/main/resources/components.js @@ -76,26 +76,34 @@ var ScrollingContent = React.createClass({ ownHeight: 0, scrollRatio: 0, lastPageY: 0, + autoScroll: true, updateScrollParams: function () { this.totalHeight = this.refs.content.scrollHeight; this.ownHeight = this.refs.content.clientHeight; this.scrollRatio = this.ownHeight / this.totalHeight; if (isNaN(this.scrollRatio)) this.scrollRatio = 0; - this.refs.scroll.style.height = this.scrollRatio * 100 + "%"; - this.refs.scroll.style.top = (this.refs.content.scrollTop / this.totalHeight) * 100 + "%"; + var h = this.scrollRatio * 100; + if (h < 100) { + if (this.autoScroll) { + this.refs.content.scrollTop = this.totalHeight; + } + + this.refs.scroll.style.height = h + "%"; + this.refs.scroll.style.top = (this.refs.content.scrollTop / this.totalHeight) * 100 + "%"; + } }, toggleSelect: function (value, event) { return value; }, - handleScrollMouseDown: function (event) { + handleMouseDown: function (event) { this.lastPageY = event.pageY; document.body.classList.add('scroll-grabbed'); this.refs.scroll.classList.add('scroll-grabbed'); document.onselectstart = this.toggleSelect.bind(null, false); - document.addEventListener('mousemove', this.handleScrollMouseMove); - document.addEventListener('mouseup', this.handleScrollMouseUp); + document.addEventListener('mousemove', this.handleMouseMove); + document.addEventListener('mouseup', this.handleMouseUp); }, - handleScrollMouseMove: function (event) { + handleMouseMove: function (event) { var delta = event.pageY - this.lastPageY; this.lastPageY = event.pageY; @@ -105,18 +113,25 @@ var ScrollingContent = React.createClass({ _this.refs.scroll.style.top = (_this.refs.content.scrollTop / _this.totalHeight) * 100 + "%"; }); }, - handleScrollMouseUp: function (event) { + handleMouseUp: function (event) { document.body.classList.remove('scroll-grabbed'); this.refs.scroll.classList.remove('scroll-grabbed'); document.onselectstart = this.toggleSelect.bind(null, true); - document.removeEventListener('mousemove', this.handleScrollMouseMove); - document.removeEventListener('mouseup', this.handleScrollMouseUp); + document.removeEventListener('mousemove', this.handleMouseMove); + document.removeEventListener('mouseup', this.handleMouseUp); + }, + handleScroll: function (event) { + var zH = this.refs.gencon.offsetHeight; + var bottomPoint = ((this.refs.content.scrollTop / this.totalHeight) * zH) + + (this.scrollRatio * zH); + + this.autoScroll = (bottomPoint === zH); }, /*--------------------*/ render: function () { return ( - ce('div', {className: this.props.className}, + ce('div', {className: this.props.className, ref: 'gencon'}, ce('div', {className: 'wrapper'}, ce('div', {className: 'content', ref: 'content'}, this.props.children) ), @@ -126,7 +141,8 @@ var ScrollingContent = React.createClass({ }, componentDidMount: function () { this.updateScrollParams(); - this.refs.scroll.addEventListener('mousedown', this.handleScrollMouseDown); + this.refs.scroll.addEventListener('mousedown', this.handleMouseDown); + this.refs.content.addEventListener('scroll', this.handleScroll); var _this = this; this.refs.content.addEventListener('scroll', function(){ @@ -142,12 +158,15 @@ var ScrollingContent = React.createClass({ var WebConsole = React.createClass({ ws: null, - connect: function(){ + connect: function(serverName){ if (this.ws !== null) return; var _this = this; this.ws = new WebSocket("ws://127.0.0.1:8770"); //FIXME указывать ip:port из настроек - this.ws.onopen = function(){ console.debug('WS: open...'); }; + this.ws.onopen = function(){ + console.debug('WS: open...'); + _this.ws.send(']'+serverName); + }; this.ws.onclose = function(){ console.debug('WS: close...'); }; this.ws.onerror = function(e){ console.debug('WS: error'); console.error(e); }; this.ws.onmessage = function(event){ @@ -195,7 +214,7 @@ var WebConsole = React.createClass({ var ServerInfo = React.createClass({ tabStateWebConsole: function(state) { if (state === 1) { - this.refs.webconsole.connect(); + this.refs.webconsole.connect(this.state.title); this.refs.webconsole.focusInput(); } }, @@ -211,7 +230,7 @@ var ServerInfo = React.createClass({ return( ce('div', null, ce('h2', {style: {'margin-top': '0px'}}, this.state.title), - ce(Tabs, {tabs: ['Онлайн', 'Консоль'], stateCallback: this.tabStateWebConsole}, + ce(Tabs, {tabs: ['Онлайн', 'Консоль'], stateCallback: this.tabStateWebConsole, ref: 'tabs'}, ce(NvLineChart, {datum: [{ key: 'Online players', color: '#37d668', @@ -223,6 +242,14 @@ var ServerInfo = React.createClass({ ) ) } + }, + componentWillUpdate: function(nextProps, nextState) { + if (this.state.title === null) return; + if (this.state.title !== nextState.title) { + this.refs.webconsole.disconnect(); + this.refs.webconsole.setState({lines: []}); + this.refs.tabs.setState({activeTab: 0}); + } } }); diff --git a/mcserver-manager/src/main/resources/module.js b/mcserver-manager/src/main/resources/module.js index d8c052b..62b2236 100644 --- a/mcserver-manager/src/main/resources/module.js +++ b/mcserver-manager/src/main/resources/module.js @@ -79,8 +79,8 @@ var ContentModule = React.createClass({ }); element = ce('div', {className: 'row'}, - ce('div', {className: 'col-md-4'}, ce(ServerList, null, serverListItems)), - ce('div', {className: 'col-md-8'}, ce(ServerInfo, {ref: "serverInfo"})) + ce('div', {className: 'col-md-3'}, ce(ServerList, null, serverListItems)), + ce('div', {className: 'col-md-9'}, ce(ServerInfo, {ref: "serverInfo"})) ); } else if (this.state.nvScriptReady < 0) { element = ce('span', null, 'error');