212 lines
7.8 KiB
Java
212 lines
7.8 KiB
Java
package ctbrec.ui;
|
|
|
|
import static java.nio.charset.StandardCharsets.*;
|
|
|
|
import java.io.BufferedReader;
|
|
import java.io.File;
|
|
import java.io.IOException;
|
|
import java.io.InputStream;
|
|
import java.io.InputStreamReader;
|
|
import java.io.OutputStream;
|
|
import java.net.Socket;
|
|
import java.util.Arrays;
|
|
import java.util.concurrent.locks.Lock;
|
|
import java.util.concurrent.locks.ReentrantLock;
|
|
import java.util.function.Consumer;
|
|
|
|
import org.json.JSONObject;
|
|
import org.slf4j.Logger;
|
|
import org.slf4j.LoggerFactory;
|
|
|
|
import ctbrec.Config;
|
|
import ctbrec.OS;
|
|
import ctbrec.Settings.ProxyType;
|
|
import ctbrec.io.StreamRedirector;
|
|
|
|
public class ExternalBrowser implements AutoCloseable {
|
|
private static final Logger LOG = LoggerFactory.getLogger(ExternalBrowser.class);
|
|
private static final ExternalBrowser INSTANCE = new ExternalBrowser();
|
|
private Lock lock = new ReentrantLock();
|
|
|
|
private Process p;
|
|
private Consumer<String> messageListener;
|
|
private InputStream in;
|
|
private OutputStream out;
|
|
private Socket socket;
|
|
private Thread reader;
|
|
private volatile boolean stopped = true;
|
|
private volatile boolean browserReady = false;
|
|
private Object browserReadyLock = new Object();
|
|
|
|
public static ExternalBrowser getInstance() {
|
|
return INSTANCE;
|
|
}
|
|
|
|
public void run(JSONObject jsonConfig, Consumer<String> messageListener) throws InterruptedException, IOException {
|
|
LOG.debug("Running browser with config {}", jsonConfig);
|
|
lock.lock();
|
|
try {
|
|
stopped = false;
|
|
this.messageListener = messageListener;
|
|
|
|
addProxyConfig(jsonConfig.getJSONObject("config"));
|
|
|
|
File configDir = new File(Config.getInstance().getConfigDir(), "ctbrec-minimal-browser");
|
|
String[] cmdline = OS.getBrowserCommand(configDir.getCanonicalPath());
|
|
p = new ProcessBuilder(cmdline).start();
|
|
if (LOG.isTraceEnabled()) {
|
|
new Thread(new StreamRedirector(p.getInputStream(), System.out)).start();
|
|
new Thread(new StreamRedirector(p.getErrorStream(), System.err)).start();
|
|
} else {
|
|
new Thread(new StreamRedirector(p.getInputStream(), OutputStream.nullOutputStream())).start();
|
|
new Thread(new StreamRedirector(p.getErrorStream(), OutputStream.nullOutputStream())).start();
|
|
}
|
|
LOG.debug("Browser started: {}", Arrays.toString(cmdline));
|
|
|
|
connectToRemoteControlSocket();
|
|
while (!browserReady) {
|
|
synchronized (browserReadyLock) {
|
|
browserReadyLock.wait(100);
|
|
}
|
|
}
|
|
if(LOG.isTraceEnabled()) {
|
|
LOG.debug("Connected to remote control server. Sending config {}", jsonConfig);
|
|
} else {
|
|
LOG.debug("Connected to remote control server. Sending config");
|
|
}
|
|
out.write(jsonConfig.toString().getBytes(UTF_8));
|
|
out.write('\n');
|
|
out.flush();
|
|
|
|
LOG.debug("Waiting for browser to terminate");
|
|
p.waitFor();
|
|
int exitValue = p.exitValue();
|
|
p = null;
|
|
reader = null;
|
|
in = null;
|
|
out = null;
|
|
this.messageListener = null;
|
|
LOG.debug("Browser Process terminated with {}", exitValue);
|
|
} finally {
|
|
lock.unlock();
|
|
}
|
|
}
|
|
|
|
private void connectToRemoteControlSocket() throws IOException {
|
|
for (int i = 0; i < 20; i++) {
|
|
try {
|
|
socket = new Socket("localhost", 3202);
|
|
in = socket.getInputStream();
|
|
out = socket.getOutputStream();
|
|
reader = new Thread(this::readBrowserOutput);
|
|
reader.start();
|
|
LOG.debug("Connected to control socket");
|
|
return;
|
|
} catch (IOException e) {
|
|
if(i == 19) {
|
|
LOG.error("Connection to remote control socket failed", e);
|
|
throw e;
|
|
}
|
|
}
|
|
|
|
// wait a bit, so that the remote server socket can be opened by electron
|
|
try {
|
|
Thread.sleep(100);
|
|
} catch (InterruptedException e) {
|
|
Thread.currentThread().interrupt();
|
|
}
|
|
}
|
|
}
|
|
|
|
public void executeJavaScript(String javaScript) throws IOException {
|
|
//LOG.debug("Executing JS {}", javaScript);
|
|
JSONObject script = new JSONObject();
|
|
script.put("execute", javaScript);
|
|
out.write(script.toString().getBytes(UTF_8));
|
|
out.write('\n');
|
|
out.flush();
|
|
if(javaScript.equals("quit")) {
|
|
stopped = true;
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void close() throws IOException {
|
|
if(stopped) {
|
|
return;
|
|
}
|
|
stopped = true;
|
|
executeJavaScript("quit");
|
|
}
|
|
|
|
private void readBrowserOutput() {
|
|
LOG.debug("Browser output reader started");
|
|
try {
|
|
BufferedReader br = new BufferedReader(new InputStreamReader(in));
|
|
String line;
|
|
synchronized (browserReadyLock) {
|
|
browserReady = true;
|
|
browserReadyLock.notifyAll();
|
|
}
|
|
while( !Thread.interrupted() && (line = br.readLine()) != null ) {
|
|
LOG.debug("Browser output: {}", line);
|
|
if(!line.startsWith("{")) {
|
|
} else {
|
|
if(messageListener != null) {
|
|
messageListener.accept(line);
|
|
}
|
|
}
|
|
}
|
|
} catch (IOException e) {
|
|
if(!stopped) {
|
|
LOG.error("Couldn't read browser output", e);
|
|
}
|
|
} finally {
|
|
stopped = true;
|
|
synchronized (browserReadyLock) {
|
|
browserReady = true;
|
|
browserReadyLock.notifyAll();
|
|
}
|
|
}
|
|
}
|
|
|
|
private void addProxyConfig(JSONObject jsonConfig) {
|
|
ProxyType proxyType = Config.getInstance().getSettings().proxyType;
|
|
switch (proxyType) {
|
|
case HTTP:
|
|
JSONObject proxy = new JSONObject();
|
|
proxy.put("address",
|
|
"http=" + Config.getInstance().getSettings().proxyHost + ':' + Config.getInstance().getSettings().proxyPort
|
|
+ ";https=" + Config.getInstance().getSettings().proxyHost + ':' + Config.getInstance().getSettings().proxyPort);
|
|
if(Config.getInstance().getSettings().proxyUser != null && !Config.getInstance().getSettings().proxyUser.isEmpty()) {
|
|
String username = Config.getInstance().getSettings().proxyUser;
|
|
String password = Config.getInstance().getSettings().proxyPassword;
|
|
proxy.put("user", username);
|
|
proxy.put("password", password);
|
|
}
|
|
jsonConfig.put("proxy", proxy);
|
|
break;
|
|
case SOCKS4:
|
|
proxy = new JSONObject();
|
|
proxy.put("address", "socks4://" + Config.getInstance().getSettings().proxyHost + ':' + Config.getInstance().getSettings().proxyPort);
|
|
jsonConfig.put("proxy", proxy);
|
|
break;
|
|
case SOCKS5:
|
|
proxy = new JSONObject();
|
|
proxy.put("address", "socks5://" + Config.getInstance().getSettings().proxyHost + ':' + Config.getInstance().getSettings().proxyPort);
|
|
if(Config.getInstance().getSettings().proxyUser != null && !Config.getInstance().getSettings().proxyUser.isEmpty()) {
|
|
String username = Config.getInstance().getSettings().proxyUser;
|
|
String password = Config.getInstance().getSettings().proxyPassword;
|
|
proxy.put("user", username);
|
|
proxy.put("password", password);
|
|
}
|
|
jsonConfig.put("proxy", proxy);
|
|
break;
|
|
case DIRECT:
|
|
default:
|
|
// nothing to do here
|
|
break;
|
|
}
|
|
}
|
|
}
|