001package com.killcoding.jsplus; 002 003import java.io.BufferedReader; 004import java.io.InputStreamReader; 005import java.lang.Process; 006import java.lang.Runtime; 007import java.lang.System; 008import java.util.concurrent.ConcurrentHashMap; 009import java.util.concurrent.ExecutorService; 010import java.util.concurrent.Executors; 011import com.killcoding.log.LoggerFactory; 012 013public class CmdConsole extends ScriptConsole { 014 015 private final boolean enableSystemOut; 016 private final String[] shellPrefix; 017 private final ExecutorService monitorExecutor = Executors.newSingleThreadExecutor(); 018 private final ConcurrentHashMap<Long, ProcessInfo> processes = new ConcurrentHashMap<>(); 019 020 public CmdConsole() { 021 this(true); 022 } 023 024 public CmdConsole(boolean enableSystemOut) { 025 super(); 026 this.enableSystemOut = enableSystemOut; 027 setUseCmdEnv(); 028 String os = System.getProperty("os.name").toUpperCase(); 029 if (os.contains("WINDOWS")) { 030 shellPrefix = new String[]{"cmd.exe", "/c"}; 031 } else { 032 shellPrefix = new String[]{"bash", "-c"}; 033 } 034 startMonitor(); 035 } 036 037 private void startMonitor() { 038 monitorExecutor.execute(() -> { 039 while (true) { 040 try { 041 for (ProcessInfo info : processes.values()) { 042 if (info.process.isAlive()) { 043 continue; 044 } 045 info.waitForAndCallback(); 046 processes.remove(info.pid); 047 } 048 Thread.sleep(200); 049 } catch (Exception e) { 050 LoggerFactory.getLogger(CmdConsole.class).error("Monitor error", e); 051 } 052 } 053 }); 054 } 055 056 public Process submit(String cmd, Runnable success, Runnable fail) { 057 try { 058 String[] fullCmd = new String[shellPrefix.length + 1]; 059 System.arraycopy(shellPrefix, 0, fullCmd, 0, shellPrefix.length); 060 fullCmd[shellPrefix.length] = cmd; 061 062 LoggerFactory.getLogger(CmdConsole.class).info("Command: {}", String.join(" ", fullCmd)); 063 064 Process process = Runtime.getRuntime().exec(fullCmd); 065 long pid = process.pid(); 066 ProcessInfo info = new ProcessInfo(pid, process, success, fail); 067 processes.put(pid, info); 068 return process; 069 } catch (Exception e) { 070 LoggerFactory.getLogger(CmdConsole.class).error("ErrorCommand: {}", cmd); 071 LoggerFactory.getLogger(CmdConsole.class).error(e.getMessage(), e); 072 return null; 073 } 074 } 075 076 public Long getCurrentPid() { 077 return processes.keys().nextElement(); 078 } 079 080 private class ProcessInfo { 081 final long pid; 082 final Process process; 083 final Runnable successCallback; 084 final Runnable failCallback; 085 boolean callbackTriggered = false; 086 087 ProcessInfo(long pid, Process process, Runnable success, Runnable fail) { 088 this.pid = pid; 089 this.process = process; 090 this.successCallback = success; 091 this.failCallback = fail; 092 } 093 094 void waitForAndCallback() { 095 if (callbackTriggered) return; 096 synchronized (this) { 097 if (callbackTriggered) return; 098 callbackTriggered = true; 099 } 100 101 try { 102 try (BufferedReader stdoutReader = new BufferedReader( 103 new InputStreamReader(process.getInputStream()))) { 104 String line; 105 while ((line = stdoutReader.readLine()) != null) { 106 if (enableSystemOut) System.out.println(line); 107 } 108 } 109 110 boolean hasError = false; 111 try (BufferedReader stderrReader = new BufferedReader( 112 new InputStreamReader(process.getErrorStream()))) { 113 String line; 114 while ((line = stderrReader.readLine()) != null) { 115 hasError = true; 116 if (enableSystemOut) System.err.println(line); 117 } 118 } 119 120 int exitCode = process.waitFor(); 121 if (exitCode == 0 && !hasError && successCallback != null) { 122 successCallback.run(); 123 } else if (failCallback != null) { 124 failCallback.run(); 125 } 126 } catch (Exception e) { 127 LoggerFactory.getLogger(CmdConsole.class).error(e.getMessage(), e); 128 if (failCallback != null) failCallback.run(); 129 } 130 } 131 } 132}