Transmission: update to 2.82
[tomato.git] / release / src / router / transmission / libtransmission / webseed.c
blob820e0bce9f988805ed1ea9300443368fd4b68d44
1 /*
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"
20 #include "cache.h"
21 #include "inout.h" /* tr_ioFindFileLocation () */
22 #include "list.h"
23 #include "peer-mgr.h"
24 #include "torrent.h"
25 #include "trevent.h" /* tr_runInEventThread () */
26 #include "utils.h"
27 #include "web.h"
28 #include "webseed.h"
30 struct tr_webseed_task
32 bool dead;
33 struct evbuffer * content;
34 struct tr_webseed * webseed;
35 tr_session * session;
36 tr_block_index_t block;
37 tr_piece_index_t piece_index;
38 uint32_t piece_offset;
39 uint32_t length;
40 tr_block_index_t blocks_done;
41 uint32_t block_size;
42 struct tr_web_task * web_task;
43 long response_code;
46 struct tr_webseed
48 tr_peer parent;
49 tr_bandwidth bandwidth;
50 tr_session * session;
51 tr_peer_callback * callback;
52 void * callback_data;
53 tr_list * tasks;
54 struct event * timer;
55 char * base_url;
56 size_t base_url_len;
57 int torrent_id;
58 int consecutive_failures;
59 int retry_tickcount;
60 int retry_challenge;
61 int idle_connections;
62 int active_transfers;
63 char ** file_urls;
66 enum
68 TR_IDLE_TIMER_MSEC = 2000,
70 FAILURE_RETRY_INTERVAL = 150,
72 MAX_CONSECUTIVE_FAILURES = 5,
74 MAX_WEBSEED_CONNECTIONS = 4
77 /***
78 ****
79 ***/
81 static void
82 publish (tr_webseed * w, tr_peer_event * e)
84 if (w->callback != NULL)
85 w->callback (&w->parent, e, w->callback_data);
88 static void
89 fire_client_got_rejs (tr_torrent * tor,
90 tr_webseed * w,
91 tr_block_index_t block,
92 tr_block_index_t count)
94 tr_block_index_t i;
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++)
100 if (i == count)
101 e.length = tr_torBlockCountBytes (tor, block + count - 1);
102 publish (w, &e);
103 e.offset += e.length;
107 static void
108 fire_client_got_blocks (tr_torrent * tor,
109 tr_webseed * w,
110 tr_block_index_t block,
111 tr_block_index_t count)
113 tr_block_index_t i;
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++)
119 if (i == count)
120 e.length = tr_torBlockCountBytes (tor, block + count - 1);
121 publish (w, &e);
122 e.offset += e.length;
126 static void
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;
131 e.length = length;
132 publish (w, &e);
135 /***
136 ****
137 ***/
139 struct write_block_data
141 tr_session * session;
142 int torrent_id;
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;
151 static void
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);
160 if (tor != NULL)
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;
168 while (len > 0)
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);
178 evbuffer_free (buf);
179 tr_free (data);
182 /***
183 ****
184 ***/
186 struct connection_succeeded_data
188 struct tr_webseed * webseed;
189 char * real_url;
190 tr_piece_index_t piece_index;
191 uint32_t piece_offset;
194 static void
195 connection_succeeded (void * vdata)
197 tr_torrent * tor;
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);
218 tr_free (data);
221 /***
222 ****
223 ***/
225 static void
226 on_content_changed (struct evbuffer * buf,
227 const struct evbuffer_cb_info * info,
228 void * vtask)
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))
238 uint32_t len;
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)
251 const char * url;
252 struct connection_succeeded_data * data;
254 url = NULL;
255 tr_webGetTaskInfo (task->web_task, TR_WEB_GET_REAL_URL, &url);
257 data = tr_new (struct connection_succeeded_data, 1);
258 data->webseed = w;
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);
302 static void
303 on_idle (tr_webseed * w)
305 int want;
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 */
316 ++want;
317 /* if this challenge is fulfilled we will reset consecutive_failures */
318 w->retry_challenge = running_tasks + want;
321 else
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))
329 int i;
330 int got = 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;
348 task->webseed = w;
349 task->block = b;
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);
362 tr_free (blocks);
367 static void
368 web_response_func (tr_session * session,
369 bool did_connect UNUSED,
370 bool did_timeout UNUSED,
371 long response_code,
372 const void * response UNUSED,
373 size_t response_byte_count UNUSED,
374 void * vtask)
376 tr_webseed * w;
377 tr_torrent * tor;
378 struct tr_webseed_task * t = vtask;
379 const int success = (response_code == 206);
381 if (t->dead)
383 evbuffer_free (t->content);
384 tr_free (t);
385 return;
388 w = t->webseed;
389 tor = tr_torrentFindFromId (session, w->torrent_id);
390 if (tor != NULL)
392 /* active_transfers was only increased if the connection was successful */
393 if (t->response_code == 206)
394 --w->active_transfers;
396 if (!success)
398 const tr_block_index_t blocks_remain = (t->length + tor->blockSize - 1)
399 / tor->blockSize - t->blocks_done;
401 if (blocks_remain)
402 fire_client_got_rejs (tor, w, t->block + t->blocks_done, blocks_remain);
404 if (t->blocks_done)
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);
412 tr_free (t);
414 else
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);
426 else
428 if (buf_len)
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);
444 tr_free (t);
446 on_idle (w);
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);
463 return buf;
466 static void
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);
471 if (tor != NULL)
473 char range[64];
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,
481 t->piece_offset,
482 t->length - remain);
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;
489 uint64_t this_pass;
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);
507 /***
508 ****
509 ***/
511 static void
512 webseed_timer_func (evutil_socket_t foo UNUSED, short bar UNUSED, void * vw)
514 tr_webseed * w = vw;
516 if (w->retry_tickcount)
517 ++w->retry_tickcount;
519 on_idle (w);
521 tr_timerAddMsec (w->timer, TR_IDLE_TIMER_MSEC);
524 /***
525 **** tr_peer virtual functions
526 ***/
528 static bool
529 webseed_is_transferring_pieces (const tr_peer * peer,
530 uint64_t now,
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)
545 *setme_Bps = Bps;
547 return is_active;
550 static void
551 webseed_destruct (tr_peer * peer)
553 tr_list * l;
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;
560 task->dead = true;
562 tr_list_free (&w->tasks, NULL);
564 /* if we have an array of file URLs, free it */
565 if (w->file_urls != NULL)
567 tr_file_index_t i;
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
591 /***
592 ****
593 ***/
595 tr_webseed*
596 tr_webseedNew (struct tr_torrent * tor,
597 const char * url,
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);
623 return w;