mirror of
https://github.com/transmission/transmission
synced 2024-12-27 18:18:10 +00:00
35eb016209
get_next_scrape_time() was introduced in r12297. The rationale is that by rounding all scrape times to their nearest 10th second, they will tend to occur in batches and improve multiscrape.
1681 lines
49 KiB
C
1681 lines
49 KiB
C
/*
|
|
* This file Copyright (C) Mnemosyne LLC
|
|
*
|
|
* 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 <assert.h>
|
|
#include <limits.h> /* INT_MAX */
|
|
#include <stdio.h>
|
|
#include <stdlib.h> /* qsort() */
|
|
#include <string.h> /* strcmp(), memcpy() */
|
|
|
|
#include <event2/buffer.h>
|
|
#include <event2/event.h> /* evtimer */
|
|
|
|
#define __LIBTRANSMISSION_ANNOUNCER_MODULE___
|
|
|
|
#include "transmission.h"
|
|
#include "announcer.h"
|
|
#include "announcer-common.h"
|
|
#include "crypto.h" /* tr_cryptoRandInt(), tr_cryptoWeakRandInt() */
|
|
#include "peer-mgr.h" /* tr_peerMgrCompactToPex() */
|
|
#include "ptrarray.h"
|
|
#include "session.h"
|
|
#include "torrent.h"
|
|
#include "utils.h"
|
|
|
|
struct tr_tier;
|
|
|
|
static void tier_build_log_name( const struct tr_tier * tier,
|
|
char * buf, size_t buflen );
|
|
|
|
#define dbgmsg( tier, ... ) \
|
|
if( tr_deepLoggingIsActive( ) ) do { \
|
|
char name[128]; \
|
|
tier_build_log_name( tier, name, sizeof( name ) ); \
|
|
tr_deepLog( __FILE__, __LINE__, name, __VA_ARGS__ ); \
|
|
} while( 0 )
|
|
|
|
enum
|
|
{
|
|
/* unless the tracker says otherwise, rescrape this frequently */
|
|
DEFAULT_SCRAPE_INTERVAL_SEC = ( 60 * 30 ),
|
|
|
|
/* unless the tracker says otherwise, this is the announce interval */
|
|
DEFAULT_ANNOUNCE_INTERVAL_SEC = ( 60 * 10 ),
|
|
|
|
/* unless the tracker says otherwise, this is the announce min_interval */
|
|
DEFAULT_ANNOUNCE_MIN_INTERVAL_SEC = ( 60 * 2 ),
|
|
|
|
/* how many web tasks we allow at one time */
|
|
MAX_CONCURRENT_TASKS = 48,
|
|
|
|
/* the value of the 'numwant' argument passed in tracker requests. */
|
|
NUMWANT = 80,
|
|
|
|
UPKEEP_INTERVAL_SECS = 1,
|
|
|
|
/* this is how often to call the UDP tracker upkeep */
|
|
TAU_UPKEEP_INTERVAL_SECS = 5
|
|
};
|
|
|
|
/***
|
|
****
|
|
***/
|
|
|
|
const char*
|
|
tr_announce_event_get_string( tr_announce_event e )
|
|
{
|
|
switch( e )
|
|
{
|
|
case TR_ANNOUNCE_EVENT_COMPLETED: return "completed";
|
|
case TR_ANNOUNCE_EVENT_STARTED: return "started";
|
|
case TR_ANNOUNCE_EVENT_STOPPED: return "stopped";
|
|
default: return "";
|
|
}
|
|
}
|
|
|
|
/***
|
|
****
|
|
***/
|
|
|
|
static int
|
|
compareTransfer( uint64_t a_uploaded, uint64_t a_downloaded,
|
|
uint64_t b_uploaded, uint64_t b_downloaded )
|
|
{
|
|
/* higher upload count goes first */
|
|
if( a_uploaded != b_uploaded )
|
|
return a_uploaded > b_uploaded ? -1 : 1;
|
|
|
|
/* then higher download count goes first */
|
|
if( a_downloaded != b_downloaded )
|
|
return a_downloaded > b_downloaded ? -1 : 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
compareStops( const void * va, const void * vb )
|
|
{
|
|
const tr_announce_request * a = va;
|
|
const tr_announce_request * b = vb;
|
|
return compareTransfer( a->up, a->down, b->up, b->down);
|
|
}
|
|
|
|
/***
|
|
****
|
|
***/
|
|
|
|
/**
|
|
* "global" (per-tr_session) fields
|
|
*/
|
|
typedef struct tr_announcer
|
|
{
|
|
tr_ptrArray stops; /* tr_announce_request */
|
|
|
|
tr_session * session;
|
|
struct event * upkeepTimer;
|
|
int slotsAvailable;
|
|
int key;
|
|
time_t tauUpkeepAt;
|
|
}
|
|
tr_announcer;
|
|
|
|
bool
|
|
tr_announcerHasBacklog( const struct tr_announcer * announcer )
|
|
{
|
|
return announcer->slotsAvailable < 1;
|
|
}
|
|
|
|
static void
|
|
onUpkeepTimer( int foo UNUSED, short bar UNUSED, void * vannouncer );
|
|
|
|
void
|
|
tr_announcerInit( tr_session * session )
|
|
{
|
|
tr_announcer * a;
|
|
|
|
assert( tr_isSession( session ) );
|
|
|
|
a = tr_new0( tr_announcer, 1 );
|
|
a->stops = TR_PTR_ARRAY_INIT;
|
|
a->key = tr_cryptoRandInt( INT_MAX );
|
|
a->session = session;
|
|
a->slotsAvailable = MAX_CONCURRENT_TASKS;
|
|
a->upkeepTimer = evtimer_new( session->event_base, onUpkeepTimer, a );
|
|
tr_timerAdd( a->upkeepTimer, UPKEEP_INTERVAL_SECS, 0 );
|
|
|
|
session->announcer = a;
|
|
}
|
|
|
|
static void flushCloseMessages( tr_announcer * announcer );
|
|
|
|
void
|
|
tr_announcerClose( tr_session * session )
|
|
{
|
|
tr_announcer * announcer = session->announcer;
|
|
|
|
flushCloseMessages( announcer );
|
|
|
|
tr_tracker_udp_start_shutdown( session );
|
|
|
|
event_free( announcer->upkeepTimer );
|
|
announcer->upkeepTimer = NULL;
|
|
|
|
tr_ptrArrayDestruct( &announcer->stops, NULL );
|
|
|
|
session->announcer = NULL;
|
|
tr_free( announcer );
|
|
}
|
|
|
|
/***
|
|
****
|
|
***/
|
|
|
|
/* a row in tr_tier's list of trackers */
|
|
typedef struct
|
|
{
|
|
char * key;
|
|
char * announce;
|
|
char * scrape;
|
|
|
|
char * tracker_id_str;
|
|
|
|
int seederCount;
|
|
int leecherCount;
|
|
int downloadCount;
|
|
int downloaderCount;
|
|
|
|
int consecutiveFailures;
|
|
|
|
uint32_t id;
|
|
}
|
|
tr_tracker;
|
|
|
|
/* format: host+':'+ port */
|
|
static char *
|
|
getKey( const char * url )
|
|
{
|
|
char * ret;
|
|
char * scheme = NULL;
|
|
char * host = NULL;
|
|
int port = 0;
|
|
|
|
tr_urlParse( url, -1, &scheme, &host, &port, NULL );
|
|
ret = tr_strdup_printf( "%s://%s:%d", (scheme?scheme:"invalid"), (host?host:"invalid"), port );
|
|
|
|
tr_free( host );
|
|
tr_free( scheme );
|
|
return ret;
|
|
}
|
|
|
|
static void
|
|
trackerConstruct( tr_tracker * tracker, const tr_tracker_info * inf )
|
|
{
|
|
memset( tracker, 0, sizeof( tr_tracker ) );
|
|
tracker->key = getKey( inf->announce );
|
|
tracker->announce = tr_strdup( inf->announce );
|
|
tracker->scrape = tr_strdup( inf->scrape );
|
|
tracker->id = inf->id;
|
|
tracker->seederCount = -1;
|
|
tracker->leecherCount = -1;
|
|
tracker->downloadCount = -1;
|
|
}
|
|
|
|
static void
|
|
trackerDestruct( tr_tracker * tracker )
|
|
{
|
|
tr_free( tracker->tracker_id_str );
|
|
tr_free( tracker->scrape );
|
|
tr_free( tracker->announce );
|
|
tr_free( tracker->key );
|
|
}
|
|
|
|
/***
|
|
****
|
|
***/
|
|
|
|
struct tr_torrent_tiers;
|
|
|
|
/** @brief A group of trackers in a single tier, as per the multitracker spec */
|
|
typedef struct tr_tier
|
|
{
|
|
/* number of up/down/corrupt bytes since the last time we sent an
|
|
* "event=stopped" message that was acknowledged by the tracker */
|
|
uint64_t byteCounts[3];
|
|
|
|
tr_tracker * trackers;
|
|
int tracker_count;
|
|
tr_tracker * currentTracker;
|
|
int currentTrackerIndex;
|
|
|
|
tr_torrent * tor;
|
|
|
|
time_t scrapeAt;
|
|
time_t lastScrapeStartTime;
|
|
time_t lastScrapeTime;
|
|
bool lastScrapeSucceeded;
|
|
bool lastScrapeTimedOut;
|
|
|
|
time_t announceAt;
|
|
time_t manualAnnounceAllowedAt;
|
|
time_t lastAnnounceStartTime;
|
|
time_t lastAnnounceTime;
|
|
bool lastAnnounceSucceeded;
|
|
bool lastAnnounceTimedOut;
|
|
|
|
tr_announce_event * announce_events;
|
|
int announce_event_count;
|
|
int announce_event_alloc;
|
|
|
|
/* unique lookup key */
|
|
int key;
|
|
|
|
int scrapeIntervalSec;
|
|
int announceIntervalSec;
|
|
int announceMinIntervalSec;
|
|
|
|
int lastAnnouncePeerCount;
|
|
|
|
bool isRunning;
|
|
bool isAnnouncing;
|
|
bool isScraping;
|
|
bool wasCopied;
|
|
|
|
char lastAnnounceStr[128];
|
|
char lastScrapeStr[128];
|
|
}
|
|
tr_tier;
|
|
|
|
static time_t
|
|
get_next_scrape_time( int interval )
|
|
{
|
|
const time_t now = tr_time( );
|
|
|
|
/* Add the interval, and then increment to the nearest 10th second.
|
|
* The latter step is to increase the odds of several torrents coming
|
|
* due at the same time to improve multiscrape. */
|
|
return (now+interval) + (10-((now+interval)%10));
|
|
}
|
|
|
|
static void
|
|
tierConstruct( tr_tier * tier, tr_torrent * tor )
|
|
{
|
|
static int nextKey = 1;
|
|
|
|
memset( tier, 0, sizeof( tr_tier ) );
|
|
|
|
tier->key = nextKey++;
|
|
tier->currentTrackerIndex = -1;
|
|
tier->scrapeIntervalSec = DEFAULT_SCRAPE_INTERVAL_SEC;
|
|
tier->announceIntervalSec = DEFAULT_ANNOUNCE_INTERVAL_SEC;
|
|
tier->announceMinIntervalSec = DEFAULT_ANNOUNCE_MIN_INTERVAL_SEC;
|
|
tier->scrapeAt = get_next_scrape_time( tr_cryptoWeakRandInt( 180 ) );
|
|
tier->tor = tor;
|
|
}
|
|
|
|
static void
|
|
tierDestruct( tr_tier * tier )
|
|
{
|
|
tr_free( tier->announce_events );
|
|
}
|
|
|
|
static void
|
|
tier_build_log_name( const tr_tier * tier, char * buf, size_t buflen )
|
|
{
|
|
tr_snprintf( buf, buflen, "[%s---%s]",
|
|
( tier && tier->tor ) ? tr_torrentName( tier->tor ) : "?",
|
|
( tier && tier->currentTracker ) ? tier->currentTracker->key : "?" );
|
|
}
|
|
|
|
static void
|
|
tierIncrementTracker( tr_tier * tier )
|
|
{
|
|
/* move our index to the next tracker in the tier */
|
|
const int i = ( tier->currentTracker == NULL )
|
|
? 0
|
|
: ( tier->currentTrackerIndex + 1 ) % tier->tracker_count;
|
|
tier->currentTrackerIndex = i;
|
|
tier->currentTracker = &tier->trackers[i];
|
|
|
|
/* reset some of the tier's fields */
|
|
tier->scrapeIntervalSec = DEFAULT_SCRAPE_INTERVAL_SEC;
|
|
tier->announceIntervalSec = DEFAULT_ANNOUNCE_INTERVAL_SEC;
|
|
tier->announceMinIntervalSec = DEFAULT_ANNOUNCE_MIN_INTERVAL_SEC;
|
|
tier->isAnnouncing = false;
|
|
tier->isScraping = false;
|
|
tier->lastAnnounceStartTime = 0;
|
|
tier->lastScrapeStartTime = 0;
|
|
}
|
|
|
|
/***
|
|
****
|
|
***/
|
|
|
|
/**
|
|
* @brief Opaque, per-torrent data structure for tracker announce information
|
|
*
|
|
* this opaque data structure can be found in tr_torrent.tiers
|
|
*/
|
|
typedef struct tr_torrent_tiers
|
|
{
|
|
tr_tier * tiers;
|
|
int tier_count;
|
|
|
|
tr_tracker * trackers;
|
|
int tracker_count;
|
|
|
|
tr_tracker_callback * callback;
|
|
void * callbackData;
|
|
}
|
|
tr_torrent_tiers;
|
|
|
|
static tr_torrent_tiers*
|
|
tiersNew( void )
|
|
{
|
|
return tr_new0( tr_torrent_tiers, 1 );
|
|
}
|
|
|
|
static void
|
|
tiersDestruct( tr_torrent_tiers * tt )
|
|
{
|
|
int i;
|
|
|
|
for( i=0; i<tt->tracker_count; ++i )
|
|
trackerDestruct( &tt->trackers[i] );
|
|
tr_free( tt->trackers );
|
|
|
|
for( i=0; i<tt->tier_count; ++i )
|
|
tierDestruct( &tt->tiers[i] );
|
|
tr_free( tt->tiers );
|
|
}
|
|
|
|
static void
|
|
tiersFree( tr_torrent_tiers * tt )
|
|
{
|
|
tiersDestruct( tt );
|
|
tr_free( tt );
|
|
}
|
|
|
|
static tr_tier*
|
|
getTier( tr_announcer * announcer, const uint8_t * info_hash, int tierId )
|
|
{
|
|
tr_tier * tier = NULL;
|
|
|
|
if( announcer != NULL )
|
|
{
|
|
tr_session * session = announcer->session;
|
|
tr_torrent * tor = tr_torrentFindFromHash( session, info_hash );
|
|
|
|
if( tor && tor->tiers )
|
|
{
|
|
int i;
|
|
tr_torrent_tiers * tt = tor->tiers;
|
|
|
|
for( i=0; !tier && i<tt->tier_count; ++i )
|
|
if( tt->tiers[i].key == tierId )
|
|
tier = &tt->tiers[i];
|
|
}
|
|
}
|
|
|
|
return tier;
|
|
}
|
|
|
|
/***
|
|
**** PUBLISH
|
|
***/
|
|
|
|
static const tr_tracker_event TRACKER_EVENT_INIT = { 0, 0, 0, 0, 0, 0 };
|
|
|
|
static void
|
|
publishMessage( tr_tier * tier, const char * msg, int type )
|
|
{
|
|
if( tier && tier->tor && tier->tor->tiers && tier->tor->tiers->callback )
|
|
{
|
|
tr_torrent_tiers * tiers = tier->tor->tiers;
|
|
tr_tracker_event event = TRACKER_EVENT_INIT;
|
|
event.messageType = type;
|
|
event.text = msg;
|
|
if( tier->currentTracker )
|
|
event.tracker = tier->currentTracker->announce;
|
|
|
|
tiers->callback( tier->tor, &event, tiers->callbackData );
|
|
}
|
|
}
|
|
|
|
static void
|
|
publishErrorClear( tr_tier * tier )
|
|
{
|
|
publishMessage( tier, NULL, TR_TRACKER_ERROR_CLEAR );
|
|
}
|
|
|
|
static void
|
|
publishWarning( tr_tier * tier, const char * msg )
|
|
{
|
|
publishMessage( tier, msg, TR_TRACKER_WARNING );
|
|
}
|
|
|
|
static void
|
|
publishError( tr_tier * tier, const char * msg )
|
|
{
|
|
publishMessage( tier, msg, TR_TRACKER_ERROR );
|
|
}
|
|
|
|
static int8_t
|
|
getSeedProbability( tr_tier * tier, int seeds, int leechers, int pex_count )
|
|
{
|
|
/* special case optimization:
|
|
ocelot omits seeds from peer lists sent to seeds on private trackers.
|
|
so check for that case... */
|
|
if( ( leechers == pex_count ) && tr_torrentIsPrivate( tier->tor )
|
|
&& tr_torrentIsSeed( tier->tor )
|
|
&& ( seeds + leechers < NUMWANT ) )
|
|
return 0;
|
|
|
|
if( !seeds )
|
|
return 0;
|
|
|
|
if( seeds>=0 && leechers>=0 )
|
|
return (int8_t)((100.0*seeds)/(seeds+leechers));
|
|
|
|
return -1; /* unknown */
|
|
}
|
|
|
|
static void
|
|
publishPeersPex( tr_tier * tier, int seeds, int leechers,
|
|
const tr_pex * pex, int n )
|
|
{
|
|
if( tier->tor->tiers->callback )
|
|
{
|
|
tr_tracker_event e = TRACKER_EVENT_INIT;
|
|
e.messageType = TR_TRACKER_PEERS;
|
|
e.seedProbability = getSeedProbability( tier, seeds, leechers, n );
|
|
e.pex = pex;
|
|
e.pexCount = n;
|
|
dbgmsg( tier, "got %d peers; seed prob %d", n, (int)e.seedProbability );
|
|
|
|
tier->tor->tiers->callback( tier->tor, &e, NULL );
|
|
}
|
|
}
|
|
|
|
/***
|
|
****
|
|
***/
|
|
|
|
struct ann_tracker_info
|
|
{
|
|
tr_tracker_info info;
|
|
|
|
char * scheme;
|
|
char * host;
|
|
char * path;
|
|
int port;
|
|
};
|
|
|
|
|
|
/* primary key: tier
|
|
* secondary key: udp comes before http */
|
|
static int
|
|
filter_trackers_compare_func( const void * va, const void * vb )
|
|
{
|
|
const struct ann_tracker_info * a = va;
|
|
const struct ann_tracker_info * b = vb;
|
|
if( a->info.tier != b->info.tier )
|
|
return a->info.tier - b->info.tier;
|
|
return -strcmp( a->scheme, b->scheme );
|
|
}
|
|
|
|
/**
|
|
* Massages the incoming list of trackers into something we can use.
|
|
*/
|
|
static tr_tracker_info *
|
|
filter_trackers( tr_tracker_info * input, int input_count, int * setme_count )
|
|
{
|
|
int i, in;
|
|
int j, jn;
|
|
int n = 0;
|
|
struct tr_tracker_info * ret;
|
|
struct ann_tracker_info * tmp = tr_new0( struct ann_tracker_info, input_count );
|
|
|
|
/*for( i=0, in=input_count; i<in; ++i ) fprintf( stderr, "IN: [%d][%s]\n", input[i].tier, input[i].announce );*/
|
|
|
|
/* build a list of valid trackers */
|
|
for( i=0, in=input_count; i<in; ++i ) {
|
|
if( tr_urlIsValidTracker( input[i].announce ) ) {
|
|
int port;
|
|
char * scheme;
|
|
char * host;
|
|
char * path;
|
|
bool is_duplicate = false;
|
|
tr_urlParse( input[i].announce, -1, &scheme, &host, &port, &path );
|
|
|
|
/* weed out one common source of duplicates:
|
|
* "http://tracker/announce" +
|
|
* "http://tracker:80/announce"
|
|
*/
|
|
for( j=0, jn=n; !is_duplicate && j<jn; ++j )
|
|
is_duplicate = (tmp[j].port==port)
|
|
&& !strcmp(tmp[j].scheme,scheme)
|
|
&& !strcmp(tmp[j].host,host)
|
|
&& !strcmp(tmp[j].path,path);
|
|
|
|
if( is_duplicate ) {
|
|
tr_free( path );
|
|
tr_free( host );
|
|
tr_free( scheme );
|
|
continue;
|
|
}
|
|
tmp[n].info = input[i];
|
|
tmp[n].scheme = scheme;
|
|
tmp[n].host = host;
|
|
tmp[n].port = port;
|
|
tmp[n].path = path;
|
|
n++;
|
|
}
|
|
}
|
|
|
|
/* if two announce URLs differ only by scheme, put them in the same tier.
|
|
* (note: this can leave gaps in the `tier' values, but since the calling
|
|
* function doesn't care, there's no point in removing the gaps...) */
|
|
for( i=0, in=n; i<in; ++i )
|
|
for( j=i+1, jn=n; j<jn; ++j )
|
|
if( (tmp[i].info.tier!=tmp[j].info.tier)
|
|
&& (tmp[i].port==tmp[j].port)
|
|
&& !tr_strcmp0(tmp[i].host,tmp[j].host)
|
|
&& !tr_strcmp0(tmp[i].path,tmp[j].path) )
|
|
tmp[j].info.tier = tmp[i].info.tier;
|
|
|
|
/* sort them, for two reasons:
|
|
* (1) unjumble the tiers from the previous step
|
|
* (2) move the UDP trackers to the front of each tier */
|
|
qsort( tmp, n, sizeof(struct ann_tracker_info), filter_trackers_compare_func );
|
|
|
|
/* build the output */
|
|
*setme_count = n;
|
|
ret = tr_new0( tr_tracker_info, n );
|
|
for( i=0, in=n; i<in; ++i )
|
|
ret[i] = tmp[i].info;
|
|
|
|
/* cleanup */
|
|
for( i=0, in=n; i<in; ++i ) {
|
|
tr_free( tmp[i].path );
|
|
tr_free( tmp[i].host );
|
|
tr_free( tmp[i].scheme );
|
|
}
|
|
tr_free( tmp );
|
|
|
|
/*for( i=0, in=n; i<in; ++i ) fprintf( stderr, "OUT: [%d][%s]\n", ret[i].tier, ret[i].announce );*/
|
|
return ret;
|
|
}
|
|
|
|
|
|
static void
|
|
addTorrentToTier( tr_torrent_tiers * tt, tr_torrent * tor )
|
|
{
|
|
int i, n;
|
|
int tier_count;
|
|
tr_tier * tier;
|
|
tr_tracker_info * infos = filter_trackers( tor->info.trackers,
|
|
tor->info.trackerCount, &n );
|
|
|
|
/* build the array of trackers */
|
|
tt->trackers = tr_new0( tr_tracker, n );
|
|
tt->tracker_count = n;
|
|
for( i=0; i<n; ++i )
|
|
trackerConstruct( &tt->trackers[i], &infos[i] );
|
|
|
|
/* count how many tiers there are */
|
|
tier_count = 0;
|
|
for( i=0; i<n; ++i )
|
|
if( !i || ( infos[i].tier != infos[i-1].tier ) )
|
|
++tier_count;
|
|
|
|
/* build the array of tiers */
|
|
tier = NULL;
|
|
tt->tiers = tr_new0( tr_tier, tier_count );
|
|
tt->tier_count = 0;
|
|
for( i=0; i<n; ++i ) {
|
|
if( i && ( infos[i].tier == infos[i-1].tier ) )
|
|
++tier->tracker_count;
|
|
else {
|
|
tier = &tt->tiers[tt->tier_count++];
|
|
tierConstruct( tier, tor );
|
|
tier->trackers = &tt->trackers[i];
|
|
tier->tracker_count = 1;
|
|
tierIncrementTracker( tier );
|
|
}
|
|
}
|
|
|
|
/* cleanup */
|
|
tr_free( infos );
|
|
}
|
|
|
|
tr_torrent_tiers *
|
|
tr_announcerAddTorrent( tr_torrent * tor,
|
|
tr_tracker_callback * callback,
|
|
void * callbackData )
|
|
{
|
|
tr_torrent_tiers * tiers;
|
|
|
|
assert( tr_isTorrent( tor ) );
|
|
|
|
tiers = tiersNew( );
|
|
tiers->callback = callback;
|
|
tiers->callbackData = callbackData;
|
|
|
|
addTorrentToTier( tiers, tor );
|
|
|
|
return tiers;
|
|
}
|
|
|
|
/***
|
|
****
|
|
***/
|
|
|
|
static bool
|
|
tierCanManualAnnounce( const tr_tier * tier )
|
|
{
|
|
return tier->manualAnnounceAllowedAt <= tr_time( );
|
|
}
|
|
|
|
bool
|
|
tr_announcerCanManualAnnounce( const tr_torrent * tor )
|
|
{
|
|
int i;
|
|
struct tr_torrent_tiers * tt = NULL;
|
|
|
|
assert( tr_isTorrent( tor ) );
|
|
assert( tor->tiers != NULL );
|
|
|
|
if( tor->isRunning )
|
|
tt = tor->tiers;
|
|
|
|
/* return true if any tier can manual announce */
|
|
for( i=0; tt && i<tt->tier_count; ++i )
|
|
if( tierCanManualAnnounce( &tt->tiers[i] ) )
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
time_t
|
|
tr_announcerNextManualAnnounce( const tr_torrent * tor )
|
|
{
|
|
int i;
|
|
time_t ret = ~(time_t)0;
|
|
struct tr_torrent_tiers * tt = tor->tiers;
|
|
|
|
/* find the earliest manual announce time from all peers */
|
|
for( i=0; tt && i<tt->tier_count; ++i )
|
|
if( tt->tiers[i].isRunning )
|
|
ret = MIN( ret, tt->tiers[i].manualAnnounceAllowedAt );
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void
|
|
dbgmsg_tier_announce_queue( const tr_tier * tier )
|
|
{
|
|
if( tr_deepLoggingIsActive( ) )
|
|
{
|
|
int i;
|
|
char * str;
|
|
char name[128];
|
|
struct evbuffer * buf = evbuffer_new( );
|
|
|
|
tier_build_log_name( tier, name, sizeof( name ) );
|
|
for( i=0; i<tier->announce_event_count; ++i )
|
|
{
|
|
const tr_announce_event e = tier->announce_events[i];
|
|
const char * str = tr_announce_event_get_string( e );
|
|
evbuffer_add_printf( buf, "[%d:%s]", i, str );
|
|
}
|
|
str = evbuffer_free_to_str( buf );
|
|
tr_deepLog( __FILE__, __LINE__, name, "announce queue is %s", str );
|
|
tr_free( str );
|
|
}
|
|
}
|
|
|
|
static void
|
|
tier_announce_remove_trailing( tr_tier * tier, tr_announce_event e )
|
|
{
|
|
while( ( tier->announce_event_count > 0 )
|
|
&& ( tier->announce_events[tier->announce_event_count-1] == e ) )
|
|
--tier->announce_event_count;
|
|
}
|
|
|
|
static void
|
|
tier_announce_event_push( tr_tier * tier,
|
|
tr_announce_event e,
|
|
time_t announceAt )
|
|
{
|
|
int i;
|
|
|
|
assert( tier != NULL );
|
|
|
|
dbgmsg_tier_announce_queue( tier );
|
|
dbgmsg( tier, "queued \"%s\"", tr_announce_event_get_string( e ) );
|
|
|
|
if( tier->announce_event_count > 0 )
|
|
{
|
|
/* special case #1: if we're adding a "stopped" event,
|
|
* dump everything leading up to it except "completed" */
|
|
if( e == TR_ANNOUNCE_EVENT_STOPPED ) {
|
|
bool has_completed = false;
|
|
const tr_announce_event c = TR_ANNOUNCE_EVENT_COMPLETED;
|
|
for( i=0; !has_completed && i<tier->announce_event_count; ++i )
|
|
has_completed = c == tier->announce_events[i];
|
|
tier->announce_event_count = 0;
|
|
if( has_completed )
|
|
tier->announce_events[tier->announce_event_count++] = c;
|
|
}
|
|
|
|
/* special case #2: dump all empty strings leading up to this event */
|
|
tier_announce_remove_trailing( tier, TR_ANNOUNCE_EVENT_NONE );
|
|
|
|
/* special case #3: no consecutive duplicates */
|
|
tier_announce_remove_trailing( tier, e );
|
|
}
|
|
|
|
/* make room in the array for another event */
|
|
if( tier->announce_event_alloc <= tier->announce_event_count ) {
|
|
tier->announce_event_alloc += 4;
|
|
tier->announce_events = tr_renew( tr_announce_event,
|
|
tier->announce_events,
|
|
tier->announce_event_alloc );
|
|
}
|
|
|
|
/* add it */
|
|
tier->announce_events[tier->announce_event_count++] = e;
|
|
tier->announceAt = announceAt;
|
|
|
|
dbgmsg_tier_announce_queue( tier );
|
|
dbgmsg( tier, "announcing in %d seconds", (int)difftime(announceAt,tr_time()) );
|
|
}
|
|
|
|
static tr_announce_event
|
|
tier_announce_event_pull( tr_tier * tier )
|
|
{
|
|
const tr_announce_event e = tier->announce_events[0];
|
|
|
|
tr_removeElementFromArray( tier->announce_events,
|
|
0, sizeof( tr_announce_event ),
|
|
tier->announce_event_count-- );
|
|
|
|
return e;
|
|
}
|
|
|
|
static void
|
|
torrentAddAnnounce( tr_torrent * tor, tr_announce_event e, time_t announceAt )
|
|
{
|
|
int i;
|
|
struct tr_torrent_tiers * tt = tor->tiers;
|
|
|
|
/* walk through each tier and tell them to announce */
|
|
for( i=0; i<tt->tier_count; ++i )
|
|
tier_announce_event_push( &tt->tiers[i], e, announceAt );
|
|
}
|
|
|
|
void
|
|
tr_announcerTorrentStarted( tr_torrent * tor )
|
|
{
|
|
torrentAddAnnounce( tor, TR_ANNOUNCE_EVENT_STARTED, tr_time( ) );
|
|
}
|
|
void
|
|
tr_announcerManualAnnounce( tr_torrent * tor )
|
|
{
|
|
torrentAddAnnounce( tor, TR_ANNOUNCE_EVENT_NONE, tr_time( ) );
|
|
}
|
|
void
|
|
tr_announcerTorrentStopped( tr_torrent * tor )
|
|
{
|
|
torrentAddAnnounce( tor, TR_ANNOUNCE_EVENT_STOPPED, tr_time( ) );
|
|
}
|
|
void
|
|
tr_announcerTorrentCompleted( tr_torrent * tor )
|
|
{
|
|
torrentAddAnnounce( tor, TR_ANNOUNCE_EVENT_COMPLETED, tr_time( ) );
|
|
}
|
|
void
|
|
tr_announcerChangeMyPort( tr_torrent * tor )
|
|
{
|
|
tr_announcerTorrentStarted( tor );
|
|
}
|
|
|
|
/***
|
|
****
|
|
***/
|
|
|
|
void
|
|
tr_announcerAddBytes( tr_torrent * tor, int type, uint32_t byteCount )
|
|
{
|
|
int i;
|
|
struct tr_torrent_tiers * tt = tor->tiers;
|
|
|
|
assert( tr_isTorrent( tor ) );
|
|
assert( type==TR_ANN_UP || type==TR_ANN_DOWN || type==TR_ANN_CORRUPT );
|
|
|
|
for( i=0; i<tt->tier_count; ++i )
|
|
tt->tiers[i].byteCounts[ type ] += byteCount;
|
|
}
|
|
|
|
/***
|
|
****
|
|
***/
|
|
|
|
static tr_announce_request *
|
|
announce_request_new( const tr_announcer * announcer,
|
|
const tr_torrent * tor,
|
|
const tr_tier * tier,
|
|
tr_announce_event event )
|
|
{
|
|
tr_announce_request * req = tr_new0( tr_announce_request, 1 );
|
|
req->port = tr_sessionGetPublicPeerPort( announcer->session );
|
|
req->url = tr_strdup( tier->currentTracker->announce );
|
|
req->tracker_id_str = tr_strdup( tier->currentTracker->tracker_id_str );
|
|
memcpy( req->info_hash, tor->info.hash, SHA_DIGEST_LENGTH );
|
|
memcpy( req->peer_id, tor->peer_id, PEER_ID_LEN );
|
|
req->up = tier->byteCounts[TR_ANN_UP];
|
|
req->down = tier->byteCounts[TR_ANN_DOWN];
|
|
req->corrupt = tier->byteCounts[TR_ANN_CORRUPT];
|
|
req->left = tr_cpLeftUntilComplete( &tor->completion ),
|
|
req->event = event;
|
|
req->numwant = event == TR_ANNOUNCE_EVENT_STOPPED ? 0 : NUMWANT;
|
|
req->key = announcer->key;
|
|
req->partial_seed = tr_cpGetStatus( &tor->completion ) == TR_PARTIAL_SEED;
|
|
tier_build_log_name( tier, req->log_name, sizeof( req->log_name ) );
|
|
return req;
|
|
}
|
|
|
|
void
|
|
tr_announcerRemoveTorrent( tr_announcer * announcer, tr_torrent * tor )
|
|
{
|
|
struct tr_torrent_tiers * tt = tor->tiers;
|
|
|
|
if( tt != NULL )
|
|
{
|
|
int i;
|
|
for( i=0; i<tt->tier_count; ++i )
|
|
{
|
|
tr_tier * tier = &tt->tiers[i];
|
|
if( tier->isRunning )
|
|
{
|
|
const tr_announce_event e = TR_ANNOUNCE_EVENT_STOPPED;
|
|
tr_announce_request * req = announce_request_new( announcer, tor, tier, e );
|
|
tr_ptrArrayInsertSorted( &announcer->stops, req, compareStops );
|
|
}
|
|
}
|
|
|
|
tiersFree( tor->tiers );
|
|
tor->tiers = NULL;
|
|
}
|
|
}
|
|
|
|
static int
|
|
getRetryInterval( const tr_tracker * t )
|
|
{
|
|
int minutes;
|
|
const unsigned int jitter_seconds = tr_cryptoWeakRandInt( 60 );
|
|
switch( t->consecutiveFailures ) {
|
|
case 0: minutes = 1; break;
|
|
case 1: minutes = 5; break;
|
|
case 2: minutes = 15; break;
|
|
case 3: minutes = 30; break;
|
|
case 4: minutes = 60; break;
|
|
default: minutes = 120; break;
|
|
}
|
|
return ( minutes * 60 ) + jitter_seconds;
|
|
}
|
|
|
|
struct announce_data
|
|
{
|
|
int tierId;
|
|
time_t timeSent;
|
|
tr_announce_event event;
|
|
tr_session * session;
|
|
|
|
/** If the request succeeds, the value for tier's "isRunning" flag */
|
|
bool isRunningOnSuccess;
|
|
};
|
|
|
|
static void
|
|
on_announce_error( tr_tier * tier, const char * err, tr_announce_event e )
|
|
{
|
|
int interval;
|
|
|
|
/* increment the error count */
|
|
if( tier->currentTracker != NULL )
|
|
++tier->currentTracker->consecutiveFailures;
|
|
|
|
/* set the error message */
|
|
dbgmsg( tier, "%s", err );
|
|
tr_torinf( tier->tor, "%s", err );
|
|
tr_strlcpy( tier->lastAnnounceStr, err, sizeof( tier->lastAnnounceStr ) );
|
|
|
|
/* switch to the next tracker */
|
|
tierIncrementTracker( tier );
|
|
|
|
/* schedule a reannounce */
|
|
interval = getRetryInterval( tier->currentTracker );
|
|
dbgmsg( tier, "Retrying announce in %d seconds.", interval );
|
|
tr_torinf( tier->tor, "Retrying scrape in %d seconds.", interval );
|
|
tier_announce_event_push( tier, e, tr_time( ) + interval );
|
|
}
|
|
|
|
static void
|
|
on_announce_done( const tr_announce_response * response,
|
|
void * vdata )
|
|
{
|
|
struct announce_data * data = vdata;
|
|
tr_announcer * announcer = data->session->announcer;
|
|
tr_tier * tier = getTier( announcer, response->info_hash, data->tierId );
|
|
const time_t now = tr_time( );
|
|
const tr_announce_event event = data->event;
|
|
|
|
if( announcer )
|
|
++announcer->slotsAvailable;
|
|
|
|
if( tier != NULL )
|
|
{
|
|
tr_tracker * tracker;
|
|
|
|
dbgmsg( tier, "Got announce response: "
|
|
"connected:%d "
|
|
"timeout:%d "
|
|
"seeders:%d "
|
|
"leechers:%d "
|
|
"downloads:%d "
|
|
"interval:%d "
|
|
"min_interval:%d "
|
|
"tracker_id_str:%s "
|
|
"pex:%zu "
|
|
"pex6:%zu "
|
|
"err:%s "
|
|
"warn:%s",
|
|
(int)response->did_connect,
|
|
(int)response->did_timeout,
|
|
response->seeders,
|
|
response->leechers,
|
|
response->downloads,
|
|
response->interval,
|
|
response->min_interval,
|
|
response->tracker_id_str ? response->tracker_id_str : "none",
|
|
response->pex_count,
|
|
response->pex6_count,
|
|
response->errmsg ? response->errmsg : "none",
|
|
response->warning ? response->warning : "none" );
|
|
|
|
tier->lastAnnounceTime = now;
|
|
tier->lastAnnounceTimedOut = response->did_timeout;
|
|
tier->lastAnnounceSucceeded = false;
|
|
tier->isAnnouncing = false;
|
|
tier->manualAnnounceAllowedAt = now + tier->announceMinIntervalSec;
|
|
|
|
if( !response->did_connect )
|
|
{
|
|
on_announce_error( tier, _( "Could not connect to tracker" ), event );
|
|
}
|
|
else if( response->did_timeout )
|
|
{
|
|
on_announce_error( tier, _( "Tracker did not respond" ), event );
|
|
}
|
|
else if( response->errmsg )
|
|
{
|
|
publishError( tier, response->errmsg );
|
|
on_announce_error( tier, response->errmsg, event );
|
|
}
|
|
else
|
|
{
|
|
int i;
|
|
const char * str;
|
|
const bool isStopped = event == TR_ANNOUNCE_EVENT_STOPPED;
|
|
|
|
publishErrorClear( tier );
|
|
|
|
if(( tracker = tier->currentTracker ))
|
|
{
|
|
tracker->consecutiveFailures = 0;
|
|
tracker->seederCount = response->seeders;
|
|
tracker->leecherCount = response->leechers;
|
|
tracker->downloadCount = response->downloads;
|
|
|
|
if(( str = response->tracker_id_str ))
|
|
{
|
|
tr_free( tracker->tracker_id_str );
|
|
tracker->tracker_id_str = tr_strdup( str );
|
|
}
|
|
}
|
|
|
|
if(( str = response->warning ))
|
|
{
|
|
tr_strlcpy( tier->lastAnnounceStr, str,
|
|
sizeof( tier->lastAnnounceStr ) );
|
|
dbgmsg( tier, "tracker gave \"%s\"", str );
|
|
publishWarning( tier, str );
|
|
}
|
|
|
|
if(( i = response->min_interval ))
|
|
tier->announceMinIntervalSec = i;
|
|
|
|
if(( i = response->interval ))
|
|
tier->announceIntervalSec = i;
|
|
|
|
if( response->pex_count > 0 )
|
|
publishPeersPex( tier, response->seeders, response->leechers,
|
|
response->pex, response->pex_count );
|
|
|
|
if( response->pex6_count > 0 )
|
|
publishPeersPex( tier, response->seeders, response->leechers,
|
|
response->pex6, response->pex6_count );
|
|
|
|
if( !*tier->lastAnnounceStr )
|
|
tr_strlcpy( tier->lastAnnounceStr, _( "Success" ),
|
|
sizeof( tier->lastAnnounceStr ) );
|
|
|
|
tier->isRunning = data->isRunningOnSuccess;
|
|
tier->scrapeAt = get_next_scrape_time( tier->scrapeIntervalSec );
|
|
tier->lastScrapeTime = now;
|
|
tier->lastScrapeSucceeded = true;
|
|
tier->lastAnnounceSucceeded = true;
|
|
tier->lastAnnouncePeerCount = response->pex_count
|
|
+ response->pex6_count;
|
|
|
|
if( isStopped )
|
|
{
|
|
/* now that we've successfully stopped the torrent,
|
|
* we can reset the up/down/corrupt count we've kept
|
|
* for this tracker */
|
|
tier->byteCounts[ TR_ANN_UP ] = 0;
|
|
tier->byteCounts[ TR_ANN_DOWN ] = 0;
|
|
tier->byteCounts[ TR_ANN_CORRUPT ] = 0;
|
|
}
|
|
|
|
if( !isStopped && !tier->announce_event_count )
|
|
{
|
|
/* the queue is empty, so enqueue a perodic update */
|
|
i = tier->announceIntervalSec;
|
|
dbgmsg( tier, "Sending periodic reannounce in %d seconds", i );
|
|
tier_announce_event_push( tier, TR_ANNOUNCE_EVENT_NONE, now + i );
|
|
}
|
|
}
|
|
}
|
|
|
|
tr_free( data );
|
|
}
|
|
|
|
static void
|
|
announce_request_delegate( tr_announcer * announcer,
|
|
tr_announce_request * request,
|
|
tr_announce_response_func * callback,
|
|
void * callback_data )
|
|
{
|
|
tr_session * session = announcer->session;
|
|
|
|
if( !memcmp( request->url, "http", 4 ) )
|
|
tr_tracker_http_announce( session, request, callback, callback_data );
|
|
else if( !memcmp( request->url, "udp://", 6 ) )
|
|
tr_tracker_udp_announce( session, request, callback, callback_data );
|
|
else
|
|
tr_err( "Unsupported url: %s", request->url );
|
|
|
|
tr_free( request->tracker_id_str );
|
|
tr_free( request->url );
|
|
tr_free( request );
|
|
}
|
|
|
|
static void
|
|
tierAnnounce( tr_announcer * announcer, tr_tier * tier )
|
|
{
|
|
tr_announce_event announce_event;
|
|
tr_announce_request * req;
|
|
struct announce_data * data;
|
|
const tr_torrent * tor = tier->tor;
|
|
const time_t now = tr_time( );
|
|
|
|
assert( !tier->isAnnouncing );
|
|
assert( tier->announce_event_count > 0 );
|
|
|
|
announce_event = tier_announce_event_pull( tier );
|
|
req = announce_request_new( announcer, tor, tier, announce_event );
|
|
|
|
data = tr_new0( struct announce_data, 1 );
|
|
data->session = announcer->session;
|
|
data->tierId = tier->key;
|
|
data->isRunningOnSuccess = tor->isRunning;
|
|
data->timeSent = now;
|
|
data->event = announce_event;
|
|
|
|
tier->isAnnouncing = true;
|
|
tier->lastAnnounceStartTime = now;
|
|
--announcer->slotsAvailable;
|
|
|
|
announce_request_delegate( announcer, req, on_announce_done, data );
|
|
}
|
|
|
|
/***
|
|
****
|
|
**** SCRAPE
|
|
****
|
|
***/
|
|
|
|
static void
|
|
on_scrape_error( tr_tier * tier, const char * errmsg )
|
|
{
|
|
int interval;
|
|
|
|
/* increment the error count */
|
|
if( tier->currentTracker != NULL )
|
|
++tier->currentTracker->consecutiveFailures;
|
|
|
|
/* set the error message */
|
|
dbgmsg( tier, "Scrape error: %s", errmsg );
|
|
tr_torinf( tier->tor, "Scrape error: %s", errmsg );
|
|
tr_strlcpy( tier->lastScrapeStr, errmsg, sizeof( tier->lastScrapeStr ) );
|
|
|
|
/* switch to the next tracker */
|
|
tierIncrementTracker( tier );
|
|
|
|
/* schedule a rescrape */
|
|
interval = get_next_scrape_time( getRetryInterval( tier->currentTracker ) );
|
|
dbgmsg( tier, "Retrying scrape in %d seconds.", interval );
|
|
tr_torinf( tier->tor, "Retrying scrape in %d seconds.", interval );
|
|
tier->lastScrapeSucceeded = false;
|
|
tier->scrapeAt = get_next_scrape_time( interval );
|
|
}
|
|
|
|
static tr_tier *
|
|
find_tier( tr_torrent * tor, const char * scrape )
|
|
{
|
|
int i;
|
|
struct tr_torrent_tiers * tt = tor->tiers;
|
|
|
|
for( i=0; tt && i<tt->tier_count; ++i ) {
|
|
const tr_tracker * const tracker = tt->tiers[i].currentTracker;
|
|
if( tracker && !tr_strcmp0( scrape, tracker->scrape ) )
|
|
return &tt->tiers[i];
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static void
|
|
on_scrape_done( const tr_scrape_response * response, void * vsession )
|
|
{
|
|
int i;
|
|
const time_t now = tr_time( );
|
|
tr_session * session = vsession;
|
|
tr_announcer * announcer = session->announcer;
|
|
|
|
for( i=0; i<response->row_count; ++i )
|
|
{
|
|
const struct tr_scrape_response_row * row = &response->rows[i];
|
|
tr_torrent * tor = tr_torrentFindFromHash( session, row->info_hash );
|
|
|
|
if( tor != NULL )
|
|
{
|
|
tr_tier * tier = find_tier( tor, response->url );
|
|
|
|
if( tier != NULL )
|
|
{
|
|
dbgmsg( tier, "scraped url:%s -- "
|
|
"did_connect:%d "
|
|
"did_timeout:%d "
|
|
"seeders:%d "
|
|
"leechers:%d "
|
|
"downloads:%d "
|
|
"downloaders:%d "
|
|
"min_request_interval:%d "
|
|
"err:%s ",
|
|
response->url,
|
|
(int)response->did_connect,
|
|
(int)response->did_timeout,
|
|
row->seeders,
|
|
row->leechers,
|
|
row->downloads,
|
|
row->downloaders,
|
|
response->min_request_interval,
|
|
response->errmsg ? response->errmsg : "none" );
|
|
|
|
tier->isScraping = false;
|
|
tier->lastScrapeTime = now;
|
|
tier->lastScrapeSucceeded = false;
|
|
tier->lastScrapeTimedOut = response->did_timeout;
|
|
|
|
if( !response->did_connect )
|
|
{
|
|
on_scrape_error( tier, _( "Could not connect to tracker" ) );
|
|
}
|
|
else if( response->did_timeout )
|
|
{
|
|
on_scrape_error( tier, _( "Tracker did not respond" ) );
|
|
}
|
|
else if( response->errmsg )
|
|
{
|
|
on_scrape_error( tier, response->errmsg );
|
|
}
|
|
else
|
|
{
|
|
tr_tracker * tracker;
|
|
|
|
tier->lastScrapeSucceeded = true;
|
|
tier->scrapeIntervalSec = MAX( DEFAULT_SCRAPE_INTERVAL_SEC,
|
|
response->min_request_interval );
|
|
tier->scrapeAt = get_next_scrape_time( tier->scrapeIntervalSec );
|
|
tr_tordbg( tier->tor, "Scrape successful. Rescraping in %d seconds.",
|
|
tier->scrapeIntervalSec );
|
|
|
|
if(( tracker = tier->currentTracker ))
|
|
{
|
|
tracker->seederCount = row->seeders;
|
|
tracker->leecherCount = row->leechers;
|
|
tracker->downloadCount = row->downloads;
|
|
tracker->downloaderCount = row->downloaders;
|
|
tracker->consecutiveFailures = 0;
|
|
}
|
|
|
|
if( tr_torrentIsPrivate( tier->tor ) && !row->downloaders )
|
|
tr_peerMgrMarkAllAsSeeds( tier->tor );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if( announcer )
|
|
++announcer->slotsAvailable;
|
|
}
|
|
|
|
static void
|
|
scrape_request_delegate( tr_announcer * announcer,
|
|
tr_scrape_request * request,
|
|
tr_scrape_response_func * callback,
|
|
void * callback_data )
|
|
{
|
|
tr_session * session = announcer->session;
|
|
|
|
if( !memcmp( request->url, "http", 4 ) )
|
|
tr_tracker_http_scrape( session, request, callback, callback_data );
|
|
else if( !memcmp( request->url, "udp://", 6 ) )
|
|
tr_tracker_udp_scrape( session, request, callback, callback_data );
|
|
else
|
|
tr_err( "Unsupported url: %s", request->url );
|
|
}
|
|
|
|
static void
|
|
multiscrape( tr_announcer * announcer, tr_ptrArray * tiers )
|
|
{
|
|
int i;
|
|
int request_count = 0;
|
|
const time_t now = tr_time( );
|
|
const int tier_count = tr_ptrArraySize( tiers );
|
|
const int max_request_count = MIN( announcer->slotsAvailable, tier_count );
|
|
tr_scrape_request * requests = tr_new0( tr_scrape_request, max_request_count );
|
|
|
|
/* batch as many info_hashes into a request as we can */
|
|
for( i=0; i<tier_count; ++i )
|
|
{
|
|
int j;
|
|
tr_tier * tier = tr_ptrArrayNth( tiers, i );
|
|
char * url = tier->currentTracker->scrape;
|
|
const uint8_t * hash = tier->tor->info.hash;
|
|
|
|
/* if there's a request with this scrape URL and a free slot, use it */
|
|
for( j=0; j<request_count; ++j )
|
|
{
|
|
tr_scrape_request * req = &requests[j];
|
|
|
|
if( req->info_hash_count >= TR_MULTISCRAPE_MAX )
|
|
continue;
|
|
if( tr_strcmp0( req->url, url ) )
|
|
continue;
|
|
|
|
memcpy( req->info_hash[req->info_hash_count++], hash, SHA_DIGEST_LENGTH );
|
|
tier->isScraping = true;
|
|
tier->lastScrapeStartTime = now;
|
|
break;
|
|
}
|
|
|
|
/* otherwise, if there's room for another request, build a new one */
|
|
if( ( j==request_count ) && ( request_count < max_request_count ) )
|
|
{
|
|
tr_scrape_request * req = &requests[request_count++];
|
|
req->url = url;
|
|
tier_build_log_name( tier, req->log_name, sizeof( req->log_name ) );
|
|
|
|
memcpy( req->info_hash[req->info_hash_count++], hash, SHA_DIGEST_LENGTH );
|
|
tier->isScraping = true;
|
|
tier->lastScrapeStartTime = now;
|
|
}
|
|
}
|
|
|
|
/* send the requests we just built */
|
|
for( i=0; i<request_count; ++i )
|
|
scrape_request_delegate( announcer, &requests[i], on_scrape_done, announcer->session );
|
|
|
|
/* cleanup */
|
|
tr_free( requests );
|
|
}
|
|
|
|
static void
|
|
flushCloseMessages( tr_announcer * announcer )
|
|
{
|
|
int i;
|
|
const int n = tr_ptrArraySize( &announcer->stops );
|
|
|
|
for( i=0; i<n; ++i )
|
|
announce_request_delegate( announcer, tr_ptrArrayNth( &announcer->stops, i ), NULL, NULL );
|
|
|
|
tr_ptrArrayClear( &announcer->stops );
|
|
}
|
|
|
|
static bool
|
|
tierNeedsToAnnounce( const tr_tier * tier, const time_t now )
|
|
{
|
|
return !tier->isAnnouncing
|
|
&& !tier->isScraping
|
|
&& ( tier->announceAt != 0 )
|
|
&& ( tier->announceAt <= now )
|
|
&& ( tier->announce_event_count > 0 );
|
|
}
|
|
|
|
static bool
|
|
tierNeedsToScrape( const tr_tier * tier, const time_t now )
|
|
{
|
|
return ( !tier->isScraping )
|
|
&& ( tier->scrapeAt != 0 )
|
|
&& ( tier->scrapeAt <= now )
|
|
&& ( tier->currentTracker != NULL )
|
|
&& ( tier->currentTracker->scrape != NULL );
|
|
}
|
|
|
|
static int
|
|
compareTiers( const void * va, const void * vb )
|
|
{
|
|
int ret;
|
|
const tr_tier * a = *(const tr_tier**)va;
|
|
const tr_tier * b = *(const tr_tier**)vb;
|
|
|
|
/* primary key: larger stats come before smaller */
|
|
ret = compareTransfer( a->byteCounts[TR_ANN_UP], a->byteCounts[TR_ANN_DOWN],
|
|
b->byteCounts[TR_ANN_UP], b->byteCounts[TR_ANN_DOWN] );
|
|
|
|
/* secondary key: announcements that have been waiting longer go first */
|
|
if( !ret && ( a->announceAt != b->announceAt ) )
|
|
ret = a->announceAt < b->announceAt ? -1 : 1;
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void
|
|
announceMore( tr_announcer * announcer )
|
|
{
|
|
int i;
|
|
int n;
|
|
tr_torrent * tor;
|
|
tr_ptrArray announceMe = TR_PTR_ARRAY_INIT;
|
|
tr_ptrArray scrapeMe = TR_PTR_ARRAY_INIT;
|
|
const time_t now = tr_time( );
|
|
|
|
dbgmsg( NULL, "announceMore: slotsAvailable is %d", announcer->slotsAvailable );
|
|
|
|
if( announcer->slotsAvailable < 1 )
|
|
return;
|
|
|
|
/* build a list of tiers that need to be announced */
|
|
tor = NULL;
|
|
while(( tor = tr_torrentNext( announcer->session, tor ))) {
|
|
struct tr_torrent_tiers * tt = tor->tiers;
|
|
for( i=0; tt && i<tt->tier_count; ++i ) {
|
|
tr_tier * tier = &tt->tiers[i];
|
|
if( tierNeedsToAnnounce( tier, now ) )
|
|
tr_ptrArrayAppend( &announceMe, tier );
|
|
else if( tierNeedsToScrape( tier, now ) )
|
|
tr_ptrArrayAppend( &scrapeMe, tier );
|
|
}
|
|
}
|
|
|
|
/* if there are more tiers than slots available, prioritize */
|
|
n = tr_ptrArraySize( &announceMe );
|
|
if( n > announcer->slotsAvailable )
|
|
qsort( tr_ptrArrayBase(&announceMe), n, sizeof(tr_tier*), compareTiers );
|
|
|
|
/* announce some */
|
|
n = MIN( tr_ptrArraySize( &announceMe ), announcer->slotsAvailable );
|
|
for( i=0; i<n; ++i ) {
|
|
tr_tier * tier = tr_ptrArrayNth( &announceMe, i );
|
|
tr_tordbg( tier->tor, "%s", "Announcing to tracker" );
|
|
dbgmsg( tier, "announcing tier %d of %d", i, n );
|
|
tierAnnounce( announcer, tier );
|
|
}
|
|
|
|
/* scrape some */
|
|
multiscrape( announcer, &scrapeMe );
|
|
|
|
/* cleanup */
|
|
tr_ptrArrayDestruct( &scrapeMe, NULL );
|
|
tr_ptrArrayDestruct( &announceMe, NULL );
|
|
}
|
|
|
|
static void
|
|
onUpkeepTimer( int foo UNUSED, short bar UNUSED, void * vannouncer )
|
|
{
|
|
tr_announcer * announcer = vannouncer;
|
|
tr_session * session = announcer->session;
|
|
const bool is_closing = session->isClosed;
|
|
const time_t now = tr_time( );
|
|
|
|
tr_sessionLock( session );
|
|
|
|
/* maybe send out some "stopped" messages for closed torrents */
|
|
flushCloseMessages( announcer );
|
|
|
|
/* maybe send out some announcements to trackers */
|
|
if( !is_closing )
|
|
announceMore( announcer );
|
|
|
|
/* TAU upkeep */
|
|
if( announcer->tauUpkeepAt <= now ) {
|
|
announcer->tauUpkeepAt = now + TAU_UPKEEP_INTERVAL_SECS;
|
|
tr_tracker_udp_upkeep( session );
|
|
}
|
|
|
|
/* set up the next timer */
|
|
tr_timerAdd( announcer->upkeepTimer, UPKEEP_INTERVAL_SECS, 0 );
|
|
|
|
tr_sessionUnlock( session );
|
|
}
|
|
|
|
/***
|
|
****
|
|
***/
|
|
|
|
tr_tracker_stat *
|
|
tr_announcerStats( const tr_torrent * torrent, int * setmeTrackerCount )
|
|
{
|
|
int i;
|
|
int out = 0;
|
|
tr_tracker_stat * ret;
|
|
struct tr_torrent_tiers * tt;
|
|
const time_t now = tr_time( );
|
|
|
|
assert( tr_isTorrent( torrent ) );
|
|
assert( tr_torrentIsLocked( torrent ) );
|
|
|
|
tt = torrent->tiers;
|
|
|
|
/* alloc the stats */
|
|
*setmeTrackerCount = tt->tracker_count;
|
|
ret = tr_new0( tr_tracker_stat, tt->tracker_count );
|
|
|
|
/* populate the stats */
|
|
for( i=0; i<tt->tier_count; ++i )
|
|
{
|
|
int j;
|
|
const tr_tier * const tier = &tt->tiers[i];
|
|
for( j=0; j<tier->tracker_count; ++j )
|
|
{
|
|
const tr_tracker * const tracker = &tier->trackers[j];
|
|
tr_tracker_stat * st = &ret[out++];
|
|
|
|
st->id = tracker->id;
|
|
tr_strlcpy( st->host, tracker->key, sizeof( st->host ) );
|
|
tr_strlcpy( st->announce, tracker->announce, sizeof( st->announce ) );
|
|
st->tier = i;
|
|
st->isBackup = tracker != tier->currentTracker;
|
|
st->lastScrapeStartTime = tier->lastScrapeStartTime;
|
|
if( tracker->scrape )
|
|
tr_strlcpy( st->scrape, tracker->scrape, sizeof( st->scrape ) );
|
|
else
|
|
st->scrape[0] = '\0';
|
|
|
|
st->seederCount = tracker->seederCount;
|
|
st->leecherCount = tracker->leecherCount;
|
|
st->downloadCount = tracker->downloadCount;
|
|
|
|
if( st->isBackup )
|
|
{
|
|
st->scrapeState = TR_TRACKER_INACTIVE;
|
|
st->announceState = TR_TRACKER_INACTIVE;
|
|
st->nextScrapeTime = 0;
|
|
st->nextAnnounceTime = 0;
|
|
}
|
|
else
|
|
{
|
|
if(( st->hasScraped = tier->lastScrapeTime != 0 )) {
|
|
st->lastScrapeTime = tier->lastScrapeTime;
|
|
st->lastScrapeSucceeded = tier->lastScrapeSucceeded;
|
|
st->lastScrapeTimedOut = tier->lastScrapeTimedOut;
|
|
tr_strlcpy( st->lastScrapeResult, tier->lastScrapeStr,
|
|
sizeof( st->lastScrapeResult ) );
|
|
}
|
|
|
|
if( tier->isScraping )
|
|
st->scrapeState = TR_TRACKER_ACTIVE;
|
|
else if( !tier->scrapeAt )
|
|
st->scrapeState = TR_TRACKER_INACTIVE;
|
|
else if( tier->scrapeAt > now )
|
|
{
|
|
st->scrapeState = TR_TRACKER_WAITING;
|
|
st->nextScrapeTime = tier->scrapeAt;
|
|
}
|
|
else
|
|
st->scrapeState = TR_TRACKER_QUEUED;
|
|
|
|
st->lastAnnounceStartTime = tier->lastAnnounceStartTime;
|
|
|
|
if(( st->hasAnnounced = tier->lastAnnounceTime != 0 )) {
|
|
st->lastAnnounceTime = tier->lastAnnounceTime;
|
|
tr_strlcpy( st->lastAnnounceResult, tier->lastAnnounceStr,
|
|
sizeof( st->lastAnnounceResult ) );
|
|
st->lastAnnounceSucceeded = tier->lastAnnounceSucceeded;
|
|
st->lastAnnounceTimedOut = tier->lastAnnounceTimedOut;
|
|
st->lastAnnouncePeerCount = tier->lastAnnouncePeerCount;
|
|
}
|
|
|
|
if( tier->isAnnouncing )
|
|
st->announceState = TR_TRACKER_ACTIVE;
|
|
else if( !torrent->isRunning || !tier->announceAt )
|
|
st->announceState = TR_TRACKER_INACTIVE;
|
|
else if( tier->announceAt > now )
|
|
{
|
|
st->announceState = TR_TRACKER_WAITING;
|
|
st->nextAnnounceTime = tier->announceAt;
|
|
}
|
|
else
|
|
st->announceState = TR_TRACKER_QUEUED;
|
|
}
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
void
|
|
tr_announcerStatsFree( tr_tracker_stat * trackers,
|
|
int trackerCount UNUSED )
|
|
{
|
|
tr_free( trackers );
|
|
}
|
|
|
|
/***
|
|
****
|
|
***/
|
|
|
|
static void
|
|
copy_tier_attributes_impl( struct tr_tier * tgt, int trackerIndex, const tr_tier * src )
|
|
{
|
|
const tr_tier keep = *tgt;
|
|
|
|
/* sanity clause */
|
|
assert( trackerIndex < tgt->tracker_count );
|
|
assert( !tr_strcmp0( tgt->trackers[trackerIndex].announce, src->currentTracker->announce ) );
|
|
|
|
/* bitwise copy will handle most of tr_tier's fields... */
|
|
*tgt = *src;
|
|
|
|
/* ...fix the fields that can't be cleanly bitwise-copied */
|
|
tgt->wasCopied = true;
|
|
tgt->trackers = keep.trackers;
|
|
tgt->announce_events = tr_memdup( src->announce_events, sizeof( tr_announce_event ) * src->announce_event_count );
|
|
tgt->announce_event_count = src->announce_event_count;
|
|
tgt->announce_event_alloc = src->announce_event_count;
|
|
tgt->currentTrackerIndex = trackerIndex;
|
|
tgt->currentTracker = &tgt->trackers[trackerIndex];
|
|
tgt->currentTracker->seederCount = src->currentTracker->seederCount;
|
|
tgt->currentTracker->leecherCount = src->currentTracker->leecherCount;
|
|
tgt->currentTracker->downloadCount = src->currentTracker->downloadCount;
|
|
tgt->currentTracker->downloaderCount = src->currentTracker->downloaderCount;
|
|
}
|
|
|
|
static void
|
|
copy_tier_attributes( struct tr_torrent_tiers * tt, const tr_tier * src )
|
|
{
|
|
int i, j;
|
|
bool found = false;
|
|
|
|
/* find a tier (if any) which has a match for src->currentTracker */
|
|
for( i=0; !found && i<tt->tier_count; ++i )
|
|
for( j=0; !found && j<tt->tiers[i].tracker_count; ++j )
|
|
if(( found = !tr_strcmp0( src->currentTracker->announce, tt->tiers[i].trackers[j].announce )))
|
|
copy_tier_attributes_impl( &tt->tiers[i], j, src );
|
|
}
|
|
|
|
void
|
|
tr_announcerResetTorrent( tr_announcer * announcer UNUSED, tr_torrent * tor )
|
|
{
|
|
int i;
|
|
const time_t now = tr_time( );
|
|
struct tr_torrent_tiers * tt = tor->tiers;
|
|
tr_torrent_tiers old = *tt;
|
|
|
|
assert( tt != NULL );
|
|
|
|
/* remove the old tiers / trackers */
|
|
tt->tiers = NULL;
|
|
tt->trackers = NULL;
|
|
tt->tier_count = 0;
|
|
tt->tracker_count = 0;
|
|
|
|
/* create the new tiers / trackers */
|
|
addTorrentToTier( tt, tor );
|
|
|
|
/* copy the old tiers' states into their replacements */
|
|
for( i=0; i<old.tier_count; ++i )
|
|
if( old.tiers[i].currentTracker != NULL )
|
|
copy_tier_attributes( tt, &old.tiers[i] );
|
|
|
|
/* kickstart any tiers that didn't get started */
|
|
if( tor->isRunning )
|
|
for( i=0; i<tt->tier_count; ++i )
|
|
if( !tt->tiers[i].wasCopied )
|
|
tier_announce_event_push( &tt->tiers[i], TR_ANNOUNCE_EVENT_STARTED, now );
|
|
|
|
/* cleanup */
|
|
tiersDestruct( &old );
|
|
}
|