tr_timer * refillTimer;
tr_torrent * tor;
tr_peer * optimistic; /* the optimistic peer, or NULL if none */
- tr_bitfield * requestedPieces;
+ tr_bitfield * requestedBlocks;
unsigned int isRunning : 1;
tr_timerFree( &t->rechokeTimer );
tr_timerFree( &t->refillTimer );
- tr_bitfieldFree( t->requestedPieces );
+ tr_bitfieldFree( t->requestedBlocks );
tr_ptrArrayFree( t->webseeds, (PtrArrayForeachFunc)tr_webseedFree );
tr_ptrArrayFree( t->pool, (PtrArrayForeachFunc)tr_free );
tr_ptrArrayFree( t->outgoingHandshakes, NULL );
t->peers = tr_ptrArrayNew( );
t->webseeds = tr_ptrArrayNew( );
t->outgoingHandshakes = tr_ptrArrayNew( );
- t->requestedPieces = tr_bitfieldNew( tor->info.pieceCount );
+ t->requestedBlocks = tr_bitfieldNew( tor->blockCount );
memcpy( t->hash, tor->info.hash, SHA_DIGEST_LENGTH );
for( i = 0; i < tor->info.webseedCount; ++i )
struct tr_refill_piece
{
+ int missingBlockCount;
tr_priority_t priority;
int random;
uint32_t piece;
const struct tr_refill_piece * a = aIn;
const struct tr_refill_piece * b = bIn;
+ /* fewer missing pieces goes first */
+ if( a->missingBlockCount != b->missingBlockCount )
+ return a->missingBlockCount < b->missingBlockCount ? -1 : 1;
+
/* if one piece has a higher priority, it goes first */
if( a->priority != b->priority )
return a->priority > b->priority ? -1 : 1;
setme->priority = inf->pieces[piece].priority;
setme->peerCount = 0;
setme->random = tr_cryptoWeakRandInt( INT_MAX );
+ setme->missingBlockCount = tr_cpMissingBlocksInPiece( tor->completion, piece );
for( k = 0; k < peerCount; ++k )
{
return pool;
}
+static uint64_t*
+getPreferredBlocks( Torrent * t, tr_block_index_t * setmeCount )
+{
+ int s;
+ uint32_t i;
+ uint32_t pieceCount;
+ uint32_t blockCount;
+ uint32_t unreqCount[3], reqCount[3];
+ uint32_t * pieces;
+ uint64_t * ret, * walk;
+ uint64_t * unreq[3], *req[3];
+ const tr_torrent * tor = t->tor;
+
+ assert( torrentIsLocked( t ) );
+
+ pieces = getPreferredPieces( t, &pieceCount );
+
+ /**
+ * Now we walk through those preferred pieces to find all the blocks
+ * are still missing from them. We put unrequested blocks first,
+ * of course, but by including requested blocks afterwards, endgame
+ * handling happens naturally.
+ *
+ * By doing this once per priority we also effectively get an endgame
+ * mode for each priority level. The helps keep high priority files
+ * from getting stuck at 99% due of unresponsive peers.
+ */
+
+ /* make temporary bins for the four tiers of blocks */
+ for( i=0; i<3; ++i ) {
+ req[i] = tr_new( uint64_t, pieceCount * tor->blockCountInPiece );
+ reqCount[i] = 0;
+ unreq[i] = tr_new( uint64_t, pieceCount * tor->blockCountInPiece );
+ unreqCount[i] = 0;
+ }
+
+ /* sort the blocks into our temp bins */
+ for( i=blockCount=0; i<pieceCount; ++i )
+ {
+ const tr_piece_index_t index = pieces[i];
+ const int priorityIndex = tor->info.pieces[index].priority + 1;
+ const tr_block_index_t begin = tr_torPieceFirstBlock( tor, index );
+ const tr_block_index_t end = begin + tr_torPieceCountBlocks( tor, index );
+ tr_block_index_t block;
+
+ assert( tr_bitfieldTestFast( t->requestedBlocks, end-1 ) );
+
+ for( block=begin; block<end; ++block )
+ {
+ if( tr_cpBlockIsComplete( tor->completion, block ) )
+ continue;
+
+ ++blockCount;
+
+ if( tr_bitfieldHasFast( t->requestedBlocks, block ) )
+ {
+ const uint32_t n = reqCount[priorityIndex]++;
+ req[priorityIndex][n] = block;
+ }
+ else
+ {
+ const uint32_t n = unreqCount[priorityIndex]++;
+ unreq[priorityIndex][n] = block;
+ }
+ }
+ }
+
+ /* join the bins together, going from highest priority to lowest so
+ * the the blocks we want to request first will be first in the list */
+ ret = walk = tr_new( uint64_t, blockCount );
+ for( s=2; s>=0; --s ) {
+ memcpy( walk, unreq[s], sizeof(uint64_t) * unreqCount[s] );
+ walk += unreqCount[s];
+ memcpy( walk, req[s], sizeof(uint64_t) * reqCount[s] );
+ walk += reqCount[s];
+ }
+ assert( ( walk - ret ) == ( int )blockCount );
+ *setmeCount = blockCount;
+
+ /* cleanup */
+ tr_free( pieces );
+ for( i=0; i<3; ++i ) {
+ tr_free( unreq[i] );
+ tr_free( req[i] );
+ }
+ return ret;
+}
+
static tr_peer**
getPeersUploadingToClient( Torrent * t,
int * setmeCount )
{
Torrent * t = vtorrent;
tr_torrent * tor = t->tor;
+ tr_block_index_t i;
int peerCount;
int webseedCount;
tr_peer ** peers;
tr_webseed ** webseeds;
- uint32_t pieceCount;
- uint32_t * pieces;
- tr_piece_index_t i;
+ tr_block_index_t blockCount;
+ uint64_t * blocks;
if( !t->isRunning )
return TRUE;
torrentLock( t );
tordbg( t, "Refilling Request Buffers..." );
- pieces = getPreferredPieces( t, &pieceCount );
+ blocks = getPreferredBlocks( t, &blockCount );
peers = getPeersUploadingToClient( t, &peerCount );
webseedCount = tr_ptrArraySize( t->webseeds );
webseeds = tr_memdup( tr_ptrArrayBase(
t->webseeds ), webseedCount *
sizeof( tr_webseed* ) );
- for( i = 0; ( webseedCount || peerCount ) && i < pieceCount; ++i )
+ for( i = 0; ( webseedCount || peerCount ) && i < blockCount; ++i )
{
- int j;
- int handled = FALSE;
- const tr_piece_index_t piece = pieces[i];
+ int j;
+ int handled = FALSE;
- assert( piece < tor->info.pieceCount );
+ const tr_block_index_t block = blocks[i];
+ const tr_piece_index_t index = tr_torBlockPiece( tor, block );
+ const uint32_t begin = (block * tor->blockSize) - (index * tor->info.pieceSize);
+ const uint32_t length = tr_torBlockCountBytes( tor, block );
- /* find a peer who can ask for this piece */
+ assert( tr_torrentReqIsValid( tor, index, begin, length ) );
+ assert( _tr_block( tor, index, begin ) == block );
+ assert( begin < tr_torPieceCountBytes( tor, index ) );
+ assert( (begin + length) <= tr_torPieceCountBytes( tor, index ) );
+
+ /* find a peer who can ask for this block */
for( j = 0; !handled && j < peerCount; )
{
- const tr_addreq_t val = tr_peerMsgsAddRequest( peers[j]->msgs,
- piece );
+ const int val = tr_peerMsgsAddRequest( peers[j]->msgs, index, begin, length );
switch( val )
{
case TR_ADDREQ_FULL:
break;
case TR_ADDREQ_OK:
- tr_bitfieldAdd( t->requestedPieces, piece );
+ tr_bitfieldAdd( t->requestedBlocks, block );
handled = TRUE;
break;
/* maybe one of the webseeds can do it */
for( j = 0; !handled && j < webseedCount; )
{
- const tr_addreq_t val = tr_webseedAddRequest( webseeds[j],
- piece );
+ const tr_addreq_t val = tr_webseedAddRequest( webseeds[j], index, begin, length );
switch( val )
{
case TR_ADDREQ_FULL:
break;
case TR_ADDREQ_OK:
- tr_bitfieldAdd( t->requestedPieces, piece );
+ tr_bitfieldAdd( t->requestedBlocks, block );
handled = TRUE;
break;
/* cleanup */
tr_free( webseeds );
tr_free( peers );
- tr_free( pieces );
+ tr_free( blocks );
t->refillTimer = NULL;
torrentUnlock( t );
return FALSE;
}
+static void
+broadcastGotBlock( Torrent * t, uint32_t index, uint32_t offset, uint32_t length )
+{
+ int i, size;
+ tr_peer ** peers;
+
+ assert( torrentIsLocked( t ) );
+
+ peers = getConnectedPeers( t, &size );
+ for( i=0; i<size; ++i )
+ tr_peerMsgsCancel( peers[i]->msgs, index, offset, length );
+ tr_free( peers );
+}
+
static void
addStrike( Torrent * t,
tr_peer * peer )
break;
case TR_PEER_CANCEL:
- tr_bitfieldRem( t->requestedPieces, e->pieceIndex );
+ tr_bitfieldRem( t->requestedBlocks, _tr_block( t->tor, e->pieceIndex, e->offset ) );
break;
case TR_PEER_PEER_GOT_DATA:
if( peer )
tor->downloadedCur += e->length;
tr_statsAddDownloaded( tor->session, e->length );
- if( peer )
- {
- struct peer_atom * atom = getExistingAtom( t,
- &peer->in_addr );
+ if( peer ) {
+ struct peer_atom * atom = getExistingAtom( t, &peer->in_addr );
atom->piece_data_time = time( NULL );
}
break;
tr_cpBlockAdd( tor->completion, block );
+ broadcastGotBlock( t, e->pieceIndex, e->offset, e->length );
+
if( tr_cpPieceIsComplete( tor->completion, e->pieceIndex ) )
{
const tr_piece_index_t p = e->pieceIndex;
list->requests[list->count - 1] = *req;
}
-static void
-reqListAppendPiece( const tr_torrent * tor,
- struct request_list * list,
- tr_piece_index_t piece )
-{
- const time_t now = time( NULL );
- const size_t n = tr_torPieceCountBlocks( tor, piece );
- const tr_block_index_t begin = tr_torPieceFirstBlock( tor, piece );
- const tr_block_index_t end = begin + n;
- tr_block_index_t i;
-
- if( list->count + n >= list->max )
- reqListReserve( list, list->max + n );
-
- for( i = begin; i < end; ++i )
- {
- if( !tr_cpBlockIsComplete( tor->completion, i ) )
- {
- struct peer_request * req = list->requests + list->count++;
- req->index = piece;
- req->offset =
- ( i * tor->blockSize ) - ( piece * tor->info.pieceSize );
- req->length = tr_torBlockCountBytes( tor, i );
- req->time_requested = now;
- assert( tr_torrentReqIsValid( tor, req->index, req->offset,
- req->length ) );
- }
- }
-}
-
static int
reqListPop( struct request_list * list,
struct peer_request * setme )
return success;
}
-static int
-reqListHasPiece( struct request_list * list,
- const tr_piece_index_t piece )
-{
- uint16_t i;
-
- for( i = 0; i < list->count; ++i )
- if( list->requests[i].index == piece )
- return 1;
-
- return 0;
-}
-
static int
reqListFind( struct request_list * list,
const struct peer_request * key )
}
static void
-fireCancelledReq( tr_peermsgs * msgs,
- const tr_piece_index_t pieceIndex )
+fireCancelledReq( tr_peermsgs * msgs, const struct peer_request * req )
{
tr_peer_event e = blankEvent;
-
e.eventType = TR_PEER_CANCEL;
- e.pieceIndex = pieceIndex;
+ e.pieceIndex = req->index;
+ e.offset = req->offset;
+ e.length = req->length;
publish( msgs, &e );
}
}
static int
-requestIsValid( const tr_peermsgs * peer,
- const struct peer_request * req )
+requestIsValid( const tr_peermsgs * msgs, const struct peer_request * req )
{
- return reqIsValid( peer, req->index, req->offset, req->length );
-}
-
-static void
-tr_peerMsgsCancel( tr_peermsgs * msgs,
- uint32_t pieceIndex )
-{
- uint16_t i;
- struct request_list tmp = REQUEST_LIST_INIT;
- struct request_list * src;
-
- src = &msgs->clientWillAskFor;
- for( i = 0; i < src->count; ++i )
- if( src->requests[i].index != pieceIndex )
- reqListAppend( &tmp, src->requests + i );
-
- /* swap */
- reqListClear( &msgs->clientWillAskFor );
- msgs->clientWillAskFor = tmp;
- tmp = REQUEST_LIST_INIT;
-
- src = &msgs->clientAskedFor;
- for( i = 0; i < src->count; ++i )
- if( src->requests[i].index == pieceIndex )
- protocolSendCancel( msgs, src->requests + i );
- else
- reqListAppend( &tmp, src->requests + i );
-
- /* swap */
- reqListClear( &msgs->clientAskedFor );
- msgs->clientAskedFor = tmp;
- tmp = REQUEST_LIST_INIT;
-
- fireCancelledReq( msgs, pieceIndex );
+ return reqIsValid( msgs, req->index, req->offset, req->length );
}
static void
{
const struct peer_request * req = &tmp.requests[i];
if( req->time_requested < oldestAllowed )
- tr_peerMsgsCancel( msgs, req->index );
+ tr_peerMsgsCancel( msgs, req->index, req->offset, req->length );
}
reqListClear( &tmp );
{
const struct peer_request * req = &tmp.requests[i];
if( req->time_requested < oldestAllowed )
- tr_peerMsgsCancel( msgs, req->index );
+ tr_peerMsgsCancel( msgs, req->index, req->offset, req->length );
}
reqListClear( &tmp );
}
requestQueueIsFull( const tr_peermsgs * msgs )
{
const int req_max = msgs->maxActiveRequests;
-
return msgs->clientWillAskFor.count >= req_max;
}
tr_addreq_t
tr_peerMsgsAddRequest( tr_peermsgs * msgs,
- tr_piece_index_t piece )
+ uint32_t index,
+ uint32_t offset,
+ uint32_t length )
{
struct peer_request req;
assert( msgs );
assert( msgs->torrent );
- assert( piece < msgs->torrent->info.pieceCount );
+ assert( reqIsValid( msgs, index, offset, length ) );
/**
*** Reasons to decline the request
}
/* peer doesn't have this piece */
- if( !tr_bitfieldHas( msgs->info->have, piece ) )
+ if( !tr_bitfieldHas( msgs->info->have, index ) )
return TR_ADDREQ_MISSING;
/* peer's queue is full */
- if( requestQueueIsFull( msgs ) )
- {
+ if( requestQueueIsFull( msgs ) ) {
dbgmsg( msgs, "declining request because we're full" );
return TR_ADDREQ_FULL;
}
/* have we already asked for this piece? */
- if( reqListHasPiece( &msgs->clientAskedFor, piece )
- || reqListHasPiece( &msgs->clientWillAskFor, piece ) )
- {
+ req.index = index;
+ req.offset = offset;
+ req.length = length;
+ if( reqListFind( &msgs->clientAskedFor, &req ) != -1 ) {
+ dbgmsg( msgs, "declining because it's a duplicate" );
+ return TR_ADDREQ_DUPLICATE;
+ }
+ if( reqListFind( &msgs->clientWillAskFor, &req ) != -1 ) {
dbgmsg( msgs, "declining because it's a duplicate" );
return TR_ADDREQ_DUPLICATE;
}
*** Accept this request
**/
- dbgmsg( msgs, "added req for piece %lu", (unsigned long)piece );
+ dbgmsg( msgs, "added req for piece %lu", (unsigned long)index );
req.time_requested = time( NULL );
- reqListAppendPiece( msgs->torrent, &msgs->clientWillAskFor, piece );
+ reqListAppend( &msgs->clientWillAskFor, &req );
return TR_ADDREQ_OK;
}
msgs->clientAskedFor = REQUEST_LIST_INIT;
msgs->clientWillAskFor = REQUEST_LIST_INIT;
- for( i = 0; i < a.count; ++i )
- fireCancelledReq( msgs, a.requests[i].index );
+ for( i=0; i<a.count; ++i )
+ fireCancelledReq( msgs, &a.requests[i] );
- for( i = 0; i < b.count; ++i )
- {
- fireCancelledReq( msgs, b.requests[i].index );
+ for( i = 0; i < b.count; ++i ) {
+ fireCancelledReq( msgs, &b.requests[i] );
protocolSendCancel( msgs, &b.requests[i] );
}
reqListClear( &b );
}
+void
+tr_peerMsgsCancel( tr_peermsgs * msgs,
+ uint32_t pieceIndex,
+ uint32_t offset,
+ uint32_t length )
+{
+ struct peer_request req;
+
+ assert( msgs != NULL );
+ assert( length > 0 );
+
+ /* have we asked the peer for this piece? */
+ req.index = pieceIndex;
+ req.offset = offset;
+ req.length = length;
+
+ /* if it's only in the queue and hasn't been sent yet, free it */
+ if( !reqListRemove( &msgs->clientWillAskFor, &req ) )
+ fireCancelledReq( msgs, &req );
+
+ /* if it's already been sent, send a cancel message too */
+ if( !reqListRemove( &msgs->clientAskedFor, &req ) ) {
+ protocolSendCancel( msgs, &req );
+ fireCancelledReq( msgs, &req );
+ }
+}
+
/**
***
**/
void tr_peerMsgsPulse( tr_peermsgs * msgs );
-#if 0
void tr_peerMsgsCancel( tr_peermsgs * msgs,
uint32_t pieceIndex,
uint32_t offset,
uint32_t length );
-#endif
void tr_peerMsgsFree( tr_peermsgs* );
-tr_addreq_t tr_peerMsgsAddRequest( tr_peermsgs * peer,
- tr_piece_index_t piece );
+tr_addreq_t tr_peerMsgsAddRequest( tr_peermsgs * peer,
+ uint32_t pieceIndex,
+ uint32_t offset,
+ uint32_t length );
void tr_peerMsgsUnsubscribe( tr_peermsgs * peer,
tr_publisher_tag tag );
#include "web.h"
#include "webseed.h"
-#define MAX_QUEUE_SIZE 4
-
struct tr_webseed
{
+ unsigned int busy : 1;
unsigned int dead : 1;
tr_torrent * torrent;
tr_delivery_func * callback;
void * callback_userdata;
- uint64_t bytesSaved;
-
- tr_piece_index_t queue[MAX_QUEUE_SIZE];
- int queueSize;
+ tr_piece_index_t pieceIndex;
+ uint32_t pieceOffset;
+ uint32_t byteCount;
tr_ratecontrol * rateDown;
{
/* FIXME */
}
- else if( w->dead )
- {
- tr_webseedFree( w );
- }
else
{
- const tr_piece_index_t piece = w->queue[0];
- tr_block_index_t block;
- size_t len;
-
evbuffer_add( w->content, response, response_byte_count );
-
- fireClientGotData( w, response_byte_count );
-
- block = _tr_block( w->torrent, piece, w->bytesSaved );
- len = tr_torBlockCountBytes( w->torrent, block );
-
- while( EVBUFFER_LENGTH( w->content ) >= len )
+ if( !w->dead )
{
-/*fprintf( stderr, "saving piece index %lu, offset %lu, len %lu\n", (unsigned
- long)piece, (unsigned long)w->bytesSaved, (unsigned long)len );*/
- /* save one block */
- tr_ioWrite( w->torrent, piece, w->bytesSaved, len,
- EVBUFFER_DATA( w->content ) );
- evbuffer_drain( w->content, len );
- tr_rcTransferred( w->rateDown, len );
- fireClientGotBlock( w, piece, w->bytesSaved, len );
- w->bytesSaved += len;
-
- /* march to the next one */
- ++block;
- len = tr_torBlockCountBytes( w->torrent, block );
+ fireClientGotData( w, response_byte_count );
+ tr_rcTransferred( w->rateDown, response_byte_count );
}
- if( w->bytesSaved < tr_torPieceCountBytes( w->torrent, piece ) )
+ if( EVBUFFER_LENGTH( w->content ) < w->byteCount )
requestNextChunk( w );
- else
- {
- w->bytesSaved = 0;
+ else {
+ tr_ioWrite( w->torrent, w->pieceIndex, w->pieceOffset, w->byteCount, EVBUFFER_DATA(w->content) );
evbuffer_drain( w->content, EVBUFFER_LENGTH( w->content ) );
-/*fprintf( stderr, "w->callback_userdata is %p\n", w->callback_userdata );*/
- memmove( w->queue, w->queue + 1, sizeof( tr_piece_index_t ) *
- ( MAX_QUEUE_SIZE - 1 ) );
-/*fprintf( stderr, "w->callback_userdata is %p\n", w->callback_userdata );*/
- if( --w->queueSize )
- requestNextChunk( w );
- if( w->queueSize < ( MAX_QUEUE_SIZE / 2 ) )
+ w->busy = 0;
+ if( w->dead )
+ tr_webseedFree( w );
+ else {
+ fireClientGotBlock( w, w->pieceIndex, w->pieceOffset, w->byteCount );
fireNeedReq( w );
+ }
}
}
}
static void
requestNextChunk( tr_webseed * w )
{
- const tr_info * inf = tr_torrentInfo( w->torrent );
- const uint32_t have = w->bytesSaved + EVBUFFER_LENGTH(
- w->content );
- const tr_piece_index_t piece = w->queue[0];
- const uint32_t left =
- tr_torPieceCountBytes( w->torrent, piece ) - have;
- const uint32_t pieceOffset = have;
- tr_file_index_t fileIndex;
- uint64_t fileOffset;
- uint32_t thisPass;
- char * url;
- char * range;
-
- tr_ioFindFileLocation( w->torrent, piece, pieceOffset,
+ const tr_info * inf = tr_torrentInfo( w->torrent );
+ const uint32_t have = EVBUFFER_LENGTH( w->content );
+ const uint32_t left = w->byteCount - have;
+ const uint32_t pieceOffset = w->pieceOffset + have;
+ tr_file_index_t fileIndex;
+ uint64_t fileOffset;
+ uint32_t thisPass;
+ char * url;
+ char * range;
+
+ tr_ioFindFileLocation( w->torrent, w->pieceIndex, pieceOffset,
&fileIndex, &fileOffset );
thisPass = MIN( left, inf->files[fileIndex].length - fileOffset );
url = makeURL( w, &inf->files[fileIndex] );
- range = tr_strdup_printf( "%" PRIu64 "-%" PRIu64, fileOffset,
- fileOffset + thisPass - 1 );
-/*fprintf( stderr, "range is [%s] ... we want %lu total, we have %lu, so %lu are
- left, and we're asking for %lu this time\n", range, (unsigned
- long)tr_torPieceCountBytes(w->torrent,piece), (unsigned long)have, (unsigned
- long)left, (unsigned long)thisPass );*/
+//fprintf( stderr, "url is [%s]\n", url );
+ range = tr_strdup_printf( "%"PRIu64"-%"PRIu64, fileOffset, fileOffset + thisPass - 1 );
+//fprintf( stderr, "range is [%s] ... we want %lu total, we have %lu, so %lu are left, and we're asking for %lu this time\n", range, (unsigned long)w->byteCount, (unsigned long)have, (unsigned long)left, (unsigned long)thisPass );
tr_webRun( w->torrent->session, url, range, webResponseFunc, w );
tr_free( range );
tr_free( url );
}
tr_addreq_t
-tr_webseedAddRequest( tr_webseed * w,
- tr_piece_index_t piece )
+tr_webseedAddRequest( tr_webseed * w,
+ uint32_t pieceIndex,
+ uint32_t pieceOffset,
+ uint32_t byteCount )
{
int ret;
- if( w->dead || w->queueSize >= MAX_QUEUE_SIZE )
+ if( w->busy || w->dead )
{
ret = TR_ADDREQ_FULL;
}
else
{
- int wasEmpty = w->queueSize == 0;
- w->queue[w->queueSize++] = piece;
- if( wasEmpty )
- requestNextChunk( w );
+ w->busy = 1;
+ w->pieceIndex = pieceIndex;
+ w->pieceOffset = pieceOffset;
+ w->byteCount = byteCount;
+ evbuffer_drain( w->content, EVBUFFER_LENGTH( w->content ) );
+ requestNextChunk( w );
ret = TR_ADDREQ_OK;
}
int
tr_webseedIsActive( const tr_webseed * w )
{
- return w->queueSize > 0;
+ return w->busy != 0;
}
int
{
if( w )
{
- if( w->queueSize > 0 )
+ if( w->busy )
{
w->dead = 1;
}
}
}
}
-
void tr_webseedFree( tr_webseed * );
tr_addreq_t tr_webseedAddRequest( tr_webseed * w,
- tr_piece_index_t piece );
+ uint32_t index,
+ uint32_t offset,
+ uint32_t length );
/** @return true if a request is being processed, or false if idle */
int tr_webseedGetSpeed( const tr_webseed * w,