source: trunk/libtransmission/announcer.c @ 9229

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

(trunk, libT) filter out unsupported announce addresses

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