/*
* 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.peer;
import java.net.InetAddress;
import java.net.Socket;
import java.util.LinkedList;
import java.util.List;
import java.util.logging.Level;
import org.bitlet.wetorrent.Event;
import org.bitlet.wetorrent.Torrent;
import org.bitlet.wetorrent.peer.message.Cancel;
import org.bitlet.wetorrent.peer.message.Piece;
import org.bitlet.wetorrent.peer.message.Message;
import org.bitlet.wetorrent.peer.message.Request;
import org.bitlet.wetorrent.peer.task.StartMessageReceiver;
import org.bitlet.wetorrent.peer.task.MessageReceiver;
import org.bitlet.wetorrent.peer.task.MessageSender;
import org.bitlet.wetorrent.peer.task.Handshake;
import org.bitlet.wetorrent.peer.task.SendBitfield;
import org.bitlet.wetorrent.peer.task.StartConnection;
import org.bitlet.wetorrent.util.thread.InterruptableTasksThread;
import org.bitlet.wetorrent.util.Utils;
public class TorrentPeer implements Peer {
private byte[] peerId;
private String peerIdEncoded;
private int port;
private InetAddress ip;
private byte[] bitfield;
private Socket socket;
boolean isChoked = true;
boolean isInterested = false;
boolean amChoked = true;
boolean amInterested = false;
private PeersManager peersManager;
private InterruptableTasksThread receiverThread;
private InterruptableTasksThread mainThread;
private long downloaded;
private MessageSender messageSender;
private MessageReceiver messageReceiver;
private List<Request> unfulfilledRequests = new LinkedList<Request>();
public TorrentPeer(byte[] peerId, InetAddress ip, int port, PeersManager peersManager) {
this.peersManager = peersManager;
this.peerId = peerId;
peerIdEncoded = Utils.byteArrayToURLString(peerId);
this.port = port;
this.ip = ip;
bitfield = new byte[peersManager.getTorrent().getTorrentDisk().getBitfieldCopy().length];
mainThread = new InterruptableTasksThread();
mainThread.addTask(new StartConnection(this));
mainThread.addTask(new Handshake(this));
mainThread.addTask(new SendBitfield(this));
receiverThread = new InterruptableTasksThread();
messageReceiver = new MessageReceiver(this);
receiverThread.addTask(messageReceiver);
mainThread.addTask(new StartMessageReceiver(this));
messageSender = new MessageSender(this);
mainThread.addTask(messageSender);
}
public TorrentPeer(Socket socket, IncomingPeerListener incomingPeerListener) {
this.socket = socket;
port = socket.getPort();
ip = socket.getInetAddress();
mainThread = new InterruptableTasksThread();
mainThread.addTask(new Handshake(this, incomingPeerListener));
mainThread.addTask(new SendBitfield(this));
receiverThread = new InterruptableTasksThread();
messageReceiver = new MessageReceiver(this);
receiverThread.addTask(messageReceiver);
mainThread.addTask(new StartMessageReceiver(this));
messageSender = new MessageSender(this);
mainThread.addTask(messageSender);
}
public void start() {
mainThread.start();
}
public void exceptionCought(Exception e) {
if (Torrent.verbose) {
peersManager.getTorrent().addEvent(new Event(e, "UUoops exception cought.", Level.WARNING));
}
receiverThread.interrupt();
mainThread.interrupt();
}
public String getPeerIdEncoded() {
return peerIdEncoded;
}
public byte[] getPeerId() {
return peerId;
}
public void setPeerId(byte[] peerId) {
this.peerId = peerId;
}
public void setSocket(Socket socket) {
this.socket = socket;
}
public Socket getSocket() {
return socket;
}
public PeersManager getPeersManager() {
return peersManager;
}
public InetAddress getIp() {
return ip;
}
public int getPort() {
return port;
}
public synchronized void setBitfield(byte[] bitfield) {
this.bitfield = bitfield;
}
/*messages sent by remote peer*/
public void bitfield(byte[] bitfield) {
if (Torrent.verbose) {
peersManager.getTorrent().addEvent(new Event(this, "Bitfield received ", Level.FINEST));
}
setBitfield(bitfield);
peersManager.getTorrent().bitfield(bitfield, this);
}
public void choke() {
if (Torrent.verbose) {
peersManager.getTorrent().addEvent(new Event(this, "Choke received ", Level.FINEST));
}
amChoked = true;
peersManager.getTorrent().choke(this);
}
public void unchoke() {
if (Torrent.verbose) {
peersManager.getTorrent().addEvent(new Event(this, "Unchoke received ", Level.FINEST));
}
amChoked = false;
/* if there are pending request not satisfied */
messageSender.cancelAll();
peersManager.getTorrent().unchoke(this);
}
public void interested() {
if (Torrent.verbose) {
peersManager.getTorrent().addEvent(new Event(this, "Interested received ", Level.FINEST));
}
isInterested = true;
peersManager.getTorrent().interested(this);
}
public void notInterested() {
if (Torrent.verbose) {
peersManager.getTorrent().addEvent(new Event(this, "Not interested received ", Level.FINEST));
}
isInterested = false;
peersManager.getTorrent().notInterested(this);
}
public void have(int i) {
if (Torrent.verbose) {
peersManager.getTorrent().addEvent(new Event(this, "Have received ", Level.FINEST));
}
setPiece(i);
peersManager.getTorrent().have(i, this);
}
public void request(int index, int begin, int length) {
if (Torrent.verbose) {
peersManager.getTorrent().addEvent(new Event(this, "Request received ", Level.FINEST));
/* TODO: Check block avaiabilty */
}
if (!isChoked) {
messageSender.addMessage(new Piece(index, begin, length, peersManager.getTorrent().getTorrentDisk()));
}
}
public void piece(int index, int begin, byte[] block) {
if (Torrent.verbose) {
peersManager.getTorrent().addEvent(new Event(this, "Piece received " + index + " " + begin + " " + block.length, Level.FINEST));
}
downloaded += block.length;
if (requestFulfilled(index, begin, block))
peersManager.getTorrent().piece(index, begin, block, this);
}
public void cancel(int index, int begin, int length) {
if (Torrent.verbose) {
peersManager.getTorrent().addEvent(new Event(this, "Cancel received ", Level.FINEST));
}
messageSender.cancel(index, begin, length);
}
public InterruptableTasksThread getReceiverThread() {
return receiverThread;
}
public synchronized void sendMessage(Message message) {
messageSender.addMessage(message);
switch (message.getType()) {
case Message.REQUEST:
addRequest((Request) message);
break;
case Message.CANCEL:
Cancel cancel = (Cancel) message;
requestCanceled(cancel.getIndex(), cancel.getBegin());
break;
}
}
public void interrupt() {
mainThread.interrupt();
receiverThread.interrupt();
if (peersManager != null) {
peersManager.interrupted(this);
}
}
public synchronized byte[] getBitfieldCopy() {
return bitfield.clone();
}
public synchronized boolean hasPiece(int index) {
return (bitfield[index >> 3] & (0x80 >> (index & 0x7))) > 0;
}
public synchronized void setPiece(int index) {
bitfield[index >> 3] |= (0x80 >> (index & 0x7));
}
public void keepAlive() {
long now = System.currentTimeMillis();
if (now - messageSender.getLastSentMessageMillis() < 2000) {
sendMessage(new Message(Message.KEEP_ALIVE, null));
}
}
public long getUploaded() {
return messageSender.getUploaded();
}
public long getDownloaded() {
return downloaded;
}
public void setAmInterested(boolean amInterested) {
if (!this.amInterested && amInterested) {
sendMessage(new Message(Message.INTERESTED, null));
} else if (this.amInterested && !amInterested) {
sendMessage(new Message(Message.NOT_INTERESTED, null));
}
this.amInterested = amInterested;
}
public void setIsChoked(boolean isChoked) {
if (!this.isChoked && isChoked) {
sendMessage(new Message(Message.CHOKE, null));
} else if (this.isChoked && !isChoked) {
sendMessage(new Message(Message.UNCHOKE, null));
}
this.isChoked = isChoked;
}
public boolean isIsChoked() {
return isChoked;
}
public boolean isAmChoked() {
return amChoked;
}
public synchronized boolean isSeeder() {
for (int i = 0; i < getPeersManager().getTorrent().getMetafile().getPieces().size(); i++) {
if(!hasPiece(i))
return false;
}
return true;
}
private synchronized void addRequest(Request request) {
unfulfilledRequests.add(request);
}
public synchronized int getUnfulfilledRequestNumber() {
return unfulfilledRequests.size();
}
private synchronized boolean requestFulfilled(int index, int begin, byte[] block) {
for (Request r : unfulfilledRequests) {
if (r.getIndex() == index && r.getBegin() == begin && r.getLength() == block.length) {
unfulfilledRequests.remove(r);
return true;
}
}
return false;
}
private synchronized boolean requestCanceled(int index, int begin) {
for (Request r : unfulfilledRequests) {
if (r.getIndex() == index && r.getBegin() == begin) {
unfulfilledRequests.remove(r);
return true;
}
}
return false;
}
public synchronized Request getLastUnfulfilledRequest() {
if (unfulfilledRequests.size() == 0) {
return null;
}
Request last = unfulfilledRequests.get(unfulfilledRequests.size() - 1);
return last;
}
public long getLastReceivedMessageMillis() {
return messageReceiver.getLastReceivedMessageMillis();
}
void setPeersManager(PeersManager peersManager) {
this.peersManager = peersManager;
bitfield = new byte[peersManager.getTorrent().getTorrentDisk().getBitfieldCopy().length];
}
}