Archived
0

Merge branch 'dev-webconsole' into dev-zond

# Conflicts:
#	bridge/build.gradle
#	bridge/src/main/java/asys/bridge/bukkit/BridgePlugin.java
#	bridge/src/main/java/asys/bridge/client/ClientPacketHandler.java
This commit is contained in:
2017-06-14 21:34:15 +03:00
26 changed files with 478 additions and 314 deletions

View File

@@ -1,5 +1,5 @@
group = 'asys' group = 'asys'
version = '0.4-SNAPSHOT' version = '0.5-SNAPSHOT'
task jar(type: Jar, overwrite: true) { task jar(type: Jar, overwrite: true) {
// не собирать jar // не собирать jar

View File

@@ -0,0 +1,32 @@
/*
* DmitriyMX <dimon550@gmail.com>
* 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);
}
}

View File

@@ -1,5 +1,5 @@
group = 'asys' group = 'asys'
version = '0.7-SNAPSHOT' version = '0.8-SNAPSHOT'
repositories { repositories {
maven { url 'https://hub.spigotmc.org/nexus/content/groups/public/' } maven { url 'https://hub.spigotmc.org/nexus/content/groups/public/' }

View File

@@ -0,0 +1,48 @@
/*
* DmitriyMX <d.mihailov@samson-rus.com>
* 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
);
}
}

View File

@@ -4,44 +4,26 @@
*/ */
package asys.bridge.bukkit; 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.Command;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import org.bukkit.plugin.java.JavaPlugin; import org.bukkit.plugin.java.JavaPlugin;
public class BridgePlugin extends JavaPlugin { public class BridgePlugin extends JavaPlugin {
public static BridgePlugin INSTANCE; private BridgeBukkitImpl bridgeBukkit;
private Client client;
private TaskTicker connectTicker, pingTicker;
private int tryConnect = 0;
private boolean needReconnect = true;
@Override @Override
public void onEnable() { public void onLoad() {
if (INSTANCE == null) {
INSTANCE = this;
saveDefaultConfig(); saveDefaultConfig();
this.bridgeBukkit = new BridgeBukkitImpl(this);
startReconnect(); this.bridgeBukkit.startReconnect();
}
} }
@Override @Override
public void onDisable() { public void onDisable() {
setNeedReconnect(false); this.bridgeBukkit.setNeedReconnect(false);
stopReconnect(); this.bridgeBukkit.stopPing();
stopPing(); this.bridgeBukkit.stopReconnect();
this.bridgeBukkit.disconnect();
if (client.isConnected()) {
getLogger().info("Disconnect...");
client.disconnect();
}
INSTANCE = null;
} }
@Override @Override
@@ -49,65 +31,4 @@ public class BridgePlugin extends JavaPlugin {
sender.sendMessage("ASys Bridge by DmitriyMX"); sender.sendMessage("ASys Bridge by DmitriyMX");
return true; 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;
}
} }

View File

@@ -0,0 +1,26 @@
/*
* DmitriyMX <d.mihailov@samson-rus.com>
* 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);
}
}

View File

@@ -0,0 +1,32 @@
/*
* DmitriyMX <d.mihailov@samson-rus.com>
* 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);
}
}

View File

@@ -10,23 +10,23 @@ public class TaskTicker implements Runnable {
private Thread thread; private Thread thread;
private boolean loop = false; private boolean loop = false;
TaskTicker setTask(Runnable task) { public TaskTicker setTask(Runnable task) {
this.task = task; this.task = task;
return this; return this;
} }
TaskTicker setStepTimeMs(long stepTimeMs) { public TaskTicker setStepTimeMs(long stepTimeMs) {
this.stepTimeMs = stepTimeMs; this.stepTimeMs = stepTimeMs;
return this; return this;
} }
void start() { public void start() {
thread = new Thread(this, "TaskTicker"); thread = new Thread(this, "TaskTicker");
loop = true; loop = true;
thread.start(); thread.start();
} }
void stop() { public void stop() {
loop = false; loop = false;
if (thread != null) { if (thread != null) {
thread.interrupt(); thread.interrupt();

View File

@@ -0,0 +1,86 @@
/*
* DmitriyMX <d.mihailov@samson-rus.com>
* 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();
}
}
}

View File

@@ -19,6 +19,11 @@ public class Client {
private EventLoopGroup group; private EventLoopGroup group;
private Bootstrap bootstrap; private Bootstrap bootstrap;
private ChannelFuture channelFuture; private ChannelFuture channelFuture;
private IBridge bridge;
public Client(IBridge bridge) {
this.bridge = bridge;
}
public void connect(String host, int port) { public void connect(String host, int port) {
if (group == null || bootstrap == null) { if (group == null || bootstrap == null) {
@@ -55,7 +60,7 @@ public class Client {
new PacketEncoder(), new PacketEncoder(),
new PacketDecoder(), new PacketDecoder(),
new PacketHandler(), new PacketHandler(),
new ClientPacketHandler() new ClientPacketHandler(bridge)
); );
} }
}; };

View File

@@ -4,7 +4,6 @@
*/ */
package asys.bridge.client; package asys.bridge.client;
import asys.bridge.bukkit.BridgePlugin;
import asys.mcsmanager.packets.*; import asys.mcsmanager.packets.*;
import com.google.common.collect.BiMap; import com.google.common.collect.BiMap;
import com.google.common.collect.ImmutableBiMap; 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; import static asys.mcsmanager.packets.codec.Params.KNOWN_PACKETS;
public class ClientPacketHandler extends ChannelInboundHandlerAdapter implements IPacketHandler { public class ClientPacketHandler extends ChannelInboundHandlerAdapter implements IPacketHandler {
private static final BiMap<Integer, Class<? extends Packet>> handshakePackets = ImmutableBiMap.of( private static Map<Class<? extends Packet>, IPacketHandler> knownHandlers;
1, CS_Handshake.class,
2, SC_HandshakeResult.class
);
private static Map<Class<? extends Packet>, IPacketHandler> handshakeHandlers;
private static final BiMap<Integer, Class<? extends Packet>> pingPackets = ImmutableBiMap.of( private static final BiMap<Integer, Class<? extends Packet>> knownPackets = ImmutableBiMap.of(
3, CS_Ping.class, 3, CS_Ping.class,
4, CS_ConsoleMessage.class,
5, SC_Command.class 5, SC_Command.class
); );
private IBridge bridge;
ClientPacketHandler() { ClientPacketHandler(IBridge bridge) {
if (handshakeHandlers == null) { this.bridge = bridge;
handshakeHandlers = ImmutableMap.of( if (knownHandlers == null) {
SC_HandshakeResult.class, this, knownHandlers = ImmutableMap.of(
SC_Command.class, this SC_Command.class, this
); );
} }
@@ -41,33 +36,18 @@ public class ClientPacketHandler extends ChannelInboundHandlerAdapter implements
@Override @Override
public void channelActive(ChannelHandlerContext context) throws Exception { public void channelActive(ChannelHandlerContext context) throws Exception {
BridgePlugin.INSTANCE.getLogger().info("channelActive"); bridge.getLogger().info("channelActive");
if (BridgePlugin.INSTANCE.getConfig().getInt("mode") == 1) { context.channel().attr(KNOWN_PACKETS).set(knownPackets);
context.channel().attr(KNOWN_PACKETS).set(handshakePackets); context.channel().attr(KNOWN_HANDLERS).set(knownHandlers);
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());
}
super.channelActive(context); super.channelActive(context);
} }
@Override @Override
public void channelInactive(ChannelHandlerContext context) throws Exception { public void channelInactive(ChannelHandlerContext context) throws Exception {
if (BridgePlugin.INSTANCE != null) { bridge.stopPing();
BridgePlugin.INSTANCE.stopPing(); if (bridge.isNeedReconnect()) {
if (BridgePlugin.INSTANCE.isNeedReconnect()) { bridge.getLogger().warn("Lost connection! Try reconnect...");
BridgePlugin.INSTANCE.getLogger().warning("Lost connection! Try reconnect..."); bridge.startReconnect();
BridgePlugin.INSTANCE.startReconnect();
}
} }
context.channel().attr(KNOWN_PACKETS).remove(); context.channel().attr(KNOWN_PACKETS).remove();
context.channel().attr(KNOWN_HANDLERS).remove(); context.channel().attr(KNOWN_HANDLERS).remove();
@@ -76,33 +56,14 @@ public class ClientPacketHandler extends ChannelInboundHandlerAdapter implements
@Override @Override
public void handle(Packet packet, ChannelHandlerContext context) { public void handle(Packet packet, ChannelHandlerContext context) {
BridgePlugin.INSTANCE.getLogger().info("handle : " + packet.getClass().getSimpleName()); bridge.getLogger().info("handle : " + packet.getClass().getSimpleName());
if (packet instanceof SC_HandshakeResult) { if (packet instanceof SC_Command) {
if (BridgePlugin.INSTANCE.getConfig().getInt("mode") == 1) {
handleHandshakeResult((SC_HandshakeResult) packet, context);
}
} else if (packet instanceof SC_Command) {
handleCommand((SC_Command) packet); 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) { private void handleCommand(SC_Command packet) {
BridgePlugin.INSTANCE.getLogger().info("Command: " + packet.getCommand()); bridge.getLogger().info("Command: " + packet.getCommand());
BridgePlugin.INSTANCE.getServer().dispatchCommand( bridge.dispatchCommand(packet.getCommand());
BridgePlugin.INSTANCE.getServer().getConsoleSender(),
packet.getCommand()
);
} }
} }

View File

@@ -0,0 +1,21 @@
/*
* DmitriyMX <d.mihailov@samson-rus.com>
* 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();
}

View File

@@ -0,0 +1,10 @@
/*
* DmitriyMX <d.mihailov@samson-rus.com>
* 2017-05-17
*/
package asys.bridge.client;
public interface IConfig {
String getString(String path);
int getInt(String path);
}

View File

@@ -0,0 +1,11 @@
/*
* DmitriyMX <d.mihailov@samson-rus.com>
* 2017-05-17
*/
package asys.bridge.client;
public interface ILogger {
void info(String string);
void warn(String string);
void error(String string);
}

View File

@@ -1,5 +1,5 @@
group = 'asys' group = 'asys'
version = '0.8.13-SNAPSHOT' version = '0.10.4-SNAPSHOT'
apply plugin: 'osgi' apply plugin: 'osgi'
@@ -8,6 +8,10 @@ configurations {
compile.extendsFrom include compile.extendsFrom include
} }
compileJava {
dependsOn ':bridge-protocol:compileJava'
}
jar { jar {
manifest { manifest {
name = 'ASys MC server manager' name = 'ASys MC server manager'

View File

@@ -29,9 +29,7 @@ public class Activator implements BundleActivator, ServiceListener {
Config config = serviceConfigTracker.getService(); Config config = serviceConfigTracker.getService();
if (config == null) throw new RuntimeException("Service 'Config' is not avalable!"); if (config == null) throw new RuntimeException("Service 'Config' is not avalable!");
Manager manager = new Manager(); module = new MCSM_WebModule();
module = new MCSM_WebModule(manager);
logger.debug("Get service: {}", Webinterface.class); logger.debug("Get service: {}", Webinterface.class);
serviceTracker = new ServiceTracker<>(context, Webinterface.class, null); 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"); String passcode = config.getString("asys.mcsmanager.passcode", "testpasscode");
logger.debug("Start server manager: {}:{}", host, port); logger.debug("Start server manager: {}:{}", host, port);
serverManager = new asys.mcsmanager.server.Server(); 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"); host = config.getString("asys.mcsmanager.webconsole.host", "127.0.0.1");
port = config.getInt("asys.mcsmanager.webconsole.port", 8770); port = config.getInt("asys.mcsmanager.webconsole.port", 8770);
logger.debug("Start webconsole server: {}:{}", host, port); logger.debug("Start webconsole server: {}:{}", host, port);
webconsoleServer = new asys.mcsmanager.websocket.Server(); webconsoleServer = new asys.mcsmanager.websocket.Server();
webconsoleServer.start(host, port, manager); webconsoleServer.start(host, port);
serviceConfigTracker.close(); serviceConfigTracker.close();
} }
@@ -82,7 +80,6 @@ public class Activator implements BundleActivator, ServiceListener {
logger.debug("service not found =("); logger.debug("service not found =(");
} }
} catch (InterruptedException ignore) { } catch (InterruptedException ignore) {
// ignore
} }
} }
} }

View File

@@ -0,0 +1,84 @@
/*
* DmitriyMX <dimon550@gmail.com>
* 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<String, ServerInfo> serverMap = new HashMap<>();
private Map<String, List<Channel>> 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<String> 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<Channel> 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<Channel> 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));
}
}
}

View File

@@ -27,11 +27,6 @@ public class MCSM_WebModule extends WebModule {
private final String MODULE_URL = "/"+MODULE_NAME; private final String MODULE_URL = "/"+MODULE_NAME;
private final Pattern URL_PATTERN_JS = Pattern.compile(MODULE_URL+"/(\\w+)\\.js"); 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 final Pattern URL_PATTERN_CSS = Pattern.compile(MODULE_URL+"/(\\w+)\\.css");
private Manager manager;
MCSM_WebModule(Manager manager) {
this.manager = manager;
}
@Override @Override
public String getName() { public String getName() {
@@ -89,7 +84,7 @@ public class MCSM_WebModule extends WebModule {
if (httpExchange.getRequestURI().getQuery() != null && if (httpExchange.getRequestURI().getQuery() != null &&
!httpExchange.getRequestURI().getQuery().isEmpty()) { !httpExchange.getRequestURI().getQuery().isEmpty()) {
Map<String, String> query = this.queryToMap(httpExchange.getRequestURI().getQuery()); Map<String, String> query = this.queryToMap(httpExchange.getRequestURI().getQuery());
ServerInfo serverInfo = manager.getInfo(query.get("clientid")); ServerInfo serverInfo = Linker.getInstance().getServer(query.get("clientid"));
if (serverInfo == null) { if (serverInfo == null) {
this.sendJson(httpExchange, "{}"); this.sendJson(httpExchange, "{}");
} else { } else {
@@ -102,6 +97,6 @@ public class MCSM_WebModule extends WebModule {
} }
private JsonElement serverList() { private JsonElement serverList() {
return GSON.toJsonTree(manager.getServerList()); return GSON.toJsonTree(Linker.getInstance().getServerList());
} }
} }

View File

@@ -1,96 +0,0 @@
/*
* DmitriyMX <dimon550@gmail.com>
* 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<String, ServerInfo> serversMap = new HashMap<>();
private Map<String, Channel> serverChannels = new HashMap<>();
private List<Channel> webconsoleListener = new ArrayList<>();
/**
* Добавляем в список ClientID
* @param clientId id сервера. Чувствителен к регистру
* @return <code>false</code>, если сервер с таким 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<String> 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()
)));
}
}
}

View File

@@ -5,6 +5,7 @@
package asys.mcsmanager; package asys.mcsmanager;
import asys.mcsmanager.packets.CS_Ping; import asys.mcsmanager.packets.CS_Ping;
import io.netty.channel.Channel;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -14,18 +15,23 @@ import java.util.LinkedList;
public class ServerInfo { public class ServerInfo {
private final Logger logger = LoggerFactory.getLogger(ServerInfo.class); private final Logger logger = LoggerFactory.getLogger(ServerInfo.class);
private final String name; private final String name;
private Channel channel;
private int lastOnline; private int lastOnline;
private Deque<CS_Ping> pingDeque; private Deque<CS_Ping> pingDeque = new LinkedList<>();
public ServerInfo(String name) { public ServerInfo(String name, Channel channel) {
this.name = name; this.name = name;
this.pingDeque = new LinkedList<>(); this.channel = channel;
} }
public String getName() { public String getName() {
return name; return name;
} }
public Channel getChannel() {
return channel;
}
public int getLastOnline() { public int getLastOnline() {
return lastOnline; return lastOnline;
} }

View File

@@ -4,7 +4,6 @@
*/ */
package asys.mcsmanager.server; package asys.mcsmanager.server;
import asys.mcsmanager.Manager;
import asys.mcsmanager.packets.CS_Ping; import asys.mcsmanager.packets.CS_Ping;
import asys.mcsmanager.packets.Packet; import asys.mcsmanager.packets.Packet;
import asys.mcsmanager.packets.codec.PacketDecoder; import asys.mcsmanager.packets.codec.PacketDecoder;
@@ -25,11 +24,9 @@ public class Server {
); );
private EventLoopGroup bossGroup, workerGroup; private EventLoopGroup bossGroup, workerGroup;
static String passcode; 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.passcode = passcode;
Server.manager = manager;
bossGroup = new NioEventLoopGroup(1); bossGroup = new NioEventLoopGroup(1);
workerGroup = new NioEventLoopGroup(); workerGroup = new NioEventLoopGroup();

View File

@@ -4,6 +4,7 @@
*/ */
package asys.mcsmanager.server; package asys.mcsmanager.server;
import asys.mcsmanager.Linker;
import asys.mcsmanager.packets.*; import asys.mcsmanager.packets.*;
import com.google.common.collect.BiMap; import com.google.common.collect.BiMap;
import com.google.common.collect.ImmutableBiMap; 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_HANDLERS;
import static asys.mcsmanager.packets.codec.Params.KNOWN_PACKETS; import static asys.mcsmanager.packets.codec.Params.KNOWN_PACKETS;
import static asys.mcsmanager.server.Server.manager;
class ServerPacketHandler extends ChannelInboundHandlerAdapter implements IPacketHandler { class ServerPacketHandler extends ChannelInboundHandlerAdapter implements IPacketHandler {
private static final BiMap<Integer, Class<? extends Packet>> handshakePackets = ImmutableBiMap.of( private static final BiMap<Integer, Class<? extends Packet>> handshakePackets = ImmutableBiMap.of(
@@ -26,10 +26,11 @@ class ServerPacketHandler extends ChannelInboundHandlerAdapter implements IPacke
); );
private static Map<Class<? extends Packet>, IPacketHandler> handshakeHandlers; private static Map<Class<? extends Packet>, IPacketHandler> handshakeHandlers;
private static final BiMap<Integer, Class<? extends Packet>> pingPackets = ImmutableBiMap.of( private static final BiMap<Integer, Class<? extends Packet>> knownPackets = ImmutableBiMap.of(
3, CS_Ping.class, 3, CS_Ping.class,
4, CS_ConsoleMessage.class, 4, CS_ConsoleMessage.class,
5, SC_Command.class 5, SC_Command.class,
6, SC_ToggleSendMessages.class
); );
private static Map<Class<? extends Packet>, IPacketHandler> pingHandlers; private static Map<Class<? extends Packet>, IPacketHandler> pingHandlers;
@@ -59,7 +60,7 @@ class ServerPacketHandler extends ChannelInboundHandlerAdapter implements IPacke
@Override @Override
public void channelInactive(ChannelHandlerContext context) throws Exception { 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); super.channelInactive(context);
} }
@@ -70,7 +71,7 @@ class ServerPacketHandler extends ChannelInboundHandlerAdapter implements IPacke
} else if (packet.getClass() == CS_Ping.class) { } else if (packet.getClass() == CS_Ping.class) {
handleCSPing((CS_Ping) packet, context); handleCSPing((CS_Ping) packet, context);
} else if (packet.getClass() == CS_ConsoleMessage.class) { } 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 { try {
context.channel().writeAndFlush(HandshakeResult.INVALID_PASSCODE).sync().channel().close(); context.channel().writeAndFlush(HandshakeResult.INVALID_PASSCODE).sync().channel().close();
} catch (InterruptedException ignore) { } catch (InterruptedException ignore) {
// ignore
} }
return; return;
} }
if (!manager.addClientId(packet.getClientId(), context.channel())) { if (Linker.getInstance().existsServer(packet.getClientId())) {
try { try {
context.channel().writeAndFlush(HandshakeResult.CLIENTID_EXISTS).sync().channel().close(); context.channel().writeAndFlush(HandshakeResult.CLIENTID_EXISTS).sync().channel().close();
} catch (InterruptedException ignore) { } catch (InterruptedException ignore) {
// ignore
} }
return; return;
} else {
Linker.getInstance().addServer(packet.getClientId(), context.channel());
} }
context.channel().write(HandshakeResult.OK); context.channel().write(HandshakeResult.OK);
context.channel().attr(CLIENTID).set(packet.getClientId()); 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().attr(KNOWN_HANDLERS).set(pingHandlers);
context.channel().flush(); context.channel().flush();
} }
private void handleCSPing(CS_Ping packet, ChannelHandlerContext context) { 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) { private void handleCSConsoleMessage(String serverName, CS_ConsoleMessage packet) {
manager.sendBroadcastToWebConsoleListeners(packet); Linker.getInstance().broadcastConsoleMessage(serverName, packet);
} }
} }

