source: trunk/libtransmission/tracker.c @ 6073

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

#800 initial support for GetRight?-style fetching of data through http and ftp servers specified in the .torrent's "url-list" tag

  • Property svn:keywords set to Date Rev Author Id
File size: 27.5 KB
Line 
1/*
2 * This file Copyright (C) 2007-2008 Charles Kerr <charles@rebelbase.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: tracker.c 6073 2008-06-07 21:26:41Z charles $
11 */
12
13#include <assert.h>
14#include <stdio.h> /* snprintf */
15#include <stdlib.h>
16#include <string.h> /* strcmp, strchr */
17
18#include <event.h>
19
20#include "transmission.h"
21#include "bencode.h"
22#include "completion.h"
23#include "net.h"
24#include "publish.h"
25#include "torrent.h"
26#include "tracker.h"
27#include "trevent.h"
28#include "utils.h"
29#include "web.h"
30
31enum
32{
33    HTTP_OK = 200,
34
35    /* seconds between tracker pulses */
36    PULSE_INTERVAL_MSEC = 1000,
37
38    /* unless the tracker says otherwise, rescrape this frequently */
39    DEFAULT_SCRAPE_INTERVAL_SEC = (60 * 15),
40
41    /* unless the tracker says otherwise, this is the announce interval */
42    DEFAULT_ANNOUNCE_INTERVAL_SEC = (60 * 4),
43
44    /* unless the tracker says otherwise, this is the announce min_interval */
45    DEFAULT_ANNOUNCE_MIN_INTERVAL_SEC = (60 * 2),
46
47    /* the value of the 'numwant' argument passed in tracker requests. */
48    NUMWANT = 80,
49
50    /* the length of the 'key' argument passed in tracker requests */
51    KEYLEN = 10
52};
53
54/**
55***
56**/
57
58struct tr_tracker
59{
60    unsigned int isRunning     : 1;
61
62    uint8_t randOffset;
63
64    /* sent as the "key" argument in tracker requests
65       to verify us if our IP address changes.
66       This is immutable for the life of the tracker object. */
67    char key_param[KEYLEN+1];
68
69    /* these are set from the latest scrape or tracker response */
70    int announceIntervalSec;
71    int announceMinIntervalSec;
72    int scrapeIntervalSec;
73    int retryScrapeIntervalSec;
74
75    /* index into the torrent's tr_info.trackers array */
76    int trackerIndex;
77
78    tr_session * session;
79
80    tr_publisher_t * publisher;
81
82    /* torrent hash string */
83    uint8_t hash[SHA_DIGEST_LENGTH];
84    char escaped[SHA_DIGEST_LENGTH*3 + 1];
85    char * name;
86
87    /* corresponds to the peer_id sent as a tracker request parameter.
88       one tracker admin says: "When the same torrent is opened and
89       closed and opened again without quitting Transmission ...
90       change the peerid. It would help sometimes if a stopped event
91       was missed to ensure that we didn't think someone was cheating. */
92    uint8_t * peer_id;
93
94    /* these are set from the latest tracker response... -1 is 'unknown' */
95    int timesDownloaded;
96    int seederCount;
97    int leecherCount;
98    char * trackerID;
99
100    time_t manualAnnounceAllowedAt;
101    time_t reannounceAt;
102    time_t scrapeAt;
103
104    time_t lastScrapeTime;
105    long lastScrapeResponse;
106
107    time_t lastAnnounceTime;
108    long lastAnnounceResponse;
109};
110
111#define dbgmsg(name, fmt...) tr_deepLog(__FILE__, __LINE__, name, ##fmt )
112
113/***
114****
115***/
116
117static const tr_tracker_info *
118getCurrentAddressFromTorrent( tr_tracker * t, const tr_torrent * tor )
119{
120    /* user might have removed trackers,
121     * so check to make sure our current index is in-bounds */
122    if( t->trackerIndex >= tor->info.trackerCount )
123        t->trackerIndex = 0;
124
125    assert( t->trackerIndex >= 0 );
126    assert( t->trackerIndex < tor->info.trackerCount );
127    return tor->info.trackers + t->trackerIndex;
128}
129   
130static const tr_tracker_info *
131getCurrentAddress( tr_tracker * t )
132{
133    const tr_torrent * torrent;
134    if(( torrent = tr_torrentFindFromHash( t->session, t->hash )))
135        return getCurrentAddressFromTorrent( t, torrent );
136    return NULL;
137}
138
139static int
140trackerSupportsScrape( tr_tracker * t, const tr_torrent * tor )
141{
142    const tr_tracker_info * info = getCurrentAddressFromTorrent( t, tor );
143    return info && info->scrape;
144}
145
146/***
147****
148***/
149
150tr_tracker *
151findTracker( tr_session * session, const uint8_t * hash )
152{
153    tr_torrent * torrent = tr_torrentFindFromHash( session, hash );
154    return torrent ? torrent->tracker : NULL;
155}
156
157/***
158****  PUBLISH
159***/
160
161static const tr_tracker_event emptyEvent = { 0, NULL, NULL, NULL, 0, 0 };
162
163static void
164publishMessage( tr_tracker * t, const char * msg, int type )
165{
166    if( t )
167    {
168        tr_tracker_event event = emptyEvent;
169        event.hash = t->hash;
170        event.messageType = type;
171        event.text = msg;
172        tr_publisherPublish( t->publisher, t, &event );
173    }
174}
175
176static void
177publishErrorClear( tr_tracker * t )
178{
179    publishMessage( t, NULL, TR_TRACKER_ERROR_CLEAR );
180}
181
182static void
183publishErrorMessageAndStop( tr_tracker * t, const char * msg )
184{
185    t->isRunning = 0;
186    publishMessage( t, msg, TR_TRACKER_ERROR );
187}
188
189static void
190publishWarning( tr_tracker * t, const char * msg )
191{
192    publishMessage( t, msg, TR_TRACKER_WARNING );
193}
194
195static void
196publishNewPeers( tr_tracker * t, int allAreSeeds,
197                 void * compact, int compactLen )
198{
199    tr_tracker_event event = emptyEvent;
200    event.hash = t->hash;
201    event.messageType = TR_TRACKER_PEERS;
202    event.allAreSeeds = allAreSeeds;
203    event.compact = compact;
204    event.compactLen = compactLen;
205    if( compactLen )
206        tr_publisherPublish( t->publisher, t, &event );
207}
208
209/***
210****
211***/
212
213static void onReqDone( tr_session * session );
214
215static int
216updateAddresses( tr_tracker  * t, int success )
217{
218    int retry;
219
220    tr_torrent * torrent = tr_torrentFindFromHash( t->session, t->hash );
221
222    if( success )
223    {
224        /* multitracker spec: "if a connection with a tracker is
225           successful, it will be moved to the front of the tier." */
226        t->trackerIndex = tr_torrentPromoteTracker( torrent, t->trackerIndex );
227        retry = FALSE; /* we succeeded; no need to retry */
228    }
229    else if ( ++t->trackerIndex >= torrent->info.trackerCount )
230    {
231        t->trackerIndex = 0;
232        retry = FALSE; /* we've tried them all */
233    }
234    else
235    {
236        const tr_tracker_info * n = getCurrentAddressFromTorrent( t, torrent );
237        tr_ninf( t->name, _( "Trying tracker \"%s\"" ), n->announce );
238        retry = TRUE;
239    }
240
241    return retry;
242}
243
244/* Convert to compact form */
245static uint8_t *
246parseOldPeers( tr_benc * bePeers, size_t * byteCount )
247{
248    int i;
249    uint8_t *compact, *walk;
250    const int peerCount = bePeers->val.l.count;
251
252    assert( bePeers->type == TYPE_LIST );
253
254    compact = tr_new( uint8_t, peerCount*6 );
255
256    for( i=0, walk=compact; i<peerCount; ++i )
257    {
258        const char * s;
259        int64_t itmp;
260        struct in_addr addr;
261        tr_port_t port;
262        tr_benc * peer = &bePeers->val.l.vals[i];
263
264        if( !tr_bencDictFindStr( peer, "ip", &s ) || tr_netResolve( s, &addr ) )
265            continue;
266
267        memcpy( walk, &addr, 4 );
268        walk += 4;
269
270        if( !tr_bencDictFindInt( peer, "port", &itmp ) || itmp<0 || itmp>0xffff )
271            continue;
272
273        port = htons( itmp );
274        memcpy( walk, &port, 2 );
275        walk += 2;
276    }
277
278    *byteCount = peerCount * 6;
279    return compact;
280}
281
282static void
283onStoppedResponse( tr_session    * session,
284                   long            responseCode  UNUSED,
285                   const void    * response      UNUSED,
286                   size_t          responseLen   UNUSED,
287                   void          * torrent_hash  )
288{
289    dbgmsg( NULL, "got a response to some `stop' message" );
290    tr_free( torrent_hash );
291    onReqDone( session );
292}
293
294static void
295onTrackerResponse( tr_session    * session,
296                   long            responseCode,
297                   const void    * response,
298                   size_t          responseLen,
299                   void          * torrent_hash )
300{
301    int retry;
302    int success = FALSE;
303    tr_tracker * t;
304
305    onReqDone( session );
306    t = findTracker( session, torrent_hash );
307    tr_free( torrent_hash );
308    if( !t ) /* tracker's been closed */
309        return;
310
311    dbgmsg( t->name, "tracker response: %ld", responseCode );
312    tr_ndbg( t->name, "tracker response: %ld", responseCode );
313    t->lastAnnounceResponse = responseCode;
314
315    if( responseCode == HTTP_OK )
316    {
317        tr_benc benc;
318        const int bencLoaded = !tr_bencLoad( response, responseLen, &benc, 0 );
319        publishErrorClear( t );
320        if( bencLoaded && tr_bencIsDict( &benc ) )
321        {
322            tr_benc * tmp;
323            int64_t i;
324            int incomplete = -1;
325            const char * str;
326
327            success = TRUE;
328
329            if(( tr_bencDictFindStr( &benc, "failure reason", &str ))) {
330                publishMessage( t, str, TR_TRACKER_ERROR );
331                success = FALSE;
332            }
333
334            if(( tr_bencDictFindStr( &benc, "warning message", &str )))
335                publishWarning( t, str );
336
337            if(( tr_bencDictFindInt( &benc, "interval", &i ))) {
338                dbgmsg( t->name, "setting interval to %d", (int)i );
339                t->announceIntervalSec = i;
340            }
341
342            if(( tr_bencDictFindInt( &benc, "min interval", &i ))) {
343                dbgmsg( t->name, "setting min interval to %d", (int)i );
344                t->announceMinIntervalSec = i;
345            }
346
347            if(( tr_bencDictFindStr( &benc, "tracker id", &str )))
348                t->trackerID = tr_strdup( str );
349
350            if(( tr_bencDictFindInt( &benc, "complete", &i )))
351                t->seederCount = i;
352
353            if(( tr_bencDictFindInt( &benc, "incomplete", &i )))
354                t->leecherCount = incomplete = i;
355
356            if(( tmp = tr_bencDictFind( &benc, "peers" )))
357            {
358                const int allAreSeeds = incomplete == 0;
359
360                if( tmp->type == TYPE_STR ) /* "compact" extension */
361                {
362                    publishNewPeers( t, allAreSeeds, tmp->val.s.s, tmp->val.s.i );
363                }
364                else if( tmp->type == TYPE_LIST ) /* original protocol */
365                {
366                    size_t byteCount = 0;
367                    uint8_t * compact = parseOldPeers( tmp, &byteCount );
368                    publishNewPeers( t, allAreSeeds, compact, byteCount );
369                    tr_free( compact );
370                }
371            }
372        }
373
374        if( bencLoaded )
375            tr_bencFree( &benc );
376    }
377
378    retry = updateAddresses( t, success );
379
380    /**
381    ***
382    **/
383
384    if( retry )
385        responseCode = 300;
386
387    if( 200<=responseCode && responseCode<=299 )
388    {
389        const int interval = t->announceIntervalSec + t->randOffset;
390        const time_t now = time ( NULL );
391        dbgmsg( t->name, "request succeeded. reannouncing in %d seconds", interval );
392        t->scrapeAt = now + t->scrapeIntervalSec + t->randOffset;
393        t->reannounceAt = now + interval;
394        t->manualAnnounceAllowedAt = now + t->announceMinIntervalSec;
395    }
396    else if( 300<=responseCode && responseCode<=399 )
397    {
398        /* it's a redirect... updateAddresses() has already
399         * parsed the redirect, all that's left is to retry */
400        const int interval = 5;
401        dbgmsg( t->name, "got a redirect. retrying in %d seconds", interval );
402        t->reannounceAt = time( NULL ) + interval;
403        t->manualAnnounceAllowedAt = time( NULL ) + t->announceMinIntervalSec;
404    }
405    else if( 400<=responseCode && responseCode<=499 )
406    {
407        /* The request could not be understood by the server due to
408         * malformed syntax. The client SHOULD NOT repeat the
409         * request without modifications. */
410        publishErrorMessageAndStop( t, _( "Tracker returned a 4xx message" ) );
411        t->manualAnnounceAllowedAt = ~(time_t)0;
412        t->reannounceAt = 0;
413    }
414    else if( 500<=responseCode && responseCode<=599 )
415    {
416        /* Response status codes beginning with the digit "5" indicate
417         * cases in which the server is aware that it has erred or is
418         * incapable of performing the request.  So we pause a bit and
419         * try again. */
420        t->manualAnnounceAllowedAt = ~(time_t)0;
421        t->reannounceAt = time( NULL ) + 60;
422    }
423    else
424    {
425        /* WTF did we get?? */
426        dbgmsg( t->name, "Invalid response from tracker... retrying in two minutes." );
427        t->manualAnnounceAllowedAt = ~(time_t)0;
428        t->reannounceAt = time( NULL ) + t->randOffset + 120;
429    }
430}
431
432static void
433onScrapeResponse( tr_session   * session,
434                  long           responseCode,
435                  const void   * response,
436                  size_t         responseLen,
437                  void         * torrent_hash )
438{
439    int success = FALSE;
440    int retry;
441    tr_tracker * t;
442
443    onReqDone( session );
444    t = findTracker( session, torrent_hash );
445    tr_free( torrent_hash );
446    if( !t ) /* tracker's been closed... */
447        return;
448
449    dbgmsg( t->name, "scrape response: %ld\n", responseCode );
450    tr_ndbg( t->name, "scrape response: %ld", responseCode );
451    t->lastScrapeResponse = responseCode;
452
453    if( responseCode == HTTP_OK )
454    {
455        tr_benc benc, *files;
456        const int bencLoaded = !tr_bencLoad( response, responseLen, &benc, 0 );
457        if( bencLoaded && tr_bencDictFindDict( &benc, "files", &files ) )
458        {
459            int i;
460            for( i=0; i<files->val.l.count; i+=2 )
461            {
462                int64_t itmp;
463                const uint8_t* hash =
464                    (const uint8_t*) files->val.l.vals[i].val.s.s;
465                tr_benc * flags;
466                tr_benc * tordict = &files->val.l.vals[i+1];
467                if( memcmp( t->hash, hash, SHA_DIGEST_LENGTH ) )
468                    continue;
469
470                publishErrorClear( t );
471
472                if(( tr_bencDictFindInt( tordict, "complete", &itmp )))
473                    t->seederCount = itmp;
474
475                if(( tr_bencDictFindInt( tordict, "incomplete", &itmp )))
476                    t->leecherCount = itmp;
477
478                if(( tr_bencDictFindInt( tordict, "downloaded", &itmp )))
479                    t->timesDownloaded = itmp;
480
481                if( tr_bencDictFindDict( tordict, "flags", &flags ))
482                    if(( tr_bencDictFindInt( flags, "min_request_interval", &itmp )))
483                        t->scrapeIntervalSec = i;
484
485                success = TRUE;
486
487                tr_ndbg( t->name, "Scrape successful.  Rescraping in %d seconds.",
488                         t->scrapeIntervalSec );
489
490                t->retryScrapeIntervalSec = 30;
491            }
492        }
493
494        if( bencLoaded )
495            tr_bencFree( &benc );
496    }
497
498    retry = updateAddresses( t, success );
499
500    /**
501    ***
502    **/
503
504    if( retry )
505        responseCode = 300;
506
507    if( 200<=responseCode && responseCode<=299 )
508    {
509        const int interval = t->scrapeIntervalSec + t->randOffset;
510        dbgmsg( t->name, "request succeeded. rescraping in %d seconds", interval );
511        tr_ndbg( t->name, "request succeeded. rescraping in %d seconds", interval );
512        t->scrapeAt = time( NULL ) + interval;
513    }
514    else if( 300<=responseCode && responseCode<=399 )
515    {
516        const int interval = 5;
517        dbgmsg( t->name, "got a redirect. retrying in %d seconds", interval );
518        t->scrapeAt = time( NULL ) + interval;
519    }
520    else
521    {
522        const int interval = t->retryScrapeIntervalSec + t->randOffset;
523        dbgmsg( t->name, "Tracker responded to scrape with %ld.  Retrying in %d seconds.",
524                   responseCode,  interval );
525        t->retryScrapeIntervalSec *= 2;
526        t->scrapeAt = time( NULL ) + interval;
527    }
528}
529
530/***
531****
532***/
533
534enum
535{
536    TR_REQ_STARTED,
537    TR_REQ_COMPLETED,
538    TR_REQ_STOPPED,
539    TR_REQ_REANNOUNCE,
540    TR_REQ_SCRAPE,
541    TR_REQ_COUNT
542};
543
544struct tr_tracker_request
545{
546    uint8_t torrent_hash[SHA_DIGEST_LENGTH];
547    int reqtype; /* TR_REQ_* */
548    char * url;
549    tr_web_done_func * done_func;
550    tr_session * session;
551};
552
553static void
554freeRequest( struct tr_tracker_request * req )
555{
556    tr_free( req->url );
557    tr_free( req );
558}
559
560static void
561buildTrackerRequestURI( tr_tracker        * t,
562                        const tr_torrent  * torrent,
563                        const char        * eventName,
564                        struct evbuffer   * buf )
565{
566    const int isStopping = !strcmp( eventName, "stopped" );
567    const int numwant = isStopping ? 0 : NUMWANT;
568    const char * ann = getCurrentAddressFromTorrent(t,torrent)->announce;
569   
570    evbuffer_add_printf( buf, "%cinfo_hash=%s"
571                              "&peer_id=%s"
572                              "&port=%d"
573                              "&uploaded=%"PRIu64
574                              "&downloaded=%"PRIu64
575                              "&corrupt=%"PRIu64
576                              "&left=%"PRIu64
577                              "&compact=1"
578                              "&numwant=%d"
579                              "&key=%s"
580                              "%s%s"
581                              "%s%s",
582        strchr(ann, '?') ? '&' : '?',
583        t->escaped,
584        t->peer_id,
585        tr_sessionGetPeerPort( t->session ),
586        torrent->uploadedCur,
587        torrent->downloadedCur,
588        torrent->corruptCur,
589        tr_cpLeftUntilComplete( torrent->completion ),
590        numwant,
591        t->key_param,
592        ( ( eventName && *eventName ) ? "&event=" : "" ),
593        ( ( eventName && *eventName ) ? eventName : "" ),
594        ( ( t->trackerID && *t->trackerID ) ? "&trackerid=" : "" ),
595        ( ( t->trackerID && *t->trackerID ) ? t->trackerID : "" ) );
596}
597
598static struct tr_tracker_request*
599createRequest( tr_session * session, tr_tracker * tracker, int reqtype )
600{
601    static const char* strings[] = { "started", "completed", "stopped", "", "err" };
602    const tr_torrent * torrent = tr_torrentFindFromHash( session, tracker->hash );
603    const tr_tracker_info * address = getCurrentAddressFromTorrent( tracker, torrent );
604    const int isStopping = reqtype == TR_REQ_STOPPED;
605    struct tr_tracker_request * req;
606    struct evbuffer * url;
607
608    url = evbuffer_new( );
609    evbuffer_add_printf( url, "%s", address->announce );
610    buildTrackerRequestURI( tracker, torrent, strings[reqtype], url );
611
612    req = tr_new0( struct tr_tracker_request, 1 );
613    req->session = session;
614    req->reqtype = reqtype;
615    req->done_func =  isStopping ? onStoppedResponse : onTrackerResponse;
616    req->url = tr_strdup( ( char * ) EVBUFFER_DATA( url ) );
617    memcpy( req->torrent_hash, tracker->hash, SHA_DIGEST_LENGTH );
618
619    evbuffer_free( url );
620    return req;
621}
622
623static struct tr_tracker_request*
624createScrape( tr_session * session, tr_tracker * tracker )
625{
626    const tr_tracker_info * a = getCurrentAddress( tracker );
627    struct tr_tracker_request * req;
628    struct evbuffer * url = evbuffer_new( );
629
630    evbuffer_add_printf( url, "%s%cinfo_hash=%s",
631                         a->scrape, strchr(a->scrape,'?')?'&':'?',
632                         tracker->escaped ); 
633
634    req = tr_new0( struct tr_tracker_request, 1 );
635    req->session = session;
636    req->reqtype = TR_REQ_SCRAPE;
637    req->url = tr_strdup( ( char * ) EVBUFFER_DATA( url ) );
638    req->done_func = onScrapeResponse;
639    memcpy( req->torrent_hash, tracker->hash, SHA_DIGEST_LENGTH );
640
641    evbuffer_free( url );
642    return req;
643}
644
645struct tr_tracker_handle
646{
647    unsigned int isShuttingDown : 1;
648    int runningCount;
649    tr_timer * pulseTimer;
650};
651
652static int pulse( void * vsession );
653
654static void
655ensureGlobalsExist( tr_session * session )
656{
657    if( session->tracker == NULL )
658    {
659        session->tracker = tr_new0( struct tr_tracker_handle, 1 );
660        session->tracker->pulseTimer = tr_timerNew( session, pulse, session, PULSE_INTERVAL_MSEC );
661        dbgmsg( NULL, "creating tracker timer" );
662    }
663}
664
665void
666tr_trackerShuttingDown( tr_session * session )
667{
668    if( session->tracker )
669        session->tracker->isShuttingDown = 1;
670}
671
672static int
673maybeFreeGlobals( tr_session * session )
674{
675    int globalsExist = session->tracker != NULL;
676
677    if( globalsExist
678        && ( session->tracker->runningCount < 1 )
679        && ( session->torrentList== NULL ) )
680    {
681        dbgmsg( NULL, "freeing tracker timer" );
682        tr_timerFree( &session->tracker->pulseTimer );
683        tr_free( session->tracker );
684        session->tracker = NULL;
685        globalsExist = FALSE;
686    }
687
688    return globalsExist;
689}
690
691/***
692****
693***/
694
695static void
696invokeRequest( void * vreq )
697{
698    struct tr_tracker_request * req = vreq;
699    tr_tracker * t = findTracker( req->session, req->torrent_hash );
700
701    if( t )
702    {
703        const time_t now = time( NULL );
704
705        if( req->reqtype == TR_REQ_SCRAPE )
706        {
707            t->lastScrapeTime = now;
708            t->scrapeAt = 0;
709        }
710        else
711        {
712            t->lastAnnounceTime = now;
713            t->reannounceAt = 0;
714            t->scrapeAt = req->reqtype == TR_REQ_STOPPED
715                        ? now + t->scrapeIntervalSec + t->randOffset
716                        : 0;
717        }
718    }
719
720    ++req->session->tracker->runningCount;
721
722    tr_webRun( req->session, req->url, NULL, req->done_func,
723               tr_memdup( req->torrent_hash, SHA_DIGEST_LENGTH ) );
724
725    freeRequest( req );
726}
727
728static void ensureGlobalsExist( tr_session * );
729
730static void
731enqueueScrape( tr_session * session, tr_tracker * tracker )
732{
733    struct tr_tracker_request * req;
734    ensureGlobalsExist( session );
735    req = createScrape( session, tracker );
736    tr_runInEventThread( session, invokeRequest, req );
737}
738
739static void
740enqueueRequest( tr_session * session, tr_tracker * tracker, int reqtype )
741{
742    struct tr_tracker_request * req;
743    ensureGlobalsExist( session );
744    req = createRequest( session, tracker, reqtype );
745    tr_runInEventThread( session, invokeRequest, req );
746}
747
748static int
749pulse( void * vsession )
750{
751    tr_session * session = vsession;
752    struct tr_tracker_handle * th = session->tracker;
753    tr_torrent * tor;
754    const time_t now = time( NULL );
755
756    if( !session->tracker )
757        return FALSE;
758
759    if( th->runningCount )
760        dbgmsg( NULL, "tracker pulse... %d running", th->runningCount );
761
762    /* upkeep: queue periodic rescrape / reannounce */
763    for( tor=session->torrentList; tor; tor=tor->next )
764    {
765        tr_tracker * t = tor->tracker;
766
767        if( t->scrapeAt && trackerSupportsScrape( t, tor ) && ( now >= t->scrapeAt ) ) {
768            t->scrapeAt = 0;
769            enqueueScrape( session, t );
770        }
771
772        if( t->reannounceAt && t->isRunning && ( now >= t->reannounceAt ) ) {
773            t->reannounceAt = 0;
774            enqueueRequest( session, t, TR_REQ_REANNOUNCE );
775        }
776    }
777
778    if( th->runningCount )
779        dbgmsg( NULL, "tracker pulse after upkeep... %d running", th->runningCount );
780
781    return maybeFreeGlobals( session );
782}
783
784static void
785onReqDone( tr_session * session )
786{
787    if( session->tracker )
788    {
789        --session->tracker->runningCount;
790        dbgmsg( NULL, "decrementing runningCount to %d", session->tracker->runningCount );
791        pulse( session );
792    }
793}
794
795/***
796****  LIFE CYCLE
797***/
798
799static void
800generateKeyParam( char * msg, int len )
801{
802    int i;
803    const char * pool = "abcdefghijklmnopqrstuvwxyz0123456789";
804    const int poolSize = strlen( pool );
805    for( i=0; i<len; ++i )
806        *msg++ = pool[tr_rand(poolSize)];
807    *msg = '\0';
808}
809
810static int
811is_rfc2396_alnum( char ch )
812{
813    return     ( '0' <= ch && ch <= '9' )
814            || ( 'A' <= ch && ch <= 'Z' )
815            || ( 'a' <= ch && ch <= 'z' );
816}
817
818static void
819escape( char * out, const uint8_t * in, int in_len ) /* rfc2396 */
820{
821    const uint8_t *end = in + in_len;
822    while( in != end )
823        if( is_rfc2396_alnum(*in) )
824            *out++ = (char) *in++;
825        else 
826            out += snprintf( out, 4, "%%%02X", (unsigned int)*in++ );
827    *out = '\0';
828}
829
830tr_tracker *
831tr_trackerNew( const tr_torrent * torrent )
832{
833    const tr_info * info = &torrent->info;
834    tr_tracker * t;
835
836    t = tr_new0( tr_tracker, 1 );
837    t->publisher = tr_publisherNew( );
838    t->session                  = torrent->handle;
839    t->scrapeIntervalSec        = DEFAULT_SCRAPE_INTERVAL_SEC;
840    t->retryScrapeIntervalSec   = 60;
841    t->announceIntervalSec      = DEFAULT_ANNOUNCE_INTERVAL_SEC;
842    t->announceMinIntervalSec   = DEFAULT_ANNOUNCE_MIN_INTERVAL_SEC;
843    t->timesDownloaded          = -1;
844    t->seederCount              = -1;
845    t->leecherCount             = -1;
846    t->lastAnnounceResponse     = -1;
847    t->lastScrapeResponse       = -1;
848    t->manualAnnounceAllowedAt  = ~(time_t)0;
849    t->name = tr_strdup( info->name );
850    t->randOffset = tr_rand( 30 );
851    memcpy( t->hash, info->hash, SHA_DIGEST_LENGTH );
852    escape( t->escaped, info->hash, SHA_DIGEST_LENGTH );
853    generateKeyParam( t->key_param, KEYLEN );
854
855    t->trackerIndex = 0;
856
857    if( trackerSupportsScrape( t, torrent ) )
858        t->scrapeAt = time( NULL ) + t->randOffset;
859
860    return t;
861}
862
863static void
864onTrackerFreeNow( void * vt )
865{
866    tr_tracker * t = vt;
867
868    tr_publisherFree( &t->publisher );
869    tr_free( t->name );
870    tr_free( t->trackerID );
871    tr_free( t->peer_id );
872
873    tr_free( t );
874}
875
876/***
877****  PUBLIC
878***/
879
880void
881tr_trackerFree( tr_tracker * t )
882{
883    if( t )
884        tr_runInEventThread( t->session, onTrackerFreeNow, t );
885}
886
887tr_publisher_tag
888tr_trackerSubscribe( tr_tracker          * t,
889                     tr_delivery_func      func,
890                     void                * user_data )
891{
892    return tr_publisherSubscribe( t->publisher, func, user_data );
893}
894
895void
896tr_trackerUnsubscribe( tr_tracker        * t,
897                       tr_publisher_tag    tag )
898{
899    if( t )
900        tr_publisherUnsubscribe( t->publisher, tag );
901}
902
903const tr_tracker_info *
904tr_trackerGetAddress( tr_tracker   * t )
905{
906    return getCurrentAddress( t );
907}
908
909time_t
910tr_trackerGetManualAnnounceTime( const struct tr_tracker * t )
911{
912    return t->isRunning ? t->manualAnnounceAllowedAt : 0;
913}
914
915int
916tr_trackerCanManualAnnounce ( const tr_tracker * t)
917{
918    const time_t allow = tr_trackerGetManualAnnounceTime( t );
919    return allow && ( allow <= time( NULL ) );
920}
921
922void
923tr_trackerGetCounts( const tr_tracker  * t,
924                     int               * setme_completedCount,
925                     int               * setme_leecherCount,
926                     int               * setme_seederCount )
927{
928    if( setme_completedCount )
929       *setme_completedCount = t->timesDownloaded;
930
931    if( setme_leecherCount )
932       *setme_leecherCount = t->leecherCount;
933
934    if( setme_seederCount )
935       *setme_seederCount = t->seederCount;
936}
937
938
939void
940tr_trackerStart( tr_tracker * t )
941{
942    if( t && !t->isRunning )
943    {
944        tr_free( t->peer_id );
945        t->peer_id = tr_peerIdNew( );
946
947        t->isRunning = 1;
948        enqueueRequest( t->session, t, TR_REQ_STARTED );
949    }
950}
951
952void
953tr_trackerReannounce( tr_tracker * t )
954{
955    enqueueRequest( t->session, t, TR_REQ_REANNOUNCE );
956}
957
958void
959tr_trackerCompleted( tr_tracker * t )
960{
961    enqueueRequest( t->session, t, TR_REQ_COMPLETED );
962}
963
964void
965tr_trackerStop( tr_tracker * t )
966{
967    if( t && t->isRunning ) {
968        t->isRunning = 0;
969        t->reannounceAt = t->manualAnnounceAllowedAt = 0;
970        enqueueRequest( t->session, t, TR_REQ_STOPPED );
971    }
972}
973
974void
975tr_trackerChangeMyPort( tr_tracker * t )
976{
977    if( t->isRunning )
978        tr_trackerReannounce( t );
979}
980
981void
982tr_trackerStat( const tr_tracker * t,
983                struct tr_stat   * setme)
984{
985    assert( t );
986    assert( setme );
987
988    setme->lastScrapeTime = t->lastScrapeTime;
989    setme->nextScrapeTime = t->scrapeAt;
990    setme->lastAnnounceTime = t->lastAnnounceTime;
991    setme->nextAnnounceTime = t->reannounceAt;
992    setme->manualAnnounceTime = t->manualAnnounceAllowedAt;
993
994    if( t->lastScrapeResponse == -1 ) /* never been scraped */
995        *setme->scrapeResponse = '\0';
996    else
997        snprintf( setme->scrapeResponse,
998                  sizeof( setme->scrapeResponse ),
999                  "%s (%ld)",
1000                  tr_webGetResponseStr( t->lastScrapeResponse ),
1001                  t->lastScrapeResponse );
1002
1003    if( t->lastAnnounceResponse == -1 ) /* never been announced */
1004        *setme->announceResponse = '\0';
1005    else
1006        snprintf( setme->announceResponse,
1007                  sizeof( setme->announceResponse ),
1008                  "%s (%ld)",
1009                  tr_webGetResponseStr( t->lastAnnounceResponse ),
1010                  t->lastAnnounceResponse );
1011}
Note: See TracBrowser for help on using the repository browser.