1 /*
2  * Created on 31-Jan-2005
3  * Created by Paul Gardner
4  * Copyright (C) Azureus Software, Inc, All Rights Reserved.
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU General Public License
8  * as published by the Free Software Foundation; either version 2
9  * of the License, or (at your option) any later version.
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software
16  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
17  *
18  */
19 
20 package com.aelitis.azureus.plugins.tracker.dht;
21 
22 
23 import java.net.InetSocketAddress;
24 import java.net.URL;
25 import java.net.UnknownHostException;
26 import java.util.*;
27 
28 import org.gudy.azureus2.core3.config.COConfigurationManager;
29 import org.gudy.azureus2.core3.internat.MessageText;
30 import org.gudy.azureus2.core3.peer.PEPeerManager;
31 import org.gudy.azureus2.core3.peer.PEPeerSource;
32 import org.gudy.azureus2.core3.tracker.protocol.PRHelpers;
33 import org.gudy.azureus2.core3.util.AEMonitor;
34 import org.gudy.azureus2.core3.util.AENetworkClassifier;
35 import org.gudy.azureus2.core3.util.AESemaphore;
36 import org.gudy.azureus2.core3.util.AEThread2;
37 import org.gudy.azureus2.core3.util.ByteFormatter;
38 import org.gudy.azureus2.core3.util.Constants;
39 import org.gudy.azureus2.core3.util.Debug;
40 import org.gudy.azureus2.core3.util.SystemTime;
41 import org.gudy.azureus2.core3.util.TimeFormatter;
42 import org.gudy.azureus2.core3.util.TorrentUtils;
43 import org.gudy.azureus2.plugins.Plugin;
44 import org.gudy.azureus2.plugins.PluginInterface;
45 import org.gudy.azureus2.plugins.PluginListener;
46 import org.gudy.azureus2.plugins.download.Download;
47 import org.gudy.azureus2.plugins.download.DownloadAnnounceResult;
48 import org.gudy.azureus2.plugins.download.DownloadAnnounceResultPeer;
49 import org.gudy.azureus2.plugins.download.DownloadAttributeListener;
50 import org.gudy.azureus2.plugins.download.DownloadListener;
51 import org.gudy.azureus2.plugins.download.DownloadManagerListener;
52 import org.gudy.azureus2.plugins.download.DownloadScrapeResult;
53 import org.gudy.azureus2.plugins.download.DownloadTrackerListener;
54 import org.gudy.azureus2.plugins.logging.LoggerChannel;
55 import org.gudy.azureus2.plugins.logging.LoggerChannelListener;
56 import org.gudy.azureus2.plugins.peers.Peer;
57 import org.gudy.azureus2.plugins.peers.PeerManager;
58 import org.gudy.azureus2.plugins.torrent.Torrent;
59 import org.gudy.azureus2.plugins.torrent.TorrentAttribute;
60 import org.gudy.azureus2.plugins.ui.UIManager;
61 import org.gudy.azureus2.plugins.ui.config.BooleanParameter;
62 import org.gudy.azureus2.plugins.ui.config.ConfigSection;
63 import org.gudy.azureus2.plugins.ui.config.IntParameter;
64 import org.gudy.azureus2.plugins.ui.config.Parameter;
65 import org.gudy.azureus2.plugins.ui.config.ParameterListener;
66 import org.gudy.azureus2.plugins.ui.model.BasicPluginConfigModel;
67 import org.gudy.azureus2.plugins.ui.model.BasicPluginViewModel;
68 import org.gudy.azureus2.plugins.utils.DelayedTask;
69 import org.gudy.azureus2.plugins.utils.UTTimerEvent;
70 import org.gudy.azureus2.plugins.utils.UTTimerEventPerformer;
71 import org.gudy.azureus2.pluginsimpl.local.PluginCoreUtils;
72 
73 import com.aelitis.azureus.core.networkmanager.NetworkManager;
74 import com.aelitis.azureus.core.tracker.TrackerPeerSource;
75 import com.aelitis.azureus.core.tracker.TrackerPeerSourceAdapter;
76 import com.aelitis.azureus.plugins.I2PHelpers;
77 import com.aelitis.azureus.plugins.dht.*;
78 
79 /**
80  * @author parg
81  *
82  */
83 
84 public class
85 DHTTrackerPlugin
86 	implements Plugin, DownloadListener, DownloadAttributeListener, DownloadTrackerListener
87 {
88 	public static Object	DOWNLOAD_USER_DATA_I2P_SCRAPE_KEY	= new Object();
89 
90 	private static final String	PLUGIN_NAME				= "Distributed Tracker";
91 	private static final String PLUGIN_CONFIGSECTION_ID = "plugins.dhttracker";
92 	private static final String PLUGIN_RESOURCE_ID		= "ConfigView.section.plugins.dhttracker";
93 
94 	private static final int	ANNOUNCE_TIMEOUT			= 2*60*1000;
95 	private static final int	ANNOUNCE_DERIVED_TIMEOUT	= 60*1000;	// spend less time on these
96 	private static final int	SCRAPE_TIMEOUT				= 30*1000;
97 
98 	private static final int	ANNOUNCE_MIN_DEFAULT		= 2*60*1000;
99 	private static final int	ANNOUNCE_MAX				= 60*60*1000;
100 	private static final int	ANNOUNCE_MAX_DERIVED_ONLY	= 30*60*1000;
101 
102 	private static final int	INTERESTING_CHECK_PERIOD		= 4*60*60*1000;
103 	private static final int	INTERESTING_INIT_RAND_OURS		=    5*60*1000;
104 	private static final int	INTERESTING_INIT_MIN_OURS		=    2*60*1000;
105 	private static final int	INTERESTING_INIT_RAND_OTHERS	=   30*60*1000;
106 	private static final int	INTERESTING_INIT_MIN_OTHERS		=    5*60*1000;
107 
108 	private static final int	INTERESTING_DHT_CHECK_PERIOD	= 1*60*60*1000;
109 	private static final int	INTERESTING_DHT_INIT_RAND		=    5*60*1000;
110 	private static final int	INTERESTING_DHT_INIT_MIN		=    2*60*1000;
111 
112 
113 	private static final int	INTERESTING_AVAIL_MAX		= 8;	// won't pub if more
114 	private static final int	INTERESTING_PUB_MAX_DEFAULT	= 30;	// limit on pubs
115 
116 	private static final int	REG_TYPE_NONE			= 1;
117 	private static final int	REG_TYPE_FULL			= 2;
118 	private static final int	REG_TYPE_DERIVED		= 3;
119 
120 	private static final int	LIMITED_TRACK_SIZE		= 16;
121 
122 	private static final boolean	TRACK_NORMAL_DEFAULT	= true;
123 	private static final boolean	TRACK_LIMITED_DEFAULT	= true;
124 
125 	private static final boolean	TEST_ALWAYS_TRACK		= false;
126 
127 	public static final int	NUM_WANT			= 30;	// Limit to ensure replies fit in 1 packet
128 
129 	private static final long	start_time = SystemTime.getCurrentTime();
130 
131 	private static final Object	DL_DERIVED_METRIC_KEY		= new Object();
132 	private static final int	DL_DERIVED_MIN_TRACK		= 5;
133 	private static final int	DL_DERIVED_MAX_TRACK		= 20;
134 	private static final int	DIRECT_INJECT_PEER_MAX		= 5;
135 
136 	//private static final boolean ADD_ASN_DERIVED_TARGET			= false;
137 	//private static final boolean ADD_NETPOS_DERIVED_TARGETS		= false;
138 
139 	private static URL	DEFAULT_URL;
140 
141 	static{
142 		try{
143 			DEFAULT_URL = new URL( "dht:" );
144 
145 		}catch( Throwable e ){
146 
147 			Debug.printStackTrace(e);
148 		}
149 	}
150 
151 	private PluginInterface			plugin_interface;
152 	private BasicPluginViewModel 	model;
153 	private DHTPlugin				dht;
154 
155 	private TorrentAttribute 	ta_networks;
156 	private TorrentAttribute 	ta_peer_sources;
157 
158 	private Map<Download,Long>		interesting_downloads 	= new HashMap<Download,Long>();
159 	private int						interesting_published	= 0;
160 	private int						interesting_pub_max		= INTERESTING_PUB_MAX_DEFAULT;
161 	private Map<Download,int[]>		running_downloads 		= new HashMap<Download,int[]>();
162 	private Map<Download,int[]>		run_data_cache	 		= new HashMap<Download,int[]>();
163 	private Map<Download,RegistrationDetails>	registered_downloads 	= new HashMap<Download,RegistrationDetails>();
164 
165 	private Map<Download,Boolean>	limited_online_tracking	= new HashMap<Download,Boolean>();
166 	private Map<Download,Long>		query_map			 	= new HashMap<Download,Long>();
167 
168 	private Map<Download,Integer>	in_progress				= new HashMap<Download,Integer>();
169 
170 		// external config to limit plugin op to pure decentralised only
171 
172 	private boolean				track_only_decentralsed = COConfigurationManager.getBooleanParameter( "dhtplugin.track.only.decentralised", false );
173 
174 	private BooleanParameter	track_normal_when_offline;
175 	private BooleanParameter	track_limited_when_online;
176 
177 	private long				current_announce_interval = ANNOUNCE_MIN_DEFAULT;
178 
179 	private LoggerChannel		log;
180 
181 	private Map<Download,int[]>					scrape_injection_map = new WeakHashMap<Download,int[]>();
182 
183 	private Random				random = new Random();
184 	private boolean				is_running;
185 
186 	private AEMonitor			this_mon	= new AEMonitor( "DHTTrackerPlugin" );
187 
188 	//private DHTNetworkPosition[]	current_network_positions;
189 	//private long					last_net_pos_time;
190 
191 	private AESemaphore			initialised_sem = new AESemaphore( "DHTTrackerPlugin:init" );
192 
193 	private DHTTrackerPluginAlt	alt_lookup_handler;
194 
195 	private boolean				disable_put;
196 
197 	{
COConfigurationManager.addAndFireParameterListeners( new String[]{ R, R, }, new org.gudy.azureus2.core3.config.ParameterListener() { public void parameterChanged( String parameter_name ) { boolean enable_proxy = COConfigurationManager.getBooleanParameter(R); boolean enable_socks = COConfigurationManager.getBooleanParameter(R); disable_put = enable_proxy && enable_socks; } })198 		COConfigurationManager.addAndFireParameterListeners(
199 			new String[]{
200 				"Enable.Proxy",
201 				"Enable.SOCKS",
202 			},
203 			new org.gudy.azureus2.core3.config.ParameterListener()
204 			{
205 				public void
206 				parameterChanged(
207 					String parameter_name )
208 				{
209 					boolean	enable_proxy 	= COConfigurationManager.getBooleanParameter("Enable.Proxy");
210 				    boolean enable_socks	= COConfigurationManager.getBooleanParameter("Enable.SOCKS");
211 
212 				    disable_put = enable_proxy && enable_socks;
213 				}
214 			});
215 	}
216 
217 	public static void
load( PluginInterface plugin_interface )218 	load(
219 		PluginInterface		plugin_interface )
220 	{
221 		plugin_interface.getPluginProperties().setProperty( "plugin.version", 	"1.0" );
222 		plugin_interface.getPluginProperties().setProperty( "plugin.name", 		PLUGIN_NAME );
223 	}
224 
225 	public void
initialize( PluginInterface _plugin_interface )226 	initialize(
227 		PluginInterface 	_plugin_interface )
228 	{
229 		plugin_interface	= _plugin_interface;
230 
231 		log = plugin_interface.getLogger().getTimeStampedChannel(PLUGIN_NAME);
232 
233 		ta_networks 	= plugin_interface.getTorrentManager().getAttribute( TorrentAttribute.TA_NETWORKS );
234 		ta_peer_sources = plugin_interface.getTorrentManager().getAttribute( TorrentAttribute.TA_PEER_SOURCES );
235 
236 		UIManager	ui_manager = plugin_interface.getUIManager();
237 
238 		model =
239 			ui_manager.createBasicPluginViewModel( PLUGIN_RESOURCE_ID );
240 
241 		model.setConfigSectionID(PLUGIN_CONFIGSECTION_ID);
242 
243 		BasicPluginConfigModel	config =
244 			ui_manager.createBasicPluginConfigModel( ConfigSection.SECTION_PLUGINS,
245 					PLUGIN_CONFIGSECTION_ID);
246 
247 		track_normal_when_offline = config.addBooleanParameter2( "dhttracker.tracknormalwhenoffline", "dhttracker.tracknormalwhenoffline", TRACK_NORMAL_DEFAULT );
248 
249 		track_limited_when_online = config.addBooleanParameter2( "dhttracker.tracklimitedwhenonline", "dhttracker.tracklimitedwhenonline", TRACK_LIMITED_DEFAULT );
250 
251 		track_limited_when_online.addListener(
252 			new ParameterListener()
253 			{
254 				public void
255 				parameterChanged(
256 					Parameter	param )
257 				{
258 					configChanged();
259 				}
260 			});
261 
262 		track_normal_when_offline.addListener(
263 			new ParameterListener()
264 			{
265 				public void
266 				parameterChanged(
267 					Parameter	param )
268 				{
269 					track_limited_when_online.setEnabled( track_normal_when_offline.getValue());
270 
271 					configChanged();
272 				}
273 			});
274 
275 		if ( !track_normal_when_offline.getValue()){
276 
277 			track_limited_when_online.setEnabled( false );
278 		}
279 
280 		interesting_pub_max = plugin_interface.getPluginconfig().getPluginIntParameter( "dhttracker.presencepubmax", INTERESTING_PUB_MAX_DEFAULT );
281 
282 
283 		if ( !TRACK_NORMAL_DEFAULT ){
284 			// should be TRUE by default
285 			System.out.println( "**** DHT Tracker default set for testing purposes ****" );
286 		}
287 
288 		BooleanParameter	enable_alt 	= config.addBooleanParameter2( "dhttracker.enable_alt", "dhttracker.enable_alt", true );
289 
290 		IntParameter 		alt_port 	= config.addIntParameter2( "dhttracker.alt_port", "dhttracker.alt_port", 0, 0, 65535 );
291 
292 		enable_alt.addEnabledOnSelection( alt_port );
293 
294 		config.createGroup( "dhttracker.alt_group", new Parameter[]{ enable_alt,alt_port });
295 
296 		if ( enable_alt.getValue()){
297 
298 			alt_lookup_handler = new DHTTrackerPluginAlt( alt_port.getValue());
299 		}
300 
301 		model.getActivity().setVisible( false );
302 		model.getProgress().setVisible( false );
303 
304 		model.getLogArea().setMaximumSize( 80000 );
305 
306 		log.addListener(
307 				new LoggerChannelListener()
308 				{
309 					public void
310 					messageLogged(
311 						int		type,
312 						String	message )
313 					{
314 						model.getLogArea().appendText( message+"\n");
315 					}
316 
317 					public void
318 					messageLogged(
319 						String		str,
320 						Throwable	error )
321 					{
322 						model.getLogArea().appendText( error.toString()+"\n");
323 					}
324 				});
325 
326 		model.getStatus().setText( MessageText.getString( "ManagerItem.initializing" ));
327 
328 		log.log( "Waiting for Distributed Database initialisation" );
329 
330 		plugin_interface.addListener(
331 			new PluginListener()
332 			{
333 				public void
334 				initializationComplete()
335 				{
336 					boolean	release_now = true;
337 
338 					try{
339 						final PluginInterface dht_pi =
340 							plugin_interface.getPluginManager().getPluginInterfaceByClass(
341 										DHTPlugin.class );
342 
343 						if ( dht_pi != null ){
344 
345 							dht = (DHTPlugin)dht_pi.getPlugin();
346 
347 							final DelayedTask dt =
348 								plugin_interface.getUtilities().createDelayedTask(
349 									new Runnable()
350 									{
351 
352 										public void
353 										run()
354 										{
355 											AEThread2	t =
356 												new AEThread2( "DHTTrackerPlugin:init", true )
357 												{
358 													public void
359 													run()
360 													{
361 														try{
362 
363 															if ( dht.isEnabled()){
364 
365 																log.log( "DDB Available" );
366 
367 																model.getStatus().setText( MessageText.getString( "DHTView.activity.status.false" ));
368 
369 																initialise();
370 
371 															}else{
372 
373 																log.log( "DDB Disabled" );
374 
375 																model.getStatus().setText( MessageText.getString( "dht.status.disabled" ));
376 
377 																notRunning();
378 															}
379 														}catch( Throwable e ){
380 
381 															log.log( "DDB Failed", e );
382 
383 															model.getStatus().setText( MessageText.getString( "DHTView.operations.failed" ));
384 
385 															notRunning();
386 
387 														}finally{
388 
389 															initialised_sem.releaseForever();
390 														}
391 													}
392 												};
393 
394 												t.start();
395 										}
396 									});
397 
398 							dt.queue();
399 
400 							release_now = false;
401 
402 						}else{
403 
404 							log.log( "DDB Plugin missing" );
405 
406 							model.getStatus().setText( MessageText.getString( "DHTView.operations.failed" ) );
407 
408 							notRunning();
409 						}
410 					}finally{
411 
412 						if ( release_now ){
413 
414 							initialised_sem.releaseForever();
415 						}
416 					}
417 				}
418 
419 				public void
420 				closedownInitiated()
421 				{
422 
423 				}
424 
425 				public void
426 				closedownComplete()
427 				{
428 
429 				}
430 			});
431 	}
432 
433 	protected void
notRunning()434 	notRunning()
435 	{
436 		plugin_interface.getDownloadManager().addListener(
437 				new DownloadManagerListener()
438 				{
439 					public void
440 					downloadAdded(
441 						final Download	download )
442 					{
443 						addDownload( download );
444 					}
445 
446 					public void
447 					downloadRemoved(
448 						Download	download )
449 					{
450 						removeDownload( download );
451 					}
452 				});
453 	}
454 
455 	protected void
initialise()456 	initialise()
457 	{
458 		is_running	= true;
459 
460 		plugin_interface.getDownloadManager().addListener(
461 				new DownloadManagerListener()
462 				{
463 					public void
464 					downloadAdded(
465 						Download download )
466 					{
467 						addDownload( download );
468 					}
469 
470 					public void
471 					downloadRemoved(
472 						Download download )
473 					{
474 						removeDownload( download );
475 					}
476 				});
477 
478 		plugin_interface.getUtilities().createTimer("DHT Tracker", true ).addPeriodicEvent(
479 			15000,
480 			new UTTimerEventPerformer()
481 			{
482 				private int	ticks;
483 
484 				private String 	prev_alt_status = "";
485 
486 				public void
487 				perform(
488 					UTTimerEvent event)
489 				{
490 					ticks++;
491 
492 					processRegistrations( ticks%8==0 );
493 
494 					if ( ticks == 2 || ticks%4==0 ){
495 
496 						processNonRegistrations();
497 					}
498 
499 					if ( alt_lookup_handler != null ){
500 
501 						if ( ticks % 4 == 0 ){
502 
503 							String alt_status = alt_lookup_handler.getString();
504 
505 							if ( !alt_status.equals( prev_alt_status )){
506 
507 								log.log( "Alternative stats: " + alt_status );
508 
509 								prev_alt_status = alt_status;
510 							}
511 						}
512 					}
513 				}
514 			});
515 	}
516 
517 	public void
waitUntilInitialised()518 	waitUntilInitialised()
519 	{
520 		initialised_sem.reserve();
521 	}
522 
523 	public boolean
isRunning()524 	isRunning()
525 	{
526 		return( is_running );
527 	}
528 
529 	public void
addDownload( final Download download )530 	addDownload(
531 		final Download	download )
532 	{
533 		Torrent	torrent = download.getTorrent();
534 
535 		boolean	is_decentralised = false;
536 
537 		if ( torrent != null ){
538 
539 			is_decentralised = TorrentUtils.isDecentralised( torrent.getAnnounceURL());
540 		}
541 
542 			// bail on our low noise ones, these don't require decentralised tracking unless that's what they are
543 
544 		if ( download.getFlag( Download.FLAG_LOW_NOISE ) && !is_decentralised ){
545 
546 			return;
547 		}
548 
549 		if ( track_only_decentralsed ){
550 
551 			if ( torrent != null ){
552 
553 				if ( !is_decentralised ){
554 
555 					return;
556 				}
557 			}
558 		}
559 
560 		if ( is_running ){
561 
562 			String[]	networks = download.getListAttribute( ta_networks );
563 
564 			if ( torrent != null && networks != null ){
565 
566 				boolean	public_net = false;
567 
568 				for (int i=0;i<networks.length;i++){
569 
570 					if ( networks[i].equalsIgnoreCase( "Public" )){
571 
572 						public_net	= true;
573 
574 						break;
575 					}
576 				}
577 
578 				if ( public_net && !torrent.isPrivate()){
579 
580 					boolean	our_download =  torrent.wasCreatedByUs();
581 
582 					long	delay;
583 
584 					if ( our_download ){
585 
586 						if ( download.getCreationTime() > start_time ){
587 
588 							delay = 0;
589 
590 						}else{
591 
592 							delay = plugin_interface.getUtilities().getCurrentSystemTime() +
593 									INTERESTING_INIT_MIN_OURS +
594 									random.nextInt( INTERESTING_INIT_RAND_OURS );
595 
596 						}
597 					}else{
598 
599 						int	min;
600 						int	rand;
601 
602 						if ( TorrentUtils.isDecentralised( torrent.getAnnounceURL())){
603 
604 							min		= INTERESTING_DHT_INIT_MIN;
605 							rand	= INTERESTING_DHT_INIT_RAND;
606 
607 						}else{
608 
609 							min		= INTERESTING_INIT_MIN_OTHERS;
610 							rand	= INTERESTING_INIT_RAND_OTHERS;
611 						}
612 
613 						delay = plugin_interface.getUtilities().getCurrentSystemTime() +
614 									min + random.nextInt( rand );
615 					}
616 
617 					try{
618 						this_mon.enter();
619 
620 						interesting_downloads.put( download, new Long( delay ));
621 
622 					}finally{
623 
624 						this_mon.exit();
625 					}
626 				}
627 			}
628 
629 			download.addAttributeListener(DHTTrackerPlugin.this, ta_networks, DownloadAttributeListener.WRITTEN);
630 			download.addAttributeListener(DHTTrackerPlugin.this, ta_peer_sources, DownloadAttributeListener.WRITTEN);
631 
632 			download.addTrackerListener( DHTTrackerPlugin.this );
633 
634 			download.addListener( DHTTrackerPlugin.this );
635 
636 			checkDownloadForRegistration( download, true );
637 
638 		}else{
639 
640 			if ( torrent != null && torrent.isDecentralised()){
641 
642 				download.addListener(
643 					new DownloadListener()
644 					{
645 						public void
646 						stateChanged(
647 							final Download		download,
648 							int					old_state,
649 							int					new_state )
650 						{
651 							int	state = download.getState();
652 
653 							if ( 	state == Download.ST_DOWNLOADING ||
654 									state == Download.ST_SEEDING ){
655 
656 								download.setAnnounceResult(
657 									new DownloadAnnounceResult()
658 									{
659 										public Download
660 										getDownload()
661 										{
662 											return( download );
663 										}
664 
665 										public int
666 										getResponseType()
667 										{
668 											return( DownloadAnnounceResult.RT_ERROR );
669 										}
670 
671 										public int
672 										getReportedPeerCount()
673 										{
674 											return( 0 );
675 										}
676 
677 
678 										public int
679 										getSeedCount()
680 										{
681 											return( 0 );
682 										}
683 
684 										public int
685 										getNonSeedCount()
686 										{
687 											return( 0 );
688 										}
689 
690 										public String
691 										getError()
692 										{
693 											return( "Distributed Database Offline" );
694 										}
695 
696 										public URL
697 										getURL()
698 										{
699 											return( download.getTorrent().getAnnounceURL());
700 										}
701 
702 										public DownloadAnnounceResultPeer[]
703 										getPeers()
704 										{
705 											return( new DownloadAnnounceResultPeer[0] );
706 										}
707 
708 										public long
709 										getTimeToWait()
710 										{
711 											return( 0 );
712 										}
713 
714 										public Map
715 										getExtensions()
716 										{
717 											return( null );
718 										}
719 									});
720 							}
721 						}
722 
723 						public void
724 						positionChanged(
725 							Download		download,
726 							int 			oldPosition,
727 							int 			newPosition )
728 						{
729 
730 						}
731 					});
732 
733 
734 				download.setScrapeResult(
735 					new DownloadScrapeResult()
736 					{
737 						public Download
738 						getDownload()
739 						{
740 							return( download );
741 						}
742 
743 						public int
744 						getResponseType()
745 						{
746 							return( RT_ERROR );
747 						}
748 
749 						public int
750 						getSeedCount()
751 						{
752 							return( -1 );
753 						}
754 
755 						public int
756 						getNonSeedCount()
757 						{
758 							return( -1 );
759 						}
760 
761 						public long
762 						getScrapeStartTime()
763 						{
764 							return( SystemTime.getCurrentTime());
765 						}
766 
767 						public void
768 						setNextScrapeStartTime(
769 							long nextScrapeStartTime)
770 						{
771 						}
772 
773 						public long
774 						getNextScrapeStartTime()
775 						{
776 							return( -1 );
777 						}
778 
779 						public String
780 						getStatus()
781 						{
782 							return( "Distributed Database Offline" );
783 						}
784 
785 						public URL
786 						getURL()
787 						{
788 							return( download.getTorrent().getAnnounceURL());
789 						}
790 					});
791 			}
792 		}
793 	}
794 
795 	public void
removeDownload( Download download )796 	removeDownload(
797 		Download	download )
798 	{
799 		if ( is_running ){
800 			download.removeTrackerListener( DHTTrackerPlugin.this );
801 
802 			download.removeListener( DHTTrackerPlugin.this );
803 
804 			try{
805 				this_mon.enter();
806 
807 				interesting_downloads.remove( download );
808 
809 				running_downloads.remove( download );
810 
811 				run_data_cache.remove( download );
812 
813 				limited_online_tracking.remove( download );
814 
815 			}finally{
816 
817 				this_mon.exit();
818 			}
819 		}else{
820 
821 		}
822 	}
823 
attributeEventOccurred(Download download, TorrentAttribute attr, int event_type)824 	public void attributeEventOccurred(Download download, TorrentAttribute attr, int event_type) {
825 		checkDownloadForRegistration(download, false);
826 	}
827 
828 	public void
scrapeResult( DownloadScrapeResult result )829 	scrapeResult(
830 		DownloadScrapeResult	result )
831 	{
832 		checkDownloadForRegistration( result.getDownload(), false );
833 	}
834 
835 	public void
announceResult( DownloadAnnounceResult result )836 	announceResult(
837 		DownloadAnnounceResult	result )
838 	{
839 		checkDownloadForRegistration( result.getDownload(), false );
840 	}
841 
842 
843 	protected void
checkDownloadForRegistration( Download download, boolean first_time )844 	checkDownloadForRegistration(
845 		Download		download,
846 		boolean			first_time )
847 	{
848 		if ( download == null ){
849 
850 			return;
851 		}
852 
853 		boolean	skip_log = false;
854 
855 		int	state = download.getState();
856 
857 		int	register_type	= REG_TYPE_NONE;
858 
859 		String	register_reason;
860 
861 		Random	random = new Random();
862 			/*
863 			 * Queued downloads are removed from the set to consider as we now have the "presence store"
864 			 * mechanism to ensure that there are a number of peers out there to provide torrent download
865 			 * if required. This has been done to avoid the large number of registrations that users with
866 			 * large numbers of queued torrents were getting.
867 			 */
868 
869 		if ( 	state == Download.ST_DOWNLOADING 	||
870 				state == Download.ST_SEEDING 		||
871 				// state == Download.ST_QUEUED 		||
872 				download.isPaused()){	// pause is a transitory state, don't dereg
873 
874 			String[]	networks = download.getListAttribute( ta_networks );
875 
876 			Torrent	torrent = download.getTorrent();
877 
878 			if ( torrent != null && networks != null ){
879 
880 				boolean	public_net = false;
881 
882 				for (int i=0;i<networks.length;i++){
883 
884 					if ( networks[i].equalsIgnoreCase( "Public" )){
885 
886 						public_net	= true;
887 
888 						break;
889 					}
890 				}
891 
892 				if ( public_net && !torrent.isPrivate()){
893 
894 					if ( torrent.isDecentralised()){
895 
896 							// peer source not relevant for decentralised torrents
897 
898 						register_type	= REG_TYPE_FULL;
899 
900 						register_reason = "decentralised";
901 
902 					}else{
903 
904 						if ( torrent.isDecentralisedBackupEnabled() || TEST_ALWAYS_TRACK ){
905 
906 							String[]	sources = download.getListAttribute( ta_peer_sources );
907 
908 							boolean	ok = false;
909 
910 							if ( sources != null ){
911 
912 								for (int i=0;i<sources.length;i++){
913 
914 									if ( sources[i].equalsIgnoreCase( "DHT")){
915 
916 										ok	= true;
917 
918 										break;
919 									}
920 								}
921 							}
922 
923 							if ( !( ok || TEST_ALWAYS_TRACK )){
924 
925 								register_reason = "decentralised peer source disabled";
926 
927 							}else{
928 									// this will always be true since change to exclude queued...
929 
930 								boolean	is_active =
931 											state == Download.ST_DOWNLOADING ||
932 											state == Download.ST_SEEDING ||
933 											download.isPaused();
934 
935 								if ( is_active ){
936 
937 									register_type = REG_TYPE_DERIVED;
938 								}
939 
940 								if( torrent.isDecentralisedBackupRequested() || TEST_ALWAYS_TRACK ){
941 
942 									register_type	= REG_TYPE_FULL;
943 
944 									register_reason = TEST_ALWAYS_TRACK?"testing always track":"torrent requests decentralised tracking";
945 
946 								}else if ( track_normal_when_offline.getValue()){
947 
948 										// only track if torrent's tracker is not available
949 
950 									if ( is_active ){
951 
952 										DownloadAnnounceResult result = download.getLastAnnounceResult();
953 
954 										if (	result == null ||
955 												result.getResponseType() == DownloadAnnounceResult.RT_ERROR ||
956 												TorrentUtils.isDecentralised(result.getURL())){
957 
958 											register_type	= REG_TYPE_FULL;
959 
960 											register_reason = "tracker unavailable (announce)";
961 
962 										}else{
963 
964 											register_reason = "tracker available (announce: " + result.getURL() + ")";
965 										}
966 									}else{
967 
968 										DownloadScrapeResult result = download.getLastScrapeResult();
969 
970 										if (	result == null ||
971 												result.getResponseType() == DownloadScrapeResult.RT_ERROR ||
972 												TorrentUtils.isDecentralised(result.getURL())){
973 
974 											register_type	= REG_TYPE_FULL;
975 
976 											register_reason = "tracker unavailable (scrape)";
977 
978 										}else{
979 
980 											register_reason = "tracker available (scrape: " + result.getURL() + ")";
981 										}
982 									}
983 
984 									if ( register_type != REG_TYPE_FULL && track_limited_when_online.getValue()){
985 
986 										Boolean	existing = (Boolean)limited_online_tracking.get( download );
987 
988 										boolean	track_it = false;
989 
990 										if ( existing != null ){
991 
992 											track_it = existing.booleanValue();
993 
994 										}else{
995 
996 											DownloadScrapeResult result = download.getLastScrapeResult();
997 
998 											if (	result != null&&
999 													result.getResponseType() == DownloadScrapeResult.RT_SUCCESS ){
1000 
1001 												int	seeds 		= result.getSeedCount();
1002 												int leechers	= result.getNonSeedCount();
1003 
1004 												int	swarm_size = seeds + leechers;
1005 
1006 												if ( swarm_size <= LIMITED_TRACK_SIZE ){
1007 
1008 													track_it = true;
1009 
1010 												}else{
1011 
1012 													track_it = random.nextInt( swarm_size ) < LIMITED_TRACK_SIZE;
1013 												}
1014 
1015 												if ( track_it ){
1016 
1017 													limited_online_tracking.put( download, new Boolean( track_it ));
1018 												}
1019 											}
1020 										}
1021 
1022 										if( track_it ){
1023 
1024 											register_type	= REG_TYPE_FULL;
1025 
1026 											register_reason = "limited online tracking";
1027 										}
1028 									}
1029 								}else{
1030 									register_type	= REG_TYPE_FULL;
1031 
1032 									register_reason = "peer source enabled";
1033 								}
1034 							}
1035 						}else{
1036 
1037 							register_reason = "decentralised backup disabled for the torrent";
1038 						}
1039 					}
1040 				}else{
1041 
1042 					register_reason = "not public";
1043 				}
1044 			}else{
1045 
1046 				register_reason = "torrent is broken";
1047 			}
1048 
1049 			if ( register_type == REG_TYPE_DERIVED ){
1050 
1051 				if ( register_reason.length() == 0 ){
1052 
1053 					register_reason = "derived";
1054 
1055 				}else{
1056 
1057 					register_reason = "derived (overriding ' " + register_reason + "')";
1058 				}
1059 			}
1060 		}else if ( 	state == Download.ST_STOPPED ||
1061 					state == Download.ST_ERROR ){
1062 
1063 			register_reason	= "not running";
1064 
1065 			skip_log	= true;
1066 
1067 		}else if ( 	state == Download.ST_QUEUED ){
1068 
1069 				// leave in whatever state it current is (reg or not reg) to avoid thrashing
1070 				// registrations when seeding rules are start/queueing downloads
1071 
1072 			register_reason	= "";
1073 
1074 		}else{
1075 
1076 			register_reason	= "";
1077 		}
1078 
1079 		if ( register_reason.length() > 0 ){
1080 
1081 			try{
1082 				this_mon.enter();
1083 
1084 				int[] run_data = running_downloads.get( download );
1085 
1086 				if ( register_type != REG_TYPE_NONE ){
1087 
1088 					if ( run_data == null ){
1089 
1090 						log( download,	"Monitoring '" + download.getName() + "': " + register_reason);
1091 
1092 						int[] cache = run_data_cache.remove( download );
1093 
1094 						if ( cache == null ){
1095 
1096 							running_downloads.put( download, new int[]{ register_type, 0, 0, 0, 0 });
1097 
1098 						}else{
1099 
1100 							cache[0] = register_type;
1101 
1102 							running_downloads.put( download, cache );
1103 						}
1104 
1105 						query_map.put( download, new Long( SystemTime.getCurrentTime()));
1106 
1107 					}else{
1108 
1109 						Integer	existing_type = run_data[0];
1110 
1111 						if ( 	existing_type.intValue() == REG_TYPE_DERIVED &&
1112 								register_type == REG_TYPE_FULL ){
1113 
1114 								// upgrade
1115 
1116 							run_data[0] = register_type;
1117 						}
1118 					}
1119 				}else{
1120 
1121 					if ( run_data  != null ){
1122 
1123 						if ( !skip_log ){
1124 
1125 							log( download, "Not monitoring: "	+ register_reason);
1126 						}
1127 
1128 						running_downloads.remove( download );
1129 
1130 						run_data_cache.put( download, run_data );
1131 
1132 							// add back to interesting downloads for monitoring
1133 
1134 						interesting_downloads.put(
1135 								download,
1136 								new Long( 	plugin_interface.getUtilities().getCurrentSystemTime() +
1137 											INTERESTING_INIT_MIN_OTHERS ));
1138 
1139 					}else{
1140 
1141 						if ( first_time && !skip_log ){
1142 
1143 							log( download, "Not monitoring: "	+ register_reason);
1144 						}
1145 					}
1146 				}
1147 			}finally{
1148 
1149 				this_mon.exit();
1150 			}
1151 		}
1152 	}
1153 
1154 	protected void
1155 	processRegistrations(
1156 		boolean		full_processing )
1157 	{
1158 		int	tcp_port = plugin_interface.getPluginconfig().getIntParameter( "TCP.Listen.Port" );
1159 
1160  		String port_override = COConfigurationManager.getStringParameter("TCP.Listen.Port.Override");
1161 
1162   		if( !port_override.equals("")){
1163 
1164   			try{
1165   				tcp_port	= Integer.parseInt( port_override );
1166 
1167   			}catch( Throwable e ){
1168   			}
1169   		}
1170 
1171   		if ( tcp_port == 0 ){
1172 
1173   			log.log( "TCP port=0, registration not performed" );
1174 
1175   			return;
1176   		}
1177 
1178 	    String override_ips = COConfigurationManager.getStringParameter( "Override Ip", "" );
1179 
1180 	    String override_ip	= null;
1181 
1182 	  	if ( override_ips.length() > 0 ){
1183 
1184    				// gotta select an appropriate override based on network type
1185 
1186 	  		StringTokenizer	tok = new StringTokenizer( override_ips, ";" );
1187 
1188 	  		while( tok.hasMoreTokens()){
1189 
1190 	  			String	this_address = (String)tok.nextToken().trim();
1191 
1192 	  			if ( this_address.length() > 0 ){
1193 
1194 	  				String	cat = AENetworkClassifier.categoriseAddress( this_address );
1195 
1196 	  				if ( cat == AENetworkClassifier.AT_PUBLIC ){
1197 
1198 	  					override_ip	= this_address;
1199 
1200 	  					break;
1201 	  				}
1202 	  			}
1203 			}
1204 		}
1205 
1206   	    if ( override_ip != null ){
1207 
1208     		try{
1209     			override_ip = PRHelpers.DNSToIPAddress( override_ip );
1210 
1211     		}catch( UnknownHostException e){
1212 
1213     			log.log( "    Can't resolve IP override '" + override_ip + "'" );
1214 
1215     			override_ip	= null;
1216     		}
1217     	}
1218 
1219 		ArrayList<Download>	rds;
1220 
1221 		try{
1222 			this_mon.enter();
1223 
1224 			rds = new ArrayList<Download>(running_downloads.keySet());
1225 
1226 		}finally{
1227 
1228 			this_mon.exit();
1229 		}
1230 
1231 		long	 now = SystemTime.getCurrentTime();
1232 
1233 
1234 		if ( full_processing ){
1235 
1236 			Iterator<Download>	rds_it = rds.iterator();
1237 
1238 			List<Object[]> interesting = new ArrayList<Object[]>();
1239 
1240 			while( rds_it.hasNext()){
1241 
1242 				Download	dl = rds_it.next();
1243 
1244 				int	reg_type = REG_TYPE_NONE;
1245 
1246 				try{
1247 					this_mon.enter();
1248 
1249 					int[] run_data = running_downloads.get( dl );
1250 
1251 					if ( run_data != null ){
1252 
1253 						reg_type = run_data[0];
1254 					}
1255 				}finally{
1256 
1257 					this_mon.exit();
1258 				}
1259 
1260 		  		if ( reg_type == REG_TYPE_NONE ){
1261 
1262 		  			continue;
1263 		  		}
1264 
1265 		  		long metric = getDerivedTrackMetric( dl );
1266 
1267 		  		interesting.add( new Object[]{ dl, new Long( metric )} );
1268 			}
1269 
1270 			Collections.sort(
1271 				interesting,
1272 				new Comparator<Object[]>()
1273 				{
1274 					public int
1275 					compare(
1276 						Object[] entry1,
1277 						Object[] entry2)
1278 					{
1279 						long	res = ((Long)entry2[1]).longValue() - ((Long)entry1[1]).longValue();
1280 
1281 						if( res < 0 ){
1282 
1283 							return( -1 );
1284 
1285 						}else if ( res > 0 ){
1286 
1287 							return( 1 );
1288 
1289 						}else{
1290 
1291 							return( 0 );
1292 						}
1293 					}
1294 				});
1295 
1296 			Iterator<Object[]> it	= interesting.iterator();
1297 
1298 			int	num = 0;
1299 
1300 			while( it.hasNext()){
1301 
1302 				Object[] entry = it.next();
1303 
1304 				Download	dl 		= (Download)entry[0];
1305 				long		metric	= ((Long)entry[1]).longValue();
1306 
1307 				num++;
1308 
1309 				if ( metric > 0 ){
1310 
1311 					if ( num <= DL_DERIVED_MIN_TRACK ){
1312 
1313 							// leave as is
1314 
1315 					}else if ( num <= DL_DERIVED_MAX_TRACK ){
1316 
1317 							// scale metric between limits
1318 
1319 						metric = ( metric * ( DL_DERIVED_MAX_TRACK - num )) / ( DL_DERIVED_MAX_TRACK - DL_DERIVED_MIN_TRACK );
1320 
1321 					}else{
1322 
1323 						metric = 0;
1324 					}
1325 				}
1326 
1327 				if ( metric > 0 ){
1328 
1329 					dl.setUserData( DL_DERIVED_METRIC_KEY, new Long( metric ));
1330 
1331 				}else{
1332 
1333 					dl.setUserData( DL_DERIVED_METRIC_KEY, null );
1334 				}
1335 			}
1336 		}
1337 
1338 		Iterator<Download>	rds_it = rds.iterator();
1339 
1340 			// first off do any puts
1341 
1342 		while( rds_it.hasNext()){
1343 
1344 			Download	dl = rds_it.next();
1345 
1346 			int	reg_type = REG_TYPE_NONE;
1347 
1348 			try{
1349 				this_mon.enter();
1350 
1351 				int[] run_data = running_downloads.get( dl );
1352 
1353 				if ( run_data != null ){
1354 
1355 					reg_type = run_data[0];
1356 				}
1357 			}finally{
1358 
1359 				this_mon.exit();
1360 			}
1361 
1362 	  		if ( reg_type == REG_TYPE_NONE ){
1363 
1364 	  			continue;
1365 	  		}
1366 
1367 	  			// format is [ip_override:]tcp_port[;CI...][;udp_port]
1368 
1369 	  	    String	value_to_put = override_ip==null?"":(override_ip+":");
1370 
1371 	  	    value_to_put += tcp_port;
1372 
1373 	  	    String put_flags = ";";
1374 
1375 	  	    if ( NetworkManager.REQUIRE_CRYPTO_HANDSHAKE ){
1376 
1377 	  	    	put_flags += "C";
1378 	  	    }
1379 
1380 	  	    String[]	networks = dl.getListAttribute( ta_networks );
1381 
1382 	  	    boolean	i2p = false;
1383 
1384 	  	    if ( networks != null ){
1385 
1386 	  	    	for ( String net: networks ){
1387 
1388 	  	    		if ( net == AENetworkClassifier.AT_I2P ){
1389 
1390 	  	    			if ( I2PHelpers.isI2PInstalled()){
1391 
1392 	  	    				put_flags += "I";
1393 	  	    			}
1394 
1395 	  	    			i2p = true;
1396 
1397 	  	    			break;
1398 	  	    		}
1399 	  	    	}
1400 	  	    }
1401 
1402 	  	    if ( put_flags.length() > 1 ){
1403 
1404 	  	    	value_to_put += put_flags;
1405 	  	    }
1406 
1407 			int	udp_port = plugin_interface.getPluginconfig().getIntParameter( "UDP.Listen.Port" );
1408 
1409 			int	dht_port = dht.getLocalAddress().getAddress().getPort();
1410 
1411 			if ( udp_port != dht_port ){
1412 
1413 				value_to_put += ";" + udp_port;
1414 			}
1415 
1416 			putDetails	put_details = new putDetails( value_to_put, override_ip, tcp_port, udp_port, i2p );
1417 
1418 			byte	dht_flags = isComplete( dl )?DHTPlugin.FLAG_SEEDING:DHTPlugin.FLAG_DOWNLOADING;
1419 
1420 			RegistrationDetails	registration = (RegistrationDetails)registered_downloads.get( dl );
1421 
1422 			boolean	do_it = false;
1423 
1424 			if ( registration == null ){
1425 
1426 				log( dl, "Registering download as " + (dht_flags == DHTPlugin.FLAG_SEEDING?"Seeding":"Downloading"));
1427 
1428 				registration = new RegistrationDetails( dl, reg_type, put_details, dht_flags );
1429 
1430 				registered_downloads.put( dl, registration );
1431 
1432 				do_it = true;
1433 
1434 			}else{
1435 
1436 				boolean	targets_changed = false;
1437 
1438 				if ( full_processing ){
1439 
1440 					targets_changed = registration.updateTargets( dl, reg_type );
1441 				}
1442 
1443 				if (	targets_changed ||
1444 						registration.getFlags() != dht_flags ||
1445 						!registration.getPutDetails().sameAs( put_details )){
1446 
1447 					log( dl,(registration==null?"Registering":"Re-registering") + " download as " + (dht_flags == DHTPlugin.FLAG_SEEDING?"Seeding":"Downloading"));
1448 
1449 					registration.update( put_details, dht_flags );
1450 
1451 					do_it = true;
1452 				}
1453 			}
1454 
1455 			if ( do_it ){
1456 
1457 				try{
1458 					this_mon.enter();
1459 
1460 					query_map.put( dl, new Long( now ));
1461 
1462 				}finally{
1463 
1464 					this_mon.exit();
1465 				}
1466 
1467 				trackerPut( dl, registration );
1468 			}
1469 		}
1470 
1471 			// second any removals
1472 
1473 		Iterator<Map.Entry<Download,RegistrationDetails>> rd_it = registered_downloads.entrySet().iterator();
1474 
1475 		while( rd_it.hasNext()){
1476 
1477 			Map.Entry<Download,RegistrationDetails>	entry = rd_it.next();
1478 
1479 			final Download	dl = entry.getKey();
1480 
1481 			boolean	unregister;
1482 
1483 			try{
1484 				this_mon.enter();
1485 
1486 				unregister = !running_downloads.containsKey( dl );
1487 
1488 			}finally{
1489 
1490 				this_mon.exit();
1491 			}
1492 
1493 			if ( unregister ){
1494 
1495 				log( dl, "Unregistering download" );
1496 
1497 				rd_it.remove();
1498 
1499 				try{
1500 					this_mon.enter();
1501 
1502 					query_map.remove( dl );
1503 
1504 				}finally{
1505 
1506 					this_mon.exit();
1507 				}
1508 
1509 				trackerRemove( dl, entry.getValue());
1510 			}
1511 		}
1512 
1513 			// lastly gets
1514 
1515 		rds_it = rds.iterator();
1516 
1517 		while( rds_it.hasNext()){
1518 
1519 			final Download	dl = (Download)rds_it.next();
1520 
1521 			Long	next_time;
1522 
1523 			try{
1524 				this_mon.enter();
1525 
1526 				next_time = (Long)query_map.get( dl );
1527 
1528 			}finally{
1529 
1530 				this_mon.exit();
1531 			}
1532 
1533 			if ( next_time != null && now >= next_time.longValue()){
1534 
1535 				int	reg_type = REG_TYPE_NONE;
1536 
1537 				try{
1538 					this_mon.enter();
1539 
1540 					query_map.remove( dl );
1541 
1542 					int[] run_data = running_downloads.get( dl );
1543 
1544 					if ( run_data != null ){
1545 
1546 						reg_type = run_data[0];
1547 					}
1548 				}finally{
1549 
1550 					this_mon.exit();
1551 				}
1552 
1553 				final long	start = SystemTime.getCurrentTime();
1554 
1555 					// if we're already connected to > NUM_WANT peers then don't bother with the main announce
1556 
1557 				PeerManager	pm = dl.getPeerManager();
1558 
1559 					// don't query if this download already has an active DHT operation
1560 
1561 				boolean	skip	= isActive( dl ) || reg_type == REG_TYPE_NONE;
1562 
1563 				if ( skip ){
1564 
1565 					log( dl, "Deferring announce as activity outstanding" );
1566 				}
1567 
1568 				RegistrationDetails	registration = (RegistrationDetails)registered_downloads.get( dl );
1569 
1570 				if ( registration == null ){
1571 
1572 					Debug.out( "Inconsistent, registration should be non-null" );
1573 
1574 					continue;
1575 				}
1576 
1577 				boolean	derived_only = false;
1578 
1579 				if ( pm != null && !skip ){
1580 
1581 					int	con = pm.getStats().getConnectedLeechers() + pm.getStats().getConnectedSeeds();
1582 
1583 					derived_only = con >= NUM_WANT;
1584 				}
1585 
1586 				if ( !skip ){
1587 
1588 					skip = trackerGet( dl, registration, derived_only ) == 0;
1589 
1590 				}
1591 
1592 					// if we didn't kick off a get then we have to reschedule here as normally
1593 					// the get operation will do the rescheduling when it receives a result
1594 
1595 				if ( skip ){
1596 
1597 					try{
1598 						this_mon.enter();
1599 
1600 						if ( running_downloads.containsKey( dl )){
1601 
1602 								// use "min" here as we're just deferring it
1603 
1604 							query_map.put( dl, new Long( start + ANNOUNCE_MIN_DEFAULT ));
1605 						}
1606 
1607 					}finally{
1608 
1609 						this_mon.exit();
1610 					}
1611 				}
1612 			}
1613 		}
1614 	}
1615 
1616 	protected long
1617 	getDerivedTrackMetric(
1618 		Download		download )
1619 	{
1620 			// metric between -100 and + 100. Note that all -ve mean 'don't do it'
1621 			// they're just indicating different reasons
1622 
1623 		Torrent t = download.getTorrent();
1624 
1625 		if ( t == null ){
1626 
1627 			return( -100 );
1628 		}
1629 
1630 		if ( t.getSize() < 10*1024*1024 ){
1631 
1632 			return( -99 );
1633 		}
1634 
1635 		DownloadAnnounceResult announce = download.getLastAnnounceResult();
1636 
1637 		if ( 	announce == null ||
1638 				announce.getResponseType() != DownloadAnnounceResult.RT_SUCCESS ){
1639 
1640 			return( -98 );
1641 		}
1642 
1643 		DownloadScrapeResult scrape = download.getLastScrapeResult();
1644 
1645 		if ( 	scrape == null ||
1646 				scrape.getResponseType() != DownloadScrapeResult.RT_SUCCESS ){
1647 
1648 			return( -97 );
1649 		}
1650 
1651 		int leechers 	= scrape.getNonSeedCount();
1652 		// int seeds		= scrape.getSeedCount();
1653 
1654 		int	total = leechers;	// parg - changed to just use leecher count rather than seeds+leechers
1655 
1656 		if ( total >= 2000 ){
1657 
1658 			return( 100 );
1659 
1660 		}else if ( total <= 200 ){
1661 
1662 			return( 0 );
1663 
1664 		}else{
1665 
1666 			return( ( total - 200 ) / 4 );
1667 		}
1668 	}
1669 
1670 	protected void
1671 	trackerPut(
1672 		final Download			download,
1673 		RegistrationDetails		details )
1674 	{
1675 		final 	long	start = SystemTime.getCurrentTime();
1676 
1677 		trackerTarget[] targets = details.getTargets( true );
1678 
1679 		byte flags = details.getFlags();
1680 
1681 		for (int i=0;i<targets.length;i++){
1682 
1683 			final trackerTarget target = targets[i];
1684 
1685 			int	target_type = target.getType();
1686 
1687 		 	    // don't let a put block an announce as we don't want to be waiting for
1688 		  	    // this at start of day to get a torrent running
1689 
1690 		  	    // increaseActive( dl );
1691 
1692 			String	encoded = details.getPutDetails().getEncoded();
1693 
1694 			byte[]	encoded_bytes = encoded.getBytes();
1695 
1696 			DHTPluginValue existing = dht.getLocalValue( target.getHash());
1697 
1698 			if ( 	existing != null &&
1699 					existing.getFlags() == flags &&
1700 					Arrays.equals( existing.getValue(), encoded_bytes )){
1701 
1702 					// already present, no point in updating
1703 
1704 				continue;
1705 			}
1706 
1707 			if ( disable_put ){
1708 
1709 				if ( target_type == REG_TYPE_FULL ){
1710 
1711 					log( download, "Registration of '" + target.getDesc() + "' skipped as disabled due to use of SOCKS proxy");
1712 				}
1713 			}else if ( download.getFlag( Download.FLAG_METADATA_DOWNLOAD )){
1714 
1715 				log( download, "Registration of '" + target.getDesc() + "' skipped as metadata download");
1716 
1717 			}else if ( target_type == REG_TYPE_DERIVED && dht.isSleeping()){
1718 
1719 				log( download, "Registration of '" + target.getDesc() + "' skipped as sleeping");
1720 
1721 			}else{
1722 
1723 				dht.put(
1724 					target.getHash(),
1725 					"Tracker reg of '" + download.getName() + "'" + target.getDesc() + " -> " + encoded,
1726 					encoded_bytes,
1727 					flags,
1728 					false,
1729 					new DHTPluginOperationListener()
1730 					{
1731 						public boolean
1732 						diversified()
1733 						{
1734 							return( true );
1735 						}
1736 
1737 						public void
1738 						starts(
1739 							byte[] 				key )
1740 						{
1741 						}
1742 
1743 						public void
1744 						valueRead(
1745 							DHTPluginContact	originator,
1746 							DHTPluginValue		value )
1747 						{
1748 						}
1749 
1750 						public void
1751 						valueWritten(
1752 							DHTPluginContact	target,
1753 							DHTPluginValue		value )
1754 						{
1755 						}
1756 
1757 						public void
1758 						complete(
1759 							byte[]	key,
1760 							boolean	timeout_occurred )
1761 						{
1762 							if ( target.getType() == REG_TYPE_FULL ){
1763 
1764 								log( 	download,
1765 										"Registration of '" + target.getDesc() + "' completed (elapsed="	+ TimeFormatter.formatColonMillis((SystemTime.getCurrentTime() - start)) + ")");
1766 							}
1767 
1768 								// decreaseActive( dl );
1769 						}
1770 					});
1771 			}
1772 		}
1773 	}
1774 
1775 	protected int
1776 	trackerGet(
1777 		final Download					download,
1778 		final RegistrationDetails		details,
1779 		final boolean					derived_only )
1780 	{
1781 		final 	long	start = SystemTime.getCurrentTime();
1782 
1783 		final Torrent	torrent = download.getTorrent();
1784 
1785 		final URL	url_to_report = torrent.isDecentralised()?torrent.getAnnounceURL():DEFAULT_URL;
1786 
1787 		trackerTarget[] targets = details.getTargets( false );
1788 
1789 		final long[]	max_retry = { 0 };
1790 
1791 		final boolean do_alt =
1792 			alt_lookup_handler != null &&
1793 			(!( download.getFlag( Download.FLAG_LOW_NOISE ) || download.getFlag( Download.FLAG_LIGHT_WEIGHT )));
1794 
1795 		int	num_done = 0;
1796 
1797 		for (int i=0;i<targets.length;i++){
1798 
1799 			final trackerTarget target = targets[i];
1800 
1801 			int	target_type = target.getType();
1802 
1803 			if ( target_type == REG_TYPE_FULL && derived_only ){
1804 
1805 				continue;
1806 
1807 			}else if ( target_type == REG_TYPE_DERIVED && dht.isSleeping()){
1808 
1809 				continue;
1810 			}
1811 
1812 			increaseActive( download );
1813 
1814 			num_done++;
1815 
1816 			final boolean is_complete = isComplete( download );
1817 
1818 			dht.get(target.getHash(),
1819 					"Tracker announce for '" + download.getName() + "'" + target.getDesc(),
1820 					is_complete?DHTPlugin.FLAG_SEEDING:DHTPlugin.FLAG_DOWNLOADING,
1821 					NUM_WANT,
1822 					target_type==REG_TYPE_FULL?ANNOUNCE_TIMEOUT:ANNOUNCE_DERIVED_TIMEOUT,
1823 					false, false,
1824 					new DHTPluginOperationListener()
1825 					{
1826 						List<String>	addresses 	= new ArrayList<String>();
1827 						List<Integer>	ports		= new ArrayList<Integer>();
1828 						List<Integer>	udp_ports	= new ArrayList<Integer>();
1829 						List<Boolean>	is_seeds	= new ArrayList<Boolean>();
1830 						List<String>	flags		= new ArrayList<String>();
1831 
1832 						int		seed_count;
1833 						int		leecher_count;
1834 
1835 						int		i2p_seed_count;
1836 						int 	i2p_leecher_count;
1837 
1838 						volatile boolean	complete;
1839 
1840 						{
1841 							if ( do_alt ){
1842 
1843 								alt_lookup_handler.get(
1844 										target.getHash(),
1845 										is_complete,
1846 										new DHTTrackerPluginAlt.LookupListener()
1847 										{
1848 											public void
1849 											foundPeer(
1850 												InetSocketAddress	address )
1851 											{
1852 												alternativePeerRead( address );
1853 											}
1854 
1855 											public boolean
1856 											isComplete()
1857 											{
1858 												return( complete && addresses.size() > 5 );
1859 											}
1860 
1861 											public void
1862 											completed()
1863 											{
1864 											}
1865 										});
1866 							}
1867 						}
1868 
1869 						public boolean
1870 						diversified()
1871 						{
1872 							return( true );
1873 						}
1874 
1875 						public void
1876 						starts(
1877 							byte[] 				key )
1878 						{
1879 						}
1880 
1881 						private void
1882 						alternativePeerRead(
1883 							InetSocketAddress		peer )
1884 						{
1885 							boolean	try_injection = false;
1886 
1887 							synchronized( this ){
1888 
1889 								if ( complete ){
1890 
1891 									try_injection = addresses.size() < 5;
1892 
1893 								}else{
1894 
1895 									try{
1896 										addresses.add( peer.getAddress().getHostAddress());
1897 										ports.add( peer.getPort());
1898 										udp_ports.add( 0 );
1899 										flags.add( null );
1900 
1901 										is_seeds.add( false );
1902 										leecher_count++;
1903 
1904 									}catch( Throwable e ){
1905 									}
1906 								}
1907 							}
1908 
1909 							if ( try_injection ){
1910 
1911 								PeerManager pm = download.getPeerManager();
1912 
1913 								if ( pm != null ){
1914 
1915 									pm.peerDiscovered(
1916 										PEPeerSource.PS_DHT,
1917 										peer.getAddress().getHostAddress(),
1918 										peer.getPort(),
1919 										0,
1920 										NetworkManager.getCryptoRequired( NetworkManager.CRYPTO_OVERRIDE_NONE ));
1921 								}
1922 							}
1923 						}
1924 
1925 						public void
1926 						valueRead(
1927 							DHTPluginContact	originator,
1928 							DHTPluginValue		value )
1929 						{
1930 							synchronized( this ){
1931 
1932 								if ( complete ){
1933 
1934 									return;
1935 								}
1936 
1937 								try{
1938 									String[]	tokens = new String(value.getValue()).split(";");
1939 
1940 									String	tcp_part = tokens[0].trim();
1941 
1942 									int	sep = tcp_part.indexOf(':');
1943 
1944 									String	ip_str		= null;
1945 									String	tcp_port_str;
1946 
1947 									if ( sep == -1 ){
1948 
1949 										tcp_port_str = tcp_part;
1950 
1951 									}else{
1952 
1953 										ip_str 			= tcp_part.substring( 0, sep );
1954 										tcp_port_str	= tcp_part.substring( sep+1 );
1955 									}
1956 
1957 									int	tcp_port = Integer.parseInt( tcp_port_str );
1958 
1959 									if ( tcp_port > 0 && tcp_port < 65536 ){
1960 
1961 										String	flag_str	= null;
1962 										int		udp_port	= -1;
1963 
1964 										boolean	has_i2p = false;
1965 
1966 										try{
1967 											for (int i=1;i<tokens.length;i++){
1968 
1969 												String	token = tokens[i].trim();
1970 
1971 												if ( token.length() > 0 ){
1972 
1973 													if ( Character.isDigit( token.charAt( 0 ))){
1974 
1975 														udp_port = Integer.parseInt( token );
1976 
1977 														if ( udp_port <= 0 || udp_port >=65536 ){
1978 
1979 															udp_port = -1;
1980 														}
1981 													}else{
1982 
1983 														flag_str = token;
1984 
1985 														if ( flag_str.contains("I")){
1986 
1987 															has_i2p = true;
1988 														}
1989 													}
1990 												}
1991 											}
1992 										}catch( Throwable e ){
1993 										}
1994 
1995 										addresses.add(
1996 												ip_str==null?originator.getAddress().getAddress().getHostAddress():ip_str);
1997 
1998 										ports.add( new Integer( tcp_port ));
1999 
2000 										udp_ports.add( new Integer( udp_port==-1?originator.getAddress().getPort():udp_port));
2001 
2002 										flags.add( flag_str );
2003 
2004 										if (( value.getFlags() & DHTPlugin.FLAG_DOWNLOADING ) == 1 ){
2005 
2006 											leecher_count++;
2007 
2008 											is_seeds.add( new Boolean( false ));
2009 
2010 											if ( has_i2p ){
2011 
2012 												i2p_leecher_count++;
2013 											}
2014 										}else{
2015 
2016 											is_seeds.add( new Boolean( true ));
2017 
2018 											seed_count++;
2019 
2020 											if ( has_i2p ){
2021 
2022 												i2p_seed_count++;
2023 											}
2024 										}
2025 									}
2026 
2027 								}catch( Throwable e ){
2028 
2029 									// in case we get crap back (someone spamming the DHT) just
2030 									// silently ignore
2031 								}
2032 							}
2033 						}
2034 
2035 						public void
2036 						valueWritten(
2037 							DHTPluginContact	target,
2038 							DHTPluginValue		value )
2039 						{
2040 						}
2041 
2042 						public void
2043 						complete(
2044 							byte[]	key,
2045 							boolean	timeout_occurred )
2046 						{
2047 							synchronized( this ){
2048 
2049 								if ( complete ){
2050 
2051 									return;
2052 								}
2053 
2054 								complete = true;
2055 							}
2056 
2057 							if ( 	target.getType() == REG_TYPE_FULL ||
2058 									(	target.getType() == REG_TYPE_DERIVED &&
2059 										seed_count + leecher_count > 1 )){
2060 
2061 								log( 	download,
2062 										"Get of '" + target.getDesc() + "' completed (elapsed=" + TimeFormatter.formatColonMillis(SystemTime.getCurrentTime() - start)
2063 												+ "), addresses=" + addresses.size() + ", seeds="
2064 												+ seed_count + ", leechers=" + leecher_count);
2065 							}
2066 
2067 							decreaseActive(download);
2068 
2069 							int	peers_found = addresses.size();
2070 
2071 							List<DownloadAnnounceResultPeer>	peers_for_announce = new ArrayList<DownloadAnnounceResultPeer>();
2072 
2073 								// scale min and max based on number of active torrents
2074 								// we don't want more than a few announces a minute
2075 
2076 							int	announce_per_min = 4;
2077 
2078 							int	num_active = query_map.size();
2079 
2080 							int	announce_min = Math.max( ANNOUNCE_MIN_DEFAULT, ( num_active / announce_per_min )*60*1000 );
2081 
2082 							int	announce_max = derived_only?ANNOUNCE_MAX_DERIVED_ONLY:ANNOUNCE_MAX;
2083 
2084 							announce_min = Math.min( announce_min, announce_max );
2085 
2086 							current_announce_interval = announce_min;
2087 
2088 							final long	retry = announce_min + peers_found*(long)(announce_max-announce_min)/NUM_WANT;
2089 
2090 							int download_state = download.getState();
2091 
2092 							boolean	we_are_seeding = download_state == Download.ST_SEEDING;
2093 
2094 							try{
2095 								this_mon.enter();
2096 
2097 								int[] run_data = running_downloads.get( download );
2098 
2099 								if ( run_data != null ){
2100 
2101 									boolean full = target.getType() == REG_TYPE_FULL;
2102 
2103 									int peer_count = we_are_seeding?leecher_count:(seed_count+leecher_count);
2104 
2105 									run_data[1] = full?seed_count:Math.max( run_data[1], seed_count);
2106 									run_data[2]	= full?leecher_count:Math.max( run_data[2], leecher_count);
2107 									run_data[3] = full?peer_count:Math.max( run_data[3], peer_count);
2108 
2109 									run_data[4] = (int)(SystemTime.getCurrentTime()/1000);
2110 
2111 									long	absolute_retry = SystemTime.getCurrentTime() + retry;
2112 
2113 									if ( absolute_retry > max_retry[0] ){
2114 
2115 											// only update next query time if none set yet
2116 											// or we appear to have set the existing one. If we
2117 											// don't do this then we'll overwrite any rescheduled
2118 											// announces
2119 
2120 										Long	existing = (Long)query_map.get( download );
2121 
2122 										if ( 	existing == null ||
2123 												existing.longValue() == max_retry[0] ){
2124 
2125 											max_retry[0] = absolute_retry;
2126 
2127 											query_map.put( download, new Long( absolute_retry ));
2128 										}
2129 									}
2130 								}
2131 							}finally{
2132 
2133 								this_mon.exit();
2134 							}
2135 
2136 							putDetails put_details = details.getPutDetails();
2137 
2138 							String	ext_address = put_details.getIPOverride();
2139 
2140 							if ( ext_address == null ){
2141 
2142 								ext_address = dht.getLocalAddress().getAddress().getAddress().getHostAddress();
2143 							}
2144 
2145 							if ( put_details.hasI2P()){
2146 
2147 								if ( we_are_seeding ){
2148 									if ( i2p_seed_count > 0 ){
2149 										i2p_seed_count--;
2150 									}
2151 								}else{
2152 									if ( i2p_leecher_count > 0 ){
2153 										i2p_leecher_count--;
2154 									}
2155 								}
2156 							}
2157 
2158 							if ( i2p_seed_count + i2p_leecher_count > 0 ){
2159 
2160 								download.setUserData( DOWNLOAD_USER_DATA_I2P_SCRAPE_KEY, new int[]{ i2p_seed_count,  i2p_leecher_count });
2161 
2162 							}else{
2163 
2164 								download.setUserData( DOWNLOAD_USER_DATA_I2P_SCRAPE_KEY, null );
2165 							}
2166 
2167 							for (int i=0;i<addresses.size();i++){
2168 
2169 									// when we are seeding ignore seeds
2170 
2171 								if ( we_are_seeding && ((Boolean)is_seeds.get(i)).booleanValue()){
2172 
2173 									continue;
2174 								}
2175 
2176 									// remove ourselves
2177 
2178 								String	ip = (String)addresses.get(i);
2179 
2180 								if ( ip.equals( ext_address )){
2181 
2182 									if ( ((Integer)ports.get(i)).intValue() == put_details.getTCPPort() &&
2183 										 ((Integer)udp_ports.get(i)).intValue() == put_details.getUDPPort()){
2184 
2185 										continue;
2186 									}
2187 								}
2188 
2189 								final int f_i = i;
2190 
2191 								peers_for_announce.add(
2192 									new DownloadAnnounceResultPeer()
2193 									{
2194 										public String
2195 										getSource()
2196 										{
2197 											return( PEPeerSource.PS_DHT );
2198 										}
2199 
2200 										public String
2201 										getAddress()
2202 										{
2203 											return((String)addresses.get(f_i));
2204 										}
2205 
2206 										public int
2207 										getPort()
2208 										{
2209 											return(((Integer)ports.get(f_i)).intValue());
2210 										}
2211 
2212 										public int
2213 										getUDPPort()
2214 										{
2215 											return(((Integer)udp_ports.get(f_i)).intValue());
2216 										}
2217 
2218 										public byte[]
2219 										getPeerID()
2220 										{
2221 											return( null );
2222 										}
2223 
2224 										public short
2225 										getProtocol()
2226 										{
2227 											String	flag = (String)flags.get(f_i);
2228 
2229 											short protocol = PROTOCOL_NORMAL;
2230 
2231 											if ( flag != null ){
2232 
2233 												if ( flag.contains("C")){
2234 
2235 													protocol = PROTOCOL_CRYPT;
2236 												}
2237 											}
2238 
2239 											return( protocol );
2240 										}
2241 									});
2242 
2243 							}
2244 
2245 							if ( target.getType() == REG_TYPE_DERIVED && peers_for_announce.size() > 0 ){
2246 
2247 								PeerManager pm = download.getPeerManager();
2248 
2249 								if ( pm != null ){
2250 
2251 										// try some limited direct injection
2252 
2253 									List<DownloadAnnounceResultPeer>	temp = new ArrayList<DownloadAnnounceResultPeer>( peers_for_announce );
2254 
2255 									Random rand = new Random();
2256 
2257 									for (int i=0;i<DIRECT_INJECT_PEER_MAX && temp.size() > 0; i++ ){
2258 
2259 										DownloadAnnounceResultPeer peer = temp.remove( rand.nextInt( temp.size()));
2260 
2261 										log( download, "Injecting derived peer " + peer.getAddress() + " into " + download.getName());
2262 
2263 										Map<Object,Object>	user_data = new HashMap<Object,Object>();
2264 
2265 										user_data.put( Peer.PR_PRIORITY_CONNECTION, new Boolean( true ));
2266 
2267 										pm.addPeer(
2268 												peer.getAddress(),
2269 												peer.getPort(),
2270 												peer.getUDPPort(),
2271 												peer.getProtocol() == DownloadAnnounceResultPeer.PROTOCOL_CRYPT,
2272 												user_data );
2273 									}
2274 								}
2275 							}
2276 
2277 							if ( 	download_state == Download.ST_DOWNLOADING ||
2278 									download_state == Download.ST_SEEDING ){
2279 
2280 								final DownloadAnnounceResultPeer[]	peers = new DownloadAnnounceResultPeer[peers_for_announce.size()];
2281 
2282 								peers_for_announce.toArray( peers );
2283 
2284 								download.setAnnounceResult(
2285 										new DownloadAnnounceResult()
2286 										{
2287 											public Download
2288 											getDownload()
2289 											{
2290 												return( download );
2291 											}
2292 
2293 											public int
2294 											getResponseType()
2295 											{
2296 												return( DownloadAnnounceResult.RT_SUCCESS );
2297 											}
2298 
2299 											public int
2300 											getReportedPeerCount()
2301 											{
2302 												return( peers.length);
2303 											}
2304 
2305 											public int
2306 											getSeedCount()
2307 											{
2308 												return( seed_count );
2309 											}
2310 
2311 											public int
2312 											getNonSeedCount()
2313 											{
2314 												return( leecher_count );
2315 											}
2316 
2317 											public String
2318 											getError()
2319 											{
2320 												return( null );
2321 											}
2322 
2323 											public URL
2324 											getURL()
2325 											{
2326 												return( url_to_report );
2327 											}
2328 
2329 											public DownloadAnnounceResultPeer[]
2330 											getPeers()
2331 											{
2332 												return( peers );
2333 											}
2334 
2335 											public long
2336 											getTimeToWait()
2337 											{
2338 												return( retry/1000 );
2339 											}
2340 
2341 											public Map
2342 											getExtensions()
2343 											{
2344 												return( null );
2345 											}
2346 										});
2347 							}
2348 
2349 								// only inject the scrape result if the torrent is decentralised. If we do this for
2350 								// "normal" torrents then it can have unwanted side-effects, such as stopping the torrent
2351 								// due to ignore rules if there are no downloaders in the DHT - bthub backup, for example,
2352 								// isn't scrapable...
2353 
2354 								// hmm, ok, try being a bit more relaxed about this, inject the scrape if
2355 								// we have any peers.
2356 
2357 							boolean	inject_scrape = leecher_count > 0;
2358 
2359 							DownloadScrapeResult result = download.getLastScrapeResult();
2360 
2361 							if (	result == null ||
2362 									result.getResponseType() == DownloadScrapeResult.RT_ERROR ){
2363 
2364 							}else{
2365 
2366 									// if the currently reported values are the same as the
2367 									// ones we previously injected then overwrite them
2368 									// note that we can't test the URL to see if we originated
2369 									// the scrape values as this gets replaced when a normal
2370 									// scrape fails :(
2371 
2372 								synchronized( scrape_injection_map ){
2373 
2374 									int[]	prev = (int[])scrape_injection_map.get( download );
2375 
2376 									if ( 	prev != null &&
2377 											prev[0] == result.getSeedCount() &&
2378 											prev[1] == result.getNonSeedCount()){
2379 
2380 										inject_scrape	= true;
2381 									}
2382 								}
2383 							}
2384 
2385 							if ( torrent.isDecentralised() || inject_scrape ){
2386 
2387 
2388 									// make sure that the injected scrape values are consistent
2389 									// with our currently connected peers
2390 
2391 								PeerManager	pm = download.getPeerManager();
2392 
2393 								int	local_seeds 	= 0;
2394 								int	local_leechers 	= 0;
2395 
2396 								if ( pm != null ){
2397 
2398 									Peer[]	dl_peers = pm.getPeers();
2399 
2400 									for (int i=0;i<dl_peers.length;i++){
2401 
2402 										Peer	dl_peer = dl_peers[i];
2403 
2404 										if ( dl_peer.getPercentDoneInThousandNotation() == 1000 ){
2405 
2406 											local_seeds++;
2407 
2408 										}else{
2409 											local_leechers++;
2410 										}
2411 									}
2412 								}
2413 
2414 								final int f_adj_seeds 		= Math.max( seed_count, local_seeds );
2415 								final int f_adj_leechers	= Math.max( leecher_count, local_leechers );
2416 
2417 								synchronized( scrape_injection_map ){
2418 
2419 									scrape_injection_map.put( download, new int[]{ f_adj_seeds, f_adj_leechers });
2420 								}
2421 
2422 								try{
2423 									this_mon.enter();
2424 
2425 									int[] run_data = running_downloads.get( download );
2426 
2427 									if ( run_data == null ){
2428 
2429 										run_data = run_data_cache.get( download );
2430 									}
2431 
2432 									if ( run_data != null ){
2433 
2434 										run_data[1] = f_adj_seeds;
2435 										run_data[2]	= f_adj_leechers;
2436 
2437 										run_data[4] = (int)(SystemTime.getCurrentTime()/1000);
2438 									}
2439 								}finally{
2440 
2441 									this_mon.exit();
2442 								}
2443 
2444 								download.setScrapeResult(
2445 									new DownloadScrapeResult()
2446 									{
2447 										public Download
2448 										getDownload()
2449 										{
2450 											return( download );
2451 										}
2452 
2453 										public int
2454 										getResponseType()
2455 										{
2456 											return( RT_SUCCESS );
2457 										}
2458 
2459 										public int
2460 										getSeedCount()
2461 										{
2462 											return( f_adj_seeds );
2463 										}
2464 
2465 										public int
2466 										getNonSeedCount()
2467 										{
2468 											return( f_adj_leechers );
2469 										}
2470 
2471 										public long
2472 										getScrapeStartTime()
2473 										{
2474 											return( start );
2475 										}
2476 
2477 										public void
2478 										setNextScrapeStartTime(
2479 											long nextScrapeStartTime)
2480 										{
2481 
2482 										}
2483 										public long
2484 										getNextScrapeStartTime()
2485 										{
2486 											return( SystemTime.getCurrentTime() + retry );
2487 										}
2488 
2489 										public String
2490 										getStatus()
2491 										{
2492 											return( "OK" );
2493 										}
2494 
2495 										public URL
2496 										getURL()
2497 										{
2498 											return( url_to_report );
2499 										}
2500 									});
2501 								}
2502 						}
2503 					});
2504 		}
2505 
2506 		return( num_done );
2507 	}
2508 
2509 	protected boolean
2510 	isComplete(
2511 		Download	download )
2512 	{
2513 		if ( Constants.DOWNLOAD_SOURCES_PRETEND_COMPLETE ){
2514 
2515 			return( true );
2516 		}
2517 
2518 		boolean	is_complete = download.isComplete();
2519 
2520 		if ( is_complete ){
2521 
2522 			PeerManager pm = download.getPeerManager();
2523 
2524 			if ( pm != null ){
2525 
2526 				PEPeerManager core_pm = PluginCoreUtils.unwrap( pm );
2527 
2528 				if ( core_pm != null && core_pm.getHiddenBytes() > 0 ){
2529 
2530 					is_complete = false;
2531 				}
2532 			}
2533 		}
2534 
2535 		return( is_complete );
2536 	}
2537 
2538 	protected void
2539 	trackerRemove(
2540 		final Download			download,
2541 		RegistrationDetails		details )
2542 	{
2543 		if ( disable_put ){
2544 
2545 			return;
2546 		}
2547 
2548 		if ( download.getFlag( Download.FLAG_METADATA_DOWNLOAD )){
2549 
2550 			return;
2551 		}
2552 
2553 		final 	long	start = SystemTime.getCurrentTime();
2554 
2555 		trackerTarget[] targets = details.getTargets( true );
2556 
2557 		for (int i=0;i<targets.length;i++){
2558 
2559 			final trackerTarget target = targets[i];
2560 
2561 			if ( dht.hasLocalKey( target.getHash())){
2562 
2563 				increaseActive( download );
2564 
2565 				dht.remove(
2566 						target.getHash(),
2567 						"Tracker dereg of '" + download.getName() + "'" + target.getDesc(),
2568 						new DHTPluginOperationListener()
2569 						{
2570 							public boolean
2571 							diversified()
2572 							{
2573 								return( true );
2574 							}
2575 
2576 							public void
2577 							starts(
2578 								byte[] 				key )
2579 							{
2580 							}
2581 
2582 							public void
2583 							valueRead(
2584 								DHTPluginContact	originator,
2585 								DHTPluginValue		value )
2586 							{
2587 							}
2588 
2589 							public void
2590 							valueWritten(
2591 								DHTPluginContact	target,
2592 								DHTPluginValue		value )
2593 							{
2594 							}
2595 
2596 							public void
2597 							complete(
2598 								byte[]	key,
2599 								boolean	timeout_occurred )
2600 							{
2601 								if ( target.getType() == REG_TYPE_FULL ){
2602 
2603 									log( 	download,
2604 											"Unregistration of '" + target.getDesc() + "' completed (elapsed="
2605 												+ TimeFormatter.formatColonMillis(SystemTime.getCurrentTime() - start) + ")");
2606 								}
2607 
2608 								decreaseActive( download );
2609 							}
2610 						});
2611 			}
2612 		}
2613 	}
2614 
2615 	protected void
2616 	trackerRemove(
2617 		final Download			download,
2618 		final trackerTarget 	target )
2619 	{
2620 		if ( disable_put ){
2621 
2622 			return;
2623 		}
2624 
2625 		if ( download.getFlag( Download.FLAG_METADATA_DOWNLOAD )){
2626 
2627 			return;
2628 		}
2629 
2630 		final 	long	start = SystemTime.getCurrentTime();
2631 
2632 		if ( dht.hasLocalKey( target.getHash())){
2633 
2634 			increaseActive( download );
2635 
2636 			dht.remove(
2637 					target.getHash(),
2638 					"Tracker dereg of '" + download.getName() + "'" + target.getDesc(),
2639 					new DHTPluginOperationListener()
2640 					{
2641 						public boolean
2642 						diversified()
2643 						{
2644 							return( true );
2645 						}
2646 
2647 						public void
2648 						starts(
2649 							byte[] 				key )
2650 						{
2651 						}
2652 
2653 						public void
2654 						valueRead(
2655 							DHTPluginContact	originator,
2656 							DHTPluginValue		value )
2657 						{
2658 						}
2659 
2660 						public void
2661 						valueWritten(
2662 							DHTPluginContact	target,
2663 							DHTPluginValue		value )
2664 						{
2665 						}
2666 
2667 						public void
2668 						complete(
2669 							byte[]	key,
2670 							boolean	timeout_occurred )
2671 						{
2672 							if ( target.getType() == REG_TYPE_FULL ){
2673 
2674 								log( 	download,
2675 										"Unregistration of '" + target.getDesc() + "' completed (elapsed="
2676 										+ TimeFormatter.formatColonMillis(SystemTime.getCurrentTime() - start) + ")");
2677 							}
2678 
2679 							decreaseActive( download );
2680 						}
2681 					});
2682 		}
2683 	}
2684 
2685 	protected void
2686 	processNonRegistrations()
2687 	{
2688 		Download	ready_download 				= null;
2689 		long		ready_download_next_check	= -1;
2690 
2691 		long	now = plugin_interface.getUtilities().getCurrentSystemTime();
2692 
2693 			// unfortunately getting scrape results can acquire locks and there is a vague
2694 			// possibility of deadlock here, so pre-fetch the scrape results
2695 
2696 		List<Download>	to_scrape = new ArrayList<Download>();
2697 
2698 		try{
2699 			this_mon.enter();
2700 
2701 			Iterator<Download>	it = interesting_downloads.keySet().iterator();
2702 
2703 			while( it.hasNext() && ready_download == null ){
2704 
2705 				Download	download = it.next();
2706 
2707 				Torrent	torrent = download.getTorrent();
2708 
2709 				if ( torrent == null ){
2710 
2711 					continue;
2712 				}
2713 
2714 				int[] run_data = running_downloads.get( download );
2715 
2716 				if ( run_data == null || run_data[0] == REG_TYPE_DERIVED ){
2717 
2718 						// looks like we'll need the scrape below
2719 
2720 					to_scrape.add( download );
2721 				}
2722 			}
2723 		}finally{
2724 
2725 			this_mon.exit();
2726 		}
2727 
2728 		Map<Download,DownloadScrapeResult> scrapes = new HashMap<Download,DownloadScrapeResult>();
2729 
2730 		for (int i=0;i<to_scrape.size();i++){
2731 
2732 			Download	download = (Download)to_scrape.get(i);
2733 
2734 			scrapes.put( download, download.getLastScrapeResult());
2735 		}
2736 
2737 		try{
2738 			this_mon.enter();
2739 
2740 			Iterator<Download>	it = interesting_downloads.keySet().iterator();
2741 
2742 			while( it.hasNext() && ready_download == null ){
2743 
2744 				Download	download = it.next();
2745 
2746 				Torrent	torrent = download.getTorrent();
2747 
2748 				if ( torrent == null ){
2749 
2750 					continue;
2751 				}
2752 
2753 				int[] run_data = running_downloads.get( download );
2754 
2755 				if ( run_data == null || run_data[0] == REG_TYPE_DERIVED ){
2756 
2757 					boolean	force =  torrent.wasCreatedByUs();
2758 
2759 					if ( !force ){
2760 
2761 						if ( interesting_pub_max > 0 && interesting_published > interesting_pub_max ){
2762 
2763 							continue;
2764 						}
2765 
2766 						DownloadScrapeResult	scrape = (DownloadScrapeResult)scrapes.get( download );
2767 
2768 						if ( scrape == null ){
2769 
2770 								// catch it next time round
2771 
2772 							continue;
2773 						}
2774 
2775 						if ( scrape.getSeedCount() + scrape.getNonSeedCount() > NUM_WANT ){
2776 
2777 							continue;
2778 						}
2779 					}
2780 
2781 					long	target = ((Long)interesting_downloads.get( download )).longValue();
2782 
2783 					long check_period = TorrentUtils.isDecentralised( torrent.getAnnounceURL())?INTERESTING_DHT_CHECK_PERIOD:INTERESTING_CHECK_PERIOD;
2784 
2785 					if ( target <= now ){
2786 
2787 						ready_download				= download;
2788 						ready_download_next_check 	= now + check_period;
2789 
2790 						interesting_downloads.put( download, new Long( ready_download_next_check ));
2791 
2792 					}else if ( target - now > check_period ){
2793 
2794 						interesting_downloads.put( download, new Long( now + (target%check_period)));
2795 					}
2796 				}
2797 			}
2798 
2799 		}finally{
2800 
2801 			this_mon.exit();
2802 		}
2803 
2804 		if ( ready_download != null ){
2805 
2806 			final Download	f_ready_download = ready_download;
2807 
2808 			final Torrent torrent = ready_download.getTorrent();
2809 
2810 			if ( ready_download.getFlag( Download.FLAG_METADATA_DOWNLOAD )){
2811 
2812 				try{
2813 					this_mon.enter();
2814 
2815 					interesting_downloads.remove( f_ready_download );
2816 
2817 				}finally{
2818 
2819 					this_mon.exit();
2820 				}
2821 
2822 			}else if ( dht.isDiversified( torrent.getHash())){
2823 
2824 				// System.out.println( "presence query for " + f_ready_download.getName() + "-> diversified pre start" );
2825 
2826 				try{
2827 					this_mon.enter();
2828 
2829 					interesting_downloads.remove( f_ready_download );
2830 
2831 				}finally{
2832 
2833 					this_mon.exit();
2834 				}
2835 			}else{
2836 
2837 				//System.out.println( "presence query for " + ready_download.getName());
2838 
2839 				final long start 		= now;
2840 				final long f_next_check = ready_download_next_check;
2841 
2842 				dht.get(	torrent.getHash(),
2843 							"Presence query for '" + ready_download.getName() + "'",
2844 							(byte)0,
2845 							INTERESTING_AVAIL_MAX,
2846 							ANNOUNCE_TIMEOUT,
2847 							false, false,
2848 							new DHTPluginOperationListener()
2849 							{
2850 								private boolean diversified;
2851 								private int 	leechers = 0;
2852 								private int 	seeds	 = 0;
2853 
2854 								private int 	i2p_leechers = 0;
2855 								private int 	i2p_seeds	 = 0;
2856 
2857 								public boolean
2858 								diversified()
2859 								{
2860 									diversified	= true;
2861 
2862 									return( false );
2863 								}
2864 
2865 								public void
2866 								starts(
2867 									byte[] 				key )
2868 								{
2869 								}
2870 
2871 								public void
2872 								valueRead(
2873 									DHTPluginContact	originator,
2874 									DHTPluginValue		value )
2875 								{
2876 									boolean is_leecher = ( value.getFlags() & DHTPlugin.FLAG_DOWNLOADING ) == 1;
2877 
2878 									if ( is_leecher ){
2879 
2880 										leechers++;
2881 
2882 									}else{
2883 
2884 										seeds++;
2885 									}
2886 
2887 									try{
2888 										String[]	tokens = new String(value.getValue()).split(";");
2889 
2890 										for (int i=1;i<tokens.length;i++){
2891 
2892 											String	token = tokens[i].trim();
2893 
2894 											if ( token.length() > 0 ){
2895 
2896 												if ( !Character.isDigit( token.charAt( 0 ))){
2897 
2898 													String flag_str = token;
2899 
2900 													if ( flag_str.contains("I")){
2901 
2902 														if ( is_leecher ){
2903 
2904 															i2p_leechers++;
2905 
2906 														}else{
2907 
2908 															i2p_seeds++;
2909 														}
2910 													}
2911 												}
2912 											}
2913 
2914 										}
2915 									}catch( Throwable e ){
2916 
2917 									}
2918 								}
2919 
2920 								public void
2921 								valueWritten(
2922 									DHTPluginContact	target,
2923 									DHTPluginValue		value )
2924 								{
2925 								}
2926 
2927 								public void
2928 								complete(
2929 									byte[]	key,
2930 									boolean	timeout_occurred )
2931 								{
2932 									// System.out.println( "    presence query for " + f_ready_download.getName() + "->" + total + "/div = " + diversified );
2933 
2934 									int	total = leechers + seeds;
2935 
2936 									log( torrent,
2937 											"Presence query: availability="+
2938 											(total==INTERESTING_AVAIL_MAX?(INTERESTING_AVAIL_MAX+"+"):(total+"")) + ",div=" + diversified +
2939 											" (elapsed=" + TimeFormatter.formatColonMillis(SystemTime.getCurrentTime() - start) + ")");
2940 
2941 									if ( diversified ){
2942 
2943 										try{
2944 											this_mon.enter();
2945 
2946 											interesting_downloads.remove( f_ready_download );
2947 
2948 										}finally{
2949 
2950 											this_mon.exit();
2951 										}
2952 
2953 									}else if ( total < INTERESTING_AVAIL_MAX ){
2954 
2955 											// once we're registered we don't need to process this download any
2956 											// more unless it goes active and then inactive again
2957 
2958 										try{
2959 											this_mon.enter();
2960 
2961 											interesting_downloads.remove( f_ready_download );
2962 
2963 										}finally{
2964 
2965 											this_mon.exit();
2966 										}
2967 
2968 										interesting_published++;
2969 
2970 										if ( !disable_put ){
2971 
2972 											dht.put(
2973 												torrent.getHash(),
2974 												"Presence store '" + f_ready_download.getName() + "'",
2975 												"0".getBytes(),	// port 0, no connections
2976 												(byte)0,
2977 												new DHTPluginOperationListener()
2978 												{
2979 													public boolean
2980 													diversified()
2981 													{
2982 														return( true );
2983 													}
2984 
2985 													public void
2986 													starts(
2987 														byte[] 				key )
2988 													{
2989 													}
2990 
2991 													public void
2992 													valueRead(
2993 														DHTPluginContact	originator,
2994 														DHTPluginValue		value )
2995 													{
2996 													}
2997 
2998 													public void
2999 													valueWritten(
3000 														DHTPluginContact	target,
3001 														DHTPluginValue		value )
3002 													{
3003 													}
3004 
3005 													public void
3006 													complete(
3007 														byte[]	key,
3008 														boolean	timeout_occurred )
3009 													{
3010 													}
3011 												});
3012 										}
3013 									}
3014 
3015 
3016 									try{
3017 										this_mon.enter();
3018 
3019 										int[] run_data = running_downloads.get( f_ready_download );
3020 
3021 										if ( run_data == null ){
3022 
3023 											run_data = run_data_cache.get( f_ready_download );
3024 										}
3025 
3026 										if ( run_data != null ){
3027 
3028 											if ( total < INTERESTING_AVAIL_MAX ){
3029 
3030 												run_data[1] = seeds;
3031 												run_data[2]	= leechers;
3032 												run_data[3] = total;
3033 
3034 											}else{
3035 
3036 												run_data[1] = Math.max( run_data[1], seeds );
3037 												run_data[2] = Math.max( run_data[2], leechers );
3038 											}
3039 
3040 											run_data[4] = (int)(SystemTime.getCurrentTime()/1000);
3041 										}
3042 									}finally{
3043 
3044 										this_mon.exit();
3045 									}
3046 
3047 									if ( i2p_seeds + i2p_leechers > 0 ){
3048 
3049 										int[] details = (int[])f_ready_download.getUserData( DOWNLOAD_USER_DATA_I2P_SCRAPE_KEY );
3050 
3051 										if ( details == null ){
3052 
3053 											details = new int[]{ i2p_seeds, i2p_leechers };
3054 
3055 											f_ready_download.setUserData( DOWNLOAD_USER_DATA_I2P_SCRAPE_KEY,details );
3056 
3057 										}else{
3058 
3059 											details[0] = Math.max( details[0], i2p_seeds );
3060 											details[1] = Math.max( details[1], i2p_leechers );
3061 										}
3062 									}
3063 
3064 
3065 									f_ready_download.setScrapeResult(
3066 										new DownloadScrapeResult()
3067 										{
3068 											public Download
3069 											getDownload()
3070 											{
3071 												return( null );
3072 											}
3073 
3074 											public int
3075 											getResponseType()
3076 											{
3077 												return( RT_SUCCESS );
3078 											}
3079 
3080 											public int
3081 											getSeedCount()
3082 											{
3083 												return( seeds );
3084 											}
3085 
3086 											public int
3087 											getNonSeedCount()
3088 											{
3089 												return( leechers );
3090 											}
3091 
3092 											public long
3093 											getScrapeStartTime()
3094 											{
3095 												return( SystemTime.getCurrentTime());
3096 											}
3097 
3098 											public void
3099 											setNextScrapeStartTime(
3100 												long nextScrapeStartTime)
3101 											{
3102 											}
3103 
3104 											public long
3105 											getNextScrapeStartTime()
3106 											{
3107 												return( f_next_check );
3108 											}
3109 
3110 											public String
3111 											getStatus()
3112 											{
3113 												return( "OK" );
3114 											}
3115 
3116 											public URL
3117 											getURL()
3118 											{
3119 												URL	url_to_report = torrent.isDecentralised()?torrent.getAnnounceURL():DEFAULT_URL;
3120 
3121 												return( url_to_report );
3122 											}
3123 										});
3124 								}
3125 							});
3126 
3127 			}
3128 		}
3129 	}
3130 
3131 	public void
3132 	stateChanged(
3133 		Download		download,
3134 		int				old_state,
3135 		int				new_state )
3136 	{
3137 		int	state = download.getState();
3138 
3139 		try{
3140 			this_mon.enter();
3141 
3142 			if ( 	state == Download.ST_DOWNLOADING ||
3143 					state == Download.ST_SEEDING ||
3144 					state == Download.ST_QUEUED ){	// included queued here for the mo to avoid lots
3145 													// of thrash for torrents that flip a lot
3146 
3147 				if ( running_downloads.containsKey( download )){
3148 
3149 						// force requery
3150 
3151 					query_map.put( download, new Long( SystemTime.getCurrentTime()));
3152 				}
3153 			}
3154 		}finally{
3155 
3156 			this_mon.exit();
3157 		}
3158 
3159 			// don't do anything if paused as we want things to just continue as they are (we would force an announce here otherwise)
3160 
3161 		if ( !download.isPaused()){
3162 
3163 			checkDownloadForRegistration( download, false );
3164 		}
3165 	}
3166 
3167 	public void
3168 	announceAll()
3169 	{
3170 		log.log( "Announce-all requested" );
3171 
3172 		Long now = new Long( SystemTime.getCurrentTime());
3173 
3174 		try{
3175 			this_mon.enter();
3176 
3177 			Iterator<Map.Entry<Download,Long>> it = query_map.entrySet().iterator();
3178 
3179 			while( it.hasNext()){
3180 
3181 				Map.Entry<Download,Long>	entry = it.next();
3182 
3183 				entry.setValue( now );
3184 			}
3185 		}finally{
3186 
3187 			this_mon.exit();
3188 		}
3189 	}
3190 
3191 	private void
3192 	announce(
3193 		Download	download )
3194 	{
3195 		log.log( "Announce requested for " + download.getName());
3196 
3197 		try{
3198 			this_mon.enter();
3199 
3200 			query_map.put(download,  SystemTime.getCurrentTime());
3201 
3202 		}finally{
3203 
3204 			this_mon.exit();
3205 		}
3206 	}
3207 
3208 	public void
3209 	positionChanged(
3210 		Download		download,
3211 		int 			oldPosition,
3212 		int 			newPosition )
3213 	{
3214 	}
3215 
3216 	protected void
3217 	configChanged()
3218 	{
3219 		Download[] downloads = plugin_interface.getDownloadManager().getDownloads();
3220 
3221 		for (int i=0;i<downloads.length;i++){
3222 
3223 			checkDownloadForRegistration(downloads[i], false );
3224 		}
3225 	}
3226 
3227 		/**
3228 		 * This is used by the dhtscraper plugin
3229 		 */
3230 
3231 	public DownloadScrapeResult
3232 	scrape(
3233 		byte[]		hash )
3234 	{
3235 		final int[]	seeds 		= {0};
3236 		final int[] leechers 	= {0};
3237 
3238 		final AESemaphore	sem = new AESemaphore( "DHTTrackerPlugin:scrape" );
3239 
3240 		dht.get(hash,
3241 				"Scrape for " + ByteFormatter.encodeString( hash ).substring( 0, 16 ),
3242 				DHTPlugin.FLAG_DOWNLOADING,
3243 				NUM_WANT,
3244 				SCRAPE_TIMEOUT,
3245 				false, false,
3246 				new DHTPluginOperationListener()
3247 				{
3248 					public boolean
3249 					diversified()
3250 					{
3251 						return( true );
3252 					}
3253 
3254 					public void
3255 					starts(
3256 						byte[] 				key )
3257 					{
3258 					}
3259 
3260 					public void
3261 					valueRead(
3262 						DHTPluginContact	originator,
3263 						DHTPluginValue		value )
3264 					{
3265 						if (( value.getFlags() & DHTPlugin.FLAG_DOWNLOADING ) == 1 ){
3266 
3267 							leechers[0]++;
3268 
3269 						}else{
3270 
3271 							seeds[0]++;
3272 						}
3273 					}
3274 
3275 					public void
3276 					valueWritten(
3277 						DHTPluginContact	target,
3278 						DHTPluginValue		value )
3279 					{
3280 					}
3281 
3282 					public void
3283 					complete(
3284 						byte[]	key,
3285 						boolean	timeout_occurred )
3286 					{
3287 						sem.release();
3288 					}
3289 				});
3290 
3291 		sem.reserve();
3292 
3293 		return(
3294 				new DownloadScrapeResult()
3295 				{
3296 					public Download
3297 					getDownload()
3298 					{
3299 						return( null );
3300 					}
3301 
3302 					public int
3303 					getResponseType()
3304 					{
3305 						return( RT_SUCCESS );
3306 					}
3307 
3308 					public int
3309 					getSeedCount()
3310 					{
3311 						return( seeds[0] );
3312 					}
3313 
3314 					public int
3315 					getNonSeedCount()
3316 					{
3317 						return( leechers[0] );
3318 					}
3319 
3320 					public long
3321 					getScrapeStartTime()
3322 					{
3323 						return( 0 );
3324 					}
3325 
3326 					public void
3327 					setNextScrapeStartTime(
3328 						long nextScrapeStartTime)
3329 					{
3330 					}
3331 
3332 					public long
3333 					getNextScrapeStartTime()
3334 					{
3335 						return( 0 );
3336 					}
3337 
3338 					public String
3339 					getStatus()
3340 					{
3341 						return( "OK" );
3342 					}
3343 
3344 					public URL
3345 					getURL()
3346 					{
3347 						return( null );
3348 					}
3349 				});
3350 	}
3351 
3352 	protected void
3353 	increaseActive(
3354 		Download		dl )
3355 	{
3356 		try{
3357 			this_mon.enter();
3358 
3359 			Integer	active_i = (Integer)in_progress.get( dl );
3360 
3361 			int	active = active_i==null?0:active_i.intValue();
3362 
3363 			in_progress.put( dl, new Integer( active+1 ));
3364 
3365 		}finally{
3366 
3367 			this_mon.exit();
3368 		}
3369 	}
3370 
3371 	protected void
3372 	decreaseActive(
3373 		Download		dl )
3374 	{
3375 		try{
3376 			this_mon.enter();
3377 
3378 			Integer	active_i = (Integer)in_progress.get( dl );
3379 
3380 			if ( active_i == null ){
3381 
3382 				Debug.out( "active count inconsistent" );
3383 
3384 			}else{
3385 
3386 				int	active = active_i.intValue()-1;
3387 
3388 				if ( active == 0 ){
3389 
3390 					in_progress.remove( dl );
3391 
3392 				}else{
3393 
3394 					in_progress.put( dl, new Integer( active ));
3395 				}
3396 			}
3397 		}finally{
3398 
3399 			this_mon.exit();
3400 		}
3401 	}
3402 
3403 	protected boolean
3404 	isActive(
3405 		Download		dl )
3406 	{
3407 		try{
3408 			this_mon.enter();
3409 
3410 			return( in_progress.get(dl) != null );
3411 
3412 		}finally{
3413 
3414 			this_mon.exit();
3415 		}
3416 	}
3417 
3418 	protected class
3419 	RegistrationDetails
3420 	{
3421 		//private static final int DERIVED_ACTIVE_MIN_MILLIS	= 2*60*60*1000;
3422 
3423 		private putDetails			put_details;
3424 		private byte				flags;
3425 		private trackerTarget[]		put_targets;
3426 		private List<trackerTarget>	not_put_targets;
3427 
3428 		//private long			derived_active_start	= -1;
3429 		//private long			previous_metric;
3430 
3431 		protected
3432 		RegistrationDetails(
3433 			Download			_download,
3434 			int					_reg_type,
3435 			putDetails			_put_details,
3436 			byte				_flags )
3437 		{
3438 			put_details		= _put_details;
3439 			flags			= _flags;
3440 
3441 			getTrackerTargets( _download, _reg_type );
3442 		}
3443 
3444 		protected void
3445 		update(
3446 			putDetails		_put_details,
3447 			byte			_flags )
3448 		{
3449 			put_details	= _put_details;
3450 			flags		= _flags;
3451 		}
3452 
3453 		protected boolean
3454 		updateTargets(
3455 			Download			_download,
3456 			int					_reg_type )
3457 		{
3458 			trackerTarget[]	old_put_targets = put_targets;
3459 
3460 			getTrackerTargets( _download, _reg_type );
3461 
3462 				// first remove any redundant entries
3463 
3464 			for (int i=0;i<old_put_targets.length;i++){
3465 
3466 				boolean	found = false;
3467 
3468 				byte[]	old_hash = old_put_targets[i].getHash();
3469 
3470 				for (int j=0;j<put_targets.length;j++){
3471 
3472 					if ( Arrays.equals( put_targets[j].getHash(), old_hash )){
3473 
3474 						found	= true;
3475 
3476 						break;
3477 					}
3478 				}
3479 
3480 				if ( !found ){
3481 
3482 					trackerRemove( _download, old_put_targets[i] );
3483 				}
3484 			}
3485 
3486 				// now look to see if we have any new stuff
3487 
3488 			boolean	changed = false;
3489 
3490 			for (int i=0;i<put_targets.length;i++){
3491 
3492 				byte[]	new_hash = put_targets[i].getHash();
3493 
3494 				boolean	found = false;
3495 
3496 				for (int j=0;j<old_put_targets.length;j++){
3497 
3498 					if ( Arrays.equals( old_put_targets[j].getHash(), new_hash )){
3499 
3500 						found = true;
3501 
3502 						break;
3503 					}
3504 				}
3505 
3506 				if ( !found ){
3507 
3508 					changed = true;
3509 				}
3510 			}
3511 
3512 			return( changed );
3513 		}
3514 
3515 		protected putDetails
3516 		getPutDetails()
3517 		{
3518 			return( put_details );
3519 		}
3520 
3521 		protected byte
3522 		getFlags()
3523 		{
3524 			return( flags );
3525 		}
3526 
3527 		protected trackerTarget[]
3528 		getTargets(
3529 			boolean		for_put )
3530 		{
3531 			if ( for_put || not_put_targets == null ){
3532 
3533 				return( put_targets );
3534 
3535 			}else{
3536 
3537 				List<trackerTarget>	result = new ArrayList<trackerTarget>( Arrays.asList( put_targets ));
3538 
3539 				for (int i=0;i<not_put_targets.size()&& i < 2; i++ ){
3540 
3541 					trackerTarget target = (trackerTarget)not_put_targets.remove(0);
3542 
3543 					not_put_targets.add( target );
3544 
3545 					// System.out.println( "Mixing in " + target.getDesc());
3546 
3547 					result.add( target );
3548 				}
3549 
3550 				return( (trackerTarget[])result.toArray( new trackerTarget[result.size()]));
3551 			}
3552 		}
3553 
3554 		protected void
3555     	getTrackerTargets(
3556     		Download		download,
3557     		int				type )
3558     	{
3559     		byte[]	torrent_hash = download.getTorrent().getHash();
3560 
3561     		List<trackerTarget>	result = new ArrayList<trackerTarget>();
3562 
3563     		if ( type == REG_TYPE_FULL ){
3564 
3565     			result.add( new trackerTarget( torrent_hash, REG_TYPE_FULL, "" ));
3566     		}
3567  /*
3568     		if ( ADD_ASN_DERIVED_TARGET ){
3569 
3570 	    	    NetworkAdminASN net_asn = NetworkAdmin.getSingleton().getCurrentASN();
3571 
3572 	    	    String	as 	= net_asn.getAS();
3573 	    	    String	asn = net_asn.getASName();
3574 
3575 	    		if ( as.length() > 0 && asn.length() > 0 ){
3576 
3577 	    			String	key = "azderived:asn:" + as;
3578 
3579 	    			try{
3580 	    				byte[] asn_bytes = key.getBytes( "UTF-8" );
3581 
3582 	    				byte[] key_bytes = new byte[torrent_hash.length + asn_bytes.length];
3583 
3584 	    				System.arraycopy( torrent_hash, 0, key_bytes, 0, torrent_hash.length );
3585 
3586 	    				System.arraycopy( asn_bytes, 0, key_bytes, torrent_hash.length, asn_bytes.length );
3587 
3588 	    				result.add( new trackerTarget( key_bytes, REG_TYPE_DERIVED, asn + "/" + as ));
3589 
3590 	    			}catch( Throwable e ){
3591 
3592 	    				Debug.printStackTrace(e);
3593 	    			}
3594 	    		}
3595     		}
3596 
3597     		if ( ADD_NETPOS_DERIVED_TARGETS ){
3598 
3599 	    		long	now = SystemTime.getMonotonousTime();
3600 
3601 	    		boolean	do_it;
3602 
3603 	       		Long	metric = (Long)download.getUserData( DL_DERIVED_METRIC_KEY );
3604 
3605 	       		boolean	do_it_now = metric != null;
3606 
3607 	    		if ( derived_active_start >= 0 && now - derived_active_start <= DERIVED_ACTIVE_MIN_MILLIS ){
3608 
3609 	    			do_it = true;
3610 
3611 	    			if ( metric == null ){
3612 
3613 	    				metric = new Long( previous_metric );
3614 	    			}
3615 	    		}else{
3616 
3617 	    			if ( do_it_now ){
3618 
3619 	    				do_it = true;
3620 
3621 	    			}else{
3622 
3623 	    				derived_active_start = -1;
3624 
3625 	    				do_it = false;
3626 	    			}
3627 	    		}
3628 
3629 	    		boolean	newly_active = false;
3630 
3631 	    		if ( do_it_now ){
3632 
3633 	    			newly_active = derived_active_start == -1;
3634 
3635 	    			derived_active_start = now;
3636 	    		}
3637 
3638 	    		List<trackerTarget>	skipped_targets = null;
3639 
3640 	    		if ( do_it ){
3641 
3642 	    			previous_metric = metric.longValue();
3643 
3644 		    		try{
3645 		    			DHTNetworkPosition[] positions = getNetworkPositions();
3646 
3647 		    			for (int i=0;i<positions.length;i++){
3648 
3649 		    				DHTNetworkPosition pos = positions[i];
3650 
3651 		    				if ( pos.getPositionType() == DHTNetworkPosition.POSITION_TYPE_VIVALDI_V2 ){
3652 
3653 		    					if ( pos.isValid()){
3654 
3655 		    						List<Object[]>	derived_results = getVivaldiTargets( torrent_hash, pos.getLocation());
3656 
3657 		    		    			int	num_to_add = metric.intValue() * derived_results.size() / 100;
3658 
3659 		    		    			// System.out.println( download.getName() + ": metric=" + metric + ", adding=" + num_to_add );
3660 
3661 		    						for (int j=0;j<derived_results.size();j++){
3662 
3663 		    							Object[] entry = derived_results.get(j);
3664 
3665 		    							// int	distance = ((Integer)entry[0]).intValue();
3666 
3667 		    							trackerTarget	target= (trackerTarget)entry[1];
3668 
3669 		    							if ( j < num_to_add ){
3670 
3671 		    								result.add( target );
3672 
3673 		    							}else{
3674 
3675 		    								if ( skipped_targets == null ){
3676 
3677 		    									skipped_targets = new ArrayList<trackerTarget>();
3678 		    								}
3679 
3680 		    								skipped_targets.add( target );
3681 		    							}
3682 		    						}
3683 		    					}
3684 		    				}
3685 		    			}
3686 		    		}catch( Throwable e ){
3687 
3688 		    			Debug.printStackTrace(e);
3689 		    		}
3690 	    		}
3691 
3692 	    		not_put_targets = skipped_targets;
3693     		}
3694     		*/
3695 
3696 	    	put_targets 	= result.toArray( new trackerTarget[result.size()]);
3697     	}
3698 	}
3699 
3700 	/*
3701 	private DHTNetworkPosition[]
3702 	getNetworkPositions()
3703 	{
3704 		DHTNetworkPosition[] res = current_network_positions;
3705 
3706 		long	now = SystemTime.getMonotonousTime();
3707 
3708 		if ( 	res == null ||
3709 				now - last_net_pos_time >  30*60*1000 ){
3710 
3711 			res = current_network_positions = DHTNetworkPositionManager.getLocalPositions();
3712 
3713 			last_net_pos_time = now;
3714 		}
3715 
3716 		return( res );
3717 	}
3718 	*/
3719 
3720 	private void
3721 	log(
3722 		Download		download,
3723 		String			str )
3724 	{
3725 		log( download.getTorrent(), str );
3726 	}
3727 
3728 	private void
3729 	log(
3730 		Torrent			torrent,
3731 		String			str )
3732 	{
3733 		log.log( torrent, LoggerChannel.LT_INFORMATION, str );
3734 	}
3735 
3736 	public TrackerPeerSource
3737 	getTrackerPeerSource(
3738 		final Download		download )
3739 	{
3740 		return(
3741 			new TrackerPeerSourceAdapter()
3742 			{
3743 				private long	last_fixup;
3744 				private boolean	updating;
3745 				private int		status		= ST_UNKNOWN;
3746 				private long	next_time	= -1;
3747 				private int[]	run_data;
3748 
3749 				private void
3750 				fixup()
3751 				{
3752 					long now = SystemTime.getMonotonousTime();
3753 
3754 					if ( now - last_fixup > 5*1000 ){
3755 
3756 						try{
3757 							this_mon.enter();
3758 
3759 							updating 	= false;
3760 							next_time	= -1;
3761 
3762 							run_data = running_downloads.get( download );
3763 
3764 							if ( run_data != null ){
3765 
3766 								if ( in_progress.containsKey( download )){
3767 
3768 									updating = true;
3769 								}
3770 
3771 								status = initialised_sem.isReleasedForever()?ST_ONLINE:ST_STOPPED;
3772 
3773 								Long l_next_time = query_map.get( download );
3774 
3775 								if ( l_next_time != null ){
3776 
3777 									next_time = l_next_time.longValue();
3778 								}
3779 							}else if ( interesting_downloads.containsKey( download )){
3780 
3781 								status = ST_STOPPED;
3782 
3783 							}else{
3784 
3785 								int dl_state = download.getState();
3786 
3787 								if ( 	dl_state == Download.ST_DOWNLOADING ||
3788 										dl_state == Download.ST_SEEDING ||
3789 										dl_state == Download.ST_QUEUED ){
3790 
3791 									status = ST_DISABLED;
3792 
3793 								}else{
3794 
3795 									status = ST_STOPPED;
3796 								}
3797 							}
3798 
3799 							if ( run_data == null ){
3800 
3801 								run_data = run_data_cache.get( download );
3802 							}
3803 						}finally{
3804 
3805 							this_mon.exit();
3806 						}
3807 
3808 						String[]	sources = download.getListAttribute( ta_peer_sources );
3809 
3810 						boolean	ok = false;
3811 
3812 						if ( sources != null ){
3813 
3814 							for (int i=0;i<sources.length;i++){
3815 
3816 								if ( sources[i].equalsIgnoreCase( "DHT")){
3817 
3818 									ok	= true;
3819 
3820 									break;
3821 								}
3822 							}
3823 						}
3824 
3825 						if ( !ok ){
3826 
3827 							status = ST_DISABLED;
3828 						}
3829 
3830 						last_fixup = now;
3831 					}
3832 				}
3833 
3834 				public int
3835 				getType()
3836 				{
3837 					return( TP_DHT );
3838 				}
3839 
3840 				public String
3841 				getName()
3842 				{
3843 					return( "DHT: " + model.getStatus().getText());
3844 				}
3845 
3846 				public int
3847 				getStatus()
3848 				{
3849 					fixup();
3850 
3851 					return( status );
3852 				}
3853 
3854 				public int
3855 				getSeedCount()
3856 				{
3857 					fixup();
3858 
3859 					if ( run_data == null ){
3860 
3861 						return( -1 );
3862 					}
3863 
3864 					return( run_data[1] );
3865 				}
3866 
3867 				public int
3868 				getLeecherCount()
3869 				{
3870 					fixup();
3871 
3872 					if ( run_data == null ){
3873 
3874 						return( -1 );
3875 					}
3876 
3877 					return( run_data[2] );
3878 				}
3879 
3880 				public int
3881 				getPeers()
3882 				{
3883 					fixup();
3884 
3885 					if ( run_data == null ){
3886 
3887 						return( -1 );
3888 					}
3889 
3890 					return( run_data[3] );
3891 				}
3892 
3893 				public int
3894 				getLastUpdate()
3895 				{
3896 					fixup();
3897 
3898 					if ( run_data == null ){
3899 
3900 						return( 0 );
3901 					}
3902 
3903 					return( run_data[4] );
3904 				}
3905 
3906 				public int
3907 				getSecondsToUpdate()
3908 				{
3909 					fixup();
3910 
3911 					if ( next_time < 0 ){
3912 
3913 						return( -1 );
3914 					}
3915 
3916 					return((int)(( next_time - SystemTime.getCurrentTime())/1000 ));
3917 				}
3918 
3919 				public int
3920 				getInterval()
3921 				{
3922 					fixup();
3923 
3924 					if ( run_data == null ){
3925 
3926 						return( -1 );
3927 					}
3928 
3929 					return((int)(current_announce_interval/1000));
3930 				}
3931 
3932 				public int
3933 				getMinInterval()
3934 				{
3935 					fixup();
3936 
3937 					if ( run_data == null ){
3938 
3939 						return( -1 );
3940 					}
3941 
3942 					return( ANNOUNCE_MIN_DEFAULT/1000 );
3943 				}
3944 
3945 				public boolean
3946 				isUpdating()
3947 				{
3948 					return( updating );
3949 				}
3950 
3951 				public boolean
3952 				canManuallyUpdate()
3953 				{
3954 					fixup();
3955 
3956 					return( run_data != null );
3957 				}
3958 
3959 				public void
3960 				manualUpdate()
3961 				{
3962 					announce( download );
3963 				}
3964 			});
3965 	}
3966 
3967 
3968 	public TrackerPeerSource[]
3969 	getTrackerPeerSources(
3970 		final Torrent		torrent )
3971 	{
3972 		TrackerPeerSource vuze_dht =
3973 			new TrackerPeerSourceAdapter()
3974 			{
3975 				private volatile boolean	query_done;
3976 				private volatile int		status		= ST_INITIALISING;
3977 
3978 				private volatile int		seeds 		= 0;
3979 				private volatile int		leechers 	= 0;
3980 
3981 
3982 				private void
3983 				fixup()
3984 				{
3985 					if ( initialised_sem.isReleasedForever()){
3986 
3987 						synchronized( this ){
3988 
3989 							if ( query_done ){
3990 
3991 								return;
3992 							}
3993 
3994 							query_done = true;
3995 
3996 							status = ST_UPDATING;
3997 						}
3998 
3999 						dht.get(	torrent.getHash(),
4000 									"Availability lookup for '" + torrent.getName() + "'",
4001 									DHTPlugin.FLAG_DOWNLOADING,
4002 									NUM_WANT,
4003 									ANNOUNCE_DERIVED_TIMEOUT,
4004 									false, true,
4005 									new DHTPluginOperationListener()
4006 									{
4007 										public void
4008 										starts(
4009 											byte[]				key )
4010 										{
4011 										}
4012 
4013 										public boolean
4014 										diversified()
4015 										{
4016 											return( true );
4017 										}
4018 
4019 										public void
4020 										valueRead(
4021 											DHTPluginContact	originator,
4022 											DHTPluginValue		value )
4023 										{
4024 											if (( value.getFlags() & DHTPlugin.FLAG_DOWNLOADING ) == 1 ){
4025 
4026 												seeds++;
4027 
4028 											}else{
4029 
4030 												leechers++;
4031 											}
4032 										}
4033 
4034 										public void
4035 										valueWritten(
4036 											DHTPluginContact	target,
4037 											DHTPluginValue		value )
4038 										{
4039 
4040 										}
4041 
4042 										public void
4043 										complete(
4044 											byte[]				key,
4045 											boolean				timeout_occurred )
4046 										{
4047 											status		= ST_ONLINE;
4048 										}
4049 									});
4050 					}
4051 				}
4052 
4053 				public int
4054 				getType()
4055 				{
4056 					return( TP_DHT );
4057 				}
4058 
4059 				public String
4060 				getName()
4061 				{
4062 					return( "Vuze DHT" );
4063 				}
4064 
4065 				public int
4066 				getStatus()
4067 				{
4068 					fixup();
4069 
4070 					return( status );
4071 				}
4072 
4073 				public int
4074 				getSeedCount()
4075 				{
4076 					fixup();
4077 
4078 					int	result = seeds;
4079 
4080 					if ( result == 0 && status != ST_ONLINE ){
4081 
4082 						return( -1 );
4083 					}
4084 
4085 					return( result );
4086 				}
4087 
4088 				public int
4089 				getLeecherCount()
4090 				{
4091 					fixup();
4092 
4093 					int	result = leechers;
4094 
4095 					if ( result == 0 && status != ST_ONLINE ){
4096 
4097 						return( -1 );
4098 					}
4099 
4100 					return( result );
4101 				}
4102 
4103 				public int
4104 				getPeers()
4105 				{
4106 					return( -1 );
4107 				}
4108 
4109 				public boolean
4110 				isUpdating()
4111 				{
4112 					return( status == ST_UPDATING );
4113 				}
4114 
4115 			};
4116 
4117 		if ( alt_lookup_handler != null ){
4118 
4119 			TrackerPeerSource alt_dht =
4120 					new TrackerPeerSourceAdapter()
4121 					{
4122 						private volatile int		status 	= ST_UPDATING;
4123 						private volatile int		peers 	= 0;
4124 
4125 						{
4126 							alt_lookup_handler.get(
4127 									torrent.getHash(),
4128 									false,
4129 									new DHTTrackerPluginAlt.LookupListener()
4130 									{
4131 										public void
4132 										foundPeer(
4133 											InetSocketAddress	address )
4134 										{
4135 											peers++;
4136 										}
4137 
4138 										public boolean
4139 										isComplete()
4140 										{
4141 											return( false );
4142 										}
4143 
4144 										public void
4145 										completed()
4146 										{
4147 											status = ST_ONLINE;
4148 										}
4149 									});
4150 						}
4151 
4152 
4153 						public int
4154 						getType()
4155 						{
4156 							return( TP_DHT );
4157 						}
4158 
4159 						public String
4160 						getName()
4161 						{
4162 							return( "Mainline DHT" );
4163 						}
4164 
4165 						public int
4166 						getStatus()
4167 						{
4168 							return( status );
4169 						}
4170 
4171 						public int
4172 						getPeers()
4173 						{
4174 							int	result = peers;
4175 
4176 							if ( result == 0 && status != ST_ONLINE ){
4177 
4178 								return( -1 );
4179 							}
4180 
4181 							return( result );
4182 						}
4183 
4184 						public boolean
4185 						isUpdating()
4186 						{
4187 							return( status == ST_UPDATING );
4188 						}
4189 
4190 					};
4191 
4192 			return( new TrackerPeerSource[]{ vuze_dht, alt_dht } );
4193 
4194 		}else{
4195 
4196 			return( new TrackerPeerSource[]{ vuze_dht } );
4197 		}
4198 	}
4199 
4200 
4201 
4202 	/*
4203 	public static List<Object[]>
4204 	getVivaldiTargets(
4205 		byte[]					torrent_hash,
4206 		double[]				loc )
4207 	{
4208 		List<Object[]>	derived_results = new ArrayList<Object[]>();
4209 
4210 		String	loc_str = "";
4211 
4212 		for (int j=0;j<loc.length;j++){
4213 
4214 			loc_str += (j==0?"":",") + loc[j];
4215 		}
4216 
4217 		TriangleSlicer slicer = new TriangleSlicer( 25 );
4218 
4219 		double	t1_x = loc[0];
4220 		double	t1_y = loc[1];
4221 		double	t2_x = loc[2];
4222 		double	t2_y = loc[3];
4223 
4224 		int[] triangle1 = slicer.findVertices( t1_x, t1_y );
4225 
4226 		int[] triangle2 = slicer.findVertices( t2_x, t2_y );
4227 
4228 
4229 		for (int j=0;j<triangle1.length;j+=2 ){
4230 
4231 			int	t1_vx = triangle1[j];
4232 			int t1_vy = triangle1[j+1];
4233 
4234 			double	t1_distance = getDistance( t1_x, t1_y, t1_vx, t1_vy );
4235 
4236 			for (int k=0;k<triangle2.length;k+=2 ){
4237 
4238 				int	t2_vx = triangle2[k];
4239 				int t2_vy = triangle2[k+1];
4240 
4241 				double	t2_distance = getDistance( t2_x, t2_y, t2_vx, t2_vy );
4242 
4243 					// these distances are in different dimensions - make up a combined distance
4244 
4245 				double distance = getDistance( t1_distance, 0, 0, t2_distance );
4246 
4247 
4248 				String	key = "azderived:vivaldi:";
4249 
4250 				String v_str = 	t1_vx + "." + t1_vy + "." + t2_vx + "." + t2_vy;
4251 
4252 				key += v_str;
4253 
4254 				try{
4255 					byte[] v_bytes = key.getBytes( "UTF-8" );
4256 
4257 					byte[] key_bytes = new byte[torrent_hash.length + v_bytes.length];
4258 
4259 					System.arraycopy( torrent_hash, 0, key_bytes, 0, torrent_hash.length );
4260 
4261 					System.arraycopy( v_bytes, 0, key_bytes, torrent_hash.length, v_bytes.length );
4262 
4263 					derived_results.add(
4264 						new Object[]{
4265 							new Integer((int)distance),
4266 							new trackerTarget( key_bytes, REG_TYPE_DERIVED, "Vivaldi: " + v_str ) });
4267 
4268 
4269 				}catch( Throwable e ){
4270 
4271 					Debug.printStackTrace(e);
4272 				}
4273 			}
4274 		}
4275 
4276 		Collections.sort(
4277 			derived_results,
4278 			new Comparator<Object[]>()
4279 			{
4280 				public int
4281 				compare(
4282 					Object[] 	entry1,
4283 					Object[] 	entry2 )
4284 				{
4285 					int	d1 = ((Integer)entry1[0]).intValue();
4286 					int	d2 = ((Integer)entry2[0]).intValue();
4287 
4288 					return( d1 - d2 );
4289 				}
4290 			});
4291 
4292 		return( derived_results );
4293 	}
4294 
4295 	protected static double
4296 	getDistance(
4297 		double	x1,
4298 		double	y1,
4299 		double	x2,
4300 		double	y2 )
4301 	{
4302 		return(Math.sqrt((x2-x1)*(x2-x1) + (y2-y1)*(y2-y1)));
4303 	}
4304 	*/
4305 
4306 	protected static class
4307 	putDetails
4308 	{
4309 		private String	encoded;
4310 		private String	ip_override;
4311 		private int		tcp_port;
4312 		private int		udp_port;
4313 		private boolean	i2p;
4314 
4315 		private
4316 		putDetails(
4317 			String	_encoded,
4318 			String	_ip,
4319 			int		_tcp_port,
4320 			int		_udp_port,
4321 			boolean	_i2p )
4322 		{
4323 			encoded			= _encoded;
4324 			ip_override		= _ip;
4325 			tcp_port		= _tcp_port;
4326 			udp_port		= _udp_port;
4327 			i2p				= _i2p;
4328 		}
4329 
4330 		protected String
4331 		getEncoded()
4332 		{
4333 			return( encoded );
4334 		}
4335 
4336 		protected String
4337 		getIPOverride()
4338 		{
4339 			return( ip_override );
4340 		}
4341 
4342 		protected int
4343 		getTCPPort()
4344 		{
4345 			return( tcp_port );
4346 		}
4347 
4348 		protected int
4349 		getUDPPort()
4350 		{
4351 			return( udp_port );
4352 		}
4353 
4354 		private boolean
4355 		hasI2P()
4356 		{
4357 			return( i2p );
4358 		}
4359 
4360 		protected boolean
4361 		sameAs(
4362 			putDetails		other )
4363 		{
4364 			return( getEncoded().equals( other.getEncoded()));
4365 		}
4366 	}
4367 
4368 	public static class
4369 	trackerTarget
4370 	{
4371 		private String		desc;
4372 		private	byte[]		hash;
4373 		private int			type;
4374 
4375 		protected
4376 		trackerTarget(
4377 			byte[]			_hash,
4378 			int				_type,
4379 			String			_desc )
4380 		{
4381 			hash		= _hash;
4382 			type		= _type;
4383 			desc		= _desc;
4384 		}
4385 
4386 		public int
4387 		getType()
4388 		{
4389 			return( type );
4390 		}
4391 
4392 		public byte[]
4393 		getHash()
4394 		{
4395 			return( hash );
4396 		}
4397 
4398 		public String
4399 		getDesc()
4400 		{
4401 			if ( type != REG_TYPE_FULL ){
4402 
4403 				return( "(" + desc + ")" );
4404 			}
4405 
4406 			return( "" );
4407 		}
4408 	}
4409 
4410 	public static class
4411 	TriangleSlicer
4412 	{
4413 		int width;
4414 
4415 		private double w;
4416 		private double w2;
4417 		private double h;
4418 
4419 		private double tan60;
4420 
4421 		public TriangleSlicer(int width) {
4422 			this.width = width;
4423 
4424 			this.w = (float) width;
4425 			this.w2 = w / 2;
4426 			this.h = Math.cos(Math.PI / 6) * w;
4427 
4428 			this.tan60 = Math.tan(Math.PI / 3);
4429 
4430 		}
4431 
4432 		/**
4433 		 *
4434 		 * @param x
4435 		 * @param y
4436 		 * @return an array of int values being x,y coordinate pairs
4437 		 */
4438 		public int[] findVertices(double x,double y) {
4439 
4440 			int yN = (int) Math.floor((y / h));
4441 			int xN = (int) Math.floor((x /w2));
4442 
4443 			double v1x,v2x,v3x,v1y,v2y,v3y;
4444 
4445 			//weither the triangle is like /\ (true) or \/ (false)
4446 			boolean upTriangle;
4447 
4448 			if((xN+yN) % 2 == 0) {
4449 				// we have a / separator in the "cell"
4450 				if( (y-h*yN) > (x-w2*xN) * tan60 ) {
4451 					//we're in the upper part
4452 					upTriangle = false;
4453 					v1x = w2 * (xN - 1);
4454 					v1y = h * (yN + 1) ;
4455 				} else {
4456 					//we're in the lower part
4457 					upTriangle = true;
4458 					v1x = w2 * xN;
4459 					v1y = h * yN;
4460 				}
4461 			} else {
4462 				// We have a \ separator in the "cell"
4463 				if( (y- h*yN) > (w2 - (x-w2*xN)) * tan60 ) {
4464 					//we're in the upper part
4465 					upTriangle = false;
4466 					v1x = w2 * xN;
4467 					v1y = h * (yN+1);
4468 				} else {
4469 					//we're in the lower part
4470 					upTriangle = true;
4471 					v1x = w2 * (xN - 1);
4472 					v1y = h * yN;
4473 				}
4474 			}
4475 
4476 			if(upTriangle) {
4477 				v2x = v1x + w;
4478 				v2y = v1y;
4479 
4480 				v3x = v1x + w2;
4481 				v3y = v1y + h;
4482 			} else {
4483 				v2x = v1x + w;
4484 				v2y = v1y;
4485 
4486 				v3x = v1x + w2;
4487 				v3y = v1y - h;
4488 			}
4489 
4490 			int[] result = new int[6];
4491 
4492 			result[0] = (int) v1x;
4493 			result[1] = (int) v1y;
4494 
4495 			result[2] = (int) v2x;
4496 			result[3] = (int) v2y;
4497 
4498 			result[4] = (int) v3x;
4499 			result[5] = (int) v3y;
4500 
4501 			return result;
4502 
4503 		}
4504 	}
4505 }
4506