package p2pp;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Timer;
import java.util.TimerTask;
import org.klomp.snark.BitField;
import org.klomp.snark.CoordinatorListener;
import org.klomp.snark.MetaInfo;
import org.klomp.snark.Peer;
import org.klomp.snark.PeerCoordinator;
import org.klomp.snark.Storage;
import org.klomp.snark.TrackerClient;
import p2pp.LiveBTPeer.LiveBTPeerOrganizer;
public class LiveBTPeerCoordinator extends PeerCoordinator {
// Every MBDF_SLEEP milliseconds find a new piece to
// request by using the MBDF rules.
private static final int MBDF_SLEEP = 1000;
// Every RESCHEDULE_SLEEP milliseconds reorder the piece
// request queue if needed.
private static final int RESCHEDULE_SLEEP = 40000;
// Every DOWNLOAD_QUEUE_SLEEP milliseconds tune the download
// queue (update download queue capacities).
private static final int TUNE_QUEUE_SLEEP = RESCHEDULE_SLEEP;
private static final int TUNE_QUEUE_INIT_DELAY = TUNE_QUEUE_SLEEP / 2;
public static final int NO_PIECE = -1;
private static final int DOWNLOAD_TIMEOUT = 10000;
private static final int NUMBER_OF_MOST_WANTED_BLOCKS = 32;
private static final int THETA = 32;
private static final double P = 0.8;
private final Random RAND = new Random();
private final Timer TASK_TIMER = new Timer(true);
private LiveBTTable table;
private LiveBTPeerOrganizer organizer;
private Map<Integer, Long> downloadingPieces = new HashMap<Integer, Long>();
public LiveBTPeerCoordinator(byte[] id, MetaInfo metainfo,
int bitrate, Storage storage, CoordinatorListener listener) {
super(id, metainfo, storage, listener);
table = new LiveBTTable(metainfo.getPieceLength(0), bitrate);
organizer = new LiveBTPeerOrganizer();
Collections.sort(wantedPieces);
}
@Override
public boolean gotPiece(Peer peer, int piece, byte[] bs) throws IOException {
synchronized(peers) {
boolean valid = super.gotPiece(peer, piece, bs);
downloadingPieces.remove(piece);
if(valid){
organizer.updateDownloaded(peer, bs.length);
table.removeRequest(piece);
}
return valid;
}
}
@Override
public int wantPiece(Peer peer, BitField field) {
synchronized(peers) {
int piece = table.getFirstInQueue(peer);
if(piece == NO_PIECE) {
piece = getMostWantedBlock(field);
}
else
table.removeRequest(peer, piece);
if(piece != NO_PIECE && downloadProgress != null)
downloadProgress.pieceRequested(peer, piece);
if(piece != NO_PIECE)
downloadingPieces.put(piece, System.currentTimeMillis());
return piece;
}
}
@Override
public void setTracker(TrackerClient client) {
super.setTracker(client);
startMBDF();
startRescheduleBlock();
startTuneQueue();
}
@Override
public void halt() {
super.halt();
TASK_TIMER.cancel();
}
private void startMBDF() {
TASK_TIMER.schedule(new TimerTask() {
@Override
public void run() {
synchronized(peers) {
int piece = getMostWantedBlock(null);
if(piece != NO_PIECE)
downloadNewPiece(piece);
}
}
}, 0, MBDF_SLEEP);
}
private void startRescheduleBlock() {
TASK_TIMER.schedule(new TimerTask() {
@Override
public void run() {
synchronized(peers) {
reschedulePieces();
}
}
}, 0, RESCHEDULE_SLEEP);
}
private void startTuneQueue() {
TASK_TIMER.schedule(new TimerTask() {
@Override
public void run() {
synchronized(peers) {
for(Peer peer : peers) {
double speed = organizer.getCurrentSpeed(peer);
table.updateQueueLength(peer, speed,
organizer.updateSpeed(peer));
}
}
}
}, TUNE_QUEUE_INIT_DELAY, TUNE_QUEUE_SLEEP);
}
// Called from MBDF timer and wantPiece
private int getMostWantedBlock(BitField field) {
if(downloadingPieces.size() > THETA)
return NO_PIECE;
int piece = NO_PIECE;
if(RAND.nextDouble() < P)
piece = getMostWantedPeerBlock(field);
else
piece = getMostWantedSystemBlock(field);
return piece;
}
private int getMostWantedPeerBlock(BitField field) {
for(int i = 0; i < wantedPieces.size(); i++) {
int piece = wantedPieces.get(i);
boolean has = field != null ? field.get(piece) :
blockAvailable(piece);
boolean doRequest = downloadingPieces.containsKey(piece) ?
System.currentTimeMillis() - downloadingPieces.get(piece) >
DOWNLOAD_TIMEOUT : true;
if(has && doRequest) {
return piece;
}
}
return NO_PIECE;
}
private boolean blockAvailable(int piece) {
for(Peer peer : peers) {
if(peer.getHaves() != null &&
peer.getHaves().get(piece) && !table.isQueueFull(peer))
return true;
}
return false;
}
private int getMostWantedSystemBlock(BitField field) {
Map<Integer, Integer> count = new HashMap<Integer, Integer>();
for(Peer peer : peers) {
BitField haves = peer.getHaves();
if(haves == null)
continue;
for(int i = 0, j = 0; i < haves.size() &&
j < NUMBER_OF_MOST_WANTED_BLOCKS; i++) {
boolean has = field != null ? field.get(i) : true;
if(has && !haves.get(i)) {
j++;
int c = count.containsKey(i) ? count.get(i) + 1 : 1;
count.put(i, c);
}
}
}
List<Integer> result = new ArrayList<Integer>();
int wanted = 0;
for(int i : count.keySet())
if(count.get(i) > wanted) {
wanted = count.get(i);
result.clear();
result.add(i);
}
else if(count.get(i) == wanted)
result.add(i);
int piece = result.isEmpty() ? NO_PIECE : result.get(
RAND.nextInt(result.size()));
return piece;
}
// Called from MBDF and rescheduleBlock timers.
private void downloadNewPiece(int piece) {
List<Peer> qualified = new ArrayList<Peer>();
for(Peer peer : peers)
if(peer.getHaves() != null &&
peer.getHaves().get(piece) && !table.isQueueFull(peer))
qualified.add(peer);
if(qualified.isEmpty()) //Should not happen often. But then again...
return;
Comparator<Peer> finishTime = new Comparator<Peer>() {
@Override
public int compare(Peer o1, Peer o2) {
return finishTime(o1) - finishTime(o2);
}
private int finishTime(Peer peer) {
return table.estimatedAllDownloadsFinish(peer,
organizer.getCurrentSpeed(peer));
}
};
Peer peer = Collections.min(qualified, finishTime);
table.addRequest(peer, piece,
table.estimatedDownloadFinish(
peer, organizer.getCurrentSpeed(peer)));
}
private void rescheduleAPiece(int piece) {
int index = wantedPieces.indexOf(piece);
if(index >= NUMBER_OF_MOST_WANTED_BLOCKS)
table.removeRequest(piece);
// TODO Send cancel message to the corresponding peers.
downloadNewPiece(piece);
}
// Called from rescheduleBlock timer.
private void reschedulePieces() {
for(Peer peer : peers) {
double speed = organizer.updateSpeed(peer);
table.updateTimes(peer, speed);
}
table.sortBlockQueues();
ArrayList<Integer> pieces =
new ArrayList<Integer>(table.getRequestedPieces());
for(int piece : pieces)
if(!table.canMeetDeadline(piece)) {
rescheduleAPiece(piece);
}
}
}