/*
* PeerConnectionOut - Keeps a queue of outgoing messages and delivers them.
* Copyright (C) 2003 Mark J. Wielaard
*
* This file is part of Snark.
*
* This program is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by the Free Software
* Foundation; either version 2, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License along with
* this program; if not, write to the Free Software Foundation, Inc., 59 Temple
* Place - Suite 330, Boston, MA 02111-1307, USA.
*/
package org.klomp.snark;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
class PeerConnectionOut implements Runnable
{
private final Peer peer;
private final DataOutputStream dout;
private Thread thread;
private boolean quit;
private List<Message> sendQueue = new ArrayList<Message>();
public PeerConnectionOut (Peer peer, DataOutputStream dout)
{
this.peer = peer;
this.dout = dout;
quit = false;
thread = new Thread(this);
thread.start();
}
/**
* Continuesly monitors for more outgoing messages that have to be send.
* Stops if quit is true of an IOException occurs.
*/
public void run ()
{
try {
while (!quit) {
Message m = null;
PeerState state = null;
synchronized (sendQueue) {
while (!quit && sendQueue.isEmpty()) {
try {
// Make sure everything will reach the other side.
dout.flush();
// Wait till more data arrives.
sendQueue.wait();
} catch (InterruptedException ie) {
/* ignored */
}
}
state = peer.state;
if (!quit && state != null) {
// Piece messages are big. So if there are other
// (control) messages make sure they are send first.
// Also remove request messages from the queue if
// we are currently being choked to prevent them from
// being send even if we get unchoked a little later.
// (Since we will resent them anyway in that case.)
// And remove piece messages if we are choking.
Iterator it = sendQueue.iterator();
while (m == null && it.hasNext()) {
Message nm = (Message)it.next();
if (nm.type == Message.PIECE) {
if (state.choking) {
it.remove();
}
nm = null;
} else if (nm.type == Message.REQUEST
&& state.choked) {
it.remove();
nm = null;
}
if (m == null && nm != null) {
m = nm;
it.remove();
}
}
if (m == null && sendQueue.size() > 0) {
m = sendQueue.remove(0);
}
}
}
if (m != null) {
log.log(Level.ALL, "Send " + peer + ": " + m);
m.sendMessage(dout);
// Remove all piece messages after sending a choke message.
if (m.type == Message.CHOKE) {
removeMessage(Message.PIECE);
}
// XXX - Should also register overhead...
if (m.type == Message.PIECE) {
state.uploaded(m.len);
}
m = null;
}
}
} catch (IOException ioe) {
// Ignore, probably other side closed connection.
} catch (Throwable t) {
log.log(Level.SEVERE, peer + " failed", t);
} finally {
quit = true;
peer.disconnect();
}
}
public void disconnect ()
{
synchronized (sendQueue) {
if (quit == true) {
return;
}
quit = true;
thread.interrupt();
sendQueue.clear();
sendQueue.notify();
}
}
/**
* Adds a message to the sendQueue and notifies the method waiting on the
* sendQueue to change.
*/
private void addMessage (Message m)
{
synchronized (sendQueue) {
sendQueue.add(m);
sendQueue.notify();
}
}
/**
* Removes a particular message type from the queue.
*
* @param type
* the Message type to remove.
* @returns true when a message of the given type was removed, false
* otherwise.
*/
private boolean removeMessage (int type)
{
boolean removed = false;
synchronized (sendQueue) {
Iterator it = sendQueue.iterator();
while (it.hasNext()) {
Message m = (Message)it.next();
if (m.type == type) {
it.remove();
removed = true;
}
}
}
return removed;
}
void sendAlive ()
{
Message m = new Message();
m.type = Message.KEEP_ALIVE;
addMessage(m);
}
void sendChoke (boolean choke)
{
// We cancel the (un)choke but keep PIECE messages.
// PIECE messages are purged if a choke is actually send.
synchronized (sendQueue) {
int inverseType = choke ? Message.UNCHOKE : Message.CHOKE;
if (!removeMessage(inverseType)) {
Message m = new Message();
if (choke) {
m.type = Message.CHOKE;
} else {
m.type = Message.UNCHOKE;
}
addMessage(m);
}
}
}
void sendInterest (boolean interest)
{
synchronized (sendQueue) {
int inverseType = interest ? Message.UNINTERESTED
: Message.INTERESTED;
if (!removeMessage(inverseType)) {
Message m = new Message();
if (interest) {
m.type = Message.INTERESTED;
} else {
m.type = Message.UNINTERESTED;
}
addMessage(m);
}
}
}
void sendHave (int piece)
{
Message m = new Message();
m.type = Message.HAVE;
m.piece = piece;
addMessage(m);
}
void sendBitfield (BitField bitfield)
{
Message m = new Message();
m.type = Message.BITFIELD;
m.data = bitfield.getFieldBytes();
m.off = 0;
m.len = m.data.length;
addMessage(m);
}
void sendRequests (List requests)
{
Iterator it = requests.iterator();
while (it.hasNext()) {
Request req = (Request)it.next();
sendRequest(req);
}
}
void sendRequest (Request req)
{
Message m = new Message();
m.type = Message.REQUEST;
m.piece = req.piece;
m.begin = req.off;
m.length = req.len;
addMessage(m);
}
void sendPiece (int piece, int begin, int length, byte[] bytes)
{
Message m = new Message();
m.type = Message.PIECE;
m.piece = piece;
m.begin = begin;
m.length = length;
m.data = bytes;
m.off = begin;
m.len = length;
addMessage(m);
}
void sendCancel (Request req)
{
// See if it is still in our send queue
synchronized (sendQueue) {
Iterator it = sendQueue.iterator();
while (it.hasNext()) {
Message m = (Message)it.next();
if (m.type == Message.REQUEST && m.piece == req.piece
&& m.begin == req.off && m.length == req.len) {
it.remove();
}
}
}
// Always send, just to be sure it it is really canceled.
Message m = new Message();
m.type = Message.CANCEL;
m.piece = req.piece;
m.begin = req.off;
m.length = req.len;
addMessage(m);
}
// Called by the PeerState when the other side doesn't want this
// request to be handled anymore. Removes any pending Piece Message
// from out send queue.
void cancelRequest (int piece, int begin, int length)
{
synchronized (sendQueue) {
Iterator it = sendQueue.iterator();
while (it.hasNext()) {
Message m = (Message)it.next();
if (m.type == Message.PIECE && m.piece == piece
&& m.begin == begin && m.length == length) {
it.remove();
}
}
}
}
protected static final Logger log = Logger.getLogger("org.klomp.snark.peer");
}