/*
* bitlet - Simple bittorrent library
* Copyright (C) 2008 Alessandro Bahgat Shehata, Daniele Castagna
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.bitlet.wetorrent.pieceChooser;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import org.bitlet.wetorrent.Event;
import org.bitlet.wetorrent.Torrent;
import org.bitlet.wetorrent.peer.Peer;
import org.bitlet.wetorrent.peer.message.Cancel;
import org.bitlet.wetorrent.peer.message.Request;
public abstract class PieceChooser {
protected abstract Integer choosePiece(Peer peer, int[] piecesFrequencies);
protected Torrent torrent;
public final static int blockLength = 1<<14;
/**
* Pieces in this set are almost complete or complete. We have already requested all the blocks
*/
private Set<Integer> completingPieces = new HashSet<Integer>();
/**
* Associates a peer to the next piece that should be suggested to download
*/
private Map<Peer,Integer> peerPiece = new HashMap<Peer,Integer>();
/**
* Associates a peer to its pending requests
*/
private Map<Peer,Set<RequestExtended>> peerPendingRequests = new HashMap<Peer,Set<RequestExtended>>();
/**
* Associates a piece to its pending requests
*/
private Map<Integer,Set<RequestExtended>> piecePendingRequests = new HashMap<Integer,Set<RequestExtended>>();
protected Set<RequestExtended> getPiecePendingRequests(int index){
return piecePendingRequests.get(index);
}
/*
* All the pending requests, ordered by the creation date
*/
private Set<RequestExtended> allPendingRequests = new HashSet<RequestExtended>();
/*
* All the pending requests, ordered by the creation date
*/
private Set<RequestExtended> orphanPendingRequests = new HashSet<RequestExtended>();
protected RequestExtended recoverRequest(Peer peer){
/* Search request in limbo */
for (RequestExtended re : orphanPendingRequests) {
if (peer.hasPiece(re.getIndex())){
if (Torrent.verbose)
torrent.addEvent(new Event(this,"Recovering limbo piece ",Level.FINER));
return re;
}
}
return null;
}
public synchronized Request getNextBlockRequest(Peer peer, int[] piecesFrequencies){
RequestExtended recoveredRequest = recoverRequest(peer);
if (recoveredRequest != null){
associateRequest(peer,recoveredRequest);
return recoveredRequest;
}
Integer pieceIndex = peerPiece.get(peer);
// null if not downloading, completing if piece is completing
if (pieceIndex==null || torrent.getTorrentDisk().isCompleted(pieceIndex) || completingPieces.contains(pieceIndex)){
peerPiece.remove(peer);
// get next piece
pieceIndex = choosePiece(peer,piecesFrequencies);
if (Torrent.verbose)
torrent.addEvent(new Event(this,"chosen piece " + pieceIndex,Level.FINER));
// no more pieces to download
if (pieceIndex == null)
return endGameBlockRequest(peer);
// requested piece pieceIndex to peer
peerPiece.put(peer,pieceIndex);
}
int firstMissingByte = getFirstMissingByte(pieceIndex);
if (firstMissingByte >= torrent.getTorrentDisk().getLength(pieceIndex))
throw new AssertionError("this piece is already completely requested, we should not be here.");
int pieceMissingBytes = torrent.getTorrentDisk().getLength(pieceIndex) - firstMissingByte;
int requestBlockLength = blockLength;
// we have all but the last block
if (pieceMissingBytes <= blockLength){
completingPieces.add(pieceIndex);
peerPiece.remove(peer);
requestBlockLength = pieceMissingBytes;
if (Torrent.verbose)
torrent.addEvent(new Event(this,"request piece completed " + pieceIndex + " " + firstMissingByte + " " + requestBlockLength,Level.FINER));
}
RequestExtended re = new RequestExtended(pieceIndex,firstMissingByte,requestBlockLength);
associateRequest(peer,re);
return re;
}
private int getFirstMissingByte(Integer pieceIndex) {
int firstMissingByte = torrent.getTorrentDisk().getFirstMissingByte(pieceIndex);
Collection<RequestExtended> pendingRequests = piecePendingRequests.get(pieceIndex);
/*
* This works just because we made the requests in a sequential order,
* So, we know that the max( firstMissingByte ,max( pendingRequests.getEnd() ))
* is the next missing byte.
*/
if (pendingRequests != null){
for (RequestExtended r : pendingRequests) {
// if the first missing byte is being downloaded by the other peer
if (firstMissingByte < r.getBegin() + r.getLength()){
firstMissingByte = r.getBegin() + r.getLength();
}
}
}
return firstMissingByte;
}
public synchronized void interrupted(Peer peer){
peerPiece.remove(peer);
Set<RequestExtended> pendingRequests = peerPendingRequests.get(peer);
if (pendingRequests != null){
for (RequestExtended re : pendingRequests) {
re.getPeers().remove(peer);
if (re.getPeers().size() == 0)
orphanPendingRequests.add(re);
}
peerPendingRequests.remove(peer);
}
}
/**
* A piece has been received
*/
public synchronized void piece(int index, int begin, byte[] block, Peer peer) {
RequestExtended request = null;
Set<RequestExtended> pendingRequests = null;
pendingRequests = peerPendingRequests.get(peer);
if (pendingRequests != null){
for (RequestExtended re : pendingRequests){
if (re.getIndex() == index && re.getBegin() == begin && re.getLength() == block.length){
request = re;
break;
}
}
if (request != null){
pendingRequests.remove(request);
if (pendingRequests.size() == 0)
peerPendingRequests.remove(peer);
request.getPeers().remove(peer);
/*
* Cancel the same request to all the other peers
*/
for (Peer p : request.getPeers()) {
p.sendMessage(new Cancel(index,begin,block.length));
pendingRequests = peerPendingRequests.get(p);
pendingRequests.remove(request);
if (pendingRequests.size() == 0)
peerPendingRequests.remove(p);
}
piecePendingRequests.get(index).remove(request);
allPendingRequests.remove(request);
}
}
/*
* else, the peer is sending us a block we didn't request
* or a block we already canceled
*/
}
private Request endGameBlockRequest(Peer peer) {
RequestExtended reToReturn = null;
for (RequestExtended re : allPendingRequests) {
if (peer.hasPiece(re.getIndex()) && !re.getPeers().contains(peer) &&
(reToReturn == null || reToReturn.getPeers().size()>re.getPeers().size()))
reToReturn = re;
}
if (reToReturn != null){
associateRequest(peer,reToReturn);
if (Torrent.verbose)
torrent.addEvent(new Event(this,"Duplicated request ",Level.FINER));
}
return reToReturn;
}
private void associateRequest(Peer peer, RequestExtended re){
Set<RequestExtended> pendingRequests = null;
/*
* piecePengingRequests
*/
pendingRequests = piecePendingRequests.get(re.getIndex());
if (pendingRequests == null){
pendingRequests = new HashSet<RequestExtended>();
piecePendingRequests.put(re.getIndex(),pendingRequests);
}
pendingRequests.add(re);
/*
* peerPengingRequests
*/
pendingRequests = peerPendingRequests.get(peer);
if (pendingRequests == null){
pendingRequests = new HashSet<RequestExtended>();
peerPendingRequests.put(peer,pendingRequests);
}
pendingRequests.add(re);
allPendingRequests.add(re);
orphanPendingRequests.remove(re);
re.getPeers().add(peer);
}
public void setTorrent(Torrent torrent) {
this.torrent = torrent;
}
public Torrent getTorrent() {
return torrent;
}
public boolean isCompletingPiece(int pieceIndex){
return completingPieces.contains(pieceIndex);
}
}