/* * File : TRHostImpl.java * Created : 24-Oct-2003 * By : parg * * Azureus - a Java Bittorrent client * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details ( see the LICENSE file ). * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package org.gudy.azureus2.core3.tracker.host.impl; /** * @author parg */ import java.util.*; import java.io.*; import java.net.*; import org.gudy.azureus2.core3.logging.*; import org.gudy.azureus2.core3.config.*; import org.gudy.azureus2.core3.util.*; import org.gudy.azureus2.core3.tracker.host.*; import org.gudy.azureus2.core3.tracker.server.*; import org.gudy.azureus2.core3.tracker.util.TRTrackerUtils; import org.gudy.azureus2.core3.tracker.client.*; import org.gudy.azureus2.core3.torrent.*; import com.aelitis.azureus.core.util.CopyOnWriteList; public class TRHostImpl implements TRHost, TRTrackerAnnouncerFactoryListener, TRTrackerServerListener2, TRTrackerServerListener, TRTrackerServerFactoryListener, TRTrackerServerRequestListener, TRTrackerServerAuthenticationListener { private static final LogIDs LOGID = LogIDs.TRACKER; private static final int URL_DEFAULT_PORT = 80; // port to use if none in announce URL private static final int URL_DEFAULT_PORT_SSL = 443; // port to use if none in announce URL public static final int STATS_PERIOD_SECS = 60; private static final int TICK_PERIOD_SECS = 10; private static final int TICKS_PER_STATS_PERIOD = STATS_PERIOD_SECS/TICK_PERIOD_SECS; private static TRHostImpl singleton; private static AEMonitor class_mon = new AEMonitor( "TRHost:class" ); private TRHostConfigImpl config; private Hashtable server_map = new Hashtable(); private List host_torrents = new ArrayList(); private Map host_torrent_hash_map = new HashMap(); private Map host_torrent_map = new HashMap(); private Map tracker_client_map = new HashMap(); private static final int LDT_TORRENT_ADDED = 1; private static final int LDT_TORRENT_REMOVED = 2; private static final int LDT_TORRENT_CHANGED = 3; private ListenerManager<TRHostListener> listeners = ListenerManager.createAsyncManager( "TRHost:ListenDispatcher", new ListenerManagerDispatcher<TRHostListener>() { public void dispatch( TRHostListener _listener, int type, Object value ) { TRHostListener target = (TRHostListener)_listener; if ( type == LDT_TORRENT_ADDED ){ target.torrentAdded((TRHostTorrent)value); }else if ( type == LDT_TORRENT_REMOVED ){ target.torrentRemoved((TRHostTorrent)value); }else if ( type == LDT_TORRENT_CHANGED ){ target.torrentChanged((TRHostTorrent)value); } } }); private CopyOnWriteList<TRHostListener2> listeners2 = new CopyOnWriteList<TRHostListener2>(); private static boolean host_add_announce_urls; static{ COConfigurationManager.addAndFireParameterListener( "Tracker Host Add Our Announce URLs", new ParameterListener() { public void parameterChanged( String name ) { host_add_announce_urls = COConfigurationManager.getBooleanParameter( name ); } }); } private List<TRHostAuthenticationListener> auth_listeners = new ArrayList<TRHostAuthenticationListener>(); private boolean server_factory_listener_added; protected AEMonitor this_mon = new AEMonitor( "TRHost" ); private volatile boolean closed; public static TRHost create() { try{ class_mon.enter(); if ( singleton == null ){ singleton = new TRHostImpl(); } return( singleton ); }finally{ class_mon.exit(); } } protected TRHostImpl() { // we need to synchronize this so that the async (possible) establishment of // a server within the stats loop (to deal with public trackers with no locally // hosted torrents) doesn't get ahead of the reading of persisted torrents // If we allow the server to start early then it can potentially receive an // announce/scrape and result in the creation of an "external" torrent when // it should really be using an existing torrent try{ this_mon.enter(); config = new TRHostConfigImpl(this); TRTrackerAnnouncerFactory.addListener( this ); Thread t = new AEThread("TRHost::stats.loop") { private int tick_count = 0; private Set failed_ports = new HashSet(); public void runSupport() { while(true){ try{ URL[][] url_sets = TRTrackerUtils.getAnnounceURLs(); for (int i=0;i<url_sets.length;i++){ URL[] urls = url_sets[i]; for (int j=0;j<urls.length;j++){ URL url = urls[j]; int port = url.getPort(); if ( port == -1 ){ port = url.getDefaultPort(); } String protocol = url.getProtocol().toLowerCase(); try{ if ( protocol.equals( "http" )){ startServer( TRTrackerServerFactory.PR_TCP, port, false ); }else if ( protocol.equals( "udp" )){ startServer( TRTrackerServerFactory.PR_UDP, port, false ); }else if ( protocol.equals( "https" )){ startServer( TRTrackerServerFactory.PR_TCP, port, true ); }else{ Debug.out( "Unknown protocol '" + protocol + "'" ); } }catch( Throwable e ){ Integer port_i = new Integer(port); if ( !failed_ports.contains(port_i)){ failed_ports.add( port_i ); Logger.log( new LogEvent(LOGID, "Tracker Host: failed to start server", e)); } } } } Thread.sleep( TICK_PERIOD_SECS*1000 ); if ( closed ){ break; } if ( tick_count % TICKS_PER_STATS_PERIOD == 0 ){ try{ this_mon.enter(); for (int i=0;i<host_torrents.size();i++){ TRHostTorrent ht = (TRHostTorrent)host_torrents.get(i); if ( ht instanceof TRHostTorrentHostImpl ){ ((TRHostTorrentHostImpl)ht).updateStats(); }else{ ((TRHostTorrentPublishImpl)ht).updateStats(); } } }finally{ this_mon.exit(); } config.saveConfig( true ); }else{ config.saveConfig( false ); } }catch( InterruptedException e ){ Debug.printStackTrace( e ); break; }finally{ tick_count++; } } } }; t.setDaemon(true); // try to ensure that the tracker stats are collected reasonably // regularly t.setPriority( Thread.MAX_PRIORITY -1); t.start(); }finally{ this_mon.exit(); } } public void initialise( TRHostTorrentFinder finder ) { config.loadConfig( finder ); } public String getName() { return( TRTrackerServer.DEFAULT_NAME ); } public TRHostTorrent hostTorrent( TOTorrent torrent, boolean persistent, boolean passive ) throws TRHostException { return( addTorrent( torrent, TRHostTorrent.TS_STARTED, persistent, passive, SystemTime.getCurrentTime() )); } public TRHostTorrent publishTorrent( TOTorrent torrent ) throws TRHostException { return( addTorrent( torrent, TRHostTorrent.TS_PUBLISHED, true, false, SystemTime.getCurrentTime())); } protected TRHostTorrent addTorrent( TOTorrent torrent, int state, boolean persistent, boolean passive, long date_added ) throws TRHostException { try{ this_mon.enter(); // non-persistent additions should know what they're doing regarding // announce URL if ( persistent && state != TRHostTorrent.TS_PUBLISHED ){ if ( host_add_announce_urls ){ addTrackerAnnounce( torrent ); } } TRHostTorrent ht = lookupHostTorrent( torrent ); if ( ht != null ){ // check that this isn't the explicit publish/host of a torrent already there // as an external torrent. If so then just replace the torrent try{ ht = lookupHostTorrentViaHash( torrent.getHash()); if ( ht instanceof TRHostTorrentHostImpl ){ TRHostTorrentHostImpl hti = (TRHostTorrentHostImpl)ht; if ( hti.getTorrent() != torrent ){ hti.setTorrent( torrent ); if ( persistent && !hti.isPersistent()){ hti.setPersistent( true ); } if ( passive && !hti.isPassive()){ hti.setPassive( true ); } if ( state != TRHostTorrent.TS_PUBLISHED ){ startHosting( hti ); if ( state == TRHostTorrent.TS_STARTED ){ hti.start(); } } listeners.dispatch( LDT_TORRENT_CHANGED, ht ); } } }catch( TOTorrentException e ){ Debug.printStackTrace( e ); } return( ht ); } int port; boolean ssl; int protocol = TRTrackerServerFactory.PR_TCP; if ( state == TRHostTorrent.TS_PUBLISHED ){ port = COConfigurationManager.getIntParameter("Tracker Port", TRHost.DEFAULT_PORT ); ssl = false; }else{ URL announce_url = torrent.getAnnounceURL(); String protocol_str = announce_url.getProtocol(); ssl = protocol_str.equalsIgnoreCase("https"); if ( protocol_str.equalsIgnoreCase("udp")){ protocol = TRTrackerServerFactory.PR_UDP; }else if ( TorrentUtils.isDecentralised( torrent )){ protocol = TRTrackerServerFactory.PR_DHT; } boolean force_external = COConfigurationManager.getBooleanParameter("Tracker Port Force External"); port = announce_url.getPort(); if ( force_external ){ String tracker_ip = COConfigurationManager.getStringParameter("Tracker IP", ""); if ( tracker_ip.length() > 0 && !announce_url.getHost().equalsIgnoreCase( tracker_ip )){ if ( ssl ){ port = COConfigurationManager.getIntParameter("Tracker Port SSL", TRHost.DEFAULT_PORT_SSL ); }else{ port = COConfigurationManager.getIntParameter("Tracker Port", TRHost.DEFAULT_PORT ); } } } if ( port == -1 ){ port = ssl?URL_DEFAULT_PORT_SSL:URL_DEFAULT_PORT; } } TRTrackerServer server = startServer( protocol, port, ssl ); TRHostTorrent host_torrent; if ( state == TRHostTorrent.TS_PUBLISHED ){ TRHostTorrentPublishImpl new_torrent = new TRHostTorrentPublishImpl( this, torrent, date_added ); new_torrent.setPersistent( persistent ); host_torrent = new_torrent; }else{ TRHostTorrentHostImpl new_torrent = new TRHostTorrentHostImpl( this, server, torrent, port, date_added ); new_torrent.setPersistent( persistent ); new_torrent.setPassive( passive ); host_torrent = new_torrent; } host_torrents.add( host_torrent ); try{ host_torrent_hash_map.put( new HashWrapper( torrent.getHash()), host_torrent ); }catch( TOTorrentException e ){ Debug.printStackTrace( e ); } host_torrent_map.put( torrent, host_torrent ); if ( state != TRHostTorrent.TS_PUBLISHED ){ startHosting((TRHostTorrentHostImpl)host_torrent ); if ( state == TRHostTorrent.TS_STARTED ){ host_torrent.start(); } // if not persistent, see if we can recover the stats if ( !persistent ){ config.recoverStats( (TRHostTorrentHostImpl)host_torrent ); } } listeners.dispatch( LDT_TORRENT_ADDED, host_torrent ); config.saveRequired(); return( host_torrent ); }finally{ this_mon.exit(); } } public InetAddress getBindIP() { return( null ); } protected TRTrackerServer startServer( int protocol, int port, boolean ssl ) throws TRHostException { try{ this_mon.enter(); String key = ""+protocol+ ":" + port; TRTrackerServer server = (TRTrackerServer)server_map.get( key ); if ( server == null ){ try{ if ( ssl ){ server = TRTrackerServerFactory.createSSL( "tracker", protocol, port, true, true ); }else{ server = TRTrackerServerFactory.create( "tracker", protocol, port, true, true ); } server_map.put( key, server ); if ( auth_listeners.size() > 0 ){ server.addAuthenticationListener( this ); } server.addListener( this ); server.addListener2( this ); }catch( TRTrackerServerException e ){ throw( new TRHostException( "startServer failed", e )); } } return( server ); }finally{ this_mon.exit(); } } protected TRHostTorrent lookupHostTorrent( TOTorrent torrent ) { if (torrent == null) return null; try{ return((TRHostTorrent)host_torrent_hash_map.get( torrent.getHashWrapper())); }catch( TOTorrentException e ){ Debug.printStackTrace( e ); } return( null ); } protected void startHosting( TRHostTorrentHostImpl host_torrent ) { TOTorrent torrent = host_torrent.getTorrent(); TRTrackerAnnouncer tc = (TRTrackerAnnouncer)tracker_client_map.get( torrent ); if ( tc != null ){ startHosting( host_torrent, tc ); } } protected void startHosting( TRTrackerAnnouncer tracker_client ) { TRHostTorrent host_torrent = (TRHostTorrent)host_torrent_map.get( tracker_client.getTorrent()); if ( host_torrent instanceof TRHostTorrentHostImpl ){ startHosting( (TRHostTorrentHostImpl)host_torrent, tracker_client ); } } protected void startHosting( TRHostTorrentHostImpl host_torrent, final TRTrackerAnnouncer tracker_client ) { final TOTorrent torrent = host_torrent.getTorrent(); // set the ip override so that we announce ourselves to other peers via the // real external address, not the local one used to connect to the tracker URL announce = torrent.getAnnounceURL(); if ( host_add_announce_urls ){ tracker_client.setIPOverride( announce.getHost()); }else{ // prolly a backup tracker, we only want to override the IP if we're hosting it if ( TRTrackerUtils.isHosting( announce )){ tracker_client.setIPOverride( announce.getHost()); } } // hook into the client so that when the announce succeeds after the refresh below // we can force a rescrape to pick up the new status TRTrackerAnnouncerListener listener = new TRTrackerAnnouncerListener() { public void receivedTrackerResponse( TRTrackerAnnouncerResponse response ) { try{ TRTrackerScraperFactory.getSingleton().scrape( torrent, true ); }finally{ tracker_client.removeListener( this ); } } public void urlChanged( TRTrackerAnnouncer announcer, URL old_url, URL new_url, boolean explicit ) { } public void urlRefresh() { } }; tracker_client.addListener(listener); tracker_client.refreshListeners(); } protected void remove( TRHostTorrent host_torrent ) { try{ this_mon.enter(); if ( !host_torrents.contains( host_torrent )){ return; } host_torrents.remove( host_torrent ); TOTorrent torrent = host_torrent.getTorrent(); try{ host_torrent_hash_map.remove(new HashWrapper(torrent.getHash())); }catch( TOTorrentException e ){ Debug.printStackTrace( e ); } host_torrent_map.remove( torrent ); if ( host_torrent instanceof TRHostTorrentHostImpl ){ stopHosting((TRHostTorrentHostImpl)host_torrent ); } listeners.dispatch( LDT_TORRENT_REMOVED, host_torrent ); // this'll get saved sometime soon anyway - performance problems // here when removing multiple torrents from a large set (e.g. 1000) // config.saveConfig(); }finally{ this_mon.exit(); } } protected void stopHosting( TRHostTorrentHostImpl host_torrent ) { TOTorrent torrent = host_torrent.getTorrent(); TRTrackerAnnouncer tc = (TRTrackerAnnouncer)tracker_client_map.get( torrent ); if ( tc != null ){ stopHosting( host_torrent, tc ); } } protected void stopHosting( TRTrackerAnnouncer tracker_client ) { TRHostTorrent host_torrent = (TRHostTorrent)host_torrent_map.get( tracker_client.getTorrent()); if ( host_torrent instanceof TRHostTorrentHostImpl ){ // we only "connect" the announcer and the hosted torrent if it isn't passive. This allows // us to make a torrent passive without losing the tracker stats by // 1) making it passive // 2) removing the Download //if ( !host_torrent.isPassive()){ stopHosting( (TRHostTorrentHostImpl)host_torrent, tracker_client ); //} } } protected void stopHosting( final TRHostTorrentHostImpl host_torrent, final TRTrackerAnnouncer tracker_client ) { // unfortunately a lot of the "stop" operations that occur when a tracker client // connection is closed happen async. In particular the "stopped" message to the // tracker. Hence, if we switch the URL back here the "stopped" doesn't get // through. // for the moment stick a delay in to allow any async stuff to complete Thread thread = new AEThread("StopHosting") { public void runSupport() { try{ Thread.sleep(2500); }catch( InterruptedException e ){ } try{ this_mon.enter(); // got to look up the host torrent again as may have been // removed and re-added TRHostTorrent ht = lookupHostTorrent( host_torrent.getTorrent()); // check it's still in stopped state and hasn't been restarted if ( ht == null || ( ht == host_torrent && ht.getStatus() == TRHostTorrent.TS_STOPPED )){ tracker_client.clearIPOverride(); } }finally{ this_mon.exit(); } } }; thread.setDaemon(true); thread.start(); } protected TRTrackerAnnouncer getTrackerClient( TRHostTorrent host_torrent ) { try{ this_mon.enter(); return((TRTrackerAnnouncer)tracker_client_map.get( host_torrent.getTorrent())); }finally{ this_mon.exit(); } } protected void hostTorrentStateChange( TRHostTorrent host_torrent ) { try{ this_mon.enter(); TOTorrent torrent = host_torrent.getTorrent(); TRTrackerAnnouncer tc = (TRTrackerAnnouncer)tracker_client_map.get( torrent ); if ( tc != null ){ tc.refreshListeners(); } // config will get saved soon anyway (periodic or on closedown) - perf issues // here with multiple torrent removal if we save each time // config.saveConfig(); }finally{ this_mon.exit(); } } public TRHostTorrent[] getTorrents() { try{ this_mon.enter(); TRHostTorrent[] res = new TRHostTorrent[host_torrents.size()]; host_torrents.toArray( res ); return( res ); }finally{ this_mon.exit(); } } public void clientCreated( TRTrackerAnnouncer client ) { try{ this_mon.enter(); tracker_client_map.put( client.getTorrent(), client ); startHosting( client ); }finally{ this_mon.exit(); } } public void clientDestroyed( TRTrackerAnnouncer client ) { try{ this_mon.enter(); tracker_client_map.remove( client.getTorrent()); stopHosting( client ); }finally{ this_mon.exit(); } } protected TRHostTorrent lookupHostTorrentViaHash( byte[] hash ) { return((TRHostTorrent)host_torrent_hash_map.get(new HashWrapper(hash))); } // reports from TRTrackerServer regarding state of hashes // if we get a "permitted" event for a torrent we know nothing about // the the server is allowing public hosting and this is a new hash // create an 'external' entry for it public boolean permitted( String originator, byte[] hash, boolean explicit ) { try{ this_mon.enter(); TRHostTorrent ht = lookupHostTorrentViaHash( hash ); if ( ht != null ){ if ( !explicit ){ if ( ht.getStatus() != TRHostTorrent.TS_STARTED ){ return( false ); } } return( true ); } addExternalTorrent( hash, TRHostTorrent.TS_STARTED, SystemTime.getCurrentTime()); return( true ); }finally{ this_mon.exit(); } } protected void addExternalTorrent( byte[] hash, int state, long date_added ) { try{ this_mon.enter(); if ( lookupHostTorrentViaHash( hash ) != null ){ return; } String tracker_ip = COConfigurationManager.getStringParameter("Tracker IP", "127.0.0.1"); // external torrents don't care whether ssl or not so just assume non-ssl for simplicity int port = COConfigurationManager.getIntParameter("Tracker Port", TRHost.DEFAULT_PORT ); try{ TOTorrent external_torrent = new TRHostExternalTorrent(hash, new URL( "http://" + UrlUtils.convertIPV6Host(tracker_ip) + ":" + port + "/announce")); addTorrent( external_torrent, state, true, false, date_added ); }catch( Throwable e ){ Debug.printStackTrace( e ); } }finally{ this_mon.exit(); } } public boolean denied( byte[] hash, boolean permitted ) { return( true ); } public boolean handleExternalRequest( InetSocketAddress client_address, String user, String url, URL absolute_url, String header, InputStream is, OutputStream os, AsyncController async ) throws IOException { List<TRHostListener> listeners_copy = listeners.getListenersCopy(); for (int i=0;i<listeners_copy.size();i++){ TRHostListener listener = listeners_copy.get(i); try{ if ( listener.handleExternalRequest( client_address, user, url, absolute_url, header, is, os, async )){ return( true ); } }catch( Throwable e ){ Debug.out( e ); } } return( false ); } public boolean handleExternalRequest( ExternalRequest request ) throws IOException { Iterator<TRHostListener2> it = listeners2.iterator(); while( it.hasNext()){ try{ if ( it.next().handleExternalRequest(request)){ return( true ); } }catch( Throwable e ){ Debug.out( e ); } } return( false ); } public TRHostTorrent getHostTorrent( TOTorrent torrent ) { return( lookupHostTorrent( torrent )); } public void addListener( TRHostListener l ) { try{ this_mon.enter(); listeners.addListener( l ); for (int i=0;i<host_torrents.size();i++){ listeners.dispatch( l, LDT_TORRENT_ADDED, host_torrents.get(i)); } }finally{ this_mon.exit(); } } public void removeListener( TRHostListener l ) { listeners.removeListener( l ); } public void addListener2( TRHostListener2 l ) { listeners2.add(l); } public void removeListener2( TRHostListener2 l ) { listeners2.remove(l); } protected void torrentListenerRegistered() { try{ this_mon.enter(); if ( !server_factory_listener_added ){ server_factory_listener_added = true; TRTrackerServerFactory.addListener( this ); } }finally{ this_mon.exit(); } } public void serverCreated( TRTrackerServer server ) { server.addRequestListener(this); } public void serverDestroyed( TRTrackerServer server ) { server.removeRequestListener(this); } public void preProcess( TRTrackerServerRequest request ) throws TRTrackerServerException { if ( request.getType() == TRTrackerServerRequest.RT_ANNOUNCE || request.getType() == TRTrackerServerRequest.RT_SCRAPE ){ TRTrackerServerTorrent ts_torrent = request.getTorrent(); HashWrapper hash_wrapper = ts_torrent.getHash(); TRHostTorrent h_torrent = lookupHostTorrentViaHash( hash_wrapper.getHash()); if ( h_torrent != null ){ TRHostTorrentRequest req = new TRHostTorrentRequestImpl( h_torrent, new TRHostPeerHostImpl(request.getPeer()), request ); try{ if ( h_torrent instanceof TRHostTorrentHostImpl ){ ((TRHostTorrentHostImpl)h_torrent).preProcess( req ); }else{ ((TRHostTorrentPublishImpl)h_torrent).preProcess( req ); } }catch( TRHostException e ){ throw( new TRTrackerServerException( e.getMessage(), e )); }catch( Throwable e ){ throw( new TRTrackerServerException( "Pre-process fails", e )); } } } } public void postProcess( TRTrackerServerRequest request ) throws TRTrackerServerException { if ( request.getType() == TRTrackerServerRequest.RT_ANNOUNCE || request.getType() == TRTrackerServerRequest.RT_SCRAPE ){ TRTrackerServerTorrent ts_torrent = request.getTorrent(); // can be null for multi-hash scrapes... should fix this sometime I guess if ( ts_torrent != null ){ HashWrapper hash_wrapper = ts_torrent.getHash(); TRHostTorrent h_torrent = lookupHostTorrentViaHash( hash_wrapper.getHash()); if ( h_torrent != null ){ TRHostTorrentRequest req = new TRHostTorrentRequestImpl( h_torrent, new TRHostPeerHostImpl(request.getPeer()), request ); try{ if ( h_torrent instanceof TRHostTorrentHostImpl ){ ((TRHostTorrentHostImpl)h_torrent).postProcess( req ); }else{ ((TRHostTorrentPublishImpl)h_torrent).postProcess( req ); } }catch( TRHostException e ){ throw( new TRTrackerServerException( "Post process fails", e )); } } } } } public void close() { closed = true; config.saveConfig( true ); } public boolean authenticate( String headers, URL resource, String user, String password ) { for (int i=0;i<auth_listeners.size();i++){ try{ boolean res = auth_listeners.get(i).authenticate( headers, resource, user, password ); if ( res ){ return(true ); } }catch( Throwable e ){ Debug.printStackTrace( e ); } } return( false ); } public byte[] authenticate( URL resource, String user ) { for (int i=0;i<auth_listeners.size();i++){ try{ byte[] res = auth_listeners.get(i).authenticate( resource, user ); if ( res != null ){ return( res ); } }catch( Throwable e ){ Debug.printStackTrace( e ); } } return( null ); } public void addAuthenticationListener( TRHostAuthenticationListener l ) { try{ this_mon.enter(); auth_listeners.add(l); if ( auth_listeners.size() == 1 ){ Iterator it = server_map.values().iterator(); while( it.hasNext()){ ((TRTrackerServer)it.next()).addAuthenticationListener( this ); } } }finally{ this_mon.exit(); } } public void removeAuthenticationListener( TRHostAuthenticationListener l ) { try{ this_mon.enter(); auth_listeners.remove(l); if ( auth_listeners.size() == 0 ){ Iterator it = server_map.values().iterator(); while( it.hasNext()){ ((TRTrackerServer)it.next()).removeAuthenticationListener( this ); } } }finally{ this_mon.exit(); } } // see comment in TRHostTorrentHost impl for reason for this delegation + monitor // aquisition protected void startTorrent( TRHostTorrentHostImpl torrent ) { try{ this_mon.enter(); torrent.startSupport(); }finally{ this_mon.exit(); } } protected void stopTorrent( TRHostTorrentHostImpl torrent ) { try{ this_mon.enter(); torrent.stopSupport(); }finally{ this_mon.exit(); } } protected void addTrackerAnnounce( TOTorrent torrent ) { if ( TorrentUtils.isDecentralised( torrent )){ return; } // ensure that the tracker's announce details are in the torrent URL[][] url_sets = TRTrackerUtils.getAnnounceURLs(); if ( url_sets.length == 0 ){ // fall back to decentralised, no tracker defined TorrentUtils.setDecentralised( torrent ); }else{ URL[] primary_urls = url_sets[0]; // backwards so that they end up in right order for (int i=primary_urls.length-1;i>=0;i--){ String url_str = primary_urls[i].toString(); if ( TorrentUtils.announceGroupsContainsURL( torrent, url_str )){ TorrentUtils.announceGroupsSetFirst( torrent, url_str ); }else{ TorrentUtils.announceGroupsInsertFirst( torrent, url_str ); } } } } }