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}