bt.torrent.messaging.RoutingPeerWorker Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of bt-core Show documentation
Show all versions of bt-core Show documentation
BitTorrent Client Library (Core)
package bt.torrent.messaging;
import bt.metainfo.TorrentId;
import bt.net.Peer;
import bt.protocol.Cancel;
import bt.protocol.Choke;
import bt.protocol.Interested;
import bt.protocol.Message;
import bt.protocol.NotInterested;
import bt.protocol.Piece;
import bt.protocol.Unchoke;
import java.util.Deque;
import java.util.Optional;
import java.util.concurrent.LinkedBlockingDeque;
/**
* Messaging interface, that encapsulates all messaging agents (consumers and producers)
* for a particular torrent processing session.
* Routes incoming messages directly to interested consumers, based on message types.
*
* @since 1.0
*/
class RoutingPeerWorker implements PeerWorker {
private ConnectionState connectionState;
private MessageRouter router;
private MessageContext context;
private Deque outgoingMessages;
private Choker choker;
public RoutingPeerWorker(Peer peer, Optional torrentId, MessageRouter router) {
this.connectionState = new ConnectionState();
this.router = router;
this.context = new MessageContext(torrentId, peer, connectionState);
this.outgoingMessages = new LinkedBlockingDeque<>();
this.choker = Choker.choker();
}
@Override
public ConnectionState getConnectionState() {
return connectionState;
}
@Override
public void accept(Message message) {
router.consume(message, context);
updateConnection();
}
private void postMessage(Message message) {
if (isUrgent(message)) {
addUrgent(message);
} else {
add(message);
}
}
private boolean isUrgent(Message message) {
// TODO: this should be done based on priorities
Class extends Message> messageType = message.getClass();
return Choke.class.equals(messageType) || Unchoke.class.equals(messageType) || Cancel.class.equals(messageType);
}
private void add(Message message) {
outgoingMessages.add(message);
}
private void addUrgent(Message message) {
outgoingMessages.addFirst(message);
}
@Override
public Message get() {
if (outgoingMessages.isEmpty()) {
router.produce(this::postMessage, context);
updateConnection();
}
return postProcessOutgoingMessage(outgoingMessages.poll());
}
private Message postProcessOutgoingMessage(Message message) {
if (message == null) {
return null;
}
Class extends Message> messageType = message.getClass();
if (Piece.class.equals(messageType)) {
Piece piece = (Piece) message;
// check that peer hadn't sent cancel while we were preparing the requested block
if (isCancelled(piece)) {
// dispose of message
return null;
} else {
connectionState.incrementUploaded(piece.getBlock().length);
}
}
if (Interested.class.equals(messageType)) {
connectionState.setInterested(true);
}
if (NotInterested.class.equals(messageType)) {
connectionState.setInterested(false);
}
if (Choke.class.equals(messageType)) {
connectionState.setShouldChoke(true);
}
if (Unchoke.class.equals(messageType)) {
connectionState.setShouldChoke(false);
}
return message;
}
private boolean isCancelled(Piece piece) {
int pieceIndex = piece.getPieceIndex(),
offset = piece.getOffset(),
length = piece.getBlock().length;
return connectionState.getCancelledPeerRequests().remove(Mapper.mapper().buildKey(pieceIndex, offset, length));
}
private void updateConnection() {
choker.handleConnection(connectionState, this::postMessage);
}
}