source: trunk/libtransmission/announcer.c @ 9305

Last change on this file since 9305 was 9305, checked in by charles, 13 years ago

(trunk libT) #2494: Adding a tracker changes announce status from time to "queued."

File size: 48.9 KB
Line 
1/*
2 * This file Copyright (C) 2009 Charles Kerr <charles@transmissionbt.com>
3 *
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.
9 *
10 * $Id:$
11 */
12
13#include <assert.h>
14#include <limits.h>
15
16#include <event.h>
17#include <evhttp.h> /* for HTTP_OK */
18
19#include "transmission.h"
20#include "announcer.h"
21#include "crypto.h"
22#include "net.h"
23#include "ptrarray.h"
24#include "publish.h"
25#include "session.h"
26#include "torrent.h"
27#include "utils.h"
28#include "web.h"
29
30#define dbgmsg( tier, ... ) \
31if( tr_deepLoggingIsActive( ) ) do { \
32  char name[128]; \
33  tr_snprintf( name, sizeof( name ), "[%s--%s]", tr_torrentName( tier->tor ), \
34      ( tier->currentTracker ? tier->currentTracker->host->name : "" ) ); \
35  tr_deepLog( __FILE__, __LINE__, name, __VA_ARGS__ ); \
36} while( 0 )
37
38enum
39{
40    /* unless the tracker says otherwise, rescrape this frequently */
41    DEFAULT_SCRAPE_INTERVAL_SEC = ( 60 * 15 ),
42
43    /* unless the tracker says otherwise, this is the announce interval */
44    DEFAULT_ANNOUNCE_INTERVAL_SEC = ( 60 * 10 ),
45
46    /* unless the tracker says otherwise, this is the announce min_interval */
47    DEFAULT_ANNOUNCE_MIN_INTERVAL_SEC = ( 60 * 2 ),
48
49    /* how long to wait before a reannounce the first time we get an error */
50    FIRST_ANNOUNCE_RETRY_INTERVAL_SEC = 30,
51
52    /* how long to wait before a rescrape the first time we get an error */
53    FIRST_SCRAPE_RETRY_INTERVAL_SEC = 120,
54
55    /* the length of the 'key' argument passed in tracker requests */
56    KEYLEN = 8,
57
58    /* how many scrapes we allow at one time */
59    MAX_CONCURRENT_SCRAPES = 16,
60
61    /* how many announces we allow at one time */
62    MAX_CONCURRENT_ANNOUNCES = 48,
63
64    /* if a tracker takes more than this long to respond,
65     * we treat it as nonresponsive */
66    MAX_TRACKER_RESPONSE_TIME_SECS = ( 60 * 2 ),
67
68    /* the value of the 'numwant' argument passed in tracker requests. */
69    NUMWANT = 200,
70
71    /* how long to put slow (nonresponsive) trackers in the penalty box */
72    SLOW_HOST_PENALTY_SECS = ( 60 * 10 ),
73
74    UPKEEP_INTERVAL_SECS = 1
75
76};
77
78/***
79****
80***/
81
82static int
83compareTransfer( int a_uploaded, int a_downloaded,
84                 int b_uploaded, int b_downloaded )
85{
86    /* higher upload count goes first */
87    if( a_uploaded != b_uploaded )
88        return a_uploaded > b_uploaded ? -1 : 1;
89
90    /* then higher download count goes first */
91    if( a_downloaded != b_downloaded )
92        return a_downloaded > b_downloaded ? -1 : 1;
93
94    return 0;
95}
96
97/***
98****
99***/
100
101/**
102 * used by tr_announcer to recognize nonresponsive
103 * trackers and de-prioritize them
104 */
105typedef struct
106{
107    char * name;
108
109    /* how many seconds it took to get the last tracker response */
110    int lastResponseInterval;
111
112    /* the last time we sent an announce or scrape message */
113    time_t lastRequestTime;
114}
115tr_host;
116
117static int
118compareHosts( const void * va, const void * vb )
119{
120    const tr_host * a = va;
121    const tr_host * b = vb;
122    return strcmp( a->name, b->name );
123}
124
125static int
126compareHostToName( const void * va, const void * vb )
127{
128    const tr_host * a = va;
129    return strcmp( a->name, vb );
130}
131
132static char *
133getHostName( const char * url )
134{
135    int port = 0;
136    char * host = NULL;
137    char * ret;
138    tr_httpParseURL( url, strlen( url ), &host, &port, NULL );
139    ret = tr_strdup_printf( "%s:%d", ( host ? host : "invalid" ), port );
140    tr_free( host );
141    return ret;
142}
143
144static tr_host*
145hostNew( const char * name )
146{
147    tr_host * host = tr_new0( tr_host, 1 );
148    host->name = tr_strdup( name );
149    return host;
150}
151
152static void
153hostFree( void * vhost )
154{
155    tr_host * host = vhost;
156
157    tr_free( host->name );
158    tr_free( host );
159}
160
161/***
162****
163***/
164
165/**
166 * Since we can't poll a tr_torrent's fields after it's destroyed,
167 * we pre-build the "stop" announcement message when a torrent
168 * is removed from Transmission
169 */
170struct stop_message
171{
172    tr_host * host;
173    char * url;
174    int up;
175    int down;
176};
177
178static void
179stopFree( struct stop_message * stop )
180{
181    tr_free( stop->url );
182    tr_free( stop );
183}
184
185static int
186compareStops( const void * va, const void * vb )
187{
188    const struct stop_message * a = va;
189    const struct stop_message * b = vb;
190    return compareTransfer( a->up, a->down, b->up, b->down);
191}
192
193/***
194****
195***/
196
197/**
198 * "global" (per-tr_session) fields
199 */
200typedef struct tr_announcer
201{
202    tr_ptrArray hosts; /* tr_host */
203    tr_ptrArray stops; /* struct stop_message */
204    tr_session * session;
205    struct event * upkeepTimer;
206    int announceSlotsAvailable;
207    int scrapeSlotsAvailable;
208}
209tr_announcer;
210
211static tr_host *
212getHost( tr_announcer * announcer, const char * url )
213{
214    char * name = getHostName( url );
215    tr_host * host;
216
217    host = tr_ptrArrayFindSorted( &announcer->hosts, name, compareHostToName );
218    if( host == NULL )
219    {
220        host = hostNew( name );
221        tr_ptrArrayInsertSorted( &announcer->hosts, host, compareHosts );
222    }
223
224    tr_free( name );
225    return host;
226}
227
228static void
229onUpkeepTimer( int foo UNUSED, short bar UNUSED, void * vannouncer );
230
231void
232tr_announcerInit( tr_session * session )
233{
234    tr_announcer * a;
235
236    assert( tr_isSession( session ) );
237
238    a = tr_new0( tr_announcer, 1 );
239    a->hosts = TR_PTR_ARRAY_INIT;
240    a->stops = TR_PTR_ARRAY_INIT;
241    a->session = session;
242    a->announceSlotsAvailable = MAX_CONCURRENT_ANNOUNCES;
243    a->scrapeSlotsAvailable = MAX_CONCURRENT_SCRAPES;
244    a->upkeepTimer = tr_new0( struct event, 1 );
245    evtimer_set( a->upkeepTimer, onUpkeepTimer, a );
246    tr_timerAdd( a->upkeepTimer, UPKEEP_INTERVAL_SECS, 0 );
247
248    session->announcer = a;
249}
250
251static void flushCloseMessages( tr_announcer * announcer );
252
253void
254tr_announcerClose( tr_session * session )
255{
256    tr_announcer * announcer = session->announcer;
257
258    flushCloseMessages( announcer );
259
260    evtimer_del( announcer->upkeepTimer );
261    tr_free( announcer->upkeepTimer );
262    announcer->upkeepTimer = NULL;
263
264    tr_ptrArrayDestruct( &announcer->stops, NULL );
265    tr_ptrArrayDestruct( &announcer->hosts, hostFree );
266
267    session->announcer = NULL;
268    tr_free( announcer );
269}
270
271/***
272****
273***/
274
275/* a row in tr_tier's list of trackers */
276typedef struct
277{
278    tr_host * host;
279
280    char * announce;
281    char * scrape;
282
283    char * tracker_id;
284
285    int seederCount;
286    int leecherCount;
287    int downloadCount;
288    int downloaderCount;
289
290    /* sent as the "key" argument in tracker requests
291     * to verify us if our IP address changes.
292     * This is immutable for the life of the tracker object.
293     * The +1 is for '\0' */
294    char key_param[KEYLEN + 1];
295}
296tr_tracker_item;
297
298static void
299generateKeyParam( char * msg, size_t msglen )
300{
301    size_t i;
302    const char * pool = "abcdefghijklmnopqrstuvwxyz0123456789";
303    const int poolSize = strlen( pool );
304
305    for( i=0; i<msglen; ++i )
306        *msg++ = pool[tr_cryptoRandInt( poolSize )];
307    *msg = '\0';
308}
309
310static tr_tracker_item*
311trackerNew( tr_announcer  * announcer,
312            const char    * announce,
313            const char    * scrape )
314{
315    tr_tracker_item * tracker = tr_new0( tr_tracker_item, 1  );
316    tracker->host = getHost( announcer, announce );
317    tracker->announce = tr_strdup( announce );
318    tracker->scrape = tr_strdup( scrape );
319    generateKeyParam( tracker->key_param, KEYLEN );
320    tracker->seederCount = -1;
321    tracker->leecherCount = -1;
322    tracker->downloadCount = -1;
323    return tracker;
324}
325
326static void
327trackerFree( void * vtracker )
328{
329    tr_tracker_item * tracker = vtracker;
330
331    tr_free( tracker->tracker_id );
332    tr_free( tracker->announce );
333    tr_free( tracker->scrape );
334    tr_free( tracker );
335}
336
337/***
338****
339***/
340
341struct tr_torrent_tiers;
342
343/**
344 * A group of trackers in a single tier,
345 * as per the multitracker spec
346 */
347typedef struct
348{
349    tr_ptrArray trackers; /* tr_tracker_item */
350    tr_tracker_item * currentTracker;
351
352    tr_torrent * tor;
353
354    time_t lastScrapeTime;
355    time_t lastAnnounceTime;
356    time_t lastScrapeStartTime;
357    time_t lastAnnounceStartTime;
358    tr_bool lastScrapeSucceeded;
359    tr_bool lastAnnounceSucceeded;
360
361    time_t scrapeAt;
362    time_t manualAnnounceAllowedAt;
363
364    time_t announceAt;
365    const char * announceEvent;
366
367    /* unique lookup key */
368    int key;
369
370    int currentTrackerIndex;
371    int scrapeIntervalSec;
372    int announceIntervalSec;
373    int announceMinIntervalSec;
374    int retryAnnounceIntervalSec;
375    int retryScrapeIntervalSec;
376
377    int lastAnnouncePeerCount;
378
379    tr_bool isRunning;
380    tr_bool isAnnouncing;
381    tr_bool isScraping;
382
383    char lastAnnounceStr[128];
384    char lastScrapeStr[128];
385}
386tr_tier;
387
388static tr_tier *
389tierNew( tr_torrent * tor )
390{
391    tr_tier * t;
392    static int nextKey = 1;
393    const time_t now = time( NULL );
394
395    t = tr_new0( tr_tier, 1 );
396    t->key = nextKey++;
397    t->trackers = TR_PTR_ARRAY_INIT;
398    t->currentTracker = NULL;
399    t->currentTrackerIndex = -1;
400    t->scrapeIntervalSec = DEFAULT_SCRAPE_INTERVAL_SEC;
401    t->announceIntervalSec = DEFAULT_ANNOUNCE_INTERVAL_SEC;
402    t->announceMinIntervalSec = DEFAULT_ANNOUNCE_MIN_INTERVAL_SEC;
403    t->scrapeAt = now;
404    t->tor = tor;
405
406    return t;
407}
408
409static void
410tierFree( void * vtier )
411{
412    tr_tier * tier = vtier;
413    tr_ptrArrayDestruct( &tier->trackers, trackerFree );
414    tr_free( tier );
415}
416
417static void
418tierIncrementTracker( tr_tier * tier )
419{
420    const int i = ( tier->currentTrackerIndex + 1 )
421                        % tr_ptrArraySize( &tier->trackers );
422    tier->currentTracker = tr_ptrArrayNth( &tier->trackers, i );
423    tier->currentTrackerIndex = i;
424
425    /* reset some of the tier's fields */
426    tier->scrapeIntervalSec = DEFAULT_SCRAPE_INTERVAL_SEC;
427    tier->announceIntervalSec = DEFAULT_ANNOUNCE_INTERVAL_SEC;
428    tier->announceMinIntervalSec = DEFAULT_ANNOUNCE_MIN_INTERVAL_SEC;
429    tier->retryAnnounceIntervalSec = FIRST_ANNOUNCE_RETRY_INTERVAL_SEC;
430    tier->retryScrapeIntervalSec = FIRST_SCRAPE_RETRY_INTERVAL_SEC;
431    tier->isAnnouncing = FALSE;
432    tier->isScraping = FALSE;
433    tier->lastAnnounceStartTime = 0;
434    tier->lastScrapeStartTime = 0;
435}
436
437static void
438tierAddTracker( tr_announcer * announcer,
439                tr_tier      * tier,
440                const char   * announce,
441                const char   * scrape )
442{
443    tr_tracker_item * tracker = trackerNew( announcer, announce, scrape );
444
445    tr_ptrArrayAppend( &tier->trackers, tracker );
446    dbgmsg( tier, "adding tracker %s", announce );
447
448    if( !tier->currentTracker )
449        tierIncrementTracker( tier );
450}
451
452
453/***
454****
455***/
456
457/*
458 * per-torrent info.
459 * this opaque data structure can be found in tr_torrent.tiers
460 */
461typedef struct tr_torrent_tiers
462{
463    tr_ptrArray tiers;
464    tr_publisher publisher;
465}
466tr_torrent_tiers;
467
468static tr_torrent_tiers*
469tiersNew( void )
470{
471    tr_torrent_tiers * tiers = tr_new0( tr_torrent_tiers, 1 );
472    tiers->tiers = TR_PTR_ARRAY_INIT;
473    tiers->publisher = TR_PUBLISHER_INIT;
474    return tiers;
475}
476
477static void
478tiersClear( tr_torrent_tiers * tiers )
479{
480    tr_ptrArrayDestruct( &tiers->tiers, tierFree );
481    tiers->tiers = TR_PTR_ARRAY_INIT;
482}
483
484static void
485tiersFree( tr_torrent_tiers * tiers )
486{
487    tr_publisherDestruct( &tiers->publisher );
488    tr_ptrArrayDestruct( &tiers->tiers, tierFree );
489    tr_free( tiers );
490}
491
492static tr_tier*
493getTier( tr_announcer * announcer, int torrentId, int tierId )
494{
495    tr_tier * tier = NULL;
496
497    if( announcer )
498    {
499        tr_torrent * tor = tr_torrentFindFromId( announcer->session, torrentId );
500
501        if( tor && tor->tiers )
502        {
503            int i;
504            tr_ptrArray * tiers = &tor->tiers->tiers;
505            const int n = tr_ptrArraySize( tiers );
506            for( i=0; !tier && i<n; ++i )
507            {
508                tr_tier * tmp = tr_ptrArrayNth( tiers, i );
509                if( tmp->key == tierId )
510                    tier = tmp;
511            }
512        }
513    }
514
515    return tier;
516}
517
518/***
519****  PUBLISH
520***/
521
522static const tr_tracker_event emptyEvent = { 0, NULL, NULL, 0, 0 };
523
524static void
525publishMessage( tr_tier * tier, const char * msg, int type )
526{
527    if( tier && tier->tor && tier->tor->tiers )
528    {
529        tr_torrent_tiers * tiers = tier->tor->tiers;
530        tr_tracker_event event = emptyEvent;
531        event.messageType = type;
532        event.text = msg;
533        tr_publisherPublish( &tiers->publisher, tier, &event );
534    }
535}
536
537static void
538publishErrorClear( tr_tier * tier )
539{
540    publishMessage( tier, NULL, TR_TRACKER_ERROR_CLEAR );
541}
542
543static void
544publishErrorMessageAndStop( tr_tier * tier, const char * msg )
545{
546    tier->isRunning = FALSE;
547
548    publishMessage( tier, msg, TR_TRACKER_ERROR );
549}
550
551static void
552publishWarning( tr_tier * tier, const char * msg )
553{
554    publishMessage( tier, msg, TR_TRACKER_WARNING );
555}
556
557static int
558publishNewPeers( tr_tier * tier, tr_bool allAreSeeds,
559                 const void * compact, int compactLen )
560{
561    tr_tracker_event e = emptyEvent;
562
563    e.messageType = TR_TRACKER_PEERS;
564    e.allAreSeeds = allAreSeeds;
565    e.compact = compact;
566    e.compactLen = compactLen;
567
568    if( compactLen )
569        tr_publisherPublish( &tier->tor->tiers->publisher, tier, &e );
570
571    return compactLen / 6;
572}
573
574static int
575publishNewPeersCompact( tr_tier * tier, tr_bool allAreSeeds,
576                        const void * compact, int compactLen )
577{
578    int i;
579    const uint8_t *compactWalk;
580    uint8_t *array, *walk;
581    const int peerCount = compactLen / 6;
582    const int arrayLen = peerCount * ( sizeof( tr_address ) + 2 );
583    tr_address addr;
584    tr_port port;
585
586    addr.type = TR_AF_INET;
587    memset( &addr.addr, 0, sizeof( addr.addr ) );
588    array = tr_new( uint8_t, arrayLen );
589    for ( i=0, walk=array, compactWalk=compact ; i<peerCount ; ++i )
590    {
591        memcpy( &addr.addr.addr4, compactWalk, 4 );
592        memcpy( &port, compactWalk + 4, 2 );
593
594        memcpy( walk, &addr, sizeof( addr ) );
595        memcpy( walk + sizeof( addr ), &port, 2 );
596
597        walk += sizeof( tr_address ) + 2;
598        compactWalk += 6;
599    }
600
601    publishNewPeers( tier, allAreSeeds, array, arrayLen );
602
603    tr_free( array );
604
605    return peerCount;
606}
607
608static int
609publishNewPeersCompact6( tr_tier * tier, tr_bool allAreSeeds,
610                         const void * compact, int compactLen )
611{
612    int i;
613    const uint8_t *compactWalk;
614    uint8_t *array, *walk;
615    const int peerCount = compactLen / 18;
616    const int arrayLen = peerCount * ( sizeof( tr_address ) + 2 );
617    tr_address addr;
618    tr_port port;
619
620    addr.type = TR_AF_INET6;
621    memset( &addr.addr, 0, sizeof( addr.addr ) );
622    array = tr_new( uint8_t, arrayLen );
623    for ( i = 0, walk = array, compactWalk = compact ; i < peerCount ; ++i )
624    {
625        memcpy( &addr.addr.addr6, compactWalk, 16 );
626        memcpy( &port, compactWalk + 16, 2 );
627        compactWalk += 18;
628
629        memcpy( walk, &addr, sizeof( addr ) );
630        memcpy( walk + sizeof( addr ), &port, 2 );
631        walk += sizeof( tr_address ) + 2;
632    }
633    publishNewPeers( tier, allAreSeeds, array, arrayLen );
634    tr_free( array );
635
636    return peerCount;
637}
638
639static char*
640createAnnounceURL( const tr_announcer     * announcer,
641                   const tr_torrent       * torrent,
642                   const tr_tier          * tier,
643                   const char             * eventName )
644{
645    const int isStopping = !strcmp( eventName, "stopped" );
646    const int numwant = isStopping ? 0 : NUMWANT;
647    const tr_tracker_item  * tracker = tier->currentTracker;
648    const char * ann = tracker->announce;
649    struct evbuffer * buf = evbuffer_new( );
650    char * ret;
651
652    evbuffer_add_printf( buf, "%s"
653                              "%c"
654                              "info_hash=%s"
655                              "&peer_id=%s"
656                              "&port=%d"
657                              "&uploaded=%" PRIu64
658                              "&downloaded=%" PRIu64
659                              "&left=%" PRIu64
660                              "&numwant=%d"
661                              "&key=%s"
662                              "&compact=1"
663                              "&supportcrypto=1",
664                              ann,
665                              strchr( ann, '?' ) ? '&' : '?',
666                              torrent->info.hashEscaped,
667                              torrent->peer_id,
668                              tr_sessionGetPeerPort( announcer->session ),
669                              torrent->uploadedCur,
670                              torrent->downloadedCur,
671                              tr_cpLeftUntilComplete( &torrent->completion ),
672                              numwant,
673                              tracker->key_param );
674
675    if( torrent->corruptCur )
676        evbuffer_add_printf( buf, "&corrupt=%" PRIu64, torrent->corruptCur );
677
678    if( !isStopping )
679        evbuffer_add_printf( buf, "&upload_only=%d", tr_torrentIsSeed( torrent ) ? 1 : 0 );
680
681    if( announcer->session->encryptionMode == TR_ENCRYPTION_REQUIRED )
682        evbuffer_add_printf( buf, "&requirecrypto=1" );
683
684    if( eventName && *eventName )
685        evbuffer_add_printf( buf, "&event=%s", eventName );
686
687    if( tracker->tracker_id && *tracker->tracker_id )
688        evbuffer_add_printf( buf, "&trackerid=%s", tracker->tracker_id );
689
690    ret = tr_strndup( EVBUFFER_DATA( buf ), EVBUFFER_LENGTH( buf ) );
691    evbuffer_free( buf );
692
693    dbgmsg( tier, "announce URL is \"%s\"", ret );
694    return ret;
695}
696
697
698/***
699****
700***/
701
702static tr_bool
703announceURLIsSupported( const char * announce )
704{
705    return ( announce != NULL )
706        && ( ( strstr( announce, "http://" ) == announce ) ||
707             ( strstr( announce, "https://" ) == announce ) );
708}
709
710static void
711addTorrentToTier( tr_announcer * announcer, tr_torrent_tiers * tiers, tr_torrent * tor )
712{
713    int i, n;
714    const tr_tracker_info ** infos;
715    const int trackerCount = tor->info.trackerCount;
716    const tr_tracker_info  * trackers = tor->info.trackers;
717
718    /* get the trackers that we support... */
719    infos = tr_new0( const tr_tracker_info*, trackerCount );
720    for( i=n=0; i<trackerCount; ++i )
721        if( announceURLIsSupported( trackers[i].announce ) )
722            infos[n++] = &trackers[i];
723
724    /* build our private table of tiers... */
725    if( n > 0 )
726    {
727        int tierIndex = -1;
728        tr_tier * tier = NULL;
729
730        for( i=0; i<trackerCount; ++i )
731        {
732            const tr_tracker_info * info = infos[i];
733
734            if( info->tier != tierIndex )
735                tier = NULL;
736
737            tierIndex = info->tier;
738
739            if( tier == NULL ) {
740                tier = tierNew( tor );
741                dbgmsg( tier, "adding tier" );
742                tr_ptrArrayAppend( &tiers->tiers, tier );
743            }
744
745            tierAddTracker( announcer, tier, info->announce, info->scrape );
746        }
747    }
748
749    tr_free( infos );
750}
751
752tr_torrent_tiers *
753tr_announcerAddTorrent( tr_announcer * announcer, tr_torrent * tor )
754{
755    tr_torrent_tiers * tiers;
756
757    assert( announcer != NULL );
758    assert( tr_isTorrent( tor ) );
759
760    tiers = tiersNew( );
761
762    addTorrentToTier( announcer, tiers, tor );
763
764    return tiers;
765}
766
767void
768tr_announcerResetTorrent( tr_announcer * announcer, tr_torrent * tor )
769{
770    if( tor->tiers != NULL )
771    {
772        tiersClear( tor->tiers );
773
774        addTorrentToTier( announcer, tor->tiers, tor );
775
776        if( tor->isRunning )
777            tr_announcerTorrentStarted( tor );
778    }
779}
780
781tr_publisher_tag
782tr_announcerSubscribe( struct tr_torrent_tiers   * tiers,
783                       tr_delivery_func            func,
784                       void                      * userData )
785{
786    return tr_publisherSubscribe( &tiers->publisher, func, userData );
787}
788
789void
790tr_announcerUnsubscribe( struct tr_torrent_tiers  * tiers,
791                         tr_publisher_tag           tag )
792{
793    if( tiers )
794        tr_publisherUnsubscribe( &tiers->publisher, tag );
795}
796
797static tr_bool
798tierCanManualAnnounce( const tr_tier * tier )
799{
800    return tier->isRunning
801        && tier->manualAnnounceAllowedAt <= time( NULL );
802}
803
804tr_bool
805tr_announcerCanManualAnnounce( const tr_torrent * tor )
806{
807    int i;
808    int n;
809    const tr_tier ** tiers;
810
811    assert( tr_isTorrent( tor ) );
812    assert( tor->tiers != NULL );
813
814    n = tr_ptrArraySize( &tor->tiers->tiers );
815    tiers = (const tr_tier**) tr_ptrArrayBase( &tor->tiers->tiers );
816    for( i=0; i<n; ++i )
817        if( tierCanManualAnnounce( tiers[i] ) )
818            return TRUE;
819
820    return FALSE;
821}
822
823time_t
824tr_announcerNextManualAnnounce( const tr_torrent * tor )
825{
826    int i;
827    int n;
828    const tr_torrent_tiers * tiers;
829    time_t ret = ~(time_t)0;
830
831    assert( tr_isTorrent( tor  ) );
832
833    tiers = tor->tiers;
834    n = tr_ptrArraySize( &tiers->tiers );
835    for( i=0; i<n; ++i ) {
836        tr_tier * tier = tr_ptrArrayNth( (tr_ptrArray*)&tiers->tiers, i );
837        if( tier->isRunning )
838            ret = MIN( ret, tier->manualAnnounceAllowedAt );
839    }
840
841    return ret;
842}
843
844static void
845tierClearNextAnnounce( tr_tier * tier )
846{
847    tier->announceAt = 0;
848    tier->announceEvent = NULL;
849    dbgmsg( tier, "cleared out announceEvent -- no announces pending." );
850}
851
852static void
853tierSetNextAnnounce( tr_tier * tier, const char * announceEvent, time_t announceAt )
854{
855    assert( tier != NULL );
856    assert( announceEvent != NULL );
857
858    if( !strcmp( announceEvent, "manual" ) && !tierCanManualAnnounce( tier ) )
859        return;
860
861    tier->announceAt = announceAt;
862    tier->announceEvent = announceEvent;
863    dbgmsg( tier, "next announce event is \"%s\" in %d seconds\n", announceEvent, (int)difftime(announceAt,time(NULL)) );
864}
865
866static void
867torrentSetNextAnnounce( tr_torrent * tor, const char * announceEvent, time_t announceAt )
868{
869    int i;
870    int n;
871    tr_torrent_tiers * tiers;
872
873    assert( tr_isTorrent( tor ) );
874
875    tiers = tor->tiers;
876    n = tr_ptrArraySize( &tiers->tiers );
877    for( i=0; i<n; ++i )
878        tierSetNextAnnounce( tr_ptrArrayNth( &tiers->tiers, i ), announceEvent, announceAt );
879}
880
881void
882tr_announcerManualAnnounce( tr_torrent * tor )
883{
884    torrentSetNextAnnounce( tor, "manual", time( NULL ) );
885}
886void
887tr_announcerTorrentStarted( tr_torrent * tor )
888{
889    torrentSetNextAnnounce( tor, "started", time( NULL ) );
890}
891void
892tr_announcerTorrentStopped( tr_torrent * tor )
893{
894    torrentSetNextAnnounce( tor, "stopped", time( NULL ) );
895}
896void
897tr_announcerTorrentCompleted( tr_torrent * tor )
898{
899    torrentSetNextAnnounce( tor, "completed", time( NULL ) );
900}
901void
902tr_announcerChangeMyPort( tr_torrent * tor )
903{
904    tr_announcerTorrentStarted( tor );
905}
906
907
908void
909tr_announcerRemoveTorrent( tr_announcer * announcer, tr_torrent * tor )
910{
911    assert( tr_isTorrent( tor ) );
912
913    if( tor->tiers )
914    {
915        int i;
916        const int n = tr_ptrArraySize( &tor->tiers->tiers );
917        for( i=0; i<n; ++i )
918        {
919            tr_tier * tier = tr_ptrArrayNth( &tor->tiers->tiers, i );
920
921            if( tier->isRunning )
922            {
923                struct stop_message * s = tr_new0( struct stop_message, 1 );
924                s->up = tor->uploadedCur;
925                s->down = tor->downloadedCur;
926                s->url = createAnnounceURL( announcer, tor, tier, "stopped" );
927                s->host = tier->currentTracker->host;
928                tr_ptrArrayInsertSorted( &announcer->stops, s, compareStops );
929            }
930        }
931
932        tiersFree( tor->tiers );
933        tor->tiers = NULL;
934    }
935}
936
937/* return true if (1) we've tried it recently AND (2) it didn't respond... */
938static tr_bool
939hostIsNotResponding( const tr_host * host, const time_t now )
940{
941    tr_bool b = ( host->lastRequestTime )
942             && ( host->lastRequestTime >= ( now - SLOW_HOST_PENALTY_SECS ) )
943             && ( host->lastResponseInterval > MAX_TRACKER_RESPONSE_TIME_SECS );
944    return b;
945}
946
947static tr_bool
948tierIsNotResponding( const tr_tier * tier, const time_t now )
949{
950    return !tier->currentTracker
951        || hostIsNotResponding( tier->currentTracker->host, now );
952}
953
954static int
955compareTiers( const void * va, const void * vb )
956{
957    int ret = 0;
958    tr_bool af, bf;
959    const tr_tier * a = *(const tr_tier**)va;
960    const tr_tier * b = *(const tr_tier**)vb;
961
962    /* working domains come before non-working */
963    if( !ret ) {
964        const time_t now = time( NULL );
965        af = tierIsNotResponding( a, now );
966        bf = tierIsNotResponding( b, now );
967        if( af != bf )
968            ret = !af ? -1 : 1;
969    }
970
971    /* stops come before starts */
972    if( !ret ) {
973        af = a->tor->isRunning;
974        bf = b->tor->isRunning;
975        if( af != bf )
976            ret = af ? 1 : -1;
977    }
978
979    /* upload comes before download */
980    if( !ret )
981        ret = compareTransfer( a->tor->uploadedCur, a->tor->downloadedCur,
982                               b->tor->uploadedCur, b->tor->downloadedCur );
983
984    /* incomplete comes before complete */
985    if( !ret ) {
986        af = a->tor->completeness == TR_LEECH;
987        bf = b->tor->completeness == TR_LEECH;
988        if( af != bf )
989            return af ? -1 : 1;
990    }
991
992    /* private before public */
993    if( !ret ) {
994        af = tr_torrentIsPrivate( a->tor );
995        bf = tr_torrentIsPrivate( b->tor );
996        if( af != bf )
997            ret = af ? -1 : 1;
998    }
999
1000    return ret;
1001}
1002
1003static uint8_t *
1004parseOldPeers( tr_benc * bePeers, size_t *  byteCount )
1005{
1006    int       i;
1007    uint8_t * array, *walk;
1008    const int peerCount = bePeers->val.l.count;
1009
1010    assert( tr_bencIsList( bePeers ) );
1011
1012    array = tr_new( uint8_t, peerCount * ( sizeof( tr_address ) + 2 ) );
1013
1014    for( i = 0, walk = array; i < peerCount; ++i )
1015    {
1016        const char * s;
1017        int64_t      itmp;
1018        tr_address   addr;
1019        tr_port      port;
1020        tr_benc    * peer = &bePeers->val.l.vals[i];
1021
1022        if( tr_bencDictFindStr( peer, "ip", &s ) )
1023            if( tr_pton( s, &addr ) == NULL )
1024                continue;
1025
1026        if( !tr_bencDictFindInt( peer, "port", &itmp )
1027                || itmp < 0
1028                || itmp > USHRT_MAX )
1029            continue;
1030
1031        memcpy( walk, &addr, sizeof( tr_address ) );
1032        port = htons( itmp );
1033        memcpy( walk + sizeof( tr_address ), &port, 2 );
1034        walk += sizeof( tr_address ) + 2;
1035    }
1036
1037    *byteCount = peerCount * sizeof( tr_address ) + 2;
1038    return array;
1039}
1040
1041static tr_bool
1042parseAnnounceResponse( tr_tier     * tier,
1043                       const char  * response,
1044                       size_t        responseLen,
1045                       tr_bool     * gotScrape )
1046{
1047    tr_benc benc;
1048    tr_bool success = FALSE;
1049    int scrapeFields = 0;
1050    const int bencLoaded = !tr_bencLoad( response, responseLen, &benc, NULL );
1051
1052    dbgmsg( tier, "response len: %d, isBenc: %d", (int)responseLen, (int)bencLoaded );
1053    publishErrorClear( tier );
1054    if( bencLoaded && tr_bencIsDict( &benc ) )
1055    {
1056        int incomplete = -1;
1057        size_t rawlen;
1058        int64_t i;
1059        tr_benc * tmp;
1060        const char * str;
1061        const uint8_t * raw;
1062
1063        success = TRUE;
1064
1065        tier->retryAnnounceIntervalSec = FIRST_ANNOUNCE_RETRY_INTERVAL_SEC;
1066
1067        if( tr_bencDictFindStr( &benc, "failure reason", &str ) )
1068        {
1069            tr_strlcpy( tier->lastAnnounceStr, str,
1070                        sizeof( tier->lastAnnounceStr ) );
1071            dbgmsg( tier, "tracker gave \"%s\"", str );
1072            publishMessage( tier, str, TR_TRACKER_ERROR );
1073            success = FALSE;
1074        }
1075
1076        if( tr_bencDictFindStr( &benc, "warning message", &str ) )
1077        {
1078            tr_strlcpy( tier->lastAnnounceStr, str,
1079                        sizeof( tier->lastAnnounceStr ) );
1080            dbgmsg( tier, "tracker gave \"%s\"", str );
1081            publishWarning( tier, str );
1082        }
1083
1084        if( tr_bencDictFindInt( &benc, "interval", &i ) )
1085        {
1086            dbgmsg( tier, "setting interval to %d", (int)i );
1087            tier->announceIntervalSec = i;
1088        }
1089
1090        if( tr_bencDictFindInt( &benc, "min interval", &i ) )
1091        {
1092            dbgmsg( tier, "setting min interval to %d", (int)i );
1093            tier->announceMinIntervalSec = i;
1094        }
1095
1096        if( tr_bencDictFindStr( &benc, "tracker id", &str ) )
1097        {
1098            tier->currentTracker->tracker_id = tr_strdup( str );
1099        }
1100
1101        if( tr_bencDictFindInt( &benc, "complete", &i ) )
1102        {
1103            ++scrapeFields;
1104            tier->currentTracker->seederCount = i;
1105        }
1106
1107        if( tr_bencDictFindInt( &benc, "incomplete", &i ) )
1108        {
1109            ++scrapeFields;
1110            tier->currentTracker->leecherCount = incomplete = i;
1111        }
1112
1113        if( tr_bencDictFindInt( &benc, "downloaded", &i ) )
1114        {
1115            ++scrapeFields;
1116            tier->currentTracker->downloadCount = i;
1117        }
1118
1119        if( tr_bencDictFindRaw( &benc, "peers", &raw, &rawlen ) )
1120        {
1121            /* "compact" extension */
1122            const int allAreSeeds = incomplete == 0;
1123            tier->lastAnnouncePeerCount = publishNewPeersCompact( tier, allAreSeeds, raw, rawlen );
1124        }
1125        else if( tr_bencDictFindList( &benc, "peers", &tmp ) )
1126        {
1127            /* original version of peers */
1128            const tr_bool allAreSeeds = incomplete == 0;
1129            size_t byteCount = 0;
1130            uint8_t * array = parseOldPeers( tmp, &byteCount );
1131            tier->lastAnnouncePeerCount = publishNewPeers( tier, allAreSeeds, array, byteCount );
1132            tr_free( array );
1133        }
1134
1135        if( tr_bencDictFindRaw( &benc, "peers6", &raw, &rawlen ) )
1136        {
1137            /* "compact" extension */
1138            const tr_bool allAreSeeds = incomplete == 0;
1139            tier->lastAnnouncePeerCount += publishNewPeersCompact6( tier, allAreSeeds, raw, rawlen );
1140        }
1141
1142        if( tier->lastAnnounceStr[0] == '\0' )
1143            tr_strlcpy( tier->lastAnnounceStr, _( "Success" ),
1144                        sizeof( tier->lastAnnounceStr ) );
1145    }
1146
1147    if( bencLoaded )
1148        tr_bencFree( &benc );
1149
1150    *gotScrape = scrapeFields >= 2;
1151
1152    return success;
1153}
1154
1155struct announce_data
1156{
1157    int torrentId;
1158    int tierId;
1159    time_t timeSent;
1160
1161    /** If the request succeeds, the value for tier's "isRunning" flag */
1162    tr_bool isRunningOnSuccess;
1163};
1164
1165static void
1166onAnnounceDone( tr_session   * session,
1167                long           responseCode,
1168                const void   * response,
1169                size_t         responseLen,
1170                void         * vdata )
1171{
1172    tr_announcer * announcer = session->announcer;
1173    struct announce_data * data = vdata;
1174    tr_tier * tier = getTier( announcer, data->torrentId, data->tierId );
1175    tr_bool gotScrape = FALSE;
1176    tr_bool success = FALSE;
1177    const time_t now = time ( NULL );
1178
1179    if( announcer && tier )
1180    {
1181        tier->lastAnnouncePeerCount = 0;
1182
1183        if( tier->currentTracker->host )
1184        {
1185            tr_host * host = tier->currentTracker->host;
1186            host->lastRequestTime = data->timeSent;
1187            host->lastResponseInterval = now - data->timeSent;
1188        }
1189
1190        if( responseCode == HTTP_OK )
1191        {
1192            tier->lastAnnounceTime = now;
1193            success = parseAnnounceResponse( tier, response, responseLen, &gotScrape );
1194            dbgmsg( tier, "success is %d", success );
1195        }
1196        else if( responseCode )
1197        {
1198            /* %1$ld - http status code, such as 404
1199             * %2$s - human-readable explanation of the http status code */
1200            char * buf = tr_strdup_printf(
1201                _( "Announce failed: tracker gave HTTP Response Code %1$ld (%2$s)" ),
1202                responseCode,
1203                tr_webGetResponseStr( responseCode ) );
1204
1205            tr_strlcpy( tier->lastAnnounceStr, buf,
1206                        sizeof( tier->lastAnnounceStr ) );
1207
1208            /* if the repsonse may require intervention, notify the user;
1209             * otherwise, just log it */
1210            if( responseCode >= 400 )
1211                publishWarning( tier, buf );
1212            tr_torinf( tier->tor, "%s", buf );
1213            dbgmsg( tier, "%s", buf );
1214
1215            tr_free( buf );
1216        }
1217        else
1218        {
1219            tr_strlcpy( tier->lastAnnounceStr,
1220                        _( "tracker did not respond" ),
1221                        sizeof( tier->lastAnnounceStr ) );
1222            dbgmsg( tier, "%s", tier->lastAnnounceStr );
1223        }
1224    }
1225
1226    if( tier )
1227    {
1228        tier->isAnnouncing = FALSE;
1229
1230        if( responseCode == 0 )
1231        {
1232            dbgmsg( tier, "No response from tracker... retrying in two minutes." );
1233            tier->manualAnnounceAllowedAt = ~(time_t)0;
1234            tierSetNextAnnounce( tier, tier->announceEvent, now + 120 );
1235        }
1236        else if( 200 <= responseCode && responseCode <= 299 )
1237        {
1238            const int interval = tier->announceIntervalSec;
1239            dbgmsg( tier, "request succeeded. reannouncing in %d seconds", interval );
1240
1241            if( gotScrape )
1242            {
1243                tier->lastScrapeTime = now;
1244                tier->scrapeAt = now + tier->scrapeIntervalSec;
1245            }
1246
1247            tier->manualAnnounceAllowedAt = now + tier->announceMinIntervalSec;
1248
1249            if( strcmp( tier->announceEvent, "stopped" ) == 0 )
1250                tierClearNextAnnounce( tier );
1251            else
1252                tierSetNextAnnounce( tier, "", now + interval );
1253        }
1254        else if( 300 <= responseCode && responseCode <= 399 )
1255        {
1256            /* how did this get here?  libcurl handles this */
1257            const int interval = 5;
1258            dbgmsg( tier, "got a redirect. retrying in %d seconds", interval );
1259            tierSetNextAnnounce( tier, tier->announceEvent, now + interval );
1260            tier->manualAnnounceAllowedAt = now + tier->announceMinIntervalSec;
1261        }
1262        else if( 400 <= responseCode && responseCode <= 499 )
1263        {
1264            /* The request could not be understood by the server due to
1265             * malformed syntax. The client SHOULD NOT repeat the
1266             * request without modifications. */
1267            publishErrorMessageAndStop( tier, _( "Tracker returned a 4xx message" ) );
1268            tier->manualAnnounceAllowedAt = ~(time_t)0;
1269        }
1270        else if( 500 <= responseCode && responseCode <= 599 )
1271        {
1272            /* Response status codes beginning with the digit "5" indicate
1273             * cases in which the server is aware that it has erred or is
1274             * incapable of performing the request.  So we pause a bit and
1275             * try again. */
1276            tier->manualAnnounceAllowedAt = ~(time_t)0;
1277            tierSetNextAnnounce( tier, tier->announceEvent, now + tier->retryAnnounceIntervalSec );
1278            tier->retryAnnounceIntervalSec *= 2;
1279        }
1280        else
1281        {
1282            /* WTF did we get?? */
1283            const int interval = 120;
1284            dbgmsg( tier, "Invalid response from tracker... retrying in two minutes." );
1285            tier->manualAnnounceAllowedAt = ~(time_t)0;
1286            tierSetNextAnnounce( tier, tier->announceEvent, now + interval );
1287        }
1288
1289        tier->lastAnnounceSucceeded = success;
1290
1291        if( success )
1292            tier->isRunning = data->isRunningOnSuccess;
1293
1294        if( !success )
1295            tierIncrementTracker( tier );
1296    }
1297
1298    if( announcer != NULL )
1299    {
1300        ++announcer->announceSlotsAvailable;
1301    }
1302
1303    tr_free( data );
1304}
1305
1306static const char *
1307getAnnounceEvent( const tr_tier * tier )
1308{
1309    const char * event;
1310
1311    assert( tier != NULL );
1312    assert( tier->announceEvent != NULL );
1313    assert( tr_isTorrent( tier->tor ) );
1314
1315    /* BEP 21: "In order to tell the tracker that a peer is a partial seed,
1316     * it MUST send an event=paused parameter in every announce while
1317     * it is a partial seed." */
1318    if( tr_cpGetStatus( &tier->tor->completion ) == TR_PARTIAL_SEED )
1319        return "paused";
1320
1321    event = tier->announceEvent;
1322
1323    if( !strcmp( event, "manual" ) )
1324        return "started";
1325
1326    return event;
1327}
1328
1329static void
1330tierAnnounce( tr_announcer * announcer, tr_tier * tier )
1331{
1332    char * url;
1333    struct announce_data * data;
1334    const tr_torrent * tor = tier->tor;
1335    const time_t now = time( NULL );
1336
1337    assert( !tier->isAnnouncing );
1338
1339    data = tr_new0( struct announce_data, 1 );
1340    data->torrentId = tr_torrentId( tor );
1341    data->tierId = tier->key;
1342    data->isRunningOnSuccess = tor->isRunning;
1343    data->timeSent = now;
1344
1345    url = createAnnounceURL( announcer, tor, tier, getAnnounceEvent( tier ) );
1346
1347    tier->isAnnouncing = TRUE;
1348    tier->lastAnnounceStartTime = now;
1349    --announcer->announceSlotsAvailable;
1350    tr_webRun( announcer->session, url, NULL, onAnnounceDone, data );
1351
1352    tr_free( url );
1353}
1354
1355static tr_bool
1356parseScrapeResponse( tr_tier     * tier,
1357                     const char  * response,
1358                     size_t        responseLen,
1359                     char        * result,
1360                     size_t        resultlen )
1361{
1362    tr_bool success = FALSE;
1363    tr_benc benc, *files;
1364    const int bencLoaded = !tr_bencLoad( response, responseLen, &benc, NULL );
1365    if( bencLoaded && tr_bencDictFindDict( &benc, "files", &files ) )
1366    {
1367        const char * key;
1368        tr_benc * val;
1369        int i = 0;
1370        while( tr_bencDictChild( files, i++, &key, &val ))
1371        {
1372            int64_t intVal;
1373            tr_benc * flags;
1374
1375            if( memcmp( tier->tor->info.hash, key, SHA_DIGEST_LENGTH ) )
1376                continue;
1377
1378            success = TRUE;
1379            publishErrorClear( tier );
1380
1381            if( ( tr_bencDictFindInt( val, "complete", &intVal ) ) )
1382                tier->currentTracker->seederCount = intVal;
1383
1384            if( ( tr_bencDictFindInt( val, "incomplete", &intVal ) ) )
1385                tier->currentTracker->leecherCount = intVal;
1386
1387            if( ( tr_bencDictFindInt( val, "downloaded", &intVal ) ) )
1388                tier->currentTracker->downloadCount = intVal;
1389
1390            if( ( tr_bencDictFindInt( val, "downloaders", &intVal ) ) )
1391                tier->currentTracker->downloaderCount = intVal;
1392
1393            if( tr_bencDictFindDict( val, "flags", &flags ) )
1394                if( ( tr_bencDictFindInt( flags, "min_request_interval", &intVal ) ) )
1395                    tier->scrapeIntervalSec = intVal;
1396
1397            /* as per ticket #1045, safeguard against trackers returning
1398             * a very low min_request_interval... */
1399            if( tier->scrapeIntervalSec < DEFAULT_SCRAPE_INTERVAL_SEC )
1400                tier->scrapeIntervalSec = DEFAULT_SCRAPE_INTERVAL_SEC;
1401
1402            tr_tordbg( tier->tor,
1403                       "Scrape successful. Rescraping in %d seconds.",
1404                       tier->scrapeIntervalSec );
1405
1406            tier->retryScrapeIntervalSec = FIRST_SCRAPE_RETRY_INTERVAL_SEC;
1407        }
1408    }
1409
1410    if( bencLoaded )
1411        tr_bencFree( &benc );
1412
1413    if( success )
1414        tr_strlcpy( result, _( "Success" ), resultlen );
1415    else
1416        tr_strlcpy( result, _( "Error parsing response" ), resultlen );
1417
1418    return success;
1419}
1420
1421static void
1422onScrapeDone( tr_session   * session,
1423              long           responseCode,
1424              const void   * response,
1425              size_t         responseLen,
1426              void         * vdata )
1427{
1428    tr_announcer * announcer = session->announcer;
1429    struct announce_data * data = vdata;
1430    tr_tier * tier = getTier( announcer, data->torrentId, data->tierId );
1431    const time_t now = time( NULL );
1432    tr_bool success = FALSE;
1433
1434    if( announcer )
1435        ++announcer->scrapeSlotsAvailable;
1436
1437    if( announcer && tier )
1438    {
1439        tier->isScraping = FALSE;
1440        tier->lastScrapeTime = now;
1441   
1442        if( tier->currentTracker->host )
1443        {
1444            tr_host * host = tier->currentTracker->host;
1445            host->lastRequestTime = data->timeSent;
1446            host->lastResponseInterval = now - data->timeSent;
1447        }
1448
1449        if( 200 <= responseCode && responseCode <= 299 )
1450        {
1451            const int interval = tier->scrapeIntervalSec;
1452            tier->scrapeAt = now + interval;
1453
1454            if( responseCode == HTTP_OK )
1455                success = parseScrapeResponse( tier, response, responseLen,
1456                                               tier->lastScrapeStr, sizeof( tier->lastScrapeStr ) );
1457            else
1458                tr_snprintf( tier->lastScrapeStr, sizeof( tier->lastScrapeStr ),
1459                             _( "tracker gave HTTP Response Code %1$ld (%2$s)" ),
1460                             responseCode, tr_webGetResponseStr( responseCode ) );
1461            tr_tordbg( tier->tor, "%s", tier->lastScrapeStr );
1462        }
1463        else if( 300 <= responseCode && responseCode <= 399 )
1464        {
1465            /* this shouldn't happen; libcurl should handle this */
1466            const int interval = 5;
1467            tier->scrapeAt = now + interval;
1468            tr_snprintf( tier->lastScrapeStr, sizeof( tier->lastScrapeStr ),
1469                         "Got a redirect. Retrying in %d seconds", interval );
1470            tr_tordbg( tier->tor, "%s", tier->lastScrapeStr );
1471        }
1472        else
1473        {
1474            const int interval = tier->retryScrapeIntervalSec;
1475            tier->retryScrapeIntervalSec *= 2;
1476            tier->scrapeAt = now + interval;
1477
1478            /* %1$ld - http status code, such as 404
1479             * %2$s - human-readable explanation of the http status code */
1480            if( !responseCode )
1481                tr_strlcpy( tier->lastScrapeStr, _( "tracker did not respond" ),
1482                            sizeof( tier->lastScrapeStr ) );
1483            else
1484                tr_snprintf( tier->lastScrapeStr, sizeof( tier->lastScrapeStr ),
1485                             _( "tracker gave HTTP Response Code %1$ld (%2$s)" ),
1486                             responseCode, tr_webGetResponseStr( responseCode ) );
1487        }
1488
1489        tier->lastScrapeSucceeded = success;
1490    }
1491
1492    tr_free( data );
1493}
1494
1495static void
1496tierScrape( tr_announcer * announcer, tr_tier * tier )
1497{
1498    const char * scrape;
1499    struct evbuffer * buf;
1500    struct announce_data * data;
1501    const time_t now = time( NULL );
1502
1503    assert( tier );
1504    assert( !tier->isScraping );
1505    assert( tier->currentTracker != NULL );
1506    assert( tr_isTorrent( tier->tor ) );
1507
1508    data = tr_new0( struct announce_data, 1 );
1509    data->torrentId = tr_torrentId( tier->tor );
1510    data->tierId = tier->key;
1511
1512    scrape = tier->currentTracker->scrape;
1513
1514    buf = evbuffer_new( );
1515    evbuffer_add_printf( buf, "%s%cinfo_hash=%s",
1516                         scrape,
1517                         strchr( scrape, '?' ) ? '&' : '?',
1518                         tier->tor->info.hashEscaped );
1519
1520    tier->isScraping = TRUE;
1521    tier->lastScrapeStartTime = now;
1522    --announcer->scrapeSlotsAvailable;
1523    dbgmsg( tier, "scraping \"%s\"", (const char*)EVBUFFER_DATA(buf) );
1524    tr_webRun( announcer->session, (const char*)EVBUFFER_DATA(buf), NULL, onScrapeDone, data );
1525
1526    evbuffer_free( buf );
1527}
1528
1529static void
1530flushCloseMessages( tr_announcer * announcer )
1531{
1532    int i;
1533    const int n = tr_ptrArraySize( &announcer->stops );
1534
1535    for( i=0; i<n; ++i )
1536    {
1537        struct stop_message * stop = tr_ptrArrayNth( &announcer->stops, i );
1538        tr_webRun( announcer->session, stop->url, NULL, NULL, NULL );
1539        stopFree( stop );
1540    }
1541
1542    tr_ptrArrayClear( &announcer->stops );
1543}
1544
1545static tr_bool
1546tierNeedsToAnnounce( const tr_tier * tier, const time_t now )
1547{
1548    return !tier->isAnnouncing
1549        && !tier->isScraping
1550        && ( tier->announceEvent != NULL )
1551        && ( tier->announceAt <= now );
1552}
1553
1554static tr_bool
1555tierNeedsToScrape( const tr_tier * tier, const time_t now )
1556{
1557    return !tier->isScraping
1558        && ( tier->scrapeAt <= now )
1559        && ( tier->currentTracker != NULL )
1560        && ( tier->currentTracker->scrape != NULL );
1561}
1562
1563static void
1564announceMore( tr_announcer * announcer )
1565{
1566    const tr_bool canAnnounce = announcer->announceSlotsAvailable > 0;
1567    const tr_bool canScrape = announcer->scrapeSlotsAvailable > 0;
1568
1569    if( announcer->announceSlotsAvailable > 0 )
1570    {
1571        int i;
1572        int n;
1573        const time_t now = time( NULL );
1574        tr_torrent * tor = NULL;
1575        tr_ptrArray announceMe = TR_PTR_ARRAY_INIT;
1576        tr_ptrArray scrapeMe = TR_PTR_ARRAY_INIT;
1577
1578        /* build a list of tiers that need to be announced */
1579        while(( tor = tr_torrentNext( announcer->session, tor ))) {
1580            if( tor->tiers ) {
1581                n = tr_ptrArraySize( &tor->tiers->tiers );
1582                for( i=0; i<n; ++i ) {
1583                    tr_tier * tier = tr_ptrArrayNth( &tor->tiers->tiers, i );
1584                    if( canAnnounce && tierNeedsToAnnounce( tier, now ) )
1585                        tr_ptrArrayAppend( &announceMe, tier );
1586                    else if( canScrape && tierNeedsToScrape( tier, now ) )
1587                        tr_ptrArrayAppend( &scrapeMe, tier );
1588                }
1589            }
1590        }
1591
1592        /* if there are more tiers than slots available, prioritize */
1593        n = tr_ptrArraySize( &announceMe );
1594        if( n > announcer->announceSlotsAvailable )
1595            qsort( tr_ptrArrayBase( &announceMe ), n, sizeof( tr_tier * ), compareTiers );
1596
1597        /* announce some */
1598        n = MIN( tr_ptrArraySize( &announceMe ), announcer->announceSlotsAvailable );
1599        for( i=0; i<n; ++i ) {
1600            tr_tier * tier = tr_ptrArrayNth( &announceMe, i );
1601            dbgmsg( tier, "announcing tier %d of %d", i, n );
1602            tierAnnounce( announcer, tier );
1603        }
1604
1605
1606        /* scrape some */
1607        n = MIN( tr_ptrArraySize( &scrapeMe ), announcer->scrapeSlotsAvailable );
1608        for( i=0; i<n; ++i ) {
1609            tr_tier * tier = tr_ptrArrayNth( &scrapeMe, i );
1610            dbgmsg( tier, "scraping tier %d of %d", (i+1), n );
1611            tierScrape( announcer, tier );
1612        }
1613
1614        /* cleanup */
1615        tr_ptrArrayDestruct( &scrapeMe, NULL );
1616        tr_ptrArrayDestruct( &announceMe, NULL );
1617    }
1618}
1619
1620static void
1621onUpkeepTimer( int foo UNUSED, short bar UNUSED, void * vannouncer )
1622{
1623    tr_announcer * announcer = vannouncer;
1624
1625    /* maybe send out some "stopped" messages for closed torrents */
1626    flushCloseMessages( announcer );
1627
1628    /* maybe send out some announcements to trackers */
1629    announceMore( announcer );
1630
1631    /* set up the next timer */
1632    tr_timerAdd( announcer->upkeepTimer, UPKEEP_INTERVAL_SECS, 0 );
1633}
1634
1635/***
1636****
1637***/
1638
1639tr_tracker_stat *
1640tr_announcerStats( const tr_torrent * torrent,
1641                   int              * setmeTrackerCount )
1642{
1643    int i;
1644    int n;
1645    int out = 0;
1646    int tierCount;
1647    tr_tracker_stat * ret;
1648    const time_t now = time( NULL );
1649
1650    assert( tr_isTorrent( torrent ) );
1651
1652    /* count the trackers... */
1653    for( i=n=0, tierCount=tr_ptrArraySize( &torrent->tiers->tiers ); i<tierCount; ++i ) {
1654        const tr_tier * tier = tr_ptrArrayNth( &torrent->tiers->tiers, i );
1655        n += tr_ptrArraySize( &tier->trackers );
1656    }
1657
1658    /* alloc the stats */
1659    *setmeTrackerCount = n;
1660    ret = tr_new0( tr_tracker_stat, n );
1661
1662    /* populate the stats */
1663    for( i=0, tierCount=tr_ptrArraySize( &torrent->tiers->tiers ); i<tierCount; ++i )
1664    {
1665        int j;
1666        const tr_tier * tier = tr_ptrArrayNth( &torrent->tiers->tiers, i );
1667        n = tr_ptrArraySize( &tier->trackers );
1668        for( j=0; j<n; ++j )
1669        {
1670            const tr_tracker_item * tracker = tr_ptrArrayNth( (tr_ptrArray*)&tier->trackers, j );
1671            tr_tracker_stat * st = ret + out++;
1672
1673            tr_strlcpy( st->host, tracker->host->name, sizeof( st->host ) );
1674            tr_strlcpy( st->announce, tracker->announce, sizeof( st->announce ) );
1675            st->tier = i + 1;
1676            st->isBackup = tracker != tier->currentTracker;
1677            st->lastScrapeStartTime = tier->lastScrapeStartTime;
1678
1679            if( st->isBackup )
1680            {
1681                st->scrapeState = TR_TRACKER_INACTIVE;
1682                st->announceState = TR_TRACKER_INACTIVE;
1683                st->nextScrapeTime = 0;
1684                st->nextAnnounceTime = 0;
1685            }
1686            else
1687            {
1688                if(( st->hasScraped = tier->lastScrapeTime != 0 )) {
1689                    st->lastScrapeTime = tier->lastScrapeTime;
1690                    st->lastScrapeSucceeded = tier->lastScrapeSucceeded;
1691                    tr_strlcpy( st->lastScrapeResult, tier->lastScrapeStr, sizeof( st->lastScrapeResult ) );
1692                }
1693
1694                if( tier->isScraping )
1695                    st->scrapeState = TR_TRACKER_ACTIVE;
1696                else if( tier->scrapeAt > now )
1697                {
1698                    st->scrapeState = TR_TRACKER_WAITING;
1699                    st->nextScrapeTime = tier->scrapeAt; 
1700                }
1701                else
1702                    st->scrapeState = TR_TRACKER_QUEUED;
1703
1704                st->lastAnnounceStartTime = tier->lastAnnounceStartTime;
1705
1706                if(( st->hasAnnounced = tier->lastAnnounceTime != 0 )) {
1707                    st->lastAnnounceTime = tier->lastAnnounceTime;
1708                    tr_strlcpy( st->lastAnnounceResult, tier->lastAnnounceStr, sizeof( st->lastAnnounceResult ) );
1709                    if(( st->lastAnnounceSucceeded = tier->lastAnnounceSucceeded )) {
1710                        st->lastAnnouncePeerCount = tier->lastAnnouncePeerCount;
1711                    }
1712                }
1713
1714                if( tier->isAnnouncing )
1715                    st->announceState = TR_TRACKER_ACTIVE;
1716                else if( !torrent->isRunning )
1717                    st->announceState = TR_TRACKER_INACTIVE;
1718                else if( tier->announceAt > now )
1719                {
1720                    st->announceState = TR_TRACKER_WAITING;
1721                    st->nextAnnounceTime = tier->announceAt; 
1722                }
1723                else
1724                    st->announceState = TR_TRACKER_QUEUED;
1725
1726                st->seederCount = tracker->seederCount;
1727                st->leecherCount = tracker->leecherCount;
1728                st->downloadCount = tracker->downloadCount;
1729            }
1730        }
1731    }
1732
1733    return ret;
1734}
1735
1736void
1737tr_announcerStatsFree( tr_tracker_stat * trackers,
1738                       int trackerCount UNUSED )
1739{
1740    tr_free( trackers );
1741}
Note: See TracBrowser for help on using the repository browser.