gt: 							if ( names == null ){
context: /*
   * Created on Jul 11, 2008
   * Created by Paul Gardner
   * 
   * Copyright 2008 Vuze, Inc.  All rights reserved.
   * 
   * 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; version 2 of the License only.
   * 
   * 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.
   * 
   * 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 com.aelitis.azureus.core.subs.impl;
  
  import java.io.*;
  import java.net.InetSocketAddress;
  import java.net.URL;
  import java.security.KeyPair;
  import java.util.*;
  import java.util.regex.Pattern;
  import java.util.zip.GZIPInputStream;
  import java.util.zip.GZIPOutputStream;
  
  import org.bouncycastle.util.encoders.Base64;
  import org.gudy.azureus2.core3.config.COConfigurationManager;
  import org.gudy.azureus2.core3.config.ParameterListener;
  import org.gudy.azureus2.core3.internat.MessageText;
  import org.gudy.azureus2.core3.torrent.TOTorrent;
  import org.gudy.azureus2.core3.torrent.TOTorrentFactory;
  import org.gudy.azureus2.core3.util.*;
  import org.gudy.azureus2.plugins.PluginException;
  import org.gudy.azureus2.plugins.utils.Utilities;
  import org.gudy.azureus2.plugins.PluginInterface;
  import org.gudy.azureus2.plugins.download.*;
  import org.gudy.azureus2.plugins.peers.PeerManager;
  import org.gudy.azureus2.plugins.torrent.Torrent;
  import org.gudy.azureus2.plugins.torrent.TorrentAttribute;
  import org.gudy.azureus2.plugins.torrent.TorrentManager;
  import org.gudy.azureus2.plugins.ui.UIManager;
  import org.gudy.azureus2.plugins.ui.UIManagerEvent;
  import org.gudy.azureus2.plugins.utils.DelayedTask;
  import org.gudy.azureus2.plugins.utils.StaticUtilities;
  import org.gudy.azureus2.plugins.utils.search.SearchException;
  import org.gudy.azureus2.plugins.utils.search.SearchInstance;
  import org.gudy.azureus2.plugins.utils.search.SearchObserver;
  import org.gudy.azureus2.plugins.utils.search.SearchProvider;
  import org.gudy.azureus2.plugins.utils.search.SearchResult;
  import org.gudy.azureus2.pluginsimpl.local.PluginCoreUtils;
  import org.gudy.azureus2.pluginsimpl.local.PluginInitializer;
  import org.gudy.azureus2.pluginsimpl.local.torrent.TorrentImpl;
  import org.gudy.azureus2.pluginsimpl.local.utils.UtilitiesImpl;
  
  import com.aelitis.azureus.core.AzureusCore;
  import com.aelitis.azureus.core.AzureusCoreRunningListener;
  import com.aelitis.azureus.core.AzureusCoreFactory;
  import com.aelitis.azureus.core.custom.Customization;
  import com.aelitis.azureus.core.custom.CustomizationManager;
  import com.aelitis.azureus.core.custom.CustomizationManagerFactory;
  import com.aelitis.azureus.core.dht.DHT;
  import com.aelitis.azureus.core.lws.LightWeightSeed;
  import com.aelitis.azureus.core.lws.LightWeightSeedManager;
  import com.aelitis.azureus.core.messenger.config.PlatformSubscriptionsMessenger;
  import com.aelitis.azureus.core.metasearch.Engine;
  import com.aelitis.azureus.core.metasearch.MetaSearchListener;
  import com.aelitis.azureus.core.metasearch.MetaSearchManagerFactory;
  import com.aelitis.azureus.core.metasearch.impl.web.WebEngine;
  import com.aelitis.azureus.core.metasearch.impl.web.rss.RSSEngine;
  import com.aelitis.azureus.core.security.CryptoECCUtils;
  import com.aelitis.azureus.core.subs.*;
  import com.aelitis.azureus.core.subs.SubscriptionUtils.SubscriptionDownloadDetails;
  import com.aelitis.azureus.core.tag.Tag;
  import com.aelitis.azureus.core.tag.TagManagerFactory;
  import com.aelitis.azureus.core.torrent.PlatformTorrentUtils;
  import com.aelitis.azureus.core.util.CopyOnWriteList;
  import com.aelitis.azureus.core.vuzefile.*;
  import com.aelitis.azureus.plugins.dht.*;
  import com.aelitis.azureus.plugins.magnet.MagnetPlugin;
  import com.aelitis.azureus.plugins.magnet.MagnetPluginProgressListener;
  import com.aelitis.azureus.util.ImportExportUtils;
  import com.aelitis.azureus.util.UrlFilter;
  import com.aelitis.net.magneturi.MagnetURIHandler;
  
  
  public class 
  SubscriptionManagerImpl 
  	implements SubscriptionManager, AEDiagnosticsEvidenceGenerator
  {	
  	private static final String	CONFIG_FILE = "subscriptions.config";
  	private static final String	LOGGER_NAME = "Subscriptions";
  
  	private static final String CONFIG_MAX_RESULTS 			= "subscriptions.max.non.deleted.results";
  	private static final String CONFIG_AUTO_START_DLS 		= "subscriptions.auto.start.downloads";
  	private static final String CONFIG_AUTO_START_MIN_MB 	= "subscriptions.auto.start.min.mb";
  	private static final String CONFIG_AUTO_START_MAX_MB 	= "subscriptions.auto.start.max.mb";
  	
  	private static final String	CONFIG_RSS_ENABLE			= "subscriptions.config.rss_enable";
  
  	private static final String	CONFIG_ENABLE_SEARCH			= "subscriptions.config.search_enable";
  	
  	private static final String	CONFIG_HIDE_SEARCH_TEMPLATES	= "subscriptions.config.hide_search_templates";
  	
  	private static final String	CONFIG_DL_SUBS_ENABLE		= "subscriptions.config.dl_subs_enable";
  
  	private static final int DELETE_UNUSED_AFTER_MILLIS = 2*7*24*60*60*1000;
  	
  	
  	private static SubscriptionManagerImpl		singleton;
  	private static boolean						pre_initialised;
  	
  	private static final int random_seed = RandomUtils.nextInt( 256 );
  	
  	public static void
  	preInitialise()
  	{
  		synchronized( SubscriptionManagerImpl.class ){
  			
  			if ( pre_initialised ){
  				
  				return;
  			}
  			
  			pre_initialised = true;
  		}
  		
  		VuzeFileHandler.getSingleton().addProcessor(
  			new VuzeFileProcessor()
  			{
  				public void
  				process(
  					VuzeFile[]		files,
  					int				expected_types )
  				{
  					for (int i=0;i<files.length;i++){
  						
  						VuzeFile	vf = files[i];
  						
  						VuzeFileComponent[] comps = vf.getComponents();
  						
  						for (int j=0;j<comps.length;j++){
  							
  							VuzeFileComponent comp = comps[j];
  							
  							int	type = comp.getType();
  							
  							if ( 	type == VuzeFileComponent.COMP_TYPE_SUBSCRIPTION ||
  									type == VuzeFileComponent.COMP_TYPE_SUBSCRIPTION_SINGLETON ){
  								
  								try{
  									Subscription subs = ((SubscriptionManagerImpl)getSingleton( false )).importSubscription(
  											type,
  											comp.getContent(),
  											( expected_types & 
  												( VuzeFileComponent.COMP_TYPE_SUBSCRIPTION | VuzeFileComponent.COMP_TYPE_SUBSCRIPTION_SINGLETON )) == 0 );
  									
  									comp.setProcessed();
  									
  									comp.setData( Subscription.VUZE_FILE_COMPONENT_SUBSCRIPTION_KEY, subs );
  									
  								}catch( Throwable e ){
  									
  									Debug.printStackTrace(e);
  								}
  							}
  						}
  					}
  				}
  			});		
  	}
  		
  	public static SubscriptionManager
  	getSingleton(
  		boolean		stand_alone )
  	{
  		preInitialise();
  		
  		synchronized( SubscriptionManagerImpl.class ){
  			
  			if ( singleton != null ){
  			
  				return( singleton );
  			}
  			
  			singleton = new SubscriptionManagerImpl( stand_alone );
  		}
  		
  			// saw deadlock here when adding core listener while synced on class - rework
  			// to avoid 
  		
  		if ( !stand_alone ){
  			
  			singleton.initialise();
  		}
  		
  		return( singleton );
  	}
  	
  	
  	private boolean		started;
  		
  	private static final int	TIMER_PERIOD		= 30*1000;
  	
  	private static final int	ASSOC_CHECK_PERIOD	= 5*60*1000;
  	private static final int	ASSOC_CHECK_TICKS	= ASSOC_CHECK_PERIOD/TIMER_PERIOD;
  	
  	private static final int	SERVER_PUB_CHECK_PERIOD	= 10*60*1000;
  	private static final int	SERVER_PUB_CHECK_TICKS	= SERVER_PUB_CHECK_PERIOD/TIMER_PERIOD;
  	
  	private static final int	TIDY_POT_ASSOC_PERIOD	= 30*60*1000;
  	private static final int	TIDY_POT_ASSOC_TICKS	= TIDY_POT_ASSOC_PERIOD/TIMER_PERIOD;
  
  	private static final int	SET_SELECTED_PERIOD		= 23*60*60*1000;
  	private static final int	SET_SELECTED_FIRST_TICK	= 3*60*1000 /TIMER_PERIOD;
  	private static final int	SET_SELECTED_TICKS		= SET_SELECTED_PERIOD/TIMER_PERIOD;
  
  	private static final Object	SP_LAST_ATTEMPTED	= new Object();
  	private static final Object	SP_CONSEC_FAIL		= new Object();
  	
  	private AzureusCore		azureus_core;
  	
  	private volatile DHTPlugin	dht_plugin;
  	
  	private List<SubscriptionImpl>		subscriptions	= new ArrayList<SubscriptionImpl>();
  	
  	private boolean	config_dirty;
  	
  	private static final int PUB_ASSOC_CONC_MAX				= 3;
  	private static final int PUB_SLEEPING_ASSOC_CONC_MAX	= 1;
  	
  	private int		publish_associations_active;
  	private boolean	publish_next_asyc_pending;
  	
  	private boolean publish_subscription_active;
  	
  	private TorrentAttribute		ta_subs_download;
  	private TorrentAttribute		ta_subs_download_rd;
  	private TorrentAttribute		ta_subscription_info;
  	private TorrentAttribute		ta_category;
  	
  	private boolean					periodic_lookup_in_progress;
  	private int						priority_lookup_pending;
  	
  	private CopyOnWriteList<SubscriptionManagerListener>			listeners = new CopyOnWriteList<SubscriptionManagerListener>();
  	
  	private SubscriptionSchedulerImpl	scheduler;
  	
  	private List<Object[]>				potential_associations	= new ArrayList<Object[]>();
  	private Map<HashWrapper,Object[]>	potential_associations2	= new HashMap<HashWrapper,Object[]>();
  	
  	private boolean					meta_search_listener_added;
  	
  	private Pattern					exclusion_pattern = Pattern.compile( "azdev[0-9]+\\.azureus\\.com" );
  	
  	private SubscriptionRSSFeed		rss_publisher;
  	
  	private AEDiagnosticsLogger		logger;
  	
  	
  	protected
  	SubscriptionManagerImpl(
  		boolean	stand_alone )
  	{
  		if ( !stand_alone ){
  			
  			loadConfig();
  	
  			AEDiagnostics.addEvidenceGenerator( this );
  			
  			CustomizationManager cust_man = CustomizationManagerFactory.getSingleton();
  			
  			Customization cust = cust_man.getActiveCustomization();
  			
  			if ( cust != null ){
  				
  				String cust_name 	= COConfigurationManager.getStringParameter( "subscriptions.custom.name", "" );
  				String cust_version = COConfigurationManager.getStringParameter( "subscriptions.custom.version", "0" );
  				
  				boolean	new_name 	= !cust_name.equals( cust.getName());
  				boolean	new_version = org.gudy.azureus2.core3.util.Constants.compareVersions( cust_version, cust.getVersion() ) < 0;
  				
  				if ( new_name || new_version ){
  
  					log( "Customization: checking templates for " + cust.getName() + "/" + cust.getVersion());
  					
  					try{
  						InputStream[] streams = cust.getResources( Customization.RT_SUBSCRIPTIONS );
  						
  						for (int i=0;i<streams.length;i++){
  							
  							InputStream is = streams[i];
  							
  							try{
  								VuzeFile vf = VuzeFileHandler.getSingleton().loadVuzeFile(is);
  								
  								if ( vf != null ){
  									
  									VuzeFileComponent[] comps = vf.getComponents();
  									
  									for (int j=0;j<comps.length;j++){
  										
  										VuzeFileComponent comp = comps[j];
  										
  										int type = comp.getType();
  										
  										if ( 	type == VuzeFileComponent.COMP_TYPE_SUBSCRIPTION ||
  												type == VuzeFileComponent.COMP_TYPE_SUBSCRIPTION_SINGLETON	){
  											
  											try{
  												importSubscription(
  														type,
  														comp.getContent(),
  														false );
  												
  												comp.setProcessed();
  												
  											}catch( Throwable e ){
  												
  												Debug.printStackTrace(e);
  											}
  										}
  									}
  								}
  							}finally{
  								
  								try{
  									is.close();
  									
  								}catch( Throwable e ){
  								}
  							}
  						}
  					}finally{
  						
  						COConfigurationManager.setParameter( "subscriptions.custom.name", cust.getName());
  						COConfigurationManager.setParameter( "subscriptions.custom.version", cust.getVersion());
  					}
  				}
  			}
  			
  			scheduler = new SubscriptionSchedulerImpl( this );
  		}
  	}
  	
  	protected void
  	initialise()
  	{
  		AzureusCoreFactory.addCoreRunningListener(new AzureusCoreRunningListener() {
  			public void azureusCoreRunning(final AzureusCore core) {
  				initWithCore(core);
  			}
  		});
  	}
  
  	protected void
  	initWithCore(
  		AzureusCore 	_core )
  	{
  		synchronized( this ){
  			
  			if ( started ){
  				
  				return;
  			}
  			
  			started	= true;
  		}
  
  		azureus_core = _core;
  		
  		final PluginInterface default_pi = PluginInitializer.getDefaultInterface();
  
  		rss_publisher = new SubscriptionRSSFeed( this, default_pi );
  		
  		TorrentManager  tm = default_pi.getTorrentManager();
  		
  		ta_subs_download 		= tm.getPluginAttribute( "azsubs.subs_dl" );
  		ta_subs_download_rd 	= tm.getPluginAttribute( "azsubs.subs_dl_rd" );
  		ta_subscription_info 	= tm.getPluginAttribute( "azsubs.subs_info" );
  		ta_category				= tm.getAttribute( TorrentAttribute.TA_CATEGORY );
  		
  		PluginInterface  dht_plugin_pi  = AzureusCoreFactory.getSingleton().getPluginManager().getPluginInterfaceByClass( DHTPlugin.class );
  				
  		if ( dht_plugin_pi != null ){
  			
  			dht_plugin = (DHTPlugin)dht_plugin_pi.getPlugin();
  
  			/*
  			if ( Constants.isCVSVersion()){
  				
  				addListener(
  						new SubscriptionManagerListener()
  						{
  							public void 
  							subscriptionAdded(
  								Subscription subscription ) 
  							{
  							}
  				
  							public void
  							subscriptionChanged(
  								Subscription		subscription )
  							{
  							}
  							
  							public void 
  							subscriptionRemoved(
  								Subscription subscription ) 
  							{
  							}
  							
  							public void 
  							associationsChanged(
  								byte[] hash )
  							{
  								System.out.println( "Subscriptions changed: " + ByteFormatter.encodeString( hash ));
  								
  								Subscription[] subs = getKnownSubscriptions( hash );
  							
  								for (int i=0;i<subs.length;i++){
  									
  									System.out.println( "    " + subs[i].getString());
  								}
  							}
  						});	
  			}
  			*/
  			
  			default_pi.getDownloadManager().addListener(
  				new DownloadManagerListener()
  				{
  					public void
  					downloadAdded(
  						Download	download )
  					{
  						Torrent	torrent = download.getTorrent();
  						
  						if ( torrent != null ){
  							
  							byte[]	hash = torrent.getHash();
  							
  							Object[] entry;
  							
  							synchronized( potential_associations2 ){
  								
  								entry = (Object[])potential_associations2.remove( new HashWrapper( hash ));
  							}
  							
  							if ( entry != null ){
  								
  								SubscriptionImpl[] subs = (SubscriptionImpl[])entry[0];
  								
  								String	subs_str = "";
  								for (int i=0;i<subs.length;i++){
  									subs_str += (i==0?"":",") + subs[i].getName();
  								}
  								
  								log( "Applying deferred asocciation for " + ByteFormatter.encodeString( hash ) + " -> " + subs_str );
  								
  								recordAssociationsSupport(
  									hash,
  									subs,
  									((Boolean)entry[1]).booleanValue());
  							}
  						}
  					}
  					
  					public void
  					downloadRemoved(
  						Download	download )
  					{	
  					}
  				},
  				false );
  			
  			TorrentUtils.addTorrentAttributeListener(
  				new TorrentUtils.torrentAttributeListener()
  				{
  					public void 
  					attributeSet(
  						TOTorrent 	torrent,
  						String 		attribute, 
  						Object 		value )
  					{
  						if ( attribute == TorrentUtils.TORRENT_AZ_PROP_OBTAINED_FROM ){
  							
  							try{
  								checkPotentialAssociations( torrent.getHash(), (String)value );
  								
  							}catch( Throwable e ){
  								
  								Debug.printStackTrace(e);
  							}
  						}
  					}
  				});
  				
  			DelayedTask delayed_task = UtilitiesImpl.addDelayedTask( "Subscriptions", 
  					new Runnable()
  					{
  						public void 
  						run() 
  						{
  							new AEThread2( "Subscriptions:delayInit", true )
  							{
  								public void
  								run()
  								{
  									asyncInit();
  								}
  							}.start();
  							
  						}
  						
  						protected void
  						asyncInit()
  						{
  							Download[] downloads = default_pi.getDownloadManager().getDownloads();
  									
  							for (int i=0;i<downloads.length;i++){
  								
  								Download download = downloads[i];
  								
  								if ( download.getBooleanAttribute( ta_subs_download )){
  									
  									Map	rd = download.getMapAttribute( ta_subs_download_rd );
  									
  									boolean	delete_it;
  									
  									if ( rd == null ){
  										
  										delete_it = true;
  										
  									}else{
  										
  										delete_it = !recoverSubscriptionUpdate( download, rd );
  									}
  									
  									if ( delete_it ){
  										
  										removeDownload( download, true );
  									}
  								}
  							}
  								
  							default_pi.getDownloadManager().addListener(
  								new DownloadManagerListener()
  								{
  									public void
  									downloadAdded(
  										final Download	download )
  									{
  											// if ever changed to handle non-persistent then you need to fix init deadlock
  											// potential with share-hoster plugin
  										
  										if ( download.isPersistent()){
  											
  											if ( !dht_plugin.isInitialising()){
  								
  													// if new download then we want to check out its subscription status 
  												
  												lookupAssociations( download.getMapAttribute( ta_subscription_info ) == null );
  												
  											}else{
  												
  												new AEThread2( "Subscriptions:delayInit", true )
  												{
  													public void
  													run()
  													{
  														lookupAssociations( download.getMapAttribute( ta_subscription_info ) == null );
  													}
  												}.start();
  											}
  										}
  									}
  									
  									public void
  									downloadRemoved(
  										Download	download )
  									{
  									}
  								},
  								false );
  							
  							for (int i=0;i<PUB_ASSOC_CONC_MAX;i++){
  							
  								if ( publishAssociations()){
  									
  									break;
  								}
  							}
  							
  							publishSubscriptions();
  							
  							COConfigurationManager.addParameterListener(
  									CONFIG_MAX_RESULTS,
  									new ParameterListener()
  									{
  										public void 
  										parameterChanged(
  											String	 name )
  										{
  											final int	max_results = COConfigurationManager.getIntParameter( CONFIG_MAX_RESULTS );
  											
  											new AEThread2( "Subs:max results changer", true )
  											{
  												public void
  												run()
  												{
  													checkMaxResults( max_results );
  												}
  											}.start();
  										}
  									});
  							
  							SimpleTimer.addPeriodicEvent(
  									"SubscriptionChecker",
  									TIMER_PERIOD,
  									new TimerEventPerformer()
  									{
  										private int	ticks;
  										
  										public void 
  										perform(
  											TimerEvent event )
  										{
  											ticks++;
  											
  											checkStuff( ticks );
  										}
  									});
  						}
  					});
  		
  			delayed_task.queue();
  		}
  		
  		if ( isSearchEnabled()){
  			
  			try{
  				default_pi.getUtilities().registerSearchProvider(
  					new SearchProvider()
  					{
  						private Map<Integer,Object>	properties = new HashMap<Integer, Object>();
  						
  						{
  							properties.put( PR_NAME, MessageText.getString( "ConfigView.section.Subscriptions" ));
  							
  							try{
  								URL url = 
  									MagnetURIHandler.getSingleton().registerResource(
  										new MagnetURIHandler.ResourceProvider()
  										{
  											public String
  											getUID()
  											{
  												return( SubscriptionManager.class.getName() + ".2" );
  											}
  											
  											public String
  											getFileType()
  											{
  												return( "png" );
  											}
  													
  											public byte[]
  											getData()
  											{
  												InputStream is = getClass().getClassLoader().getResourceAsStream( "com/aelitis/azureus/ui/images/subscription_icon_1616.png" );
  
  												if ( is == null ){
  													
  													return( null );
  												}
  												
  												try{
  													ByteArrayOutputStream	baos = new ByteArrayOutputStream();
  													
  													try{
  														byte[]	buffer = new byte[8192];
  														
  														while( true ){
  								
  															int	len = is.read( buffer );
  											
  															if ( len <= 0 ){
  																
  																break;
  															}
  									
  															baos.write( buffer, 0, len );
  														}
  													}finally{
  														
  														is.close();
  													}
  													
  													return( baos.toByteArray());
  													
  												}catch( Throwable e ){
  													
  													return( null );
  												}
  											}
  										});
  																		
  								properties.put( PR_ICON_URL, url.toExternalForm());
  								
  							}catch( Throwable e ){
  								
  								Debug.out( e );
  							}
  						}
  						
  						public SearchInstance
  						search(
  							Map<String,Object>	search_parameters,
  							SearchObserver		observer )
  						
  							throws SearchException
  						{		
  							try{
  								return( searchSubscriptions( search_parameters, observer ));
  								
  							}catch( Throwable e ){
  								
  								throw( new SearchException( "Search failed", e ));
  							}
  						}
  						
  						public Object
  						getProperty(
  							int			property )
  						{
  							return( properties.get( property ));
  						}
  						
  						public void
  						setProperty(
  							int			property,
  							Object		value )
  						{
  							properties.put( property, value );
  						}
  					});
  				
  			}catch( Throwable e ){
  				
  				Debug.out( "Failed to register search provider" );
  			}
  		}
  		
  		default_pi.getUtilities().registerJSONRPCServer(
  			new Utilities.JSONServer()
  			{
  				private List<String>	methods = new ArrayList<String>();
  				
  				{
  					methods.add( "vuze-subs-list" );
  				}
  				
  				public String
  				getName()
  				{
  					return( "Subscriptions" );
  				}
  				
  				public List<String> 
  				getSupportedMethods() 
  				{
  					return( methods );
  				}
  				
  				public Map 
  				call(
  					String 		method, 
  					Map		 	args )
  						
  					throws PluginException 
  				{
  					throw( new PluginException( "derp" ));
  				}
  			});
  	}
  
  	protected Object[]
  	getSearchTemplateVuzeFile(
  		SubscriptionImpl	sub )
  	{
  		try{
  			String subs_url_str = ((RSSEngine)sub.getEngine()).getSearchUrl( true );
  			
  			URL subs_url = new URL( subs_url_str );
  			
  			final byte[] vf_bytes = FileUtil.readInputStreamAsByteArray(subs_url.openConnection().getInputStream());
  
  			VuzeFile vf = VuzeFileHandler.getSingleton().loadVuzeFile( vf_bytes );
  			
  			if ( MetaSearchManagerFactory.getSingleton().isImportable( vf )){
  	
  				return( new Object[]{ vf, vf_bytes });
  			}
  		}catch( Throwable e ){
  			
  			Debug.out( e );
  		}
  		
  		return( null );
  	}
  	
  	public boolean 
  	isSearchTemplateImportable(
  		SubscriptionImpl	sub )
  	{
  		try{
  			String subs_url_str = ((RSSEngine)sub.getEngine()).getSearchUrl( true );
  			
  			URL subs_url = new URL( subs_url_str );
  			
  			final byte[] vf_bytes = FileUtil.readInputStreamAsByteArray(subs_url.openConnection().getInputStream());
  
  			VuzeFile vf = VuzeFileHandler.getSingleton().loadVuzeFile( vf_bytes );
  			
  			return( MetaSearchManagerFactory.getSingleton().isImportable( vf ));
  	
  		}catch( Throwable e ){
  			
  			Debug.out( e );
  		}
  		
  		return( false );
  	}
  	
  	public SearchInstance
  	searchSubscriptions(
  		Map<String,Object>		search_parameters,
  		final SearchObserver	observer )
  	
  		throws SearchException
  	{
  		final String	term = (String)search_parameters.get( SearchProvider.SP_SEARCH_TERM );
  		
  		final SearchInstance si = 
  			new SearchInstance()
  			{
  				public void
  				cancel()
  				{
  					Debug.out( "Cancelled" );
  				}
  			};
  			
  		if ( term == null ){
  		
  			try{
  				observer.complete();
  				
  			}catch( Throwable e ){
  				
  				Debug.out( e );
  			}
  		}else{
  		
  			new AEThread2( "Subscriptions:search", true )
  			{
  				public void
  				run()
  				{
  					final Set<String>	hashes = new HashSet<String>();
  					
  					searchMatcher	matcher = new searchMatcher( term );
  
  					try{				
  						List<SubscriptionResult>	matches = matchSubscriptionResults( matcher );
  							
  						for ( final SubscriptionResult result: matches ){
  							
  							final Map result_properties = result.toPropertyMap();
  							
  							byte[] hash = (byte[])result_properties.get( SearchResult.PR_HASH );
  														
  							if ( hash != null ){
  															
  								String hash_str = Base32.encode( hash );
  							
  								if ( hashes.contains( hash_str )){
  								
  									continue;
  								}
  							
  								hashes.add( hash_str );
  							}
  							
  							SearchResult search_result = 
  								new SearchResult()
  								{
  									public Object
  									getProperty(
  										int		property_name )
  									{
  										return( result_properties.get( property_name ));
  									}
  								};
  						
  							try{
  								observer.resultReceived( si, search_result );
  								
  							}catch( Throwable e ){
  								
  								Debug.out( e );
  							}
  						}
  												
  						Map<String,Object[]> template_matches = new HashMap<String, Object[]>();
  								
  						Engine[] engines = MetaSearchManagerFactory.getSingleton().getMetaSearch().getEngines( false, false );
  						
  						Map<Subscription,List<String>>	sub_dl_name_map = null;
  						
  						for ( Subscription sub: getSubscriptions( false )){
  							
  							if ( !sub.isSearchTemplate()){
  								
  								continue;
  							}
  							
  							String	sub_name = sub.getName(false);
  														
  							Engine sub_engine = sub.getEngine();
  							
  							if ( sub_engine.isActive() || !(sub_engine instanceof RSSEngine )){
  								
  								continue;
  							}
  							
  							int	pos = sub_name.indexOf( ":" );
  							
  							String t_name = sub_name.substring( pos+1 );
  							
  							pos	= t_name.indexOf( "(v" );
  							
  							int t_ver;
  							
  							if ( pos == -1 ){
  								
  								t_ver = 1;
  								
  							}else{
  								
  								String s = t_name.substring( pos+2, t_name.length()-1);
  								
  								t_name = t_name.substring( 0, pos );
  								
  								try{
  							
  									t_ver = Integer.parseInt(s);
  									
  								}catch( Throwable e ){
  									
  									t_ver = 1;
  								}
  							}
  							
  							t_name = t_name.trim();
  							
  							boolean skip = false;
  							
  							for ( Engine e: engines ){
  								
  								if ( e != sub_engine && e.sameLogicAs( sub_engine )){
  									
  									skip = true;
  									
  									break;
  								}
  								
  								if ( e.getName().equalsIgnoreCase( t_name )){
  									
  									if ( e.getVersion() >= t_ver ){
  										
  										skip = true;
  									}
  								}
  							}
  							
  							if ( skip ){
  								
  								continue;
  							}
  							
  							if ( sub_dl_name_map == null ){
  								
  								sub_dl_name_map = new HashMap<Subscription, List<String>>();
  								
  								SubscriptionDownloadDetails[] sdds = SubscriptionUtils.getAllCachedDownloadDetails( azureus_core );
  								
  								for ( SubscriptionDownloadDetails sdd: sdds ){
  									
  									String name = sdd.getDownload().getDisplayName();
  									
  									if ( matcher.matches( name )){
  										
  										Subscription[] x = sdd.getSubscriptions();
  										
  										for ( Subscription s: x ){
  											
  											List<String> g = sub_dl_name_map.get( s );
  											
  											if ( g == null ){
  												
  												g = new ArrayList<String>();
  												
  												sub_dl_name_map.put( s, g );
  											}
  											
  											g.add( name );
  										}
  									}
  								}
  							}
  							
  							List<String> names = sub_dl_name_map.get( sub );
  							
  