1
0
Fork 0
mirror of https://github.com/transmission/transmission synced 2024-12-27 10:07:40 +00:00
transmission/libtransmission/webseed.c
Jordan Lee 5dffc2814d (trunk libT) #3767 "rarest first policy" -- fixed.
This commit, started by a patch from athy, implements a rarest first policy when deciding which pieces to request from peers. It keeps a count of how many peers have each piece, and updates the count when getting bitfields, have, have all, and have none messages, as well as decrementing the counts when peers disconnect.

This running total is generated only for downloading torrents. Seeds don't have this overhead.
2011-02-17 05:14:53 +00:00

365 lines
10 KiB
C

/*
* This file Copyright (C) Mnemosyne LLC
*
* This file is licensed by the GPL version 2. Works owned by the
* Transmission project are granted a special exemption to clause 2(b)
* so that the bulk of its code can remain under the MIT license.
* This exemption does not extend to derived works not owned by
* the Transmission project.
*
* $Id$
*/
#include <string.h> /* strlen() */
#include <event2/buffer.h>
#include <event2/event.h>
#include "transmission.h"
#include "cache.h"
#include "inout.h" /* tr_ioFindFileLocation() */
#include "list.h"
#include "ratecontrol.h"
#include "peer-mgr.h"
#include "torrent.h"
#include "utils.h"
#include "web.h"
#include "webseed.h"
struct tr_webseed_task
{
tr_session * session;
struct evbuffer * content;
struct tr_webseed * webseed;
tr_block_index_t block;
tr_piece_index_t piece_index;
uint32_t piece_offset;
uint32_t length;
int torrent_id;
};
struct tr_webseed
{
tr_peer parent;
tr_ratecontrol download_rate;
tr_session * session;
tr_peer_callback * callback;
void * callback_data;
tr_list * tasks;
struct event * timer;
char * base_url;
size_t base_url_len;
int torrent_id;
tr_bool is_stopping;
int consecutive_failures;
};
enum
{
TR_IDLE_TIMER_MSEC = 2000,
MAX_CONSECUIVE_FAILURES = 5
};
static void
webseed_free( struct tr_webseed * w )
{
tr_bitsetDestructor( &w->parent.have );
tr_free( w->parent.client );
event_free( w->timer );
tr_rcDestruct( &w->download_rate );
tr_free( w->base_url );
tr_free( w );
}
/***
****
***/
static void
publish( tr_webseed * w, tr_peer_event * e )
{
if( w->callback != NULL )
w->callback( &w->parent, e, w->callback_data );
}
static void
fire_client_got_rej( tr_torrent * tor, tr_webseed * w, tr_block_index_t block )
{
tr_peer_event e = TR_PEER_EVENT_INIT;
e.eventType = TR_PEER_CLIENT_GOT_REJ;
e.pieceIndex = tr_torBlockPiece( tor, block );
e.offset = tor->blockSize * block - tor->info.pieceSize * e.pieceIndex;
e.length = tr_torBlockCountBytes( tor, block );
publish( w, &e );
}
static void
fire_client_got_block( tr_torrent * tor, tr_webseed * w, tr_block_index_t block )
{
tr_peer_event e = TR_PEER_EVENT_INIT;
e.eventType = TR_PEER_CLIENT_GOT_BLOCK;
e.pieceIndex = tr_torBlockPiece( tor, block );
e.offset = tor->blockSize * block - tor->info.pieceSize * e.pieceIndex;
e.length = tr_torBlockCountBytes( tor, block );
publish( w, &e );
}
static void
fire_client_got_data( tr_webseed * w, uint32_t length )
{
tr_peer_event e = TR_PEER_EVENT_INIT;
e.eventType = TR_PEER_CLIENT_GOT_DATA;
e.length = length;
e.wasPieceData = TRUE;
publish( w, &e );
}
/***
****
***/
static void
on_content_changed( struct evbuffer * buf UNUSED,
const struct evbuffer_cb_info * info,
void * vw )
{
tr_webseed * w = vw;
if( ( info->n_added > 0 ) && !w->is_stopping )
{
tr_rcTransferred( &w->download_rate, info->n_added );
fire_client_got_data( w, info->n_added );
}
}
static void task_request_next_chunk( struct tr_webseed_task * task );
static tr_bool
webseed_has_tasks( const tr_webseed * w )
{
return w->tasks != NULL;
}
static void
on_idle( tr_webseed * w )
{
tr_torrent * tor = tr_torrentFindFromId( w->session, w->torrent_id );
if( w->is_stopping && !webseed_has_tasks( w ) )
{
webseed_free( w );
}
else if( !w->is_stopping && tor
&& tor->isRunning
&& !tr_torrentIsSeed( tor )
&& ( w->consecutive_failures < MAX_CONSECUIVE_FAILURES ) )
{
int i;
int got = 0;
const int max = tor->blockCountInPiece;
const int want = max - tr_list_size( w->tasks );
tr_block_index_t * blocks = NULL;
if( want > 0 )
{
blocks = tr_new( tr_block_index_t, want );
tr_peerMgrGetNextRequests( tor, &w->parent, want, blocks, &got );
}
for( i=0; i<got; ++i )
{
const tr_block_index_t b = blocks[i];
struct tr_webseed_task * task = tr_new( struct tr_webseed_task, 1 );
task->webseed = w;
task->session = w->session;
task->torrent_id = w->torrent_id;
task->block = b;
task->piece_index = tr_torBlockPiece( tor, b );
task->piece_offset = ( tor->blockSize * b )
- ( tor->info.pieceSize * task->piece_index );
task->length = tr_torBlockCountBytes( tor, b );
task->content = evbuffer_new( );
evbuffer_add_cb( task->content, on_content_changed, w );
tr_list_append( &w->tasks, task );
task_request_next_chunk( task );
}
tr_free( blocks );
}
}
static void
web_response_func( tr_session * session,
tr_bool did_connect UNUSED,
tr_bool did_timeout UNUSED,
long response_code,
const void * response UNUSED,
size_t response_byte_count UNUSED,
void * vtask )
{
struct tr_webseed_task * t = vtask;
tr_webseed * w = t->webseed;
tr_torrent * tor = tr_torrentFindFromId( session, t->torrent_id );
const int success = ( response_code == 206 );
if( success )
w->consecutive_failures = 0;
else
++w->consecutive_failures;
if( tor )
{
if( !success )
{
fire_client_got_rej( tor, w, t->block );
tr_list_remove_data( &w->tasks, t );
evbuffer_free( t->content );
tr_free( t );
}
else
{
if( evbuffer_get_length( t->content ) < t->length )
{
task_request_next_chunk( t );
}
else
{
tr_cacheWriteBlock( session->cache, tor,
t->piece_index, t->piece_offset, t->length,
t->content );
fire_client_got_block( tor, w, t->block );
tr_list_remove_data( &w->tasks, t );
evbuffer_free( t->content );
tr_free( t );
on_idle( w );
}
}
}
}
static char*
make_url( tr_webseed * w, const tr_file * file )
{
struct evbuffer * out = evbuffer_new( );
evbuffer_add( out, w->base_url, w->base_url_len );
/* if url ends with a '/', add the torrent name */
if( w->base_url[w->base_url_len - 1] == '/' && file->name )
tr_http_escape( out, file->name, strlen(file->name), FALSE );
return evbuffer_free_to_str( out );
}
static void
task_request_next_chunk( struct tr_webseed_task * t )
{
tr_torrent * tor = tr_torrentFindFromId( t->session, t->torrent_id );
if( tor != NULL )
{
char * url;
char range[64];
const tr_info * inf = tr_torrentInfo( tor );
const uint32_t remain = t->length - evbuffer_get_length( t->content );
const uint64_t total_offset = inf->pieceSize * t->piece_index
+ t->piece_offset
+ evbuffer_get_length( t->content );
const tr_piece_index_t step_piece = total_offset / inf->pieceSize;
const uint32_t step_piece_offset
= total_offset - ( inf->pieceSize * step_piece );
tr_file_index_t file_index;
uint64_t file_offset;
const tr_file * file;
uint32_t this_pass;
tr_ioFindFileLocation( tor, step_piece, step_piece_offset,
&file_index, &file_offset );
file = &inf->files[file_index];
this_pass = MIN( remain, file->length - file_offset );
url = make_url( t->webseed, file );
tr_snprintf( range, sizeof range, "%"PRIu64"-%"PRIu64,
file_offset, file_offset + this_pass - 1 );
tr_webRunWithBuffer( t->session, url, range,
web_response_func, t, t->content );
tr_free( url );
}
}
tr_bool
tr_webseedGetSpeed_Bps( const tr_webseed * w, uint64_t now, int * setme_Bps )
{
const tr_bool is_active = webseed_has_tasks( w );
*setme_Bps = is_active ? tr_rcRate_Bps( &w->download_rate, now ) : 0;
return is_active;
}
tr_bool
tr_webseedIsActive( const tr_webseed * w )
{
int Bps = 0;
return tr_webseedGetSpeed_Bps( w, tr_time_msec(), &Bps ) && ( Bps > 0 );
}
/***
****
***/
static void
webseed_timer_func( evutil_socket_t foo UNUSED, short bar UNUSED, void * vw )
{
tr_webseed * w = vw;
on_idle( w );
tr_timerAddMsec( w->timer, TR_IDLE_TIMER_MSEC );
}
tr_webseed*
tr_webseedNew( struct tr_torrent * tor,
const char * url,
tr_peer_callback * callback,
void * callback_data )
{
tr_webseed * w = tr_new0( tr_webseed, 1 );
tr_peer * peer = &w->parent;
peer->peerIsChoked = TRUE;
peer->clientIsInterested = !tr_torrentIsSeed( tor );
tr_bitsetConstructor( &peer->have, tor->info.pieceCount );
tr_bitsetSetHaveAll( &peer->have );
peer->progress = 1.0;
peer->client = tr_strdup( "webseed" );
w->torrent_id = tr_torrentId( tor );
w->session = tor->session;
w->base_url_len = strlen( url );
w->base_url = tr_strndup( url, w->base_url_len );
w->callback = callback;
w->callback_data = callback_data;
tr_rcConstruct( &w->download_rate );
w->timer = evtimer_new( w->session->event_base, webseed_timer_func, w );
tr_timerAddMsec( w->timer, TR_IDLE_TIMER_MSEC );
return w;
}
void
tr_webseedFree( tr_webseed * w )
{
if( w )
{
if( webseed_has_tasks( w ) )
w->is_stopping = TRUE;
else
webseed_free( w );
}
}