2 * This file Copyright (C) Mnemosyne LLC
4 * This file is licensed by the GPL version 2. Works owned by the
5 * Transmission project are granted a special exemption to clause 2(b)
6 * so that the bulk of its code can remain under the MIT license.
7 * This exemption does not extend to derived works not owned by
8 * the Transmission project.
10 * $Id: webseed.c 12555 2011-07-17 18:11:34Z jordan $
13 #include <string.h> /* strlen() */
15 #include <event2/buffer.h>
16 #include <event2/event.h>
18 #include "transmission.h"
19 #include "bandwidth.h"
21 #include "inout.h" /* tr_ioFindFileLocation() */
25 #include "trevent.h" /* tr_runInEventThread() */
30 struct tr_webseed_task
33 struct evbuffer
* content
;
34 struct tr_webseed
* webseed
;
35 tr_block_index_t block
;
36 tr_piece_index_t piece_index
;
37 uint32_t piece_offset
;
39 tr_block_index_t blocks_done
;
41 struct tr_web_task
* web_task
;
49 tr_bandwidth bandwidth
;
51 tr_peer_callback
* callback
;
59 int consecutive_failures
;
67 struct tr_blockwrite_info
69 struct tr_webseed
* webseed
;
70 struct evbuffer
* data
;
71 tr_piece_index_t piece_index
;
72 tr_block_index_t block_index
;
73 tr_block_index_t count
;
74 uint32_t block_offset
;
79 struct tr_webseed
* webseed
;
81 tr_piece_index_t piece_index
;
82 uint32_t piece_offset
;
87 TR_IDLE_TIMER_MSEC
= 2000,
89 FAILURE_RETRY_INTERVAL
= 150,
91 MAX_CONSECUTIVE_FAILURES
= 5,
93 MAX_WEBSEED_CONNECTIONS
= 4
97 webseed_free( struct tr_webseed
* w
)
99 tr_torrent
* tor
= tr_torrentFindFromId( w
->session
, w
->torrent_id
);
100 const tr_info
* inf
= tr_torrentInfo( tor
);
103 for( i
= 0; i
< inf
->fileCount
; i
++ ) {
104 if( w
->file_urls
[i
] )
105 tr_free( w
->file_urls
[i
] );
107 tr_free( w
->file_urls
);
109 /* webseed destruct */
110 event_free( w
->timer
);
111 tr_bandwidthDestruct( &w
->bandwidth
);
112 tr_free( w
->base_url
);
114 /* parent class destruct */
115 tr_peerDestruct( tor
, &w
->parent
);
125 publish( tr_webseed
* w
, tr_peer_event
* e
)
127 if( w
->callback
!= NULL
)
128 w
->callback( &w
->parent
, e
, w
->callback_data
);
132 fire_client_got_rejs( tr_torrent
* tor
, tr_webseed
* w
,
133 tr_block_index_t block
, tr_block_index_t count
)
136 tr_peer_event e
= TR_PEER_EVENT_INIT
;
137 e
.eventType
= TR_PEER_CLIENT_GOT_REJ
;
138 tr_torrentGetBlockLocation( tor
, block
, &e
.pieceIndex
, &e
.offset
, &e
.length
);
139 for( i
= 1; i
<= count
; i
++ ) {
141 e
.length
= tr_torBlockCountBytes( tor
, block
+ count
- 1 );
143 e
.offset
+= e
.length
;
148 fire_client_got_blocks( tr_torrent
* tor
, tr_webseed
* w
,
149 tr_block_index_t block
, tr_block_index_t count
)
152 tr_peer_event e
= TR_PEER_EVENT_INIT
;
153 e
.eventType
= TR_PEER_CLIENT_GOT_BLOCK
;
154 tr_torrentGetBlockLocation( tor
, block
, &e
.pieceIndex
, &e
.offset
, &e
.length
);
155 for( i
= 1; i
<= count
; i
++ ) {
157 e
.length
= tr_torBlockCountBytes( tor
, block
+ count
- 1 );
159 e
.offset
+= e
.length
;
164 fire_client_got_data( tr_webseed
* w
, uint32_t length
)
166 tr_peer_event e
= TR_PEER_EVENT_INIT
;
167 e
.eventType
= TR_PEER_CLIENT_GOT_DATA
;
169 e
.wasPieceData
= true;
178 write_block_func( void * vblock
)
180 struct tr_blockwrite_info
* block
= vblock
;
181 struct tr_webseed
* w
= block
->webseed
;
182 struct evbuffer
* buf
= block
->data
;
183 struct tr_torrent
* tor
;
185 tor
= tr_torrentFindFromId( w
->session
, w
->torrent_id
);
188 const uint32_t block_size
= tor
->blockSize
;
189 uint32_t len
= evbuffer_get_length( buf
);
190 uint32_t offset_end
= block
->block_offset
+ len
;
191 tr_cache
* cache
= w
->session
->cache
;
192 tr_piece_index_t piece
= block
->piece_index
;
196 if( len
> block_size
) {
197 tr_cacheWriteBlock( cache
, tor
, piece
, offset_end
- len
,
202 tr_cacheWriteBlock( cache
, tor
, piece
, offset_end
- len
,
207 fire_client_got_blocks( tor
, w
, block
->block_index
, block
->count
);
210 evbuffer_free( buf
);
215 connection_succeeded( void * vinf
)
217 struct tr_http_info
* inf
= vinf
;
218 struct tr_webseed
* w
= inf
->webseed
;
219 struct tr_torrent
* tor
;
221 if( ++w
->active_transfers
>= w
->retry_challenge
&& w
->retry_challenge
)
222 /* the server seems to be accepting more connections now */
223 w
->consecutive_failures
= w
->retry_tickcount
= w
->retry_challenge
= 0;
225 if( inf
->redirect_url
&&
226 (tor
= tr_torrentFindFromId( w
->session
, w
->torrent_id
)))
228 uint64_t file_offset
;
229 tr_file_index_t file_index
;
231 tr_ioFindFileLocation( tor
, inf
->piece_index
, inf
->piece_offset
,
232 &file_index
, &file_offset
);
233 tr_free( w
->file_urls
[file_index
] );
234 w
->file_urls
[file_index
] = inf
->redirect_url
;
239 on_content_changed( struct evbuffer
* buf
,
240 const struct evbuffer_cb_info
* info
,
243 struct tr_webseed_task
* task
= vtask
;
244 struct tr_webseed
* w
= task
->webseed
;
247 if( info
->n_added
<= 0 )
250 if( !w
->is_stopping
)
252 tr_bandwidthUsed( &w
->bandwidth
, TR_DOWN
, info
->n_added
, true, tr_time_msec( ) );
253 fire_client_got_data( w
, info
->n_added
);
256 if( !task
->response_code
) {
257 tr_webGetTaskInfo( task
->web_task
, TR_WEB_GET_CODE
, &task
->response_code
);
259 if( task
->response_code
== 206 ) {
260 struct tr_http_info
* inf
= tr_new( struct tr_http_info
, 1 );
264 inf
->piece_index
= task
->piece_index
;
265 inf
->piece_offset
= task
->piece_offset
;
266 tr_webGetTaskInfo( task
->web_task
, TR_WEB_GET_REDIRECTS
, &redirects
);
269 tr_webGetTaskInfo( task
->web_task
, TR_WEB_GET_REAL_URL
, &redirect_url
);
270 inf
->redirect_url
= tr_strdup( redirect_url
);
273 inf
->redirect_url
= NULL
;
274 /* run this in the webseed thread to avoid tampering with mutexes and to
275 not cost the web thrad too much time */
276 tr_runInEventThread( task
->session
, connection_succeeded
, inf
);
280 len
= evbuffer_get_length( buf
);
282 if( task
->response_code
== 206 && len
>= task
->block_size
)
284 /* one (ore more) block(s) received. write to hd */
285 const uint32_t block_size
= task
->block_size
;
286 const tr_block_index_t completed
= len
/ block_size
;
287 struct tr_blockwrite_info
* b
= tr_new( struct tr_blockwrite_info
, 1 );
289 b
->webseed
= task
->webseed
;
290 b
->piece_index
= task
->piece_index
;
291 b
->block_index
= task
->block
+ task
->blocks_done
;
292 b
->count
= completed
;
293 b
->block_offset
= task
->piece_offset
+ task
->blocks_done
* block_size
;
294 b
->data
= evbuffer_new( );
296 /* we don't use locking on this evbuffer so we must copy out the data
297 that will be needed when writing the block in a different thread */
298 evbuffer_remove_buffer( task
->content
, b
->data
,
299 block_size
* completed
);
301 tr_runInEventThread( task
->session
, write_block_func
, b
);
302 task
->blocks_done
+= completed
;
306 static void task_request_next_chunk( struct tr_webseed_task
* task
);
309 webseed_has_tasks( const tr_webseed
* w
)
311 return w
->tasks
!= NULL
;
316 on_idle( tr_webseed
* w
)
318 tr_torrent
* tor
= tr_torrentFindFromId( w
->session
, w
->torrent_id
);
319 int want
, running_tasks
= tr_list_size( w
->tasks
);
321 if( w
->consecutive_failures
>= MAX_CONSECUTIVE_FAILURES
) {
322 want
= w
->idle_connections
;
324 if( w
->retry_tickcount
>= FAILURE_RETRY_INTERVAL
) {
325 /* some time has passed since our connection attempts failed. try again */
327 /* if this challenge is fulfilled we will reset consecutive_failures */
328 w
->retry_challenge
= running_tasks
+ want
;
332 want
= MAX_WEBSEED_CONNECTIONS
- running_tasks
;
333 w
->retry_challenge
= running_tasks
+ w
->idle_connections
+ 1;
336 if( w
->is_stopping
&& !webseed_has_tasks( w
) )
340 else if( !w
->is_stopping
&& tor
342 && !tr_torrentIsSeed( tor
)
347 tr_block_index_t
* blocks
= NULL
;
349 blocks
= tr_new( tr_block_index_t
, want
*2 );
350 tr_peerMgrGetNextRequests( tor
, &w
->parent
, want
, blocks
, &got
, true );
352 w
->idle_connections
-= MIN( w
->idle_connections
, got
);
353 if( w
->retry_tickcount
>= FAILURE_RETRY_INTERVAL
&& got
== want
)
354 w
->retry_tickcount
= 0;
356 for( i
=0; i
<got
; ++i
)
358 const tr_block_index_t b
= blocks
[i
*2];
359 const tr_block_index_t be
= blocks
[i
*2+1];
360 struct tr_webseed_task
* task
= tr_new( struct tr_webseed_task
, 1 );
362 task
->session
= w
->session
;
363 task
->torrent_id
= w
->torrent_id
;
365 task
->piece_index
= tr_torBlockPiece( tor
, b
);
366 task
->piece_offset
= ( tor
->blockSize
* b
)
367 - ( tor
->info
.pieceSize
* task
->piece_index
);
368 task
->length
= (be
- b
) * tor
->blockSize
+ tr_torBlockCountBytes( tor
, be
);
369 task
->blocks_done
= 0;
370 task
->response_code
= 0;
371 task
->block_size
= tor
->blockSize
;
372 task
->content
= evbuffer_new( );
373 evbuffer_add_cb( task
->content
, on_content_changed
, task
);
374 tr_list_append( &w
->tasks
, task
);
375 task_request_next_chunk( task
);
384 web_response_func( tr_session
* session
,
385 bool did_connect UNUSED
,
386 bool did_timeout UNUSED
,
388 const void * response UNUSED
,
389 size_t response_byte_count UNUSED
,
392 struct tr_webseed_task
* t
= vtask
;
393 tr_webseed
* w
= t
->webseed
;
394 tr_torrent
* tor
= tr_torrentFindFromId( session
, t
->torrent_id
);
395 const int success
= ( response_code
== 206 );
399 /* active_transfers was only increased if the connection was successful */
400 if( t
->response_code
== 206 )
401 --w
->active_transfers
;
405 const tr_block_index_t blocks_remain
= (t
->length
+ tor
->blockSize
- 1)
406 / tor
->blockSize
- t
->blocks_done
;
409 fire_client_got_rejs( tor
, w
, t
->block
+ t
->blocks_done
, blocks_remain
);
412 ++w
->idle_connections
;
413 else if( ++w
->consecutive_failures
>= MAX_CONSECUTIVE_FAILURES
&& !w
->retry_tickcount
)
414 /* now wait a while until retrying to establish a connection */
415 ++w
->retry_tickcount
;
417 tr_list_remove_data( &w
->tasks
, t
);
418 evbuffer_free( t
->content
);
423 const uint32_t bytes_done
= t
->blocks_done
* tor
->blockSize
;
424 const uint32_t buf_len
= evbuffer_get_length( t
->content
);
426 if( bytes_done
+ buf_len
< t
->length
)
428 /* request finished successfully but there's still data missing. that
429 means we've reached the end of a file and need to request the next one */
430 t
->response_code
= 0;
431 task_request_next_chunk( t
);
436 /* on_content_changed() will not write a block if it is smaller than
437 the torrent's block size, i.e. the torrent's very last block */
438 tr_cacheWriteBlock( session
->cache
, tor
,
439 t
->piece_index
, t
->piece_offset
+ bytes_done
,
440 buf_len
, t
->content
);
442 fire_client_got_blocks( tor
, t
->webseed
,
443 t
->block
+ t
->blocks_done
, 1 );
446 ++w
->idle_connections
;
448 tr_list_remove_data( &w
->tasks
, t
);
449 evbuffer_free( t
->content
);
458 static struct evbuffer
*
459 make_url( tr_webseed
* w
, const tr_file
* file
)
461 struct evbuffer
* buf
= evbuffer_new( );
463 evbuffer_add( buf
, w
->base_url
, w
->base_url_len
);
465 /* if url ends with a '/', add the torrent name */
466 if( w
->base_url
[w
->base_url_len
- 1] == '/' && file
->name
)
467 tr_http_escape( buf
, file
->name
, strlen(file
->name
), false );
473 task_request_next_chunk( struct tr_webseed_task
* t
)
475 tr_torrent
* tor
= tr_torrentFindFromId( t
->session
, t
->torrent_id
);
479 char ** urls
= t
->webseed
->file_urls
;
481 const tr_info
* inf
= tr_torrentInfo( tor
);
482 const uint32_t remain
= t
->length
- t
->blocks_done
* tor
->blockSize
483 - evbuffer_get_length( t
->content
);
485 const uint64_t total_offset
= inf
->pieceSize
* t
->piece_index
486 + t
->piece_offset
+ t
->length
- remain
;
487 const tr_piece_index_t step_piece
= total_offset
/ inf
->pieceSize
;
488 const uint32_t step_piece_offset
489 = total_offset
- ( inf
->pieceSize
* step_piece
);
491 tr_file_index_t file_index
;
492 uint64_t file_offset
;
493 const tr_file
* file
;
496 tr_ioFindFileLocation( tor
, step_piece
, step_piece_offset
,
497 &file_index
, &file_offset
);
498 file
= &inf
->files
[file_index
];
499 this_pass
= MIN( remain
, file
->length
- file_offset
);
501 if( !urls
[file_index
] )
502 urls
[file_index
] = evbuffer_free_to_str( make_url( t
->webseed
, file
) );
504 tr_snprintf( range
, sizeof range
, "%"PRIu64
"-%"PRIu64
,
505 file_offset
, file_offset
+ this_pass
- 1 );
506 t
->web_task
= tr_webRunWithBuffer( t
->session
, urls
[file_index
],
507 range
, NULL
, web_response_func
, t
, t
->content
);
512 tr_webseedGetSpeed_Bps( const tr_webseed
* w
, uint64_t now
, int * setme_Bps
)
514 const bool is_active
= webseed_has_tasks( w
);
515 *setme_Bps
= is_active
? tr_bandwidthGetPieceSpeed_Bps( &w
->bandwidth
, now
, TR_DOWN
) : 0;
520 tr_webseedIsActive( const tr_webseed
* w
)
523 return tr_webseedGetSpeed_Bps( w
, tr_time_msec(), &Bps
) && ( Bps
> 0 );
531 webseed_timer_func( evutil_socket_t foo UNUSED
, short bar UNUSED
, void * vw
)
534 if( w
->retry_tickcount
)
535 ++w
->retry_tickcount
;
537 tr_timerAddMsec( w
->timer
, TR_IDLE_TIMER_MSEC
);
541 tr_webseedNew( struct tr_torrent
* tor
,
543 tr_peer_callback
* callback
,
544 void * callback_data
)
546 tr_webseed
* w
= tr_new0( tr_webseed
, 1 );
547 tr_peer
* peer
= &w
->parent
;
548 const tr_info
* inf
= tr_torrentInfo( tor
);
550 /* construct parent class */
551 tr_peerConstruct( peer
);
552 peer
->peerIsChoked
= true;
553 peer
->clientIsInterested
= !tr_torrentIsSeed( tor
);
554 peer
->client
= tr_strdup( "webseed" );
555 tr_bitfieldSetHasAll( &peer
->have
);
556 tr_peerUpdateProgress( tor
, peer
);
558 w
->torrent_id
= tr_torrentId( tor
);
559 w
->session
= tor
->session
;
560 w
->base_url_len
= strlen( url
);
561 w
->base_url
= tr_strndup( url
, w
->base_url_len
);
562 w
->callback
= callback
;
563 w
->callback_data
= callback_data
;
564 w
->file_urls
= tr_new0( char *, inf
->fileCount
);
565 //tr_rcConstruct( &w->download_rate );
566 tr_bandwidthConstruct( &w
->bandwidth
, tor
->session
, &tor
->bandwidth
);
567 w
->timer
= evtimer_new( w
->session
->event_base
, webseed_timer_func
, w
);
568 tr_timerAddMsec( w
->timer
, TR_IDLE_TIMER_MSEC
);
573 tr_webseedFree( tr_webseed
* w
)
577 if( webseed_has_tasks( w
) )
578 w
->is_stopping
= true;