View File

@@ -4,36 +4,35 @@
*/ */
package asys.mcsmanager.websocket; package asys.mcsmanager.websocket;
import asys.mcsmanager.Linker;
import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler; import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame; import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import io.netty.handler.codec.http.websocketx.WebSocketFrame; import io.netty.handler.codec.http.websocketx.WebSocketFrame;
import io.netty.util.AttributeKey;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import static asys.mcsmanager.websocket.Server.manager;
public class FrameHandler extends SimpleChannelInboundHandler<WebSocketFrame> { public class FrameHandler extends SimpleChannelInboundHandler<WebSocketFrame> {
private static final AttributeKey<String> WC_SERVERNAME = AttributeKey.valueOf("WC_SERVERNAME");
private final Logger logger = LoggerFactory.getLogger(FrameHandler.class); private final Logger logger = LoggerFactory.getLogger(FrameHandler.class);
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
manager.addWebConsoleListener(ctx.channel());
super.channelActive(ctx);
}
@Override @Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception { public void channelInactive(ChannelHandlerContext ctx) throws Exception {
manager.removeWebConsoleListener(ctx.channel()); Linker.getInstance().removeWebconsoleListener(ctx.channel().attr(WC_SERVERNAME).get(), ctx.channel());
} }
@Override @Override
protected void channelRead0(ChannelHandlerContext ctx, WebSocketFrame frame) throws Exception { protected void channelRead0(ChannelHandlerContext ctx, WebSocketFrame frame) throws Exception {
if (frame instanceof TextWebSocketFrame) { if (frame instanceof TextWebSocketFrame) {
String requestText = ((TextWebSocketFrame)frame).text(); String requestText = ((TextWebSocketFrame)frame).text();
if (requestText.startsWith(":")) { if (requestText.startsWith("]")) {
//FIXME убрать костыли String serverName = requestText.substring(1);
manager.sendCommand(null, 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 { } else {
logger.warn("unsupport frame type: {}", frame.getClass().getName()); logger.warn("unsupport frame type: {}", frame.getClass().getName());

View File

@@ -4,7 +4,6 @@
*/ */
package asys.mcsmanager.websocket; package asys.mcsmanager.websocket;
import asys.mcsmanager.Manager;
import io.netty.bootstrap.ServerBootstrap; import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoopGroup; import io.netty.channel.EventLoopGroup;
@@ -16,11 +15,9 @@ import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler; import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;
public class Server { public class Server {
static Manager manager;
private EventLoopGroup bossGroup, workerGroup; private EventLoopGroup bossGroup, workerGroup;
public void start(String host, int port, Manager manager) { public void start(String host, int port) {
Server.manager = manager;
bossGroup = new NioEventLoopGroup(1); bossGroup = new NioEventLoopGroup(1);
workerGroup = new NioEventLoopGroup(); workerGroup = new NioEventLoopGroup();

View File

@@ -76,26 +76,34 @@ var ScrollingContent = React.createClass({
ownHeight: 0, ownHeight: 0,
scrollRatio: 0, scrollRatio: 0,
lastPageY: 0, lastPageY: 0,
autoScroll: true,
updateScrollParams: function () { updateScrollParams: function () {
this.totalHeight = this.refs.content.scrollHeight; this.totalHeight = this.refs.content.scrollHeight;
this.ownHeight = this.refs.content.clientHeight; this.ownHeight = this.refs.content.clientHeight;
this.scrollRatio = this.ownHeight / this.totalHeight; this.scrollRatio = this.ownHeight / this.totalHeight;
if (isNaN(this.scrollRatio)) this.scrollRatio = 0; if (isNaN(this.scrollRatio)) this.scrollRatio = 0;
this.refs.scroll.style.height = this.scrollRatio * 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 + "%"; this.refs.scroll.style.top = (this.refs.content.scrollTop / this.totalHeight) * 100 + "%";
}
}, },
toggleSelect: function (value, event) { return value; }, toggleSelect: function (value, event) { return value; },
handleScrollMouseDown: function (event) { handleMouseDown: function (event) {
this.lastPageY = event.pageY; this.lastPageY = event.pageY;
document.body.classList.add('scroll-grabbed'); document.body.classList.add('scroll-grabbed');
this.refs.scroll.classList.add('scroll-grabbed'); this.refs.scroll.classList.add('scroll-grabbed');
document.onselectstart = this.toggleSelect.bind(null, false); document.onselectstart = this.toggleSelect.bind(null, false);
document.addEventListener('mousemove', this.handleScrollMouseMove); document.addEventListener('mousemove', this.handleMouseMove);
document.addEventListener('mouseup', this.handleScrollMouseUp); document.addEventListener('mouseup', this.handleMouseUp);
}, },
handleScrollMouseMove: function (event) { handleMouseMove: function (event) {
var delta = event.pageY - this.lastPageY; var delta = event.pageY - this.lastPageY;
this.lastPageY = event.pageY; this.lastPageY = event.pageY;
@@ -105,18 +113,25 @@ var ScrollingContent = React.createClass({
_this.refs.scroll.style.top = (_this.refs.content.scrollTop / _this.totalHeight) * 100 + "%"; _this.refs.scroll.style.top = (_this.refs.content.scrollTop / _this.totalHeight) * 100 + "%";
}); });
}, },
handleScrollMouseUp: function (event) { handleMouseUp: function (event) {
document.body.classList.remove('scroll-grabbed'); document.body.classList.remove('scroll-grabbed');
this.refs.scroll.classList.remove('scroll-grabbed'); this.refs.scroll.classList.remove('scroll-grabbed');
document.onselectstart = this.toggleSelect.bind(null, true); document.onselectstart = this.toggleSelect.bind(null, true);
document.removeEventListener('mousemove', this.handleScrollMouseMove); document.removeEventListener('mousemove', this.handleMouseMove);
document.removeEventListener('mouseup', this.handleScrollMouseUp); 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 () { render: function () {
return ( return (
ce('div', {className: this.props.className}, ce('div', {className: this.props.className, ref: 'gencon'},
ce('div', {className: 'wrapper'}, ce('div', {className: 'wrapper'},
ce('div', {className: 'content', ref: 'content'}, this.props.children) ce('div', {className: 'content', ref: 'content'}, this.props.children)
), ),
@@ -126,7 +141,8 @@ var ScrollingContent = React.createClass({
}, },
componentDidMount: function () { componentDidMount: function () {
this.updateScrollParams(); 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; var _this = this;
this.refs.content.addEventListener('scroll', function(){ this.refs.content.addEventListener('scroll', function(){
@@ -142,12 +158,15 @@ var ScrollingContent = React.createClass({
var WebConsole = React.createClass({ var WebConsole = React.createClass({
ws: null, ws: null,
connect: function(){ connect: function(serverName){
if (this.ws !== null) return; if (this.ws !== null) return;
var _this = this; var _this = this;
this.ws = new WebSocket("ws://127.0.0.1:8770"); //FIXME указывать ip:port из настроек 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.onclose = function(){ console.debug('WS: close...'); };
this.ws.onerror = function(e){ console.debug('WS: error'); console.error(e); }; this.ws.onerror = function(e){ console.debug('WS: error'); console.error(e); };
this.ws.onmessage = function(event){ this.ws.onmessage = function(event){
@@ -195,7 +214,7 @@ var WebConsole = React.createClass({
var ServerInfo = React.createClass({ var ServerInfo = React.createClass({
tabStateWebConsole: function(state) { tabStateWebConsole: function(state) {
if (state === 1) { if (state === 1) {
this.refs.webconsole.connect(); this.refs.webconsole.connect(this.state.title);
this.refs.webconsole.focusInput(); this.refs.webconsole.focusInput();
} }
}, },
@@ -211,7 +230,7 @@ var ServerInfo = React.createClass({
return( return(
ce('div', null, ce('div', null,
ce('h2', {style: {'margin-top': '0px'}}, this.state.title), 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: [{ ce(NvLineChart, {datum: [{
key: 'Online players', key: 'Online players',
color: '#37d668', 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});
}
} }
}); });

View File

@@ -79,8 +79,8 @@ var ContentModule = React.createClass({
}); });
element = ce('div', {className: 'row'}, element = ce('div', {className: 'row'},
ce('div', {className: 'col-md-4'}, ce(ServerList, null, serverListItems)), ce('div', {className: 'col-md-3'}, ce(ServerList, null, serverListItems)),
ce('div', {className: 'col-md-8'}, ce(ServerInfo, {ref: "serverInfo"})) ce('div', {className: 'col-md-9'}, ce(ServerInfo, {ref: "serverInfo"}))
); );
} else if (this.state.nvScriptReady < 0) { } else if (this.state.nvScriptReady < 0) {
element = ce('span', null, 'error'); element = ce('span', null, 'error');