1 /*
2  * File    : TRHostImpl.java
3  * Created : 24-Oct-2003
4  * By      : parg
5  *
6  * Azureus - a Java Bittorrent client
7  *
8  * This program is free software; you can redistribute it and/or modify
9  * it under the terms of the GNU General Public License as published by
10  * the Free Software Foundation; either version 2 of the License, or
11  * (at your option) any later version.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  * GNU General Public License for more details ( see the LICENSE file ).
17  *
18  * You should have received a copy of the GNU General Public License
19  * along with this program; if not, write to the Free Software
20  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
21  */
22 
23 package org.gudy.azureus2.core3.tracker.host.impl;
24 
25 /**
26  * @author parg
27  */
28 
29 import java.util.*;
30 import java.io.*;
31 import java.net.*;
32 
33 import org.gudy.azureus2.core3.logging.*;
34 import org.gudy.azureus2.core3.config.*;
35 import org.gudy.azureus2.core3.util.*;
36 import org.gudy.azureus2.core3.tracker.host.*;
37 import org.gudy.azureus2.core3.tracker.server.*;
38 import org.gudy.azureus2.core3.tracker.util.TRTrackerUtils;
39 import org.gudy.azureus2.core3.tracker.client.*;
40 import org.gudy.azureus2.core3.torrent.*;
41 
42 import com.aelitis.azureus.core.util.CopyOnWriteList;
43 
44 public class
45 TRHostImpl
46 	implements 	TRHost, TRTrackerAnnouncerFactoryListener,
47 				TRTrackerServerListener2, TRTrackerServerListener,
48 				TRTrackerServerFactoryListener,
49 				TRTrackerServerRequestListener, TRTrackerServerAuthenticationListener
50 {
51 	private static final LogIDs LOGID = LogIDs.TRACKER;
52 	private static final int URL_DEFAULT_PORT		= 80;	// port to use if none in announce URL
53 	private static final int URL_DEFAULT_PORT_SSL	= 443;	// port to use if none in announce URL
54 
55 	public static final int STATS_PERIOD_SECS		= 60;
56 	private static final int TICK_PERIOD_SECS			= 10;
57 	private static final int TICKS_PER_STATS_PERIOD	= STATS_PERIOD_SECS/TICK_PERIOD_SECS;
58 
59 	private static TRHostImpl	singleton;
60 	private static AEMonitor 	class_mon 	= new AEMonitor( "TRHost:class" );
61 
62 	private TRHostConfigImpl		config;
63 
64 	private Hashtable				server_map 	= new Hashtable();
65 
66 	private List	host_torrents			= new ArrayList();
67 	private Map	host_torrent_hash_map	= new HashMap();
68 
69 	private Map	host_torrent_map		= new HashMap();
70 	private Map	tracker_client_map		= new HashMap();
71 
72 	private static final int LDT_TORRENT_ADDED			= 1;
73 	private static final int LDT_TORRENT_REMOVED		= 2;
74 	private static final int LDT_TORRENT_CHANGED		= 3;
75 
76 	private ListenerManager<TRHostListener>	listeners 	= ListenerManager.createAsyncManager(
77 		"TRHost:ListenDispatcher",
78 		new ListenerManagerDispatcher<TRHostListener>()
79 		{
80 			public void
81 			dispatch(
82 				TRHostListener	_listener,
83 				int				type,
84 				Object			value )
85 			{
86 				TRHostListener	target = (TRHostListener)_listener;
87 
88 				if ( type == LDT_TORRENT_ADDED ){
89 
90 					target.torrentAdded((TRHostTorrent)value);
91 
92 				}else if ( type == LDT_TORRENT_REMOVED ){
93 
94 					target.torrentRemoved((TRHostTorrent)value);
95 
96 				}else if ( type == LDT_TORRENT_CHANGED ){
97 
98 					target.torrentChanged((TRHostTorrent)value);
99 				}
100 			}
101 		});
102 
103 	private CopyOnWriteList<TRHostListener2>	listeners2 = new CopyOnWriteList<TRHostListener2>();
104 
105 	private static boolean host_add_announce_urls;
106 
107 	static{
108 		COConfigurationManager.addAndFireParameterListener(
109 				"Tracker Host Add Our Announce URLs",
110 				new ParameterListener()
111 				{
112 					public void
113 					parameterChanged(
114 						String name )
115 					{
116 						host_add_announce_urls = COConfigurationManager.getBooleanParameter( name );
117 					}
118 				});
119 	}
120 
121 	private List<TRHostAuthenticationListener>	auth_listeners		= new ArrayList<TRHostAuthenticationListener>();
122 
123 	private boolean	server_factory_listener_added;
124 
125 	protected AEMonitor this_mon 	= new AEMonitor( "TRHost" );
126 
127 	private volatile boolean	closed;
128 
129 	public static TRHost
create()130 	create()
131 	{
132 		try{
133 			class_mon.enter();
134 
135 			if ( singleton == null ){
136 
137 				singleton = new TRHostImpl();
138 			}
139 
140 			return( singleton );
141 
142 		}finally{
143 
144 			class_mon.exit();
145 		}
146 	}
147 
148 	protected
TRHostImpl()149 	TRHostImpl()
150 	{
151 			// we need to synchronize this so that the async (possible) establishment of
152 			// a server within the stats loop (to deal with public trackers with no locally
153 			// hosted torrents) doesn't get ahead of the reading of persisted torrents
154 			// If we allow the server to start early then it can potentially receive an
155 			// announce/scrape and result in the creation of an "external" torrent when
156 			// it should really be using an existing torrent
157 
158 		try{
159 			this_mon.enter();
160 
161 			config = new TRHostConfigImpl(this);
162 
163 			TRTrackerAnnouncerFactory.addListener( this );
164 
165 			Thread t = new AEThread("TRHost::stats.loop")
166 						{
167 							private int	tick_count = 0;
168 
169 							private Set	failed_ports = new HashSet();
170 
171 							public void
172 							runSupport()
173 							{
174 								while(true){
175 
176 									try{
177 
178 										URL[][]	url_sets = TRTrackerUtils.getAnnounceURLs();
179 
180 										for (int i=0;i<url_sets.length;i++){
181 
182 											URL[]	urls = url_sets[i];
183 
184 											for (int j=0;j<urls.length;j++){
185 
186 												URL	url = urls[j];
187 
188 												int port = url.getPort();
189 
190 												if ( port == -1 ){
191 
192 													port = url.getDefaultPort();
193 												}
194 
195 												String	protocol = url.getProtocol().toLowerCase();
196 
197 												try{
198 													if ( protocol.equals( "http" )){
199 
200 														startServer( TRTrackerServerFactory.PR_TCP, port, false );
201 
202 													}else if ( protocol.equals( "udp" )){
203 
204 														startServer( TRTrackerServerFactory.PR_UDP, port, false );
205 
206 													}else if ( protocol.equals( "https" )){
207 
208 														startServer( TRTrackerServerFactory.PR_TCP, port, true );
209 
210 													}else{
211 
212 														Debug.out( "Unknown protocol '" + protocol + "'" );
213 													}
214 
215 												}catch( Throwable e ){
216 
217 													Integer port_i = new Integer(port);
218 
219 													if ( !failed_ports.contains(port_i)){
220 
221 														failed_ports.add( port_i );
222 
223 														Logger.log(
224 																new LogEvent(LOGID,
225 																"Tracker Host: failed to start server", e));
226 													}
227 												}
228 											}
229 										}
230 
231 										Thread.sleep( TICK_PERIOD_SECS*1000 );
232 
233 										if ( closed ){
234 
235 											break;
236 										}
237 
238 										if ( tick_count % TICKS_PER_STATS_PERIOD == 0 ){
239 
240 											try{
241 												this_mon.enter();
242 
243 												for (int i=0;i<host_torrents.size();i++){
244 
245 													TRHostTorrent	ht = (TRHostTorrent)host_torrents.get(i);
246 
247 													if ( ht instanceof TRHostTorrentHostImpl ){
248 
249 														((TRHostTorrentHostImpl)ht).updateStats();
250 
251 													}else{
252 
253 														((TRHostTorrentPublishImpl)ht).updateStats();
254 
255 													}
256 												}
257 											}finally{
258 
259 												this_mon.exit();
260 											}
261 
262 											config.saveConfig( true );
263 
264 										}else{
265 
266 											config.saveConfig( false );
267 										}
268 
269 									}catch( InterruptedException e ){
270 
271 										Debug.printStackTrace( e );
272 
273 										break;
274 									}finally{
275 
276 										tick_count++;
277 									}
278 								}
279 							}
280 						};
281 
282 			t.setDaemon(true);
283 
284 				// try to ensure that the tracker stats are collected reasonably
285 				// regularly
286 
287 			t.setPriority( Thread.MAX_PRIORITY -1);
288 
289 			t.start();
290 
291 		}finally{
292 
293 			this_mon.exit();
294 		}
295 	}
296 
297 	public void
initialise( TRHostTorrentFinder finder )298 	initialise(
299 		TRHostTorrentFinder	finder )
300 	{
301 		config.loadConfig( finder );
302 	}
303 
304 	public String
getName()305 	getName()
306 	{
307 		return( TRTrackerServer.DEFAULT_NAME );
308 	}
309 
310 	public TRHostTorrent
hostTorrent( TOTorrent torrent, boolean persistent, boolean passive )311 	hostTorrent(
312 		TOTorrent		torrent,
313 		boolean			persistent,
314 		boolean			passive )
315 
316 		throws TRHostException
317 	{
318 		return( addTorrent( torrent, TRHostTorrent.TS_STARTED, persistent, passive, SystemTime.getCurrentTime() ));
319 	}
320 
321 	public TRHostTorrent
publishTorrent( TOTorrent torrent )322 	publishTorrent(
323 		TOTorrent		torrent )
324 
325 		throws TRHostException
326 	{
327 		return( addTorrent( torrent, TRHostTorrent.TS_PUBLISHED, true, false, SystemTime.getCurrentTime()));
328 	}
329 
330 	protected TRHostTorrent
addTorrent( TOTorrent torrent, int state, boolean persistent, boolean passive, long date_added )331 	addTorrent(
332 		TOTorrent		torrent,
333 		int				state,
334 		boolean			persistent,
335 		boolean			passive,
336 		long			date_added )
337 
338 		throws TRHostException
339 	{
340 		try{
341 			this_mon.enter();
342 
343 				// non-persistent additions should know what they're doing regarding
344 				// announce URL
345 
346 			if ( persistent && state != TRHostTorrent.TS_PUBLISHED ){
347 
348 				if ( host_add_announce_urls ){
349 
350 					addTrackerAnnounce( torrent );
351 				}
352 			}
353 
354 			TRHostTorrent	ht = lookupHostTorrent( torrent );
355 
356 			if ( ht != null ){
357 
358 				// check that this isn't the explicit publish/host of a torrent already there
359 				// as an external torrent. If so then just replace the torrent
360 
361 				try{
362 
363 					ht = lookupHostTorrentViaHash( torrent.getHash());
364 
365 					if ( ht instanceof TRHostTorrentHostImpl ){
366 
367 						TRHostTorrentHostImpl hti = (TRHostTorrentHostImpl)ht;
368 
369 						if ( hti.getTorrent() != torrent ){
370 
371 							hti.setTorrentInternal( torrent );
372 
373 							if ( persistent && !hti.isPersistent()){
374 
375 								hti.setPersistent( true );
376 							}
377 
378 							if ( passive && !hti.isPassive()){
379 
380 								hti.setPassive( true );
381 							}
382 
383 							if ( state != TRHostTorrent.TS_PUBLISHED ){
384 
385 								startHosting( hti );
386 
387 								if ( state == TRHostTorrent.TS_STARTED ){
388 
389 									hti.start();
390 								}
391 							}
392 
393 							listeners.dispatch( LDT_TORRENT_CHANGED, ht );
394 						}
395 					}
396 				}catch( TOTorrentException e ){
397 
398 					Debug.printStackTrace( e );
399 				}
400 
401 				return( ht );
402 			}
403 
404 			int		port;
405 			boolean	ssl;
406 			int		protocol	= TRTrackerServerFactory.PR_TCP;
407 
408 			if ( state == TRHostTorrent.TS_PUBLISHED ){
409 
410 				port = COConfigurationManager.getIntParameter("Tracker Port", TRHost.DEFAULT_PORT );
411 
412 				ssl	= false;
413 			}else{
414 
415 				URL	announce_url = torrent.getAnnounceURL();
416 
417 				String	protocol_str = announce_url.getProtocol();
418 
419 				ssl = protocol_str.equalsIgnoreCase("https");
420 
421 				if ( protocol_str.equalsIgnoreCase("udp")){
422 
423 					protocol = TRTrackerServerFactory.PR_UDP;
424 
425 				}else if ( TorrentUtils.isDecentralised( torrent )){
426 
427 					protocol = TRTrackerServerFactory.PR_DHT;
428 				}
429 
430 				boolean force_external = COConfigurationManager.getBooleanParameter("Tracker Port Force External");
431 
432 				port = announce_url.getPort();
433 
434 				if ( force_external ){
435 
436 					String 	tracker_ip 		= COConfigurationManager.getStringParameter("Tracker IP", "");
437 
438 					if ( 	tracker_ip.length() > 0 &&
439 							!announce_url.getHost().equalsIgnoreCase( tracker_ip )){
440 
441 						if ( ssl ){
442 
443 							port = COConfigurationManager.getIntParameter("Tracker Port SSL", TRHost.DEFAULT_PORT_SSL );
444 
445 						}else{
446 
447 							port = COConfigurationManager.getIntParameter("Tracker Port", TRHost.DEFAULT_PORT );
448 
449 						}
450 					}
451 				}
452 
453 				if ( port == -1 ){
454 
455 					port = ssl?URL_DEFAULT_PORT_SSL:URL_DEFAULT_PORT;
456 				}
457 			}
458 
459 			TRTrackerServer server = startServer( protocol, port, ssl );
460 
461 			TRHostTorrent host_torrent;
462 
463 			if ( state == TRHostTorrent.TS_PUBLISHED ){
464 
465 				TRHostTorrentPublishImpl new_torrent = new TRHostTorrentPublishImpl( this, torrent, date_added );
466 
467 				new_torrent.setPersistent( persistent );
468 
469 				host_torrent	= new_torrent;
470 			}else{
471 
472 				TRHostTorrentHostImpl	new_torrent = new TRHostTorrentHostImpl( this, server, torrent, port, date_added );
473 
474 				new_torrent.setPersistent( persistent );
475 
476 				new_torrent.setPassive( passive );
477 
478 				host_torrent	= new_torrent;
479 			}
480 
481 			host_torrents.add( host_torrent );
482 
483 			try{
484 				host_torrent_hash_map.put( new HashWrapper( torrent.getHash()), host_torrent );
485 
486 			}catch( TOTorrentException e ){
487 
488 				Debug.printStackTrace( e );
489 			}
490 
491 			host_torrent_map.put( torrent, host_torrent );
492 
493 			if ( state != TRHostTorrent.TS_PUBLISHED ){
494 
495 				startHosting((TRHostTorrentHostImpl)host_torrent );
496 
497 				if ( state == TRHostTorrent.TS_STARTED ){
498 
499 					host_torrent.start();
500 				}
501 
502 					// if not persistent, see if we can recover the stats
503 
504 				if ( !persistent ){
505 
506 					config.recoverStats( (TRHostTorrentHostImpl)host_torrent );
507 				}
508 			}
509 
510 			listeners.dispatch( LDT_TORRENT_ADDED, host_torrent );
511 
512 			config.saveRequired();
513 
514 			return( host_torrent );
515 
516 		}finally{
517 
518 			this_mon.exit();
519 		}
520 	}
521 
522 	protected void
torrentUpdated( TRHostTorrentHostImpl hti )523 	torrentUpdated(
524 		TRHostTorrentHostImpl hti )
525 	{
526 		int state = hti.getStatus();
527 
528 		if ( state != TRHostTorrent.TS_PUBLISHED ){
529 
530 			startHosting( hti );
531 
532 			if ( state == TRHostTorrent.TS_STARTED ){
533 
534 				hti.start();
535 			}
536 		}
537 
538 		listeners.dispatch( LDT_TORRENT_CHANGED, hti );
539 	}
540 
541 	public InetAddress
getBindIP()542 	getBindIP()
543 	{
544 		return( null );
545 	}
546 
547 	protected TRTrackerServer
startServer( int protocol, int port, boolean ssl )548 	startServer(
549 		int		protocol,
550 		int		port,
551 		boolean	ssl )
552 
553 		throws TRHostException
554 	{
555 		try{
556 			this_mon.enter();
557 
558 			String	key = ""+protocol+ ":" + port;
559 
560 			TRTrackerServer	server = (TRTrackerServer)server_map.get( key );
561 
562 			if ( server == null ){
563 
564 				try{
565 
566 					if ( ssl ){
567 
568 						server = TRTrackerServerFactory.createSSL( "tracker", protocol, port, true, true );
569 
570 					}else{
571 
572 						server = TRTrackerServerFactory.create( "tracker", protocol, port, true, true );
573 					}
574 
575 					server_map.put( key, server );
576 
577 					if ( auth_listeners.size() > 0 ){
578 
579 						server.addAuthenticationListener( this );
580 					}
581 
582 					server.addListener( this );
583 					server.addListener2( this );
584 
585 				}catch( TRTrackerServerException e ){
586 
587 					throw( new TRHostException( "startServer failed", e ));
588 				}
589 			}
590 
591 			return( server );
592 
593 		}finally{
594 
595 			this_mon.exit();
596 		}
597 	}
598 
599 	protected TRHostTorrent
lookupHostTorrent( TOTorrent torrent )600 	lookupHostTorrent(
601 		TOTorrent	torrent )
602 	{
603 	  if (torrent == null)
604 	    return null;
605 
606 		try{
607 			return((TRHostTorrent)host_torrent_hash_map.get( torrent.getHashWrapper()));
608 
609 		}catch( TOTorrentException e ){
610 
611 			Debug.printStackTrace( e );
612 		}
613 
614 		return( null );
615 	}
616 
617 	protected void
startHosting( TRHostTorrentHostImpl host_torrent )618 	startHosting(
619 		TRHostTorrentHostImpl	host_torrent )
620 	{
621 		TOTorrent	torrent = host_torrent.getTorrent();
622 
623 		TRTrackerAnnouncer tc = (TRTrackerAnnouncer)tracker_client_map.get( torrent );
624 
625 		if ( tc != null ){
626 
627 			startHosting( host_torrent, tc );
628 		}
629 	}
630 
631 	protected void
startHosting( TRTrackerAnnouncer tracker_client )632 	startHosting(
633 		TRTrackerAnnouncer	tracker_client )
634 	{
635 		TRHostTorrent	host_torrent = (TRHostTorrent)host_torrent_map.get( tracker_client.getTorrent());
636 
637 		if ( host_torrent instanceof TRHostTorrentHostImpl ){
638 
639 			startHosting( (TRHostTorrentHostImpl)host_torrent, tracker_client );
640 		}
641 	}
642 
643 	protected void
startHosting( TRHostTorrentHostImpl host_torrent, final TRTrackerAnnouncer tracker_client )644 	startHosting(
645 		TRHostTorrentHostImpl	host_torrent,
646 		final TRTrackerAnnouncer 	tracker_client )
647 	{
648 		final TOTorrent	torrent = host_torrent.getTorrent();
649 
650 			// set the ip override so that we announce ourselves to other peers via the
651 			// real external address, not the local one used to connect to the tracker
652 
653 		URL	announce = torrent.getAnnounceURL();
654 
655 		if ( host_add_announce_urls ){
656 
657 			tracker_client.setIPOverride( announce.getHost());
658 
659 		}else{
660 
661 				// prolly a backup tracker, we only want to override the IP if we're hosting it
662 
663 			if ( TRTrackerUtils.isHosting( announce )){
664 
665 				tracker_client.setIPOverride( announce.getHost());
666 
667 			}
668 		}
669 
670 			// hook into the client so that when the announce succeeds after the refresh below
671 			// we can force a rescrape to pick up the new status
672 
673 		TRTrackerAnnouncerListener	listener =
674 			new TRTrackerAnnouncerListener()
675 			{
676 				public void
677 				receivedTrackerResponse(
678 					TRTrackerAnnouncerResponse	response	)
679 				{
680 					try{
681 						TRTrackerScraperFactory.getSingleton().scrape( torrent, true );
682 
683 					}finally{
684 
685 						tracker_client.removeListener( this );
686 					}
687 				}
688 
689 				public void
690 				urlChanged(
691 					TRTrackerAnnouncer	announcer,
692 					URL					old_url,
693 					URL					new_url,
694 					boolean				explicit )
695 				{
696 				}
697 
698 				public void
699 				urlRefresh()
700 				{
701 				}
702 			};
703 
704 		tracker_client.addListener(listener);
705 
706 		tracker_client.refreshListeners();
707 	}
708 
709 	protected void
remove( TRHostTorrent host_torrent )710 	remove(
711 		TRHostTorrent	host_torrent )
712 	{
713 		try{
714 			this_mon.enter();
715 
716 			if ( !host_torrents.contains( host_torrent )){
717 
718 				return;
719 			}
720 
721 			host_torrents.remove( host_torrent );
722 
723 			TOTorrent	torrent = host_torrent.getTorrent();
724 
725 			try{
726 				host_torrent_hash_map.remove(new HashWrapper(torrent.getHash()));
727 
728 			}catch( TOTorrentException e ){
729 
730 				Debug.printStackTrace( e );
731 			}
732 
733 			host_torrent_map.remove( torrent );
734 
735 			if ( host_torrent instanceof TRHostTorrentHostImpl ){
736 
737 				stopHosting((TRHostTorrentHostImpl)host_torrent );
738 			}
739 
740 			listeners.dispatch( LDT_TORRENT_REMOVED, host_torrent );
741 
742 				// this'll get saved sometime soon anyway - performance problems
743 				// here when removing multiple torrents from a large set (e.g. 1000)
744 
745 			// config.saveConfig();
746 
747 		}finally{
748 
749 			this_mon.exit();
750 		}
751 	}
752 
753 	protected void
stopHosting( TRHostTorrentHostImpl host_torrent )754 	stopHosting(
755 		TRHostTorrentHostImpl	host_torrent )
756 	{
757 		TOTorrent	torrent = host_torrent.getTorrent();
758 
759 		TRTrackerAnnouncer tc = (TRTrackerAnnouncer)tracker_client_map.get( torrent );
760 
761 		if ( tc != null ){
762 
763 			stopHosting( host_torrent, tc );
764 		}
765 	}
766 
767 	protected void
stopHosting( TRTrackerAnnouncer tracker_client )768 	stopHosting(
769 		TRTrackerAnnouncer	tracker_client )
770 	{
771 		TRHostTorrent	host_torrent = (TRHostTorrent)host_torrent_map.get( tracker_client.getTorrent());
772 
773 		if ( host_torrent instanceof TRHostTorrentHostImpl ){
774 
775 				// we only "connect" the announcer and the hosted torrent if it isn't passive. This allows
776 				// us to make a torrent passive without losing the tracker stats by
777 				// 1) making it passive
778 				// 2) removing the Download
779 
780 			//if ( !host_torrent.isPassive()){
781 
782 				stopHosting( (TRHostTorrentHostImpl)host_torrent, tracker_client );
783 			//}
784 		}
785 	}
786 
787 	private AsyncDispatcher dispatcher = new AsyncDispatcher( "TRHost:stopHosting" );
788 
789 	protected void
stopHosting( final TRHostTorrentHostImpl host_torrent, final TRTrackerAnnouncer tracker_client )790 	stopHosting(
791 		final TRHostTorrentHostImpl		host_torrent,
792 		final TRTrackerAnnouncer 		tracker_client )
793 	{
794 			// unfortunately a lot of the "stop" operations that occur when a tracker client
795 			// connection is closed happen async. In particular the "stopped" message to the
796 			// tracker. Hence, if we switch the URL back here the "stopped" doesn't get
797 			// through.
798 
799 			// for the moment stick a delay in to allow any async stuff to complete
800 
801 		SimpleTimer.addEvent(
802 			"StopHosting",
803 			SystemTime.getOffsetTime( 2500 ),
804 			new TimerEventPerformer() {
805 
806 				public void
807 				perform(TimerEvent event)
808 				{
809 					dispatcher.dispatch(
810 						new AERunnable()
811 						{
812 							public void
813 							runSupport()
814 							{
815 								try{
816 									this_mon.enter();
817 
818 										// got to look up the host torrent again as may have been
819 										// removed and re-added
820 
821 									TRHostTorrent	ht = lookupHostTorrent( host_torrent.getTorrent());
822 
823 										// check it's still in stopped state and hasn't been restarted
824 
825 									if ( ht == null ||
826 											( 	ht == host_torrent &&
827 											 	ht.getStatus() == TRHostTorrent.TS_STOPPED )){
828 
829 										tracker_client.clearIPOverride();
830 									}
831 								}finally{
832 
833 									this_mon.exit();
834 								}
835 							}
836 						});
837 				}
838 			});
839 	}
840 
841 	protected TRTrackerAnnouncer
getTrackerClient( TRHostTorrent host_torrent )842 	getTrackerClient(
843 		TRHostTorrent host_torrent )
844 	{
845 		try{
846 			this_mon.enter();
847 
848 			return((TRTrackerAnnouncer)tracker_client_map.get( host_torrent.getTorrent()));
849 
850 		}finally{
851 
852 			this_mon.exit();
853 		}
854 	}
855 
856 	protected void
hostTorrentStateChange( TRHostTorrent host_torrent )857 	hostTorrentStateChange(
858 		TRHostTorrent host_torrent )
859 	{
860 		try{
861 			this_mon.enter();
862 
863 			TOTorrent	torrent = host_torrent.getTorrent();
864 
865 			TRTrackerAnnouncer tc = (TRTrackerAnnouncer)tracker_client_map.get( torrent );
866 
867 			if ( tc != null ){
868 
869 				tc.refreshListeners();
870 			}
871 
872 			// config will get saved soon anyway (periodic or on closedown) - perf issues
873 			// here with multiple torrent removal if we save each time
874 			// config.saveConfig();
875 
876 		}finally{
877 
878 			this_mon.exit();
879 		}
880 	}
881 
882 	public TRHostTorrent[]
getTorrents()883 	getTorrents()
884 	{
885 		try{
886 			this_mon.enter();
887 
888 			TRHostTorrent[]	res = new TRHostTorrent[host_torrents.size()];
889 
890 			host_torrents.toArray( res );
891 
892 			return( res );
893 
894 		}finally{
895 
896 			this_mon.exit();
897 		}
898 	}
899 
900 	public void
clientCreated( TRTrackerAnnouncer client )901 	clientCreated(
902 		TRTrackerAnnouncer		client )
903 	{
904 		try{
905 			this_mon.enter();
906 
907 			tracker_client_map.put( client.getTorrent(), client );
908 
909 			startHosting( client );
910 
911 		}finally{
912 
913 			this_mon.exit();
914 		}
915 	}
916 
917 	public void
clientDestroyed( TRTrackerAnnouncer client )918 	clientDestroyed(
919 		TRTrackerAnnouncer		client )
920 	{
921 		try{
922 			this_mon.enter();
923 
924 			tracker_client_map.remove( client.getTorrent());
925 
926 			stopHosting( client );
927 
928 		}finally{
929 
930 			this_mon.exit();
931 		}
932 	}
933 
934 	protected TRHostTorrent
lookupHostTorrentViaHash( byte[] hash )935 	lookupHostTorrentViaHash(
936 		byte[]		hash )
937 	{
938 		return((TRHostTorrent)host_torrent_hash_map.get(new HashWrapper(hash)));
939 	}
940 
941 		// reports from TRTrackerServer regarding state of hashes
942 		// if we get a "permitted" event for a torrent we know nothing about
943 		// the the server is allowing public hosting and this is a new hash
944 		// create an 'external' entry for it
945 
946 	public boolean
permitted( String originator, byte[] hash, boolean explicit )947 	permitted(
948 		String		originator,
949 		byte[]		hash,
950 		boolean		explicit  )
951 	{
952 		try{
953 			this_mon.enter();
954 
955 			TRHostTorrent ht = lookupHostTorrentViaHash( hash );
956 
957 			if ( ht != null ){
958 
959 				if ( !explicit ){
960 
961 					if ( ht.getStatus() != TRHostTorrent.TS_STARTED ){
962 
963 						return( false );
964 					}
965 				}
966 
967 				return( true );
968 			}
969 
970 			addExternalTorrent( hash, TRHostTorrent.TS_STARTED, SystemTime.getCurrentTime());
971 
972 			return( true );
973 
974 		}finally{
975 
976 			this_mon.exit();
977 		}
978 	}
979 
980 	protected void
addExternalTorrent( byte[] hash, int state, long date_added )981 	addExternalTorrent(
982 		byte[]		hash,
983 		int			state,
984 		long		date_added )
985 	{
986 		try{
987 			this_mon.enter();
988 
989 			if ( lookupHostTorrentViaHash( hash ) != null ){
990 
991 				return;
992 			}
993 
994 			String 	tracker_ip 		= COConfigurationManager.getStringParameter("Tracker IP", "127.0.0.1");
995 
996 				// external torrents don't care whether ssl or not so just assume non-ssl for simplicity
997 
998 			int port = COConfigurationManager.getIntParameter("Tracker Port", TRHost.DEFAULT_PORT );
999 
1000 			try{
1001 				TOTorrent	external_torrent = new TRHostExternalTorrent(hash, new URL( "http://" + UrlUtils.convertIPV6Host(tracker_ip) + ":" + port + "/announce"));
1002 
1003 				addTorrent( external_torrent, state, true, false, date_added );
1004 
1005 			}catch( Throwable e ){
1006 
1007 				Debug.printStackTrace( e );
1008 			}
1009 
1010 		}finally{
1011 
1012 			this_mon.exit();
1013 		}
1014 	}
1015 
1016 	public boolean
denied( byte[] hash, boolean permitted )1017 	denied(
1018 		byte[]		hash,
1019 		boolean		permitted )
1020 	{
1021 		return( true );
1022 	}
1023 
1024 	public boolean
handleExternalRequest( InetSocketAddress client_address, String user, String url, URL absolute_url, String header, InputStream is, OutputStream os, AsyncController async )1025 	handleExternalRequest(
1026 		InetSocketAddress	client_address,
1027 		String				user,
1028 		String				url,
1029 		URL					absolute_url,
1030 		String				header,
1031 		InputStream			is,
1032 		OutputStream		os,
1033 		AsyncController		async )
1034 
1035 		throws IOException
1036 	{
1037 		List<TRHostListener>	listeners_copy = listeners.getListenersCopy();
1038 
1039 		for (int i=0;i<listeners_copy.size();i++){
1040 
1041 			TRHostListener	listener = listeners_copy.get(i);
1042 
1043 			try{
1044 				if ( listener.handleExternalRequest( client_address, user, url, absolute_url, header, is, os, async )){
1045 
1046 					return( true );
1047 				}
1048 			}catch( Throwable e ){
1049 
1050 				Debug.out( e );
1051 			}
1052 		}
1053 
1054 		return( false );
1055 	}
1056 
1057 	public boolean
handleExternalRequest( ExternalRequest request )1058 	handleExternalRequest(
1059 		ExternalRequest 	request )
1060 
1061 			throws IOException
1062 	{
1063 		Iterator<TRHostListener2> it = listeners2.iterator();
1064 
1065 		while( it.hasNext()){
1066 
1067 			try{
1068 				if ( it.next().handleExternalRequest(request)){
1069 
1070 					return( true );
1071 				}
1072 			}catch( Throwable e ){
1073 
1074 				Debug.out( e );
1075 			}
1076 		}
1077 
1078 		return( false );
1079 	}
1080 
1081 	public TRHostTorrent
getHostTorrent( TOTorrent torrent )1082 	getHostTorrent(
1083 		TOTorrent		torrent )
1084 	{
1085 		return( lookupHostTorrent( torrent ));
1086 	}
1087 
1088 	/**
1089 	 * Add and fire listener for each torrent already hosted
1090 	 */
1091 	public void
addListener( TRHostListener l )1092 	addListener(
1093 		TRHostListener	l )
1094 	{
1095 		try{
1096 			this_mon.enter();
1097 
1098 			listeners.addListener( l );
1099 
1100 			for (int i=0;i<host_torrents.size();i++){
1101 
1102 				listeners.dispatch( l, LDT_TORRENT_ADDED, host_torrents.get(i));
1103 			}
1104 		}finally{
1105 
1106 			this_mon.exit();
1107 		}
1108 	}
1109 
1110 	public void
removeListener( TRHostListener l )1111 	removeListener(
1112 		TRHostListener	l )
1113 	{
1114 		listeners.removeListener( l );
1115 	}
1116 
1117 	public void
addListener2( TRHostListener2 l )1118 	addListener2(
1119 		TRHostListener2	l )
1120 	{
1121 		listeners2.add(l);
1122 	}
1123 
1124 	public void
removeListener2( TRHostListener2 l )1125 	removeListener2(
1126 		TRHostListener2	l )
1127 	{
1128 		listeners2.remove(l);
1129 	}
1130 
1131 	protected void
torrentListenerRegistered()1132 	torrentListenerRegistered()
1133 	{
1134 		try{
1135 			this_mon.enter();
1136 
1137 			if ( !server_factory_listener_added ){
1138 
1139 				server_factory_listener_added	= true;
1140 
1141 				TRTrackerServerFactory.addListener( this );
1142 			}
1143 		}finally{
1144 
1145 			this_mon.exit();
1146 		}
1147 	}
1148 
1149 	public void
serverCreated( TRTrackerServer server )1150 	serverCreated(
1151 		TRTrackerServer	server )
1152 	{
1153 		server.addRequestListener(this);
1154 	}
1155 
1156 	public void
serverDestroyed( TRTrackerServer server )1157 	serverDestroyed(
1158 		TRTrackerServer	server )
1159 	{
1160 		server.removeRequestListener(this);
1161 	}
1162 
1163 	public void
preProcess( TRTrackerServerRequest request )1164 	preProcess(
1165 		TRTrackerServerRequest	request )
1166 
1167 		throws TRTrackerServerException
1168 	{
1169 		if ( 	request.getType() 	== TRTrackerServerRequest.RT_ANNOUNCE  ||
1170 				request.getType() 	== TRTrackerServerRequest.RT_SCRAPE ){
1171 
1172 			TRTrackerServerTorrent ts_torrent = request.getTorrent();
1173 
1174 			HashWrapper	hash_wrapper = ts_torrent.getHash();
1175 
1176 			TRHostTorrent h_torrent = lookupHostTorrentViaHash( hash_wrapper.getHash());
1177 
1178 			if ( h_torrent != null ){
1179 
1180 				TRHostTorrentRequest	req = new TRHostTorrentRequestImpl( h_torrent, new TRHostPeerHostImpl(request.getPeer()), request );
1181 
1182 				try{
1183 					if ( h_torrent instanceof TRHostTorrentHostImpl ){
1184 
1185 						((TRHostTorrentHostImpl)h_torrent).preProcess( req );
1186 					}else{
1187 
1188 						((TRHostTorrentPublishImpl)h_torrent).preProcess( req );
1189 					}
1190 				}catch( TRHostException e ){
1191 
1192 					throw( new TRTrackerServerException( e.getMessage(), e ));
1193 
1194 				}catch( Throwable e ){
1195 
1196 					throw( new TRTrackerServerException( "Pre-process fails", e ));
1197 				}
1198 			}
1199 		}
1200 	}
1201 
1202 	public void
postProcess( TRTrackerServerRequest request )1203 	postProcess(
1204 		TRTrackerServerRequest	request )
1205 
1206 		throws TRTrackerServerException
1207 	{
1208 		if ( 	request.getType() 	== TRTrackerServerRequest.RT_ANNOUNCE  ||
1209 				request.getType() 	== TRTrackerServerRequest.RT_SCRAPE ){
1210 
1211 			TRTrackerServerTorrent ts_torrent = request.getTorrent();
1212 
1213 				// can be null for multi-hash scrapes... should fix this sometime I guess
1214 
1215 			if ( ts_torrent != null ){
1216 
1217 				HashWrapper	hash_wrapper = ts_torrent.getHash();
1218 
1219 				TRHostTorrent h_torrent = lookupHostTorrentViaHash( hash_wrapper.getHash());
1220 
1221 				if ( h_torrent != null ){
1222 
1223 					TRHostTorrentRequest	req = new TRHostTorrentRequestImpl( h_torrent, new TRHostPeerHostImpl(request.getPeer()), request );
1224 
1225 					try{
1226 						if ( h_torrent instanceof TRHostTorrentHostImpl ){
1227 
1228 							((TRHostTorrentHostImpl)h_torrent).postProcess( req );
1229 						}else{
1230 
1231 							((TRHostTorrentPublishImpl)h_torrent).postProcess( req );
1232 						}
1233 					}catch( TRHostException e ){
1234 
1235 						throw( new TRTrackerServerException( "Post process fails", e ));
1236 					}
1237 				}
1238 			}
1239 		}
1240 	}
1241 
1242 	public void
close()1243 	close()
1244 	{
1245 		closed	= true;
1246 
1247 		config.saveConfig( true );
1248 	}
1249 
1250 	public boolean
authenticate( String headers, URL resource, String user, String password )1251 	authenticate(
1252 		String		headers,
1253 		URL			resource,
1254 		String		user,
1255 		String		password )
1256 	{
1257 		for (int i=0;i<auth_listeners.size();i++){
1258 
1259 			try{
1260 				boolean res = auth_listeners.get(i).authenticate( headers, resource, user, password );
1261 
1262 				if ( res ){
1263 
1264 					return(true );
1265 				}
1266 			}catch( Throwable e ){
1267 
1268 				Debug.printStackTrace( e );
1269 			}
1270 		}
1271 
1272 		return( false );
1273 	}
1274 
1275 	public byte[]
authenticate( URL resource, String user )1276 	authenticate(
1277 		URL			resource,
1278 		String		user )
1279 	{
1280 		for (int i=0;i<auth_listeners.size();i++){
1281 
1282 			try{
1283 				byte[] res = auth_listeners.get(i).authenticate( resource, user );
1284 
1285 				if ( res != null ){
1286 
1287 					return( res );
1288 				}
1289 			}catch( Throwable e ){
1290 
1291 				Debug.printStackTrace( e );
1292 			}
1293 		}
1294 
1295 		return( null );
1296 	}
1297 
1298 	public void
addAuthenticationListener( TRHostAuthenticationListener l )1299 	addAuthenticationListener(
1300 		TRHostAuthenticationListener	l )
1301 	{
1302 		try{
1303 			this_mon.enter();
1304 
1305 			auth_listeners.add(l);
1306 
1307 			if ( auth_listeners.size() == 1 ){
1308 
1309 				Iterator it = server_map.values().iterator();
1310 
1311 				while( it.hasNext()){
1312 
1313 					((TRTrackerServer)it.next()).addAuthenticationListener( this );
1314 				}
1315 			}
1316 		}finally{
1317 
1318 			this_mon.exit();
1319 		}
1320 	}
1321 
1322 	public void
removeAuthenticationListener( TRHostAuthenticationListener l )1323 	removeAuthenticationListener(
1324 		TRHostAuthenticationListener	l )
1325 	{
1326 		try{
1327 			this_mon.enter();
1328 
1329 			auth_listeners.remove(l);
1330 
1331 			if ( auth_listeners.size() == 0 ){
1332 
1333 				Iterator it = server_map.values().iterator();
1334 
1335 				while( it.hasNext()){
1336 
1337 					((TRTrackerServer)it.next()).removeAuthenticationListener( this );
1338 				}
1339 			}
1340 		}finally{
1341 
1342 			this_mon.exit();
1343 		}
1344 	}
1345 
1346 		// see comment in TRHostTorrentHost impl for reason for this delegation + monitor
1347 		// aquisition
1348 
1349 	protected void
startTorrent( TRHostTorrentHostImpl torrent )1350 	startTorrent(
1351 		TRHostTorrentHostImpl	torrent )
1352 	{
1353 		try{
1354 			this_mon.enter();
1355 
1356 			torrent.startSupport();
1357 
1358 		}finally{
1359 
1360 			this_mon.exit();
1361 		}
1362 	}
1363 
1364 	protected void
stopTorrent( TRHostTorrentHostImpl torrent )1365 	stopTorrent(
1366 		TRHostTorrentHostImpl	torrent )
1367 	{
1368 		try{
1369 			this_mon.enter();
1370 
1371 			torrent.stopSupport();
1372 
1373 		}finally{
1374 
1375 			this_mon.exit();
1376 		}
1377 	}
1378 
1379 	protected void
addTrackerAnnounce( TOTorrent torrent )1380 	addTrackerAnnounce(
1381 		TOTorrent	torrent )
1382 	{
1383 		if ( TorrentUtils.isDecentralised( torrent )){
1384 
1385 			return;
1386 		}
1387 
1388 			// ensure that the tracker's announce details are in the torrent
1389 
1390 		URL[][]	url_sets = TRTrackerUtils.getAnnounceURLs();
1391 
1392 		if ( url_sets.length == 0 ){
1393 
1394 				// fall back to decentralised, no tracker defined
1395 
1396 			TorrentUtils.setDecentralised( torrent );
1397 
1398 		}else{
1399 
1400 			URL[]	primary_urls = url_sets[0];
1401 
1402 				// backwards so that they end up in right order
1403 
1404 			for (int i=primary_urls.length-1;i>=0;i--){
1405 
1406 				String	url_str = primary_urls[i].toString();
1407 
1408 				if ( TorrentUtils.announceGroupsContainsURL( torrent, url_str )){
1409 
1410 					TorrentUtils.announceGroupsSetFirst( torrent, url_str );
1411 
1412 				}else{
1413 
1414 					TorrentUtils.announceGroupsInsertFirst( torrent, url_str );
1415 				}
1416 			}
1417 		}
1418 	}
1419 }
1420