package the8472.mldht.indexing;
import static the8472.bencode.Utils.buf2str;
import static the8472.bencode.Utils.hex2ary;
import static the8472.bencode.Utils.str2buf;
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
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.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.atomic.AtomicInteger;
import lbms.plugins.mldht.kad.DHT;
import lbms.plugins.mldht.kad.Key;
import lbms.plugins.mldht.kad.ScrapeResponseHandler;
import lbms.plugins.mldht.kad.tasks.PeerLookupTask;
import lbms.plugins.mldht.utils.NIOConnectionManager;
import lbms.plugins.mldht.utils.Selectable;
import the8472.mldht.Component;
import the8472.utils.ConfigReader;
public class ActiveLookupProvider implements Component {
Collection<DHT> dhts;
NIOConnectionManager manager;
@Override
public void start(Collection<DHT> dhts, ConfigReader config) {
this.dhts = dhts;
manager = new NIOConnectionManager("active-lookups");
try {
manager.register(new Server());
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
@Override
public void stop() {
// TODO Auto-generated method stub
}
void lookupRequested(Connection c, byte[] infoHash) {
List<PeerLookupTask> tasks = new ArrayList<PeerLookupTask>();
Key k = new Key(infoHash);
AtomicInteger counter = new AtomicInteger();
ScrapeResponseHandler scrape = new ScrapeResponseHandler();
for(DHT d : dhts) {
if(!d.isRunning())
continue;
PeerLookupTask t = d.createPeerLookup(infoHash);
if(!d.getTaskManager().canStartTask(t))
return;
counter.incrementAndGet();
t.setFastTerminate(false);
t.setLowPriority(true);
t.setScrapeHandler(scrape);
t.addListener(x -> {
if(counter.decrementAndGet() <= 0) {
lookupDone(c, k, scrape);
};
});
tasks.add(t);
}
c.send(str2buf("starting\t"+k.toString(false)+'\n'));
tasks.forEach(t -> t.getRPC().getDHT().getTaskManager().addTask(t));
}
void lookupDone(Connection c, Key k, ScrapeResponseHandler h) {
h.process();
c.send(str2buf("done\t"+k.toString(false)+"\tscrapeSeeds:"+h.getScrapedSeeds()+"\tscrapePeers:"+h.getScrapedPeers()+"\tdirect:"+h.getDirectResultCount()+'\n'));
}
class Connection implements Selectable {
SocketChannel chan;
volatile boolean writePending = false;
Queue<ByteBuffer> toWrite = new ConcurrentLinkedQueue<>();
public void send(ByteBuffer b) {
//if(toWrite.isEmpty()) {
writePending = true;
manager.interestOpsChanged(this);
//}
toWrite.add(b);
}
public Connection(SocketChannel chan) throws IOException {
this.chan = chan;
chan.configureBlocking(false);
manager.register(this);
}
@Override
public SelectableChannel getChannel() {
return chan;
}
@Override
public void registrationEvent(NIOConnectionManager manager, SelectionKey key) throws IOException {
// TODO Auto-generated method stub
}
@Override
public void selectionEvent(SelectionKey key) throws IOException {
if(key.isValid() && key.isWritable())
write();
if(key.isValid() && key.isReadable())
read();
}
ByteBuffer readBuffer = ByteBuffer.allocate(1024);
void read() throws IOException {
while(true) {
int bytes = chan.read(readBuffer);
if(bytes < 0) {
chan.close();
return;
}
if(bytes == 0)
break;
readBuffer.flip();
processBuffer(readBuffer);
readBuffer.compact();
}
}
void processBuffer(ByteBuffer buf) {
// scan for newline
ByteBuffer line = buf.slice();
int i = 0;
buf.mark();
while(buf.remaining() > 0) {
i++;
if(buf.get() == '\n') {
buf.mark();
line.limit(i-1);
line(line);
i = 0;
line = buf.slice();
}
}
buf.reset();
}
void line(ByteBuffer buf) {
if(buf.remaining() != 40)
return;
String hex = buf2str(buf);
lookupRequested(this, hex2ary(hex));
//Key key = new Key(hex2ary(hex));
}
ByteBuffer writeBuffer;
void write() throws IOException {
try {
while(true) {
if(writeBuffer == null || writeBuffer.remaining() == 0)
writeBuffer = toWrite.poll();
if(writeBuffer == null) {
writePending = false;
manager.interestOpsChanged(this);
return;
}
if(chan.write(writeBuffer) == 0)
break;
}
} catch (IOException e) {
chan.close();;
}
}
@Override
public void doStateChecks(long now) throws IOException {
// TODO Auto-generated method stub
}
@Override
public int calcInterestOps() {
int ops = SelectionKey.OP_READ;
if(writePending)
ops |= SelectionKey.OP_WRITE;
return ops;
}
}
class Server implements Selectable {
ServerSocketChannel chan;
public Server() throws IOException {
chan = ServerSocketChannel.open();
chan.configureBlocking(false);
chan.bind(new InetSocketAddress(InetAddress.getByAddress(new byte[16]), 36578));
}
@Override
public SelectableChannel getChannel() {
return chan;
}
@Override
public void registrationEvent(NIOConnectionManager manager, SelectionKey key) throws IOException {
// TODO Auto-generated method stub
}
@Override
public void selectionEvent(SelectionKey key) throws IOException {
if(key.isValid() && key.isAcceptable()) {
SocketChannel c;
while((c = chan.accept()) != null) {
new Connection(c);
}
}
}
@Override
public void doStateChecks(long now) throws IOException {
// TODO Auto-generated method stub
}
@Override
public int calcInterestOps() {
return SelectionKey.OP_ACCEPT;
}
}
}