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 14134 2013-07-20 16:45:02Z 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
;
36 tr_block_index_t block
;
37 tr_piece_index_t piece_index
;
38 uint32_t piece_offset
;
40 tr_block_index_t blocks_done
;
42 struct tr_web_task
* web_task
;
49 tr_bandwidth bandwidth
;
51 tr_peer_callback
* callback
;
58 int consecutive_failures
;
68 TR_IDLE_TIMER_MSEC
= 2000,
70 FAILURE_RETRY_INTERVAL
= 150,
72 MAX_CONSECUTIVE_FAILURES
= 5,
74 MAX_WEBSEED_CONNECTIONS
= 4
82 publish (tr_webseed
* w
, tr_peer_event
* e
)
84 if (w
->callback
!= NULL
)
85 w
->callback (&w
->parent
, e
, w
->callback_data
);
89 fire_client_got_rejs (tr_torrent
* tor
,
91 tr_block_index_t block
,
92 tr_block_index_t count
)
95 tr_peer_event e
= TR_PEER_EVENT_INIT
;
96 e
.eventType
= TR_PEER_CLIENT_GOT_REJ
;
97 tr_torrentGetBlockLocation (tor
, block
, &e
.pieceIndex
, &e
.offset
, &e
.length
);
98 for (i
= 1; i
<= count
; i
++)
101 e
.length
= tr_torBlockCountBytes (tor
, block
+ count
- 1);
103 e
.offset
+= e
.length
;
108 fire_client_got_blocks (tr_torrent
* tor
,
110 tr_block_index_t block
,
111 tr_block_index_t count
)
114 tr_peer_event e
= TR_PEER_EVENT_INIT
;
115 e
.eventType
= TR_PEER_CLIENT_GOT_BLOCK
;
116 tr_torrentGetBlockLocation (tor
, block
, &e
.pieceIndex
, &e
.offset
, &e
.length
);
117 for (i
= 1; i
<= count
; i
++)
120 e
.length
= tr_torBlockCountBytes (tor
, block
+ count
- 1);
122 e
.offset
+= e
.length
;
127 fire_client_got_piece_data (tr_webseed
* w
, uint32_t length
)
129 tr_peer_event e
= TR_PEER_EVENT_INIT
;
130 e
.eventType
= TR_PEER_CLIENT_GOT_PIECE_DATA
;
139 struct write_block_data
141 tr_session
* session
;
143 struct tr_webseed
* webseed
;
144 struct evbuffer
* content
;
145 tr_piece_index_t piece_index
;
146 tr_block_index_t block_index
;
147 tr_block_index_t count
;
148 uint32_t block_offset
;
152 write_block_func (void * vdata
)
154 struct write_block_data
* data
= vdata
;
155 struct tr_webseed
* w
= data
->webseed
;
156 struct evbuffer
* buf
= data
->content
;
157 struct tr_torrent
* tor
;
159 tor
= tr_torrentFindFromId (data
->session
, data
->torrent_id
);
162 const uint32_t block_size
= tor
->blockSize
;
163 uint32_t len
= evbuffer_get_length (buf
);
164 const uint32_t offset_end
= data
->block_offset
+ len
;
165 tr_cache
* cache
= data
->session
->cache
;
166 const tr_piece_index_t piece
= data
->piece_index
;
170 const uint32_t bytes_this_pass
= MIN (len
, block_size
);
171 tr_cacheWriteBlock (cache
, tor
, piece
, offset_end
- len
, bytes_this_pass
, buf
);
172 len
-= bytes_this_pass
;
175 fire_client_got_blocks (tor
, w
, data
->block_index
, data
->count
);
186 struct connection_succeeded_data
188 struct tr_webseed
* webseed
;
190 tr_piece_index_t piece_index
;
191 uint32_t piece_offset
;
195 connection_succeeded (void * vdata
)
198 struct connection_succeeded_data
* data
= vdata
;
199 struct tr_webseed
* w
= data
->webseed
;
201 if (++w
->active_transfers
>= w
->retry_challenge
&& w
->retry_challenge
)
202 /* the server seems to be accepting more connections now */
203 w
->consecutive_failures
= w
->retry_tickcount
= w
->retry_challenge
= 0;
205 if (data
->real_url
&& (tor
= tr_torrentFindFromId (w
->session
, w
->torrent_id
)))
207 uint64_t file_offset
;
208 tr_file_index_t file_index
;
210 tr_ioFindFileLocation (tor
, data
->piece_index
, data
->piece_offset
,
211 &file_index
, &file_offset
);
212 tr_free (w
->file_urls
[file_index
]);
213 w
->file_urls
[file_index
] = data
->real_url
;
214 data
->real_url
= NULL
;
217 tr_free (data
->real_url
);
226 on_content_changed (struct evbuffer
* buf
,
227 const struct evbuffer_cb_info
* info
,
230 const size_t n_added
= info
->n_added
;
231 struct tr_webseed_task
* task
= vtask
;
232 tr_session
* session
= task
->session
;
234 tr_sessionLock (session
);
236 if (!task
->dead
&& (n_added
>0))
239 struct tr_webseed
* w
= task
->webseed
;
241 tr_bandwidthUsed (&w
->bandwidth
, TR_DOWN
, n_added
, true, tr_time_msec ());
242 fire_client_got_piece_data (w
, n_added
);
243 len
= evbuffer_get_length (buf
);
245 if (!task
->response_code
)
247 tr_webGetTaskInfo (task
->web_task
, TR_WEB_GET_CODE
, &task
->response_code
);
249 if (task
->response_code
== 206)
252 struct connection_succeeded_data
* data
;
255 tr_webGetTaskInfo (task
->web_task
, TR_WEB_GET_REAL_URL
, &url
);
257 data
= tr_new (struct connection_succeeded_data
, 1);
259 data
->real_url
= tr_strdup (url
);
260 data
->piece_index
= task
->piece_index
;
261 data
->piece_offset
= task
->piece_offset
+ (task
->blocks_done
* task
->block_size
) + (len
- 1);
263 /* processing this uses a tr_torrent pointer,
264 so push the work to the libevent thread... */
265 tr_runInEventThread (w
->session
, connection_succeeded
, data
);
269 if ((task
->response_code
== 206) && (len
>= task
->block_size
))
271 /* once we've got at least one full block, save it */
273 struct write_block_data
* data
;
274 const uint32_t block_size
= task
->block_size
;
275 const tr_block_index_t completed
= len
/ block_size
;
277 data
= tr_new (struct write_block_data
, 1);
278 data
->webseed
= task
->webseed
;
279 data
->piece_index
= task
->piece_index
;
280 data
->block_index
= task
->block
+ task
->blocks_done
;
281 data
->count
= completed
;
282 data
->block_offset
= task
->piece_offset
+ task
->blocks_done
* block_size
;
283 data
->content
= evbuffer_new ();
284 data
->torrent_id
= w
->torrent_id
;
285 data
->session
= w
->session
;
287 /* we don't use locking on this evbuffer so we must copy out the data
288 that will be needed when writing the block in a different thread */
289 evbuffer_remove_buffer (task
->content
, data
->content
,
290 block_size
* completed
);
292 tr_runInEventThread (w
->session
, write_block_func
, data
);
293 task
->blocks_done
+= completed
;
297 tr_sessionUnlock (session
);
300 static void task_request_next_chunk (struct tr_webseed_task
* task
);
303 on_idle (tr_webseed
* w
)
306 int running_tasks
= tr_list_size (w
->tasks
);
307 tr_torrent
* tor
= tr_torrentFindFromId (w
->session
, w
->torrent_id
);
309 if (w
->consecutive_failures
>= MAX_CONSECUTIVE_FAILURES
)
311 want
= w
->idle_connections
;
313 if (w
->retry_tickcount
>= FAILURE_RETRY_INTERVAL
)
315 /* some time has passed since our connection attempts failed. try again */
317 /* if this challenge is fulfilled we will reset consecutive_failures */
318 w
->retry_challenge
= running_tasks
+ want
;
323 want
= MAX_WEBSEED_CONNECTIONS
- running_tasks
;
324 w
->retry_challenge
= running_tasks
+ w
->idle_connections
+ 1;
327 if (tor
&& tor
->isRunning
&& !tr_torrentIsSeed (tor
) && (want
> 0))
331 tr_block_index_t
* blocks
= NULL
;
333 blocks
= tr_new (tr_block_index_t
, want
*2);
334 tr_peerMgrGetNextRequests (tor
, &w
->parent
, want
, blocks
, &got
, true);
336 w
->idle_connections
-= MIN (w
->idle_connections
, got
);
337 if (w
->retry_tickcount
>= FAILURE_RETRY_INTERVAL
&& got
== want
)
338 w
->retry_tickcount
= 0;
340 for (i
=0; i
<got
; ++i
)
342 const tr_block_index_t b
= blocks
[i
*2];
343 const tr_block_index_t be
= blocks
[i
*2+1];
344 struct tr_webseed_task
* task
;
346 task
= tr_new0 (struct tr_webseed_task
, 1);
347 task
->session
= tor
->session
;
350 task
->piece_index
= tr_torBlockPiece (tor
, b
);
351 task
->piece_offset
= (tor
->blockSize
* b
) - (tor
->info
.pieceSize
* task
->piece_index
);
352 task
->length
= (be
- b
) * tor
->blockSize
+ tr_torBlockCountBytes (tor
, be
);
353 task
->blocks_done
= 0;
354 task
->response_code
= 0;
355 task
->block_size
= tor
->blockSize
;
356 task
->content
= evbuffer_new ();
357 evbuffer_add_cb (task
->content
, on_content_changed
, task
);
358 tr_list_append (&w
->tasks
, task
);
359 task_request_next_chunk (task
);
368 web_response_func (tr_session
* session
,
369 bool did_connect UNUSED
,
370 bool did_timeout UNUSED
,
372 const void * response UNUSED
,
373 size_t response_byte_count UNUSED
,
378 struct tr_webseed_task
* t
= vtask
;
379 const int success
= (response_code
== 206);
383 evbuffer_free (t
->content
);
389 tor
= tr_torrentFindFromId (session
, w
->torrent_id
);
392 /* active_transfers was only increased if the connection was successful */
393 if (t
->response_code
== 206)
394 --w
->active_transfers
;
398 const tr_block_index_t blocks_remain
= (t
->length
+ tor
->blockSize
- 1)
399 / tor
->blockSize
- t
->blocks_done
;
402 fire_client_got_rejs (tor
, w
, t
->block
+ t
->blocks_done
, blocks_remain
);
405 ++w
->idle_connections
;
406 else if (++w
->consecutive_failures
>= MAX_CONSECUTIVE_FAILURES
&& !w
->retry_tickcount
)
407 /* now wait a while until retrying to establish a connection */
408 ++w
->retry_tickcount
;
410 tr_list_remove_data (&w
->tasks
, t
);
411 evbuffer_free (t
->content
);
416 const uint32_t bytes_done
= t
->blocks_done
* tor
->blockSize
;
417 const uint32_t buf_len
= evbuffer_get_length (t
->content
);
419 if (bytes_done
+ buf_len
< t
->length
)
421 /* request finished successfully but there's still data missing. that
422 means we've reached the end of a file and need to request the next one */
423 t
->response_code
= 0;
424 task_request_next_chunk (t
);
430 /* on_content_changed () will not write a block if it is smaller than
431 the torrent's block size, i.e. the torrent's very last block */
432 tr_cacheWriteBlock (session
->cache
, tor
,
433 t
->piece_index
, t
->piece_offset
+ bytes_done
,
434 buf_len
, t
->content
);
436 fire_client_got_blocks (tor
, t
->webseed
,
437 t
->block
+ t
->blocks_done
, 1);
440 ++w
->idle_connections
;
442 tr_list_remove_data (&w
->tasks
, t
);
443 evbuffer_free (t
->content
);
452 static struct evbuffer
*
453 make_url (tr_webseed
* w
, const tr_file
* file
)
455 struct evbuffer
* buf
= evbuffer_new ();
457 evbuffer_add (buf
, w
->base_url
, w
->base_url_len
);
459 /* if url ends with a '/', add the torrent name */
460 if (w
->base_url
[w
->base_url_len
- 1] == '/' && file
->name
)
461 tr_http_escape (buf
, file
->name
, strlen (file
->name
), false);
467 task_request_next_chunk (struct tr_webseed_task
* t
)
469 tr_webseed
* w
= t
->webseed
;
470 tr_torrent
* tor
= tr_torrentFindFromId (w
->session
, w
->torrent_id
);
474 char ** urls
= t
->webseed
->file_urls
;
476 const tr_info
* inf
= tr_torrentInfo (tor
);
477 const uint64_t remain
= t
->length
- t
->blocks_done
* tor
->blockSize
478 - evbuffer_get_length (t
->content
);
480 const uint64_t total_offset
= tr_pieceOffset (tor
, t
->piece_index
,
483 const tr_piece_index_t step_piece
= total_offset
/ inf
->pieceSize
;
484 const uint64_t step_piece_offset
= total_offset
- (inf
->pieceSize
* step_piece
);
486 tr_file_index_t file_index
;
487 const tr_file
* file
;
488 uint64_t file_offset
;
491 tr_ioFindFileLocation (tor
, step_piece
, step_piece_offset
,
492 &file_index
, &file_offset
);
493 file
= &inf
->files
[file_index
];
494 this_pass
= MIN (remain
, file
->length
- file_offset
);
496 if (!urls
[file_index
])
497 urls
[file_index
] = evbuffer_free_to_str (make_url (t
->webseed
, file
));
499 tr_snprintf (range
, sizeof range
, "%"PRIu64
"-%"PRIu64
,
500 file_offset
, file_offset
+ this_pass
- 1);
502 t
->web_task
= tr_webRunWebseed (tor
, urls
[file_index
], range
,
503 web_response_func
, t
, t
->content
);
512 webseed_timer_func (evutil_socket_t foo UNUSED
, short bar UNUSED
, void * vw
)
516 if (w
->retry_tickcount
)
517 ++w
->retry_tickcount
;
521 tr_timerAddMsec (w
->timer
, TR_IDLE_TIMER_MSEC
);
525 **** tr_peer virtual functions
529 webseed_is_transferring_pieces (const tr_peer
* peer
,
531 tr_direction direction
,
532 unsigned int * setme_Bps
)
534 unsigned int Bps
= 0;
535 bool is_active
= false;
537 if (direction
== TR_DOWN
)
539 const tr_webseed
* w
= (const tr_webseed
*) peer
;
540 is_active
= w
->tasks
!= NULL
;
541 Bps
= tr_bandwidthGetPieceSpeed_Bps (&w
->bandwidth
, now
, direction
);
544 if (setme_Bps
!= NULL
)
551 webseed_destruct (tr_peer
* peer
)
554 tr_webseed
* w
= (tr_webseed
*) peer
;
556 /* flag all the pending tasks as dead */
557 for (l
=w
->tasks
; l
!=NULL
; l
=l
->next
)
559 struct tr_webseed_task
* task
= l
->data
;
562 tr_list_free (&w
->tasks
, NULL
);
564 /* if we have an array of file URLs, free it */
565 if (w
->file_urls
!= NULL
)
568 tr_torrent
* tor
= tr_torrentFindFromId (w
->session
, w
->torrent_id
);
569 const tr_info
* inf
= tr_torrentInfo (tor
);
571 for (i
=0; i
<inf
->fileCount
; ++i
)
572 tr_free (w
->file_urls
[i
]);
573 tr_free (w
->file_urls
);
576 /* webseed destruct */
577 event_free (w
->timer
);
578 tr_bandwidthDestruct (&w
->bandwidth
);
579 tr_free (w
->base_url
);
581 /* parent class destruct */
582 tr_peerDestruct (&w
->parent
);
585 static const struct tr_peer_virtual_funcs my_funcs
=
587 .destruct
= webseed_destruct
,
588 .is_transferring_pieces
= webseed_is_transferring_pieces
596 tr_webseedNew (struct tr_torrent
* tor
,
598 tr_peer_callback
* callback
,
599 void * callback_data
)
601 tr_webseed
* w
= tr_new0 (tr_webseed
, 1);
602 tr_peer
* peer
= &w
->parent
;
603 const tr_info
* inf
= tr_torrentInfo (tor
);
605 /* construct parent class */
606 tr_peerConstruct (peer
, tor
);
607 peer
->client
= TR_KEY_webseeds
;
608 peer
->funcs
= &my_funcs
;
609 tr_bitfieldSetHasAll (&peer
->have
);
610 tr_peerUpdateProgress (tor
, peer
);
612 w
->torrent_id
= tr_torrentId (tor
);
613 w
->session
= tor
->session
;
614 w
->base_url_len
= strlen (url
);
615 w
->base_url
= tr_strndup (url
, w
->base_url_len
);
616 w
->callback
= callback
;
617 w
->callback_data
= callback_data
;
618 w
->file_urls
= tr_new0 (char *, inf
->fileCount
);
619 //tr_rcConstruct (&w->download_rate);
620 tr_bandwidthConstruct (&w
->bandwidth
, tor
->session
, &tor
->bandwidth
);
621 w
->timer
= evtimer_new (w
->session
->event_base
, webseed_timer_func
, w
);
622 tr_timerAddMsec (w
->timer
, TR_IDLE_TIMER_MSEC
);