1 /*
2  * Created on Dec 4, 2009
3  * Created by Paul Gardner
4  *
5  * Copyright (C) Azureus Software, Inc, All Rights Reserved.
6  *
7  * This program is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License
9  * as published by the Free Software Foundation; either version 2
10  * of the License, or (at your option) any later version.
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
18  */
19 
20 
21 package org.gudy.azureus2.core3.tracker.client.impl;
22 
23 import java.net.URL;
24 import java.util.*;
25 
26 import org.gudy.azureus2.core3.logging.LogEvent;
27 import org.gudy.azureus2.core3.logging.Logger;
28 import org.gudy.azureus2.core3.peer.PEPeerSource;
29 import org.gudy.azureus2.core3.torrent.TOTorrent;
30 import org.gudy.azureus2.core3.torrent.TOTorrentAnnounceURLSet;
31 import org.gudy.azureus2.core3.torrent.TOTorrentException;
32 import org.gudy.azureus2.core3.tracker.client.TRTrackerAnnouncer;
33 import org.gudy.azureus2.core3.tracker.client.TRTrackerAnnouncerDataProvider;
34 import org.gudy.azureus2.core3.tracker.client.TRTrackerAnnouncerException;
35 import org.gudy.azureus2.core3.tracker.client.TRTrackerAnnouncerFactory;
36 import org.gudy.azureus2.core3.tracker.client.TRTrackerAnnouncerResponse;
37 import org.gudy.azureus2.core3.tracker.client.impl.bt.TRTrackerBTAnnouncerImpl;
38 import org.gudy.azureus2.core3.tracker.client.impl.dht.TRTrackerDHTAnnouncerImpl;
39 import org.gudy.azureus2.core3.util.AEThread2;
40 import org.gudy.azureus2.core3.util.Debug;
41 import org.gudy.azureus2.core3.util.IndentWriter;
42 import org.gudy.azureus2.core3.util.SimpleTimer;
43 import org.gudy.azureus2.core3.util.SystemTime;
44 import org.gudy.azureus2.core3.util.TimerEvent;
45 import org.gudy.azureus2.core3.util.TimerEventPerformer;
46 import org.gudy.azureus2.core3.util.TorrentUtils;
47 import org.gudy.azureus2.plugins.download.DownloadAnnounceResult;
48 
49 import com.aelitis.azureus.core.tracker.TrackerPeerSource;
50 import com.aelitis.azureus.core.util.CopyOnWriteList;
51 
52 public class
53 TRTrackerAnnouncerMuxer
54 	extends TRTrackerAnnouncerImpl
55 {
56 	private static final int ACT_CHECK_INIT_DELAY			= 2500;
57 	private static final int ACT_CHECK_INTERIM_DELAY		= 10*1000;
58 	private static final int ACT_CHECK_IDLE_DELAY			= 30*1000;
59 	private static final int ACT_CHECK_SEEDING_SHORT_DELAY		= 60*1000;
60 	private static final int ACT_CHECK_SEEDING_LONG_DELAY		= 3*60*1000;
61 
62 
63 	private TRTrackerAnnouncerFactory.DataProvider		f_provider;
64 	private boolean										is_manual;
65 
66 	private long				create_time = SystemTime.getMonotonousTime();
67 
68 	private CopyOnWriteList<TRTrackerAnnouncerHelper>	announcers 	= new CopyOnWriteList<TRTrackerAnnouncerHelper>();
69 	private Set<TRTrackerAnnouncerHelper>				activated	= new HashSet<TRTrackerAnnouncerHelper>();
70 	private long										last_activation_time;
71 	private Set<String>									failed_urls	= new HashSet<String>();
72 
73 	private volatile TimerEvent					event;
74 
75 	private TRTrackerAnnouncerDataProvider		provider;
76 	private String								ip_override;
77 	private boolean								complete;
78 	private boolean								stopped;
79 	private boolean								destroyed;
80 
81 	private String[]							current_networks;
82 
83 	private TRTrackerAnnouncerHelper			last_best_active;
84 	private long								last_best_active_set_time;
85 
86 	private Map<String,StatusSummary>			recent_responses = new HashMap<String,StatusSummary>();
87 
88 	private TRTrackerAnnouncerResponse			last_response_informed;
89 
90 
91 	protected
TRTrackerAnnouncerMuxer( TOTorrent _torrent, TRTrackerAnnouncerFactory.DataProvider _f_provider, boolean _manual )92 	TRTrackerAnnouncerMuxer(
93 		TOTorrent								_torrent,
94 		TRTrackerAnnouncerFactory.DataProvider	_f_provider,
95 		boolean									_manual )
96 
97 		throws TRTrackerAnnouncerException
98 	{
99 		super( _torrent );
100 
101 		try{
102 			last_response_informed = new TRTrackerAnnouncerResponseImpl( null, _torrent.getHashWrapper(), TRTrackerAnnouncerResponse.ST_OFFLINE, TRTrackerAnnouncer.REFRESH_MINIMUM_SECS, "Initialising" );
103 
104 		}catch( TOTorrentException e ){
105 
106 			Logger.log(new LogEvent( _torrent, LOGID, "Torrent hash retrieval fails", e));
107 
108 			throw( new TRTrackerAnnouncerException( "TRTrackerAnnouncer: URL encode fails"));
109 		}
110 
111 		is_manual 	= _manual;
112 		f_provider	= _f_provider;
113 
114 		split( true );
115 	}
116 
117 	protected void
split( boolean first_time )118 	split(
119 		boolean	first_time )
120 
121 		throws TRTrackerAnnouncerException
122 	{
123 		String[]	networks = f_provider==null?null:f_provider.getNetworks();
124 
125 		boolean	force_recreate = false;
126 
127 		if ( !first_time ){
128 
129 			if ( current_networks != networks ){
130 
131 				if ( current_networks == null || networks == null ){
132 
133 					force_recreate = true;
134 
135 				}else{
136 
137 					if ( networks.length != current_networks.length ){
138 
139 						force_recreate = true;
140 
141 					}else{
142 
143 						for ( String net1: current_networks ){
144 
145 							boolean	match = false;
146 
147 							for ( String net2: networks ){
148 
149 								if ( net1 == net2 ){
150 
151 									match = true;
152 								}
153 							}
154 
155 							if ( !match ){
156 
157 								force_recreate = true;
158 
159 								break;
160 							}
161 						}
162 					}
163 				}
164 			}
165 		}
166 
167 		current_networks = networks;
168 
169 		TRTrackerAnnouncerHelper to_activate = null;
170 
171 		synchronized( this ){
172 
173 			if ( stopped || destroyed ){
174 
175 				return;
176 			}
177 
178 			TOTorrent torrent = getTorrent();
179 
180 			TOTorrentAnnounceURLSet[]	sets = torrent.getAnnounceURLGroup().getAnnounceURLSets();
181 
182 				// sanitise dht entries
183 
184 			if ( sets.length == 0 ){
185 
186 				sets = new TOTorrentAnnounceURLSet[]{ torrent.getAnnounceURLGroup().createAnnounceURLSet( new URL[]{ torrent.getAnnounceURL()})};
187 
188 			}else{
189 
190 				boolean	found_decentralised = false;
191 				boolean	modified			= false;
192 
193 				for ( int i=0;i<sets.length;i++ ){
194 
195 					TOTorrentAnnounceURLSet set = sets[i];
196 
197 					URL[] urls = set.getAnnounceURLs().clone();
198 
199 					for (int j=0;j<urls.length;j++){
200 
201 						URL u = urls[j];
202 
203 						if ( u != null && TorrentUtils.isDecentralised( u )){
204 
205 							if ( found_decentralised ){
206 
207 								modified = true;
208 
209 								urls[j] = null;
210 
211 							}else{
212 
213 								found_decentralised = true;
214 							}
215 						}
216 					}
217 				}
218 
219 				if ( modified ){
220 
221 					List<TOTorrentAnnounceURLSet> s_list = new ArrayList<TOTorrentAnnounceURLSet>();
222 
223 					for ( TOTorrentAnnounceURLSet set: sets ){
224 
225 						URL[] urls = set.getAnnounceURLs();
226 
227 						List<URL> u_list = new ArrayList<URL>( urls.length );
228 
229 						for ( URL u: urls ){
230 
231 							if ( u != null ){
232 
233 								u_list.add( u );
234 							}
235 						}
236 
237 						if ( u_list.size() > 0 ){
238 
239 							s_list.add( torrent.getAnnounceURLGroup().createAnnounceURLSet( u_list.toArray( new URL[ u_list.size() ])));
240 						}
241 					}
242 
243 					sets = s_list.toArray( new TOTorrentAnnounceURLSet[ s_list.size() ]);
244 				}
245 			}
246 
247 			List<TOTorrentAnnounceURLSet[]>	new_sets = new ArrayList<TOTorrentAnnounceURLSet[]>();
248 
249 			if ( is_manual || sets.length < 2 ){
250 
251 				new_sets.add( sets );
252 
253 			}else{
254 
255 				List<TOTorrentAnnounceURLSet> list = new ArrayList<TOTorrentAnnounceURLSet>( Arrays.asList( sets ));
256 
257 					// often we have http:/xxxx/ and udp:/xxxx/ as separate groups - keep these together
258 
259 				while( list.size() > 0 ){
260 
261 					TOTorrentAnnounceURLSet set1 = list.remove(0);
262 
263 					boolean	done = false;
264 
265 					URL[] urls1 = set1.getAnnounceURLs();
266 
267 					if ( urls1.length == 1 ){
268 
269 						URL url1 = urls1[0];
270 
271 						String prot1 = url1.getProtocol().toLowerCase();
272 						String host1	= url1.getHost();
273 
274 						for (int i=0;i<list.size();i++){
275 
276 							TOTorrentAnnounceURLSet set2 = list.get(i);
277 
278 							URL[] urls2 = set2.getAnnounceURLs();
279 
280 							if ( urls2.length == 1 ){
281 
282 								URL url2 = urls2[0];
283 
284 								String prot2 = url2.getProtocol().toLowerCase();
285 								String host2 = url2.getHost();
286 
287 								if ( host1.equals( host2 )){
288 
289 									if (	( prot1.equals( "udp" ) && prot2.startsWith( "http" )) ||
290 											( prot2.equals( "udp" ) && prot1.startsWith( "http" ))){
291 
292 										list.remove( i );
293 
294 										new_sets.add( new TOTorrentAnnounceURLSet[]{ set1, set2 });
295 
296 										done	= true;
297 									}
298 								}
299 							}
300 						}
301 					}
302 
303 					if ( !done ){
304 
305 						new_sets.add( new TOTorrentAnnounceURLSet[]{ set1 });
306 					}
307 				}
308 			}
309 
310 				// work out the difference
311 
312 			Iterator<TOTorrentAnnounceURLSet[]> ns_it = new_sets.iterator();
313 
314 				// need to copy list as we modify it and returned list ain't thread safe
315 
316 			List<TRTrackerAnnouncerHelper> existing_announcers 	= new ArrayList<TRTrackerAnnouncerHelper>( announcers.getList());
317 
318 			List<TRTrackerAnnouncerHelper> new_announcers 		= new ArrayList<TRTrackerAnnouncerHelper>();
319 
320 				// first look for unchanged sets
321 
322 			if ( !force_recreate ){
323 
324 				while( ns_it.hasNext()){
325 
326 					TOTorrentAnnounceURLSet[] ns = ns_it.next();
327 
328 					Iterator<TRTrackerAnnouncerHelper> a_it = existing_announcers.iterator();
329 
330 					while( a_it.hasNext()){
331 
332 						TRTrackerAnnouncerHelper a = a_it.next();
333 
334 						TOTorrentAnnounceURLSet[] os = a.getAnnounceSets();
335 
336 						if ( same( ns, os )){
337 
338 							ns_it.remove();
339 							a_it.remove();
340 
341 							new_announcers.add( a );
342 
343 							break;
344 						}
345 					}
346 				}
347 			}
348 
349 				// first remove dht ones from the equation
350 
351 			TRTrackerAnnouncerHelper 	existing_dht_announcer 	= null;
352 			TOTorrentAnnounceURLSet[]	new_dht_set				= null;
353 
354 			ns_it = new_sets.iterator();
355 
356 			while( ns_it.hasNext()){
357 
358 				TOTorrentAnnounceURLSet[] x = ns_it.next();
359 
360 				if ( TorrentUtils.isDecentralised( x[0].getAnnounceURLs()[0])){
361 
362 					new_dht_set = x;
363 
364 					ns_it.remove();
365 
366 					break;
367 				}
368 			}
369 
370 			Iterator<TRTrackerAnnouncerHelper>	an_it = existing_announcers.iterator();
371 
372 			while( an_it.hasNext()){
373 
374 				TRTrackerAnnouncerHelper a = an_it.next();
375 
376 				TOTorrentAnnounceURLSet[] x = a.getAnnounceSets();
377 
378 				if ( TorrentUtils.isDecentralised( x[0].getAnnounceURLs()[0])){
379 
380 					existing_dht_announcer = a;
381 
382 					an_it.remove();
383 
384 					break;
385 				}
386 			}
387 
388 			if ( existing_dht_announcer != null && new_dht_set != null ){
389 
390 				new_announcers.add( existing_dht_announcer );
391 
392 			}else if ( existing_dht_announcer != null ){
393 
394 				activated.remove( existing_dht_announcer );
395 
396 				existing_dht_announcer.destroy();
397 
398 			}else if ( new_dht_set != null ){
399 
400 				TRTrackerAnnouncerHelper a = create( torrent, networks, new_dht_set );
401 
402 				new_announcers.add( a );
403 			}
404 
405 				// create any new ones required
406 
407 			ns_it = new_sets.iterator();
408 
409 			while( ns_it.hasNext()){
410 
411 				TOTorrentAnnounceURLSet[] s = ns_it.next();
412 
413 				TRTrackerAnnouncerHelper a = create( torrent, networks, s );
414 
415 				new_announcers.add( a );
416 			}
417 
418 				// finally fix up the announcer list to represent the new state
419 
420 			Iterator<TRTrackerAnnouncerHelper>	a_it = announcers.iterator();
421 
422 			while( a_it.hasNext()){
423 
424 				TRTrackerAnnouncerHelper a = a_it.next();
425 
426 				if ( !new_announcers.contains( a )){
427 
428 					a_it.remove();
429 
430 					try{
431 						if ( 	activated.contains( a ) &&
432 								torrent.getPrivate() &&
433 								a instanceof TRTrackerBTAnnouncerImpl ){
434 
435 							URL url = a.getTrackerURL();
436 
437 							if ( url != null ){
438 
439 								forceStop((TRTrackerBTAnnouncerImpl)a, networks, url );
440 							}
441 						}
442 					}finally{
443 
444 						if (Logger.isEnabled()) {
445 							Logger.log(new LogEvent(getTorrent(), LOGID, "Deactivating " + getString( a.getAnnounceSets())));
446 						}
447 
448 						activated.remove( a );
449 
450 						a.destroy();
451 					}
452 				}
453 			}
454 
455 			a_it = new_announcers.iterator();
456 
457 			while( a_it.hasNext()){
458 
459 				TRTrackerAnnouncerHelper a = a_it.next();
460 
461 				if ( !announcers.contains( a )){
462 
463 					announcers.add( a );
464 				}
465 			}
466 
467 			if ( !is_manual && announcers.size() > 0 ){
468 
469 				if ( activated.size() == 0 ){
470 
471 					TRTrackerAnnouncerHelper a = announcers.get(0);
472 
473 					if (Logger.isEnabled()) {
474 						Logger.log(new LogEvent(getTorrent(), LOGID, "Activating " + getString( a.getAnnounceSets())));
475 					}
476 
477 					activated.add( a );
478 
479 					last_activation_time = SystemTime.getMonotonousTime();
480 
481 					if ( provider != null ){
482 
483 						to_activate = a;
484 					}
485 				}
486 
487 				setupActivationCheck( ACT_CHECK_INIT_DELAY );
488 			}
489 		}
490 
491 		if ( to_activate != null ){
492 
493 			if ( complete ){
494 
495 				to_activate.complete( true );
496 
497 			}else{
498 
499 				to_activate.update( false );
500 			}
501 		}
502 	}
503 
504 	protected void
setupActivationCheck( int delay )505 	setupActivationCheck(
506 		int		delay )
507 	{
508 		if ( announcers.size() > activated.size()){
509 
510 			event = SimpleTimer.addEvent(
511 				"TRMuxer:check",
512 				SystemTime.getOffsetTime( delay ),
513 				new TimerEventPerformer()
514 				{
515 					public void
516 					perform(
517 						TimerEvent event )
518 					{
519 						checkActivation( false );
520 					}
521 				});
522 		}
523 	}
524 
525 	protected void
checkActivation( boolean force )526 	checkActivation(
527 		boolean		force )
528 	{
529 		synchronized( this ){
530 
531 			int	next_check_delay;
532 
533 			if ( 	destroyed ||
534 					stopped ||
535 					announcers.size() <= activated.size()){
536 
537 				return;
538 			}
539 
540 			if ( provider == null ){
541 
542 				next_check_delay = ACT_CHECK_INIT_DELAY;
543 
544 			}else{
545 
546 				boolean	activate = force;
547 
548 				boolean	seeding = provider.getRemaining() == 0;
549 
550 				if ( seeding && activated.size() > 0 ){
551 
552 						// when seeding we only activate on tracker fail or major lack of connections
553 						// as normally we rely on downloaders rotating and finding us
554 
555 					int	connected	= provider.getConnectedConnectionCount();
556 
557 					if ( connected < 1 ){
558 
559 						activate = SystemTime.getMonotonousTime() - last_activation_time >= 60*1000;
560 
561 						next_check_delay = ACT_CHECK_SEEDING_SHORT_DELAY;
562 
563 					}else if ( connected < 3 ){
564 
565 						next_check_delay = ACT_CHECK_SEEDING_LONG_DELAY;
566 
567 					}else{
568 
569 						next_check_delay = 0;
570 					}
571 				}else{
572 
573 					int	allowed		= provider.getMaxNewConnectionsAllowed("");	// -1 -> unlimited
574 					int	pending		= provider.getPendingConnectionCount();
575 					int	connected	= provider.getConnectedConnectionCount();
576 
577 					int	online = 0;
578 
579 					for ( TRTrackerAnnouncerHelper a: activated ){
580 
581 						TRTrackerAnnouncerResponse response = a.getLastResponse();
582 
583 						if ( 	response != null &&
584 								response.getStatus() == TRTrackerAnnouncerResponse.ST_ONLINE ){
585 
586 							online++;
587 						}
588 					}
589 
590 					/*
591 					System.out.println(
592 						"checkActivation: announcers=" + announcers.size() +
593 						", active=" + activated.size() +
594 						", online=" + online +
595 						", allowed=" + allowed +
596 						", pending=" + pending +
597 						", connected=" + connected +
598 						", seeding=" + seeding );
599 					*/
600 
601 					if ( online == 0 ){
602 
603 						activate = true;
604 
605 							// no trackers online, start next and recheck soon
606 
607 						next_check_delay = ACT_CHECK_INIT_DELAY;
608 
609 					}else{
610 
611 						int	potential = connected + pending;
612 
613 						if ( potential < 10 ){
614 
615 								// minimal connectivity
616 
617 							activate = true;
618 
619 							next_check_delay = ACT_CHECK_INIT_DELAY;
620 
621 						}else if ( allowed < 0 || ( allowed >= 5 && pending < 3*allowed/4 )){
622 
623 								// not enough to fulfill our needs
624 
625 							activate = true;
626 
627 							next_check_delay = ACT_CHECK_INTERIM_DELAY;
628 
629 						}else{
630 								// things look good, recheck in a bit
631 
632 							next_check_delay = ACT_CHECK_IDLE_DELAY;
633 						}
634 					}
635 				}
636 
637 				if ( activate ){
638 
639 					for ( TRTrackerAnnouncerHelper a: announcers ){
640 
641 						if ( !activated.contains( a )){
642 
643 							if (Logger.isEnabled()) {
644 								Logger.log(new LogEvent(getTorrent(), LOGID, "Activating " + getString( a.getAnnounceSets())));
645 							}
646 
647 							activated.add( a );
648 
649 							last_activation_time = SystemTime.getMonotonousTime();
650 
651 							if ( complete ){
652 
653 								a.complete( true );
654 
655 							}else{
656 
657 								a.update( false );
658 							}
659 
660 							break;
661 						}
662 					}
663 				}
664 			}
665 
666 			if ( next_check_delay > 0 ){
667 
668 				setupActivationCheck( next_check_delay );
669 			}
670 		}
671 	}
672 
673 	private String
getString( TOTorrentAnnounceURLSet[] sets )674 	getString(
675 		TOTorrentAnnounceURLSet[]	sets )
676 	{
677 		StringBuffer str = new StringBuffer();
678 
679 		str.append( "[" );
680 
681 		int	num1 = 0;
682 
683 		for ( TOTorrentAnnounceURLSet s: sets ){
684 
685 			if ( num1++ > 0 ){
686 				str.append( ", ");
687 			}
688 
689 			str.append( "[" );
690 
691 			URL[]	urls = s.getAnnounceURLs();
692 
693 			int	num2 = 0;
694 
695 			for ( URL u: urls ){
696 
697 				if ( num2++ > 0 ){
698 					str.append( ", ");
699 				}
700 
701 				str.append( u.toExternalForm());
702 			}
703 
704 			str.append( "]" );
705 		}
706 
707 		str.append( "]" );
708 
709 		return( str.toString());
710 	}
711 
712 	private boolean
same( TOTorrentAnnounceURLSet[] s1, TOTorrentAnnounceURLSet[] s2 )713 	same(
714 		TOTorrentAnnounceURLSet[]	s1,
715 		TOTorrentAnnounceURLSet[]	s2 )
716 	{
717 		boolean	res = sameSupport( s1, s2 );
718 
719 		// System.out.println( "same->" + res + ": " + getString(s1) + "/" + getString(s2));
720 
721 		return( res );
722 	}
723 
724 	private boolean
sameSupport( TOTorrentAnnounceURLSet[] s1, TOTorrentAnnounceURLSet[] s2 )725 	sameSupport(
726 		TOTorrentAnnounceURLSet[]	s1,
727 		TOTorrentAnnounceURLSet[]	s2 )
728 	{
729 		if ( s1.length != s2.length ){
730 
731 			return( false );
732 		}
733 
734 		for (int i=0;i<s1.length;i++){
735 
736 			URL[] u1 = s1[i].getAnnounceURLs();
737 			URL[] u2 = s2[i].getAnnounceURLs();
738 
739 			if ( u1.length != u2.length ){
740 
741 				return( false );
742 			}
743 
744 			if ( u1.length == 1 ){
745 
746 				return( u1[0].toExternalForm().equals( u2[0].toExternalForm()));
747 			}
748 
749 			Set<String> set1 = new HashSet<String>();
750 
751 			for ( URL u: u1 ){
752 
753 				set1.add( u.toExternalForm());
754 			}
755 
756 			Set<String> set2 = new HashSet<String>();
757 
758 			for ( URL u: u2 ){
759 
760 				set2.add( u.toExternalForm());
761 			}
762 
763 			if ( !set1.equals( set2 )){
764 
765 				return( false );
766 			}
767 		}
768 
769 		return( true );
770 	}
771 
772 	protected void
forceStop( final TRTrackerBTAnnouncerImpl announcer, final String[] networks, final URL url )773 	forceStop(
774 		final TRTrackerBTAnnouncerImpl		announcer,
775 		final String[]						networks,
776 		final URL							url )
777 	{
778 		if (Logger.isEnabled()) {
779 			Logger.log(new LogEvent(getTorrent(), LOGID, "Force stopping " + url + " as private torrent" ));
780 		}
781 
782 		new AEThread2( "TRMux:fs", true )
783 		{
784 			public void
785 			run()
786 			{
787 				try{
788 					TRTrackerBTAnnouncerImpl an =
789 						new TRTrackerBTAnnouncerImpl( getTorrent(), new TOTorrentAnnounceURLSet[0], networks, true, getHelper());
790 
791 					an.cloneFrom( announcer );
792 
793 					an.setTrackerURL( url );
794 
795 					an.stop( false );
796 
797 					an.destroy();
798 
799 				}catch( Throwable e ){
800 
801 				}
802 			}
803 		}.start();
804 	}
805 
806 	private TRTrackerAnnouncerHelper
create( TOTorrent torrent, String[] networks, TOTorrentAnnounceURLSet[] sets )807 	create(
808 		TOTorrent						torrent,
809 		String[]						networks,
810 		TOTorrentAnnounceURLSet[]		sets )
811 
812 		throws TRTrackerAnnouncerException
813 	{
814 		TRTrackerAnnouncerHelper announcer;
815 
816 		boolean	decentralised;
817 
818 		if ( sets.length == 0 ){
819 
820 			decentralised = TorrentUtils.isDecentralised( torrent.getAnnounceURL());
821 
822 		}else{
823 
824 			decentralised = TorrentUtils.isDecentralised( sets[0].getAnnounceURLs()[0]);
825 		}
826 
827 		if ( decentralised ){
828 
829 			announcer	= new TRTrackerDHTAnnouncerImpl( torrent, networks, is_manual, getHelper());
830 
831 		}else{
832 
833 			announcer = new TRTrackerBTAnnouncerImpl( torrent, sets, networks, is_manual, getHelper());
834 		}
835 
836 		for ( TOTorrentAnnounceURLSet set: sets ){
837 
838 			URL[] urls = set.getAnnounceURLs();
839 
840 			for ( URL u: urls ){
841 
842 				String key = u.toExternalForm();
843 
844 				StatusSummary summary = recent_responses.get( key );
845 
846 				if ( summary == null ){
847 
848 					summary = new StatusSummary( announcer, u );
849 
850 					recent_responses.put( key, summary );
851 
852 				}else{
853 
854 					summary.setHelper( announcer );
855 				}
856 			}
857 		}
858 
859 		if ( provider != null ){
860 
861 			announcer.setAnnounceDataProvider( provider );
862 		}
863 
864 		if ( ip_override != null ){
865 
866 			announcer.setIPOverride( ip_override );
867 		}
868 
869 		return( announcer );
870 	}
871 
872 
873 	public TRTrackerAnnouncerResponse
getLastResponse()874 	getLastResponse()
875 	{
876 		TRTrackerAnnouncerResponse	result = null;
877 
878 		TRTrackerAnnouncerHelper best = getBestActive();
879 
880 		if ( best != null ){
881 
882 			result = best.getLastResponse();
883 		}
884 
885 		if ( result == null ){
886 
887 			result = last_response_informed;
888 		}
889 
890 		return( result );
891 	}
892 
893 
894 	@Override
895 	protected void
informResponse( TRTrackerAnnouncerHelper helper, TRTrackerAnnouncerResponse response )896 	informResponse(
897 		TRTrackerAnnouncerHelper		helper,
898 		TRTrackerAnnouncerResponse		response )
899 	{
900 		URL	url = response.getURL();
901 
902 			// can be null for external plugins (e.g. mldht...)
903 
904 		if ( url != null ){
905 
906 			synchronized( this ){
907 
908 				String key = url.toExternalForm();
909 
910 				StatusSummary summary = recent_responses.get( key );
911 
912 				if ( summary != null ){
913 
914 					summary.updateFrom( response );
915 				}
916 			}
917 		}
918 
919 		last_response_informed = response;
920 
921 			// force recalc of best active next time
922 
923 		last_best_active_set_time = 0;
924 
925 		super.informResponse( helper, response );
926 
927 		if ( response.getStatus() != TRTrackerAnnouncerResponse.ST_ONLINE ){
928 
929 			URL	u = response.getURL();
930 
931 			if ( u != null ){
932 
933 				String s = u.toExternalForm();
934 
935 				synchronized( failed_urls ){
936 
937 					if ( failed_urls.contains( s )){
938 
939 						return;
940 					}
941 
942 					failed_urls.add( s );
943 				}
944 			}
945 
946 			checkActivation( true );
947 		}
948 	}
949 
950 	public boolean
isManual()951 	isManual()
952 	{
953 		return( is_manual );
954 	}
955 
956 	public void
setAnnounceDataProvider( TRTrackerAnnouncerDataProvider _provider )957 	setAnnounceDataProvider(
958 		TRTrackerAnnouncerDataProvider		_provider )
959 	{
960 		List<TRTrackerAnnouncerHelper>	to_set;
961 
962 		synchronized( this ){
963 
964 			provider	= _provider;
965 
966 			to_set = announcers.getList();
967 		}
968 
969 		for ( TRTrackerAnnouncer announcer: to_set ){
970 
971 			announcer.setAnnounceDataProvider( provider );
972 		}
973 	}
974 
975 	protected TRTrackerAnnouncerHelper
getBestActive()976 	getBestActive()
977 	{
978 		long	now = SystemTime.getMonotonousTime();
979 
980 		if ( now - last_best_active_set_time < 1000 ){
981 
982 			return( last_best_active );
983 		}
984 
985 		last_best_active = getBestActiveSupport();
986 
987 		last_best_active_set_time = now;
988 
989 		return( last_best_active );
990 	}
991 
992 	protected TRTrackerAnnouncerHelper
getBestActiveSupport()993 	getBestActiveSupport()
994 	{
995 		List<TRTrackerAnnouncerHelper> x = announcers.getList();
996 
997 		TRTrackerAnnouncerHelper error_resp = null;
998 
999 		for ( TRTrackerAnnouncerHelper announcer: x ){
1000 
1001 			TRTrackerAnnouncerResponse response = announcer.getLastResponse();
1002 
1003 			if ( response != null ){
1004 
1005 				int	resp_status = response.getStatus();
1006 
1007 				if ( resp_status == TRTrackerAnnouncerResponse.ST_ONLINE ){
1008 
1009 					return( announcer );
1010 
1011 				}else if ( error_resp == null && resp_status == TRTrackerAnnouncerResponse.ST_REPORTED_ERROR ){
1012 
1013 					error_resp = announcer;
1014 				}
1015 			}
1016 		}
1017 
1018 		if ( error_resp != null ){
1019 
1020 			return( error_resp );
1021 		}
1022 
1023 		if ( x.size() > 0 ){
1024 
1025 			return( x.get(0));
1026 		}
1027 
1028 		return( null );
1029 	}
1030 
1031 	public URL
getTrackerURL()1032 	getTrackerURL()
1033 	{
1034 		TRTrackerAnnouncerHelper	active = getBestActive();
1035 
1036 		if ( active != null ){
1037 
1038 			return( active.getTrackerURL());
1039 		}
1040 
1041 		return( null );
1042 	}
1043 
1044 	public void
setTrackerURL( URL url )1045 	setTrackerURL(
1046 		URL		url )
1047 	{
1048 		List<List<String>> groups = new ArrayList<List<String>>();
1049 
1050 		List<String> group = new ArrayList<String>();
1051 
1052 		group.add( url.toExternalForm());
1053 
1054 		groups.add( group );
1055 
1056 		TorrentUtils.listToAnnounceGroups( groups, getTorrent());
1057 
1058 		resetTrackerUrl( false );
1059 	}
1060 
1061 	public void
resetTrackerUrl( boolean shuffle )1062 	resetTrackerUrl(
1063 		boolean	shuffle )
1064 	{
1065 		try{
1066 			split( false );
1067 
1068 		}catch( Throwable e ){
1069 
1070 			Debug.out( e );
1071 		}
1072 
1073 		for ( TRTrackerAnnouncer announcer: announcers ){
1074 
1075 			announcer.resetTrackerUrl( shuffle );
1076 		}
1077 	}
1078 
1079 	public void
setIPOverride( String override )1080 	setIPOverride(
1081 		String		override )
1082 	{
1083 		List<TRTrackerAnnouncerHelper>	to_set;
1084 
1085 		synchronized( this ){
1086 
1087 			to_set	= announcers.getList();
1088 
1089 			ip_override	= override;
1090 		}
1091 
1092 		for ( TRTrackerAnnouncer announcer: to_set ){
1093 
1094 			announcer.setIPOverride( override );
1095 		}
1096 	}
1097 
1098 	public void
clearIPOverride()1099 	clearIPOverride()
1100 	{
1101 		List<TRTrackerAnnouncerHelper>	to_clear;
1102 
1103 		synchronized( this ){
1104 
1105 			to_clear	= announcers.getList();
1106 
1107 			ip_override	= null;
1108 		}
1109 
1110 		for ( TRTrackerAnnouncer announcer: to_clear ){
1111 
1112 			announcer.clearIPOverride();
1113 		}
1114 	}
1115 
1116 	public void
setRefreshDelayOverrides( int percentage )1117 	setRefreshDelayOverrides(
1118 		int		percentage )
1119 	{
1120 		for ( TRTrackerAnnouncer announcer: announcers ){
1121 
1122 			announcer.setRefreshDelayOverrides( percentage );
1123 		}
1124 	}
1125 
1126 	public int
getTimeUntilNextUpdate()1127 	getTimeUntilNextUpdate()
1128 	{
1129 		TRTrackerAnnouncerHelper	active = getBestActive();
1130 
1131 		if ( active != null ){
1132 
1133 			return( active.getTimeUntilNextUpdate());
1134 		}
1135 
1136 		return( Integer.MAX_VALUE );
1137 	}
1138 
1139 	public int
getLastUpdateTime()1140 	getLastUpdateTime()
1141 	{
1142 		TRTrackerAnnouncerHelper	active = getBestActive();
1143 
1144 		if ( active != null ){
1145 
1146 			return( active.getLastUpdateTime());
1147 		}
1148 
1149 		return( 0 );
1150 	}
1151 
1152 	public void
update( boolean force )1153 	update(
1154 		boolean	force )
1155 	{
1156 		List<TRTrackerAnnouncerHelper> to_update;
1157 
1158 		synchronized( this ){
1159 
1160 			to_update = is_manual?announcers.getList():new ArrayList<TRTrackerAnnouncerHelper>( activated );
1161 		}
1162 
1163 		for ( TRTrackerAnnouncer announcer: to_update ){
1164 
1165 			announcer.update(force);
1166 		}
1167 	}
1168 
1169 	public void
complete( boolean already_reported )1170 	complete(
1171 		boolean	already_reported )
1172 	{
1173 		List<TRTrackerAnnouncerHelper> to_complete;
1174 
1175 		synchronized( this ){
1176 
1177 			complete	= true;
1178 
1179 			to_complete = is_manual?announcers.getList():new ArrayList<TRTrackerAnnouncerHelper>( activated );
1180 		}
1181 
1182 		for ( TRTrackerAnnouncer announcer: to_complete ){
1183 
1184 			announcer.complete( already_reported );
1185 		}
1186 	}
1187 
1188 	public void
stop( boolean for_queue )1189 	stop(
1190 		boolean	for_queue )
1191 	{
1192 		List<TRTrackerAnnouncerHelper> to_stop;
1193 
1194 		synchronized( this ){
1195 
1196 			stopped	= true;
1197 
1198 			to_stop = is_manual?announcers.getList():new ArrayList<TRTrackerAnnouncerHelper>( activated );
1199 
1200 			activated.clear();
1201 		}
1202 
1203 		for ( TRTrackerAnnouncer announcer: to_stop ){
1204 
1205 			announcer.stop( for_queue );
1206 		}
1207 	}
1208 
1209 	public void
destroy()1210 	destroy()
1211 	{
1212 		TRTrackerAnnouncerFactoryImpl.destroy( this );
1213 
1214 		List<TRTrackerAnnouncerHelper> to_destroy;
1215 
1216 		synchronized( this ){
1217 
1218 			destroyed = true;
1219 
1220 			to_destroy = announcers.getList();
1221 		}
1222 
1223 		for ( TRTrackerAnnouncer announcer: to_destroy ){
1224 
1225 			announcer.destroy();
1226 		}
1227 
1228 		TimerEvent	ev = event;
1229 
1230 		if ( ev != null ){
1231 
1232 			ev.cancel();
1233 		}
1234 	}
1235 
1236 	public int
getStatus()1237 	getStatus()
1238 	{
1239 		TRTrackerAnnouncer	max_announcer = getBestAnnouncer();
1240 
1241 		return( max_announcer==null?-1:max_announcer.getStatus());
1242 	}
1243 
1244 	public String
getStatusString()1245 	getStatusString()
1246 	{
1247 		TRTrackerAnnouncer	max_announcer = getBestAnnouncer();
1248 
1249 		return( max_announcer==null?"":max_announcer.getStatusString());
1250 	}
1251 
1252 	public TRTrackerAnnouncer
getBestAnnouncer()1253 	getBestAnnouncer()
1254 	{
1255 		int	max = -1;
1256 
1257 		TRTrackerAnnouncer	max_announcer = null;
1258 
1259 		for ( TRTrackerAnnouncer announcer: announcers ){
1260 
1261 			int	status = announcer.getStatus();
1262 
1263 			if ( status > max ){
1264 
1265 				max_announcer 	= announcer;
1266 				max				= status;
1267 			}
1268 		}
1269 
1270 		return( max_announcer==null?this:max_announcer );
1271 	}
1272 
1273 	public void
refreshListeners()1274 	refreshListeners()
1275 	{
1276 		informURLRefresh();
1277 	}
1278 
1279 	public void
setAnnounceResult( DownloadAnnounceResult result )1280 	setAnnounceResult(
1281 		DownloadAnnounceResult	result )
1282 	{
1283 			// this is only used for setting DHT results
1284 
1285 		for ( TRTrackerAnnouncer announcer: announcers ){
1286 
1287 			if ( announcer instanceof TRTrackerDHTAnnouncerImpl ){
1288 
1289 				announcer.setAnnounceResult( result );
1290 
1291 				return;
1292 			}
1293 		}
1294 
1295 			// TODO: we should always create a DHT entry and have it denote DHT tracking for all circustances
1296 			// have the DHT plugin set it to offline if disabled
1297 
1298 		List<TRTrackerAnnouncerHelper> x = announcers.getList();
1299 
1300 		if ( x.size() > 0 ){
1301 
1302 			x.get(0).setAnnounceResult( result );
1303 		}
1304 	}
1305 
1306 	protected int
getPeerCacheLimit()1307 	getPeerCacheLimit()
1308 	{
1309 		synchronized( this ){
1310 
1311 			if ( activated.size() < announcers.size()){
1312 
1313 				return( 0 );
1314 			}
1315 		}
1316 
1317 		if ( SystemTime.getMonotonousTime() - create_time < 15*1000 ){
1318 
1319 			return( 0 );
1320 		}
1321 
1322 		TRTrackerAnnouncer active = getBestActive();
1323 
1324 		if ( active != null && provider != null && active.getStatus() == TRTrackerAnnouncerResponse.ST_ONLINE ){
1325 
1326 			if ( 	provider.getMaxNewConnectionsAllowed( "" ) != 0 &&
1327 					provider.getPendingConnectionCount() == 0 ){
1328 
1329 				return( 5 );
1330 
1331 			}else{
1332 
1333 				return( 0 );
1334 			}
1335 		}
1336 
1337 		return( 10 );
1338 	}
1339 
1340 	public TrackerPeerSource
getTrackerPeerSource( final TOTorrentAnnounceURLSet set )1341 	getTrackerPeerSource(
1342 		final TOTorrentAnnounceURLSet		set )
1343 	{
1344 		URL[]	urls = set.getAnnounceURLs();
1345 
1346 		final String[] url_strs = new String[ urls.length ];
1347 
1348 		for ( int i=0;i<urls.length;i++ ){
1349 
1350 			url_strs[i] = urls[i].toExternalForm();
1351 		}
1352 
1353 		return(
1354 			new TrackerPeerSource()
1355 			{
1356 				private StatusSummary		_summary;
1357 				private boolean				enabled;
1358 				private long				fixup_time;
1359 
1360 				private StatusSummary
1361 				fixup()
1362 				{
1363 					long now = SystemTime.getMonotonousTime();
1364 
1365 					if ( now - fixup_time > 1000 ){
1366 
1367 						long			most_recent	= 0;
1368 						StatusSummary	summary	 	= null;
1369 
1370 						synchronized( TRTrackerAnnouncerMuxer.this ){
1371 
1372 							for ( String str: url_strs ){
1373 
1374 								StatusSummary s = recent_responses.get( str );
1375 
1376 								if ( s != null ){
1377 
1378 									if ( summary == null || s.getTime() > most_recent ){
1379 
1380 										summary		= s;
1381 										most_recent	= s.getTime();
1382 									}
1383 								}
1384 							}
1385 						}
1386 
1387 						if ( provider != null ){
1388 
1389 							enabled = provider.isPeerSourceEnabled( PEPeerSource.PS_BT_TRACKER );
1390 						}
1391 
1392 						if ( summary != null ){
1393 
1394 							_summary = summary;
1395 						}
1396 
1397 						fixup_time = now;
1398 					}
1399 
1400 					return( _summary );
1401 				}
1402 
1403 				public int
1404 				getType()
1405 				{
1406 					return( TrackerPeerSource.TP_TRACKER );
1407 				}
1408 
1409 				public String
1410 				getName()
1411 				{
1412 					StatusSummary summary = fixup();
1413 
1414 					if ( summary != null ){
1415 
1416 						String str =summary.getURL().toExternalForm();
1417 
1418 						int pos = str.indexOf( '?' );
1419 
1420 						if ( pos != -1 ){
1421 
1422 							str = str.substring( 0, pos );
1423 						}
1424 
1425 						return( str );
1426 					}
1427 
1428 					return( url_strs[0] );
1429 				}
1430 
1431 				public int
1432 				getStatus()
1433 				{
1434 					StatusSummary summary = fixup();
1435 
1436 					if ( !enabled ){
1437 
1438 						return( ST_DISABLED );
1439 					}
1440 
1441 					if ( summary != null ){
1442 
1443 						return( summary.getStatus());
1444 					}
1445 
1446 					return( ST_QUEUED );
1447 				}
1448 
1449 				public String
1450 				getStatusString()
1451 				{
1452 					StatusSummary summary = fixup();
1453 
1454 					if ( summary != null && enabled ){
1455 
1456 						return( summary.getStatusString());
1457 					}
1458 
1459 					return( null );
1460 				}
1461 
1462 				public int
1463 				getSeedCount()
1464 				{
1465 					StatusSummary summary = fixup();
1466 
1467 					if ( summary != null ){
1468 
1469 						return( summary.getSeedCount());
1470 					}
1471 
1472 					return( -1 );
1473 				}
1474 
1475 				public int
1476 				getLeecherCount()
1477 				{
1478 					StatusSummary summary = fixup();
1479 
1480 					if ( summary != null ){
1481 
1482 						return( summary.getLeecherCount());
1483 					}
1484 
1485 					return( -1 );
1486 				}
1487 
1488 				public int
1489 				getCompletedCount()
1490 				{
1491 					StatusSummary summary = fixup();
1492 
1493 					if ( summary != null ){
1494 
1495 						return( summary.getCompletedCount());
1496 					}
1497 
1498 					return( -1 );
1499 				}
1500 
1501 				public int
1502 				getPeers()
1503 				{
1504 					StatusSummary summary = fixup();
1505 
1506 					if ( summary != null ){
1507 
1508 						return( summary.getPeers());
1509 					}
1510 
1511 					return( -1 );
1512 				}
1513 
1514 				public int
1515 				getLastUpdate()
1516 				{
1517 					StatusSummary summary = fixup();
1518 
1519 					if ( summary != null ){
1520 
1521 						long time = summary.getTime();
1522 
1523 						if ( time == 0 ){
1524 
1525 							return( 0 );
1526 						}
1527 
1528 						long elapsed = SystemTime.getMonotonousTime() - time;
1529 
1530 						return((int)( (SystemTime.getCurrentTime() - elapsed ) / 1000 ));
1531 					}
1532 
1533 					return( 0 );
1534 				}
1535 
1536 				public int
1537 				getSecondsToUpdate()
1538 				{
1539 					StatusSummary summary = fixup();
1540 
1541 					if ( summary != null ){
1542 
1543 						return( summary.getSecondsToUpdate());
1544 					}
1545 
1546 					return( -1 );
1547 				}
1548 
1549 				public int
1550 				getInterval()
1551 				{
1552 					StatusSummary summary = fixup();
1553 
1554 					if ( summary != null ){
1555 
1556 						return( summary.getInterval());
1557 					}
1558 
1559 					return( -1 );
1560 				}
1561 
1562 				public int
1563 				getMinInterval()
1564 				{
1565 					StatusSummary summary = fixup();
1566 
1567 					if ( summary != null && enabled ){
1568 
1569 						return( summary.getMinInterval());
1570 					}
1571 
1572 					return( -1 );
1573 				}
1574 
1575 				public boolean
1576 				isUpdating()
1577 				{
1578 					StatusSummary summary = fixup();
1579 
1580 					if ( summary != null && enabled  ){
1581 
1582 						return( summary.isUpdating());
1583 					}
1584 
1585 					return( false );
1586 				}
1587 
1588 				public boolean
1589 				canManuallyUpdate()
1590 				{
1591 					StatusSummary summary = fixup();
1592 
1593 					if ( summary == null ){
1594 
1595 						return( false );
1596 					}
1597 
1598 					return( summary.canManuallyUpdate());
1599 				}
1600 
1601 				public void
1602 				manualUpdate()
1603 				{
1604 					StatusSummary summary = fixup();
1605 
1606 					if ( summary != null ){
1607 
1608 						summary.manualUpdate();
1609 					}
1610 				}
1611 
1612 				public boolean
1613 				canDelete()
1614 				{
1615 					return( false );
1616 				}
1617 
1618 				public void
1619 				delete()
1620 				{
1621 					Debug.out( "derp" );
1622 				}
1623 			});
1624 	}
1625 
1626 	public void
generateEvidence( IndentWriter writer )1627 	generateEvidence(
1628 		IndentWriter writer )
1629 	{
1630 		for ( TRTrackerAnnouncer announcer: announcers ){
1631 
1632 			announcer.generateEvidence(writer);
1633 		}
1634 	}
1635 
1636 	private static class
1637 	StatusSummary
1638 	{
1639 		private TRTrackerAnnouncerHelper		helper;
1640 
1641 		private long		time;
1642 		private URL			url;
1643 		private int			status;
1644 		private String		status_str;
1645 		private int			seeds		= -1;
1646 		private int			leechers	= -1;
1647 		private int			peers		= -1;
1648 		private int			completed	= -1;
1649 
1650 		private int			interval;
1651 		private int			min_interval;
1652 
1653 		protected
StatusSummary( TRTrackerAnnouncerHelper _helper, URL _url )1654 		StatusSummary(
1655 			TRTrackerAnnouncerHelper		_helper,
1656 			URL								_url )
1657 		{
1658 			helper	= _helper;
1659 			url		= _url;
1660 
1661 			status = TrackerPeerSource.ST_QUEUED;
1662 		}
1663 
1664 		protected void
setHelper( TRTrackerAnnouncerHelper _helper )1665 		setHelper(
1666 			TRTrackerAnnouncerHelper		_helper )
1667 		{
1668 			helper	= _helper;
1669 		}
1670 
1671 		protected void
updateFrom( TRTrackerAnnouncerResponse response )1672 		updateFrom(
1673 			TRTrackerAnnouncerResponse		response )
1674 		{
1675 			time	= SystemTime.getMonotonousTime();
1676 
1677 			int	state = response.getStatus();
1678 
1679 			if ( state == TRTrackerAnnouncerResponse.ST_ONLINE ){
1680 
1681 				status = TrackerPeerSource.ST_ONLINE;
1682 
1683 				seeds		= response.getScrapeCompleteCount();
1684 				leechers	= response.getScrapeIncompleteCount();
1685 				completed	= response.getScrapeDownloadedCount();
1686 				peers		= response.getPeers().length;
1687 
1688 			}else{
1689 
1690 				status = TrackerPeerSource.ST_ERROR;
1691 			}
1692 
1693 			status_str = response.getStatusString();
1694 
1695 			interval 		= (int)helper.getInterval();
1696 			min_interval 	= (int)helper.getMinInterval();
1697 		}
1698 
1699 		public long
getTime()1700 		getTime()
1701 		{
1702 			return( time );
1703 		}
1704 
1705 		public URL
getURL()1706 		getURL()
1707 		{
1708 			return( url );
1709 		}
1710 
1711 		public int
getStatus()1712 		getStatus()
1713 		{
1714 			return( status );
1715 		}
1716 
1717 		public String
getStatusString()1718 		getStatusString()
1719 		{
1720 			return( status_str );
1721 		}
1722 
1723 		public int
getSeedCount()1724 		getSeedCount()
1725 		{
1726 			return( seeds );
1727 		}
1728 
1729 		public int
getLeecherCount()1730 		getLeecherCount()
1731 		{
1732 			return( leechers );
1733 		}
1734 
1735 		public int
getCompletedCount()1736 		getCompletedCount()
1737 		{
1738 			return( completed );
1739 		}
1740 
1741 		public int
getPeers()1742 		getPeers()
1743 		{
1744 			return( peers );
1745 		}
1746 
1747 		public boolean
isUpdating()1748 		isUpdating()
1749 		{
1750 			return( helper.isUpdating());
1751 		}
1752 
1753 		public int
getInterval()1754 		getInterval()
1755 		{
1756 			return( interval );
1757 		}
1758 
1759 		public int
getMinInterval()1760 		getMinInterval()
1761 		{
1762 			return( min_interval );
1763 		}
1764 
1765 		public int
getSecondsToUpdate()1766 		getSecondsToUpdate()
1767 		{
1768 			return( helper.getTimeUntilNextUpdate());
1769 		}
1770 
1771 		public boolean
canManuallyUpdate()1772 		canManuallyUpdate()
1773 		{
1774 			return( ((SystemTime.getCurrentTime() / 1000 - helper.getLastUpdateTime() >= TRTrackerAnnouncer.REFRESH_MINIMUM_SECS)));
1775 		}
1776 
1777 		public void
manualUpdate()1778 		manualUpdate()
1779 		{
1780 			helper.update( true );
1781 		}
1782 	}
1783 }
1784