package the8472.mldht.cli;
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.StandardSocketOptions;
import java.nio.ByteBuffer;
import java.nio.channels.SelectableChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.charset.StandardCharsets;
import java.nio.file.Paths;
import java.util.Collection;
import java.util.Deque;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentLinkedDeque;
import lbms.plugins.mldht.kad.DHT;
import lbms.plugins.mldht.utils.NIOConnectionManager;
import lbms.plugins.mldht.utils.Selectable;
import the8472.bencode.BDecoder;
import the8472.mldht.Component;
import the8472.utils.ConfigReader;
public class Server implements Component {
public static int SERVER_PORT = 33348;
NIOConnectionManager conMan = new NIOConnectionManager("CLI-server");
ServerSocketChannel acceptor;
Collection<DHT> dhts;
public Server() {
// TODO Auto-generated constructor stub
}
@Override
public void start(Collection<DHT> dhts, ConfigReader config) {
this.dhts = dhts;
try {
acceptor = ServerSocketChannel.open();
acceptor.configureBlocking(false);
acceptor.setOption(StandardSocketOptions.SO_REUSEADDR, true);
acceptor.bind(new InetSocketAddress(InetAddress.getLoopbackAddress(), SERVER_PORT));
conMan.register(new Selectable() {
@Override
public void selectionEvent(SelectionKey key) throws IOException {
if(key.isAcceptable())
accept();
}
@Override
public void registrationEvent(NIOConnectionManager manager, SelectionKey key) throws IOException {
}
@Override
public SelectableChannel getChannel() {
return acceptor;
}
@Override
public void doStateChecks(long now) throws IOException {
}
@Override
public int calcInterestOps() {
return SelectionKey.OP_ACCEPT;
}
});
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
void accept() {
try {
SocketChannel chan = acceptor.accept();
chan.configureBlocking(false);
chan.socket().setSoTimeout(0);
chan.setOption(StandardSocketOptions.SO_KEEPALIVE, true);
conMan.register(new Selectable() {
// 4 bytes message length
ByteBuffer header = ByteBuffer.allocate(4);
ByteBuffer payload = null;
ByteBuffer currentReadTarget = header;
@Override
public void selectionEvent(SelectionKey key) throws IOException {
if(!chan.isOpen()) {
conMan.deRegister(this);
return;
}
if(key.isValid() && key.isReadable())
read();
if(key.isValid() && key.isWritable())
write();
}
void read() throws IOException {
try {
int read = chan.read(currentReadTarget);
// end of stream
if(read == -1) {
header = null;
conMan.interestOpsChanged(this);
}
if(currentReadTarget.remaining() == 0) {
currentReadTarget.flip();
if(currentReadTarget == header) {
payload = ByteBuffer.allocate(header.getInt(0));
currentReadTarget = payload;
} else {
process(payload);
payload = null;
header.clear();
currentReadTarget = header;
}
}
} catch (IOException e) {
chan.close();
}
}
Deque<ByteBuffer> writes = new ConcurrentLinkedDeque<>();
void process(ByteBuffer buf) {
BDecoder decoder = new BDecoder();
Map<String, Object> map = decoder.decode(buf);
List<byte[]> args = (List<byte[]>) map.get("arguments");
CommandProcessor processor = CommandProcessor.from(args, (b) -> {
ByteBuffer h = ByteBuffer.allocate(4);
h.putInt(0, b.remaining());
synchronized (writes) {
writes.add(h);
writes.add(b);
}
conMan.interestOpsChanged(this);
}, dhts);
processor.currentWorkDir = Paths.get(new String((byte[])map.get("cwd"), StandardCharsets.UTF_8));
processor.active = chan::isOpen;
try {
processor.process();
} catch(Exception e) {
processor.handleException(e);
}
}
void write() throws IOException {
ByteBuffer toWrite;
while((toWrite = writes.pollFirst()) != null) {
try {
chan.write(toWrite);
} catch (IOException e) {
chan.close();
return;
}
if(toWrite.remaining() > 0) {
writes.addFirst(toWrite);
break;
}
}
if(writes.isEmpty())
conMan.interestOpsChanged(this);
}
@Override
public void registrationEvent(NIOConnectionManager manager, SelectionKey key) throws IOException {
// TODO Auto-generated method stub
}
@Override
public SelectableChannel getChannel() {
return chan;
}
@Override
public void doStateChecks(long now) throws IOException {
// TODO Auto-generated method stub
}
@Override
public int calcInterestOps() {
int ops = 0;
if(header != null)
ops = SelectionKey.OP_READ;
if(writes.peek() != null)
ops |= SelectionKey.OP_WRITE;
return ops;
}
});
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
@Override
public void stop() {
try {
acceptor.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}