diff --git a/zond/build.gradle b/zond/build.gradle index 82c85d3..a294e94 100644 --- a/zond/build.gradle +++ b/zond/build.gradle @@ -1,5 +1,5 @@ group = 'asys' -version = '0.5.6-SNAPSHOT' +version = '0.6-SNAPSHOT' apply plugin: 'application' @@ -46,4 +46,5 @@ dependencies { 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: 'jline', name: 'jline', version: '2.13' } diff --git a/zond/src/main/java/asys/zond/Main.java b/zond/src/main/java/asys/zond/Main.java index 9d98f1e..b6db385 100644 --- a/zond/src/main/java/asys/zond/Main.java +++ b/zond/src/main/java/asys/zond/Main.java @@ -6,10 +6,8 @@ package asys.zond; import asys.zond.proxy.Connector; -import org.apache.commons.exec.CommandLine; -import org.apache.commons.exec.DefaultExecutor; -import org.apache.commons.exec.ExecuteException; -import org.apache.commons.exec.PumpStreamHandler; +import asys.zond.shell.Shell; +import org.apache.commons.exec.*; import org.fusesource.jansi.Ansi; import org.fusesource.jansi.Ansi.Color; import org.fusesource.jansi.AnsiConsole; @@ -19,6 +17,8 @@ import java.util.Arrays; import java.util.stream.Collectors; public class Main { + private static ExecuteWatchdog watchdog; + private static void printLogo() { System.out.println( Ansi.ansi().reset() @@ -28,11 +28,14 @@ public class Main { } public static void log(String message){ - System.out.println( - Ansi.ansi().reset() + String msg = Ansi.ansi().reset() .bg(Color.BLUE).fg(Color.WHITE).a("[ASys Zond] ") - .a(message).reset() - ); + .a(message).reset().toString(); + if (Shell.getInstance().isActive()) { + Shell.getInstance().getOutput().println(msg); + } else { + System.out.println(msg); + } } public static void main(String[] args) throws IOException { @@ -49,32 +52,32 @@ public class Main { } loadConfig(); + + PipeInputStream pipeInputStream = new PipeInputStream(); + Shell.getInstance().start(System.in, commandLine -> { + if (commandLine.equalsIgnoreCase(":exit")) { + log("force exit"); + watchdog.destroyProcess(); + Shell.getInstance().shutdown(); + Main.shutdown(); + return; + } + pipeInputStream.write(commandLine+"\r\n"); + }); Connector.getInstance().startReconnect(); - - int resultCode = executeProcess(args); - - Connector.getInstance().setNeedReconnect(false); - Connector.getInstance().stopReconnect(); - Connector.getInstance().disconnect(); - - System.out.println( - Ansi.ansi().reset().newline() - .fg(Color.GREEN).a("Process Finished. Code: ") - .bold().fg(Color.WHITE).a(resultCode).reset() - ); + executeProcess(args, Shell.getInstance().getOutput(), pipeInputStream); } - private static int executeProcess(String[] args) { + private static void executeProcess(String[] args, PrintStream printStream, InputStream stdin) throws IOException { String cmdLine = Arrays.stream(args).collect(Collectors.joining(" ")); CommandLine commandLine = CommandLine.parse(cmdLine); DefaultExecutor executor = new DefaultExecutor(); - PrintStream proxySysOut = new ProxySysOut(System.out); - InputStream proxySysIn = new ProxySysIn(); - - PumpStreamHandler psh = new PumpStreamHandler(proxySysOut, proxySysOut, proxySysIn); + PumpStreamHandler psh = new PumpStreamHandler(printStream, printStream, stdin); psh.setStopTimeout(-1999); //hack: по-умолчанию в Apache Exec добавляется еще 2000L милисекунд executor.setStreamHandler(psh); + watchdog = new ExecuteWatchdog(ExecuteWatchdog.INFINITE_TIMEOUT); + executor.setWatchdog(watchdog); int resultCode = 0; try { @@ -85,7 +88,8 @@ public class Main { resultCode = -1; } - return resultCode; + watchdog = null; + log("Process finished. Code: " + resultCode); } private static void loadConfig() throws IOException { @@ -107,32 +111,9 @@ public class Main { fis.close(); } - private static class ProxySysOut extends PrintStream { - ProxySysOut(OutputStream out) { - super(out); - } - - @Override - public void write(byte[] buf, int off, int len) { - super.write(buf, off, len); - Connector.getInstance().sendMessage(new String(buf, off, len)); - } - } - - private static class ProxySysIn extends InputStream { - @Override - public int read() throws IOException { - return System.in.read(); - } - - @Override - public int read(byte[] b) throws IOException { - return System.in.read(b); - } - - @Override - public int read(byte[] b, int off, int len) throws IOException { - return System.in.read(b, off, len); - } + private static void shutdown() { + Connector.getInstance().setNeedReconnect(false); + Connector.getInstance().stopReconnect(); + Connector.getInstance().shutdown(); } } diff --git a/zond/src/main/java/asys/zond/PipeInputStream.java b/zond/src/main/java/asys/zond/PipeInputStream.java new file mode 100644 index 0000000..5ea3ef4 --- /dev/null +++ b/zond/src/main/java/asys/zond/PipeInputStream.java @@ -0,0 +1,88 @@ +/* + * DmitriyMX + * 2017-06-14 + */ +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; + + 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) >= 1024) { + wallPos = lastWritePos; + lastWritePos = 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 (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; + } + } + 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; + } +} diff --git a/zond/src/main/java/asys/zond/proxy/Connector.java b/zond/src/main/java/asys/zond/proxy/Connector.java index 7827d49..e41a25b 100644 --- a/zond/src/main/java/asys/zond/proxy/Connector.java +++ b/zond/src/main/java/asys/zond/proxy/Connector.java @@ -54,11 +54,9 @@ public class Connector { } } - public void disconnect() { - if (client.isConnected()) { - log("Disconnect..."); - client.disconnect(); - } + public void shutdown() { + log("Disconnect..."); + client.disconnect(); } public void setChannel(Channel channel) { diff --git a/zond/src/main/java/asys/zond/shell/CommandLineHandler.java b/zond/src/main/java/asys/zond/shell/CommandLineHandler.java new file mode 100644 index 0000000..fc1d7de --- /dev/null +++ b/zond/src/main/java/asys/zond/shell/CommandLineHandler.java @@ -0,0 +1,9 @@ +/* + * DmitriyMX + * 2017-06-14 + */ +package asys.zond.shell; + +public interface CommandLineHandler { + void handle(String commandLine); +} diff --git a/zond/src/main/java/asys/zond/shell/JlineProxySysOut.java b/zond/src/main/java/asys/zond/shell/JlineProxySysOut.java new file mode 100644 index 0000000..2ead4a6 --- /dev/null +++ b/zond/src/main/java/asys/zond/shell/JlineProxySysOut.java @@ -0,0 +1,84 @@ +/* + * DmitriyMX + * 2017-06-14 + */ +package asys.zond.shell; + +import jline.console.ConsoleReader; + +import java.io.*; + +public class JlineProxySysOut extends PrintStream { + private ConsoleReader consoleReader; + private PrintWriter writer; + + public JlineProxySysOut(OutputStream out) { + super(out, true); + } + + public void setConsoleReader(ConsoleReader consoleReader) { + this.consoleReader = consoleReader; + if (consoleReader != null) { + this.writer = new PrintWriter(consoleReader.getOutput()); + } else { + try { + super.out.write('\r'); + super.out.flush(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + + private void _print(String s) { + writer.print(ConsoleReader.RESET_LINE); + writer.print(s); + cleanTrashLine(s); + writer.println(); + try { + consoleReader.drawLine(); + } catch (IOException ignore) { + } + writer.flush(); + } + + /** + * Очистка печатной строки от мусора + */ + private void cleanTrashLine(String string) { + // очищает полностью строку + if (consoleReader.getCursorBuffer().buffer.length() + consoleReader.getPrompt().length() > string.length()) { + for (int i = string.length(); i <= consoleReader.getCursorBuffer().buffer.length() + 2; i++) { + writer.print(' '); + } + } + } + + @Override + public void write(byte[] buf, int off, int len) { + if (consoleReader != null) { + if ((char) buf[len - 1] == '\n') len--; //TODO проверить в windows + _print(new String(buf, off, len)); + } else { + super.write(buf, off, len); + } + } + + @Override + public void print(String x) { + if (consoleReader != null) { + _print(x); + } else { + super.print(x); + } + } + + @Override + public void println(String x) { + if (consoleReader != null) { + _print(x); + } else { + super.println(x); + } + } +} diff --git a/zond/src/main/java/asys/zond/shell/Shell.java b/zond/src/main/java/asys/zond/shell/Shell.java new file mode 100644 index 0000000..0809ca3 --- /dev/null +++ b/zond/src/main/java/asys/zond/shell/Shell.java @@ -0,0 +1,69 @@ +/* + * DmitriyMX + * 2017-06-14 + */ +package asys.zond.shell; + +import jline.console.ConsoleReader; +import jline.internal.Ansi; + +import java.io.*; + +public class Shell { + private static Shell instance = new Shell(); + private boolean active = false; + private ConsoleReader console; + private JlineProxySysOut proxySysOut; + private Thread threadCommandHandler; + + public static Shell getInstance() { + return instance; + } + + private Shell() { + } + + public void start(final InputStream inputStream, final CommandLineHandler commandLineHandler) throws IOException { + proxySysOut = new JlineProxySysOut(System.out); + console = new ConsoleReader(inputStream, proxySysOut); + proxySysOut.setConsoleReader(console); + console.setPrompt(ConsoleReader.RESET_LINE + ":"); + + threadCommandHandler = new Thread(() -> { + Thread current = Thread.currentThread(); + + try { + String line; + while (!current.isInterrupted() && ((line = console.readLine()) != null)) { + commandLineHandler.handle(line); + } + } catch (IOException ignore) { + } + + console.shutdown(); + }, "Shell: command handler"); + active = true; + threadCommandHandler.start(); + } + + public void shutdown() { + threadCommandHandler.interrupt(); + active = false; + proxySysOut.setConsoleReader(null); + } + + public JlineProxySysOut getOutput() { + return proxySysOut; + } + + public boolean isActive() { + return active; + } + + private void safeSleep() { + try { + Thread.sleep(1); + } catch (InterruptedException ignore) { + } + } +}