final RegistrationDetails details,
final boolean derived_only )
{
final long start = SystemTime.getCurrentTime();
final Torrent torrent = download.getTorrent();
final URL url_to_report = torrent.isDecentralised()?torrent.getAnnounceURL():DEFAULT_URL;
trackerTarget[] targets = details.getTargets( false );
final long[] max_retry = { 0 };
int num_done = 0;
for (int i=0;i<targets.length;i++){
final trackerTarget target = targets[i];
if ( target.getType() == REG_TYPE_FULL && derived_only ){
continue;
}
increaseActive( download );
num_done++;
dht.get(target.getHash(),
"Tracker announce for '" + download.getName() + "'" + target.getDesc(),
isComplete( download )?DHTPlugin.FLAG_SEEDING:DHTPlugin.FLAG_DOWNLOADING,
NUM_WANT,
target.getType()==REG_TYPE_FULL?ANNOUNCE_TIMEOUT:ANNOUNCE_DERIVED_TIMEOUT,
false, false,
new DHTPluginOperationListener()
{
List<String> addresses = new ArrayList<String>();
List<Integer> ports = new ArrayList<Integer>();
List<Integer> udp_ports = new ArrayList<Integer>();
List<Boolean> is_seeds = new ArrayList<Boolean>();
List<String> flags = new ArrayList<String>();
int seed_count;
int leecher_count;
boolean complete;
public void
diversified()
{
}
public void
starts(
byte[] key )
{
}
public void
valueRead(
DHTPluginContact originator,
DHTPluginValue value )
{
synchronized( this ){
if ( complete ){
return;
}
try{
String[] tokens = new String(value.getValue()).split(";");
String tcp_part = tokens[0].trim();
int sep = tcp_part.indexOf(':');
String ip_str = null;
String tcp_port_str;
if ( sep == -1 ){
tcp_port_str = tcp_part;
}else{
ip_str = tcp_part.substring( 0, sep );
tcp_port_str = tcp_part.substring( sep+1 );
}
int tcp_port = Integer.parseInt( tcp_port_str );
if ( tcp_port > 0 && tcp_port < 65536 ){
String flag_str = null;
int udp_port = -1;
try{
for (int i=1;i<tokens.length;i++){
String token = tokens[i].trim();
if ( token.length() > 0 ){
if ( Character.isDigit( token.charAt( 0 ))){
udp_port = Integer.parseInt( token );
if ( udp_port <= 0 || udp_port >=65536 ){
udp_port = -1;
}
}else{
flag_str = token;
}
}
}
}catch( Throwable e ){
}
addresses.add(
ip_str==null?originator.getAddress().getAddress().getHostAddress():ip_str);
ports.add( new Integer( tcp_port ));
udp_ports.add( new Integer( udp_port==-1?originator.getAddress().getPort():udp_port));
flags.add( flag_str );
if (( value.getFlags() & DHTPlugin.FLAG_DOWNLOADING ) == 1 ){
leecher_count++;
is_seeds.add( new Boolean( false ));
}else{
is_seeds.add( new Boolean( true ));
seed_count++;
}
}
}catch( Throwable e ){
// in case we get crap back (someone spamming the DHT) just
// silently ignore
}
}
}
public void
valueWritten(
DHTPluginContact target,
DHTPluginValue value )
{
}
public void
complete(
byte[] key,
boolean timeout_occurred )
{
synchronized( this ){
if ( complete ){
return;
}
complete = true;
}
if ( target.getType() == REG_TYPE_FULL ||
( target.getType() == REG_TYPE_DERIVED &&
seed_count + leecher_count > 1 )){
log( download,
"Get of '" + target.getDesc() + " completed (elapsed=" + TimeFormatter.formatColonMillis(SystemTime.getCurrentTime() - start)
+ "), addresses=" + addresses.size() + ", seeds="
+ seed_count + ", leechers=" + leecher_count);
}
decreaseActive(download);
int peers_found = addresses.size();
List<DownloadAnnounceResultPeer> peers_for_announce = new ArrayList<DownloadAnnounceResultPeer>();
// scale min and max based on number of active torrents
// we don't want more than a few announces a minute
int announce_per_min = 4;
int num_active = query_map.size();
int announce_min = Math.max( ANNOUNCE_MIN_DEFAULT, ( num_active / announce_per_min )*60*1000 );
int announce_max = derived_only?ANNOUNCE_MAX_DERIVED_ONLY:ANNOUNCE_MAX;
announce_min = Math.min( announce_min, announce_max );
current_announce_interval = announce_min;
final long retry = announce_min + peers_found*(announce_max-announce_min)/NUM_WANT;
int download_state = download.getState();
boolean we_are_seeding = download_state == Download.ST_SEEDING;
try{
this_mon.enter();
int[] run_data = running_downloads.get( download );
if ( run_data != null ){
boolean full = target.getType() == REG_TYPE_FULL;
int peer_count = we_are_seeding?leecher_count:(seed_count+leecher_count);
run_data[1] = full?seed_count:Math.max( run_data[1], seed_count);
run_data[2] = full?leecher_count:Math.max( run_data[2], leecher_count);
run_data[3] = full?peer_count:Math.max( run_data[3], peer_count);
long absolute_retry = SystemTime.getCurrentTime() + retry;
if ( absolute_retry > max_retry[0] ){
// only update next query time if none set yet
// or we appear to have set the existing one. If we
// don't do this then we'll overwrite any rescheduled
// announces
Long existing = (Long)query_map.get( download );
if ( existing == null ||
existing.longValue() == max_retry[0] ){
max_retry[0] = absolute_retry;
query_map.put( download, new Long( absolute_retry ));
}
}
}
}finally{
this_mon.exit();
}
putDetails put_details = details.getPutDetails();
String ext_address = put_details.getIPOverride();
if ( ext_address == null ){
ext_address = dht.getLocalAddress().getAddress().getAddress().getHostAddress();
}
for (int i=0;i<addresses.size();i++){
// when we are seeding ignore seeds
if ( we_are_seeding && ((Boolean)is_seeds.get(i)).booleanValue()){
continue;
}
// remove ourselves
String ip = (String)addresses.get(i);
if ( ip.equals( ext_address )){
if ( ((Integer)ports.get(i)).intValue() == put_details.getTCPPort() &&
((Integer)udp_ports.get(i)).intValue() == put_details.getUDPPort()){
continue;
}
}
final int f_i = i;
peers_for_announce.add(
new DownloadAnnounceResultPeer()
{
public String
getSource()
{
return( PEPeerSource.PS_DHT );
}
public String
getAddress()
{
return((String)addresses.get(f_i));
}
public int
getPort()
{
return(((Integer)ports.get(f_i)).intValue());
}
public int
getUDPPort()
{
return(((Integer)udp_ports.get(f_i)).intValue());
}
public byte[]
getPeerID()
{
return( null );
}
public short
getProtocol()
{
String flag = (String)flags.get(f_i);
short protocol;
if ( flag != null && flag.indexOf("C") != -1 ){
protocol = PROTOCOL_CRYPT;
}else{
protocol = PROTOCOL_NORMAL;
}
return( protocol );
}
});
}
if ( target.getType() == REG_TYPE_DERIVED && peers_for_announce.size() > 0 ){
PeerManager pm = download.getPeerManager();
if ( pm != null ){
// try some limited direct injection
List<DownloadAnnounceResultPeer> temp = new ArrayList<DownloadAnnounceResultPeer>( peers_for_announce );
Random rand = new Random();
for (int i=0;i<DIRECT_INJECT_PEER_MAX && temp.size() > 0; i++ ){
DownloadAnnounceResultPeer peer = temp.remove( rand.nextInt( temp.size()));
log( download, "Injecting derived peer " + peer.getAddress() + " into " + download.getName());
Map<Object,Object> user_data = new HashMap<Object,Object>();
user_data.put( Peer.PR_PRIORITY_CONNECTION, new Boolean( true ));
pm.addPeer(
peer.getAddress(),
peer.getPort(),
peer.getUDPPort(),
peer.getProtocol() == DownloadAnnounceResultPeer.PROTOCOL_CRYPT,
user_data );
}
}
}
if ( download_state == Download.ST_DOWNLOADING ||
download_state == Download.ST_SEEDING ){
final DownloadAnnounceResultPeer[] peers = new DownloadAnnounceResultPeer[peers_for_announce.size()];
peers_for_announce.toArray( peers );
download.setAnnounceResult(
new DownloadAnnounceResult()
{
public Download
getDownload()
{
return( download );
}
public int
getResponseType()
{
return( DownloadAnnounceResult.RT_SUCCESS );
}
public int
getReportedPeerCount()
{
return( peers.length);
}
public int
getSeedCount()
{
return( seed_count );
}
public int
getNonSeedCount()
{
return( leecher_count );
}
public String
getError()
{
return( null );
}
public URL
getURL()
{
return( url_to_report );
}
public DownloadAnnounceResultPeer[]
getPeers()
{
return( peers );
}
public long
getTimeToWait()
{
return( retry/1000 );
}
public Map
getExtensions()
{
return( null );
}
});
}
// only inject the scrape result if the torrent is decentralised. If we do this for
// "normal" torrents then it can have unwanted side-effects, such as stopping the torrent
// due to ignore rules if there are no downloaders in the DHT - bthub backup, for example,
// isn't scrapable...
// hmm, ok, try being a bit more relaxed about this, inject the scrape if
// we have any peers.
boolean inject_scrape = leecher_count > 0;
DownloadScrapeResult result = download.getLastScrapeResult();
if ( result == null ||
result.getResponseType() == DownloadScrapeResult.RT_ERROR ){
}else{
// if the currently reported values are the same as the
// ones we previously injected then overwrite them
// note that we can't test the URL to see if we originated
// the scrape values as this gets replaced when a normal
// scrape fails :(
int[] prev = (int[])scrape_injection_map.get( download );
if ( prev != null &&
prev[0] == result.getSeedCount() &&
prev[1] == result.getNonSeedCount()){
inject_scrape = true;
}
}
if ( torrent.isDecentralised() || inject_scrape ){
// make sure that the injected scrape values are consistent
// with our currently connected peers