package edu.washington.cs.oneswarm.test.integration.oop;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketTimeoutException;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.logging.Logger;
import org.junit.Assert;
/**
* A OneSwarm experimental coordinator that manages a single LocalOneSwarm
* instance only.
*/
public class LocalOneSwarmCoordinator extends Thread {
private static Logger logger = Logger.getLogger(LocalOneSwarmCoordinator.class.getName());
/** The server socket on which to listen for connections. */
ServerSocket serverSocket;
/** Whether this thread is terminating. */
boolean done = false;
/** The local OneSwarm instance we are coordinating. */
private final LocalOneSwarm instance;
/** The set of pending commands for this instance. */
private final List<String> pendingCommands = Collections
.synchronizedList(new ArrayList<String>());
/** The number of online friends for this instance. */
int onlineFriendCount;
/** The TCP listen port of this client. */
int port;
/** The key of this client. */
String encodedPublicKey;
/** Is the friend connector of this client available? */
boolean friendConnectorAvailable = false;
public LocalOneSwarmCoordinator(LocalOneSwarm instance) throws IOException {
this.instance = instance;
// Bind to any free port. Listen on localhost only.
serverSocket = new ServerSocket(0, 10, InetAddress.getByName("127.0.0.1"));
setDaemon(true);
setName("LocalOneSwarmCoordinator. Instance: " + instance.getLabel());
}
/** Returns the bound local port of this coordinator. */
public int getServerPort() {
return serverSocket.getLocalPort();
}
/** Marks this thread for termination. */
public void setDone() {
done = true;
}
/** Adds a pending command. */
public void addCommand(String command) {
pendingCommands.add(command);
}
/** Adds several pending commands. */
public void addCommands(String[] commands) {
synchronized (pendingCommands) {
for (String cmd : commands) {
pendingCommands.add(cmd);
}
}
}
@Override
public void run() {
while (!done) {
Socket socket = null;
try {
// Use a timeout so we can detect and cleanup expired threads.
serverSocket.setSoTimeout(1000);
// Since this is 1-1 with an instance, we only need to deal with
// one connection
// at a time.
try {
socket = serverSocket.accept();
socket.setSoTimeout(1000);
} catch (SocketTimeoutException e) {
continue;
}
} catch (IOException e) {
e.printStackTrace();
Assert.fail();
}
try {
instance.coordinatorReceivedHeartbeat();
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
InputStream in = socket.getInputStream();
byte[] b = new byte[4096];
while (true) {
try {
int c = in.read(b);
if (c == -1) {
break;
}
bytes.write(b, 0, c);
} catch (SocketTimeoutException e) {
break;
}
}
logger.finer("Read " + bytes.size() + " bytes from client");
if (bytes.size() == 0) {
continue;
}
BufferedReader reader = new BufferedReader(new InputStreamReader(
new ByteArrayInputStream(bytes.toByteArray())));
String lastLine = "";
while (true) {
String line = reader.readLine();
if (line == null) {
break;
}
if (line.contains("?port=")) {
port = Integer.parseInt(line.split("=")[1].split("\\s+")[0]);
}
lastLine = line;
}
// The last line contains all the form parameters, if included.
logger.fine("Got POST from instance " + instance.toString() + " / " + lastLine);
String[] kvPairs = lastLine.split("&");
for (String kv : kvPairs) {
String[] toks = kv.split("=");
if (toks[0].equals("key")) {
encodedPublicKey = URLDecoder.decode(toks[1], "UTF-8");
} else if (toks[0].equals("onlinefriends")) {
onlineFriendCount = Integer.parseInt(toks[1]);
} else if (toks[0].equals("friendConnectorAvailable")) {
friendConnectorAvailable = Boolean.parseBoolean(toks[1]);
}
// TODO(willscott): Make this extensible, so tests can
// retrieve arbitrary responses.
}
PrintStream out = new PrintStream(socket.getOutputStream());
out.print("HTTP/1.1 200 OK\r\n\r\n");
out.print("ok\r\n");
synchronized (pendingCommands) {
for (String cmd : pendingCommands) {
out.print(cmd + "\r\n");
}
}
out.flush();
logger.fine("Got ping from local instance: " + socket);
pendingCommands.clear();
} catch (IOException e) {
logger.warning("LocalOneSwarm coordinator stopped: " + e.toString());
e.printStackTrace();
Assert.fail();
} finally {
try {
socket.close();
} catch (Exception e) {
}
}
}
}
public int getOnlineFriendCount() {
return onlineFriendCount;
}
public String getEncodedPublicKey() {
return encodedPublicKey;
}
public int getPort() {
return port;
}
public boolean isFriendConnectorAvailable() {
return friendConnectorAvailable;
}
public List<String> getPendingCommands() {
return new ArrayList<String>(pendingCommands);
}
}