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 12030 2011-02-24 15:33:50Z jordan $
13 #include <string.h> /* strlen() */
15 #include <event2/buffer.h>
16 #include <event2/event.h>
18 #include "transmission.h"
20 #include "inout.h" /* tr_ioFindFileLocation() */
22 #include "ratecontrol.h"
29 struct tr_webseed_task
32 struct evbuffer
* content
;
33 struct tr_webseed
* webseed
;
34 tr_block_index_t block
;
35 tr_piece_index_t piece_index
;
36 uint32_t piece_offset
;
44 tr_ratecontrol download_rate
;
46 tr_peer_callback
* callback
;
54 int consecutive_failures
;
59 TR_IDLE_TIMER_MSEC
= 2000,
61 MAX_CONSECUIVE_FAILURES
= 5
65 webseed_free( struct tr_webseed
* w
)
67 tr_bitsetDestructor( &w
->parent
.have
);
68 tr_free( w
->parent
.client
);
70 event_free( w
->timer
);
71 tr_rcDestruct( &w
->download_rate
);
72 tr_free( w
->base_url
);
80 static const tr_peer_event blank_event
= { 0, 0, 0, 0, 0, 0, 0 };
83 publish( tr_webseed
* w
, tr_peer_event
* e
)
85 if( w
->callback
!= NULL
)
86 w
->callback( &w
->parent
, e
, w
->callback_data
);
90 fire_client_got_rej( tr_torrent
* tor
, tr_webseed
* w
, tr_block_index_t block
)
92 tr_peer_event e
= blank_event
;
93 e
.eventType
= TR_PEER_CLIENT_GOT_REJ
;
94 e
.pieceIndex
= tr_torBlockPiece( tor
, block
);
95 e
.offset
= tor
->blockSize
* block
- tor
->info
.pieceSize
* e
.pieceIndex
;
96 e
.length
= tr_torBlockCountBytes( tor
, block
);
101 fire_client_got_block( tr_torrent
* tor
, tr_webseed
* w
, tr_block_index_t block
)
103 tr_peer_event e
= blank_event
;
104 e
.eventType
= TR_PEER_CLIENT_GOT_BLOCK
;
105 e
.pieceIndex
= tr_torBlockPiece( tor
, block
);
106 e
.offset
= tor
->blockSize
* block
- tor
->info
.pieceSize
* e
.pieceIndex
;
107 e
.length
= tr_torBlockCountBytes( tor
, block
);
112 fire_client_got_data( tr_webseed
* w
, uint32_t length
)
114 tr_peer_event e
= blank_event
;
115 e
.eventType
= TR_PEER_CLIENT_GOT_DATA
;
117 e
.wasPieceData
= TRUE
;
126 on_content_changed( struct evbuffer
* buf UNUSED
,
127 const struct evbuffer_cb_info
* info
,
132 if( ( info
->n_added
> 0 ) && !w
->is_stopping
)
134 tr_rcTransferred( &w
->download_rate
, info
->n_added
);
135 fire_client_got_data( w
, info
->n_added
);
139 static void task_request_next_chunk( struct tr_webseed_task
* task
);
142 webseed_has_tasks( const tr_webseed
* w
)
144 return w
->tasks
!= NULL
;
149 on_idle( tr_webseed
* w
)
151 tr_torrent
* tor
= tr_torrentFindFromId( w
->session
, w
->torrent_id
);
153 if( w
->is_stopping
&& !webseed_has_tasks( w
) )
157 else if( !w
->is_stopping
&& tor
159 && !tr_torrentIsSeed( tor
)
160 && ( w
->consecutive_failures
< MAX_CONSECUIVE_FAILURES
) )
164 const int max
= tor
->blockCountInPiece
;
165 const int want
= max
- tr_list_size( w
->tasks
);
166 tr_block_index_t
* blocks
= NULL
;
170 blocks
= tr_new( tr_block_index_t
, want
);
171 tr_peerMgrGetNextRequests( tor
, &w
->parent
, want
, blocks
, &got
);
174 for( i
=0; i
<got
; ++i
)
176 const tr_block_index_t b
= blocks
[i
];
177 struct tr_webseed_task
* task
= tr_new( struct tr_webseed_task
, 1 );
179 task
->session
= w
->session
;
180 task
->torrent_id
= w
->torrent_id
;
182 task
->piece_index
= tr_torBlockPiece( tor
, b
);
183 task
->piece_offset
= ( tor
->blockSize
* b
)
184 - ( tor
->info
.pieceSize
* task
->piece_index
);
185 task
->length
= tr_torBlockCountBytes( tor
, b
);
186 task
->content
= evbuffer_new( );
187 evbuffer_add_cb( task
->content
, on_content_changed
, w
);
188 tr_list_append( &w
->tasks
, task
);
189 task_request_next_chunk( task
);
198 web_response_func( tr_session
* session
,
200 const void * response UNUSED
,
201 size_t response_byte_count UNUSED
,
204 struct tr_webseed_task
* t
= vtask
;
205 tr_webseed
* w
= t
->webseed
;
206 tr_torrent
* tor
= tr_torrentFindFromId( session
, t
->torrent_id
);
207 const int success
= ( response_code
== 206 );
210 w
->consecutive_failures
= 0;
212 ++w
->consecutive_failures
;
218 fire_client_got_rej( tor
, w
, t
->block
);
220 tr_list_remove_data( &w
->tasks
, t
);
221 evbuffer_free( t
->content
);
226 if( evbuffer_get_length( t
->content
) < t
->length
)
228 task_request_next_chunk( t
);
232 tr_cacheWriteBlock( session
->cache
, tor
,
233 t
->piece_index
, t
->piece_offset
, t
->length
,
235 fire_client_got_block( tor
, w
, t
->block
);
237 tr_list_remove_data( &w
->tasks
, t
);
238 evbuffer_free( t
->content
);
248 make_url( tr_webseed
* w
, const tr_file
* file
)
250 struct evbuffer
* out
= evbuffer_new( );
252 evbuffer_add( out
, w
->base_url
, w
->base_url_len
);
254 /* if url ends with a '/', add the torrent name */
255 if( w
->base_url
[w
->base_url_len
- 1] == '/' && file
->name
)
256 tr_http_escape( out
, file
->name
, strlen(file
->name
), FALSE
);
258 return evbuffer_free_to_str( out
);
262 task_request_next_chunk( struct tr_webseed_task
* t
)
264 tr_torrent
* tor
= tr_torrentFindFromId( t
->session
, t
->torrent_id
);
270 const tr_info
* inf
= tr_torrentInfo( tor
);
271 const uint32_t remain
= t
->length
- evbuffer_get_length( t
->content
);
273 const uint64_t total_offset
= inf
->pieceSize
* t
->piece_index
275 + evbuffer_get_length( t
->content
);
276 const tr_piece_index_t step_piece
= total_offset
/ inf
->pieceSize
;
277 const uint32_t step_piece_offset
278 = total_offset
- ( inf
->pieceSize
* step_piece
);
280 tr_file_index_t file_index
;
281 uint64_t file_offset
;
282 const tr_file
* file
;
285 tr_ioFindFileLocation( tor
, step_piece
, step_piece_offset
,
286 &file_index
, &file_offset
);
287 file
= &inf
->files
[file_index
];
288 this_pass
= MIN( remain
, file
->length
- file_offset
);
290 url
= make_url( t
->webseed
, file
);
291 tr_snprintf( range
, sizeof range
, "%"PRIu64
"-%"PRIu64
,
292 file_offset
, file_offset
+ this_pass
- 1 );
293 tr_webRunWithBuffer( t
->session
, url
, range
,
294 web_response_func
, t
, t
->content
);
300 tr_webseedGetSpeed_Bps( const tr_webseed
* w
, uint64_t now
, int * setme_Bps
)
302 const tr_bool is_active
= webseed_has_tasks( w
);
303 *setme_Bps
= is_active
? tr_rcRate_Bps( &w
->download_rate
, now
) : 0;
308 tr_webseedIsActive( const tr_webseed
* w
)
311 return tr_webseedGetSpeed_Bps( w
, tr_time_msec(), &Bps
) && ( Bps
> 0 );
319 webseed_timer_func( evutil_socket_t foo UNUSED
, short bar UNUSED
, void * vw
)
323 tr_timerAddMsec( w
->timer
, TR_IDLE_TIMER_MSEC
);
327 tr_webseedNew( struct tr_torrent
* tor
,
329 tr_peer_callback
* callback
,
330 void * callback_data
)
332 tr_webseed
* w
= tr_new0( tr_webseed
, 1 );
333 tr_peer
* peer
= &w
->parent
;
335 peer
->peerIsChoked
= TRUE
;
336 peer
->clientIsInterested
= !tr_torrentIsSeed( tor
);
337 tr_bitsetConstructor( &peer
->have
, tor
->info
.pieceCount
);
338 tr_bitsetSetHaveAll( &peer
->have
);
339 peer
->progress
= 1.0;
340 peer
->client
= tr_strdup( "webseed" );
342 w
->torrent_id
= tr_torrentId( tor
);
343 w
->session
= tor
->session
;
345 w
->base_url_len
= strlen( url
);
346 w
->base_url
= tr_strndup( url
, w
->base_url_len
);
347 w
->callback
= callback
;
348 w
->callback_data
= callback_data
;
349 tr_rcConstruct( &w
->download_rate
);
350 w
->timer
= evtimer_new( w
->session
->event_base
, webseed_timer_func
, w
);
351 tr_timerAddMsec( w
->timer
, TR_IDLE_TIMER_MSEC
);
356 tr_webseedFree( tr_webseed
* w
)
360 if( webseed_has_tasks( w
) )
361 w
->is_stopping
= TRUE
;