Archived
0

Merge branch 'dev-zond' into dev-webconsole

This commit is contained in:
2017-08-13 22:55:58 +03:00
43 changed files with 1774 additions and 129 deletions

11
bridge/README.MD Normal file
View File

@@ -0,0 +1,11 @@
# Bridge
Данный плагин служит "мостом" между сервером **Spigot** и **ASys**
## Настройка
Настройки хранятся в файле `config.yml`.
`port` - порт для подключения к **Zond**.
`second` - раз в сколько секунд отправлять пинг-сигнал

View File

@@ -1,5 +1,5 @@
group = 'asys'
version = '0.7.1-SNAPSHOT'
version = '0.8.4'
repositories {
maven { url 'https://hub.spigotmc.org/nexus/content/groups/public/' }
@@ -22,6 +22,7 @@ compileJava {
jar {
dependsOn configurations.include
baseName = project.group + '.' + project.name
from { configurations.include.collect { it.isDirectory() ? it : zipTree(it) } }
}

View File

@@ -8,22 +8,21 @@ 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;
import asys.mcsmanager.packets.CS_Ping;
public class BridgeBukkitImpl extends AbstractBridge implements IBridge {
private BridgePlugin plugin;
private LoggerBukkitImpl logger;
private ConfigBukkitImpl configBukkit;
private BridgeLoggerAppender loggerAppender;
private int scheduleId;
private int second;
public BridgeBukkitImpl(BridgePlugin plugin) {
BridgeBukkitImpl(BridgePlugin plugin) {
this.plugin = plugin;
this.logger = new LoggerBukkitImpl(plugin.getLogger());
this.configBukkit = new ConfigBukkitImpl(plugin.getConfig());
((Logger) LogManager.getRootLogger()).addAppender(loggerAppender = new BridgeLoggerAppender());
this.second = this.configBukkit.getInt("second");
}
@Override
@@ -50,7 +49,17 @@ public class BridgeBukkitImpl extends AbstractBridge implements IBridge {
}
@Override
public void setChannelFromConsoleMessages(Channel channel) {
this.loggerAppender.setChannel(channel);
public void startPing() {
scheduleId = plugin.getServer().getScheduler().scheduleSyncRepeatingTask(plugin,() -> {
this.client.sendPacket(new CS_Ping(
System.currentTimeMillis(),
20.0D, //FIXME fake tps
getCountOnlinePlayers()));
}, 0, 20L * second);
}
@Override
public void stopPing() {
plugin.getServer().getScheduler().cancelTask(scheduleId);
}
}

View File

@@ -1,42 +0,0 @@
/*
* DmitriyMX <dimon550@gmail.com>
* 2017-05-17
*/
package asys.bridge.bukkit;
import asys.mcsmanager.packets.CS_ConsoleMessage;
import io.netty.channel.Channel;
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.appender.AbstractAppender;
public class BridgeLoggerAppender extends AbstractAppender {
private Channel channel;
BridgeLoggerAppender() {
super("ASysBridge", null, null);
super.start();
}
@Override
public void append(LogEvent event) {
if (channel == null) return;
long timeMillis = event.getMillis();
int intLevel = event.getLevel().intLevel();
String loggerName = event.getLoggerName();
String message = event.getMessage().getFormattedMessage();
CS_ConsoleMessage messagePkg = new CS_ConsoleMessage(
timeMillis,
intLevel,
loggerName,
message
);
channel.writeAndFlush(messagePkg);
}
void setChannel(Channel channel) {
this.channel = channel;
}
}

View File

@@ -18,8 +18,14 @@ public class BridgePlugin extends JavaPlugin {
this.bridgeBukkit.startReconnect();
}
@Override
public void onEnable() {
this.bridgeBukkit.startPing();
}
@Override
public void onDisable() {
this.bridgeBukkit.sendCorrectShutdownPacket();
this.bridgeBukkit.setNeedReconnect(false);
this.bridgeBukkit.stopPing();
this.bridgeBukkit.stopReconnect();

View File

@@ -10,7 +10,7 @@ import org.bukkit.configuration.file.FileConfiguration;
public class ConfigBukkitImpl implements IConfig {
private FileConfiguration fileConfiguration;
public ConfigBukkitImpl(FileConfiguration fileConfiguration) {
ConfigBukkitImpl(FileConfiguration fileConfiguration) {
this.fileConfiguration = fileConfiguration;
}

View File

@@ -5,11 +5,11 @@
package asys.bridge.client;
import asys.bridge.bukkit.TaskTicker;
import asys.mcsmanager.packets.CS_CorrectShutdown;
import asys.mcsmanager.packets.CS_Ping;
import io.netty.channel.Channel;
public abstract class AbstractBridge implements IBridge {
private Client client;
protected Client client;
private TaskTicker connectTicker, pingTicker;
private int tryConnect = 0;
private boolean needReconnect = true;
@@ -19,8 +19,8 @@ public abstract class AbstractBridge implements IBridge {
client = new Client(this);
connectTicker = new TaskTicker().setStepTimeMs(5000L);
connectTicker.setTask(() -> {
getLogger().info(String.format("Connect(%d) to ASys...", ++tryConnect));
client.connect(getConfig().getString("host"), getConfig().getInt("port"));
getLogger().info(String.format("Connect(%d) to Zond...", ++tryConnect));
client.connect(getConfig().getInt("port"));
if (client.isConnected()) {
stopReconnect();
} else {
@@ -39,6 +39,13 @@ public abstract class AbstractBridge implements IBridge {
this.needReconnect = value;
}
@Override
public void sendCorrectShutdownPacket() {
if (client != null && client.isConnected()) {
client.sendPacket(new CS_CorrectShutdown());
}
}
@Override
public void stopReconnect() {
if (connectTicker != null) {
@@ -48,24 +55,13 @@ public abstract class AbstractBridge implements IBridge {
}
@Override
public void startPing(Channel channel) {
public void startPing() {
pingTicker = new TaskTicker().setStepTimeMs(5000L);
pingTicker.setTask(() -> {
channel.write(new CS_Ping(
client.sendPacket(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();
}
getCountOnlinePlayers()));
}).start();
}

View File

@@ -4,6 +4,7 @@
*/
package asys.bridge.client;
import asys.mcsmanager.packets.Packet;
import asys.mcsmanager.packets.codec.PacketDecoder;
import asys.mcsmanager.packets.codec.PacketEncoder;
import asys.mcsmanager.packets.codec.PacketHandler;
@@ -21,25 +22,25 @@ public class Client {
private ChannelFuture channelFuture;
private IBridge bridge;
public Client(IBridge bridge) {
Client(IBridge bridge) {
this.bridge = bridge;
}
public void connect(String host, int port) {
void connect(int port) {
if (group == null || bootstrap == null) {
group = new NioEventLoopGroup();
bootstrap = createBootstrap();
}
channelFuture = bootstrap.connect(host, port);
channelFuture = bootstrap.connect("127.0.0.1", port);
channelFuture.awaitUninterruptibly(5000);
}
public boolean isConnected() {
boolean isConnected() {
return (channelFuture != null && channelFuture.isSuccess());
}
public void disconnect() {
void disconnect() {
group.shutdownGracefully();
}
@@ -65,4 +66,10 @@ public class Client {
}
};
}
public void sendPacket(Packet packet) {
if (isConnected()) {
channelFuture.channel().writeAndFlush(packet);
}
}
}

View File

@@ -17,27 +17,20 @@ 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<Integer, Class<? extends Packet>> handshakePackets = ImmutableBiMap.of(
1, CS_Handshake.class,
2, SC_HandshakeResult.class
);
private static Map<Class<? extends Packet>, IPacketHandler> handshakeHandlers;
private static Map<Class<? extends Packet>, IPacketHandler> knownHandlers;
private static final BiMap<Integer, Class<? extends Packet>> knownPackets = ImmutableBiMap.of(
3, CS_Ping.class,
4, CS_ConsoleMessage.class,
5, SC_Command.class,
6, SC_ToggleSendMessages.class
6, CS_CorrectShutdown.class
);
private IBridge bridge;
ClientPacketHandler(IBridge bridge) {
this.bridge = bridge;
if (handshakeHandlers == null) {
handshakeHandlers = ImmutableMap.of(
SC_HandshakeResult.class, this,
SC_Command.class, this,
SC_ToggleSendMessages.class, this
if (knownHandlers == null) {
knownHandlers = ImmutableMap.of(
SC_Command.class, this
);
}
}
@@ -45,14 +38,8 @@ public class ClientPacketHandler extends ChannelInboundHandlerAdapter implements
@Override
public void channelActive(ChannelHandlerContext context) throws Exception {
bridge.getLogger().info("channelActive");
context.channel().attr(KNOWN_PACKETS).set(handshakePackets);
context.channel().attr(KNOWN_HANDLERS).set(handshakeHandlers);
CS_Handshake packet = new CS_Handshake(
bridge.getConfig().getString("clientId"),
bridge.getConfig().getString("passcode"));
bridge.getLogger().info("send Handshake packet...");
context.channel().writeAndFlush(packet);
context.channel().attr(KNOWN_PACKETS).set(knownPackets);
context.channel().attr(KNOWN_HANDLERS).set(knownHandlers);
super.channelActive(context);
}
@@ -71,24 +58,8 @@ public class ClientPacketHandler extends ChannelInboundHandlerAdapter implements
@Override
public void handle(Packet packet, ChannelHandlerContext context) {
bridge.getLogger().info("handle : " + packet.getClass().getSimpleName());
if (packet instanceof SC_HandshakeResult) {
handleHandshakeResult((SC_HandshakeResult) packet, context);
} else if (packet instanceof SC_Command) {
if (packet instanceof SC_Command) {
handleCommand((SC_Command) packet);
} else if (packet instanceof SC_ToggleSendMessages) {
handleToggleSendMessages((SC_ToggleSendMessages) packet, context);
}
}
private void handleHandshakeResult(SC_HandshakeResult packet, ChannelHandlerContext context) {
if (packet.getErrorCode() != 0) {
bridge.getLogger().error(
String.format("Handshake: #%d %s", packet.getErrorCode(), packet.getMessage()));
bridge.setNeedReconnect(false);
} else {
context.channel().attr(KNOWN_PACKETS).set(knownPackets);
bridge.getLogger().info("Handshake: OK");
bridge.startPing(context.channel());
}
}
@@ -96,8 +67,4 @@ public class ClientPacketHandler extends ChannelInboundHandlerAdapter implements
bridge.getLogger().info("Command: " + packet.getCommand());
bridge.dispatchCommand(packet.getCommand());
}
private void handleToggleSendMessages(SC_ToggleSendMessages packet, ChannelHandlerContext context) {
bridge.setChannelFromConsoleMessages(packet.isNeedSend() ? context.channel() : null);
}
}

View File

@@ -4,19 +4,17 @@
*/
package asys.bridge.client;
import io.netty.channel.Channel;
public interface IBridge {
ILogger getLogger();
IConfig getConfig();
int getCountOnlinePlayers();
void dispatchCommand(String command);
void setChannelFromConsoleMessages(Channel channel);
void startReconnect();
boolean isNeedReconnect();
void setNeedReconnect(boolean value);
void sendCorrectShutdownPacket();
void stopReconnect();
void startPing(Channel channel);
void startPing();
void stopPing();
void disconnect();
}

View File

@@ -1,4 +1,2 @@
clientId: SpigotServer0
host: 127.0.0.1
port: 8779
passcode: testpassphrase
port: 8710
second: 5

View File

@@ -13,7 +13,7 @@ subprojects {
}
subprojects {
if (it.name != 'bridge' || !it.name.startsWith('lib')) {
if (it.name != 'bridge' && it.name != 'zond' && !it.name.startsWith('lib')) {
ext {
slf4jVersion = '1.7.21'
}

View File

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

View File

@@ -0,0 +1,17 @@
/*
* DmitriyMX <dimon550@gmail.com>
* 2017-07-22
*/
package asys.mcsmanager.packets;
import io.netty.buffer.ByteBuf;
public class CS_CorrectShutdown extends Packet {
@Override
public void readSelfData(ByteBuf buffer) {
}
@Override
public void writeSelfData(ByteBuf buffer) {
}
}

View File

@@ -1,5 +1,5 @@
group = 'asys'
version = '0.10.3-SNAPSHOT'
version = '0.10.6-SNAPSHOT'
apply plugin: 'osgi'

View File

@@ -12,6 +12,7 @@ import com.google.common.collect.ImmutableMap;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.util.AttributeKey;
import org.slf4j.LoggerFactory;
import java.util.Map;

View File

@@ -199,7 +199,10 @@ var WebConsole = React.createClass({
ce('div', {id: 'webconsole'},
ce(ScrollingContent, {className: 'output'},
this.state.lines.map(function(line){
return ce('p', {dangerouslySetInnerHTML: {__html: ansi_up.ansi_to_html(line)}});
var clazz = "";
if (line.indexOf('ERROR') !== -1) { clazz = "error"; }
else if (line.indexOf('WARN') !== -1) { clazz = "warn"; }
return ce('p', {className: clazz, dangerouslySetInnerHTML: {__html: ansi_up.ansi_to_html(line)}});
})
),
ce('input', {ref: 'input', 'onKeyPress': this.handleKeyInput})

View File

@@ -3,7 +3,7 @@
color: #eee;
min-height: 500px;
height: 1px;
padding: 8px;
padding: 0 8px 0 8px;
font-family: monospace;
position: relative;
}
@@ -13,16 +13,48 @@
height: 100%;
}
#webconsole .output .wrapper::before {
content: '';
position: absolute;
z-index: 1;
top: 0;
left: 0;
right: 0;
height: 5%;
background: linear-gradient(#1e1e1e 0%, rgba(30,30,30,0) 100%);
}
#webconsole .output .wrapper::after {
content: '';
position: absolute;
z-index: 1;
bottom: 0;
left: 0;
right: 0;
height: 5%;
background: linear-gradient(rgba(30,30,30,0) 0%, #1e1e1e 100%);
}
#webconsole .output .wrapper .content {
overflow: auto;
height: 100%;
position: relative;
right: -18px;
margin-left: -18px;
padding: 8px 0 8px 0;
}
#webconsole .output .wrapper .content p {
margin: 0;
word-wrap: break-word;
}
#webconsole .output .wrapper .content p.error {
background-color: rgba(255,0,0,0.4);
}
#webconsole .output .wrapper .content p.warn {
background-color: rgba(255,200,0,0.3);
}
#webconsole .output .scroll {
@@ -34,6 +66,7 @@
cursor: -webkit-grab;
cursor: -moz-grab;
right: 0;
z-index: 1;
}
.scroll-grabbed,

View File

@@ -4,3 +4,4 @@ include 'webinterface'
include 'mcserver-manager'
include 'libprotocol'
include 'bridge'
include 'zond'

54
zond/README.MD Normal file
View File

@@ -0,0 +1,54 @@
# Zond
Проксирующий запускатор, дающий больший контроль над процессом игрового сервера.
## Настройка
Настройки храняться в файле `zond.properties`.
Если файл не будет найден, **Zond** создаст файл настроек по-умолчанию
`asys.serverId` - идентификатор запускаемого сервера. Одновременно является именем и уникальным ID
`asys.host` - IP/Hostname для подключения к **ASys**
`asys.port` - порт для подключения к **ASys**
`asys.passcode` - секретное слово для предотвращения несанкционированного подключения
`bridge.port` - порт для подключения **Bridge**
`bridge.second` - раз в сколько секунд **Bridge** будет присылать пинги
`pingmonitor.delay` - задержка старта пинг-монитора
`pingmonitor.maxlost` - сколько максимум пинг-сигналов позволено пропустить. По достижению этого числа, игровой сервер считается зависшим
`cmd.killer` - комманда для "убийства" процесса сервера
`cmd.start` - комманда для запуска провесса сервера
`cmd.prestart` - комманда выполняющаяся перед запуском сервера
`cmd.errorstart` - комманда выполняющаяся в случае проблем с запуском сервера
## Запуск
```
java -jar asys.zond.jar
```
## Комманды
`:reload` - перезагрузить настройки
`:ticker` - вкл/выкл тикера. Нужен для корректировки задержки старта пинг-монитора
`:start` - запуск процесса сервера
`:connect` - подключиться к **ASys**
`:disconnect` - отключиться от **ASys**
`:kill` - принудительно завершить процесс сервера
`:exit` - завершить работу **Zond**.

49
zond/build.gradle Normal file
View File

@@ -0,0 +1,49 @@
group = 'asys'
version = '0.7.17'
apply plugin: 'application'
mainClassName = "asys.zond.Main"
configurations {
included
includedEx
compile.extendsFrom included
compile.extendsFrom includedEx
}
def zp(FileTree ft) {
return ft.matching {
exclude 'org/apache/commons/exec/StreamPumper.class'
}
}
compileJava {
dependsOn ':libprotocol:compileJava'
}
jar {
dependsOn configurations.included
dependsOn configurations.includedEx
manifest {
attributes 'Implementation-Title': 'ASys Zond',
'Implementation-Version': version,
'Main-Class': mainClassName
}
baseName = project.group + '.' + project.name
from { configurations.included.collect { it.isDirectory() ? it : zipTree(it) } }
from { configurations.includedEx.collect { it.isDirectory() ? it : zp(zipTree(it)) } }
}
ext {
nettyVersion = '4.0.23.Final'
}
dependencies {
included files(project(':libprotocol').sourceSets.main.output.classesDir)
included group: 'jline', name: 'jline', version: '2.14.3'
includedEx group: 'org.apache.commons', name: 'commons-exec', version: '1.3'
included group: 'io.netty', name: 'netty-codec', version: nettyVersion
included group: 'com.google.guava', name: 'guava', version: '21.0'
included group: 'com.sun.jna', name: 'jna', version: '3.0.9'
}

View File

@@ -0,0 +1,66 @@
/*
* DmitriyMX <dimon550@gmail.com>
* 2017-06-08
*/
package asys.zond;
import java.io.*;
import java.util.Properties;
public class Config {
private static final Config instance = new Config();
private Properties properties = new Properties();
public static Config getInstance() {
return instance;
}
private Config(){
}
private void load(InputStream inputStream) throws IOException {
properties.load(inputStream);
if (properties.size() == 0) {
throw new IOException("Config empty!");
}
}
void load() throws IOException {
File zondPropertiesFile = new File("zond.properties");
if (!zondPropertiesFile.exists()) {
InputStream stream = Config.class.getResourceAsStream("/zond.properties");
FileOutputStream fos = new FileOutputStream(zondPropertiesFile);
byte[] buff = new byte[128];
int len;
while ((len = stream.read(buff)) > 0) {
fos.write(buff, 0, len);
}
fos.flush();
fos.close();
}
FileInputStream fis = new FileInputStream(zondPropertiesFile);
load(fis);
fis.close();
}
public String getString(String key) {
return properties.getProperty(key);
}
public int getInt(String key) {
try {
return Integer.parseInt(properties.getProperty(key));
} catch (NumberFormatException e) {
return 0;
}
}
public long getLong(String key) {
try {
return Long.parseLong(properties.getProperty(key));
} catch (NumberFormatException e) {
return 0;
}
}
}

View File

@@ -0,0 +1,72 @@
/*
* DmitriyMX <dimon550@gmail.com>
* 2017-06-15
* Idea by Daniil on 2017-06-07
*/
package asys.zond;
import asys.zond.shell.Shell;
import org.apache.commons.exec.CommandLine;
import org.apache.commons.exec.DefaultExecutor;
import org.apache.commons.exec.Executor;
import org.apache.commons.exec.PumpStreamHandler;
import org.fusesource.jansi.Ansi;
import org.fusesource.jansi.AnsiConsole;
import java.io.*;
import java.util.Arrays;
import java.util.stream.Collectors;
public class Main {
private Executor executor;
private CommandLine commandLine;
private PipeInputStream proxyStdIn;
public static void main(String[] args) throws IOException {
new Main().start();
}
private String printLogo() {
return Ansi.ansi().bold().fg(Ansi.Color.WHITE).a("ASys")
.boldOff().a(":// ")
.fg(Ansi.Color.RED).a("Zond")
.reset().newline().toString();
}
private void start() {
if (Boolean.getBoolean("ansi.install"))
AnsiConsole.systemInstall();
System.out.println(printLogo());
try {
Config.getInstance().load();
} catch (IOException e) {
e.printStackTrace();
System.exit(-2);
return;
}
proxyStdIn = new PipeInputStream();
ZondCommandHandler commandHandler = new ZondCommandHandler(proxyStdIn);
startShell(commandHandler);
initExecCommand(Shell.getInstance().getOutput());
commandHandler.setExecutor(executor, commandLine);
}
private void startShell(ZondCommandHandler commandHandler) {
Shell shell = Shell.getInstance();
try {
shell.setCommandHandler(commandHandler);
shell.start(System.in);
} catch (Exception e) {
e.printStackTrace();
}
}
private void initExecCommand(PrintStream stdout) {
String cmdLine = Config.getInstance().getString("cmd.start");
commandLine = CommandLine.parse(cmdLine);
executor = new DefaultExecutor();
PumpStreamHandler pumpStreamHandler = new PumpStreamHandler(stdout, stdout, proxyStdIn);
executor.setStreamHandler(pumpStreamHandler);
}
}

View File

@@ -0,0 +1,67 @@
/*
* DmitriyMX <dimon550@gmail.com>
* 2017-07-21
*/
package asys.zond;
public class PingMonitor {
private final long step_ping;
private final long delayStart;
private long lastPingTime;
private Thread threadPingMon;
private boolean correctShutdown = false;
private final int maxlost;
PingMonitor(int delay, int second, int maxlost) {
this.delayStart = delay*1000;
this.step_ping = second*1000;
this.maxlost = maxlost;
}
void start(final Runnable callback) {
this.threadPingMon = new Thread(() -> {
correctShutdown = false;
try {
Thread.sleep(delayStart);
} catch (InterruptedException e) {
return;
}
while (!Thread.currentThread().isInterrupted()) {
long currentTimeMillis = System.currentTimeMillis();
if ((currentTimeMillis - lastPingTime) >= (maxlost*step_ping)) { // если пропущено N пингов
callback.run(); // запускаем код завершения процесса
return; // завершаем поток
}
try {
Thread.sleep(step_ping);
} catch (InterruptedException e) {
return;
}
}
lastPingTime = 0;
});
this.threadPingMon.start();
}
void stop() {
if (threadPingMon != null) {
threadPingMon.interrupt();
threadPingMon = null;
}
}
public void checkPing() {
lastPingTime = System.currentTimeMillis();
}
public void correctShutdown() {
correctShutdown = true;
}
boolean isCorrectShutdown() {
return correctShutdown;
}
}

View File

@@ -0,0 +1,107 @@
/*
* DmitriyMX <dimon550@gmail.com>
* 2017-07-13
*/
package asys.zond;
import java.io.IOException;
import java.io.InputStream;
public class PipeInputStream extends InputStream {
private byte[] buffer = new byte[1024];
private int lastWritePos = 0,
lastReadPos = 0,
wallPos = buffer.length-1;
private boolean closed = false;
public synchronized void write(String s) {
byte[] strBytes = s.getBytes();
while ((lastWritePos < lastReadPos) && ((lastReadPos - lastWritePos) >= strBytes.length)) {
try {
wait();
} catch (InterruptedException e) {
return;
}
}
if ((lastWritePos + strBytes.length) >= buffer.length) {
wallPos = lastWritePos;
lastWritePos = 0;
if (lastReadPos == wallPos) {
lastReadPos = 0;
}
}
System.arraycopy(strBytes, 0, this.buffer, lastWritePos, strBytes.length);
lastWritePos += strBytes.length;
notify();
}
@Override
public synchronized int read() throws IOException {
if (lastReadPos == lastWritePos) {
try {
wait();
} catch (InterruptedException ignore) {
return 0;
}
}
if (closed) return 0;
if (lastReadPos == wallPos) {
lastReadPos = 0;
}
return this.buffer[lastReadPos++];
}
@Override
public int read(byte[] b) throws IOException {
return this.read(b, 0, b.length);
}
@Override
public synchronized int read(byte[] buffOut, int off, int len) throws IOException {
if (lastReadPos == lastWritePos) {
try {
wait();
} catch (InterruptedException ignore) {
return 0;
}
}
if (closed) return 0;
int actualLen = len;
if (lastReadPos > lastWritePos) {
if ((lastReadPos + len) > wallPos) {
actualLen = (wallPos - lastReadPos);
}
} else {
if ((lastReadPos + len) > lastWritePos) {
actualLen = (lastWritePos - lastReadPos);
}
}
System.arraycopy(this.buffer, lastReadPos, buffOut, off, actualLen);
lastReadPos += actualLen;
if (lastReadPos == wallPos) {
lastReadPos = 0;
}
notify();
return actualLen;
}
public void open() {
closed = false;
lastWritePos = 0;
lastReadPos = 0;
wallPos = buffer.length-1;
}
@Override
public synchronized void close() {
this.closed = true;
notify();
}
}

View File

@@ -0,0 +1,262 @@
/*
* DmitriyMX <d.mihailov@samson-rus.com>
* 2017-06-15
*/
package asys.zond;
import asys.zond.proxy.client.Connector;
import asys.zond.proxy.server.Server;
import asys.zond.shell.CommandHandler;
import asys.zond.shell.Shell;
import org.apache.commons.exec.CommandLine;
import org.apache.commons.exec.ExecuteException;
import org.apache.commons.exec.ExecuteWatchdog;
import org.apache.commons.exec.Executor;
import org.fusesource.jansi.Ansi;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.Arrays;
import java.util.List;
public class ZondCommandHandler implements CommandHandler {
private Executor executor;
private CommandLine commandLine;
private ExecuteWatchdog watchdog;
private Thread threadExec;
private Server server;
private PipeInputStream proxyStdIn;
private PingMonitor pingMonitor;
private boolean flagForceRestartProcess = false;
private boolean flagManualKill = false;
private boolean flagTicker = false;
private Thread threadTicker;
ZondCommandHandler(PipeInputStream proxyStdIn) {
this.proxyStdIn = proxyStdIn;
}
private static String zondColored(String string) {
return zondColored(string, Ansi.Color.CYAN);
}
private static String zondColored(String string, Ansi.Color color) {
return Ansi.ansi().bg(Ansi.Color.WHITE).fg(Ansi.Color.RED).a("[Zond]")
.reset().fgBright(color).a(' ').a(string)
.reset().toString();
}
private void buildThreadTicker() {
threadTicker = new Thread(() -> {
int sec = 0;
while (!Thread.currentThread().isInterrupted() && flagTicker) {
Shell.getInstance().getOutput().println(zondColored("Tick "+(sec++)+" sec"));
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
break;
}
}
}, "Thread Ticker");
}
private void preStartScript() {
if (Config.getInstance().getString("cmd.prestart").isEmpty())
return;
ProcessBuilder builder = new ProcessBuilder(Arrays.asList(
Config.getInstance().getString("cmd.prestart").split(" ", 2)));
builder.redirectErrorStream(true);
try {
Process process = builder.start();
BufferedReader br = new BufferedReader(new InputStreamReader(process.getInputStream()));
String line;
while ((line = br.readLine()) != null) {
Shell.getInstance().getOutput().println(zondColored(line));
}
process.waitFor();
} catch (IOException e) {
Shell.getInstance().getOutput()
.println(zondColored("[!] PreStart script error: " + e.getMessage(), Ansi.Color.RED));
} catch (InterruptedException ignore) {
}
}
private void errorStartScript() {
if (Config.getInstance().getString("cmd.errorstart").isEmpty())
return;
ProcessBuilder builder = new ProcessBuilder(Arrays.asList(
Config.getInstance().getString("cmd.errorstart").split(" ", 2)));
builder.redirectErrorStream(true);
try {
Process process = builder.start();
BufferedReader br = new BufferedReader(new InputStreamReader(process.getInputStream()));
String line;
while ((line = br.readLine()) != null) {
Shell.getInstance().getOutput().println(zondColored(line, Ansi.Color.RED));
}
process.waitFor();
} catch (IOException e) {
Shell.getInstance().getOutput()
.println(zondColored("[!] ErrorStart script error: " + e.getMessage(), Ansi.Color.RED));
} catch (InterruptedException ignore) {
}
}
@Override
public void handle(String commandLine) {
if (commandLine.startsWith(":")) {
internalCommand(commandLine.substring(1));
} else {
Shell.getInstance().getOutput().println(commandLine);
proxyStdIn.write(commandLine+"\n");
}
}
private void internalCommand(String line) {
if (line.equalsIgnoreCase("exit") || line.equalsIgnoreCase("quit")) {
flagManualKill = true;
killProcess();
Connector.getInstance().shutdown();
Shell.getInstance().shutdown();
} else if (line.equalsIgnoreCase("start")) {
startProcess();
} else if (line.equalsIgnoreCase("kill")) {
flagManualKill = true;
killProcess();
} else if (line.equalsIgnoreCase("connect")) {
Connector.getInstance().startReconnect();
} else if (line.equalsIgnoreCase("disconnect")) {
Shell.getInstance().getOutput().println(zondColored("Disconnect"));
Connector.getInstance().shutdown();
} else if (line.equalsIgnoreCase("reload")) {
Shell.getInstance().getOutput().println(zondColored("Reload config"));
try {
Config.getInstance().load();
} catch (IOException e) {
e.printStackTrace();
}
} else if (line.equalsIgnoreCase("ticker")) {
flagTicker = !flagTicker;
Shell.getInstance().getOutput().println(zondColored("Ticker " + (flagTicker?"on":"off")));
}
}
void setExecutor(Executor executor, CommandLine commandLine) {
this.executor = executor;
this.commandLine = commandLine;
}
private void startProcess() {
if (watchdog == null || !watchdog.isWatching()) {
preStartScript();
if (watchdog == null) {
watchdog = new ZondExecuteWatchdog(
ExecuteWatchdog.INFINITE_TIMEOUT,
proxyStdIn,
Config.getInstance().getString("cmd.killer")
);
executor.setWatchdog(watchdog);
}
pingMonitor = new PingMonitor(
Config.getInstance().getInt("pingmonitor.delay"),
Config.getInstance().getInt("bridge.second"),
Config.getInstance().getInt("pingmonitor.maxlost")
);
flagManualKill = false;
Runnable task = () -> {
short _try = 0;
do {
int code;
long deadTime = 0;
try {
server = new Server();
server.setPingMonitor(pingMonitor);
server.start(Config.getInstance().getInt("bridge.port"));
deadTime = (System.currentTimeMillis()/1000) + (Config.getInstance().getInt("pingmonitor.delay")*1000);
Runnable callback;
if (flagTicker) {
callback = () -> {
Shell.getInstance().getOutput().println(zondColored("[!] Process - zobie?", Ansi.Color.RED));
};
buildThreadTicker();
threadTicker.start();
} else {
callback = () -> {
Shell.getInstance().getOutput().println(zondColored("[!] Process - zobie?", Ansi.Color.RED));
Shell.getInstance().getOutput().println(zondColored("[!] Force shutdown process.", Ansi.Color.RED));
flagForceRestartProcess = true;
killProcess();
};
}
pingMonitor.start(callback);
code = executor.execute(commandLine);
} catch (ExecuteException e) {
code = e.getExitValue();
} catch (IOException e) {
Shell.getInstance().getOutput().println(zondColored("[!] Exception message: " + e.getMessage(), Ansi.Color.RED));
code = -99;
}
long currTime = System.currentTimeMillis()/1000;
server.shutdown();
pingMonitor.stop();
server = null;
Shell.getInstance().getOutput().println(zondColored("[i] Process finished. Code: " + code, Ansi.Color.RED));
if (pingMonitor.isCorrectShutdown()) {
flagForceRestartProcess = false;
_try = 0;
if (flagTicker)
threadTicker.interrupt();
break;
} else {
if (currTime <= deadTime && !flagManualKill) {
Shell.getInstance().getOutput().println(zondColored("[!] Premature end process.", Ansi.Color.RED));
_try++;
if (_try < 2) {
Shell.getInstance().getOutput().println(zondColored("[!] Try start process again...", Ansi.Color.RED));
}
} else if (flagForceRestartProcess) {
_try = 0;
flagForceRestartProcess = false;
} else if (code != 0 && code != -99 && !flagManualKill) {
Shell.getInstance().getOutput().println(zondColored("[!] Try start process again...", Ansi.Color.RED));
_try = 0;
} else {
break;
}
}
} while (_try < 2);
if (_try == 2) {
Shell.getInstance().getOutput().println(zondColored("[!] Discovered the problem when starting the process", Ansi.Color.RED));
errorStartScript();
}
};
threadExec = new Thread(task, "Zond Exec");
threadExec.start();
}
}
private void killProcess() {
if (watchdog != null && watchdog.isWatching()) {
server.shutdown();
pingMonitor.stop();
watchdog.destroyProcess();
threadExec.interrupt();
if (threadTicker != null)
threadTicker.interrupt();
}
}
}

View File

@@ -0,0 +1,77 @@
/*
* DmitriyMX <dimon550@gmail.com>
* 2017-07-19
*/
package asys.zond;
import asys.zond.shell.Shell;
import asys.zond.win32.Kernel32;
import asys.zond.win32.W32API;
import com.sun.jna.Pointer;
import org.apache.commons.exec.ExecuteWatchdog;
import java.io.IOException;
import java.lang.reflect.Field;
public class ZondExecuteWatchdog extends ExecuteWatchdog {
private final PipeInputStream inputStream;
private long pid;
private String cmdkiller;
ZondExecuteWatchdog(long timeout, PipeInputStream inputStream, String cmdkiller) {
super(timeout);
this.inputStream = inputStream;
this.cmdkiller = cmdkiller.trim();
}
private long get_pid(Process process) {
if (process.getClass().getName().equals("java.lang.Win32Process") ||
process.getClass().getName().equals("java.lang.ProcessImpl")) {
try {
Field f = process.getClass().getDeclaredField("handle");
f.setAccessible(true);
long handl = f.getLong(process);
Kernel32 kernel32 = Kernel32.INSTANCE;
W32API.HANDLE handle = new W32API.HANDLE();
handle.setPointer(Pointer.createConstant(handl));
return kernel32.GetProcessId(handle); //pid
} catch (Throwable ignore) {
}
} else if (process.getClass().getName().equals("java.lang.UNIXProcess")) {
try {
Field f = process.getClass().getDeclaredField("pid");
f.setAccessible(true);
return f.getLong(process); //pid
} catch (Throwable ignore) {
}
}
return -1;
}
@Override
public synchronized void start(Process processToMonitor) {
super.start(processToMonitor);
pid = get_pid(processToMonitor);
inputStream.open();
}
@Override
public synchronized void stop() {
super.stop();
inputStream.close();
}
@Override
public synchronized void destroyProcess() {
super.destroyProcess();
if (pid != -1 && cmdkiller != null && !cmdkiller.isEmpty()) {
try {
String cmd = cmdkiller.replace("%PID", String.valueOf(pid));
Runtime.getRuntime().exec(cmd);
} catch (IOException ignore) {
}
}
}
}

View File

@@ -0,0 +1,52 @@
/*
* DmitriyMX <d.mihailov@samson-rus.com>
* 2017-05-18
*/
package asys.zond.proxy;
public class TaskTicker implements Runnable {
private Runnable task;
private long stepTimeMs = 1000L;
private Thread thread;
private boolean loop = false;
public TaskTicker setTask(Runnable task) {
this.task = task;
return this;
}
public TaskTicker setStepTimeMs(long stepTimeMs) {
this.stepTimeMs = stepTimeMs;
return this;
}
public void start() {
thread = new Thread(this, "TaskTicker");
loop = true;
thread.start();
}
public void stop() {
loop = false;
if (thread != null) {
thread.interrupt();
}
}
public boolean isActive() {
return loop;
}
@Override
public void run() {
while (loop || !Thread.currentThread().isInterrupted()) {
task.run();
try {
Thread.sleep(stepTimeMs);
} catch (InterruptedException e) {
break;
}
}
}
}

View File

@@ -0,0 +1,75 @@
/*
* DmitriyMX <dimon550@gmail.com>
* 2017-06-16
*/
package asys.zond.proxy.client;
import asys.mcsmanager.packets.Packet;
import asys.mcsmanager.packets.codec.PacketDecoder;
import asys.mcsmanager.packets.codec.PacketEncoder;
import asys.mcsmanager.packets.codec.PacketHandler;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
public class Client {
private EventLoopGroup group;
private Bootstrap bootstrap;
private ChannelFuture channelFuture;
public void connect(String host, int port) {
if (group == null || bootstrap == null) {
group = new NioEventLoopGroup();
bootstrap = createBootstrap();
}
channelFuture = bootstrap.connect(host, port);
channelFuture.awaitUninterruptibly(5000);
}
public void disconnect() {
if (isConnected()) {
channelFuture.channel().close();
channelFuture = null;
group.shutdownGracefully();
}
}
public boolean isConnected() {
return (channelFuture != null && channelFuture.isSuccess());
}
public void sendPacket(Packet packet) {
if (isConnected()) {
channelFuture.channel().writeAndFlush(packet);
}
}
private Bootstrap createBootstrap() {
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(group)
.channel(NioSocketChannel.class)
.handler(createChannelInitializer());
return bootstrap;
}
private ChannelHandler createChannelInitializer() {
return new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
socketChannel.pipeline().addLast(
new PacketEncoder(),
new PacketDecoder(),
new PacketHandler(),
new ClientPacketHandler()
);
}
};
}
}

View File

@@ -0,0 +1,76 @@
/*
* DmitriyMX <dimon550@gmail.com>
* 2017-06-16
*/
package asys.zond.proxy.client;
import asys.mcsmanager.packets.*;
import asys.zond.Config;
import asys.zond.shell.Shell;
import com.google.common.collect.BiMap;
import com.google.common.collect.ImmutableBiMap;
import com.google.common.collect.ImmutableMap;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import java.util.Map;
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 final BiMap<Integer, Class<? extends Packet>> handshakePackets;
private final Map<Class<? extends Packet>, IPacketHandler> handshakeHandlers;
private final BiMap<Integer, Class<? extends Packet>> knownPackets;
ClientPacketHandler() {
handshakePackets = ImmutableBiMap.of(
1, CS_Handshake.class,
2, SC_HandshakeResult.class
);
handshakeHandlers = ImmutableMap.of(
SC_HandshakeResult.class, this
);
knownPackets = ImmutableBiMap.of(
3, CS_Ping.class,
4, CS_ConsoleMessage.class,
5, SC_Command.class
);
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
ctx.channel().attr(KNOWN_PACKETS).set(handshakePackets);
ctx.channel().attr(KNOWN_HANDLERS).set(handshakeHandlers);
ctx.channel().writeAndFlush(new CS_Handshake(
Config.getInstance().getString("asys.serverId"),
Config.getInstance().getString("asys.passcode")
));
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
ctx.channel().attr(KNOWN_PACKETS).remove();
ctx.channel().attr(KNOWN_HANDLERS).remove();
}
@Override
public void handle(Packet packet, ChannelHandlerContext context) {
if (packet instanceof SC_HandshakeResult) {
handleHandshakeResult((SC_HandshakeResult) packet, context);
}
}
private void handleHandshakeResult(SC_HandshakeResult packet, ChannelHandlerContext context) {
if (packet.getErrorCode() != 0) {
Shell.getInstance().getOutput()
.println(String.format("Handshake: #%d %s", packet.getErrorCode(), packet.getMessage()));
Connector.getInstance().shutdown();
} else {
context.channel().attr(KNOWN_PACKETS).set(knownPackets);
Shell.getInstance().getOutput().println("Handshake: OK");
}
}
}

View File

@@ -0,0 +1,64 @@
/*
* DmitriyMX <dimon550@gmail.com>
* 2017-06-16
*/
package asys.zond.proxy.client;
import asys.mcsmanager.packets.Packet;
import asys.zond.Config;
import asys.zond.proxy.TaskTicker;
import asys.zond.shell.Shell;
public class Connector {
private static Connector instance;
private Client client;
private TaskTicker connectTicker;
private int tryConnect = 0;
public static Connector getInstance() {
if (instance == null) instance = new Connector();
return instance;
}
private Connector() {
}
public void startReconnect() {
if ((connectTicker != null && connectTicker.isActive()) ||
(client != null && client.isConnected())) return;
client = new Client();
connectTicker = new TaskTicker().setStepTimeMs(5000L);
connectTicker.setTask(() -> {
Shell.getInstance().getOutput()
.println(String.format("Connect(%d) to ASys...", ++tryConnect));
client.connect(Config.getInstance().getString("asys.host"),
Config.getInstance().getInt("asys.port"));
if (client.isConnected()) {
stopReconnect();
} else {
Shell.getInstance().getOutput()
.println(String.format("Connection(%d) fail. Try reconnect...", tryConnect));
}
}).start();
}
public void stopReconnect() {
if (connectTicker != null) {
connectTicker.stop();
tryConnect = 0;
}
}
public void shutdown() {
stopReconnect();
if (client != null) {
client.disconnect();
}
}
public void sendPacket(Packet packet) {
if (client != null) {
client.sendPacket(packet);
}
}
}

View File

@@ -0,0 +1,63 @@
/*
* DmitriyMX <dimon550@gmail.com>
* 2017-06-17
*/
package asys.zond.proxy.server;
import asys.mcsmanager.packets.codec.PacketDecoder;
import asys.mcsmanager.packets.codec.PacketEncoder;
import asys.mcsmanager.packets.codec.PacketHandler;
import asys.zond.PingMonitor;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
public class Server {
private EventLoopGroup bossGroup, workerGroup;
private PingMonitor pingMonitor;
public void start(int port) {
bossGroup = new NioEventLoopGroup(1);
workerGroup = new NioEventLoopGroup();
ServerBootstrap serverBootstrap = createServerBootstrap();
serverBootstrap.bind("127.0.0.1", port);
}
public void shutdown() {
workerGroup.shutdownGracefully();
bossGroup.shutdownGracefully();
}
private ServerBootstrap createServerBootstrap() {
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.childHandler(createChannelInitializer());
return bootstrap;
}
private ChannelHandler createChannelInitializer() {
return new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
socketChannel.pipeline().addLast(
new PacketEncoder(),
new PacketDecoder(),
new PacketHandler(),
new ServerPacketHandler(pingMonitor)
);
}
};
}
public void setPingMonitor(PingMonitor pingMonitor) {
this.pingMonitor = pingMonitor;
}
}

View File

@@ -0,0 +1,69 @@
/*
* DmitriyMX <dimon550@gmail.com>
* 2017-06-17
*/
package asys.zond.proxy.server;
import asys.mcsmanager.packets.*;
import asys.zond.PingMonitor;
import asys.zond.proxy.client.Connector;
import com.google.common.collect.BiMap;
import com.google.common.collect.ImmutableBiMap;
import com.google.common.collect.ImmutableMap;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import java.util.Map;
import static asys.mcsmanager.packets.codec.Params.KNOWN_HANDLERS;
import static asys.mcsmanager.packets.codec.Params.KNOWN_PACKETS;
public class ServerPacketHandler extends ChannelInboundHandlerAdapter implements IPacketHandler {
private final BiMap<Integer, Class<? extends Packet>> knownPackets;
private final Map<Class<? extends Packet>, IPacketHandler> knownHandlers;
private final PingMonitor pingMonitor;
ServerPacketHandler(PingMonitor pingMonitor) {
this.pingMonitor = pingMonitor;
knownPackets = ImmutableBiMap.of(
3, CS_Ping.class,
5, SC_Command.class,
6, CS_CorrectShutdown.class
);
knownHandlers = ImmutableMap.of(
CS_Ping.class, this,
CS_ConsoleMessage.class, this,
CS_CorrectShutdown.class, this
);
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
ctx.channel().attr(KNOWN_PACKETS).set(knownPackets);
ctx.channel().attr(KNOWN_HANDLERS).set(knownHandlers);
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
ctx.channel().attr(KNOWN_PACKETS).remove();
ctx.channel().attr(KNOWN_HANDLERS).remove();
}
@Override
public void handle(Packet packet, ChannelHandlerContext context) {
if (packet instanceof CS_Ping) {
handleCSPing((CS_Ping) packet);
} else if (packet instanceof CS_CorrectShutdown) {
handleCSCorrectShutdown();
}
}
private void handleCSPing(CS_Ping packet) {
pingMonitor.checkPing();
Connector.getInstance().sendPacket(packet);
}
private void handleCSCorrectShutdown() {
pingMonitor.correctShutdown();
}
}

View File

@@ -0,0 +1,9 @@
/*
* DmitriyMX <d.mihailov@samson-rus.com>
* 2017-06-15
*/
package asys.zond.shell;
public interface CommandHandler {
void handle(String commandLine);
}

View File

@@ -0,0 +1,35 @@
/*
* DmitriyMX <dimon550@gmail.com>
* 2017-06-15
*/
package asys.zond.shell;
import jline.console.ConsoleReader;
import java.io.IOException;
public class CommandLooper implements Runnable {
private ConsoleReader consoleReader;
private CommandHandler commandHandler;
CommandLooper(ConsoleReader consoleReader, CommandHandler commandHandler) {
this.consoleReader = consoleReader;
this.commandHandler = commandHandler;
}
@Override
public void run() {
while (!Thread.currentThread().isInterrupted()) {
String line;
try {
line = consoleReader.readLine();
} catch (IOException e) {
break;
}
if (line == null) break;
if (line.trim().isEmpty()) continue;
commandHandler.handle(line);
}
}
}

View File

@@ -0,0 +1,12 @@
/*
* DmitriyMX <d.mihailov@samson-rus.com>
* 2017-06-15
*/
package asys.zond.shell;
public class EchoCommandHandler implements CommandHandler {
@Override
public void handle(String commandLine) {
Shell.getInstance().getOutput().println(commandLine);
}
}

View File

@@ -0,0 +1,60 @@
/*
* DmitriyMX <dimon550@gmail.com>
* 2017-06-15
*/
package asys.zond.shell;
import jline.console.ConsoleReader;
import java.io.*;
public class Shell {
private static final String DEFAULT_PROMPT = ":";
private static Shell instance;
private ConsoleReader consoleReader;
private Thread threadCommandLoop;
private CommandHandler commandHandler;
private ShellStdOut shellStdOut;
public static Shell getInstance() {
if (instance == null) instance = new Shell();
return instance;
}
private Shell() {
}
public void start(InputStream inputStream) throws IOException {
consoleReader = new ConsoleReader(inputStream, (shellStdOut = new ShellStdOut(System.out)));
shellStdOut.setConsoleReader(consoleReader);
consoleReader.setPrompt(DEFAULT_PROMPT);
if (commandHandler == null) commandHandler = new EchoCommandHandler();
threadCommandLoop = new Thread(
new CommandLooper(consoleReader, commandHandler),
"Zond shell looper");
threadCommandLoop.start();
}
public void shutdown() {
threadCommandLoop.interrupt();
consoleReader.close();
}
public void setPrompt(String value) {
consoleReader.setPrompt(value);
}
public void resetPrompt() {
consoleReader.setPrompt(DEFAULT_PROMPT);
}
public PrintStream getOutput() {
return shellStdOut;
}
public void setCommandHandler(CommandHandler commandHandler) {
this.commandHandler = commandHandler;
}
}

View File

@@ -0,0 +1,99 @@
/*
* DmitriyMX <dimon550@gmail.com>
* 2017-07-23
*/
package asys.zond.shell;
import jline.console.ConsoleReader;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
public class ShellStdOut extends PrintStream {
private ConsoleReader consoleReader;
ShellStdOut(OutputStream out) {
super(out);
}
private void _write(char c) {
try {
this.out.write(c);
} catch (IOException ignore) {
}
}
private void _write(byte[] buf) {
try {
this.out.write(buf);
} catch (IOException ignore) {
}
}
private void _write(byte[] buf, int off, int len) {
try {
this.out.write(buf, off, len);
} catch (IOException ignore) {
}
}
private void _flush() {
try {
this.out.flush();
} catch (IOException ignore) {
}
}
/**
* Очистка печатной строки от мусора
*/
private void cleanTrashLine(int len) throws IOException {
// очищает полностью строку
if (consoleReader.getCursorBuffer().buffer.length() + consoleReader.getPrompt().length() > len) {
for (int i = len; i <= consoleReader.getCursorBuffer().buffer.length() + 2; i++) {
this.out.write(' ');
}
this.out.flush();
}
}
public void printEx(byte[] buf, int len) {
_write(ConsoleReader.RESET_LINE);
_write(buf, 0, len);
try {
cleanTrashLine(len);
consoleReader.drawLine();
consoleReader.flush();
} catch (Throwable ignore) {
}
_flush();
}
@Override
public void write(byte[] buf, int off, int len) {
_write(buf, off, len);
}
@Override
public void print(String s) {
_write(ConsoleReader.RESET_LINE);
_write(s.getBytes());
}
@Override
public void println(String x) {
print(x.concat("\n"));
try {
cleanTrashLine(x.length());
consoleReader.drawLine();
consoleReader.flush();
} catch (Throwable ignore) {
}
_flush();
}
void setConsoleReader(ConsoleReader consoleReader) {
this.consoleReader = consoleReader;
}
}

View File

@@ -0,0 +1,11 @@
package asys.zond.win32;
import com.sun.jna.Native;
/* https://jna.dev.java.net/ */
public interface Kernel32 extends W32API {
Kernel32 INSTANCE = (Kernel32) Native.loadLibrary("kernel32", Kernel32.class, DEFAULT_OPTIONS);
/* http://msdn.microsoft.com/en-us/library/ms683179(VS.85).aspx */
HANDLE GetCurrentProcess();
/* http://msdn.microsoft.com/en-us/library/ms683215.aspx */
int GetProcessId(HANDLE Process);
}

View File

@@ -0,0 +1,65 @@
/* Copyright (c) 2007 Timothy Wall, All Rights Reserved
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*/
package asys.zond.win32;
import java.util.HashMap;
import java.util.Map;
import com.sun.jna.*;
import com.sun.jna.win32.StdCallLibrary;
import com.sun.jna.win32.W32APIFunctionMapper;
import com.sun.jna.win32.W32APITypeMapper;
/** Base type for most W32 API libraries. Provides standard options
* for unicode/ASCII mappings. Set the system property w32.ascii
* to true to default to the ASCII mappings.
*/
public interface W32API extends StdCallLibrary, W32Errors {
/** Standard options to use the unicode version of a w32 API. */
Map UNICODE_OPTIONS = new HashMap<String, Object>() {
{
put(OPTION_TYPE_MAPPER, W32APITypeMapper.UNICODE);
put(OPTION_FUNCTION_MAPPER, W32APIFunctionMapper.UNICODE);
}
};
/** Standard options to use the ASCII/MBCS version of a w32 API. */
Map ASCII_OPTIONS = new HashMap<String, Object>() {
{
put(OPTION_TYPE_MAPPER, W32APITypeMapper.ASCII);
put(OPTION_FUNCTION_MAPPER, W32APIFunctionMapper.ASCII);
}
};
Map DEFAULT_OPTIONS = Boolean.getBoolean("w32.ascii") ? ASCII_OPTIONS : UNICODE_OPTIONS;
class HANDLE extends PointerType {
@Override
public Object fromNative(Object nativeValue, FromNativeContext context) {
Object o = super.fromNative(nativeValue, context);
if (INVALID_HANDLE_VALUE.equals(o))
return INVALID_HANDLE_VALUE;
return o;
}
}
/** Constant value representing an invalid HANDLE. */
HANDLE INVALID_HANDLE_VALUE = new HANDLE() {
{ super.setPointer(Pointer.createConstant(-1)); }
@Override
public void setPointer(Pointer p) {
throw new UnsupportedOperationException("Immutable reference");
}
};
}

View File

@@ -0,0 +1,23 @@
/* Copyright (c) 2007 Timothy Wall, All Rights Reserved
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*/
package asys.zond.win32;
public interface W32Errors {
int NO_ERROR = 0;
int ERROR_INVALID_FUNCTION = 1;
int ERROR_FILE_NOT_FOUND = 2;
int ERROR_PATH_NOT_FOUND = 3;
}

View File

@@ -0,0 +1,157 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package org.apache.commons.exec;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import asys.zond.shell.ShellStdOut;
import org.apache.commons.exec.util.DebugUtils;
/**
* Copies all data from an input stream to an output stream.
*
* @version $Id: StreamPumper.java 1557263 2014-01-10 21:18:09Z ggregory $
*/
public class StreamPumper implements Runnable {
/** the default size of the internal buffer for copying the streams */
private static final int DEFAULT_SIZE = 1024;
/** the input stream to pump from */
private final InputStream is;
/** the output stream to pmp into */
private final OutputStream os;
/** the size of the internal buffer for copying the streams */
private final int size;
/** was the end of the stream reached */
private boolean finished;
/** close the output stream when exhausted */
private final boolean closeWhenExhausted;
/**
* Create a new stream pumper.
*
* @param is input stream to read data from
* @param os output stream to write data to.
* @param closeWhenExhausted if true, the output stream will be closed when the input is exhausted.
*/
public StreamPumper(final InputStream is, final OutputStream os,
final boolean closeWhenExhausted) {
this.is = is;
this.os = os;
this.size = DEFAULT_SIZE;
this.closeWhenExhausted = closeWhenExhausted;
}
/**
* Create a new stream pumper.
*
* @param is input stream to read data from
* @param os output stream to write data to.
* @param closeWhenExhausted if true, the output stream will be closed when the input is exhausted.
* @param size the size of the internal buffer for copying the streams
*/
public StreamPumper(final InputStream is, final OutputStream os,
final boolean closeWhenExhausted, final int size) {
this.is = is;
this.os = os;
this.size = size > 0 ? size : DEFAULT_SIZE;
this.closeWhenExhausted = closeWhenExhausted;
}
/**
* Create a new stream pumper.
*
* @param is input stream to read data from
* @param os output stream to write data to.
*/
public StreamPumper(final InputStream is, final OutputStream os) {
this(is, os, false);
}
/**
* Copies data from the input stream to the output stream. Terminates as
* soon as the input stream is closed or an error occurs.
*/
public void run() {
synchronized (this) {
// Just in case this object is reused in the future
finished = false;
}
final byte[] buf = new byte[this.size];
int length;
try {
//hack: пропатчили алгоритм
while (!Thread.currentThread().isInterrupted() && (length = is.read(buf)) > 0) {
if (os instanceof ShellStdOut) {
((ShellStdOut)os).printEx(buf, length);
} else {
os.write(buf, 0, length);
}
os.flush();
}
} catch (final Exception e) {
// nothing to do - happens quite often with watchdog
} finally {
if (closeWhenExhausted) {
try {
os.close();
} catch (final IOException e) {
final String msg = "Got exception while closing exhausted output stream";
DebugUtils.handleException(msg ,e);
}
}
synchronized (this) {
finished = true;
notifyAll();
}
}
}
/**
* Tells whether the end of the stream has been reached.
*
* @return true is the stream has been exhausted.
*/
public synchronized boolean isFinished() {
return finished;
}
/**
* This method blocks until the stream pumper finishes.
*
* @exception InterruptedException
* if any thread interrupted the current thread before or while the current thread was waiting for a
* notification.
* @see #isFinished()
*/
public synchronized void waitFor() throws InterruptedException {
while (!isFinished()) {
wait();
}
}
}

View File

@@ -0,0 +1,15 @@
asys.serverId = SpigotServer0
asys.host = 127.0.0.1
asys.port = 8779
asys.passcode = testpassphrase
bridge.port = 8710
bridge.second = 5
pingmonitor.delay = 5
pingmonitor.maxlost = 6
#Windows
#cmd.killer = taskkill /F /PID %PID
#Linux
cmd.killer = kill -KILL %PID
cmd.start = java -jar server.jar
cmd.prestart = echo Hello
cmd.errorstart = echo Error