source: trunk/libtransmission/announcer.c @ 9278

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

(trunk libT) #2463 if at first you don't succeed...

File size: 48.7 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}
777
778tr_publisher_tag
779tr_announcerSubscribe( struct tr_torrent_tiers   * tiers,
780                       tr_delivery_func            func,
781                       void                      * userData )
782{
783    return tr_publisherSubscribe( &tiers->publisher, func, userData );
784}
785
786void
787tr_announcerUnsubscribe( struct tr_torrent_tiers  * tiers,
788                         tr_publisher_tag           tag )
789{
790    if( tiers )
791        tr_publisherUnsubscribe( &tiers->publisher, tag );
792}
793
794static tr_bool
795tierCanManualAnnounce( const tr_tier * tier )
796{
797    return tier->isRunning
798        && tier->manualAnnounceAllowedAt <= time( NULL );
799}
800
801tr_bool
802tr_announcerCanManualAnnounce( const tr_torrent * tor )
803{
804    int i;
805    int n;
806    const tr_tier ** tiers;
807
808    assert( tr_isTorrent( tor ) );
809    assert( tor->tiers != NULL );
810
811    n = tr_ptrArraySize( &tor->tiers->tiers );
812    tiers = (const tr_tier**) tr_ptrArrayBase( &tor->tiers->tiers );
813    for( i=0; i<n; ++i )
814        if( tierCanManualAnnounce( tiers[i] ) )
815            return TRUE;
816
817    return FALSE;
818}
819
820time_t
821tr_announcerNextManualAnnounce( const tr_torrent * tor )
822{
823    int i;
824    int n;
825    const tr_torrent_tiers * tiers;
826    time_t ret = ~(time_t)0;
827
828    assert( tr_isTorrent( tor  ) );
829
830    tiers = tor->tiers;
831    n = tr_ptrArraySize( &tiers->tiers );
832    for( i=0; i<n; ++i ) {
833        tr_tier * tier = tr_ptrArrayNth( (tr_ptrArray*)&tiers->tiers, i );
834        if( tier->isRunning )
835            ret = MIN( ret, tier->manualAnnounceAllowedAt );
836    }
837
838    return ret;
839}
840
841static void
842tierClearNextAnnounce( tr_tier * tier )
843{
844    tier->announceAt = 0;
845    tier->announceEvent = NULL;
846    dbgmsg( tier, "cleared out announceEvent -- no announces pending." );
847}
848
849static void
850tierSetNextAnnounce( tr_tier * tier, const char * announceEvent, time_t announceAt )
851{
852    assert( tier != NULL );
853    assert( announceEvent != NULL );
854
855    if( !strcmp( announceEvent, "manual" ) && !tierCanManualAnnounce( tier ) )
856        return;
857
858    tier->announceAt = announceAt;
859    tier->announceEvent = announceEvent;
860    dbgmsg( tier, "next announce event is \"%s\" in %d seconds\n", announceEvent, (int)difftime(announceAt,time(NULL)) );
861}
862
863static void
864torrentSetNextAnnounce( tr_torrent * tor, const char * announceEvent, time_t announceAt )
865{
866    int i;
867    int n;
868    tr_torrent_tiers * tiers;
869
870    assert( tr_isTorrent( tor ) );
871
872    tiers = tor->tiers;
873    n = tr_ptrArraySize( &tiers->tiers );
874    for( i=0; i<n; ++i )
875        tierSetNextAnnounce( tr_ptrArrayNth( &tiers->tiers, i ), announceEvent, announceAt );
876}
877
878void
879tr_announcerManualAnnounce( tr_torrent * tor )
880{
881    torrentSetNextAnnounce( tor, "manual", time( NULL ) );
882}
883void
884tr_announcerTorrentStarted( tr_torrent * tor )
885{
886    torrentSetNextAnnounce( tor, "started", time( NULL ) );
887}
888void
889tr_announcerTorrentStopped( tr_torrent * tor )
890{
891    torrentSetNextAnnounce( tor, "stopped", time( NULL ) );
892}
893void
894tr_announcerTorrentCompleted( tr_torrent * tor )
895{
896    torrentSetNextAnnounce( tor, "completed", time( NULL ) );
897}
898void
899tr_announcerChangeMyPort( tr_torrent * tor )
900{
901    tr_announcerTorrentStarted( tor );
902}
903
904
905void
906tr_announcerRemoveTorrent( tr_announcer * announcer, tr_torrent * tor )
907{
908    assert( tr_isTorrent( tor ) );
909
910    if( tor->tiers )
911    {
912        int i;
913        const int n = tr_ptrArraySize( &tor->tiers->tiers );
914        for( i=0; i<n; ++i )
915        {
916            tr_tier * tier = tr_ptrArrayNth( &tor->tiers->tiers, i );
917
918            if( tier->isRunning )
919            {
920                struct stop_message * s = tr_new0( struct stop_message, 1 );
921                s->up = tor->uploadedCur;
922                s->down = tor->downloadedCur;
923                s->url = createAnnounceURL( announcer, tor, tier, "stopped" );
924                s->host = tier->currentTracker->host;
925                tr_ptrArrayInsertSorted( &announcer->stops, s, compareStops );
926            }
927        }
928
929        tiersFree( tor->tiers );
930        tor->tiers = NULL;
931    }
932}
933
934/* return true if (1) we've tried it recently AND (2) it didn't respond... */
935static tr_bool
936hostIsNotResponding( const tr_host * host, const time_t now )
937{
938    tr_bool b = ( host->lastRequestTime )
939             && ( host->lastRequestTime >= ( now - SLOW_HOST_PENALTY_SECS ) )
940             && ( host->lastResponseInterval > MAX_TRACKER_RESPONSE_TIME_SECS );
941    return b;
942}
943
944static tr_bool
945tierIsNotResponding( const tr_tier * tier, const time_t now )
946{
947    return !tier->currentTracker
948        || hostIsNotResponding( tier->currentTracker->host, now );
949}
950
951static int
952compareTiers( const void * va, const void * vb )
953{
954    int ret = 0;
955    tr_bool af, bf;
956    const tr_tier * a = *(const tr_tier**)va;
957    const tr_tier * b = *(const tr_tier**)vb;
958
959    /* working domains come before non-working */
960    if( !ret ) {
961        const time_t now = time( NULL );
962        af = tierIsNotResponding( a, now );
963        bf = tierIsNotResponding( b, now );
964        if( af != bf )
965            ret = !af ? -1 : 1;
966    }
967
968    /* stops come before starts */
969    if( !ret ) {
970        af = a->tor->isRunning;
971        bf = b->tor->isRunning;
972        if( af != bf )
973            ret = af ? 1 : -1;
974    }
975
976    /* upload comes before download */
977    if( !ret )
978        ret = compareTransfer( a->tor->uploadedCur, a->tor->downloadedCur,
979                               b->tor->uploadedCur, b->tor->downloadedCur );
980
981    /* incomplete comes before complete */
982    if( !ret ) {
983        af = a->tor->completeness == TR_LEECH;
984        bf = b->tor->completeness == TR_LEECH;
985        if( af != bf )
986            return af ? -1 : 1;
987    }
988
989    /* private before public */
990    if( !ret ) {
991        af = tr_torrentIsPrivate( a->tor );
992        bf = tr_torrentIsPrivate( b->tor );
993        if( af != bf )
994            ret = af ? -1 : 1;
995    }
996
997    return ret;
998}
999
1000static uint8_t *
1001parseOldPeers( tr_benc * bePeers, size_t *  byteCount )
1002{
1003    int       i;
1004    uint8_t * array, *walk;
1005    const int peerCount = bePeers->val.l.count;
1006
1007    assert( tr_bencIsList( bePeers ) );
1008
1009    array = tr_new( uint8_t, peerCount * ( sizeof( tr_address ) + 2 ) );
1010
1011    for( i = 0, walk = array; i < peerCount; ++i )
1012    {
1013        const char * s;
1014        int64_t      itmp;
1015        tr_address   addr;
1016        tr_port      port;
1017        tr_benc    * peer = &bePeers->val.l.vals[i];
1018
1019        if( tr_bencDictFindStr( peer, "ip", &s ) )
1020            if( tr_pton( s, &addr ) == NULL )
1021                continue;
1022
1023        if( !tr_bencDictFindInt( peer, "port", &itmp )
1024                || itmp < 0
1025                || itmp > USHRT_MAX )
1026            continue;
1027
1028        memcpy( walk, &addr, sizeof( tr_address ) );
1029        port = htons( itmp );
1030        memcpy( walk + sizeof( tr_address ), &port, 2 );
1031        walk += sizeof( tr_address ) + 2;
1032    }
1033
1034    *byteCount = peerCount * sizeof( tr_address ) + 2;
1035    return array;
1036}
1037
1038static tr_bool
1039parseAnnounceResponse( tr_tier     * tier,
1040                       const char  * response,
1041                       size_t        responseLen,
1042                       tr_bool     * gotScrape )
1043{
1044    tr_benc benc;
1045    tr_bool success = FALSE;
1046    int scrapeFields = 0;
1047    const int bencLoaded = !tr_bencLoad( response, responseLen, &benc, NULL );
1048
1049    dbgmsg( tier, "response len: %d, isBenc: %d", (int)responseLen, (int)bencLoaded );
1050    publishErrorClear( tier );
1051    if( bencLoaded && tr_bencIsDict( &benc ) )
1052    {
1053        int incomplete = -1;
1054        size_t rawlen;
1055        int64_t i;
1056        tr_benc * tmp;
1057        const char * str;
1058        const uint8_t * raw;
1059
1060        success = TRUE;
1061
1062        tier->retryAnnounceIntervalSec = FIRST_ANNOUNCE_RETRY_INTERVAL_SEC;
1063
1064        if( tr_bencDictFindStr( &benc, "failure reason", &str ) )
1065        {
1066            tr_strlcpy( tier->lastAnnounceStr, str,
1067                        sizeof( tier->lastAnnounceStr ) );
1068            dbgmsg( tier, "tracker gave \"%s\"", str );
1069            publishMessage( tier, str, TR_TRACKER_ERROR );
1070            success = FALSE;
1071        }
1072
1073        if( tr_bencDictFindStr( &benc, "warning message", &str ) )
1074        {
1075            tr_strlcpy( tier->lastAnnounceStr, str,
1076                        sizeof( tier->lastAnnounceStr ) );
1077            dbgmsg( tier, "tracker gave \"%s\"", str );
1078            publishWarning( tier, str );
1079        }
1080
1081        if( tr_bencDictFindInt( &benc, "interval", &i ) )
1082        {
1083            dbgmsg( tier, "setting interval to %d", (int)i );
1084            tier->announceIntervalSec = i;
1085        }
1086
1087        if( tr_bencDictFindInt( &benc, "min interval", &i ) )
1088        {
1089            dbgmsg( tier, "setting min interval to %d", (int)i );
1090            tier->announceMinIntervalSec = i;
1091        }
1092
1093        if( tr_bencDictFindStr( &benc, "tracker id", &str ) )
1094        {
1095            tier->currentTracker->tracker_id = tr_strdup( str );
1096        }
1097
1098        if( tr_bencDictFindInt( &benc, "complete", &i ) )
1099        {
1100            ++scrapeFields;
1101            tier->currentTracker->seederCount = i;
1102        }
1103
1104        if( tr_bencDictFindInt( &benc, "incomplete", &i ) )
1105        {
1106            ++scrapeFields;
1107            tier->currentTracker->leecherCount = incomplete = i;
1108        }
1109
1110        if( tr_bencDictFindInt( &benc, "downloaded", &i ) )
1111        {
1112            ++scrapeFields;
1113            tier->currentTracker->downloadCount = i;
1114        }
1115
1116        if( tr_bencDictFindRaw( &benc, "peers", &raw, &rawlen ) )
1117        {
1118            /* "compact" extension */
1119            const int allAreSeeds = incomplete == 0;
1120            tier->lastAnnouncePeerCount = publishNewPeersCompact( tier, allAreSeeds, raw, rawlen );
1121        }
1122        else if( tr_bencDictFindList( &benc, "peers", &tmp ) )
1123        {
1124            /* original version of peers */
1125            const tr_bool allAreSeeds = incomplete == 0;
1126            size_t byteCount = 0;
1127            uint8_t * array = parseOldPeers( tmp, &byteCount );
1128            tier->lastAnnouncePeerCount = publishNewPeers( tier, allAreSeeds, array, byteCount );
1129            tr_free( array );
1130        }
1131
1132        if( tr_bencDictFindRaw( &benc, "peers6", &raw, &rawlen ) )
1133        {
1134            /* "compact" extension */
1135            const tr_bool allAreSeeds = incomplete == 0;
1136            tier->lastAnnouncePeerCount += publishNewPeersCompact6( tier, allAreSeeds, raw, rawlen );
1137        }
1138
1139        if( tier->lastAnnounceStr[0] == '\0' )
1140            tr_strlcpy( tier->lastAnnounceStr, _( "Success" ),
1141                        sizeof( tier->lastAnnounceStr ) );
1142    }
1143
1144    if( bencLoaded )
1145        tr_bencFree( &benc );
1146
1147    *gotScrape = scrapeFields >= 2;
1148
1149    return success;
1150}
1151
1152struct announce_data
1153{
1154    int torrentId;
1155    int tierId;
1156    time_t timeSent;
1157
1158    /** If the request succeeds, the value for tier's "isRunning" flag */
1159    tr_bool isRunningOnSuccess;
1160};
1161
1162static void
1163onAnnounceDone( tr_session   * session,
1164                long           responseCode,
1165                const void   * response,
1166                size_t         responseLen,
1167                void         * vdata )
1168{
1169    tr_announcer * announcer = session->announcer;
1170    struct announce_data * data = vdata;
1171    tr_tier * tier = getTier( announcer, data->torrentId, data->tierId );
1172    tr_bool gotScrape = FALSE;
1173    tr_bool success = FALSE;
1174    const time_t now = time ( NULL );
1175
1176    if( announcer && tier )
1177    {
1178        tier->lastAnnouncePeerCount = 0;
1179
1180        if( tier->currentTracker->host )
1181        {
1182            tr_host * host = tier->currentTracker->host;
1183            host->lastRequestTime = data->timeSent;
1184            host->lastResponseInterval = now - data->timeSent;
1185        }
1186
1187        if( responseCode == HTTP_OK )
1188        {
1189            tier->lastAnnounceTime = now;
1190            success = parseAnnounceResponse( tier, response, responseLen, &gotScrape );
1191            dbgmsg( tier, "success is %d", success );
1192        }
1193        else if( responseCode )
1194        {
1195            /* %1$ld - http status code, such as 404
1196             * %2$s - human-readable explanation of the http status code */
1197            char * buf = tr_strdup_printf(
1198                _( "Announce failed: tracker gave HTTP Response Code %1$ld (%2$s)" ),
1199                responseCode,
1200                tr_webGetResponseStr( responseCode ) );
1201
1202            tr_strlcpy( tier->lastAnnounceStr, buf,
1203                        sizeof( tier->lastAnnounceStr ) );
1204
1205            /* if the repsonse may require intervention, notify the user;
1206             * otherwise, just log it */
1207            if( responseCode >= 400 )
1208                publishWarning( tier, buf );
1209            tr_torinf( tier->tor, "%s", buf );
1210            dbgmsg( tier, "%s", buf );
1211
1212            tr_free( buf );
1213        }
1214        else
1215        {
1216            tr_strlcpy( tier->lastAnnounceStr,
1217                        _( "tracker did not respond" ),
1218                        sizeof( tier->lastAnnounceStr ) );
1219            dbgmsg( tier, "%s", tier->lastAnnounceStr );
1220        }
1221    }
1222
1223    if( tier )
1224    {
1225        tier->isAnnouncing = FALSE;
1226
1227        if( responseCode == 0 )
1228        {
1229            dbgmsg( tier, "No response from tracker... retrying in two minutes." );
1230            tier->manualAnnounceAllowedAt = ~(time_t)0;
1231            tierSetNextAnnounce( tier, tier->announceEvent, now + 120 );
1232        }
1233        else if( 200 <= responseCode && responseCode <= 299 )
1234        {
1235            const int interval = tier->announceIntervalSec;
1236            dbgmsg( tier, "request succeeded. reannouncing in %d seconds", interval );
1237
1238            if( gotScrape )
1239            {
1240                tier->lastScrapeTime = now;
1241                tier->scrapeAt = now + tier->scrapeIntervalSec;
1242            }
1243
1244            tier->manualAnnounceAllowedAt = now + tier->announceMinIntervalSec;
1245
1246            if( strcmp( tier->announceEvent, "stopped" ) == 0 )
1247                tierClearNextAnnounce( tier );
1248            else
1249                tierSetNextAnnounce( tier, "", now + interval );
1250        }
1251        else if( 300 <= responseCode && responseCode <= 399 )
1252        {
1253            /* how did this get here?  libcurl handles this */
1254            const int interval = 5;
1255            dbgmsg( tier, "got a redirect. retrying in %d seconds", interval );
1256            tierSetNextAnnounce( tier, tier->announceEvent, now + interval );
1257            tier->manualAnnounceAllowedAt = now + tier->announceMinIntervalSec;
1258        }
1259        else if( 400 <= responseCode && responseCode <= 499 )
1260        {
1261            /* The request could not be understood by the server due to
1262             * malformed syntax. The client SHOULD NOT repeat the
1263             * request without modifications. */
1264            publishErrorMessageAndStop( tier, _( "Tracker returned a 4xx message" ) );
1265            tier->manualAnnounceAllowedAt = ~(time_t)0;
1266        }
1267        else if( 500 <= responseCode && responseCode <= 599 )
1268        {
1269            /* Response status codes beginning with the digit "5" indicate
1270             * cases in which the server is aware that it has erred or is
1271             * incapable of performing the request.  So we pause a bit and
1272             * try again. */
1273            tier->manualAnnounceAllowedAt = ~(time_t)0;
1274            tierSetNextAnnounce( tier, tier->announceEvent, now + tier->retryAnnounceIntervalSec );
1275            tier->retryAnnounceIntervalSec *= 2;
1276        }
1277        else
1278        {
1279            /* WTF did we get?? */
1280            const int interval = 120;
1281            dbgmsg( tier, "Invalid response from tracker... retrying in two minutes." );
1282            tier->manualAnnounceAllowedAt = ~(time_t)0;
1283            tierSetNextAnnounce( tier, tier->announceEvent, now + interval );
1284        }
1285
1286        tier->lastAnnounceSucceeded = success;
1287
1288        if( success )
1289            tier->isRunning = data->isRunningOnSuccess;
1290
1291        if( !success )
1292            tierIncrementTracker( tier );
1293    }
1294
1295    if( announcer != NULL )
1296    {
1297        ++announcer->announceSlotsAvailable;
1298    }
1299
1300    tr_free( data );
1301}
1302
1303static const char *
1304getAnnounceEvent( const tr_tier * tier )
1305{
1306    const char * event;
1307
1308    assert( tier != NULL );
1309    assert( tier->announceEvent != NULL );
1310    assert( tr_isTorrent( tier->tor ) );
1311
1312    /* BEP 21: "In order to tell the tracker that a peer is a partial seed,
1313     * it MUST send an event=paused parameter in every announce while
1314     * it is a partial seed." */
1315    if( tr_cpGetStatus( &tier->tor->completion ) == TR_PARTIAL_SEED )
1316        return "paused";
1317
1318    event = tier->announceEvent;
1319
1320    if( !strcmp( event, "manual" ) )
1321        return "started";
1322
1323    return event;
1324}
1325
1326static void
1327tierAnnounce( tr_announcer * announcer, tr_tier * tier )
1328{
1329    char * url;
1330    struct announce_data * data;
1331    const tr_torrent * tor = tier->tor;
1332    const time_t now = time( NULL );
1333
1334    assert( !tier->isAnnouncing );
1335
1336    data = tr_new0( struct announce_data, 1 );
1337    data->torrentId = tr_torrentId( tor );
1338    data->tierId = tier->key;
1339    data->isRunningOnSuccess = tor->isRunning;
1340    data->timeSent = now;
1341
1342    url = createAnnounceURL( announcer, tor, tier, getAnnounceEvent( tier ) );
1343
1344    tier->isAnnouncing = TRUE;
1345    tier->lastAnnounceStartTime = now;
1346    --announcer->announceSlotsAvailable;
1347    tr_webRun( announcer->session, url, NULL, onAnnounceDone, data );
1348
1349    tr_free( url );
1350}
1351
1352static tr_bool
1353parseScrapeResponse( tr_tier     * tier,
1354                     const char  * response,
1355                     size_t        responseLen )
1356{
1357    tr_bool success = FALSE;
1358    tr_benc benc, *files;
1359    const int bencLoaded = !tr_bencLoad( response, responseLen, &benc, NULL );
1360    if( bencLoaded && tr_bencDictFindDict( &benc, "files", &files ) )
1361    {
1362        const char * key;
1363        tr_benc * val;
1364        int i = 0;
1365        while( tr_bencDictChild( files, i++, &key, &val ))
1366        {
1367            int64_t intVal;
1368            tr_benc * flags;
1369
1370            if( memcmp( tier->tor->info.hash, key, SHA_DIGEST_LENGTH ) )
1371                continue;
1372
1373            success = TRUE;
1374            publishErrorClear( tier );
1375
1376            if( ( tr_bencDictFindInt( val, "complete", &intVal ) ) )
1377                tier->currentTracker->seederCount = intVal;
1378
1379            if( ( tr_bencDictFindInt( val, "incomplete", &intVal ) ) )
1380                tier->currentTracker->leecherCount = intVal;
1381
1382            if( ( tr_bencDictFindInt( val, "downloaded", &intVal ) ) )
1383                tier->currentTracker->downloadCount = intVal;
1384
1385            if( ( tr_bencDictFindInt( val, "downloaders", &intVal ) ) )
1386                tier->currentTracker->downloaderCount = intVal;
1387
1388            if( tr_bencDictFindDict( val, "flags", &flags ) )
1389                if( ( tr_bencDictFindInt( flags, "min_request_interval", &intVal ) ) )
1390                    tier->scrapeIntervalSec = intVal;
1391
1392            /* as per ticket #1045, safeguard against trackers returning
1393             * a very low min_request_interval... */
1394            if( tier->scrapeIntervalSec < DEFAULT_SCRAPE_INTERVAL_SEC )
1395                tier->scrapeIntervalSec = DEFAULT_SCRAPE_INTERVAL_SEC;
1396
1397            tr_tordbg( tier->tor,
1398                       "Scrape successful. Rescraping in %d seconds.",
1399                       tier->scrapeIntervalSec );
1400
1401            tier->retryScrapeIntervalSec = FIRST_SCRAPE_RETRY_INTERVAL_SEC;
1402        }
1403    }
1404
1405    if( bencLoaded )
1406        tr_bencFree( &benc );
1407
1408    return success;
1409}
1410
1411static void
1412onScrapeDone( tr_session   * session,
1413              long           responseCode,
1414              const void   * response,
1415              size_t         responseLen,
1416              void         * vdata )
1417{
1418    tr_announcer * announcer = session->announcer;
1419    struct announce_data * data = vdata;
1420    tr_tier * tier = getTier( announcer, data->torrentId, data->tierId );
1421    const time_t now = time( NULL );
1422    tr_bool success = FALSE;
1423
1424    if( announcer )
1425        ++announcer->scrapeSlotsAvailable;
1426
1427    if( announcer && tier )
1428    {
1429        tier->isScraping = FALSE;
1430        tier->lastScrapeTime = now;
1431   
1432        if( tier->currentTracker->host )
1433        {
1434            tr_host * host = tier->currentTracker->host;
1435            host->lastRequestTime = data->timeSent;
1436            host->lastResponseInterval = now - data->timeSent;
1437        }
1438
1439        if( responseCode == HTTP_OK )
1440        {
1441            success = parseScrapeResponse( tier, response, responseLen );
1442        }
1443
1444        if( 200 <= responseCode && responseCode <= 299 )
1445        {
1446            const int interval = tier->scrapeIntervalSec;
1447            tier->scrapeAt = now + interval;
1448
1449            tr_strlcpy( tier->lastScrapeStr, _( "Success" ), sizeof( tier->lastScrapeStr ) );
1450            tr_tordbg( tier->tor, "Request succeeded. Rescraping in %d seconds", interval );
1451        }
1452        else if( 300 <= responseCode && responseCode <= 399 )
1453        {
1454            /* this shouldn't happen; libcurl should handle this */
1455            const int interval = 5;
1456            tier->scrapeAt = now + interval;
1457            tr_snprintf( tier->lastScrapeStr, sizeof( tier->lastScrapeStr ),
1458                         "Got a redirect. Retrying in %d seconds", interval );
1459            tr_tordbg( tier->tor, "%s", tier->lastScrapeStr );
1460        }
1461        else
1462        {
1463            const int interval = tier->retryScrapeIntervalSec;
1464            tier->retryScrapeIntervalSec *= 2;
1465            tier->scrapeAt = now + interval;
1466
1467            /* %1$ld - http status code, such as 404
1468             * %2$s - human-readable explanation of the http status code */
1469            if( !responseCode )
1470                tr_strlcpy( tier->lastScrapeStr, _( "tracker did not respond" ),
1471                            sizeof( tier->lastScrapeStr ) );
1472            else
1473                tr_snprintf( tier->lastScrapeStr, sizeof( tier->lastScrapeStr ),
1474                             _( "tracker gave HTTP Response Code %1$ld (%2$s)" ),
1475                             responseCode, tr_webGetResponseStr( responseCode ) );
1476        }
1477
1478        tier->lastScrapeSucceeded = success;
1479    }
1480
1481    tr_free( data );
1482}
1483
1484static void
1485tierScrape( tr_announcer * announcer, tr_tier * tier )
1486{
1487    const char * scrape;
1488    struct evbuffer * buf;
1489    struct announce_data * data;
1490    const time_t now = time( NULL );
1491
1492    assert( tier );
1493    assert( !tier->isScraping );
1494    assert( tier->currentTracker != NULL );
1495    assert( tr_isTorrent( tier->tor ) );
1496
1497    data = tr_new0( struct announce_data, 1 );
1498    data->torrentId = tr_torrentId( tier->tor );
1499    data->tierId = tier->key;
1500
1501    scrape = tier->currentTracker->scrape;
1502
1503    buf = evbuffer_new( );
1504    evbuffer_add_printf( buf, "%s%cinfo_hash=%s",
1505                         scrape,
1506                         strchr( scrape, '?' ) ? '&' : '?',
1507                         tier->tor->info.hashEscaped );
1508
1509    tier->isScraping = TRUE;
1510    tier->lastScrapeStartTime = now;
1511    --announcer->scrapeSlotsAvailable;
1512    dbgmsg( tier, "scraping \"%s\"", (const char*)EVBUFFER_DATA(buf) );
1513    tr_webRun( announcer->session, (const char*)EVBUFFER_DATA(buf), NULL, onScrapeDone, data );
1514
1515    evbuffer_free( buf );
1516}
1517
1518static void
1519flushCloseMessages( tr_announcer * announcer )
1520{
1521    int i;
1522    const int n = tr_ptrArraySize( &announcer->stops );
1523
1524    for( i=0; i<n; ++i )
1525    {
1526        struct stop_message * stop = tr_ptrArrayNth( &announcer->stops, i );
1527        tr_webRun( announcer->session, stop->url, NULL, NULL, NULL );
1528        stopFree( stop );
1529    }
1530
1531    tr_ptrArrayClear( &announcer->stops );
1532}
1533
1534static tr_bool
1535tierNeedsToAnnounce( const tr_tier * tier, const time_t now )
1536{
1537    return !tier->isAnnouncing
1538        && !tier->isScraping
1539        && ( tier->announceEvent != NULL )
1540        && ( tier->announceAt <= now );
1541}
1542
1543static tr_bool
1544tierNeedsToScrape( const tr_tier * tier, const time_t now )
1545{
1546    return !tier->isScraping
1547        && ( tier->scrapeAt <= now )
1548        && ( tier->currentTracker != NULL )
1549        && ( tier->currentTracker->scrape != NULL );
1550}
1551
1552static void
1553announceMore( tr_announcer * announcer )
1554{
1555    const tr_bool canAnnounce = announcer->announceSlotsAvailable > 0;
1556    const tr_bool canScrape = announcer->scrapeSlotsAvailable > 0;
1557
1558    if( announcer->announceSlotsAvailable > 0 )
1559    {
1560        int i;
1561        int n;
1562        const time_t now = time( NULL );
1563        tr_torrent * tor = NULL;
1564        tr_ptrArray announceMe = TR_PTR_ARRAY_INIT;
1565        tr_ptrArray scrapeMe = TR_PTR_ARRAY_INIT;
1566
1567        /* build a list of tiers that need to be announced */
1568        while(( tor = tr_torrentNext( announcer->session, tor ))) {
1569            if( tor->tiers ) {
1570                n = tr_ptrArraySize( &tor->tiers->tiers );
1571                for( i=0; i<n; ++i ) {
1572                    tr_tier * tier = tr_ptrArrayNth( &tor->tiers->tiers, i );
1573                    if( canAnnounce && tierNeedsToAnnounce( tier, now ) )
1574                        tr_ptrArrayAppend( &announceMe, tier );
1575                    else if( canScrape && tierNeedsToScrape( tier, now ) )
1576                        tr_ptrArrayAppend( &scrapeMe, tier );
1577                }
1578            }
1579        }
1580
1581        /* if there are more tiers than slots available, prioritize */
1582        n = tr_ptrArraySize( &announceMe );
1583        if( n > announcer->announceSlotsAvailable )
1584            qsort( tr_ptrArrayBase( &announceMe ), n, sizeof( tr_tier * ), compareTiers );
1585
1586        /* announce some */
1587        n = MIN( tr_ptrArraySize( &announceMe ), announcer->announceSlotsAvailable );
1588        for( i=0; i<n; ++i ) {
1589            tr_tier * tier = tr_ptrArrayNth( &announceMe, i );
1590            dbgmsg( tier, "announcing tier %d of %d", i, n );
1591            tierAnnounce( announcer, tier );
1592        }
1593
1594
1595        /* scrape some */
1596        n = MIN( tr_ptrArraySize( &scrapeMe ), announcer->scrapeSlotsAvailable );
1597        for( i=0; i<n; ++i ) {
1598            tr_tier * tier = tr_ptrArrayNth( &scrapeMe, i );
1599            dbgmsg( tier, "scraping tier %d of %d", (i+1), n );
1600            tierScrape( announcer, tier );
1601        }
1602
1603        /* cleanup */
1604        tr_ptrArrayDestruct( &scrapeMe, NULL );
1605        tr_ptrArrayDestruct( &announceMe, NULL );
1606    }
1607}
1608
1609static void
1610onUpkeepTimer( int foo UNUSED, short bar UNUSED, void * vannouncer )
1611{
1612    tr_announcer * announcer = vannouncer;
1613
1614    /* maybe send out some "stopped" messages for closed torrents */
1615    flushCloseMessages( announcer );
1616
1617    /* maybe send out some announcements to trackers */
1618    announceMore( announcer );
1619
1620    /* set up the next timer */
1621    tr_timerAdd( announcer->upkeepTimer, UPKEEP_INTERVAL_SECS, 0 );
1622}
1623
1624/***
1625****
1626***/
1627
1628tr_tracker_stat *
1629tr_announcerStats( const tr_torrent * torrent,
1630                   int              * setmeTrackerCount )
1631{
1632    int i;
1633    int n;
1634    int out = 0;
1635    int tierCount;
1636    tr_tracker_stat * ret;
1637    const time_t now = time( NULL );
1638
1639    assert( tr_isTorrent( torrent ) );
1640
1641    /* count the trackers... */
1642    for( i=n=0, tierCount=tr_ptrArraySize( &torrent->tiers->tiers ); i<tierCount; ++i ) {
1643        const tr_tier * tier = tr_ptrArrayNth( &torrent->tiers->tiers, i );
1644        n += tr_ptrArraySize( &tier->trackers );
1645    }
1646
1647    /* alloc the stats */
1648    *setmeTrackerCount = n;
1649    ret = tr_new0( tr_tracker_stat, n );
1650
1651    /* populate the stats */
1652    for( i=0, tierCount=tr_ptrArraySize( &torrent->tiers->tiers ); i<tierCount; ++i )
1653    {
1654        int j;
1655        const tr_tier * tier = tr_ptrArrayNth( &torrent->tiers->tiers, i );
1656        n = tr_ptrArraySize( &tier->trackers );
1657        for( j=0; j<n; ++j )
1658        {
1659            const tr_tracker_item * tracker = tr_ptrArrayNth( (tr_ptrArray*)&tier->trackers, j );
1660            tr_tracker_stat * st = ret + out++;
1661
1662            tr_strlcpy( st->host, tracker->host->name, sizeof( st->host ) );
1663            tr_strlcpy( st->announce, tracker->announce, sizeof( st->announce ) );
1664            st->tier = i + 1;
1665            st->isBackup = tracker != tier->currentTracker;
1666            st->lastScrapeStartTime = tier->lastScrapeStartTime;
1667
1668            if( st->isBackup )
1669            {
1670                st->hasScraped = FALSE;
1671                st->lastScrapeTime = 0;
1672                st->lastScrapeSucceeded = FALSE;
1673                st->lastScrapeResult[0] = '\0';
1674                st->scrapeState = TR_TRACKER_INACTIVE;
1675                st->announceState = TR_TRACKER_INACTIVE;
1676                st->nextScrapeTime = 0;
1677                st->lastAnnounceStartTime = 0;
1678                st->hasAnnounced = 0;
1679                st->lastAnnounceTime = 0;
1680                st->lastAnnounceResult[0] = '\0';
1681                st->lastAnnounceSucceeded = FALSE;
1682                st->lastAnnouncePeerCount = 0;
1683                st->nextAnnounceTime = 0;
1684                st->seederCount = 0;
1685                st->leecherCount = 0;
1686                st->downloadCount = 0;
1687            }
1688            else
1689            {
1690                if(( st->hasScraped = tier->lastScrapeTime != 0 )) {
1691                    st->lastScrapeTime = tier->lastScrapeTime;
1692                    st->lastScrapeSucceeded = tier->lastScrapeSucceeded;
1693                    tr_strlcpy( st->lastScrapeResult, tier->lastScrapeStr, sizeof( st->lastScrapeResult ) );
1694                }
1695
1696                if( tier->isScraping )
1697                    st->scrapeState = TR_TRACKER_ACTIVE;
1698                else if( tier->scrapeAt > now )
1699                    st->scrapeState = TR_TRACKER_WAITING;
1700                else
1701                    st->scrapeState = TR_TRACKER_QUEUED;
1702
1703                st->lastAnnounceStartTime = tier->lastAnnounceStartTime;
1704
1705                if(( st->hasAnnounced = tier->lastAnnounceTime != 0 )) {
1706                    st->lastAnnounceTime = tier->lastAnnounceTime;
1707                    tr_strlcpy( st->lastAnnounceResult, tier->lastAnnounceStr, sizeof( st->lastAnnounceResult ) );
1708                    if(( st->lastAnnounceSucceeded = tier->lastAnnounceSucceeded )) {
1709                        st->lastAnnouncePeerCount = tier->lastAnnouncePeerCount;
1710                    }
1711                }
1712
1713                if( tier->isAnnouncing )
1714                    st->announceState = TR_TRACKER_ACTIVE;
1715                else if( !torrent->isRunning )
1716                    st->announceState = TR_TRACKER_INACTIVE;
1717                else if( tier->announceAt > now )
1718                    st->announceState = TR_TRACKER_WAITING;
1719                else
1720                    st->announceState = TR_TRACKER_QUEUED;
1721
1722                st->seederCount = tracker->seederCount;
1723                st->leecherCount = tracker->leecherCount;
1724                st->downloadCount = tracker->downloadCount;
1725            }
1726        }
1727    }
1728
1729    return ret;
1730}
1731
1732void
1733tr_announcerStatsFree( tr_tracker_stat * trackers,
1734                       int trackerCount UNUSED )
1735{
1736    tr_free( trackers );
1737}
Note: See TracBrowser for help on using the repository browser.