/* * 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.File; import java.io.IOException; import java.net.URL; import java.security.KeyPair; import java.util.*; import org.minicastle.util.encoders.Base64; import org.gudy.azureus2.core3.internat.MessageText; import org.gudy.azureus2.core3.torrent.TOTorrent; import org.gudy.azureus2.core3.torrent.TOTorrentCreator; import org.gudy.azureus2.core3.torrent.TOTorrentFactory; import org.gudy.azureus2.core3.util.AEThread2; import org.gudy.azureus2.core3.util.BDecoder; import org.gudy.azureus2.core3.util.BEncoder; import org.gudy.azureus2.core3.util.Base32; import org.gudy.azureus2.core3.util.ByteFormatter; import org.gudy.azureus2.core3.util.Debug; import org.gudy.azureus2.core3.util.FileUtil; import org.gudy.azureus2.core3.util.HashWrapper; import org.gudy.azureus2.core3.util.IndentWriter; import org.gudy.azureus2.core3.util.LightHashMap; import org.gudy.azureus2.core3.util.RandomUtils; import org.gudy.azureus2.core3.util.SystemTime; import org.gudy.azureus2.core3.util.TorrentUtils; import org.json.simple.JSONObject; import com.aelitis.azureus.core.lws.LightWeightSeed; import com.aelitis.azureus.core.lws.LightWeightSeedAdapter; import com.aelitis.azureus.core.lws.LightWeightSeedManager; import com.aelitis.azureus.core.metasearch.Engine; import com.aelitis.azureus.core.metasearch.MetaSearchManagerFactory; import com.aelitis.azureus.core.security.CryptoECCUtils; import com.aelitis.azureus.core.subs.Subscription; import com.aelitis.azureus.core.subs.SubscriptionException; import com.aelitis.azureus.core.subs.SubscriptionHistory; import com.aelitis.azureus.core.subs.SubscriptionListener; import com.aelitis.azureus.core.subs.SubscriptionManager; import com.aelitis.azureus.core.subs.SubscriptionPopularityListener; import com.aelitis.azureus.core.subs.SubscriptionResult; import com.aelitis.azureus.core.util.CopyOnWriteList; import com.aelitis.azureus.core.vuzefile.VuzeFile; import com.aelitis.azureus.core.vuzefile.VuzeFileHandler; import com.aelitis.azureus.util.ImportExportUtils; import com.aelitis.azureus.util.JSONUtils; public class SubscriptionImpl implements Subscription { public static final int ADD_TYPE_CREATE = 1; public static final int ADD_TYPE_IMPORT = 2; public static final int ADD_TYPE_LOOKUP = 3; private static final int MAX_ASSOCIATIONS = 256; private static final int MIN_RECENT_ASSOC_TO_RETAIN = 16; //private static final byte[] GENERIC_PUBLIC_KEY = {(byte)0x04,(byte)0xd0,(byte)0x1a,(byte)0xd9,(byte)0xb9,(byte)0x99,(byte)0xd8,(byte)0x49,(byte)0x15,(byte)0x5f,(byte)0xe9,(byte)0x6b,(byte)0x3c,(byte)0xd8,(byte)0x18,(byte)0x81,(byte)0xf7,(byte)0x92,(byte)0x15,(byte)0x3f,(byte)0x24,(byte)0xaa,(byte)0x35,(byte)0x6f,(byte)0x52,(byte)0x01,(byte)0x79,(byte)0x2e,(byte)0x93,(byte)0xf6,(byte)0xf1,(byte)0x57,(byte)0x13,(byte)0x2a,(byte)0x3c,(byte)0x31,(byte)0x66,(byte)0xa5,(byte)0x34,(byte)0x9f,(byte)0x79,(byte)0x62,(byte)0x04,(byte)0x31,(byte)0x68,(byte)0x37,(byte)0x8f,(byte)0x77,(byte)0x5c}; // private static final byte[] GENERIC_PRIVATE_KEY = {(byte)0x71,(byte)0xc3,(byte)0xe8,(byte)0x6c,(byte)0x56,(byte)0xbb,(byte)0x30,(byte)0x14,(byte)0x9e,(byte)0x19,(byte)0xa5,(byte)0x3d,(byte)0xcb,(byte)0x47,(byte)0xbb,(byte)0x6d,(byte)0x57,(byte)0x57,(byte)0xd3,(byte)0x59,(byte)0xce,(byte)0x8f,(byte)0x79,(byte)0xe5}; protected static byte[] intToBytes( int version ) { return( new byte[]{ (byte)(version>>24), (byte)(version>>16),(byte)(version>>8),(byte)version } ); } protected static int bytesToInt( byte[] bytes ) { return( (bytes[0]<<24)&0xff000000 | (bytes[1] << 16)&0x00ff0000 | (bytes[2] << 8)&0x0000ff00 | bytes[3]&0x000000ff ); } private SubscriptionManagerImpl manager; private byte[] public_key; private byte[] private_key; private String name; private String name_ex; private int version; private int az_version; private boolean is_public; private Map singleton_details; private byte[] hash; private byte[] sig; private int sig_data_size; private int add_type; private long add_time; private boolean is_subscribed; private int highest_prompted_version; private byte[] short_id; private String id; private List associations = new ArrayList(); private int fixed_random; private long popularity = -1; private long last_auto_upgrade_check = -1; private boolean published; private boolean server_published; private boolean server_publication_outstanding; private boolean singleton_sp_attempted; private String local_name; private LightWeightSeed lws; private int lws_skip_check; private boolean destroyed; private Map history_map; private Map schedule_map; private Map user_data = new LightHashMap(); private final SubscriptionHistoryImpl history; private String referer; private CopyOnWriteList listeners = new CopyOnWriteList(); private Map verify_cache_details; private boolean verify_cache_result; private String creator_ref; private String category; private long tag_id = -1; protected static String getSkeletonJSON( Engine engine, int check_interval_mins ) { JSONObject map = new JSONObject(); map.put( "engine_id", new Long( engine.getId())); map.put( "search_term", "" ); map.put( "filters", new HashMap()); map.put( "options", new HashMap()); Map schedule = new HashMap(); schedule.put( "interval", new Long( check_interval_mins )); List days = new ArrayList(); for (int i=1;i<=7;i++){ days.add( String.valueOf(i)); } schedule.put( "days", days ); map.put( "schedule", schedule ); embedEngines( map, engine ); return( JSONUtils.encodeToJSON( map )); } // new subs constructor protected SubscriptionImpl( SubscriptionManagerImpl _manager, String _name, boolean _public, Map _singleton_details, String _json_content, int _add_type ) throws SubscriptionException { manager = _manager; history_map = new HashMap(); history = new SubscriptionHistoryImpl( manager, this ); name = _name; is_public = _public; singleton_details = _singleton_details; version = 1; az_version = AZ_VERSION; add_type = _add_type; add_time = SystemTime.getCurrentTime(); is_subscribed = true; try{ KeyPair kp = CryptoECCUtils.createKeys(); public_key = CryptoECCUtils.keyToRawdata( kp.getPublic()); private_key = CryptoECCUtils.keyToRawdata( kp.getPrivate()); fixed_random = RandomUtils.nextInt(); init(); String json_content = embedEngines( _json_content ); SubscriptionBodyImpl body = new SubscriptionBodyImpl( manager, name, is_public, json_content, public_key, version, az_version, singleton_details ); syncToBody( body ); }catch( Throwable e ){ throw( new SubscriptionException( "Failed to create subscription", e )); } } // cache detail constructor protected SubscriptionImpl( SubscriptionManagerImpl _manager, Map map ) throws IOException { manager = _manager; fromMap( map ); history = new SubscriptionHistoryImpl( manager, this ); init(); } // import constructor protected SubscriptionImpl( SubscriptionManagerImpl _manager, SubscriptionBodyImpl _body, int _add_type, boolean _is_subscribed ) throws SubscriptionException { manager = _manager; history_map = new HashMap(); history = new SubscriptionHistoryImpl( manager, this ); syncFromBody( _body ); add_type = _add_type; add_time = SystemTime.getCurrentTime(); is_subscribed = _is_subscribed; fixed_random = RandomUtils.nextInt(); init(); syncToBody( _body ); } protected void syncFromBody( SubscriptionBodyImpl body ) throws SubscriptionException { public_key = body.getPublicKey(); version = body.getVersion(); az_version = body.getAZVersion(); name = body.getName(); is_public = body.isPublic(); singleton_details = body.getSingletonDetails(); if ( az_version > AZ_VERSION ){ throw( new SubscriptionException( MessageText.getString( "subscription.version.bad", new String[]{ name }))); } } protected void syncToBody( SubscriptionBodyImpl body ) throws SubscriptionException { // this picks up latest values of version, name + is_public from here body.writeVuzeFile( this ); hash = body.getHash(); sig = body.getSig(); sig_data_size = body.getSigDataSize(); } protected Map toMap() throws IOException { synchronized( this ){ Map map = new HashMap(); map.put( "name", name.getBytes( "UTF-8" )); map.put( "public_key", public_key ); map.put( "version", new Long( version )); map.put( "az_version", new Long( az_version )); map.put( "is_public", new Long( is_public?1:0 )); if ( singleton_details != null ){ map.put( "sin_details", singleton_details ); map.put( "spa", new Long( singleton_sp_attempted?1:0 )); } if ( local_name != null ){ map.put( "local_name", local_name ); } // body data map.put( "hash", hash ); map.put( "sig", sig ); map.put( "sig_data_size", new Long( sig_data_size )); // local data if ( private_key != null ){ map.put( "private_key", private_key ); } map.put( "add_type", new Long( add_type )); map.put( "add_time", new Long( add_time )); map.put( "subscribed", new Long( is_subscribed?1:0 )); map.put( "pop", new Long( popularity )); map.put( "rand", new Long( fixed_random )); map.put( "hupv", new Long( highest_prompted_version )); map.put( "sp", new Long( server_published?1:0 )); map.put( "spo", new Long( server_publication_outstanding?1:0 )); if ( associations.size() > 0 ){ List l_assoc = new ArrayList(); map.put( "assoc", l_assoc ); for (int i=0;i<associations.size();i++){ association assoc = (association)associations.get(i); Map m = new HashMap(); l_assoc.add( m ); m.put( "h", assoc.getHash()); m.put( "w", new Long( assoc.getWhen())); } } map.put( "history", history_map ); if ( creator_ref != null ){ map.put( "cref", creator_ref.getBytes( "UTF-8" )); } if ( category != null ){ map.put( "cat", category.getBytes( "UTF-8" )); } if ( tag_id != -1 ){ map.put( "tag", tag_id ); } return( map ); } } protected void fromMap( Map map ) throws IOException { name = new String((byte[])map.get( "name"), "UTF-8" ); public_key = (byte[])map.get( "public_key" ); private_key = (byte[])map.get( "private_key" ); version = ((Long)map.get( "version" )).intValue(); az_version = (int)ImportExportUtils.importLong( map, "az_version", AZ_VERSION ); is_public = ((Long)map.get( "is_public")).intValue() == 1; singleton_details = (Map)map.get( "sin_details" ); hash = (byte[])map.get( "hash" ); sig = (byte[])map.get( "sig" ); sig_data_size = ((Long)map.get( "sig_data_size" )).intValue(); fixed_random = ((Long)map.get( "rand" )).intValue(); add_type = ((Long)map.get( "add_type" )).intValue(); add_time = ((Long)map.get( "add_time" )).longValue(); is_subscribed = ((Long)map.get( "subscribed" )).intValue()==1; popularity = ((Long)map.get( "pop" )).longValue(); highest_prompted_version = ((Long)map.get( "hupv" )).intValue(); server_published = ((Long)map.get( "sp" )).intValue()==1; server_publication_outstanding = ((Long)map.get( "spo" )).intValue()==1; Long l_spa = (Long)map.get( "spa" ); if ( l_spa != null ){ singleton_sp_attempted = l_spa.longValue()==1; } byte[] b_local_name = (byte[])map.get( "local_name" ); if ( b_local_name != null ){ local_name = new String( b_local_name, "UTF-8" ); } List l_assoc = (List)map.get( "assoc" ); if ( l_assoc != null ){ for (int i=0;i<l_assoc.size();i++){ Map m = (Map)l_assoc.get(i); byte[] hash = (byte[])m.get("h"); long when = ((Long)m.get( "w" )).longValue(); associations.add( new association( hash, when )); } } history_map = (Map)map.get( "history" ); if ( history_map == null ){ history_map = new HashMap(); } byte[] b_cref = (byte[])map.get( "cref" ); if ( b_cref != null ){ creator_ref = new String( b_cref, "UTF-8" ); } byte[] b_cat = (byte[])map.get( "cat" ); if ( b_cat != null ){ category = new String( b_cat, "UTF-8" ); } Long l_tag_id = (Long)map.get( "tag" ); if ( l_tag_id != null ){ tag_id = l_tag_id; } } protected Map getScheduleConfig() { if ( schedule_map == null ){ try{ Map map = JSONUtils.decodeJSON( getJSON()); schedule_map = (Map)map.get( "schedule" ); if ( schedule_map == null ){ schedule_map = new HashMap(); } }catch( Throwable e ){ log( "Failed to load schedule", e ); schedule_map = new HashMap(); } } return( schedule_map ); } protected Map getHistoryConfig() { return( history_map ); } protected void updateHistoryConfig( Map _history_map ) { history_map = _history_map; fireChanged(); } protected void upgrade( SubscriptionBodyImpl body ) throws SubscriptionException { // pick up details from the body (excluding json that is maintained in body only) syncFromBody( body ); // write to file syncToBody(body); fireChanged(); } protected void init() { short_id = SubscriptionBodyImpl.deriveShortID( public_key, singleton_details ); id = null; } public boolean isSingleton() { return( singleton_details != null ); } public boolean isShareable() { try{ return( getEngine().isShareable() && !isSingleton()); }catch( Throwable e ){ Debug.printStackTrace(e); return( false ); } } public boolean isSearchTemplate() { return( getName(false).startsWith( "Search Template:" )); } protected Map getSingletonDetails() { return( singleton_details ); } protected boolean getSingletonPublishAttempted() { return( singleton_sp_attempted ); } protected void setSingletonPublishAttempted() { if ( !singleton_sp_attempted ){ singleton_sp_attempted = true; manager.configDirty( this ); } } public String getName() { return( getName( true )); } public String getName( boolean use_local ) { return( local_name==null?name:local_name ); } public void setLocalName( String str ) { local_name = str; manager.configDirty( this ); fireChanged(); } public void setName( String _name ) throws SubscriptionException { if ( !name.equals( _name )){ boolean ok = false; String old_name = name; int old_version = version; try{ name = _name; version++; SubscriptionBodyImpl body = new SubscriptionBodyImpl( manager, this ); syncToBody( body ); versionUpdated( body, false ); ok = true; }finally{ if ( !ok ){ name = old_name; version = old_version; } } fireChanged(); } } public String getNameEx() { if ( name_ex == null ){ try{ Map map = JSONUtils.decodeJSON( getJSON()); String search_term = (String)map.get( "search_term" ); Map filters = (Map)map.get( "filters" ); Engine engine = manager.getEngine( this, map, true ); String engine_name = engine.getNameEx(); if ( name.startsWith( engine_name )){ name_ex = name; }else if ( engine_name.startsWith( name )){ name_ex = engine_name; }else{ name_ex = name + ": " + engine.getNameEx(); } if ( search_term != null && search_term.length() > 0 ){ name_ex += ", query=" + search_term; } if ( filters != null && filters.size() > 0 ){ name_ex += ", filters=" + new SubscriptionResultFilter(filters).getString(); } }catch( Throwable e ){ name_ex = name + ": " + Debug.getNestedExceptionMessage(e); } } return( name_ex ); } public long getAddTime() { return( add_time ); } public boolean isPublic() { return( is_public ); } public void setPublic( boolean _is_public ) throws SubscriptionException { if ( is_public != _is_public ){ boolean ok = false; boolean old_public = is_public; int old_version = version; try{ is_public = _is_public; version++; SubscriptionBodyImpl body = new SubscriptionBodyImpl( manager, this ); syncToBody( body ); versionUpdated( body, false ); ok = true; }finally{ if ( !ok ){ version = old_version; is_public = old_public; } } fireChanged(); } } protected boolean getServerPublicationOutstanding() { return( server_publication_outstanding ); } protected void setServerPublicationOutstanding() { if ( !server_publication_outstanding ){ server_publication_outstanding = true; fireChanged(); } } protected void setServerPublished() { if ( server_publication_outstanding || !server_published ){ server_published = true; server_publication_outstanding = false; fireChanged(); } } protected boolean getServerPublished() { return( server_published ); } public String getJSON() throws SubscriptionException { try{ SubscriptionBodyImpl body = new SubscriptionBodyImpl( manager, this ); return( body.getJSON()); }catch( Throwable e ){ history.setFatalError( Debug.getNestedExceptionMessage(e)); if ( e instanceof SubscriptionException ){ throw((SubscriptionException)e ); } throw( new SubscriptionException( "Failed to read subscription", e )); } } public boolean setJSON( String _json ) throws SubscriptionException { String json = embedEngines( _json ); SubscriptionBodyImpl body = new SubscriptionBodyImpl( manager, this ); String old_json = body.getJSON(); if ( !json.equals( old_json )){ boolean ok = false; int old_version = version; try{ version++; body.setJSON( json ); syncToBody( body ); versionUpdated( body, true ); referer = null; ok = true; }finally{ if ( !ok ){ version = old_version; } } fireChanged(); return( true ); } return( false ); } protected String embedEngines( String json_in ) { // see if we need to embed private search templates Map map = JSONUtils.decodeJSON( json_in ); long engine_id = ((Long)map.get( "engine_id" )).longValue(); String json_out = json_in; if ( engine_id >= Integer.MAX_VALUE || engine_id < 0 ){ Engine engine = MetaSearchManagerFactory.getSingleton().getMetaSearch().getEngine( engine_id ); if ( engine == null ){ log( "Private search template with id '" + engine_id + "' not found!!!!" ); }else{ try{ embedEngines( map, engine ); json_out = JSONUtils.encodeToJSON( map ); log( "Embedded private search template '" + engine.getName() + "'" ); }catch( Throwable e ){ log( "Failed to embed private search template", e ); } } } return( json_out ); } protected static void embedEngines( Map map, Engine engine ) { Map engines = new HashMap(); map.put( "engines", engines ); Map engine_map = new HashMap(); try{ String engine_str = new String( Base64.encode( BEncoder.encode( engine.exportToBencodedMap())), "UTF-8" ); engine_map.put( "content", engine_str ); engines.put( String.valueOf( engine.getId()), engine_map ); }catch( Throwable e ){ Debug.out( e ); } } protected Engine extractEngine( Map json_map, long id ) { Map engines = (Map)json_map.get( "engines" ); if ( engines != null ){ Map engine_map = (Map)engines.get( String.valueOf( id )); if ( engine_map != null ){ String engine_str = (String)engine_map.get( "content" ); try{ Map map = BDecoder.decode( Base64.decode( engine_str.getBytes( "UTF-8" ))); return( MetaSearchManagerFactory.getSingleton().getMetaSearch().importFromBEncodedMap(map)); }catch( Throwable e ){ log( "failed to import engine", e ); } } } return( null ); } public Subscription cloneWithNewEngine( Engine engine ) throws SubscriptionException { try{ String json = getJSON(); Map map = JSONUtils.decodeJSON( json ); long id = ((Long)map.get( "engine_id" )).longValue(); if ( id == engine.getId()){ embedEngines(map, engine); SubscriptionImpl subs = new SubscriptionImpl( manager, getName(), engine.isPublic(), null, JSONUtils.encodeToJSON(map), SubscriptionImpl.ADD_TYPE_CREATE ); subs = manager.addSubscription( subs ); setLocalName( getName( false ) + " (old)" ); return( subs ); }else{ throw( new SubscriptionException( "Engine mismatch" )); } }catch( Throwable e ){ throw( new SubscriptionException( "Failed to export engine", e )); } } public Engine getEngine() throws SubscriptionException { return( getEngine( true )); } protected Engine getEngine( boolean local_only ) throws SubscriptionException { Map map = JSONUtils.decodeJSON( getJSON()); return( manager.getEngine( this, map, local_only )); } protected void engineUpdated( Engine engine ) { try{ String json = getJSON(); Map map = JSONUtils.decodeJSON( json ); long id = ((Long)map.get( "engine_id" )).longValue(); if ( id == engine.getId()){ if ( setJSON( json )){ log( "Engine has been updated, saved" ); } } }catch( Throwable e ){ log( "Engine update failed", e ); } } public boolean setDetails( String _name, boolean _is_public, String _json ) throws SubscriptionException { _json = embedEngines( _json ); SubscriptionBodyImpl body = new SubscriptionBodyImpl( manager, this ); String old_json = body.getJSON(); boolean json_changed = !_json.equals( old_json ); if ( !_name.equals( name ) || _is_public != is_public || json_changed ){ boolean ok = false; String old_name = name; boolean old_public = is_public; int old_version = version; try{ is_public = _is_public; name = _name; body.setJSON( _json ); version++; syncToBody( body ); versionUpdated( body, json_changed ); ok = true; }finally{ if ( !ok ){ version = old_version; is_public = old_public; name = old_name; } } fireChanged(); return( true ); } return( false ); } protected void versionUpdated( SubscriptionBodyImpl body, boolean json_changed ) { if ( json_changed ){ try{ Map map = JSONUtils.decodeJSON( body.getJSON()); schedule_map = (Map)map.get( "schedule" ); }catch( Throwable e ){ } } name_ex = null; if ( is_public ){ manager.updatePublicSubscription( this ); setPublished( false ); synchronized( this ){ for (int i=0;i<associations.size();i++){ ((association)associations.get(i)).setPublished( false ); } } } } public byte[] getPublicKey() { return( public_key ); } public byte[] getShortID() { return( short_id ); } public String getID() { if (id == null) { id = Base32.encode(getShortID()); } return( id ); } protected byte[] getPrivateKey() { return( private_key ); } protected int getFixedRandom() { return( fixed_random ); } public int getVersion() { return( version ); } public int getAZVersion() { return( az_version ); } protected void setHighestUserPromptedVersion( int v ) { if ( v < version ){ v = version; } if ( highest_prompted_version != v ){ highest_prompted_version = v; fireChanged(); } } protected int getHighestUserPromptedVersion() { return( highest_prompted_version ); } public int getHighestVersion() { return( Math.max( version, highest_prompted_version )); } public void resetHighestVersion() { if ( highest_prompted_version > 0 ){ highest_prompted_version = 0; fireChanged(); manager.checkUpgrade(this); } } public boolean isMine() { if ( private_key == null ){ return( false ); } if ( isSingleton() && add_type != ADD_TYPE_CREATE ){ return( false ); } return( true ); } public boolean isUpdateable() { return( private_key != null ); } public boolean isSubscribed() { return( is_subscribed ); } public void setSubscribed( boolean s ) { if ( is_subscribed != s ){ is_subscribed = s; if ( is_subscribed ){ manager.setSelected( this ); }else{ reset(); } fireChanged(); } } public boolean isAutoDownloadSupported() { return( history.isAutoDownloadSupported()); } public void getPopularity( final SubscriptionPopularityListener listener ) throws SubscriptionException { new AEThread2( "subs:popwait", true ) { public void run() { try{ manager.getPopularity( SubscriptionImpl.this, new SubscriptionPopularityListener() { public void gotPopularity( long pop ) { if ( pop != popularity ){ popularity = pop; fireChanged(); } listener.gotPopularity( popularity ); } public void failed( SubscriptionException e ) { if ( popularity == -1 ){ listener.failed( new SubscriptionException( "Failed to read popularity", e )); }else{ listener.gotPopularity( popularity ); } } }); }catch( Throwable e ){ if ( popularity == -1 ){ listener.failed( new SubscriptionException( "Failed to read popularity", e )); }else{ listener.gotPopularity( popularity ); } } } }.start(); } public long getCachedPopularity() { return( popularity ); } protected void setCachedPopularity( long pop ) { if ( pop != popularity ){ popularity = pop; fireChanged(); } } public String getReferer() { if ( referer == null ){ try{ Map map = JSONUtils.decodeJSON( getJSON()); Engine engine = manager.getEngine( this, map, false ); if ( engine != null ){ referer = engine.getReferer(); } }catch( Throwable e ){ log( "Failed to get referer", e ); } if ( referer == null ){ referer = ""; } } return( referer ); } protected void checkPublish() { synchronized( this ){ if ( destroyed ){ return; } // singleton's not available for upgrade if ( isSingleton()){ return; } // nothing to do for unsubscribed ones if ( !isSubscribed()){ return; } if ( popularity > 100 ){ // one off test on whether to track so we have around 100 active if ( lws_skip_check == 2 ){ return; }else if ( lws_skip_check == 0 ){ if ( RandomUtils.nextInt((int)(( popularity + 99 ) / 100 )) == 0 ){ lws_skip_check = 1; }else{ lws_skip_check = 2; return; } } } if ( hash != null ){ boolean create = false; if ( lws == null ){ create = true; }else{ if ( !Arrays.equals( lws.getHash().getBytes(), hash )){ lws.remove(); create = true; } } if ( create ){ try{ File original_data_location = manager.getVuzeFile( this ); if ( original_data_location.exists()){ // make a version based filename to avoid issues regarding multiple // versions final File versioned_data_location = new File( original_data_location.getParent(), original_data_location.getName() + "." + getVersion()); if ( !versioned_data_location.exists()){ if ( !FileUtil.copyFile( original_data_location, versioned_data_location )){ throw( new Exception( "Failed to copy file to '" + versioned_data_location + "'" )); } } lws = LightWeightSeedManager.getSingleton().add( getName(), new HashWrapper( hash ), TorrentUtils.getDecentralisedEmptyURL(), versioned_data_location, new LightWeightSeedAdapter() { public TOTorrent getTorrent( byte[] hash, URL announce_url, File data_location) throws Exception { log( " - generating torrent: " + Debug.getCompressedStackTrace()); TOTorrentCreator creator = TOTorrentFactory.createFromFileOrDirWithFixedPieceLength( data_location, announce_url, 256*1024 ); TOTorrent t = creator.create(); t.setHashOverride( hash ); return( t ); } }); } }catch( Throwable e ){ log( "Failed to create light-weight-seed", e ); } } } } } protected synchronized boolean canAutoUpgradeCheck() { if ( isSingleton()){ return( false ); } long now = SystemTime.getMonotonousTime(); if ( last_auto_upgrade_check == -1 || now - last_auto_upgrade_check > 4*60*60*1000 ){ last_auto_upgrade_check = now; return( true ); } return( false ); } public void addAssociation( byte[] hash ) { synchronized( this ){ for (int i=0;i<associations.size();i++){ association assoc = (association)associations.get(i); if ( Arrays.equals( assoc.getHash(), hash )){ return; } } associations.add( new association( hash, SystemTime.getCurrentTime())); if ( associations.size() > MAX_ASSOCIATIONS ){ associations.remove( RandomUtils.nextInt( MAX_ASSOCIATIONS - MIN_RECENT_ASSOC_TO_RETAIN )); } } fireChanged(); manager.associationAdded( this, hash); } public boolean hasAssociation( byte[] hash ) { synchronized( this ){ for (int i=0;i<associations.size();i++){ association assoc = (association)associations.get(i); if ( Arrays.equals( assoc.getHash(), hash )){ return( true ); } } } return( false ); } public void addPotentialAssociation( String result_id, String key ) { manager.addPotentialAssociation( this, result_id, key ); } public int getAssociationCount() { synchronized( this ){ return( associations.size()); } } protected association getAssociationForPublish() { synchronized( this ){ int num_assoc = associations.size(); // first set in order of most recent for (int i=num_assoc-1;i>=Math.max( 0, num_assoc-MIN_RECENT_ASSOC_TO_RETAIN);i--){ association assoc = (association)associations.get(i); if ( !assoc.getPublished()){ assoc.setPublished( true ); return( assoc ); } } // remaining randomised int rem = associations.size() - MIN_RECENT_ASSOC_TO_RETAIN; if ( rem > 0 ){ List l = new ArrayList( associations.subList( 0, rem )); Collections.shuffle( l ); for (int i=0;i<l.size();i++){ association assoc = (association)l.get(i); if ( !assoc.getPublished()){ assoc.setPublished( true ); return( assoc ); } } } } return( null ); } protected boolean getPublished() { return( published ); } protected void setPublished( boolean b ) { published = b; } protected int getVerifiedPublicationVersion( Map details ) { // singleton versions always 1 and each instance has separate private key so // verification will always fail so save to just return current version if ( isSingleton()){ return( getVersion()); } if ( !verifyPublicationDetails( details )){ return( -1 ); } return( getPublicationVersion( details )); } protected static int getPublicationVersion( Map details ) { return(((Long)details.get("v")).intValue()); } protected byte[] getPublicationHash() { return( hash ); } protected static byte[] getPublicationHash( Map details ) { return((byte[])details.get( "h" )); } protected static int getPublicationSize( Map details ) { return(((Long)details.get("z")).intValue()); } protected Map getPublicationDetails() { Map result = new HashMap(); result.put( "v", new Long( version )); if ( singleton_details == null ){ result.put( "h", hash ); result.put( "z", new Long( sig_data_size )); result.put( "s", sig ); }else{ result.put( "x", singleton_details ); } return( result ); } protected boolean verifyPublicationDetails( Map details ) { synchronized( this ){ if ( BEncoder.mapsAreIdentical( verify_cache_details, details )){ return( verify_cache_result ); } } byte[] hash = (byte[])details.get( "h" ); int version = ((Long)details.get( "v" )).intValue(); int size = ((Long)details.get( "z" )).intValue(); byte[] sig = (byte[])details.get( "s" ); boolean result = SubscriptionBodyImpl.verify( public_key, hash, version, size, sig ); synchronized( this ){ verify_cache_details = details; verify_cache_result = result; } return( result ); } public void setCreatorRef( String ref ) { creator_ref = ref; fireChanged(); } public String getCreatorRef() { return( creator_ref ); } public void setCategory( String _category ) { if ( _category == null && category == null ){ return; } if ( _category != null && category != null && _category.equals( category )){ return; } manager.setCategoryOnExisting( this, category, _category ); category = _category; fireChanged(); } public String getCategory() { return( category ); } public void setTagID( long _tag_id ) { if ( _tag_id == tag_id ){ return; } // don't update existing download tagging at the moment //manager.setTagOnExisting( this, tag, _tag ); tag_id = _tag_id; fireChanged(); } public long getTagID() { return( tag_id ); } protected void fireChanged() { manager.configDirty( this ); Iterator it = listeners.iterator(); while( it.hasNext()){ try{ ((SubscriptionListener)it.next()).subscriptionChanged( this ); }catch( Throwable e ){ Debug.printStackTrace(e); } } } protected void fireDownloaded( boolean was_auto ) { Iterator it = listeners.iterator(); while( it.hasNext()){ try{ ((SubscriptionListener)it.next()).subscriptionDownloaded( this, was_auto ); }catch( Throwable e ){ Debug.printStackTrace(e); } } } public void addListener( SubscriptionListener l ) { listeners.add( l ); } public void removeListener( SubscriptionListener l ) { listeners.remove( l ); } public SubscriptionHistory getHistory() { return( history ); } public SubscriptionManager getManager() { return( manager ); } public VuzeFile getVuzeFile() throws SubscriptionException { try{ return( VuzeFileHandler.getSingleton().loadVuzeFile( manager.getVuzeFile( this ).getAbsolutePath())); }catch( Throwable e ){ throw( new SubscriptionException( "Failed to get Vuze file", e )); } } public VuzeFile getSearchTemplateVuzeFile() { if ( !isSearchTemplate()){ return( null ); } Object[] details = manager.getSearchTemplateVuzeFile( this ); if ( details != null ){ return((VuzeFile)details[0]); } return( null ); } public boolean isSearchTemplateImportable() { return( manager.isSearchTemplateImportable( this )); } protected void destroy() { LightWeightSeed l; synchronized( this ){ destroyed = true; l = lws; } if ( l != null ){ l.remove(); } } public void reset() { getHistory().reset(); try{ getEngine().reset(); }catch( Throwable e ){ Debug.printStackTrace(e); } } public void remove() { destroy(); manager.removeSubscription( this ); } protected boolean isRemoved() { synchronized( this ){ return( destroyed ); } } public SubscriptionResult[] getResults( boolean include_deleted ) { return( getHistory().getResults( include_deleted )); } public void setUserData( Object key, Object data ) { synchronized( user_data ){ if ( data == null ){ user_data.remove( key ); }else{ user_data.put( key, data ); } } } public Object getUserData( Object key ) { synchronized( user_data ){ return( user_data.get( key )); } } protected void log( String str ) { manager.log( getString() + ": " + str ); } protected void log( String str, Throwable e ) { manager.log( getString() + ": " + str, e ); } public String getString() { return( "name=" + name + ",sid=" + ByteFormatter.encodeString( short_id ) + ",ver=" + version + ",pub=" + is_public + ",mine=" + isMine() + ",sub=" + is_subscribed + (is_subscribed?(",hist={" + history.getString() + "}"):"") + ",pop=" + popularity + (server_publication_outstanding?",spo=true":"")); } protected void generate( IndentWriter writer ) { String engine_str; try{ engine_str = "" + getEngine().getId(); }catch( Throwable e ){ engine_str = Debug.getNestedExceptionMessage(e); } writer.println( getString() + ": engine=" + engine_str ); try{ writer.indent(); synchronized( this ){ for (int i=0;i<associations.size();i++){ ((association)associations.get(i)).generate( writer ); } } }finally{ writer.exdent(); } } protected static class association { private byte[] hash; private long when; private boolean published; protected association( byte[] _hash, long _when ) { hash = _hash; when = _when; } protected byte[] getHash() { return( hash ); } protected long getWhen() { return( when ); } protected boolean getPublished() { return( published ); } protected void setPublished( boolean b ) { published = b; } protected String getString() { return( ByteFormatter.encodeString( hash ) + ", pub=" + published ); } protected void generate( IndentWriter writer ) { writer.println( getString()); } } }