/* * This file Copyright (C) 2007-2008 Charles Kerr * * This file is licensed by the GPL version 2. Works owned by the * Transmission project are granted a special exemption to clause 2(b) * so that the bulk of its code can remain under the MIT license. * This exemption does not extend to derived works not owned by * the Transmission project. * * $Id$ */ #include #include #include /* strcmp, strchr */ #include #include "transmission.h" #include "bencode.h" #include "crypto.h" #include "completion.h" #include "net.h" #include "publish.h" #include "resume.h" #include "torrent.h" #include "tracker.h" #include "trevent.h" #include "utils.h" #include "web.h" enum { HTTP_OK = 200, /* seconds between tracker pulses */ PULSE_INTERVAL_MSEC = 1000, /* unless the tracker says otherwise, rescrape this frequently */ DEFAULT_SCRAPE_INTERVAL_SEC = ( 60 * 15 ), /* unless the tracker says otherwise, this is the announce interval */ DEFAULT_ANNOUNCE_INTERVAL_SEC = ( 60 * 4 ), /* unless the tracker says otherwise, this is the announce min_interval */ DEFAULT_ANNOUNCE_MIN_INTERVAL_SEC = ( 60 * 2 ), /* how long to wait before a rescrape the first time we get an error */ FIRST_SCRAPE_RETRY_INTERVAL_SEC = 30, /* how long to wait before a reannounce the first time we get an error */ FIRST_ANNOUNCE_RETRY_INTERVAL_SEC = 30, /* the value of the 'numwant' argument passed in tracker requests. */ NUMWANT = 80, /* the length of the 'key' argument passed in tracker requests */ KEYLEN = 10 }; /** *** **/ struct tr_tracker { unsigned int isRunning : 1; uint8_t randOffset; /* sent as the "key" argument in tracker requests to verify us if our IP address changes. This is immutable for the life of the tracker object. */ char key_param[KEYLEN + 1]; /* these are set from the latest scrape or tracker response */ int announceIntervalSec; int announceMinIntervalSec; int scrapeIntervalSec; int retryScrapeIntervalSec; int retryAnnounceIntervalSec; /* index into the torrent's tr_info.trackers array */ int trackerIndex; tr_session * session; tr_publisher_t * publisher; /* torrent hash string */ uint8_t hash[SHA_DIGEST_LENGTH]; char escaped[SHA_DIGEST_LENGTH * 3 + 1]; char * name; /* corresponds to the peer_id sent as a tracker request parameter. one tracker admin says: "When the same torrent is opened and closed and opened again without quitting Transmission ... change the peerid. It would help sometimes if a stopped event was missed to ensure that we didn't think someone was cheating. */ uint8_t * peer_id; /* these are set from the latest tracker response... -1 is 'unknown' */ int timesDownloaded; int seederCount; int leecherCount; char * trackerID; time_t manualAnnounceAllowedAt; time_t reannounceAt; /* 0==never, 1==in progress, other values==when to scrape */ time_t scrapeAt; time_t lastScrapeTime; long lastScrapeResponse; time_t lastAnnounceTime; long lastAnnounceResponse; }; #define dbgmsg( name, fmt... ) tr_deepLog( __FILE__, __LINE__, name, ## fmt ) /*** **** ***/ static const tr_tracker_info * getCurrentAddressFromTorrent( tr_tracker * t, const tr_torrent * tor ) { /* user might have removed trackers, * so check to make sure our current index is in-bounds */ if( t->trackerIndex >= tor->info.trackerCount ) t->trackerIndex = 0; assert( t->trackerIndex >= 0 ); assert( t->trackerIndex < tor->info.trackerCount ); return tor->info.trackers + t->trackerIndex; } static const tr_tracker_info * getCurrentAddress( tr_tracker * t ) { const tr_torrent * torrent; if( ( torrent = tr_torrentFindFromHash( t->session, t->hash ) ) ) return getCurrentAddressFromTorrent( t, torrent ); return NULL; } static int trackerSupportsScrape( tr_tracker * t, const tr_torrent * tor ) { const tr_tracker_info * info = getCurrentAddressFromTorrent( t, tor ); return info && info->scrape; } /*** **** ***/ static tr_tracker * findTracker( tr_session * session, const uint8_t * hash ) { tr_torrent * torrent = tr_torrentFindFromHash( session, hash ); return torrent ? torrent->tracker : NULL; } /*** **** PUBLISH ***/ static const tr_tracker_event emptyEvent = { 0, NULL, NULL, NULL, 0, 0 }; static void publishMessage( tr_tracker * t, const char * msg, int type ) { if( t ) { tr_tracker_event event = emptyEvent; event.hash = t->hash; event.messageType = type; event.text = msg; tr_publisherPublish( t->publisher, t, &event ); } } static void publishErrorClear( tr_tracker * t ) { publishMessage( t, NULL, TR_TRACKER_ERROR_CLEAR ); } static void publishErrorMessageAndStop( tr_tracker * t, const char * msg ) { t->isRunning = 0; publishMessage( t, msg, TR_TRACKER_ERROR ); } static void publishWarning( tr_tracker * t, const char * msg ) { publishMessage( t, msg, TR_TRACKER_WARNING ); } static void publishNewPeers( tr_tracker * t, int allAreSeeds, void * compact, int compactLen ) { tr_tracker_event event = emptyEvent; event.hash = t->hash; event.messageType = TR_TRACKER_PEERS; event.allAreSeeds = allAreSeeds; event.compact = compact; event.compactLen = compactLen; if( compactLen ) tr_publisherPublish( t->publisher, t, &event ); } /*** **** ***/ static void onReqDone( tr_session * session ); static int updateAddresses( tr_tracker * t, int success ) { int retry; tr_torrent * torrent = tr_torrentFindFromHash( t->session, t->hash ); if( success ) { /* multitracker spec: "if a connection with a tracker is successful, it will be moved to the front of the tier." */ t->trackerIndex = tr_torrentPromoteTracker( torrent, t->trackerIndex ); retry = FALSE; /* we succeeded; no need to retry */ } else if( ++t->trackerIndex >= torrent->info.trackerCount ) { t->trackerIndex = 0; retry = FALSE; /* we've tried them all */ } else { const tr_tracker_info * n = getCurrentAddressFromTorrent( t, torrent ); tr_ninf( t->name, _( "Trying tracker \"%s\"" ), n->announce ); retry = TRUE; } return retry; } /* Convert to compact form */ static uint8_t * parseOldPeers( tr_benc * bePeers, size_t * byteCount ) { int i; uint8_t * compact, *walk; const int peerCount = bePeers->val.l.count; assert( bePeers->type == TYPE_LIST ); compact = tr_new( uint8_t, peerCount * 6 ); for( i = 0, walk = compact; i < peerCount; ++i ) { const char * s; int64_t itmp; struct in_addr addr; tr_port_t port; tr_benc * peer = &bePeers->val.l.vals[i]; if( !tr_bencDictFindStr( peer, "ip", &s ) || tr_netResolve( s, &addr ) ) continue; memcpy( walk, &addr, 4 ); walk += 4; if( !tr_bencDictFindInt( peer, "port", &itmp ) || itmp < 0 || itmp > 0xffff ) continue; port = htons( itmp ); memcpy( walk, &port, 2 ); walk += 2; } *byteCount = peerCount * 6; return compact; } static void onStoppedResponse( tr_session * session, long responseCode UNUSED, const void * response UNUSED, size_t responseLen UNUSED, void * torrent_hash ) { dbgmsg( NULL, "got a response to some `stop' message" ); tr_free( torrent_hash ); onReqDone( session ); } static void onTrackerResponse( tr_session * session, long responseCode, const void * response, size_t responseLen, void * torrent_hash ) { int retry; int success = FALSE; tr_tracker * t; onReqDone( session ); t = findTracker( session, torrent_hash ); tr_free( torrent_hash ); if( !t ) /* tracker's been closed */ return; dbgmsg( t->name, "tracker response: %ld", responseCode ); tr_ndbg( t->name, "tracker response: %ld", responseCode ); t->lastAnnounceResponse = responseCode; if( responseCode == HTTP_OK ) { tr_benc benc; const int bencLoaded = !tr_bencLoad( response, responseLen, &benc, NULL ); publishErrorClear( t ); if( bencLoaded && tr_bencIsDict( &benc ) ) { tr_benc * tmp; int64_t i; int incomplete = -1; const char * str; success = TRUE; t->retryAnnounceIntervalSec = FIRST_SCRAPE_RETRY_INTERVAL_SEC; if( ( tr_bencDictFindStr( &benc, "failure reason", &str ) ) ) { publishMessage( t, str, TR_TRACKER_ERROR ); success = FALSE; } if( ( tr_bencDictFindStr( &benc, "warning message", &str ) ) ) publishWarning( t, str ); if( ( tr_bencDictFindInt( &benc, "interval", &i ) ) ) { dbgmsg( t->name, "setting interval to %d", (int)i ); t->announceIntervalSec = i; } if( ( tr_bencDictFindInt( &benc, "min interval", &i ) ) ) { dbgmsg( t->name, "setting min interval to %d", (int)i ); t->announceMinIntervalSec = i; } if( ( tr_bencDictFindStr( &benc, "tracker id", &str ) ) ) t->trackerID = tr_strdup( str ); if( ( tr_bencDictFindInt( &benc, "complete", &i ) ) ) t->seederCount = i; if( ( tr_bencDictFindInt( &benc, "incomplete", &i ) ) ) t->leecherCount = incomplete = i; if( ( tr_bencDictFindInt( &benc, "downloaded", &i ) ) ) t->timesDownloaded = i; if( ( tmp = tr_bencDictFind( &benc, "peers" ) ) ) { const int allAreSeeds = incomplete == 0; if( tmp->type == TYPE_STR ) /* "compact" extension */ { publishNewPeers( t, allAreSeeds, tmp->val.s.s, tmp->val.s.i ); } else if( tmp->type == TYPE_LIST ) /* original protocol */ { size_t byteCount = 0; uint8_t * compact = parseOldPeers( tmp, &byteCount ); publishNewPeers( t, allAreSeeds, compact, byteCount ); tr_free( compact ); } } } if( bencLoaded ) tr_bencFree( &benc ); } retry = updateAddresses( t, success ); /** *** **/ if( retry ) responseCode = 300; if( 200 <= responseCode && responseCode <= 299 ) { const int interval = t->announceIntervalSec + t->randOffset; const time_t now = time ( NULL ); dbgmsg( t->name, "request succeeded. reannouncing in %d seconds", interval ); if( t->scrapeAt <= now ) t->scrapeAt = now + t->scrapeIntervalSec + t->randOffset; t->reannounceAt = now + interval; t->manualAnnounceAllowedAt = now + t->announceMinIntervalSec; /* #319: save the .resume file after an announce so that, in case * of a crash, our stats still match up with the tracker's stats */ tr_torrentSaveResume( tr_torrentFindFromHash( t->session, t->hash ) ); } else if( 300 <= responseCode && responseCode <= 399 ) { /* it's a redirect... updateAddresses() has already * parsed the redirect, all that's left is to retry */ const int interval = 5; dbgmsg( t->name, "got a redirect. retrying in %d seconds", interval ); t->reannounceAt = time( NULL ) + interval; t->manualAnnounceAllowedAt = time( NULL ) + t->announceMinIntervalSec; } else if( 400 <= responseCode && responseCode <= 499 ) { /* The request could not be understood by the server due to * malformed syntax. The client SHOULD NOT repeat the * request without modifications. */ publishErrorMessageAndStop( t, _( "Tracker returned a 4xx message" ) ); t->manualAnnounceAllowedAt = ~(time_t)0; t->reannounceAt = 0; } else if( 500 <= responseCode && responseCode <= 599 ) { /* Response status codes beginning with the digit "5" indicate * cases in which the server is aware that it has erred or is * incapable of performing the request. So we pause a bit and * try again. */ t->manualAnnounceAllowedAt = ~(time_t)0; t->reannounceAt = time( NULL ) + t->retryAnnounceIntervalSec; t->retryAnnounceIntervalSec *= 2; } else { /* WTF did we get?? */ dbgmsg( t->name, "Invalid response from tracker... retrying in two minutes." ); t->manualAnnounceAllowedAt = ~(time_t)0; t->reannounceAt = time( NULL ) + t->randOffset + 120; } } static void onScrapeResponse( tr_session * session, long responseCode, const void * response, size_t responseLen, void * torrent_hash ) { int success = FALSE; int retry; tr_tracker * t; onReqDone( session ); t = findTracker( session, torrent_hash ); tr_free( torrent_hash ); if( !t ) /* tracker's been closed... */ return; dbgmsg( t->name, "scrape response: %ld\n", responseCode ); tr_ndbg( t->name, "scrape response: %ld", responseCode ); t->lastScrapeResponse = responseCode; if( responseCode == HTTP_OK ) { tr_benc benc, *files; const int bencLoaded = !tr_bencLoad( response, responseLen, &benc, NULL ); if( bencLoaded && tr_bencDictFindDict( &benc, "files", &files ) ) { size_t i; for( i = 0; i < files->val.l.count; i += 2 ) { int64_t itmp; const uint8_t* hash = (const uint8_t*) files->val.l.vals[i].val.s.s; tr_benc * flags; tr_benc * tordict = &files->val.l.vals[i + 1]; if( memcmp( t->hash, hash, SHA_DIGEST_LENGTH ) ) continue; publishErrorClear( t ); if( ( tr_bencDictFindInt( tordict, "complete", &itmp ) ) ) t->seederCount = itmp; if( ( tr_bencDictFindInt( tordict, "incomplete", &itmp ) ) ) t->leecherCount = itmp; if( ( tr_bencDictFindInt( tordict, "downloaded", &itmp ) ) ) t->timesDownloaded = itmp; if( tr_bencDictFindDict( tordict, "flags", &flags ) ) if( ( tr_bencDictFindInt( flags, "min_request_interval", &itmp ) ) ) t->scrapeIntervalSec = i; /* as per ticket #1045, safeguard against trackers returning * a very low min_request_interval... */ if( t->scrapeIntervalSec < DEFAULT_SCRAPE_INTERVAL_SEC ) t->scrapeIntervalSec = DEFAULT_SCRAPE_INTERVAL_SEC; tr_ndbg( t->name, "Scrape successful. Rescraping in %d seconds.", t->scrapeIntervalSec ); success = TRUE; t->retryScrapeIntervalSec = FIRST_SCRAPE_RETRY_INTERVAL_SEC; } } if( bencLoaded ) tr_bencFree( &benc ); } retry = updateAddresses( t, success ); /** *** **/ if( retry ) responseCode = 300; if( 200 <= responseCode && responseCode <= 299 ) { const int interval = t->scrapeIntervalSec + t->randOffset; dbgmsg( t->name, "request succeeded. rescraping in %d seconds", interval ); tr_ndbg( t->name, "request succeeded. rescraping in %d seconds", interval ); t->scrapeAt = time( NULL ) + interval; } else if( 300 <= responseCode && responseCode <= 399 ) { const int interval = 5; dbgmsg( t->name, "got a redirect. retrying in %d seconds", interval ); t->scrapeAt = time( NULL ) + interval; } else { const int interval = t->retryScrapeIntervalSec + t->randOffset; dbgmsg( t->name, "Tracker responded to scrape with %ld. Retrying in %d seconds.", responseCode, interval ); t->retryScrapeIntervalSec *= 2; t->scrapeAt = time( NULL ) + interval; } } /*** **** ***/ enum { TR_REQ_STARTED, TR_REQ_COMPLETED, TR_REQ_STOPPED, TR_REQ_REANNOUNCE, TR_REQ_SCRAPE, TR_REQ_COUNT }; struct tr_tracker_request { uint8_t torrent_hash[SHA_DIGEST_LENGTH]; int reqtype; /* TR_REQ_* */ char * url; tr_web_done_func * done_func; tr_session * session; }; static void freeRequest( struct tr_tracker_request * req ) { tr_free( req->url ); tr_free( req ); } static void buildTrackerRequestURI( tr_tracker * t, const tr_torrent * torrent, const char * eventName, struct evbuffer * buf ) { const int isStopping = !strcmp( eventName, "stopped" ); const int numwant = isStopping ? 0 : NUMWANT; const char * ann = getCurrentAddressFromTorrent( t, torrent )->announce; evbuffer_add_printf( buf, "%cinfo_hash=%s" "&peer_id=%s" "&port=%d" "&uploaded=%" PRIu64 "&downloaded=%" PRIu64 "&corrupt=%" PRIu64 "&left=%" PRIu64 "&compact=1" "&numwant=%d" "&key=%s" "%s%s" "%s%s", strchr( ann, '?' ) ? '&' : '?', t->escaped, t->peer_id, tr_sessionGetPeerPort( t->session ), torrent->uploadedCur, torrent->downloadedCur, torrent->corruptCur, tr_cpLeftUntilComplete( torrent->completion ), numwant, t->key_param, ( ( eventName && *eventName ) ? "&event=" : "" ), ( ( eventName && *eventName ) ? eventName : "" ), ( ( t->trackerID && *t->trackerID ) ? "&trackerid=" : "" ), ( ( t->trackerID && *t->trackerID ) ? t->trackerID : "" ) ); } static struct tr_tracker_request* createRequest( tr_session * session, tr_tracker * tracker, int reqtype ) { static const char* strings[] = { "started", "completed", "stopped", "", "err" }; const tr_torrent * torrent = tr_torrentFindFromHash( session, tracker->hash ); const tr_tracker_info * address = getCurrentAddressFromTorrent( tracker, torrent ); const int isStopping = reqtype == TR_REQ_STOPPED; struct tr_tracker_request * req; struct evbuffer * url; url = evbuffer_new( ); evbuffer_add_printf( url, "%s", address->announce ); buildTrackerRequestURI( tracker, torrent, strings[reqtype], url ); req = tr_new0( struct tr_tracker_request, 1 ); req->session = session; req->reqtype = reqtype; req->done_func = isStopping ? onStoppedResponse : onTrackerResponse; req->url = tr_strdup( EVBUFFER_DATA( url ) ); memcpy( req->torrent_hash, tracker->hash, SHA_DIGEST_LENGTH ); evbuffer_free( url ); return req; } static struct tr_tracker_request* createScrape( tr_session * session, tr_tracker * tracker ) { const tr_tracker_info * a = getCurrentAddress( tracker ); struct tr_tracker_request * req; struct evbuffer * url = evbuffer_new( ); evbuffer_add_printf( url, "%s%cinfo_hash=%s", a->scrape, strchr( a->scrape, '?' ) ? '&' : '?', tracker->escaped ); req = tr_new0( struct tr_tracker_request, 1 ); req->session = session; req->reqtype = TR_REQ_SCRAPE; req->url = tr_strdup( EVBUFFER_DATA( url ) ); req->done_func = onScrapeResponse; memcpy( req->torrent_hash, tracker->hash, SHA_DIGEST_LENGTH ); evbuffer_free( url ); return req; } struct tr_tracker_handle { int runningCount; tr_timer * pulseTimer; }; static int trackerPulse( void * vsession ); static void ensureGlobalsExist( tr_session * session ) { if( session->tracker == NULL ) { session->tracker = tr_new0( struct tr_tracker_handle, 1 ); session->tracker->pulseTimer = tr_timerNew( session, trackerPulse, session, PULSE_INTERVAL_MSEC ); dbgmsg( NULL, "creating tracker timer" ); } } void tr_trackerSessionClose( tr_session * session ) { if( session && session->tracker ) { dbgmsg( NULL, "freeing tracker timer" ); tr_timerFree( &session->tracker->pulseTimer ); tr_free( session->tracker ); session->tracker = NULL; } } /*** **** ***/ static void invokeRequest( void * vreq ) { struct tr_tracker_request * req = vreq; tr_tracker * t = findTracker( req->session, req->torrent_hash ); if( t ) { const time_t now = time( NULL ); if( req->reqtype == TR_REQ_SCRAPE ) { t->lastScrapeTime = now; t->scrapeAt = 1; } else { t->lastAnnounceTime = now; t->reannounceAt = 1; t->manualAnnounceAllowedAt = 1; } } ++req->session->tracker->runningCount; tr_webRun( req->session, req->url, NULL, req->done_func, tr_memdup( req->torrent_hash, SHA_DIGEST_LENGTH ) ); freeRequest( req ); } static void enqueueScrape( tr_session * session, tr_tracker * tracker ) { struct tr_tracker_request * req; req = createScrape( session, tracker ); tr_runInEventThread( session, invokeRequest, req ); } static void enqueueRequest( tr_session * session, tr_tracker * tracker, int reqtype ) { struct tr_tracker_request * req; req = createRequest( session, tracker, reqtype ); tr_runInEventThread( session, invokeRequest, req ); } static int trackerPulse( void * vsession ) { tr_session * session = vsession; struct tr_tracker_handle * th = session->tracker; tr_torrent * tor; const time_t now = time( NULL ); if( !session->tracker ) return FALSE; if( th->runningCount ) dbgmsg( NULL, "tracker pulse... %d running", th->runningCount ); /* upkeep: queue periodic rescrape / reannounce */ for( tor = session->torrentList; tor; tor = tor->next ) { tr_tracker * t = tor->tracker; if( ( t->scrapeAt > 1 ) && ( t->scrapeAt <= now ) && ( trackerSupportsScrape( t, tor ) ) ) { t->scrapeAt = 1; enqueueScrape( session, t ); } if( ( t->reannounceAt > 1 ) && ( t->reannounceAt <= now ) && ( t->isRunning ) ) { t->reannounceAt = 1; t->manualAnnounceAllowedAt = 1; enqueueRequest( session, t, TR_REQ_REANNOUNCE ); } } if( th->runningCount ) dbgmsg( NULL, "tracker pulse after upkeep... %d running", th->runningCount ); /* free the tracker manager if no torrents are left */ if( ( session->tracker ) && ( session->tracker->runningCount < 1 ) && ( session->torrentList == NULL ) ) { tr_trackerSessionClose( session ); } /* if there are still running torrents (as indicated by * the existence of the tracker manager) then keep the * trackerPulse() timer alive */ return session->tracker != NULL; } static void onReqDone( tr_session * session ) { if( session->tracker ) { --session->tracker->runningCount; dbgmsg( NULL, "decrementing runningCount to %d", session->tracker->runningCount ); trackerPulse( session ); } } /*** **** LIFE CYCLE ***/ static void generateKeyParam( char * msg, int len ) { int i; const char * pool = "abcdefghijklmnopqrstuvwxyz0123456789"; const int poolSize = strlen( pool ); for( i = 0; i < len; ++i ) *msg++ = pool[tr_cryptoRandInt( poolSize )]; *msg = '\0'; } static int is_rfc2396_alnum( char ch ) { return ( '0' <= ch && ch <= '9' ) || ( 'A' <= ch && ch <= 'Z' ) || ( 'a' <= ch && ch <= 'z' ); } static void escape( char * out, const uint8_t * in, int in_len ) /* rfc2396 */ { const uint8_t *end = in + in_len; while( in != end ) if( is_rfc2396_alnum( *in ) ) *out++ = (char) *in++; else out += tr_snprintf( out, 4, "%%%02X", (unsigned int)*in++ ); *out = '\0'; } tr_tracker * tr_trackerNew( const tr_torrent * torrent ) { const tr_info * info = &torrent->info; tr_tracker * t; ensureGlobalsExist( torrent->handle ); t = tr_new0( tr_tracker, 1 ); t->publisher = tr_publisherNew( ); t->session = torrent->handle; t->scrapeIntervalSec = DEFAULT_SCRAPE_INTERVAL_SEC; t->retryScrapeIntervalSec = FIRST_SCRAPE_RETRY_INTERVAL_SEC; t->retryAnnounceIntervalSec = FIRST_ANNOUNCE_RETRY_INTERVAL_SEC; t->announceIntervalSec = DEFAULT_ANNOUNCE_INTERVAL_SEC; t->announceMinIntervalSec = DEFAULT_ANNOUNCE_MIN_INTERVAL_SEC; t->timesDownloaded = -1; t->seederCount = -1; t->leecherCount = -1; t->lastAnnounceResponse = -1; t->lastScrapeResponse = -1; t->manualAnnounceAllowedAt = ~(time_t)0; t->name = tr_strdup( info->name ); t->randOffset = tr_cryptoRandInt( 30 ); memcpy( t->hash, info->hash, SHA_DIGEST_LENGTH ); escape( t->escaped, info->hash, SHA_DIGEST_LENGTH ); generateKeyParam( t->key_param, KEYLEN ); t->trackerIndex = 0; if( trackerSupportsScrape( t, torrent ) ) t->scrapeAt = time( NULL ) + t->randOffset; return t; } static void onTrackerFreeNow( void * vt ) { tr_tracker * t = vt; tr_publisherFree( &t->publisher ); tr_free( t->name ); tr_free( t->trackerID ); tr_free( t->peer_id ); tr_free( t ); } /*** **** PUBLIC ***/ void tr_trackerFree( tr_tracker * t ) { if( t ) tr_runInEventThread( t->session, onTrackerFreeNow, t ); } tr_publisher_tag tr_trackerSubscribe( tr_tracker * t, tr_delivery_func func, void * user_data ) { return tr_publisherSubscribe( t->publisher, func, user_data ); } void tr_trackerUnsubscribe( tr_tracker * t, tr_publisher_tag tag ) { if( t ) tr_publisherUnsubscribe( t->publisher, tag ); } const tr_tracker_info * tr_trackerGetAddress( tr_tracker * t ) { return getCurrentAddress( t ); } time_t tr_trackerGetManualAnnounceTime( const struct tr_tracker * t ) { return t->isRunning ? t->manualAnnounceAllowedAt : 0; } int tr_trackerCanManualAnnounce( const tr_tracker * t ) { const time_t allow = tr_trackerGetManualAnnounceTime( t ); return allow && ( allow <= time( NULL ) ); } void tr_trackerGetCounts( const tr_tracker * t, int * setme_completedCount, int * setme_leecherCount, int * setme_seederCount ) { if( setme_completedCount ) *setme_completedCount = t->timesDownloaded; if( setme_leecherCount ) *setme_leecherCount = t->leecherCount; if( setme_seederCount ) *setme_seederCount = t->seederCount; } void tr_trackerStart( tr_tracker * t ) { if( t && !t->isRunning ) { tr_free( t->peer_id ); t->peer_id = tr_peerIdNew( ); t->isRunning = 1; enqueueRequest( t->session, t, TR_REQ_STARTED ); } } void tr_trackerReannounce( tr_tracker * t ) { enqueueRequest( t->session, t, TR_REQ_REANNOUNCE ); } void tr_trackerCompleted( tr_tracker * t ) { enqueueRequest( t->session, t, TR_REQ_COMPLETED ); } void tr_trackerStop( tr_tracker * t ) { if( t && t->isRunning ) { t->isRunning = 0; t->reannounceAt = t->manualAnnounceAllowedAt = 0; enqueueRequest( t->session, t, TR_REQ_STOPPED ); } } void tr_trackerChangeMyPort( tr_tracker * t ) { if( t->isRunning ) tr_trackerReannounce( t ); } void tr_trackerStat( const tr_tracker * t, struct tr_stat * setme ) { assert( t ); assert( setme ); setme->lastScrapeTime = t->lastScrapeTime; setme->nextScrapeTime = t->scrapeAt; setme->lastAnnounceTime = t->lastAnnounceTime; setme->nextAnnounceTime = t->reannounceAt; setme->manualAnnounceTime = t->manualAnnounceAllowedAt; if( t->lastScrapeResponse == -1 ) /* never been scraped */ *setme->scrapeResponse = '\0'; else tr_snprintf( setme->scrapeResponse, sizeof( setme->scrapeResponse ), "%s (%ld)", tr_webGetResponseStr( t->lastScrapeResponse ), t->lastScrapeResponse ); if( t->lastAnnounceResponse == -1 ) /* never been announced */ *setme->announceResponse = '\0'; else tr_snprintf( setme->announceResponse, sizeof( setme->announceResponse ), "%s (%ld)", tr_webGetResponseStr( t->lastAnnounceResponse ), t->lastAnnounceResponse ); }