/*
* 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.minicastle.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 org.json.simple.JSONObject;
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 );
if ( names == null ){
continue;
}
String key = t_name.toLowerCase();
Object[] entry = template_matches.get( key );
if ( entry == null ){
entry = new Object[]{ sub, t_ver };
template_matches.put( key, entry );
}else{
if ( t_ver > (Integer)entry[1]){
entry[0] = sub;
entry[1] = t_ver;
}
}
}
List<SubscriptionImpl> interesting = new ArrayList<SubscriptionImpl>();
for ( Object[] entry: template_matches.values()){
interesting.add((SubscriptionImpl)entry[0]);
}
Collections.sort(
interesting,
new Comparator<Subscription>()
{
public int
compare(
Subscription o1,
Subscription o2)
{
long res = o2.getCachedPopularity() - o1.getCachedPopularity();
if ( res < 0 ){
return( -1 );
}else if ( res > 0 ){
return( 1 );
}else{
return( 0 );
}
}
});
int added = 0;
for ( final SubscriptionImpl sub: interesting ){
if ( added >= 3 ){
break;
}
try{
Object[] vf_entry = getSearchTemplateVuzeFile( sub );
if ( vf_entry != null ){
final byte[] vf_bytes = (byte[])vf_entry[1];
final URL url =
MagnetURIHandler.getSingleton().registerResource(
new MagnetURIHandler.ResourceProvider()
{
public String
getUID()
{
return( SubscriptionManager.class.getName() + ".sid." + sub.getID() );
}
public String
getFileType()
{
return( "vuze" );
}
public byte[]
getData()
{
return( vf_bytes );
}
});
SearchResult search_result =
new SearchResult()
{
public Object
getProperty(
int property_name )
{
if ( property_name == SearchResult.PR_NAME ){
return( sub.getName());
}else if ( property_name == SearchResult.PR_DOWNLOAD_LINK ||
property_name == SearchResult.PR_DOWNLOAD_BUTTON_LINK ){
return( url.toExternalForm());
}else if ( property_name == SearchResult.PR_PUB_DATE ){
return( new Date(sub.getAddTime()));
}else if ( property_name == SearchResult.PR_SIZE ){
return( 1024L );
}else if ( property_name == SearchResult.PR_SEED_COUNT ||
property_name == SearchResult.PR_VOTES ){
return((long)sub.getCachedPopularity());
}else if ( property_name == SearchResult.PR_RANK ){
return( 100L );
}
return( null );
}
};
added++;
try{
observer.resultReceived( si, search_result );
}catch( Throwable e ){
Debug.out( e );
}
}
}catch( Throwable e ){
Debug.out( e );
}
}
}catch( Throwable e ){
Debug.out( e );
}finally{
observer.complete();
}
}
}.start();
}
return( si );
}
private List<SubscriptionResult>
matchSubscriptionResults(
searchMatcher matcher )
{
List<SubscriptionResult> result = new ArrayList<SubscriptionResult>();
for ( Subscription sub: getSubscriptions( true )){
SubscriptionResult[] results = sub.getResults( false );
for ( SubscriptionResult r: results ){
Map properties = r.toPropertyMap();
String name = (String)properties.get( SearchResult.PR_NAME );
if ( name == null ){
continue;
}
if ( matcher.matches( name )){
result.add( r );
}
}
}
return( result );
}
protected void
checkMaxResults(
int max )
{
Subscription[] subs = getSubscriptions();
for (int i=0;i<subs.length;i++){
((SubscriptionHistoryImpl)subs[i].getHistory()).checkMaxResults( max );
}
}
public SubscriptionScheduler
getScheduler()
{
return( scheduler );
}
public boolean
isRSSPublishEnabled()
{
return( COConfigurationManager.getBooleanParameter( CONFIG_RSS_ENABLE, false ));
}
public void
setRSSPublishEnabled(
boolean enabled )
{
COConfigurationManager.setParameter( CONFIG_RSS_ENABLE, enabled );
}
public boolean
isSearchEnabled()
{
return( COConfigurationManager.getBooleanParameter( CONFIG_ENABLE_SEARCH, true ));
}
public void
setSearchEnabled(
boolean enabled )
{
COConfigurationManager.setParameter( CONFIG_ENABLE_SEARCH, enabled );
}
public boolean
hideSearchTemplates()
{
return( COConfigurationManager.getBooleanParameter( CONFIG_HIDE_SEARCH_TEMPLATES, true ));
}
public boolean
isSubsDownloadEnabled()
{
return( COConfigurationManager.getBooleanParameter( CONFIG_DL_SUBS_ENABLE, true ));
}
public void
setSubsDownloadEnabled(
boolean enabled )
{
COConfigurationManager.setParameter( CONFIG_DL_SUBS_ENABLE, enabled );
}
public String
getRSSLink()
{
return( rss_publisher.getFeedURL());
}
public Subscription
create(
String name,
boolean public_subs,
String json )
throws SubscriptionException
{
name = getUniqueName( name );
SubscriptionImpl subs = new SubscriptionImpl( this, name, public_subs, null, json, SubscriptionImpl.ADD_TYPE_CREATE );
log( "Created new subscription: " + subs.getString());
if ( subs.isPublic()){
updatePublicSubscription( subs );
}
return( addSubscription( subs ));
}
public Subscription
createSingletonRSS(
String name,
URL url,
int check_interval_mins )
throws SubscriptionException
{
return( createSingletonRSSSupport( name, url, true, check_interval_mins, SubscriptionImpl.ADD_TYPE_CREATE, true ));
}
protected SubscriptionImpl
lookupSingletonRSS(
String name,
URL url,
boolean is_public,
int check_interval_mins )
throws SubscriptionException
{
checkURL( url );
Map singleton_details = getSingletonMap(name, url, is_public, check_interval_mins);
byte[] sid = SubscriptionBodyImpl.deriveSingletonShortID(singleton_details);
return( getSubscriptionFromSID( sid ));
}
protected Subscription
createSingletonRSSSupport(
String name,
URL url,
boolean is_public,
int check_interval_mins,
int add_type,
boolean subscribe )
throws SubscriptionException
{
checkURL( url );
try{
Subscription existing = lookupSingletonRSS( name, url, is_public, check_interval_mins );
if ( existing != null ){
return( existing );
}
Engine engine = MetaSearchManagerFactory.getSingleton().getMetaSearch().createRSSEngine( name, url );
String json = SubscriptionImpl.getSkeletonJSON( engine, check_interval_mins );
Map singleton_details = getSingletonMap(name, url, is_public, check_interval_mins);
SubscriptionImpl subs = new SubscriptionImpl( this, name, is_public, singleton_details, json, add_type );
subs.setSubscribed( subscribe );
log( "Created new singleton subscription: " + subs.getString());
subs = addSubscription( subs );
return( subs );
}catch( SubscriptionException e ){
throw((SubscriptionException)e);
}catch( Throwable e ){
throw( new SubscriptionException( "Failed to create subscription", e ));
}
}
protected String
getUniqueName(
String name )
{
for ( int i=0;i<1024;i++){
String test_name = name + (i==0?"":(" (" + i + ")"));
if ( getSubscriptionFromName( test_name ) == null ){
return( test_name );
}
}
return( name );
}
protected Map
getSingletonMap(
String name,
URL url,
boolean is_public,
int check_interval_mins )
throws SubscriptionException
{
try{
Map singleton_details = new HashMap();
if ( url.getProtocol().equalsIgnoreCase( "vuze" )){
// hack to minimise encoded url length for our own urls
singleton_details.put( "key", url.toExternalForm().getBytes( Constants.BYTE_ENCODING ));
}else{
singleton_details.put( "key", url.toExternalForm().getBytes( "UTF-8" ));
}
String name2 = name.length() > 64?name.substring(0,64):name;
singleton_details.put( "name", name2 );
if ( check_interval_mins != SubscriptionHistoryImpl.DEFAULT_CHECK_INTERVAL_MINS ){
singleton_details.put( "ci", new Long( check_interval_mins ));
}
return( singleton_details );
}catch( Throwable e ){
throw( new SubscriptionException( "Failed to create subscription", e ));
}
}
protected SubscriptionImpl
createSingletonSubscription(
Map singleton_details,
int add_type,
boolean subscribe )
throws SubscriptionException
{
try{
String name = ImportExportUtils.importString( singleton_details, "name", "(Anonymous)" );
URL url = new URL( ImportExportUtils.importString( singleton_details, "key" ));
int check_interval_mins = (int)ImportExportUtils.importLong( singleton_details, "ci", SubscriptionHistoryImpl.DEFAULT_CHECK_INTERVAL_MINS );
// only defined type is singleton rss
SubscriptionImpl s = (SubscriptionImpl)createSingletonRSSSupport( name, url, true, check_interval_mins, add_type, subscribe );
return( s );
}catch( Throwable e ){
log( "Creation of singleton from " + singleton_details + " failed", e );
throw( new SubscriptionException( "Creation of singleton from " + singleton_details + " failed", e ));
}
}
public Subscription
createRSS(
String name,
URL url,
int check_interval_mins,
Map user_data )
throws SubscriptionException
{
checkURL( url );
try{
name = getUniqueName(name);
Engine engine = MetaSearchManagerFactory.getSingleton().getMetaSearch().createRSSEngine( name, url );
String json = SubscriptionImpl.getSkeletonJSON( engine, check_interval_mins );
// engine name may have been modified so re-read it for subscription default
SubscriptionImpl subs = new SubscriptionImpl( this, engine.getName(), engine.isPublic(), null, json, SubscriptionImpl.ADD_TYPE_CREATE );
if ( user_data != null ){
Iterator it = user_data.entrySet().iterator();
while( it.hasNext()){
Map.Entry entry = (Map.Entry)it.next();
subs.setUserData( entry.getKey(), entry.getValue());
}
}
log( "Created new subscription: " + subs.getString());
subs = addSubscription( subs );
if ( subs.isPublic()){
updatePublicSubscription( subs );
}
return( subs );
}catch( Throwable e ){
throw( new SubscriptionException( "Failed to create subscription", e ));
}
}
protected void
checkURL(
URL url )
throws SubscriptionException
{
if ( url.getHost().trim().length() == 0 ){
String protocol = url.getProtocol().toLowerCase();
if ( ! ( protocol.equals( "azplug" ) || protocol.equals( "file" ) || protocol.equals( "vuze" ))){
throw( new SubscriptionException( "Invalid URL '" + url + "'" ));
}
}
}
protected SubscriptionImpl
addSubscription(
SubscriptionImpl subs )
{
SubscriptionImpl existing;
synchronized( this ){
int index = Collections.binarySearch(subscriptions, subs, new Comparator<Subscription>() {
public int compare(Subscription arg0, Subscription arg1) {
return arg0.getID().compareTo(arg1.getID());
}
});
if (index < 0) {
existing = null;
index = -1 * index - 1; // best guess
subscriptions.add( index, subs );
saveConfig();
} else {
existing = (SubscriptionImpl) subscriptions.get(index);
}
}
if ( existing != null ){
log( "Attempted to add subscription when already present: " + subs.getString());
subs.destroy();
return( existing );
}
if ( subs.isMine()){
addMetaSearchListener();
}
if ( subs.getCachedPopularity() == -1 ){
try{
subs.getPopularity(
new SubscriptionPopularityListener()
{
public void
gotPopularity(
long popularity )
{
}
public void
failed(
SubscriptionException error )
{
}
});
}catch( Throwable e ){
log( "", e );
}
}
Iterator it = listeners.iterator();
while( it.hasNext()){
try{
((SubscriptionManagerListener)it.next()).subscriptionAdded( subs );
}catch( Throwable e ){
Debug.printStackTrace(e);
}
}
if ( subs.isSubscribed() && subs.isPublic()){
setSelected( subs );
}
if ( dht_plugin != null ){
new AEThread2( "Publish check", true )
{
public void
run()
{
publishSubscriptions();
}
}.start();
}
return( subs );
}
protected void
addMetaSearchListener()
{
synchronized( this ){
if ( meta_search_listener_added ){
return;
}
meta_search_listener_added = true;
}
MetaSearchManagerFactory.getSingleton().getMetaSearch().addListener(
new MetaSearchListener()
{
public void
engineAdded(
Engine engine )
{
}
public void
engineUpdated(
Engine engine )
{
synchronized( SubscriptionManagerImpl.this ){
for (int i=0;i<subscriptions.size();i++){
SubscriptionImpl subs = (SubscriptionImpl)subscriptions.get(i);
if ( subs.isMine()){
subs.engineUpdated( engine );
}
}
}
}
public void
engineRemoved(
Engine engine )
{
}
});
}
protected void
changeSubscription(
SubscriptionImpl subs )
{
if ( !subs.isRemoved()){
Iterator it = listeners.iterator();
while( it.hasNext()){
try{
((SubscriptionManagerListener)it.next()).subscriptionChanged( subs );
}catch( Throwable e ){
Debug.printStackTrace(e);
}
}
}
}
protected void
selectSubscription(
SubscriptionImpl subs )
{
if ( !subs.isRemoved()){
Iterator it = listeners.iterator();
while( it.hasNext()){
try{
((SubscriptionManagerListener)it.next()).subscriptionSelected( subs );
}catch( Throwable e ){
Debug.printStackTrace(e);
}
}
}
}
protected void
removeSubscription(
SubscriptionImpl subs )
{
synchronized( this ){
if ( subscriptions.remove( subs )){
saveConfig();
}else{
return;
}
}
try{
Engine engine = subs.getEngine( true );
if ( engine.getType() == Engine.ENGINE_TYPE_RSS ){
engine.delete();
log( "Removed engine " + engine.getName() + " due to subscription removal" );
}
}catch( Throwable e ){
log( "Failed to check for engine deletion", e );
}
Iterator<SubscriptionManagerListener> it = listeners.iterator();
while( it.hasNext()){
try{
it.next().subscriptionRemoved( subs );
}catch( Throwable e ){
Debug.printStackTrace(e);
}
}
try{
FileUtil.deleteResilientFile( getResultsFile( subs ));
File vuze_file = getVuzeFile( subs );
vuze_file.delete();
new File( vuze_file.getParent(), vuze_file.getName() + ".bak" ).delete();
}catch( Throwable e ){
log( "Failed to delete results/vuze file", e );
}
}
protected void
updatePublicSubscription(
SubscriptionImpl subs )
{
if ( subs.isSingleton()){
// never update singletons
subs.setServerPublished();
}else{
Long l_last_pub = (Long)subs.getUserData( SP_LAST_ATTEMPTED );
Long l_consec_fail = (Long)subs.getUserData( SP_CONSEC_FAIL );
if ( l_last_pub != null && l_consec_fail != null ){
long delay = SERVER_PUB_CHECK_PERIOD;
for (int i=0;i<l_consec_fail.longValue();i++){
delay <<= 1;
if ( delay > 24*60*60*1000 ){
break;
}
}
if ( l_last_pub.longValue() + delay > SystemTime.getMonotonousTime()){
return;
}
}
try{
File vf = getVuzeFile( subs );
byte[] bytes = FileUtil.readFileAsByteArray( vf );
byte[] encoded_subs = Base64.encode( bytes );
PlatformSubscriptionsMessenger.updateSubscription(
!subs.getServerPublished(),
subs.getName(false),
subs.getPublicKey(),
subs.getPrivateKey(),
subs.getShortID(),
subs.getVersion(),
new String( encoded_subs ));
subs.setUserData( SP_LAST_ATTEMPTED, null );
subs.setUserData( SP_CONSEC_FAIL, null );
subs.setServerPublished();
log( " Updated public subscription " + subs.getString());
}catch( Throwable e ){
log( " Failed to update public subscription " + subs.getString(), e );
subs.setUserData( SP_LAST_ATTEMPTED, new Long( SystemTime.getMonotonousTime()));
subs.setUserData( SP_CONSEC_FAIL, new Long( l_consec_fail==null?1:(l_consec_fail.longValue()+1)));
subs.setServerPublicationOutstanding();
}
}
}
protected void
checkSingletonPublish(
SubscriptionImpl subs )
throws SubscriptionException
{
if ( subs.getSingletonPublishAttempted()){
throw( new SubscriptionException( "Singleton publish already attempted" ));
}
subs.setSingletonPublishAttempted();
try{
File vf = getVuzeFile( subs );
byte[] bytes = FileUtil.readFileAsByteArray( vf );
byte[] encoded_subs = Base64.encode( bytes );
// use a transient key-pair as we won't have the private key in general
KeyPair kp = CryptoECCUtils.createKeys();
byte[] public_key = CryptoECCUtils.keyToRawdata( kp.getPublic());
byte[] private_key = CryptoECCUtils.keyToRawdata( kp.getPrivate());
PlatformSubscriptionsMessenger.updateSubscription(
true,
subs.getName(false),
public_key,
private_key,
subs.getShortID(),
1,
new String( encoded_subs ));
log( " created singleton public subscription " + subs.getString());
}catch( Throwable e ){
throw( new SubscriptionException( "Failed to publish singleton", e ));
}
}
protected void
checkServerPublications(
List subs )
{
for (int i=0;i<subs.size();i++){
SubscriptionImpl sub = (SubscriptionImpl)subs.get(i);
if ( sub.getServerPublicationOutstanding()){
updatePublicSubscription( sub );
}
}
}
protected void
checkStuff(
int ticks )
{
long now = SystemTime.getCurrentTime();
List<SubscriptionImpl> subs;
synchronized( this ){
subs = new ArrayList<SubscriptionImpl>( subscriptions );
}
SubscriptionImpl expired_subs = null;
for (int i=0;i<subs.size();i++){
SubscriptionImpl sub = subs.get( i );
if ( !( sub.isMine() || sub.isSubscribed())){
long age = now - sub.getAddTime();
if ( age > DELETE_UNUSED_AFTER_MILLIS ){
if ( expired_subs == null ||
( sub.getAddTime() < expired_subs.getAddTime())){
expired_subs = sub;
}
continue;
}
}
sub.checkPublish();
}
if ( expired_subs != null ){
log( "Removing unsubscribed subscription '" + expired_subs.getName() + "' as expired" );
expired_subs.remove();
}
if ( ticks % ASSOC_CHECK_TICKS == 0 ){
lookupAssociations( false );
}
if ( ticks % SERVER_PUB_CHECK_TICKS == 0 ){
checkServerPublications( subs );
}
if ( ticks % TIDY_POT_ASSOC_TICKS == 0 ){
tidyPotentialAssociations();
}
if ( ticks == SET_SELECTED_FIRST_TICK ||
ticks % SET_SELECTED_TICKS == 0 ){
setSelected( subs );
}
}
public Subscription
importSubscription(
int type,
Map map,
boolean warn_user )
throws SubscriptionException
{
boolean log_errors = true;
try{
try{
if ( type == VuzeFileComponent.COMP_TYPE_SUBSCRIPTION_SINGLETON ){
String name = new String((byte[])map.get( "name" ), "UTF-8" );
URL url = new URL( new String((byte[])map.get( "url" ), "UTF-8" ));
Long l_interval = (Long)map.get( "check_interval_mins" );
int check_interval_mins = l_interval==null?SubscriptionHistoryImpl.DEFAULT_CHECK_INTERVAL_MINS:l_interval.intValue();
Long l_public = (Long)map.get( "public" );
boolean is_public = l_public==null?true:l_public.longValue()==1;
SubscriptionImpl existing = lookupSingletonRSS(name, url, is_public, check_interval_mins );
if ( UrlFilter.getInstance().urlCanRPC( url.toExternalForm())){
warn_user = false;
}
if ( existing != null && existing.isSubscribed()){
if ( warn_user ){
UIManager ui_manager = StaticUtilities.getUIManager( 120*1000 );
String details = MessageText.getString(
"subscript.add.dup.desc",
new String[]{ existing.getName()});
ui_manager.showMessageBox(
"subscript.add.dup.title",
"!" + details + "!",
UIManagerEvent.MT_OK );
}
selectSubscription( existing );
return( existing );
}else{
if ( warn_user ){
UIManager ui_manager = StaticUtilities.getUIManager( 120*1000 );
String details = MessageText.getString(
"subscript.add.desc",
new String[]{ name });
long res = ui_manager.showMessageBox(
"subscript.add.title",
"!" + details + "!",
UIManagerEvent.MT_YES | UIManagerEvent.MT_NO );
if ( res != UIManagerEvent.MT_YES ){
log_errors = false;
throw( new SubscriptionException( "User declined addition" ));
}
}
if ( existing == null ){
SubscriptionImpl new_subs = (SubscriptionImpl)createSingletonRSSSupport( name, url, is_public, check_interval_mins, SubscriptionImpl.ADD_TYPE_IMPORT, true );
log( "Imported new singleton subscription: " + new_subs.getString());
return( new_subs );
}else{
existing.setSubscribed( true );
selectSubscription( existing );
return( existing );
}
}
}else{
SubscriptionBodyImpl body = new SubscriptionBodyImpl( this, map );
SubscriptionImpl existing = getSubscriptionFromSID( body.getShortID());
if ( existing != null && existing.isSubscribed()){
if ( existing.getVersion() >= body.getVersion()){
log( "Not upgrading subscription: " + existing.getString() + " as supplied (" + body.getVersion() + ") is not more recent than existing (" + existing.getVersion() + ")");
if ( warn_user ){
UIManager ui_manager = StaticUtilities.getUIManager( 120*1000 );
String details = MessageText.getString(
"subscript.add.dup.desc",
new String[]{ existing.getName()});
ui_manager.showMessageBox(
"subscript.add.dup.title",
"!" + details + "!",
UIManagerEvent.MT_OK );
}
// we have a newer one, ignore
selectSubscription( existing );
return( existing );
}else{
if ( warn_user ){
UIManager ui_manager = StaticUtilities.getUIManager( 120*1000 );
String details = MessageText.getString(
"subscript.add.upgrade.desc",
new String[]{ existing.getName()});
long res = ui_manager.showMessageBox(
"subscript.add.upgrade.title",
"!" + details + "!",
UIManagerEvent.MT_YES | UIManagerEvent.MT_NO );
if ( res != UIManagerEvent.MT_YES ){
throw( new SubscriptionException( "User declined upgrade" ));
}
}
log( "Upgrading subscription: " + existing.getString());
existing.upgrade( body );
saveConfig();
subscriptionUpdated();
return( existing );
}
}else{
SubscriptionImpl new_subs = null;
String subs_name;
if ( existing == null ){
new_subs = new SubscriptionImpl( this, body, SubscriptionImpl.ADD_TYPE_IMPORT, true );
subs_name = new_subs.getName();
}else{
subs_name = existing.getName();
}
if ( warn_user ){
UIManager ui_manager = StaticUtilities.getUIManager( 120*1000 );
String details = MessageText.getString(
"subscript.add.desc",
new String[]{ subs_name });
long res = ui_manager.showMessageBox(
"subscript.add.title",
"!" + details + "!",
UIManagerEvent.MT_YES | UIManagerEvent.MT_NO );
if ( res != UIManagerEvent.MT_YES ){
throw( new SubscriptionException( "User declined addition" ));
}
}
if ( new_subs == null ){
existing.setSubscribed( true );
selectSubscription( existing );
return( existing );
}else{
log( "Imported new subscription: " + new_subs.getString());
new_subs = addSubscription( new_subs );
return( new_subs );
}
}
}
}catch( Throwable e ){
throw( new SubscriptionException( "Subscription import failed", e ));
}
}catch( SubscriptionException e ){
if ( warn_user && log_errors ){
UIManager ui_manager = StaticUtilities.getUIManager( 120*1000 );
String details = MessageText.getString(
"subscript.import.fail.desc",
new String[]{ Debug.getNestedExceptionMessage(e)});
ui_manager.showMessageBox(
"subscript.import.fail.title",
"!" + details + "!",
UIManagerEvent.MT_OK );
}
throw( e );
}
}
public Subscription[]
getSubscriptions()
{
synchronized( this ){
return((SubscriptionImpl[])subscriptions.toArray( new SubscriptionImpl[subscriptions.size()]));
}
}
public Subscription[]
getSubscriptions(
boolean subscribed_only )
{
if ( !subscribed_only ){
return( getSubscriptions());
}
List result = new ArrayList();
synchronized( this ){
for (int i=0;i<subscriptions.size();i++){
SubscriptionImpl subs = (SubscriptionImpl)subscriptions.get(i);
if ( subs.isSubscribed()){
result.add( subs );
}
}
}
return((SubscriptionImpl[])result.toArray( new SubscriptionImpl[result.size()]));
}
public int
getSubscriptionCount(
boolean subscribed_only )
{
if ( subscribed_only ){
int total = 0;
synchronized( this ){
for ( Subscription subs: subscriptions ){
if ( subs.isSubscribed()){
total++;
}
}
}
return( total );
}else{
synchronized( this ){
return( subscriptions.size());
}
}
}
protected SubscriptionImpl
getSubscriptionFromName(
String name )
{
synchronized( this ){
for (int i=0;i<subscriptions.size();i++){
SubscriptionImpl s = (SubscriptionImpl)subscriptions.get(i);
if ( s.getName().equalsIgnoreCase( name )){
return( s );
}
}
}
return( null );
}
public Subscription
getSubscriptionByID(
String id )
{
synchronized( this ){
int index = Collections.binarySearch(subscriptions, id, new Comparator() {
public int compare(Object o1, Object o2) {
String id1 = (o1 instanceof Subscription) ? ((Subscription) o1).getID() : o1.toString();
String id2 = (o2 instanceof Subscription) ? ((Subscription) o2).getID() : o2.toString();
return id1.compareTo(id2);
}
});
if (index >= 0) {
return subscriptions.get(index);
}
}
return null;
}
protected SubscriptionImpl
getSubscriptionFromSID(
byte[] sid )
{
return (SubscriptionImpl) getSubscriptionByID( Base32.encode(sid));
}
protected File
getSubsDir()
throws IOException
{
File dir = new File(SystemProperties.getUserPath());
dir = new File( dir, "subs" );
if ( !dir.exists()){
if ( !dir.mkdirs()){
throw( new IOException( "Failed to create '" + dir + "'" ));
}
}
return( dir );
}
protected File
getVuzeFile(
SubscriptionImpl subs )
throws IOException
{
File dir = getSubsDir();
return( new File( dir, ByteFormatter.encodeString( subs.getShortID()) + ".vuze" ));
}
protected File
getResultsFile(
SubscriptionImpl subs )
throws IOException
{
File dir = getSubsDir();
return( new File( dir, ByteFormatter.encodeString( subs.getShortID()) + ".results" ));
}
public int
getKnownSubscriptionCount()
{
PluginInterface pi = PluginInitializer.getDefaultInterface();
Download[] downloads = pi.getDownloadManager().getDownloads();
ByteArrayHashMap<String> results = new ByteArrayHashMap<String>(Math.max( 16, downloads.length * 2 ));
try{
for ( Download download: downloads ){
Map m = download.getMapAttribute( ta_subscription_info );
if ( m != null ){
List s = (List)m.get("s");
if ( s != null && s.size() > 0 ){
for (int i=0;i<s.size();i++){
byte[] sid = (byte[])s.get(i);
results.put( sid, "" );
}
}
}
}
}catch( Throwable e ){
log( "Failed to get known subscriptions", e );
}
return( results.size());
}
public Subscription[]
getKnownSubscriptions(
byte[] hash )
{
PluginInterface pi = PluginInitializer.getDefaultInterface();
try{
Download download = pi.getDownloadManager().getDownload( hash );
if ( download != null ){
Map m = download.getMapAttribute( ta_subscription_info );
if ( m != null ){
List s = (List)m.get("s");
if ( s != null && s.size() > 0 ){
List result = new ArrayList( s.size());
boolean hide_search = hideSearchTemplates();
for (int i=0;i<s.size();i++){
byte[] sid = (byte[])s.get(i);
SubscriptionImpl subs = getSubscriptionFromSID(sid);
if ( subs != null ){
if ( isVisible( subs )){
if ( hide_search && subs.isSearchTemplate()){
}else{
result.add( subs );
}
}
}
}
return((Subscription[])result.toArray( new Subscription[result.size()]));
}
}
}
}catch( Throwable e ){
log( "Failed to get known subscriptions", e );
}
return( new Subscription[0] );
}
protected boolean
subscriptionExists(
Download download,
SubscriptionImpl subs )
{
byte[] sid = subs.getShortID();
Map m = download.getMapAttribute( ta_subscription_info );
if ( m != null ){
List s = (List)m.get("s");
if ( s != null && s.size() > 0 ){
for (int i=0;i<s.size();i++){
byte[] x = (byte[])s.get(i);
if ( Arrays.equals( x, sid )){
return( true );
}
}
}
}
return( false );
}
protected boolean
isVisible(
SubscriptionImpl subs )
{
// to avoid development links polluting production we filter out such subscriptions
if ( Constants.isCVSVersion() || subs.isSubscribed()){
return( true );
}
try{
Engine engine = subs.getEngine( true );
if ( engine instanceof WebEngine ){
String url = ((WebEngine)engine).getSearchUrl();
try{
String host = new URL( url ).getHost();
return( !exclusion_pattern.matcher( host ).matches());
}catch( Throwable e ){
}
}
return( true );
}catch( Throwable e ){
log( "isVisible failed for " + subs.getString(), e );
return( false );
}
}
public Subscription[]
getLinkedSubscriptions(
byte[] hash )
{
PluginInterface pi = PluginInitializer.getDefaultInterface();
try{
Download download = pi.getDownloadManager().getDownload( hash );
if ( download != null ){
Map m = download.getMapAttribute( ta_subscription_info );
if ( m != null ){
List s = (List)m.get("s");
if ( s != null && s.size() > 0 ){
List result = new ArrayList( s.size());
for (int i=0;i<s.size();i++){
byte[] sid = (byte[])s.get(i);
SubscriptionImpl subs = getSubscriptionFromSID(sid);
if ( subs != null ){
if ( subs.hasAssociation( hash )){
result.add( subs );
}
}
}
return((Subscription[])result.toArray( new Subscription[result.size()]));
}
}
}
}catch( Throwable e ){
log( "Failed to get known subscriptions", e );
}
return( new Subscription[0] );
}
protected void
lookupAssociations(
boolean high_priority )
{
synchronized( this ){
if ( periodic_lookup_in_progress ){
if ( high_priority ){
priority_lookup_pending++;
}
return;
}
periodic_lookup_in_progress = true;
}
try{
PluginInterface pi = PluginInitializer.getDefaultInterface();
Download[] downloads = pi.getDownloadManager().getDownloads();
long now = SystemTime.getCurrentTime();
long newest_time = 0;
Download newest_download = null;
for( int i=0;i<downloads.length;i++){
Download download = downloads[i];
if ( download.getTorrent() == null || !download.isPersistent()){
continue;
}
Map map = download.getMapAttribute( ta_subscription_info );
if ( map == null ){
map = new LightHashMap();
}else{
map = new LightHashMap( map );
}
Long l_last_check = (Long)map.get( "lc" );
long last_check = l_last_check==null?0:l_last_check.longValue();
if ( last_check > now ){
last_check = now;
map.put( "lc", new Long( last_check ));
download.setMapAttribute( ta_subscription_info, map );
}
List subs = (List)map.get( "s" );
int sub_count = subs==null?0:subs.size();
if ( sub_count > 8 ){
continue;
}
long create_time = download.getCreationTime();
int time_between_checks = (sub_count + 1) * 24*60*60*1000 + (int)(create_time%4*60*60*1000);
if ( now - last_check >= time_between_checks ){
if ( create_time > newest_time ){
newest_time = create_time;
newest_download = download;
}
}
}
if ( newest_download != null ){
byte[] hash = newest_download.getTorrent().getHash();
log( "Association lookup starts for " + newest_download.getName() + "/" + ByteFormatter.encodeString( hash ));
lookupAssociationsSupport(
hash,
new SubscriptionLookupListener()
{
public void
found(
byte[] hash,
Subscription subscription )
{
}
public void
failed(
byte[] hash,
SubscriptionException error )
{
log( "Association lookup failed for " + ByteFormatter.encodeString( hash ), error );
associationLookupComplete();
}
public void
complete(
byte[] hash,
Subscription[] subs )
{
log( "Association lookup complete for " + ByteFormatter.encodeString( hash ));
associationLookupComplete();
}
});
}else{
associationLookupComplete();
}
}catch( Throwable e ){
log( "Association lookup check failed", e );
associationLookupComplete();
}
}
protected void
associationLookupComplete()
{
boolean recheck;
synchronized( this ){
periodic_lookup_in_progress = false;
recheck = priority_lookup_pending > 0;
if ( recheck ){
priority_lookup_pending--;
}
}
if ( recheck ){
new AEThread2( "SM:priAssLookup", true )
{
public void run()
{
lookupAssociations( false );
}
}.start();
}
}
protected void
setSelected(
List subs )
{
List sids = new ArrayList();
List used_subs = new ArrayList();
for (int i=0;i<subs.size();i++){
SubscriptionImpl sub = (SubscriptionImpl)subs.get(i);
if ( sub.isSubscribed()){
if ( sub.isPublic()){
used_subs.add( sub );
sids.add( sub.getShortID());
}else{
checkInitialDownload( sub );
}
}
}
if ( sids.size() > 0 ){
try{
List[] result = PlatformSubscriptionsMessenger.setSelected( sids );
List versions = result[0];
List popularities = result[1];
log( "Popularity update: updated " + sids.size());
final List dht_pops = new ArrayList();
for (int i=0;i<sids.size();i++){
SubscriptionImpl sub = (SubscriptionImpl)used_subs.get(i);
int latest_version = ((Long)versions.get(i)).intValue();
if ( latest_version > sub.getVersion()){
updateSubscription( sub, latest_version );
}else{
checkInitialDownload( sub );
}
if ( latest_version > 0 ){
try{
long pop = ((Long)popularities.get(i)).longValue();
if ( pop >= 0 && pop != sub.getCachedPopularity()){
sub.setCachedPopularity( pop );
}
}catch( Throwable e ){
log( "Popularity update: Failed to extract popularity", e );
}
}else{
dht_pops.add( sub );
}
}
if ( dht_pops.size() <= 3 ){
for (int i=0;i<dht_pops.size();i++){
updatePopularityFromDHT((SubscriptionImpl)dht_pops.get(i), false );
}
}else{
new AEThread2( "SM:asyncPop", true )
{
public void
run()
{
for (int i=0;i<dht_pops.size();i++){
updatePopularityFromDHT((SubscriptionImpl)dht_pops.get(i), true );
}
}
}.start();
}
}catch( Throwable e ){
log( "Popularity update: Failed to record selected subscriptions", e );
}
}else{
log( "Popularity update: No selected, public subscriptions" );
}
}
protected void
checkUpgrade(
SubscriptionImpl sub )
{
setSelected( sub );
}
protected void
setSelected(
final SubscriptionImpl sub )
{
if ( sub.isSubscribed()){
if ( sub.isPublic()){
new DelayedEvent(
"SM:setSelected",
0,
new AERunnable()
{
public void
runSupport()
{
try{
List sids = new ArrayList();
sids.add( sub.getShortID());
List[] result = PlatformSubscriptionsMessenger.setSelected( sids );
log( "setSelected: " + sub.getName());
int latest_version = ((Long)result[0].get(0)).intValue();
if ( latest_version == 0 ){
if ( sub.isSingleton()){
checkSingletonPublish( sub );
}
}else if ( latest_version > sub.getVersion()){
updateSubscription( sub, latest_version );
}else{
checkInitialDownload( sub );
}
if ( latest_version > 0 ){
try{
long pop = ((Long)result[1].get(0)).longValue();
if ( pop >= 0 && pop != sub.getCachedPopularity()){
sub.setCachedPopularity( pop );
}
}catch( Throwable e ){
log( "Popularity update: Failed to extract popularity", e );
}
}else{
updatePopularityFromDHT( sub, true );
}
}catch( Throwable e ){
log( "setSelected: failed for " + sub.getName(), e );
}
}
});
}else{
checkInitialDownload( sub );
}
}
}
protected void
checkInitialDownload(
SubscriptionImpl subs )
{
if ( subs.getHistory().getLastScanTime() == 0 ){
scheduler.download(
subs,
true,
new SubscriptionDownloadListener()
{
public void
complete(
Subscription subs )
{
log( "Initial download of " + subs.getName() + " complete" );
}
public void
failed(
Subscription subs,
SubscriptionException error )
{
log( "Initial download of " + subs.getName() + " failed", error );
}
});
}
}
public SubscriptionAssociationLookup
lookupAssociations(
final byte[] hash,
final SubscriptionLookupListener listener )
throws SubscriptionException
{
if ( dht_plugin != null && !dht_plugin.isInitialising()){
return( lookupAssociationsSupport( hash, listener ));
}
final boolean[] cancelled = { false };
final SubscriptionAssociationLookup[] actual_res = { null };
final SubscriptionAssociationLookup res =
new SubscriptionAssociationLookup()
{
public void
cancel()
{
log( " Association lookup cancelled" );
synchronized( actual_res ){
cancelled[0] = true;
if ( actual_res[0] != null ){
actual_res[0].cancel();
}
}
}
};
new AEThread2( "SM:initwait", true )
{
public void
run()
{
try{
SubscriptionAssociationLookup x = lookupAssociationsSupport( hash, listener );
synchronized( actual_res ){
actual_res[0] = x;
if ( cancelled[0] ){
x.cancel();
}
}
}catch( SubscriptionException e ){
listener.failed( hash, e );
}
}
}.start();
return( res );
}
protected SubscriptionAssociationLookup
lookupAssociationsSupport(
final byte[] hash,
final SubscriptionLookupListener listener )
throws SubscriptionException
{
log( "Looking up associations for '" + ByteFormatter.encodeString( hash ));
final String key = "subscription:assoc:" + ByteFormatter.encodeString( hash );
final boolean[] cancelled = { false };
dht_plugin.get(
key.getBytes(),
"Subs assoc read: " + Base32.encode( hash ).substring( 0, 16 ),
DHTPlugin.FLAG_SINGLE_VALUE,
30,
60*1000,
true,
true,
new DHTPluginOperationListener()
{
private Map<HashWrapper,Integer> hits = new HashMap<HashWrapper, Integer>();
private AESemaphore hits_sem = new AESemaphore( "Subs:lookup" );
private List<Subscription> found_subscriptions = new ArrayList<Subscription>();
private boolean complete;
public boolean
diversified()
{
return( true );
}
public void
starts(
byte[] key )
{
}
public void
valueRead(
DHTPluginContact originator,
DHTPluginValue value )
{
if ( isCancelled2()){
return;
}
byte[] val = value.getValue();
if ( val.length > 4 ){
int ver = ((val[0]<<16)&0xff0000) | ((val[1]<<8)&0xff00) | (val[2]&0xff);
byte[] sid = new byte[ val.length - 4 ];
System.arraycopy( val, 4, sid, 0, sid.length );
HashWrapper hw = new HashWrapper( sid );
boolean new_sid = false;
synchronized( hits ){
if ( complete ){
return;
}
Integer v = (Integer)hits.get(hw);
if ( v != null ){
if ( ver > v.intValue()){
hits.put( hw, new Integer( ver ));
}
}else{
new_sid = true;
hits.put( hw, new Integer( ver ));
}
}
if ( new_sid ){
log( " Found subscription " + ByteFormatter.encodeString( sid ) + " version " + ver );
// check if already subscribed
SubscriptionImpl subs = getSubscriptionFromSID( sid );
if ( subs != null ){
synchronized( hits ){
found_subscriptions.add( subs );
}
try{
listener.found( hash, subs );
}catch( Throwable e ){
Debug.printStackTrace(e);
}
hits_sem.release();
}else{
lookupSubscription(
hash,
sid,
ver,
new subsLookupListener()
{
private boolean sem_done = false;
public void
found(
byte[] hash,
Subscription subscription )
{
}
public void
complete(
byte[] hash,
Subscription[] subscriptions )
{
done( subscriptions );
}
public void
failed(
byte[] hash,
SubscriptionException error )
{
done( new Subscription[0]);
}
protected void
done(
Subscription[] subs )
{
if ( isCancelled()){
return;
}
synchronized( this ){
if ( sem_done ){
return;
}
sem_done = true;
}
if ( subs.length > 0 ){
synchronized( hits ){
found_subscriptions.add( subs[0] );
}
try{
listener.found( hash, subs[0] );
}catch( Throwable e ){
Debug.printStackTrace(e);
}
}
hits_sem.release();
}
public boolean
isCancelled()
{
return( isCancelled2());
}
});
}
}
}
}
public void
valueWritten(
DHTPluginContact target,
DHTPluginValue value )
{
}
public void
complete(
byte[] original_key,
boolean timeout_occurred )
{
new AEThread2( "Subs:lookup wait", true )
{
public void
run()
{
int num_hits;
synchronized( hits ){
if ( complete ){
return;
}
complete = true;
num_hits = hits.size();
}
for (int i=0;i<num_hits;i++){
if ( isCancelled2()){
return;
}
hits_sem.reserve();
}
if ( isCancelled2()){
return;
}
SubscriptionImpl[] s;
synchronized( hits ){
s = (SubscriptionImpl[])found_subscriptions.toArray( new SubscriptionImpl[ found_subscriptions.size() ]);
}
log( " Association lookup complete - " + s.length + " found" );
try{
// record zero assoc here for completeness
recordAssociations( hash, s, true );
}finally{
listener.complete( hash, s );
}
}
}.start();
}
protected boolean
isCancelled2()
{
synchronized( cancelled ){
return( cancelled[0] );
}
}
});
return(
new SubscriptionAssociationLookup()
{
public void
cancel()
{
log( " Association lookup cancelled" );
synchronized( cancelled ){
cancelled[0] = true;
}
}
});
}
interface
subsLookupListener
extends SubscriptionLookupListener
{
public boolean
isCancelled();
}
protected void
getPopularity(
final SubscriptionImpl subs,
final SubscriptionPopularityListener listener )
throws SubscriptionException
{
try{
long pop = PlatformSubscriptionsMessenger.getPopularityBySID( subs.getShortID());
if ( pop >= 0 ){
log( "Got popularity of " + subs.getName() + " from platform: " + pop );
listener.gotPopularity( pop );
return;
}else{
// unknown sid - if singleton try to register for popularity tracking purposes
if ( subs.isSingleton()){
try{
checkSingletonPublish( subs );
}catch( Throwable e ){
}
listener.gotPopularity( subs.isSubscribed()?1:0 );
return;
}
}
}catch( Throwable e ){
log( "Subscription lookup via platform failed", e );
}
getPopularityFromDHT( subs, listener, true );
}
protected void
getPopularityFromDHT(
final SubscriptionImpl subs,
final SubscriptionPopularityListener listener,
final boolean sync )
{
if ( dht_plugin != null && !dht_plugin.isInitialising()){
getPopularitySupport( subs, listener, sync );
}else{
new AEThread2( "SM:popwait", true )
{
public void
run()
{
getPopularitySupport( subs, listener, sync );
}
}.start();
}
}
protected void
updatePopularityFromDHT(
final SubscriptionImpl subs,
boolean sync )
{
getPopularityFromDHT(
subs,
new SubscriptionPopularityListener()
{
public void
gotPopularity(
long popularity )
{
subs.setCachedPopularity( popularity );
}
public void
failed(
SubscriptionException error )
{
log( "Failed to update subscription popularity from DHT", error );
}
},
sync );
}
protected void
getPopularitySupport(
final SubscriptionImpl subs,
final SubscriptionPopularityListener listener,
final boolean sync )
{
log( "Getting popularity of " + subs.getName() + " from DHT" );
byte[] hash = subs.getPublicationHash();
final AESemaphore sem = new AESemaphore( "SM:pop" );
final long[] result = { -1 };
final int timeout = 15*1000;
dht_plugin.get(
hash,
"Popularity lookup for subscription " + subs.getName(),
DHT.FLAG_STATS,
5,
timeout,
false,
true,
new DHTPluginOperationListener()
{
private boolean diversified;
private int hits = 0;
public boolean
diversified()
{
diversified = true;
return( false );
}
public void
starts(
byte[] key )
{
}
public void
valueRead(
DHTPluginContact originator,
DHTPluginValue value )
{
DHTPluginKeyStats stats = dht_plugin.decodeStats( value );
if ( stats != null ){
result[0] = Math.max( result[0], stats.getEntryCount());
hits++;
if ( hits >= 3 ){
done();
}
}
}
public void
valueWritten(
DHTPluginContact target,
DHTPluginValue value )
{
}
public void
complete(
byte[] key,
boolean timeout_occurred )
{
if ( diversified ){
// TODO: fix?
result[0] *= 11;
if ( result[0] == 0 ){
result[0] = 10;
}
}
done();
}
protected void
done()
{
if ( sync ){
sem.release();
}else{
if ( result[0] == -1 ){
log( "Failed to get popularity of " + subs.getName() + " from DHT" );
listener.failed( new SubscriptionException( "Timeout" ));
}else{
log( "Get popularity of " + subs.getName() + " from DHT: " + result[0] );
listener.gotPopularity( result[0] );
}
}
}
});
if ( sync ){
sem.reserve( timeout );
if ( result[0] == -1 ){
log( "Failed to get popularity of " + subs.getName() + " from DHT" );
listener.failed( new SubscriptionException( "Timeout" ));
}else{
log( "Get popularity of " + subs.getName() + " from DHT: " + result[0] );
listener.gotPopularity( result[0] );
}
}
}
protected void
lookupSubscription(
final byte[] association_hash,
final byte[] sid,
final int version,
final subsLookupListener listener )
{
try{
SubscriptionImpl subs = getSubscriptionFromPlatform( sid, SubscriptionImpl.ADD_TYPE_LOOKUP );
log( "Added temporary subscription: " + subs.getString());
subs = addSubscription( subs );
listener.complete( association_hash, new Subscription[]{ subs });
return;
}catch( Throwable e ){
if ( listener.isCancelled()){
return;
}
final String sid_str = ByteFormatter.encodeString( sid );
log( "Subscription lookup via platform for " + sid_str + " failed", e );
if ( getSubscriptionDownloadCount() > 8 ){
log( "Too many existing subscription downloads" );
listener.complete( association_hash, new Subscription[0]);
return;
}
// fall back to DHT
log( "Subscription lookup via DHT starts for " + sid_str );
final String key = "subscription:publish:" + ByteFormatter.encodeString( sid ) + ":" + version;
dht_plugin.get(
key.getBytes(),
"Subs lookup read: " + ByteFormatter.encodeString( sid ) + ":" + version,
DHTPlugin.FLAG_SINGLE_VALUE,
12,
60*1000,
false,
true,
new DHTPluginOperationListener()
{
private boolean listener_handled;
public boolean
diversified()
{
return( true );
}
public void
starts(
byte[] key )
{
}
public void
valueRead(
DHTPluginContact originator,
DHTPluginValue value )
{
byte[] data = value.getValue();
try{
final Map details = decodeSubscriptionDetails( data );
if ( SubscriptionImpl.getPublicationVersion( details ) == version ){
Map singleton_details = (Map)details.get( "x" );
if ( singleton_details == null ){
synchronized( this ){
if ( listener_handled ){
return;
}
listener_handled = true;
}
log( " found " + sid_str + ", non-singleton" );
new AEThread2( "Subs:lookup download", true )
{
public void
run()
{
downloadSubscription(
association_hash,
SubscriptionImpl.getPublicationHash( details ),
sid,
version,
SubscriptionImpl.getPublicationSize( details ),
listener );
}
}.start();
}else{
synchronized( this ){
if ( listener_handled ){
return;
}
listener_handled = true;
}
log( " found " + sid_str + ", singleton" );
try{
SubscriptionImpl subs = createSingletonSubscription( singleton_details, SubscriptionImpl.ADD_TYPE_LOOKUP, false );
listener.complete( association_hash, new Subscription[]{ subs });
}catch( Throwable e ){
listener.failed( association_hash, new SubscriptionException( "Subscription creation failed", e ));
}
}
}else{
log( " found " + sid_str + " but version mismatch" );
}
}catch( Throwable e ){
log( " found " + sid_str + " but verification failed", e );
}
}
public void
valueWritten(
DHTPluginContact target,
DHTPluginValue value )
{
}
public void
complete(
byte[] original_key,
boolean timeout_occurred )
{
if ( listener.isCancelled()){
return;
}
log( " " + sid_str + " complete" );
synchronized( this ){
if ( !listener_handled ){
listener_handled = true;
listener.complete( association_hash, new Subscription[0] );
}
}
}
});
}
}
protected SubscriptionImpl
getSubscriptionFromPlatform(
byte[] sid,
int add_type )
throws SubscriptionException
{
try{
PlatformSubscriptionsMessenger.subscriptionDetails details = PlatformSubscriptionsMessenger.getSubscriptionBySID( sid );
SubscriptionImpl res = getSubscriptionFromVuzeFileContent( sid, add_type, details.getContent());
int pop = details.getPopularity();
if ( pop >= 0 ){
res.setCachedPopularity( pop );
}
return( res );
}catch( SubscriptionException e ){
throw( e );
}catch( Throwable e ){
throw( new SubscriptionException( "Failed to read subscription from platform", e ));
}
}
protected SubscriptionImpl
getSubscriptionFromVuzeFile(
byte[] sid,
int add_type,
File file )
throws SubscriptionException
{
VuzeFileHandler vfh = VuzeFileHandler.getSingleton();
String file_str = file.getAbsolutePath();
VuzeFile vf = vfh.loadVuzeFile( file_str );
if ( vf == null ){
log( "Failed to load vuze file from " + file_str );
throw( new SubscriptionException( "Failed to load vuze file from " + file_str ));
}
return( getSubscriptionFromVuzeFile( sid, add_type, vf ));
}
protected SubscriptionImpl
getSubscriptionFromVuzeFileContent(
byte[] sid,
int add_type,
String content )
throws SubscriptionException
{
VuzeFileHandler vfh = VuzeFileHandler.getSingleton();
VuzeFile vf = vfh.loadVuzeFile( Base64.decode( content ));
if ( vf == null ){
log( "Failed to load vuze file from " + content );
throw( new SubscriptionException( "Failed to load vuze file from content" ));
}
return( getSubscriptionFromVuzeFile( sid, add_type, vf ));
}
protected SubscriptionImpl
getSubscriptionFromVuzeFile(
byte[] sid,
int add_type,
VuzeFile vf )
throws SubscriptionException
{
VuzeFileComponent[] comps = vf.getComponents();
for (int j=0;j<comps.length;j++){
VuzeFileComponent comp = comps[j];
if ( comp.getType() == VuzeFileComponent.COMP_TYPE_SUBSCRIPTION ){
Map map = comp.getContent();
try{
SubscriptionBodyImpl body = new SubscriptionBodyImpl( SubscriptionManagerImpl.this, map );
SubscriptionImpl new_subs = new SubscriptionImpl( SubscriptionManagerImpl.this, body, add_type, false );
if ( Arrays.equals( new_subs.getShortID(), sid )){
return( new_subs );
}
}catch( Throwable e ){
log( "Subscription decode failed", e );
}
}
}
throw( new SubscriptionException( "Subscription not found" ));
}
protected void
downloadSubscription(
final byte[] association_hash,
byte[] torrent_hash,
final byte[] sid,
int version,
int size,
final subsLookupListener listener )
{
try{
Object[] res = downloadTorrent( torrent_hash, size );
if ( listener.isCancelled()){
return;
}
if ( res == null ){
listener.complete( association_hash, new Subscription[0] );
return;
}
downloadSubscription(
(TOTorrent)res[0],
(InetSocketAddress)res[1],
sid,
version,
"Subscription " + ByteFormatter.encodeString( sid ) + " for " + ByteFormatter.encodeString( association_hash ),
new downloadListener()
{
public void
complete(
File data_file )
{
boolean reported = false;
try{
if ( listener.isCancelled()){
return;
}
SubscriptionImpl subs = getSubscriptionFromVuzeFile( sid, SubscriptionImpl.ADD_TYPE_LOOKUP, data_file );
log( "Added temporary subscription: " + subs.getString());
subs = addSubscription( subs );
listener.complete( association_hash, new Subscription[]{ subs });
reported = true;
}catch( Throwable e ){
log( "Subscription decode failed", e );
}finally{
if ( !reported ){
listener.complete( association_hash, new Subscription[0] );
}
}
}
public void
complete(
Download download,
File torrent_file )
{
File data_file = new File( download.getSavePath());
try{
removeDownload( download, false );
complete( data_file );
}catch( Throwable e ){
log( "Failed to remove download", e );
listener.complete( association_hash, new Subscription[0] );
}finally{
torrent_file.delete();
data_file.delete();
}
}
public void
failed(
Throwable error )
{
listener.complete( association_hash, new Subscription[0] );
}
public Map
getRecoveryData()
{
return( null );
}
public boolean
isCancelled()
{
return( listener.isCancelled());
}
});
}catch( Throwable e ){
log( "Subscription download failed",e );
listener.complete( association_hash, new Subscription[0] );
}
}
protected int
getSubscriptionDownloadCount()
{
PluginInterface pi = PluginInitializer.getDefaultInterface();
Download[] downloads = pi.getDownloadManager().getDownloads();
int res = 0;
for( int i=0;i<downloads.length;i++){
Download download = downloads[i];
if ( download.getBooleanAttribute( ta_subs_download )){
res++;
}
}
return( res );
}
protected void
associationAdded(
SubscriptionImpl subscription,
byte[] association_hash )
{
recordAssociations( association_hash, new SubscriptionImpl[]{ subscription }, false );
if ( dht_plugin != null ){
publishAssociations();
}
}
protected void
addPotentialAssociation(
SubscriptionImpl subs,
String result_id,
String key )
{
if ( key == null ){
Debug.out( "Attempt to add null key!" );
return;
}
log( "Added potential association: " + subs.getName() + "/" + result_id + " -> " + key );
synchronized( potential_associations ){
potential_associations.add( new Object[]{ subs, result_id, key, new Long( System.currentTimeMillis())} );
if ( potential_associations.size() > 512 ){
potential_associations.remove(0);
}
}
}
protected void
checkPotentialAssociations(
byte[] hash,
String key )
{
log( "Checking potential association: " + key + " -> " + ByteFormatter.encodeString( hash ));
SubscriptionImpl subs = null;
String result_id = null;
synchronized( potential_associations ){
Iterator<Object[]> it = potential_associations.iterator();
while( it.hasNext()){
Object[] entry = it.next();
String this_key = (String)entry[2];
// startswith as actual URL may have had additional parameters added such as azid
if ( key.startsWith( this_key )){
subs = (SubscriptionImpl)entry[0];
result_id = (String)entry[1];
log( " key matched to subscription " + subs.getName() + "/" + result_id);
it.remove();
break;
}
}
}
if ( subs == null ){
log( " no potential associations found" );
}else{
SubscriptionResult result = subs.getHistory().getResult( result_id );
if ( result != null ){
log( " result found, marking as read" );
result.setRead( true );
}else{
log( " result not found" );
}
log( " adding association" );
subs.addAssociation( hash );
}
}
protected void
tidyPotentialAssociations()
{
long now = SystemTime.getCurrentTime();
synchronized( potential_associations ){
Iterator it = potential_associations.iterator();
while( it.hasNext() && potential_associations.size() > 16 ){
Object[] entry = (Object[])it.next();
long created = ((Long)entry[3]).longValue();
if ( created > now ){
entry[3] = new Long( now );
}else if ( now - created > 60*60*1000 ){
SubscriptionImpl subs = (SubscriptionImpl)entry[0];
String result_id = (String)entry[1];
String key = (String)entry[2];
log( "Removing expired potential association: " + subs.getName() + "/" + result_id + " -> " + key );
it.remove();
}
}
}
synchronized( potential_associations2 ){
Iterator it = potential_associations2.entrySet().iterator();
while( it.hasNext() && potential_associations2.size() > 16 ){
Map.Entry map_entry = (Map.Entry)it.next();
byte[] hash = ((HashWrapper)map_entry.getKey()).getBytes();
Object[] entry = (Object[])map_entry.getValue();
long created = ((Long)entry[2]).longValue();
if ( created > now ){
entry[2] = new Long( now );
}else if ( now - created > 60*60*1000 ){
SubscriptionImpl[] subs = (SubscriptionImpl[])entry[0];
String subs_str = "";
for (int i=0;i<subs.length;i++){
subs_str += (i==0?"":",") + subs[i].getName();
}
log( "Removing expired potential association: " + ByteFormatter.encodeString(hash) + " -> " + subs_str );
it.remove();
}
}
}
}
protected void
recordAssociations(
byte[] association_hash,
SubscriptionImpl[] subscriptions,
boolean full_lookup )
{
HashWrapper hw = new HashWrapper( association_hash );
synchronized( potential_associations2 ){
potential_associations2.put( hw, new Object[]{ subscriptions, new Boolean( full_lookup ), new Long( SystemTime.getCurrentTime())});
}
if ( recordAssociationsSupport( association_hash, subscriptions, full_lookup )){
synchronized( potential_associations2 ){
potential_associations2.remove( hw );
}
}else{
log( "Deferring association for " + ByteFormatter.encodeString( association_hash ));
}
}
protected boolean
recordAssociationsSupport(
byte[] association_hash,
SubscriptionImpl[] subscriptions,
boolean full_lookup )
{
PluginInterface pi = PluginInitializer.getDefaultInterface();
boolean download_found = false;
boolean changed = false;
try{
Download download = pi.getDownloadManager().getDownload( association_hash );
if ( download != null ){
if ( subscriptions.length > 0 ){
String category = subscriptions[0].getCategory();
if ( category != null ){
String existing = download.getAttribute( ta_category );
if ( existing == null ){
download.setAttribute( ta_category, category );
}
}
long tag_id = subscriptions[0].getTagID();
if ( tag_id >= 0 ){
Tag tag = TagManagerFactory.getTagManager().lookupTagByUID( tag_id );
if ( tag != null ){
org.gudy.azureus2.core3.download.DownloadManager core_dm = PluginCoreUtils.unwrap( download );
if ( !tag.hasTaggable( core_dm )){
tag.addTaggable( core_dm );
}
}
}
}
download_found = true;
Map map = download.getMapAttribute( ta_subscription_info );
if ( map == null ){
map = new LightHashMap();
}else{
map = new LightHashMap( map );
}
List s = (List)map.get( "s" );
for (int i=0;i<subscriptions.length;i++){
byte[] sid = subscriptions[i].getShortID();
if ( s == null ){
s = new ArrayList();
s.add( sid );
changed = true;
map.put( "s", s );
}else{
boolean found = false;
for (int j=0;j<s.size();j++){
byte[] existing = (byte[])s.get(j);
if ( Arrays.equals( sid, existing )){
found = true;
break;
}
}
if ( !found ){
s.add( sid );
changed = true;
}
}
}
if ( full_lookup ){
map.put( "lc", new Long( SystemTime.getCurrentTime()));
changed = true;
}
if ( changed ){
download.setMapAttribute( ta_subscription_info, map );
}
}
}catch( Throwable e ){
log( "Failed to record associations", e );
}
if ( changed ){
Iterator it = listeners.iterator();
while( it.hasNext()){
try{
((SubscriptionManagerListener)it.next()).associationsChanged( association_hash );
}catch( Throwable e ){
Debug.printStackTrace(e);
}
}
}
return( download_found );
}
protected boolean
publishAssociations()
{
SubscriptionImpl subs_to_publish = null;
SubscriptionImpl.association assoc_to_publish = null;
synchronized( this ){
if ( publish_associations_active >= ( dht_plugin.isSleeping()?PUB_SLEEPING_ASSOC_CONC_MAX:PUB_ASSOC_CONC_MAX )){
return( false );
}
publish_associations_active++;
List shuffled_subs = new ArrayList( subscriptions );
Collections.shuffle( shuffled_subs );
for (int i=0;i<shuffled_subs.size();i++){
SubscriptionImpl sub = (SubscriptionImpl)shuffled_subs.get( i );
if ( sub.isSubscribed() && sub.isPublic()){
assoc_to_publish = sub.getAssociationForPublish();
if ( assoc_to_publish != null ){
subs_to_publish = sub;
break;
}
}
}
}
if ( assoc_to_publish != null ){
publishAssociation( subs_to_publish, assoc_to_publish );
return( false );
}else{
log( "Publishing Associations Complete" );
synchronized( this ){
publish_associations_active--;
}
return( true );
}
}
protected void
publishAssociation(
final SubscriptionImpl subs,
final SubscriptionImpl.association assoc )
{
log( "Checking association '" + subs.getString() + "' -> '" + assoc.getString() + "'" );
byte[] sub_id = subs.getShortID();
int sub_version = subs.getVersion();
byte[] assoc_hash = assoc.getHash();
final String key = "subscription:assoc:" + ByteFormatter.encodeString( assoc_hash );
final byte[] put_value = new byte[sub_id.length + 4];
System.arraycopy( sub_id, 0, put_value, 4, sub_id.length );
put_value[0] = (byte)(sub_version>>16);
put_value[1] = (byte)(sub_version>>8);
put_value[2] = (byte)sub_version;
put_value[3] = (byte)subs.getFixedRandom();
dht_plugin.get(
key.getBytes(),
"Subs assoc read: " + Base32.encode( assoc_hash ).substring( 0, 16 ),
DHTPlugin.FLAG_SINGLE_VALUE,
30,
60*1000,
false,
false,
new DHTPluginOperationListener()
{
private int hits;
private boolean diversified;
private int max_ver;
public boolean
diversified()
{
diversified = true;
return( false );
}
public void
starts(
byte[] key )
{
}
public void
valueRead(
DHTPluginContact originator,
DHTPluginValue value )
{
byte[] val = value.getValue();
if ( val.length == put_value.length ){
boolean diff = false;
for (int i=4;i<val.length;i++){
if ( val[i] != put_value[i] ){
diff = true;
break;
}
}
if ( !diff ){
hits++;
int ver = ((val[0]<<16)&0xff0000) | ((val[1]<<8)&0xff00) | (val[2]&0xff);
if ( ver > max_ver ){
max_ver = ver;
}
}
}
}
public void
valueWritten(
DHTPluginContact target,
DHTPluginValue value )
{
}
public void
complete(
byte[] original_key,
boolean timeout_occurred )
{
log( "Checked association '" + subs.getString() + "' -> '" + assoc.getString() + "' - max_ver=" + max_ver + ",hits=" + hits + ",div=" + diversified );
if ( max_ver > subs.getVersion()){
if ( !subs.isMine()){
updateSubscription( subs, max_ver );
}
}
if ( hits < 10 && !diversified ){
log( " Publishing association '" + subs.getString() + "' -> '" + assoc.getString() + "', existing=" + hits );
byte flags = DHTPlugin.FLAG_ANON;
if ( hits < 3 && !diversified ){
flags |= DHTPlugin.FLAG_PRECIOUS;
}
dht_plugin.put(
key.getBytes(),
"Subs assoc write: " + Base32.encode( assoc.getHash()).substring( 0, 16 ) + " -> " + Base32.encode( subs.getShortID() ) + ":" + subs.getVersion(),
put_value,
flags,
new DHTPluginOperationListener()
{
public boolean
diversified()
{
return( true );
}
public void
starts(
byte[] key )
{
}
public void
valueRead(
DHTPluginContact originator,
DHTPluginValue value )
{
}
public void
valueWritten(
DHTPluginContact target,
DHTPluginValue value )
{
}
public void
complete(
byte[] key,
boolean timeout_occurred )
{
log( " completed '" + subs.getString() + "' -> '" + assoc.getString() + "'" );
publishNext();
}
});
}else{
log( " Not publishing association '" + subs.getString() + "' -> '" + assoc.getString() + "', existing =" + hits );
publishNext();
}
}
protected void
publishNext()
{
synchronized( SubscriptionManagerImpl.this ){
publish_associations_active--;
}
publishNextAssociation();
}
});
}
private void
publishNextAssociation()
{
boolean dht_sleeping = dht_plugin.isSleeping();
if ( dht_sleeping ){
synchronized( this ){
if ( publish_next_asyc_pending ){
return;
}
publish_next_asyc_pending = true;
}
SimpleTimer.addEvent(
"subs:pn:async",
SystemTime.getCurrentTime() + 60*1000,
new TimerEventPerformer()
{
public void
perform(
TimerEvent event)
{
synchronized( SubscriptionManagerImpl.this ){
publish_next_asyc_pending = false;
}
publishAssociations();
}
});
return;
}
publishAssociations();
}
protected void
subscriptionUpdated()
{
if ( dht_plugin != null ){
publishSubscriptions();
}
}
protected void
publishSubscriptions()
{
List shuffled_subs;
synchronized( this ){
if ( publish_subscription_active ){
return;
}
shuffled_subs = new ArrayList( subscriptions );
publish_subscription_active = true;
}
boolean publish_initiated = false;
try{
Collections.shuffle( shuffled_subs );
for (int i=0;i<shuffled_subs.size();i++){
SubscriptionImpl sub = (SubscriptionImpl)shuffled_subs.get( i );
if ( sub.isSubscribed() && sub.isPublic() && !sub.getPublished()){
sub.setPublished( true );
publishSubscription( sub );
publish_initiated = true;
break;
}
}
}finally{
if ( !publish_initiated ){
log( "Publishing Subscriptions Complete" );
synchronized( this ){
publish_subscription_active = false;
}
}
}
}
protected void
publishSubscription(
final SubscriptionImpl subs )
{
log( "Checking subscription publication '" + subs.getString() + "'" );
byte[] sub_id = subs.getShortID();
int sub_version = subs.getVersion();
final String key = "subscription:publish:" + ByteFormatter.encodeString( sub_id ) + ":" + sub_version;
dht_plugin.get(
key.getBytes(),
"Subs presence read: " + ByteFormatter.encodeString( sub_id ) + ":" + sub_version,
DHTPlugin.FLAG_SINGLE_VALUE,
24,
60*1000,
false,
false,
new DHTPluginOperationListener()
{
private int hits;
private boolean diversified;
public boolean
diversified()
{
diversified = true;
return( false );
}
public void
starts(
byte[] key )
{
}
public void
valueRead(
DHTPluginContact originator,
DHTPluginValue value )
{
byte[] data = value.getValue();
try{
Map details = decodeSubscriptionDetails( data );
if ( subs.getVerifiedPublicationVersion( details ) == subs.getVersion()){
hits++;
}
}catch( Throwable e ){
}
}
public void
valueWritten(
DHTPluginContact target,
DHTPluginValue value )
{
}
public void
complete(
byte[] original_key,
boolean timeout_occurred )
{
log( "Checked subscription publication '" + subs.getString() + "' - hits=" + hits + ",div=" + diversified );
if ( hits < 10 && !diversified ){
log( " Publishing subscription '" + subs.getString() + ", existing=" + hits );
try{
byte[] put_value = encodeSubscriptionDetails( subs );
if ( put_value.length < DHTPlugin.MAX_VALUE_SIZE ){
byte flags = DHTPlugin.FLAG_SINGLE_VALUE;
if ( hits < 3 && !diversified ){
flags |= DHTPlugin.FLAG_PRECIOUS;
}
dht_plugin.put(
key.getBytes(),
"Subs presence write: " + Base32.encode( subs.getShortID() ) + ":" + subs.getVersion(),
put_value,
flags,
new DHTPluginOperationListener()
{
public boolean
diversified()
{
return( true );
}
public void
starts(
byte[] key )
{
}
public void
valueRead(
DHTPluginContact originator,
DHTPluginValue value )
{
}
public void
valueWritten(
DHTPluginContact target,
DHTPluginValue value )
{
}
public void
complete(
byte[] key,
boolean timeout_occurred )
{
log( " completed '" + subs.getString() + "'" );
publishNext();
}
});
}else{
publishNext();
}
}catch( Throwable e ){
Debug.printStackTrace( e );
publishNext();
}
}else{
log( " Not publishing subscription '" + subs.getString() + "', existing =" + hits );
publishNext();
}
}
protected void
publishNext()
{
synchronized( SubscriptionManagerImpl.this ){
publish_subscription_active = false;
}
publishSubscriptions();
}
});
}
protected void
updateSubscription(
final SubscriptionImpl subs,
final int new_version )
{
log( "Subscription " + subs.getString() + " - higher version found: " + new_version );
if ( !subs.canAutoUpgradeCheck()){
log( " Checked too recently or not updateable, ignoring" );
return;
}
if ( subs.getHighestUserPromptedVersion() >= new_version ){
log( " User has already been prompted for version " + new_version + " so ignoring" );
return;
}
byte[] sub_id = subs.getShortID();
try{
PlatformSubscriptionsMessenger.subscriptionDetails details = PlatformSubscriptionsMessenger.getSubscriptionBySID( sub_id );
if ( !askIfCanUpgrade( subs, new_version )){
return;
}
VuzeFileHandler vfh = VuzeFileHandler.getSingleton();
VuzeFile vf = vfh.loadVuzeFile( Base64.decode( details.getContent()));
vfh.handleFiles( new VuzeFile[]{ vf }, VuzeFileComponent.COMP_TYPE_SUBSCRIPTION );
return;
}catch( Throwable e ){
log( "Failed to read subscription from platform, trying DHT" );
}
log( "Checking subscription '" + subs.getString() + "' upgrade to version " + new_version );
final String key = "subscription:publish:" + ByteFormatter.encodeString( sub_id ) + ":" + new_version;
dht_plugin.get(
key.getBytes(),
"Subs update read: " + Base32.encode( sub_id ) + ":" + new_version,
DHTPlugin.FLAG_SINGLE_VALUE,
12,
60*1000,
false,
false,
new DHTPluginOperationListener()
{
private byte[] verified_hash;
private int verified_size;
public boolean
diversified()
{
return( true );
}
public void
starts(
byte[] key )
{
}
public void
valueRead(
DHTPluginContact originator,
DHTPluginValue value )
{
byte[] data = value.getValue();
try{
Map details = decodeSubscriptionDetails( data );
if ( verified_hash == null &&
subs.getVerifiedPublicationVersion( details ) == new_version ){
verified_hash = SubscriptionImpl.getPublicationHash( details );
verified_size = SubscriptionImpl.getPublicationSize( details );
}
}catch( Throwable e ){
}
}
public void
valueWritten(
DHTPluginContact target,
DHTPluginValue value )
{
}
public void
complete(
byte[] original_key,
boolean timeout_occurred )
{
if ( verified_hash != null ){
log( " Subscription '" + subs.getString() + " upgrade verified as authentic" );
updateSubscription( subs, new_version, verified_hash, verified_size );
}else{
log( " Subscription '" + subs.getString() + " upgrade not verified" );
}
}
});
}
protected byte[]
encodeSubscriptionDetails(
SubscriptionImpl subs )
throws IOException
{
Map details = subs.getPublicationDetails();
// inject a random element so we can count occurrences properly (as the DHT logic
// removes duplicates)
details.put( "!", new Long( random_seed ));
byte[] encoded = BEncoder.encode( details );
ByteArrayOutputStream baos = new ByteArrayOutputStream();
GZIPOutputStream os = new GZIPOutputStream( baos );
os.write( encoded );
os.close();
byte[] compressed = baos.toByteArray();
byte header;
byte[] data;
if ( compressed.length < encoded.length ){
header = 1;
data = compressed;
}else{
header = 0;
data = encoded;
}
byte[] result = new byte[data.length+1];
result[0] = header;
System.arraycopy( data, 0, result, 1, data.length );
return( result );
}
protected Map
decodeSubscriptionDetails(
byte[] data )
throws IOException
{
byte[] to_decode;
if ( data[0] == 0 ){
to_decode = new byte[ data.length-1 ];
System.arraycopy( data, 1, to_decode, 0, data.length - 1 );
}else{
GZIPInputStream is = new GZIPInputStream(new ByteArrayInputStream( data, 1, data.length - 1 ));
to_decode = FileUtil.readInputStreamAsByteArray( is );
is.close();
}
Map res = BDecoder.decode( to_decode );
// remove any injected random seed
res.remove( "!" );
return( res );
}
protected void
updateSubscription(
final SubscriptionImpl subs,
final int update_version,
final byte[] update_hash,
final int update_size )
{
log( "Subscription " + subs.getString() + " - update hash=" + ByteFormatter.encodeString( update_hash ) + ", size=" + update_size );
new AEThread2( "SubsUpdate", true )
{
public void
run()
{
try{
Object[] res = downloadTorrent( update_hash, update_size );
if ( res != null ){
updateSubscription( subs, update_version, (TOTorrent)res[0], (InetSocketAddress)res[1] );
}
}catch( Throwable e ){
log( " update failed", e );
}
}
}.start();
}
protected Object[]
downloadTorrent(
byte[] hash,
int update_size )
{
if ( !isSubsDownloadEnabled()){
log( " Can't download subscription " + Base32.encode( hash ) + " as feature disabled" );
return( null );
}
final MagnetPlugin magnet_plugin = getMagnetPlugin();
if ( magnet_plugin == null ){
log( " Can't download, no magnet plugin" );
return( null );
}
try{
final InetSocketAddress[] sender = { null };
byte[] torrent_data = magnet_plugin.download(
new MagnetPluginProgressListener()
{
public void
reportSize(
long size )
{
}
public void
reportActivity(
String str )
{
log( " MagnetDownload: " + str );
}
public void
reportCompleteness(
int percent )
{
}
public void
reportContributor(
InetSocketAddress address )
{
synchronized( sender ){
sender[0] = address;
}
}
public boolean
verbose()
{
return( false );
}
public boolean
cancelled()
{
return( false );
}
},
hash,
"",
new InetSocketAddress[0],
300*1000,
MagnetPlugin.FL_DISABLE_MD_LOOKUP );
if ( torrent_data == null ){
log( " download failed - timeout" );
return( null );
}
log( "Subscription torrent downloaded" );
TOTorrent torrent = TOTorrentFactory.deserialiseFromBEncodedByteArray( torrent_data );
// update size is just that of signed content, torrent itself is .vuze file
// so take this into account
if ( torrent.getSize() > update_size + 10*1024 ){
log( "Subscription download abandoned, torrent size is " + torrent.getSize() + ", underlying data size is " + update_size );
return( null );
}
if ( torrent.getSize() > 4*1024*1024 ){
log( "Subscription download abandoned, torrent size is too large (" + torrent.getSize() + ")" );
return( null );
}
synchronized( sender ){
return( new Object[]{ torrent, sender[0] });
}
}catch( Throwable e ){
log( " download failed", e );
return( null );
}
}
protected void
downloadSubscription(
final TOTorrent torrent,
final InetSocketAddress peer,
byte[] subs_id,
int version,
String name,
final downloadListener listener )
{
try{
// testing purposes, see if local exists
LightWeightSeed lws = LightWeightSeedManager.getSingleton().get( new HashWrapper( torrent.getHash()));
if ( lws != null ){
log( "Light weight seed found" );
listener.complete( lws.getDataLocation());
}else{
String sid = ByteFormatter.encodeString( subs_id );
File dir = getSubsDir();
dir = new File( dir, "temp" );
if ( !dir.exists()){
if ( !dir.mkdirs()){
throw( new IOException( "Failed to create dir '" + dir + "'" ));
}
}
final File torrent_file = new File( dir, sid + "_" + version + ".torrent" );
final File data_file = new File( dir, sid + "_" + version + ".vuze" );
PluginInterface pi = PluginInitializer.getDefaultInterface();
final DownloadManager dm = pi.getDownloadManager();
Download download = dm.getDownload( torrent.getHash());
if ( download == null ){
log( "Adding download for subscription '" + new String(torrent.getName()) + "'" );
boolean is_update = getSubscriptionFromSID( subs_id ) != null;
PlatformTorrentUtils.setContentTitle(torrent, (is_update?"Update":"Download") + " for subscription '" + name + "'" );
// PlatformTorrentUtils.setContentThumbnail(torrent, thumbnail);
TorrentUtils.setFlag( torrent, TorrentUtils.TORRENT_FLAG_LOW_NOISE, true );
Torrent t = new TorrentImpl( torrent );
t.setDefaultEncoding();
t.writeToFile( torrent_file );
download = dm.addDownload( t, torrent_file, data_file );
download.setFlag( Download.FLAG_DISABLE_AUTO_FILE_MOVE, true );
download.setBooleanAttribute( ta_subs_download, true );
Map rd = listener.getRecoveryData();
if ( rd != null ){
download.setMapAttribute( ta_subs_download_rd, rd );
}
}else{
log( "Existing download found for subscription '" + new String(torrent.getName()) + "'" );
}
final Download f_download = download;
final TimerEventPeriodic[] event = { null };
event[0] =
SimpleTimer.addPeriodicEvent(
"SM:cancelTimer",
10*1000,
new TimerEventPerformer()
{
private long start_time = SystemTime.getMonotonousTime();
public void
perform(
TimerEvent ev )
{
boolean kill = false;
try{
Download download = dm.getDownload( torrent.getHash());
if ( listener.isCancelled() || download == null ){
kill = true;
}else{
int state = download.getState();
if ( state == Download.ST_ERROR ){
log( "Download entered error state, removing" );
kill = true;
}else{
long now = SystemTime.getMonotonousTime();
long running_for = now - start_time;
if ( running_for > 2*60*1000 ){
DownloadScrapeResult scrape = download.getLastScrapeResult();
if ( scrape == null || scrape.getSeedCount() <= 0 ){
log( "Download has no seeds, removing" );
kill = true;
}
}else if ( running_for > 4*60*1000 ){
if ( download.getStats().getDownloaded() == 0 ){
log( "Download has zero downloaded, removing" );
kill = true;
}
}else if ( running_for > 10*60*1000 ){
log( "Download hasn't completed in permitted time, removing" );
kill = true;
}
}
}
}catch( Throwable e ){
log( "Download failed", e );
kill = true;
}
if ( kill && event[0] != null ){
try{
event[0].cancel();
if ( !listener.isCancelled()){
listener.failed( new SubscriptionException( "Download abandoned" ));
}
}finally{
removeDownload( f_download, true );
torrent_file.delete();
}
}
}
});
download.addCompletionListener(
new DownloadCompletionListener()
{
public void
onCompletion(
Download d )
{
listener.complete( d, torrent_file );
}
});
if ( download.isComplete()){
listener.complete( download, torrent_file );
}else{
download.setForceStart( true );
if ( peer != null ){
download.addPeerListener(
new DownloadPeerListener()
{
public void
peerManagerAdded(
Download download,
PeerManager peer_manager )
{
InetSocketAddress tcp = AddressUtils.adjustTCPAddress( peer, true );
InetSocketAddress udp = AddressUtils.adjustUDPAddress( peer, true );
log( " Injecting peer into download: " + tcp );
peer_manager.addPeer( tcp.getAddress().getHostAddress(), tcp.getPort(), udp.getPort(), true );
}
public void
peerManagerRemoved(
Download download,
PeerManager peer_manager )
{
}
});
}
}
}
}catch( Throwable e ){
log( "Failed to add download", e );
listener.failed( e );
}
}
protected interface
downloadListener
{
public void
complete(
File data_file );
public void
complete(
Download download,
File torrent_file );
public void
failed(
Throwable error );
public Map
getRecoveryData();
public boolean
isCancelled();
}
protected void
updateSubscription(
final SubscriptionImpl subs,
final int new_version,
TOTorrent torrent,
InetSocketAddress peer )
{
log( "Subscription " + subs.getString() + " - update torrent: " + new String( torrent.getName()));
if ( !askIfCanUpgrade( subs, new_version )){
return;
}
downloadSubscription(
torrent,
peer,
subs.getShortID(),
new_version,
subs.getName(false),
new downloadListener()
{
public void
complete(
File data_file )
{
updateSubscription( subs, data_file );
}
public void
complete(
Download download,
File torrent_file )
{
updateSubscription( subs, download, torrent_file, new File( download.getSavePath()));
}
public void
failed(
Throwable error )
{
log( "Failed to download subscription", error );
}
public Map
getRecoveryData()
{
Map rd = new HashMap();
rd.put( "sid", subs.getShortID());
rd.put( "ver", new Long( new_version ));
return( rd );
}
public boolean
isCancelled()
{
return( false );
}
});
}
protected boolean
askIfCanUpgrade(
SubscriptionImpl subs,
int new_version )
{
subs.setHighestUserPromptedVersion( new_version );
UIManager ui_manager = StaticUtilities.getUIManager( 120*1000 );
String details = MessageText.getString(
"subscript.add.upgradeto.desc",
new String[]{ String.valueOf(new_version), subs.getName()});
long res = ui_manager.showMessageBox(
"subscript.add.upgrade.title",
"!" + details + "!",
UIManagerEvent.MT_YES | UIManagerEvent.MT_NO );
if ( res != UIManagerEvent.MT_YES ){
log( " User declined upgrade" );
return( false );
}
return( true );
}
protected boolean
recoverSubscriptionUpdate(
Download download,
final Map rd )
{
byte[] sid = (byte[])rd.get( "sid" );
int version = ((Long)rd.get( "ver" )).intValue();
final SubscriptionImpl subs = getSubscriptionFromSID( sid );
if ( subs == null ){
log( "Can't recover '" + download.getName() + "' - subscription " + ByteFormatter.encodeString( sid ) + " not found" );
return( false );
}
downloadSubscription(
((TorrentImpl)download.getTorrent()).getTorrent(),
null,
subs.getShortID(),
version,
subs.getName(false),
new downloadListener()
{
public void
complete(
File data_file )
{
updateSubscription( subs, data_file );
}
public void
complete(
Download download,
File torrent_file )
{
updateSubscription( subs, download, torrent_file, new File( download.getSavePath()));
}
public void
failed(
Throwable error )
{
log( "Failed to download subscription", error );
}
public Map
getRecoveryData()
{
return( rd );
}
public boolean
isCancelled()
{
return( false );
}
});
return( true );
}
protected void
updateSubscription(
SubscriptionImpl subs,
Download download,
File torrent_file,
File data_file )
{
try{
removeDownload( download, false );
try{
updateSubscription( subs, data_file );
}finally{
if ( !data_file.delete()){
log( "Failed to delete update file '" + data_file + "'" );
}
if ( !torrent_file.delete()){
log( "Failed to delete update torrent '" + torrent_file + "'" );
}
}
}catch( Throwable e ){
log( "Failed to remove update download", e );
}
}
protected void
removeDownload(
Download download,
boolean remove_data )
{
try{
download.stop();
}catch( Throwable e ){
}
try{
download.remove( true, remove_data );
log( "Removed download '" + download.getName() + "'" );
}catch( Throwable e ){
log( "Failed to remove download '" + download.getName() + "'", e );
}
}
protected void
updateSubscription(
SubscriptionImpl subs,
File data_location )
{
log( "Updating subscription '" + subs.getString() + " using '" + data_location + "'" );
VuzeFileHandler vfh = VuzeFileHandler.getSingleton();
VuzeFile vf = vfh.loadVuzeFile( data_location.getAbsolutePath());
vfh.handleFiles( new VuzeFile[]{ vf }, VuzeFileComponent.COMP_TYPE_SUBSCRIPTION );
}
protected MagnetPlugin
getMagnetPlugin()
{
PluginInterface pi = AzureusCoreFactory.getSingleton().getPluginManager().getPluginInterfaceByClass( MagnetPlugin.class );
if ( pi == null ){
return( null );
}
return((MagnetPlugin)pi.getPlugin());
}
protected Engine
getEngine(
SubscriptionImpl subs,
Map json_map,
boolean local_only )
throws SubscriptionException
{
long id = ((Long)json_map.get( "engine_id" )).longValue();
Engine engine = MetaSearchManagerFactory.getSingleton().getMetaSearch().getEngine( id );
if ( engine != null ){
return( engine );
}
if ( !local_only ){
try{
if ( id >= 0 && id < Integer.MAX_VALUE ){
log( "Engine " + id + " not present, loading" );
// vuze template but user hasn't yet loaded it
try{
engine = MetaSearchManagerFactory.getSingleton().getMetaSearch().addEngine( id );
return( engine );
}catch( Throwable e ){
throw( new SubscriptionException( "Failed to load engine '" + id + "'", e ));
}
}
}catch( Throwable e ){
log( "Failed to load search template", e );
}
}
engine = subs.extractEngine( json_map, id );
if ( engine != null ){
return( engine );
}
throw( new SubscriptionException( "Failed to extract engine id " + id ));
}
protected SubscriptionResultImpl[]
loadResults(
SubscriptionImpl subs )
{
List results = new ArrayList();
try{
File f = getResultsFile( subs );
Map map = FileUtil.readResilientFile( f );
List list = (List)map.get( "results" );
if ( list != null ){
SubscriptionHistoryImpl history = (SubscriptionHistoryImpl)subs.getHistory();
for (int i=0;i<list.size();i++){
Map result_map =(Map)list.get(i);
try{
SubscriptionResultImpl result = new SubscriptionResultImpl( history, result_map );
results.add( result );
}catch( Throwable e ){
log( "Failed to decode result '" + result_map + "'", e );
}
}
}
}catch( Throwable e ){
log( "Failed to load results for '" + subs.getName() + "' - continuing with empty result set", e );
}
return((SubscriptionResultImpl[])results.toArray( new SubscriptionResultImpl[results.size()] ));
}
protected void
setCategoryOnExisting(
SubscriptionImpl subscription,
String old_category,
String new_category )
{
PluginInterface default_pi = PluginInitializer.getDefaultInterface();
Download[] downloads = default_pi.getDownloadManager().getDownloads();
for ( Download d: downloads ){
if ( subscriptionExists( d, subscription )){
String existing = d.getAttribute( ta_category );
if ( existing == null || existing.equals( old_category )){
d.setAttribute( ta_category, new_category );
}
}
}
}
public int
getMaxNonDeletedResults()
{
return( COConfigurationManager.getIntParameter( CONFIG_MAX_RESULTS ));
}
public void
setMaxNonDeletedResults(
int max )
{
if ( max != getMaxNonDeletedResults()){
COConfigurationManager.setParameter( CONFIG_MAX_RESULTS, max );
}
}
public boolean
getAutoStartDownloads()
{
return( COConfigurationManager.getBooleanParameter( CONFIG_AUTO_START_DLS ));
}
public void
setAutoStartDownloads(
boolean auto_start )
{
if ( auto_start != getAutoStartDownloads()){
COConfigurationManager.setParameter( CONFIG_AUTO_START_DLS, auto_start );
}
}
public int
getAutoStartMinMB()
{
return( COConfigurationManager.getIntParameter( CONFIG_AUTO_START_MIN_MB ));
}
public void
setAutoStartMinMB(
int mb )
{
if ( mb != getAutoStartMinMB()){
COConfigurationManager.setParameter( CONFIG_AUTO_START_MIN_MB, mb );
}
}
public int
getAutoStartMaxMB()
{
return( COConfigurationManager.getIntParameter( CONFIG_AUTO_START_MAX_MB ));
}
public void
setAutoStartMaxMB(
int mb )
{
if ( mb != getAutoStartMaxMB()){
COConfigurationManager.setParameter( CONFIG_AUTO_START_MAX_MB, mb );
}
}
protected boolean
shouldAutoStart(
Torrent torrent )
{
if ( getAutoStartDownloads()){
long min = getAutoStartMinMB()*1024*1024L;
long max = getAutoStartMaxMB()*1024*1024L;
if ( min <= 0 && max <= 0 ){
return( true );
}
long size = torrent.getSize();
if ( min > 0 && size < min ){
return( false );
}
if ( max > 0 && size > max ){
return( false );
}
return( true );
}else{
return( false );
}
}
protected void
saveResults(
SubscriptionImpl subs,
SubscriptionResultImpl[] results )
{
try{
File f = getResultsFile( subs );
Map map = new HashMap();
List list = new ArrayList( results.length );
map.put( "results", list );
for (int i=0;i<results.length;i++){
list.add( results[i].toBEncodedMap());
}
FileUtil.writeResilientFile( f, map );
}catch( Throwable e ){
log( "Failed to save results for '" + subs.getName(), e );
}
}
private void
loadConfig()
{
if ( !FileUtil.resilientConfigFileExists( CONFIG_FILE )){
return;
}
log( "Loading configuration" );
boolean some_are_mine = false;
synchronized( this ){
Map map = FileUtil.readResilientConfigFile( CONFIG_FILE );
List l_subs = (List)map.get( "subs" );
if ( l_subs != null ){
for (int i=0;i<l_subs.size();i++){
Map m = (Map)l_subs.get(i);
try{
SubscriptionImpl sub = new SubscriptionImpl( this, m );
int index = Collections.binarySearch(subscriptions, sub, new Comparator<Subscription>() {
public int compare(Subscription arg0, Subscription arg1) {
return arg0.getID().compareTo(arg1.getID());
}
});
if (index < 0) {
index = -1 * index - 1; // best guess
subscriptions.add( index, sub );
}
if ( sub.isMine()){
some_are_mine = true;
}
log( " loaded " + sub.getString());
}catch( Throwable e ){
log( "Failed to import subscription from " + m, e );
}
}
}
}
if ( some_are_mine ){
addMetaSearchListener();
}
}
protected void
configDirty(
SubscriptionImpl subs )
{
changeSubscription( subs );
configDirty();
}
protected void
configDirty()
{
synchronized( this ){
if ( config_dirty ){
return;
}
config_dirty = true;
new DelayedEvent(
"Subscriptions:save", 5000,
new AERunnable()
{
public void
runSupport()
{
synchronized( SubscriptionManagerImpl.this ){
if ( !config_dirty ){
return;
}
saveConfig();
}
}
});
}
}
protected void
saveConfig()
{
log( "Saving configuration" );
synchronized( this ){
config_dirty = false;
if ( subscriptions.size() == 0 ){
FileUtil.deleteResilientConfigFile( CONFIG_FILE );
}else{
Map map = new HashMap();
List l_subs = new ArrayList();
map.put( "subs", l_subs );
Iterator it = subscriptions.iterator();
while( it.hasNext()){
SubscriptionImpl sub = (SubscriptionImpl)it.next();
try{
l_subs.add( sub.toMap());
}catch( Throwable e ){
log( "Failed to save subscription " + sub.getString(), e );
}
}
FileUtil.writeResilientConfigFile( CONFIG_FILE, map );
}
}
}
private AEDiagnosticsLogger
getLogger()
{
// sync not required (and has caused deadlock) as AEDiagnostics handles singleton
if ( logger == null ){
logger = AEDiagnostics.getLogger( LOGGER_NAME );
}
return( logger );
}
public void
log(
String s,
Throwable e )
{
AEDiagnosticsLogger diag_logger = getLogger();
diag_logger.log( s );
diag_logger.log( e );
}
public void
log(
String s )
{
AEDiagnosticsLogger diag_logger = getLogger();
diag_logger.log( s );
}
public void
addListener(
SubscriptionManagerListener listener )
{
listeners.add( listener );
}
public void
removeListener(
SubscriptionManagerListener listener )
{
listeners.remove( listener );
}
public void
generate(
IndentWriter writer )
{
writer.println( "Subscriptions" );
try{
writer.indent();
Subscription[] subs = getSubscriptions();
for (int i=0;i<subs.length;i++){
SubscriptionImpl sub = (SubscriptionImpl)subs[i];
sub.generate( writer );
}
}finally{
writer.exdent();
}
}
private class
searchMatcher
{
private String[] bits;
private int[] bit_types;
private Pattern[] bit_patterns;
protected
searchMatcher(
String term )
{
bits = Constants.PAT_SPLIT_SPACE.split(term.toLowerCase() );
bit_types = new int[bits.length];
bit_patterns = new Pattern[bits.length];
for (int i=0;i<bits.length;i++){
String bit = bits[i] = bits[i].trim();
if ( bit.length() > 0 ){
char c = bit.charAt(0);
if ( c == '+' ){
bit_types[i] = 1;
bit = bits[i] = bit.substring(1);
}else if ( c == '-' ){
bit_types[i] = 2;
bit = bits[i] = bit.substring(1);
}
if ( bit.startsWith( "(" ) && bit.endsWith((")"))){
bit = bit.substring( 1, bit.length()-1 );
try{
bit_patterns[i] = Pattern.compile( bit, Pattern.CASE_INSENSITIVE );
}catch( Throwable e ){
}
}else if ( bit.contains( "|" )){
try{
bit_patterns[i] = Pattern.compile( bit, Pattern.CASE_INSENSITIVE );
}catch( Throwable e ){
}
}
}
}
}
public boolean
matches(
String str )
{
// term is made up of space separated bits - all bits must match
// each bit can be prefixed by + or -, a leading - means 'bit doesn't match'. + doesn't mean anything
// each bit (with prefix removed) can be "(" regexp ")"
// if bit isn't regexp but has "|" in it it is turned into a regexp so a|b means 'a or b'
str = str.toLowerCase();
boolean match = true;
boolean at_least_one = false;
for (int i=0;i<bits.length;i++){
String bit = bits[i];
if ( bit.length() > 0 ){
boolean hit;
if ( bit_patterns[i] == null ){
hit = str.contains( bit );
}else{
hit = bit_patterns[i].matcher( str ).find();
}
int type = bit_types[i];
if ( hit ){
if ( type == 2 ){
match = false;
break;
}else{
at_least_one = true;
}
}else{
if ( type == 2 ){
at_least_one = true;
}else{
match = false;
break;
}
}
}
}
boolean res = match && at_least_one;
return( res );
}
}
public static void
main(
String[] args )
{
final String NAME = "lalalal";
final String URL_STR = "http://www.vuze.com/feed/publisher/ALL/1";
try{
//AzureusCoreFactory.create();
/*
Subscription subs =
getSingleton(true).createSingletonRSS(
NAME,
new URL( URL_STR ),
240 );
subs.getVuzeFile().write( new File( "C:\\temp\\srss.vuze" ));
subs.remove();
*/
VuzeFile vf = VuzeFileHandler.getSingleton().create();
Map map = new HashMap();
map.put( "name", NAME );
map.put( "url", URL_STR );
map.put( "public", new Long( 0 ));
map.put( "check_interval_mins", new Long( 345 ));
vf.addComponent( VuzeFileComponent.COMP_TYPE_SUBSCRIPTION_SINGLETON, map );
vf.write( new File( "C:\\temp\\srss_2.vuze" ) );
}catch( Throwable e ){
e.printStackTrace();
}
}
}