mirror of
https://github.com/peterosterlund2/droidfish.git
synced 2024-11-23 03:21:20 +01:00
Create EngineServer Java program
This program can be run on a PC and works as an engine server that DroidFish can connect to using its "network engine" feature.
This commit is contained in:
parent
490bacfce0
commit
fa9cf93245
1
EngineServer/.gitignore
vendored
Normal file
1
EngineServer/.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
/build
|
15
EngineServer/build.gradle
Normal file
15
EngineServer/build.gradle
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
apply plugin: 'java-library'
|
||||||
|
|
||||||
|
jar {
|
||||||
|
manifest {
|
||||||
|
attributes "Main-Class": "org.petero.engineserver.EngineServer"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||||
|
testImplementation 'junit:junit:4.12'
|
||||||
|
}
|
||||||
|
|
||||||
|
sourceCompatibility = "8"
|
||||||
|
targetCompatibility = "8"
|
|
@ -0,0 +1,35 @@
|
||||||
|
/*
|
||||||
|
EngineServer - Network engine server for DroidFish
|
||||||
|
Copyright (C) 2019 Peter Österlund, peterosterlund2@gmail.com
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program 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 General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.petero.engineserver;
|
||||||
|
|
||||||
|
|
||||||
|
/** Defines settings for one network engine. */
|
||||||
|
public class EngineConfig {
|
||||||
|
boolean enabled;
|
||||||
|
int port;
|
||||||
|
String filename;
|
||||||
|
String arguments;
|
||||||
|
|
||||||
|
public EngineConfig(boolean e, int p, String fn, String args) {
|
||||||
|
enabled = e;
|
||||||
|
port = p;
|
||||||
|
filename = fn;
|
||||||
|
arguments = args;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,134 @@
|
||||||
|
/*
|
||||||
|
EngineServer - Network engine server for DroidFish
|
||||||
|
Copyright (C) 2019 Peter Österlund, peterosterlund2@gmail.com
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program 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 General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.petero.engineserver;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.util.Properties;
|
||||||
|
|
||||||
|
|
||||||
|
/** Manages starting and stopping PortListeners. */
|
||||||
|
public class EngineServer implements ErrorHandler {
|
||||||
|
private EngineConfig[] configs;
|
||||||
|
private PortListener[] portListeners;
|
||||||
|
private MainWindow window;
|
||||||
|
|
||||||
|
public EngineServer(int numEngines) {
|
||||||
|
configs = new EngineConfig[numEngines];
|
||||||
|
portListeners = new PortListener[numEngines];
|
||||||
|
for (int i = 0; i < numEngines; i++) {
|
||||||
|
configs[i] = new EngineConfig(false, 4567 + i, "", "");
|
||||||
|
}
|
||||||
|
readConfig();
|
||||||
|
for (int i = 0; i < numEngines; i++)
|
||||||
|
configChanged(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
private File getConfigFile() {
|
||||||
|
String home = System.getProperty("user.home");
|
||||||
|
return new File(home, ".engineServer.ini");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void readConfig() {
|
||||||
|
try {
|
||||||
|
Properties prop = new Properties();
|
||||||
|
InputStream is = new FileInputStream(getConfigFile());
|
||||||
|
prop.load(is);
|
||||||
|
for (int i = 0; i < configs.length; i++) {
|
||||||
|
boolean enabled = Boolean.valueOf(prop.getProperty("enabled" + i, "false"));
|
||||||
|
String defPort = Integer.toString(4567 + i);
|
||||||
|
int port = Integer.valueOf(prop.getProperty("port" + i, defPort));
|
||||||
|
String filename = prop.getProperty("filename" + i, "");
|
||||||
|
String arguments = prop.getProperty("arguments" + i, "");
|
||||||
|
configs[i] = new EngineConfig(enabled, port, filename, arguments);
|
||||||
|
}
|
||||||
|
} catch (IOException ignore) {
|
||||||
|
} catch (NumberFormatException ignore) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void writeConfig() {
|
||||||
|
Properties prop = new Properties();
|
||||||
|
for (int i = 0; i < configs.length; i++) {
|
||||||
|
EngineConfig config = configs[i];
|
||||||
|
String enabled = config.enabled ? "true" : "false";
|
||||||
|
String port = Integer.toString(config.port);
|
||||||
|
String filename = config.filename;
|
||||||
|
String arguments = config.arguments;
|
||||||
|
prop.setProperty("enabled" + i, enabled);
|
||||||
|
prop.setProperty("port" + i, port);
|
||||||
|
prop.setProperty("filename" + i, filename);
|
||||||
|
prop.setProperty("arguments" + i, arguments);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
OutputStream os = new FileOutputStream(getConfigFile());
|
||||||
|
prop.store(os, "Created by EngineServer for DroidFish");
|
||||||
|
} catch (IOException ignore) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void run() {
|
||||||
|
window = new MainWindow(this, configs);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void configChanged(int engineNo) {
|
||||||
|
EngineConfig config = configs[engineNo];
|
||||||
|
if (portListeners[engineNo] != null) {
|
||||||
|
portListeners[engineNo].shutdown();
|
||||||
|
portListeners[engineNo] = null;
|
||||||
|
}
|
||||||
|
if (config.enabled)
|
||||||
|
portListeners[engineNo] = new PortListener(config, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void shutdown() {
|
||||||
|
writeConfig();
|
||||||
|
for (PortListener pl : portListeners)
|
||||||
|
if (pl != null)
|
||||||
|
pl.shutdown();
|
||||||
|
System.exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
int numEngines = 8;
|
||||||
|
if (args.length > 0) {
|
||||||
|
try {
|
||||||
|
numEngines = Integer.valueOf(args[0]);
|
||||||
|
numEngines = Math.max(1, numEngines);
|
||||||
|
numEngines = Math.min(20, numEngines);
|
||||||
|
} catch (NumberFormatException ignore) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EngineServer server = new EngineServer(numEngines);
|
||||||
|
server.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void reportError(String title, String message) {
|
||||||
|
if (window != null) {
|
||||||
|
window.reportError(title, message);
|
||||||
|
} else {
|
||||||
|
System.err.printf("%s\n%s\n", title, message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
/*
|
||||||
|
EngineServer - Network engine server for DroidFish
|
||||||
|
Copyright (C) 2019 Peter Österlund, peterosterlund2@gmail.com
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program 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 General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.petero.engineserver;
|
||||||
|
|
||||||
|
|
||||||
|
public interface ErrorHandler {
|
||||||
|
void reportError(String title, String message);
|
||||||
|
}
|
|
@ -0,0 +1,283 @@
|
||||||
|
/*
|
||||||
|
EngineServer - Network engine server for DroidFish
|
||||||
|
Copyright (C) 2019 Peter Österlund, peterosterlund2@gmail.com
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program 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 General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.petero.engineserver;
|
||||||
|
|
||||||
|
import java.awt.Container;
|
||||||
|
import java.awt.GridBagConstraints;
|
||||||
|
import java.awt.GridBagLayout;
|
||||||
|
import java.awt.Insets;
|
||||||
|
import java.awt.event.ActionEvent;
|
||||||
|
import java.awt.event.ActionListener;
|
||||||
|
import java.awt.event.FocusAdapter;
|
||||||
|
import java.awt.event.FocusEvent;
|
||||||
|
import java.awt.event.WindowAdapter;
|
||||||
|
import java.awt.event.WindowEvent;
|
||||||
|
import java.io.File;
|
||||||
|
|
||||||
|
import javax.swing.JButton;
|
||||||
|
import javax.swing.JCheckBox;
|
||||||
|
import javax.swing.JDialog;
|
||||||
|
import javax.swing.JFileChooser;
|
||||||
|
import javax.swing.JFrame;
|
||||||
|
import javax.swing.JLabel;
|
||||||
|
import javax.swing.JOptionPane;
|
||||||
|
import javax.swing.JTextField;
|
||||||
|
import javax.swing.SwingUtilities;
|
||||||
|
|
||||||
|
|
||||||
|
/** Displays the GUI to configure engine settings. */
|
||||||
|
public class MainWindow {
|
||||||
|
private JFrame frame;
|
||||||
|
private JCheckBox[] enabled;
|
||||||
|
private JTextField[] port;
|
||||||
|
private JTextField[] filename;
|
||||||
|
private JTextField[] arguments;
|
||||||
|
private JButton[] browse;
|
||||||
|
|
||||||
|
private EngineServer server;
|
||||||
|
private EngineConfig[] configs;
|
||||||
|
|
||||||
|
public MainWindow(EngineServer server, EngineConfig[] configs) {
|
||||||
|
this.server = server;
|
||||||
|
this.configs = configs;
|
||||||
|
SwingUtilities.invokeLater(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
initUI();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initUI() {
|
||||||
|
final int numEngines = configs.length;
|
||||||
|
|
||||||
|
frame = new JFrame();
|
||||||
|
enabled = new JCheckBox[numEngines];
|
||||||
|
port = new JTextField[numEngines];
|
||||||
|
filename = new JTextField[numEngines];
|
||||||
|
arguments = new JTextField[numEngines];
|
||||||
|
browse = new JButton[numEngines];
|
||||||
|
|
||||||
|
Container pane = frame.getContentPane();
|
||||||
|
frame.setTitle("Chess Engine Server");
|
||||||
|
frame.addWindowListener(new WindowAdapter() {
|
||||||
|
@Override
|
||||||
|
public void windowClosing(WindowEvent event) {
|
||||||
|
server.shutdown();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
GridBagLayout layout = new GridBagLayout();
|
||||||
|
pane.setLayout(layout);
|
||||||
|
int row = 0;
|
||||||
|
|
||||||
|
GridBagConstraints constr = new GridBagConstraints();
|
||||||
|
Insets inset = new Insets(0, 0, 5, 5);
|
||||||
|
constr.insets = inset;
|
||||||
|
constr.gridx = 0;
|
||||||
|
constr.gridy = row;
|
||||||
|
pane.add(new JLabel("Enabled"), constr);
|
||||||
|
|
||||||
|
constr = new GridBagConstraints();
|
||||||
|
constr.insets = inset;
|
||||||
|
constr.gridx = 1;
|
||||||
|
constr.gridy = row;
|
||||||
|
pane.add(new JLabel("Port"), constr);
|
||||||
|
|
||||||
|
constr = new GridBagConstraints();
|
||||||
|
constr.insets = inset;
|
||||||
|
constr.gridx = 2;
|
||||||
|
constr.gridy = row;
|
||||||
|
pane.add(new JLabel("Program and arguments"), constr);
|
||||||
|
|
||||||
|
row++;
|
||||||
|
|
||||||
|
for (int r = 0; r < numEngines; r++) {
|
||||||
|
final int engineNo = r;
|
||||||
|
final EngineConfig config = configs[r];
|
||||||
|
enabled[r] = new JCheckBox("");
|
||||||
|
constr = new GridBagConstraints();
|
||||||
|
constr.insets = inset;
|
||||||
|
constr.gridx = 0;
|
||||||
|
constr.gridy = row;
|
||||||
|
pane.add(enabled[r], constr);
|
||||||
|
enabled[r].setSelected(config.enabled);
|
||||||
|
enabled[r].addActionListener(new ActionListener() {
|
||||||
|
@Override
|
||||||
|
public void actionPerformed(ActionEvent event) {
|
||||||
|
enabledChanged(engineNo);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
port[r] = new JTextField();
|
||||||
|
constr = new GridBagConstraints();
|
||||||
|
constr.anchor = GridBagConstraints.WEST;
|
||||||
|
constr.insets = inset;
|
||||||
|
constr.gridx = 1;
|
||||||
|
constr.gridy = row;
|
||||||
|
pane.add(port[r], constr);
|
||||||
|
port[r].setColumns(5);
|
||||||
|
port[r].setText(Integer.toString(config.port));
|
||||||
|
port[r].addActionListener(new ActionListener() {
|
||||||
|
@Override
|
||||||
|
public void actionPerformed(ActionEvent event) {
|
||||||
|
portChanged(engineNo);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
port[r].addFocusListener(new FocusAdapter() {
|
||||||
|
@Override
|
||||||
|
public void focusLost(FocusEvent focusEvent) {
|
||||||
|
portChanged(engineNo);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
filename[r] = new JTextField();
|
||||||
|
constr = new GridBagConstraints();
|
||||||
|
constr.insets = inset;
|
||||||
|
constr.fill = GridBagConstraints.HORIZONTAL;
|
||||||
|
constr.gridx = 2;
|
||||||
|
constr.gridy = row;
|
||||||
|
pane.add(filename[r], constr);
|
||||||
|
filename[r].setColumns(40);
|
||||||
|
filename[r].setText(config.filename);
|
||||||
|
filename[r].addActionListener(new ActionListener() {
|
||||||
|
@Override
|
||||||
|
public void actionPerformed(ActionEvent event) {
|
||||||
|
filenameChanged(engineNo);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
filename[r].addFocusListener(new FocusAdapter() {
|
||||||
|
@Override
|
||||||
|
public void focusLost(FocusEvent focusEvent) {
|
||||||
|
filenameChanged(engineNo);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
arguments[r] = new JTextField();
|
||||||
|
constr = new GridBagConstraints();
|
||||||
|
constr.insets = inset;
|
||||||
|
constr.fill = GridBagConstraints.HORIZONTAL;
|
||||||
|
constr.gridx = 2;
|
||||||
|
constr.gridy = row + 1;
|
||||||
|
pane.add(arguments[r], constr);
|
||||||
|
arguments[r].setColumns(40);
|
||||||
|
arguments[r].setText(config.arguments);
|
||||||
|
arguments[r].addActionListener(new ActionListener() {
|
||||||
|
@Override
|
||||||
|
public void actionPerformed(ActionEvent event) {
|
||||||
|
argumentsChanged(engineNo);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
arguments[r].addFocusListener(new FocusAdapter() {
|
||||||
|
@Override
|
||||||
|
public void focusLost(FocusEvent focusEvent) {
|
||||||
|
argumentsChanged(engineNo);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
browse[r] = new JButton("Browse");
|
||||||
|
constr = new GridBagConstraints();
|
||||||
|
constr.anchor = GridBagConstraints.NORTH;
|
||||||
|
constr.insets = inset;
|
||||||
|
constr.gridx = 3;
|
||||||
|
constr.gridy = row;
|
||||||
|
constr.gridheight = 2;
|
||||||
|
pane.add(browse[r], constr);
|
||||||
|
browse[r].addActionListener(new ActionListener() {
|
||||||
|
@Override
|
||||||
|
public void actionPerformed(ActionEvent event) {
|
||||||
|
browseFile(engineNo);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
row += 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
frame.pack();
|
||||||
|
frame.setLocationRelativeTo(null);
|
||||||
|
frame.setVisible(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void enabledChanged(int engineNo) {
|
||||||
|
EngineConfig config = configs[engineNo];
|
||||||
|
boolean e = enabled[engineNo].isSelected();
|
||||||
|
if (e != config.enabled) {
|
||||||
|
config.enabled = e;
|
||||||
|
server.configChanged(engineNo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void portChanged(int engineNo) {
|
||||||
|
EngineConfig config = configs[engineNo];
|
||||||
|
try {
|
||||||
|
int p = Integer.valueOf(port[engineNo].getText());
|
||||||
|
if (p >= 1024 && p < 65536 && p != config.port) {
|
||||||
|
config.port = p;
|
||||||
|
server.configChanged(engineNo);
|
||||||
|
}
|
||||||
|
} catch (NumberFormatException ignore) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void filenameChanged(int engineNo) {
|
||||||
|
EngineConfig config = configs[engineNo];
|
||||||
|
String fn = filename[engineNo].getText().trim();
|
||||||
|
if (!fn.equals(config.filename)) {
|
||||||
|
config.filename = fn;
|
||||||
|
server.configChanged(engineNo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void browseFile(int engineNo) {
|
||||||
|
String fn = filename[engineNo].getText();
|
||||||
|
JFileChooser chooser = new JFileChooser();
|
||||||
|
chooser.setDialogTitle("Select chess engine");
|
||||||
|
chooser.setSelectedFile(new File(fn));
|
||||||
|
if (chooser.showOpenDialog(frame) == JFileChooser.APPROVE_OPTION)
|
||||||
|
fn = chooser.getSelectedFile().getAbsolutePath();
|
||||||
|
filename[engineNo].setText(fn);
|
||||||
|
filenameChanged(engineNo);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void argumentsChanged(int engineNo) {
|
||||||
|
EngineConfig config = configs[engineNo];
|
||||||
|
String args = arguments[engineNo].getText().trim();
|
||||||
|
if (!args.equals(config.arguments)) {
|
||||||
|
config.arguments = args;
|
||||||
|
server.configChanged(engineNo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void reportError(String title, String message) {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
int lineLen = 100;
|
||||||
|
while (message.length() > lineLen) {
|
||||||
|
sb.append(message.substring(0, lineLen));
|
||||||
|
sb.append('\n');
|
||||||
|
message = message.substring(lineLen);
|
||||||
|
}
|
||||||
|
sb.append(message);
|
||||||
|
SwingUtilities.invokeLater(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
JOptionPane.showMessageDialog(frame, sb.toString(), title, JOptionPane.ERROR_MESSAGE);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,201 @@
|
||||||
|
/*
|
||||||
|
EngineServer - Network engine server for DroidFish
|
||||||
|
Copyright (C) 2019 Peter Österlund, peterosterlund2@gmail.com
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program 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 General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.petero.engineserver;
|
||||||
|
|
||||||
|
import java.io.Closeable;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
import java.net.ServerSocket;
|
||||||
|
import java.net.Socket;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
|
||||||
|
|
||||||
|
/** Listens to a TCP port and connects an engine process to a TCP socket. */
|
||||||
|
public class PortListener {
|
||||||
|
private EngineConfig config;
|
||||||
|
private ErrorHandler errorHandler;
|
||||||
|
|
||||||
|
private Thread thread;
|
||||||
|
private ServerSocket serverSocket;
|
||||||
|
private Socket clientSocket;
|
||||||
|
private Process proc;
|
||||||
|
private volatile boolean shutDownFlag = false;
|
||||||
|
|
||||||
|
public PortListener(EngineConfig config, ErrorHandler errorHandler) {
|
||||||
|
this.config = config;
|
||||||
|
this.errorHandler = errorHandler;
|
||||||
|
|
||||||
|
thread = new Thread() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
try {
|
||||||
|
mainLoop();
|
||||||
|
} catch (InterruptedException ex) {
|
||||||
|
if (!shutDownFlag)
|
||||||
|
reportError("Background thread interrupted", ex);
|
||||||
|
} catch (IOException ex) {
|
||||||
|
if (!shutDownFlag)
|
||||||
|
reportError("IO error in background thread", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
thread.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void mainLoop() throws IOException, InterruptedException {
|
||||||
|
try (ServerSocket serverSocket = new ServerSocket()) {
|
||||||
|
serverSocket.setReuseAddress(true);
|
||||||
|
serverSocket.bind(new InetSocketAddress(config.port));
|
||||||
|
|
||||||
|
this.serverSocket = serverSocket;
|
||||||
|
while (!shutDownFlag) {
|
||||||
|
try (Socket clientSocket = serverSocket.accept()) {
|
||||||
|
this.clientSocket = clientSocket;
|
||||||
|
|
||||||
|
ProcessBuilder builder = new ProcessBuilder();
|
||||||
|
ArrayList<String> args = new ArrayList<>();
|
||||||
|
args.add(config.filename);
|
||||||
|
addArguments(args, config.arguments);
|
||||||
|
builder.command(args);
|
||||||
|
File dir = new File(config.filename).getParentFile();
|
||||||
|
if (dir != null)
|
||||||
|
builder.directory(dir);
|
||||||
|
builder.redirectError(ProcessBuilder.Redirect.INHERIT);
|
||||||
|
|
||||||
|
Process proc = builder.start();
|
||||||
|
this.proc = proc;
|
||||||
|
Thread t1 = forwardIO(proc.getInputStream(), clientSocket.getOutputStream());
|
||||||
|
Thread t2 = forwardIO(clientSocket.getInputStream(), proc.getOutputStream());
|
||||||
|
try {
|
||||||
|
int exitCode = proc.waitFor();
|
||||||
|
if (exitCode != 0) {
|
||||||
|
// errorHandler.reportError("Engine error",
|
||||||
|
// "Engine terminated with status " + exitCode);
|
||||||
|
}
|
||||||
|
} catch (InterruptedException ex) {
|
||||||
|
proc.getOutputStream().close();
|
||||||
|
proc.destroyForcibly();
|
||||||
|
} finally {
|
||||||
|
close(clientSocket);
|
||||||
|
t1.join();
|
||||||
|
t2.join();
|
||||||
|
proc.waitFor();
|
||||||
|
this.proc = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addArguments(ArrayList<String> cmdList, String argString) {
|
||||||
|
boolean inQuote = false;
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
int len = argString.length();
|
||||||
|
for (int i = 0; i < len; i++) {
|
||||||
|
char c = argString.charAt(i);
|
||||||
|
switch (c) {
|
||||||
|
case '"':
|
||||||
|
inQuote = !inQuote;
|
||||||
|
if (!inQuote) {
|
||||||
|
cmdList.add(sb.toString());
|
||||||
|
sb = new StringBuilder();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case '\\':
|
||||||
|
if (i < len - 1) {
|
||||||
|
sb.append(argString.charAt(i + 1));
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ' ':
|
||||||
|
case '\t':
|
||||||
|
if (!inQuote) {
|
||||||
|
if (!sb.toString().isEmpty()) {
|
||||||
|
cmdList.add(sb.toString());
|
||||||
|
sb = new StringBuilder();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
sb.append(c);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!sb.toString().isEmpty())
|
||||||
|
cmdList.add(sb.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
private Thread forwardIO(InputStream is, OutputStream os) {
|
||||||
|
Thread t = new Thread() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
try {
|
||||||
|
while (true) {
|
||||||
|
byte[] buffer = new byte[4096];
|
||||||
|
int len = is.read(buffer);
|
||||||
|
if (len < 0)
|
||||||
|
break;
|
||||||
|
os.write(buffer, 0, len);
|
||||||
|
os.flush();
|
||||||
|
}
|
||||||
|
} catch (IOException ignore) {
|
||||||
|
}
|
||||||
|
close(is);
|
||||||
|
close(os);
|
||||||
|
Process p = proc;
|
||||||
|
if (p != null)
|
||||||
|
p.destroyForcibly();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
t.start();
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void shutdown() {
|
||||||
|
shutDownFlag = true;
|
||||||
|
thread.interrupt();
|
||||||
|
ServerSocket ss = serverSocket;
|
||||||
|
if (ss != null) {
|
||||||
|
close(ss);
|
||||||
|
}
|
||||||
|
Socket s = clientSocket;
|
||||||
|
if (s != null) {
|
||||||
|
close(s);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
thread.join();
|
||||||
|
} catch (InterruptedException ex) {
|
||||||
|
reportError("Failed to shutdown background thread", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void close(Closeable closeable) {
|
||||||
|
try {
|
||||||
|
closeable.close();
|
||||||
|
} catch (IOException ignore) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void reportError(String errMsg, Exception ex) {
|
||||||
|
errorHandler.reportError(errMsg, ex.getMessage());
|
||||||
|
}
|
||||||
|
}
|
|
@ -1 +1 @@
|
||||||
include ':DroidFishApp', ':CuckooChessEngine', ':CuckooChessApp', ':CuckooChess'
|
include ':DroidFishApp', ':CuckooChessEngine', ':CuckooChessApp', ':CuckooChess', ':EngineServer'
|
||||||
|
|
Loading…
Reference in New Issue
Block a user