/* * Created on 15-Nov-2004 * Created by Paul Gardner * Copyright (C) 2004, 2005, 2006 Aelitis, 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; either version 2 * of the License, or (at your option) any later version. * 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. * * AELITIS, SAS au capital de 46,603.30 euros * 8 Allee Lenotre, La Grille Royale, 78600 Le Mesnil le Roi, France. * */ package org.gudy.azureus2.core3.download.impl; import java.io.*; import java.lang.ref.WeakReference; import java.net.URL; import java.util.*; import java.util.zip.GZIPInputStream; import java.util.zip.GZIPOutputStream; import org.gudy.azureus2.core3.category.Category; import org.gudy.azureus2.core3.category.CategoryManager; import org.gudy.azureus2.core3.config.COConfigurationManager; import org.gudy.azureus2.core3.config.ParameterListener; import org.gudy.azureus2.core3.disk.DiskManagerFactory; import org.gudy.azureus2.core3.disk.DiskManagerFileInfo; import org.gudy.azureus2.core3.download.*; import org.gudy.azureus2.core3.ipfilter.IpFilterManagerFactory; import org.gudy.azureus2.core3.logging.LogEvent; import org.gudy.azureus2.core3.logging.LogIDs; import org.gudy.azureus2.core3.logging.LogRelation; import org.gudy.azureus2.core3.logging.Logger; import org.gudy.azureus2.core3.peer.PEPeerSource; import org.gudy.azureus2.core3.torrent.*; import org.gudy.azureus2.core3.tracker.client.TRTrackerAnnouncer; import org.gudy.azureus2.core3.util.*; import com.aelitis.azureus.core.AzureusCoreFactory; import com.aelitis.azureus.core.util.CaseSensitiveFileMap; import com.aelitis.azureus.core.util.CopyOnWriteList; import com.aelitis.azureus.core.util.CopyOnWriteMap; import com.aelitis.azureus.core.util.LinkFileMap; /** * @author parg * Overall aim of this is to stop updating the torrent file itself and update something * Azureus owns. To this end a file based on torrent hash is created in user-dir/active * It is actually just a copy of the torrent file */ public class DownloadManagerStateImpl implements DownloadManagerState, ParameterListener { private static final int VER_INCOMING_PEER_SOURCE = 1; private static final int VER_CURRENT = VER_INCOMING_PEER_SOURCE; private static final LogIDs LOGID = LogIDs.DISK; private static final String RESUME_KEY = "resume"; private static final String TRACKER_CACHE_KEY = "tracker_cache"; private static final String ATTRIBUTE_KEY = "attributes"; private static final String AZUREUS_PROPERTIES_KEY = "azureus_properties"; private static final File ACTIVE_DIR; public static boolean SUPPRESS_FIXUP_ERRORS = false; static{ ACTIVE_DIR = FileUtil.getUserFile( "active" ); if ( !ACTIVE_DIR.exists()){ FileUtil.mkdirs(ACTIVE_DIR); } } private static Random random = RandomUtils.SECURE_RANDOM; private static final Map default_parameters; private static final Map default_attributes; static{ default_parameters = new HashMap(); for (int i=0;i<PARAMETERS.length;i++){ default_parameters.put( PARAMETERS[i][0], PARAMETERS[i][1] ); } default_attributes = new HashMap(); for (int i=0;i<ATTRIBUTE_DEFAULTS.length;i++){ default_attributes.put( ATTRIBUTE_DEFAULTS[i][0], ATTRIBUTE_DEFAULTS[i][1] ); } // only add keys that will point to Map objects here! TorrentUtils.registerMapFluff( new String[] {TRACKER_CACHE_KEY,RESUME_KEY} ); } private static AEMonitor class_mon = new AEMonitor( "DownloadManagerState:class" ); private static Map<HashWrapper,DownloadManagerStateImpl> state_map = new HashMap<HashWrapper,DownloadManagerStateImpl>(); static{ ParameterListener listener = new ParameterListener() { public void parameterChanged( String parameterName) { List<DownloadManagerStateImpl> states; synchronized( state_map ){ states = new ArrayList<DownloadManagerStateImpl>( state_map.values()); } for ( DownloadManagerStateImpl state: states ){ try{ state.parameterChanged( parameterName ); }catch( Throwable e ){ Debug.out( e ); } } } }; COConfigurationManager.addParameterListener( "Max.Peer.Connections.Per.Torrent.When.Seeding", listener ); COConfigurationManager.addParameterListener( "Max.Peer.Connections.Per.Torrent.When.Seeding.Enable", listener ); COConfigurationManager.addParameterListener( "Max.Peer.Connections.Per.Torrent", listener ); COConfigurationManager.addParameterListener( "Max Uploads", listener ); COConfigurationManager.addParameterListener( "Max Uploads Seeding", listener ); COConfigurationManager.addParameterListener( "Max Seeds Per Torrent", listener ); COConfigurationManager.addParameterListener( "enable.seedingonly.maxuploads", listener ); } private static Map global_state_cache = new HashMap(); private static ArrayList global_state_cache_wrappers = new ArrayList(); private DownloadManagerImpl download_manager; private final TorrentUtils.ExtendedTorrent torrent; private boolean write_required; private Category category; private CopyOnWriteList listeners_cow = new CopyOnWriteList(); private CopyOnWriteMap listeners_read_map_cow = new CopyOnWriteMap(); private CopyOnWriteMap listeners_write_map_cow = new CopyOnWriteMap(); private Map parameters; private Map attributes; private AEMonitor this_mon = new AEMonitor( "DownloadManagerState" ); private int supressWrites = 0; private static ThreadLocal tls_wbr = new ThreadLocal() { public Object initialValue() { return( new ArrayList(1)); } }; private static DownloadManagerState getDownloadState( DownloadManagerImpl download_manager, TOTorrent original_torrent, TorrentUtils.ExtendedTorrent target_torrent ) throws TOTorrentException { byte[] hash = target_torrent.getHash(); DownloadManagerStateImpl res = null; try{ class_mon.enter(); HashWrapper hash_wrapper = new HashWrapper( hash ); res = (DownloadManagerStateImpl)state_map.get(hash_wrapper); if ( res == null ){ res = new DownloadManagerStateImpl( download_manager, target_torrent ); state_map.put( hash_wrapper, res ); }else{ // if original state was created without a download manager, // bind it to this one if ( res.getDownloadManager() == null && download_manager != null ){ res.setDownloadManager( download_manager ); } if ( original_torrent != null ){ res.mergeTorrentDetails( original_torrent ); } } }finally{ class_mon.exit(); } return( res ); } public static DownloadManagerState getDownloadState( TOTorrent original_torrent ) throws TOTorrentException { byte[] torrent_hash = original_torrent.getHash(); // System.out.println( "getDownloadState: hash = " + ByteFormatter.encodeString(torrent_hash)); TorrentUtils.ExtendedTorrent saved_state = null; File saved_file = getStateFile( torrent_hash ); if ( saved_file.exists()){ try{ saved_state = TorrentUtils.readDelegateFromFile( saved_file, false ); }catch( Throwable e ){ Debug.out( "Failed to load download state for " + saved_file, e ); } } // if saved state not found then recreate from original torrent if ( saved_state == null ){ TorrentUtils.copyToFile( original_torrent, saved_file ); saved_state = TorrentUtils.readDelegateFromFile( saved_file, false ); } return( getDownloadState( null, original_torrent, saved_state )); } protected static DownloadManagerState getDownloadState( DownloadManagerImpl download_manager, String torrent_file, byte[] torrent_hash, boolean inactive ) throws TOTorrentException { boolean discard_pieces = state_map.size() > 32; // System.out.println( "getDownloadState: hash = " + (torrent_hash==null?"null":ByteFormatter.encodeString(torrent_hash) + ", file = " + torrent_file )); TOTorrent original_torrent = null; TorrentUtils.ExtendedTorrent saved_state = null; // first, if we already have the hash then see if we can load the saved state if ( torrent_hash != null ){ File saved_file = getStateFile( torrent_hash ); if ( saved_file.exists()){ try{ Map cached_state = (Map)global_state_cache.remove( new HashWrapper( torrent_hash )); if ( cached_state != null ){ CachedStateWrapper wrapper = new CachedStateWrapper( download_manager, torrent_file, torrent_hash, cached_state, inactive ); global_state_cache_wrappers.add( wrapper ); saved_state = wrapper; }else{ saved_state = TorrentUtils.readDelegateFromFile( saved_file, discard_pieces ); } }catch( Throwable e ){ Debug.out( "Failed to load download state for " + saved_file ); } } } // if saved state not found then recreate from original torrent if required if ( saved_state == null ){ original_torrent = TorrentUtils.readDelegateFromFile( new File(torrent_file), discard_pieces ); torrent_hash = original_torrent.getHash(); File saved_file = getStateFile( torrent_hash ); if ( saved_file.exists()){ try{ saved_state = TorrentUtils.readDelegateFromFile( saved_file, discard_pieces ); }catch( Throwable e ){ Debug.out( "Failed to load download state for " + saved_file ); } } if ( saved_state == null ){ // we must copy the torrent as we want one independent from the // original (someone might still have references to the original // and do stuff like write it somewhere else which would screw us // up) TorrentUtils.copyToFile( original_torrent, saved_file ); saved_state = TorrentUtils.readDelegateFromFile( saved_file, discard_pieces ); } } DownloadManagerState res = getDownloadState( download_manager, original_torrent, saved_state ); if ( inactive ){ res.setActive( false ); } return( res ); } protected static File getStateFile( byte[] torrent_hash ) { return( new File( ACTIVE_DIR, ByteFormatter.encodeString( torrent_hash ) + ".dat" )); } protected static File getGlobalStateFile() { return( new File( ACTIVE_DIR, "cache.dat" )); } public static void loadGlobalStateCache() { File file = getGlobalStateFile(); if ( !file.canRead()){ return; } try{ BufferedInputStream is = new BufferedInputStream( new GZIPInputStream( new FileInputStream( file ))); try{ Map map = BDecoder.decode( is ); List cache = (List)map.get( "state" ); if ( cache != null ){ for (int i=0;i<cache.size();i++){ Map entry = (Map)cache.get(i); byte[] hash = (byte[])entry.get( "hash" ); if ( hash != null ){ global_state_cache.put( new HashWrapper( hash ), entry ); } } } is.close(); }catch( IOException e){ Debug.printStackTrace( e ); }finally{ try{ is.close(); }catch( Throwable e ){ } } }catch( Throwable e ){ Debug.printStackTrace( e ); } } public static void saveGlobalStateCache() { try{ class_mon.enter(); Map map = new HashMap(); List cache = new ArrayList(); map.put( "state", cache ); Iterator it = state_map.values().iterator(); while( it.hasNext()){ DownloadManagerState dms = (DownloadManagerState)it.next(); DownloadManager dm = dms.getDownloadManager(); if ( dm != null && dm.isPersistent()){ try{ Map state = CachedStateWrapper.export( dms ); cache.add( state ); }catch( Throwable e ){ Debug.printStackTrace( e ); } } } GZIPOutputStream os = new GZIPOutputStream( new FileOutputStream( getGlobalStateFile())); try{ os.write( BEncoder.encode( map )); os.close(); }catch( IOException e ){ Debug.printStackTrace( e ); try{ os.close(); }catch( IOException f ){ } } }catch( Throwable e ){ Debug.printStackTrace( e ); }finally{ class_mon.exit(); } } public static void discardGlobalStateCache() { getGlobalStateFile().delete(); for ( int i=0;i<global_state_cache_wrappers.size();i++){ ((CachedStateWrapper)global_state_cache_wrappers.get(i)).clearCache(); } global_state_cache_wrappers.clear(); global_state_cache_wrappers.trimToSize(); } public static void importDownloadState( File source_dir, byte[] download_hash ) throws DownloadManagerException { String hash_str = ByteFormatter.encodeString( download_hash ); String state_file = hash_str + ".dat"; File target_state_file = new File( ACTIVE_DIR, state_file ); File source_state_file = new File( source_dir, state_file ); if ( !source_state_file.exists()){ throw( new DownloadManagerException( "Source state file missing: " + source_state_file )); } if ( target_state_file.exists()){ throw( new DownloadManagerException( "Target state file already exists: " + target_state_file )); } if ( !FileUtil.copyFile( source_state_file, target_state_file )){ throw( new DownloadManagerException( "Failed to copy state file: " + source_state_file + " -> " + target_state_file )); } File source_state_dir = new File( source_dir, hash_str ); if ( source_state_dir.exists()){ File target_state_dir = new File( ACTIVE_DIR, hash_str ); try{ FileUtil.copyFileOrDirectory( source_state_dir, target_state_dir ); }catch( Throwable e ){ target_state_file.delete(); throw( new DownloadManagerException( "Failed to copy state dir: " + source_dir + " -> " + target_state_dir, e )); } } } public static void deleteDownloadState( byte[] download_hash ) throws DownloadManagerException { deleteDownloadState( ACTIVE_DIR, download_hash ); } public static void deleteDownloadState( File source_dir, byte[] download_hash ) throws DownloadManagerException { String hash_str = ByteFormatter.encodeString( download_hash ); String state_file = hash_str + ".dat"; File target_state_file = new File( source_dir, state_file ); if ( target_state_file.exists()){ if ( !target_state_file.delete()){ throw( new DownloadManagerException( "Failed to delete state file: " + target_state_file )); } } File target_state_dir = new File( source_dir, hash_str ); if ( target_state_dir.exists()){ if ( !FileUtil.recursiveDelete( target_state_dir )){ throw( new DownloadManagerException( "Failed to delete state dir: " + target_state_dir )); } } } protected DownloadManagerStateImpl( DownloadManagerImpl _download_manager, TorrentUtils.ExtendedTorrent _torrent ) { download_manager = _download_manager; torrent = _torrent; attributes = torrent.getAdditionalMapProperty( ATTRIBUTE_KEY ); if ( attributes == null ){ attributes = new HashMap(); } String cat_string = getStringAttribute( AT_CATEGORY ); if ( cat_string != null ){ Category cat = CategoryManager.getCategory( cat_string ); if ( cat != null ){ setCategory( cat ); } } parameters = getMapAttribute( AT_PARAMETERS ); if ( parameters == null ){ parameters = new HashMap(); } // note that version will be -1 for the first time through this code int version = getIntAttribute( AT_VERSION ); if ( version < VER_INCOMING_PEER_SOURCE ){ // migrate by adding incoming as enabled - only needed if we have any specified as other // code takes care of the case where we have none if ( getPeerSources().length > 0 ){ if ( PEPeerSource.isPeerSourceEnabledByDefault( PEPeerSource.PS_INCOMING )){ setPeerSourceEnabled( PEPeerSource.PS_INCOMING, true ); } }else{ // set default for newly added torrent setPeerSources( PEPeerSource.getDefaultEnabledPeerSources()); } } long flags = getFlags(); if (( flags & FLAG_DISABLE_IP_FILTER ) != 0 ){ try{ IpFilterManagerFactory.getSingleton().getIPFilter().addExcludedHash( torrent.getHash()); }catch( Throwable e ){ Debug.out( e ); } } if ( version < VER_CURRENT ){ setIntAttribute( AT_VERSION, VER_CURRENT ); } } public void parameterChanged( String parameterName) { // get any listeners to pick up new values as their defaults are based on core params informWritten( AT_PARAMETERS ); } public DownloadManager getDownloadManager() { return( download_manager ); } protected void setDownloadManager( DownloadManagerImpl dm ) { download_manager = dm; } public File getStateFile( ) { try{ File parent = new File( ACTIVE_DIR, ByteFormatter.encodeString( torrent.getHash()) + File.separatorChar); return( StringInterner.internFile(parent)); }catch( Throwable e ){ Debug.printStackTrace(e); return( null ); } } public void clearTrackerResponseCache() { setTrackerResponseCache( new HashMap()); } public Map getTrackerResponseCache() { Map tracker_response_cache = null; tracker_response_cache = torrent.getAdditionalMapProperty(TRACKER_CACHE_KEY); if (tracker_response_cache == null) tracker_response_cache = new HashMap(); return (tracker_response_cache); } public void setTrackerResponseCache( Map value ) { try{ this_mon.enter(); // System.out.println( "setting download state/tracker cache for '" + new String(torrent.getName())); boolean changed = !BEncoder.mapsAreIdentical( value, getTrackerResponseCache() ); if ( changed ){ write_required = true; torrent.setAdditionalMapProperty( TRACKER_CACHE_KEY, value ); } }finally{ this_mon.exit(); } } public Map getResumeData() { try{ this_mon.enter(); return( torrent.getAdditionalMapProperty(RESUME_KEY)); }finally{ this_mon.exit(); } } public void clearResumeData() { setResumeData( null ); } public void setResumeData( Map data ) { try{ this_mon.enter(); // System.out.println( "setting download state/resume data for '" + new String(torrent.getName())); if ( data == null ){ setLongAttribute( AT_RESUME_STATE, 1 ); torrent.removeAdditionalProperty( RESUME_KEY ); }else{ torrent.setAdditionalMapProperty( RESUME_KEY, data ); boolean complete = DiskManagerFactory.isTorrentResumeDataComplete( this ); setLongAttribute( AT_RESUME_STATE, complete?2:1 ); } write_required = true; }finally{ this_mon.exit(); } // we need to ensure this is persisted now as it has implications regarding crash restarts etc save(); } public boolean isResumeDataComplete() { // this is a cache of resume state to speed up startup long state = getLongAttribute( AT_RESUME_STATE ); if ( state == 0 ){ // don't know boolean complete = DiskManagerFactory.isTorrentResumeDataComplete( this ); setLongAttribute( AT_RESUME_STATE, complete?2:1 ); return( complete ); }else{ return( state == 2 ); } } public TOTorrent getTorrent() { return( torrent ); } public void setActive( boolean active ) { torrent.setDiscardFluff( !active ); } public void discardFluff() { torrent.setDiscardFluff(true); } public boolean exportState( File target_dir ) { try{ this_mon.enter(); save( true ); byte[] hash = torrent.getHash(); String hash_str = ByteFormatter.encodeString( hash ); String state_file = hash_str + ".dat"; File existing_state_file = new File( ACTIVE_DIR, state_file ); File target_state_file = new File( target_dir, state_file ); if ( !FileUtil.copyFile( existing_state_file, target_state_file )){ throw( new IOException( "Failed to copy state file" )); } File existing_state_dir = new File( ACTIVE_DIR, hash_str ); if ( existing_state_dir.exists()){ File target_state_dir = new File( target_dir, hash_str ); FileUtil.copyFileOrDirectory( existing_state_dir, target_state_dir ); } return( true ); }catch( Throwable e ){ Debug.out( e ); return( false ); }finally{ this_mon.exit(); } } public void suppressStateSave(boolean suppress) { if(suppress) supressWrites++; else if(supressWrites > 0) supressWrites--; } public void save() { save( false ); } protected void save( boolean force ) { if ( supressWrites > 0 && !force ){ return; } boolean do_write; try{ this_mon.enter(); do_write = write_required; write_required = false; }finally{ this_mon.exit(); } if ( do_write ){ try { // System.out.println( "writing download state for '" + new String(torrent.getName())); if (Logger.isEnabled()) Logger.log(new LogEvent(torrent, LOGID, "Saving state for download '" + TorrentUtils.getLocalisedName(torrent) + "'")); torrent.setAdditionalMapProperty( ATTRIBUTE_KEY, attributes ); TorrentUtils.writeToFile(torrent, true); } catch (Throwable e) { Logger.log(new LogEvent(torrent, LOGID, "Saving state", e)); } } else { // System.out.println( "not writing download state for '" + new String(torrent.getName())); } } public void delete() { try{ class_mon.enter(); HashWrapper wrapper = torrent.getHashWrapper(); state_map.remove( wrapper ); TorrentUtils.delete( torrent ); File dir = new File( ACTIVE_DIR, ByteFormatter.encodeString( wrapper.getBytes())); if ( dir.exists() && dir.isDirectory()){ FileUtil.recursiveDelete( dir ); } }catch( Throwable e ){ Debug.printStackTrace( e ); }finally{ class_mon.exit(); } } protected void mergeTorrentDetails( TOTorrent other_torrent ) { try{ boolean write = TorrentUtils.mergeAnnounceURLs( other_torrent, torrent ); // System.out.println( "DownloadManagerState:mergeTorrentDetails -> " + write ); if ( write ){ save(); if ( download_manager != null ){ TRTrackerAnnouncer client = download_manager.getTrackerClient(); if ( client != null ){ // pick up any URL changes client.resetTrackerUrl( false ); } } } }catch( Throwable e ){ Debug.printStackTrace( e ); } } public void setFlag( long flag, boolean set ) { long old_value = getLongAttribute( AT_FLAGS ); long new_value; if ( set ){ new_value = old_value | flag; }else{ new_value = old_value & ~flag; } if ( old_value != new_value ){ setLongAttribute( AT_FLAGS, new_value ); if (( old_value & FLAG_DISABLE_IP_FILTER ) != ( new_value & FLAG_DISABLE_IP_FILTER )){ try{ if (( new_value & FLAG_DISABLE_IP_FILTER ) != 0 ){ IpFilterManagerFactory.getSingleton().getIPFilter().addExcludedHash( torrent.getHash()); }else{ IpFilterManagerFactory.getSingleton().getIPFilter().removeExcludedHash( torrent.getHash()); } }catch( Throwable e ){ Debug.out( e ); } } } } public boolean getFlag( long flag ) { long value = getLongAttribute( AT_FLAGS ); return(( value & flag ) != 0 ); } public long getFlags() { return( getLongAttribute( AT_FLAGS )); } public boolean parameterExists(String name) { return parameters.containsKey(name); } public void setParameterDefault( String name ) { try{ this_mon.enter(); Object value = parameters.get( name ); if ( value == null ){ return; } // gotta clone here otherwise we update the underlying map and the setMapAttribute code // doesn't think it has changed parameters = new LightHashMap(parameters); parameters.remove( name ); }finally{ this_mon.exit(); } setMapAttribute( AT_PARAMETERS, parameters ); } public long getLongParameter( String name ) { try{ this_mon.enter(); Object value = parameters.get( name ); if ( value == null ){ value = default_parameters.get( name ); if ( value == null ){ Debug.out( "Unknown parameter '" + name + "' - must be defined in DownloadManagerState" ); return( 0 ); }else{ // default overrides // **** note - if you add to these make sure you extend the parameter listeners // registered as well (see static initialiser at top) if ( name == PARAM_MAX_UPLOADS_WHEN_SEEDING_ENABLED ){ if ( COConfigurationManager.getBooleanParameter( "enable.seedingonly.maxuploads" )){ value = new Boolean( true ); } }else if ( name == PARAM_MAX_UPLOADS_WHEN_SEEDING ){ int def = COConfigurationManager.getIntParameter( "Max Uploads Seeding" ); value = new Integer( def ); }else if ( name == PARAM_MAX_UPLOADS ){ int def = COConfigurationManager.getIntParameter("Max Uploads" ); value = new Integer( def ); }else if ( name == PARAM_MAX_PEERS ){ int def = COConfigurationManager.getIntParameter( "Max.Peer.Connections.Per.Torrent" ); value = new Integer( def ); }else if ( name == PARAM_MAX_PEERS_WHEN_SEEDING_ENABLED ){ if ( COConfigurationManager.getBooleanParameter( "Max.Peer.Connections.Per.Torrent.When.Seeding.Enable" )){ value = new Boolean( true ); } }else if ( name == PARAM_MAX_PEERS_WHEN_SEEDING ){ int def = COConfigurationManager.getIntParameter( "Max.Peer.Connections.Per.Torrent.When.Seeding" ); value = new Integer( def ); }else if ( name == PARAM_MAX_SEEDS ){ value = new Integer(COConfigurationManager.getIntParameter( "Max Seeds Per Torrent" )); }else if ( name == PARAM_RANDOM_SEED ){ long rand = random.nextLong(); setLongParameter( name, rand ); value = new Long( rand ); } } } if ( value instanceof Boolean ){ return(((Boolean)value).booleanValue()?1:0); }else if ( value instanceof Integer ){ return( ((Integer)value).longValue()); }else if ( value instanceof Long ){ return( ((Long)value).longValue()); } Debug.out( "Invalid parameter value for '" + name + "' - " + value ); return( 0 ); }finally{ this_mon.exit(); } } public void setLongParameter( String name, long value ) { Object default_value = default_parameters.get( name ); if ( default_value == null ){ Debug.out( "Unknown parameter '" + name + "' - must be defined in DownloadManagerState" ); } try{ this_mon.enter(); // gotta clone here otherwise we update the underlying map and the setMapAttribute code // doesn't think it has changed parameters = new LightHashMap(parameters); parameters.put( name, new Long(value)); setMapAttribute( AT_PARAMETERS, parameters ); }finally{ this_mon.exit(); } } public int getIntParameter( String name ) { return( (int)getLongParameter( name )); } public void setIntParameter( String name, int value ) { setLongParameter( name, value ); } public boolean getBooleanParameter( String name ) { return( getLongParameter( name ) != 0 ); } public void setBooleanParameter( String name, boolean value ) { setLongParameter( name, value?1:0 ); } public void setAttribute( String name, String value ) { if ( name.equals( AT_CATEGORY )){ if ( value == null ){ setCategory( null ); }else{ Category cat = CategoryManager.getCategory( value ); if ( cat == null ){ cat = CategoryManager.createCategory( value ); } setCategory( cat ); } return; } if (name.equals(AT_RELATIVE_SAVE_PATH)) { if (value.length() > 0) { File relative_path_file = new File(value); relative_path_file = DownloadManagerDefaultPaths.normaliseRelativePath(relative_path_file); value = (relative_path_file == null) ? "" : relative_path_file.getPath(); } } setStringAttribute( name, value ); } public String getAttribute( String name ) { if ( name.equals( AT_CATEGORY )){ Category cat = getCategory(); if ( cat == null ){ return( null ); } if ( cat == CategoryManager.getCategory( Category.TYPE_UNCATEGORIZED )){ return( null ); } return( cat.getName()); }else{ return( getStringAttribute( name )); } } public Category getCategory() { return category; } public void setCategory( Category cat ) { if ( cat == category ){ return; } if (cat != null && cat.getType() != Category.TYPE_USER){ cat = null; if (cat == category) { return; } } Category oldCategory = (category == null)?CategoryManager.getCategory(Category.TYPE_UNCATEGORIZED):category; category = cat; if (oldCategory != null ){ oldCategory.removeManager( this ); } DownloadManager dm = getDownloadManager(); if ( dm != null && !dm.isDestroyed()){ if ( category != null ){ category.addManager( this ); } else { CategoryManager.getCategory(Category.TYPE_UNCATEGORIZED).addManager(this); } } if ( category != null ){ setStringAttribute( AT_CATEGORY, category.getName()); }else{ setStringAttribute( AT_CATEGORY, null ); } } public String getTrackerClientExtensions() { return( getStringAttribute( AT_TRACKER_CLIENT_EXTENSIONS )); } public void setTrackerClientExtensions( String value ) { setStringAttribute( AT_TRACKER_CLIENT_EXTENSIONS, value ); } public String getDisplayName() { return this.getStringAttribute(AT_DISPLAY_NAME); } public void setDisplayName(String value) { this.setStringAttribute(AT_DISPLAY_NAME, value); } public String getUserComment() { return this.getStringAttribute(AT_USER_COMMENT); } public void setUserComment(String value) { this.setStringAttribute(AT_USER_COMMENT, value); } public String getRelativeSavePath() { return this.getStringAttribute(AT_RELATIVE_SAVE_PATH); } public DiskManagerFileInfo getPrimaryFile() { int primaryIndex = -1; DiskManagerFileInfo[] fileInfo = download_manager.getDiskManagerFileInfoSet().getFiles(); if (hasAttribute(AT_PRIMARY_FILE_IDX)) { primaryIndex = getIntAttribute(AT_PRIMARY_FILE_IDX); } if (primaryIndex < 0 || primaryIndex >= fileInfo.length) { primaryIndex = -1; if (fileInfo.length > 0) { int idxBiggest = -1; long lBiggest = -1; int numChecked = 0; for (int i = 0; i < fileInfo.length && numChecked < 10; i++) { if (!fileInfo[i].isSkipped()) { numChecked++; if (fileInfo[i].getLength() > lBiggest) { lBiggest = fileInfo[i].getLength(); idxBiggest = i; } } } if (idxBiggest >= 0) { primaryIndex = idxBiggest; } } if (primaryIndex >= 0) { setPrimaryFile(fileInfo[primaryIndex]); } } if (primaryIndex >= 0) { return fileInfo[primaryIndex]; } return null; } /** * @param primary */ public void setPrimaryFile(DiskManagerFileInfo dmfi) { setIntAttribute(AT_PRIMARY_FILE_IDX, dmfi.getIndex()); } public String[] getNetworks() { List values = getListAttributeSupport( AT_NETWORKS ); List res = new ArrayList(); // map back to the constants to allow == comparisons for (int i=0;i<values.size();i++){ String nw = (String)values.get(i); for (int j=0;j<AENetworkClassifier.AT_NETWORKS.length;j++){ String nn = AENetworkClassifier.AT_NETWORKS[j]; if ( nn.equals( nw )){ res.add( nn ); } } } String[] x = new String[res.size()]; res.toArray(x); return( x ); } public boolean isNetworkEnabled( String network) { List values = getListAttributeSupport( AT_NETWORKS ); return values.contains(network); } public void setNetworks( String[] networks ) { if ( networks == null ){ networks = new String[0]; } List l = new ArrayList(); for (int i=0;i<networks.length;i++){ l.add( networks[i]); } setListAttribute( AT_NETWORKS, l ); } public void setNetworkEnabled( String network, boolean enabled) { List values = getListAttributeSupport( AT_NETWORKS ); boolean alreadyEnabled = values.contains(network); List l = new ArrayList(); if(enabled && !alreadyEnabled) { for (int i=0;i<values.size();i++){ l.add(values.get(i)); } l.add(network); setListAttribute( AT_NETWORKS, l ); } if(!enabled && alreadyEnabled) { for (int i=0;i<values.size();i++){ l.add(values.get(i)); } l.remove(network); setListAttribute( AT_NETWORKS, l ); } } // peer sources public String[] getPeerSources() { List values = getListAttributeSupport( AT_PEER_SOURCES ); List res = new ArrayList(); // map back to the constants to allow == comparisons for (int i=0;i<values.size();i++){ String ps = (String)values.get(i); for (int j=0;j<PEPeerSource.PS_SOURCES.length;j++){ String x = PEPeerSource.PS_SOURCES[j]; if ( x.equals( ps )){ res.add( x ); } } } String[] x = new String[res.size()]; res.toArray(x); return( x ); } public boolean isPeerSourceEnabled( String peerSource ) { List values = getListAttributeSupport( AT_PEER_SOURCES ); return values.contains(peerSource); } public boolean isPeerSourcePermitted( String peerSource ) { // no DHT for private torrents or explicitly prevented if ( peerSource.equals( PEPeerSource.PS_DHT )){ if ( TorrentUtils.getPrivate( torrent ) || !TorrentUtils.getDHTBackupEnabled( torrent )){ return( false ); } } // no PEX for private torrents if ( peerSource.equals( PEPeerSource.PS_OTHER_PEER )){ if ( TorrentUtils.getPrivate( torrent )){ return( false ); } } List values = getListAttributeSupport( AT_PEER_SOURCES_DENIED ); if ( values != null ){ if ( values.contains( peerSource )){ return( false ); } } return( true ); } public void setPeerSourcePermitted( String peerSource, boolean enabled ) { if ( !getFlag( FLAG_ALLOW_PERMITTED_PEER_SOURCE_CHANGES )){ Logger.log(new LogEvent(torrent, LOGID, "Attempt to modify permitted peer sources denied as disabled '" + TorrentUtils.getLocalisedName(torrent) + "'")); return; } if ( !enabled ){ setPeerSourceEnabled( peerSource, false ); } List values = getListAttributeSupport( AT_PEER_SOURCES_DENIED ); if ( values == null ){ if ( !enabled ){ values = new ArrayList(); values.add( peerSource ); setListAttribute( AT_PEER_SOURCES_DENIED, values ); } }else{ if ( enabled ){ values.remove( peerSource ); }else{ if ( !values.contains( peerSource )){ values.add( peerSource ); } } setListAttribute( AT_PEER_SOURCES_DENIED, values ); } } public void setPeerSources( String[] ps ) { if ( ps == null ){ ps = new String[0]; } List l = new ArrayList(); for (int i=0;i<ps.length;i++){ String p = ps[i]; if ( isPeerSourcePermitted(p)){ l.add( ps[i]); } } setListAttribute( AT_PEER_SOURCES, l ); } public void setPeerSourceEnabled( String source, boolean enabled ) { if ( enabled && !isPeerSourcePermitted( source )){ return; } List values = getListAttributeSupport( AT_PEER_SOURCES ); boolean alreadyEnabled = values.contains(source); List l = new ArrayList(); if(enabled && !alreadyEnabled) { for (int i=0;i<values.size();i++){ l.add(values.get(i)); } l.add(source); setListAttribute( AT_PEER_SOURCES, l ); } if(!enabled && alreadyEnabled) { for (int i=0;i<values.size();i++){ l.add(values.get(i)); } l.remove(source); setListAttribute( AT_PEER_SOURCES, l ); } } // links stuff private volatile WeakReference<LinkFileMap> file_link_cache = null; public void setFileLink( int source_index, File link_source, File link_destination ) { LinkFileMap links = getFileLinks(); File existing = (File)links.get( source_index, link_source); if ( link_destination == null ){ if ( existing == null ){ return; } }else if ( existing != null && existing.equals( link_destination )){ return; } links.put( source_index, link_source, link_destination ); List list = new ArrayList(); Iterator<LinkFileMap.Entry> it = links.entryIterator(); while( it.hasNext()){ LinkFileMap.Entry entry = it.next(); int index = entry.getIndex(); File source = entry.getFromFile(); File target = entry.getToFile(); String str = index + "\n" + source + "\n" + (target==null?"":target.toString()); list.add( str ); } //System.out.println( "setFileLink: " + link_source + " -> " + link_destination ); synchronized( this ){ file_link_cache = new WeakReference<LinkFileMap>( links ); } setListAttribute( AT_FILE_LINKS2, list ); } public void setFileLinks( List<Integer> source_indexes, List<File> link_sources, List<File> link_destinations ) { LinkFileMap links = getFileLinks(); boolean changed = false; for ( int i=0;i<link_sources.size();i++){ int source_index = source_indexes.get( i ); File link_source = link_sources.get(i); File link_destination = link_destinations.get(i); File existing = links.get( source_index, link_source); if ( link_destination == null ){ if ( existing == null ){ continue; } }else if ( existing != null && existing.equals( link_destination )){ continue; } links.put( source_index, link_source, link_destination ); changed = true; } if ( !changed ){ return; } //System.out.println( "setFileLinks: " + links.getString()); List list = new ArrayList(); Iterator<LinkFileMap.Entry> it = links.entryIterator(); while( it.hasNext()){ LinkFileMap.Entry entry = it.next(); int index = entry.getIndex(); File source = entry.getFromFile(); File target = entry.getToFile(); String str = index + "\n" + source + "\n" + (target==null?"":target.toString()); list.add( str ); } synchronized( this ){ file_link_cache = new WeakReference<LinkFileMap>( links ); } setListAttribute( AT_FILE_LINKS2, list ); } public void clearFileLinks() { LinkFileMap links = getFileLinks(); List list = new ArrayList(); Iterator<LinkFileMap.Entry> it = links.entryIterator(); boolean changed = false; while( it.hasNext()){ LinkFileMap.Entry entry = it.next(); int index = entry.getIndex(); File source = entry.getFromFile(); File target = entry.getToFile(); if ( target != null ){ changed = true; } String str = index + "\n" + source + "\n"; list.add( str ); } if ( changed ){ synchronized( this ){ file_link_cache = null; } setListAttribute( AT_FILE_LINKS2, list ); } //System.out.println( "clearFileLinks" ); } public File getFileLink( int source_index, File link_source ) { LinkFileMap map = null; WeakReference<LinkFileMap> ref = file_link_cache; if ( ref != null ){ map = ref.get(); } if ( map == null ){ map = getFileLinks(); synchronized( this ){ file_link_cache = new WeakReference<LinkFileMap>( map ); } } File res = map.get( source_index, link_source ); //System.out.println( "getFileLink: " + link_source + " -> " + res ); return( res ); } public LinkFileMap getFileLinks() { LinkFileMap map = null; WeakReference<LinkFileMap> ref = file_link_cache; if ( ref != null ){ map = ref.get(); } if ( map == null ){ map = getFileLinksSupport(); synchronized( this ){ file_link_cache = new WeakReference<LinkFileMap>( map ); } } return( map ); } private LinkFileMap getFileLinksSupport() { LinkFileMap res = new LinkFileMap(); List new_values = getListAttributeSupport( AT_FILE_LINKS2 ); if ( new_values.size() > 0 ){ for (int i=0;i<new_values.size();i++){ String entry = (String)new_values.get(i); String[] bits = entry.split( "\n" ); if ( bits.length >= 2 ){ try{ int index = Integer.parseInt( bits[0].trim()); File source = new File(bits[1].trim()); File target = bits.length<3?null:new File(bits[2].trim()); if( index >= 0 ){ res.put( index, source, target ); }else{ // can get here when partially resolved link state is saved and then re-read res.putMigration( source, target ); } }catch( Throwable e ){ Debug.out( e ); } } } }else{ List old_values = getListAttributeSupport( AT_FILE_LINKS_DEPRECATED ); for (int i=0;i<old_values.size();i++){ String entry = (String)old_values.get(i); int sep = entry.indexOf( "\n" ); if ( sep != -1 ){ File target = (sep == entry.length()-1)?null:new File( entry.substring( sep+1 )); res.putMigration( new File( entry.substring(0,sep)), target ); } } } //System.out.println( "getFileLinks: " + res.getString()); return( res ); } public boolean isOurContent() { // HACK! Map mapAttr = getMapAttribute("Plugin.azdirector.ContentMap"); return mapAttr != null && mapAttr.containsKey("DIRECTOR PUBLISH"); } // general stuff protected String getStringAttribute( String attribute_name ) { informWillRead( attribute_name ); try{ this_mon.enter(); if ( !(attributes.get( attribute_name) instanceof byte[] )){ return( null ); } byte[] bytes = (byte[])attributes.get( attribute_name ); if ( bytes == null ){ return( null ); } try{ return( new String( bytes, Constants.DEFAULT_ENCODING )); }catch( UnsupportedEncodingException e ){ Debug.printStackTrace(e); return( null ); } }finally{ this_mon.exit(); } } protected void setStringAttribute( final String attribute_name, final String attribute_value ) { boolean changed = false; try{ this_mon.enter(); if ( attribute_value == null ){ if ( attributes.containsKey( attribute_name )){ attributes.remove( attribute_name ); write_required = changed = true; } }else{ try{ byte[] existing_bytes = (byte[])attributes.get( attribute_name ); byte[] new_bytes = attribute_value.getBytes( Constants.DEFAULT_ENCODING ); if ( existing_bytes == null || !Arrays.equals( existing_bytes, new_bytes )){ attributes.put( attribute_name, new_bytes ); write_required = changed = true; } }catch( UnsupportedEncodingException e ){ Debug.printStackTrace(e); } } }finally{ this_mon.exit(); } if ( changed ){ informWritten( attribute_name ); } } public long getLongAttribute( String attribute_name ) { informWillRead( attribute_name ); try{ this_mon.enter(); Long l = (Long)attributes.get( attribute_name ); if ( l == null ){ Object def = default_attributes.get( attribute_name ); if ( def != null ){ if ( def instanceof Long ){ return(((Long)def).longValue()); }else if ( def instanceof Integer ){ return(((Integer)def).longValue()); }else{ Debug.out( "unknown default type " + def ); } }else if ( attribute_name == AT_FILES_EXPANDED ){ boolean featured = TorrentUtils.isFeaturedContent( torrent ); long res = featured?1:0; attributes.put( attribute_name, new Long( res )); write_required = true; return( res ); } return( 0 ); } return( l.longValue()); }finally{ this_mon.exit(); } } public void setLongAttribute( final String attribute_name, final long attribute_value ) { boolean changed = false; try{ this_mon.enter(); Long existing_value = (Long)attributes.get( attribute_name ); if ( existing_value == null || existing_value.longValue() != attribute_value ){ attributes.put( attribute_name, new Long( attribute_value) ); write_required = changed = true; } }finally{ this_mon.exit(); } if ( changed ){ informWritten( attribute_name ); } } public void setListAttribute( String name, String[] values ) { List list = values==null?null:Arrays.asList((Object[]) values.clone()); /* if ( list != null ){ for (int i=0;i<values.length;i++){ list.add( values[i]); } }*/ setListAttribute( name, list ); } public String getListAttribute(String name, int idx) { if (name.equals(AT_NETWORKS) || name.equals(AT_PEER_SOURCES)) throw new UnsupportedOperationException("not supported right now, implement it yourself :P"); informWillRead(name); try { this_mon.enter(); List values = (List) attributes.get(name); if(values == null || idx >= values.size() || idx < 0) return null; Object o = values.get(idx); if (o instanceof byte[]) { byte[] bytes = (byte[]) o; String s = null; try { s = StringInterner.intern(new String(bytes, Constants.DEFAULT_ENCODING)); } catch (UnsupportedEncodingException e) { Debug.printStackTrace(e); } if (s != null) values.set(idx, s); return s; } else if (o instanceof String) { return (String) o; } } finally { this_mon.exit(); } return null; } public String[] getListAttribute( String attribute_name ) { if ( attribute_name == AT_NETWORKS ){ return( getNetworks()); }else if ( attribute_name == AT_PEER_SOURCES ){ return( getPeerSources()); }else{ List l = getListAttributeSupport( attribute_name ); if ( l == null ){ return( null ); } String[] res = new String[l.size()]; try { res = (String[])l.toArray(res); } catch (ArrayStoreException e) { Debug.out( "getListAttribute( " + attribute_name + ") - object isnt String - " + e ); return( null ); } return( res ); } } protected List getListAttributeSupport( String attribute_name ) { informWillRead( attribute_name ); try{ this_mon.enter(); List values = (List)attributes.get( attribute_name ); List res = new ArrayList(values != null ? values.size() : 0); if ( values != null ){ for (int i=0;i<values.size();i++){ Object o = values.get(i); if ( o instanceof byte[] ){ byte[] bytes = (byte[])o; String s = null; try{ s = StringInterner.intern(new String( bytes, Constants.DEFAULT_ENCODING )); }catch( UnsupportedEncodingException e ){ Debug.printStackTrace(e); } if(s != null) { res.add(s); values.set(i, s); } }else if ( o instanceof String ){ res.add( o ); } } } return( res ); }finally{ this_mon.exit(); } } protected void setListAttribute( final String attribute_name, final List attribute_value ) { boolean changed = false; try{ this_mon.enter(); if ( attribute_value == null ){ if ( attributes.containsKey( attribute_name )){ attributes.remove( attribute_name ); write_required = changed = true; } }else{ List old_value = getListAttributeSupport( attribute_name ); if ( old_value == null || old_value.size() != attribute_value.size()){ attributes.put( attribute_name, attribute_value ); write_required = changed = true; }else{ if ( old_value == attribute_value ){ Debug.out( "setListAttribute: should clone?" ); } changed = !BEncoder.listsAreIdentical( old_value, attribute_value ); if ( changed ){ write_required = true; attributes.put( attribute_name, attribute_value ); } } } }finally{ this_mon.exit(); } if ( changed ){ informWritten( attribute_name ); } } public Map getMapAttribute( String attribute_name ) { informWillRead( attribute_name ); try{ this_mon.enter(); Map value = (Map)attributes.get( attribute_name ); return( value ); }finally{ this_mon.exit(); } } public void setMapAttribute( final String attribute_name, final Map attribute_value ) { setMapAttribute( attribute_name, attribute_value, false ); } protected void setMapAttribute( final String attribute_name, final Map attribute_value, boolean disable_change_notification ) { boolean changed = false; try{ this_mon.enter(); if ( attribute_value == null ){ if ( attributes.containsKey( attribute_name )){ attributes.remove( attribute_name ); write_required = changed = true; } }else{ Map old_value = getMapAttribute( attribute_name ); if ( old_value == null || old_value.size() != attribute_value.size()){ attributes.put( attribute_name, attribute_value ); write_required = changed = true; }else{ if ( old_value == attribute_value ){ Debug.out( "setMapAttribute: should clone?" ); } changed = !BEncoder.mapsAreIdentical( old_value, attribute_value ); if ( changed ){ write_required = true; attributes.put( attribute_name, attribute_value ); } } } }finally{ this_mon.exit(); } if ( changed && !disable_change_notification ){ informWritten( attribute_name ); } } public boolean hasAttribute( String name ) { try{ this_mon.enter(); if ( attributes == null) {return false;} return attributes.containsKey(name); }finally{ this_mon.exit(); } } // These methods just use long attributes to store data into. public void setIntAttribute( String name, int value) { setLongAttribute(name, value); } public int getIntAttribute( String name ) { return (int)getLongAttribute(name); } public void setBooleanAttribute( String name, boolean value ) { setLongAttribute(name, (value ? 1 : 0)); } public boolean getBooleanAttribute( String name ) { return getLongAttribute(name) != 0; } public static DownloadManagerState getDownloadState( DownloadManager dm ) { return( new nullState(dm)); } protected void informWritten( final String attribute_name ) { // don't make any of this async as the link management code for cache files etc // relies on callbacks here being synchronous... List listeners_ref = listeners_cow.getList(); for (int i=0;i<listeners_ref.size();i++){ try{ ((DownloadManagerStateListener)listeners_ref.get(i)).stateChanged( this, new DownloadManagerStateEvent() { public int getType() { return( DownloadManagerStateEvent.ET_ATTRIBUTE_WRITTEN ); } public Object getData() { return( attribute_name ); } }); }catch( Throwable e ){ Debug.printStackTrace(e); } } listeners_ref = null; CopyOnWriteList write_listeners = (CopyOnWriteList)listeners_write_map_cow.get(attribute_name); if (write_listeners != null) {listeners_ref = write_listeners.getList();} if (listeners_ref != null) { for (int i=0;i<listeners_ref.size();i++) { try {((DownloadManagerStateAttributeListener)listeners_ref.get(i)).attributeEventOccurred(download_manager, attribute_name, DownloadManagerStateAttributeListener.WRITTEN);} catch (Throwable t) {Debug.printStackTrace(t);} } } } protected void informWillRead( final String attribute_name ) { // avoid potential recursion will a will-be-read causing a write that then // causes a further will-be-read... List will_be_read_list = (List)tls_wbr.get(); if ( !will_be_read_list.contains( attribute_name )){ will_be_read_list.add( attribute_name ); try{ List listeners_ref = listeners_cow.getList(); for (int i=0;i<listeners_ref.size();i++){ try{ ((DownloadManagerStateListener)listeners_ref.get(i)).stateChanged( this, new DownloadManagerStateEvent() { public int getType() { return( DownloadManagerStateEvent.ET_ATTRIBUTE_WILL_BE_READ ); } public Object getData() { return( attribute_name ); } }); }catch( Throwable e ){ Debug.printStackTrace(e); } } listeners_ref = null; CopyOnWriteList read_listeners = null; read_listeners = (CopyOnWriteList)listeners_read_map_cow.get(attribute_name); if (read_listeners != null) {listeners_ref = read_listeners.getList();} if (listeners_ref != null) { for (int i=0;i<listeners_ref.size();i++) { try {((DownloadManagerStateAttributeListener)listeners_ref.get(i)).attributeEventOccurred(download_manager, attribute_name, DownloadManagerStateAttributeListener.WILL_BE_READ);} catch (Throwable t) {Debug.printStackTrace(t);} } } }finally{ will_be_read_list.remove( attribute_name ); } } } public void addListener( DownloadManagerStateListener l ) { listeners_cow.add( l ); } public void removeListener( DownloadManagerStateListener l ) { listeners_cow.remove(l); } public void addListener(DownloadManagerStateAttributeListener l, String attribute, int event_type) { CopyOnWriteMap map_to_use = (event_type == DownloadManagerStateAttributeListener.WILL_BE_READ) ? this.listeners_read_map_cow : this.listeners_write_map_cow; CopyOnWriteList lst = (CopyOnWriteList)map_to_use.get(attribute); if (lst == null) { lst = new CopyOnWriteList(); map_to_use.put(attribute, lst); } lst.add(l); } public void removeListener(DownloadManagerStateAttributeListener l, String attribute, int event_type) { CopyOnWriteMap map_to_use = (event_type == DownloadManagerStateAttributeListener.WILL_BE_READ) ? this.listeners_read_map_cow : this.listeners_write_map_cow; CopyOnWriteList lst = (CopyOnWriteList)map_to_use.get(attribute); if (lst != null) {lst.remove(l);} } public void generateEvidence( IndentWriter writer) { writer.println( "DownloadManagerState" ); try{ writer.indent(); writer.println( "parameters=" + parameters ); writer.println( "flags=" + getFlags()); DiskManagerFileInfo primaryFile = getPrimaryFile(); if (primaryFile != null) { writer.println("primary file=" + Debug.secretFileName(primaryFile.getFile(true).getAbsolutePath())); } }finally{ writer.exdent(); } } public void dump( IndentWriter writer) { writer.println( "attributes: " + parameters ); } protected static class nullState implements DownloadManagerState { protected DownloadManager download_manager; protected nullState( DownloadManager _dm ) { download_manager = _dm; } public TOTorrent getTorrent() { return( null ); } public File getStateFile( ) { return( null ); } public DownloadManager getDownloadManager() { return( download_manager ); } public void clearResumeData() { } public Map getResumeData() { return( new HashMap()); } public void setResumeData( Map data ) { } public boolean isResumeDataComplete() { return( false ); } public void clearTrackerResponseCache() { } public Map getTrackerResponseCache() { return( new HashMap()); } public void setTrackerResponseCache( Map value ) { } public void setFlag( long flag, boolean set ) { } public boolean getFlag( long flag ) { return( false ); } public long getFlags() { return 0; } public void setParameterDefault( String name ) { } public long getLongParameter( String name ) { return( 0 ); } public void setLongParameter( String name, long value ) { } public int getIntParameter( String name ) { return( 0 ); } public void setIntParameter( String name, int value ) { } public boolean getBooleanParameter( String name ) { return( false ); } public void setBooleanParameter( String name, boolean value ) { } public void setAttribute( String name, String value ) { } public String getAttribute( String name ) { return( null ); } public String getTrackerClientExtensions() { return( null ); } public void setTrackerClientExtensions( String value ) { } public void setListAttribute( String name, String[] values ) { } public String getListAttribute(String name, int idx) { return null; } public String[] getListAttribute( String name ) { return( null ); } public void setMapAttribute( String name, Map value ) { } public Map getMapAttribute( String name ) { return( null ); } public boolean hasAttribute(String name) {return false;} public int getIntAttribute(String name) {return 0;} public long getLongAttribute(String name) {return 0L;} public boolean getBooleanAttribute(String name) {return false;} public void setIntAttribute(String name, int value) {} public void setLongAttribute(String name, long value) {} public void setBooleanAttribute(String name, boolean value) {} public Category getCategory() { return( null ); } public void setCategory( Category cat ) { } public String[] getNetworks() { return( new String[0] ); } public boolean isNetworkEnabled(String network) { return false; } public void setNetworks( String[] networks ) { } public void setNetworkEnabled( String network, boolean enabled) { } public String[] getPeerSources() { return( new String[0] ); } public boolean isPeerSourcePermitted( String peerSource ) { return( false ); } public void setPeerSourcePermitted(String peerSource, boolean permitted) { } public boolean isPeerSourceEnabled( String peerSource) { return false; } public void suppressStateSave(boolean suppress) {} public void setPeerSources( String[] networks ) { } public void setPeerSourceEnabled( String source, boolean enabled) { } public void setFileLink( int source_index, File link_source, File link_destination ) { } public void setFileLinks( List<Integer> source_indexes, List<File> link_sources, List<File> link_destinations ) { } public void clearFileLinks() { } public File getFileLink( int source_index, File link_source ) { return( null ); } public LinkFileMap getFileLinks() { return( new LinkFileMap()); } public void setActive(boolean active ) { } public void discardFluff() {} public boolean exportState( File target_dir ) { return( false ); } public void save() { } public void delete() { } public void addListener( DownloadManagerStateListener l ) {} public void removeListener( DownloadManagerStateListener l ) {} public void addListener(DownloadManagerStateAttributeListener l, String attribute, int event_type) {} public void removeListener(DownloadManagerStateAttributeListener l, String attribute, int event_type) {} public void setDisplayName(String name) {} public String getDisplayName() {return null;} public void setUserComment(String name) {} public String getUserComment() {return null;} public void setRelativeSavePath(String name) {} public String getRelativeSavePath() {return null;} public boolean parameterExists(String name) { // TODO Auto-generated method stub return false; } public void generateEvidence( IndentWriter writer) { writer.println( "DownloadManagerState: broken torrent" ); } public void dump( IndentWriter writer) { } public boolean isOurContent() { // TODO Auto-generated method stub return false; } // @see org.gudy.azureus2.core3.download.DownloadManagerState#getPrimaryFile() public DiskManagerFileInfo getPrimaryFile() { // TODO Auto-generated method stub return null; } // @see org.gudy.azureus2.core3.download.DownloadManagerState#setPrimaryFile(org.gudy.azureus2.core3.disk.DiskManagerFileInfo) public void setPrimaryFile(DiskManagerFileInfo dmfi) { // TODO Auto-generated method stub } } protected static class CachedStateWrapper extends LogRelation implements TorrentUtils.ExtendedTorrent { private DownloadManagerImpl download_manager; private String torrent_file; private HashWrapper torrent_hash_wrapper; private Map cache; private Map cache_attributes; private Map cache_azp; private volatile TorrentUtils.ExtendedTorrent delegate; private TOTorrentException fixup_failure; private boolean discard_pieces; private boolean logged_failure; private Boolean simple_torrent; private long size; private int file_count; private URL announce_url; private cacheGroup announce_group; private volatile boolean discard_fluff; protected CachedStateWrapper( DownloadManagerImpl _download_manager, String _torrent_file, byte[] _torrent_hash, Map _cache, boolean _force_piece_discard ) { download_manager = _download_manager; torrent_file = _torrent_file; torrent_hash_wrapper = new HashWrapper( _torrent_hash ); cache = _cache; cache_attributes = (Map)cache.get( "attributes" ); cache_azp = (Map)cache.get( "azp" ); if ( _force_piece_discard ){ discard_pieces = true; }else{ Long l_fp = (Long)cache.get( "dp" ); if ( l_fp != null ){ discard_pieces = l_fp.longValue() == 1; } } Long st = (Long)cache.get( "simple" ); if ( st != null ){ simple_torrent = new Boolean( st.longValue()==1 ); } Long fc = (Long)cache.get( "fc" ); if ( fc != null ){ file_count = fc.intValue(); } Long l_size = (Long)cache.get( "size" ); if ( l_size != null ){ size = l_size.longValue(); } byte[] au = (byte[])cache.get( "au" ); if ( au != null ){ try{ announce_url = StringInterner.internURL(new URL((new String( au, "UTF-8" )))); }catch( Throwable e ){ } } List ag = (List)cache.get( "ag" ); if ( ag != null ){ try{ announce_group = importGroup( ag ); }catch( Throwable e ){ } } } protected static Map export( DownloadManagerState dms ) throws TOTorrentException { Map cache = new HashMap(); TOTorrent state = dms.getTorrent(); cache.put( "hash", state.getHash()); cache.put( "name", state.getName()); cache.put( "utf8name", state.getUTF8Name() == null ? "" : state.getUTF8Name()); cache.put( "comment", state.getComment()); cache.put( "createdby", state.getCreatedBy()); cache.put( "size", new Long( state.getSize())); cache.put( "encoding", state.getAdditionalStringProperty( "encoding" )); cache.put( "torrent filename", state.getAdditionalStringProperty( "torrent filename" )); cache.put( "attributes", state.getAdditionalMapProperty( ATTRIBUTE_KEY )); cache.put( "azp", state.getAdditionalMapProperty( AZUREUS_PROPERTIES_KEY )); try{ cache.put( "au", state.getAnnounceURL().toExternalForm()); cache.put( "ag", exportGroup(state.getAnnounceURLGroup())); }catch( Throwable e ){ } boolean discard_pieces = dms.isResumeDataComplete(); TOTorrent t = dms.getTorrent(); if ( t instanceof CachedStateWrapper ){ // We don't want to force a fixup here when saving! CachedStateWrapper csw = (CachedStateWrapper)t; if ( !discard_pieces ){ // discard pieces if they are currently discarded discard_pieces = csw.peekPieces() == null; } Boolean simple_torrent = csw.simple_torrent; if ( simple_torrent != null ){ cache.put( "simple", new Long(simple_torrent.booleanValue()?1:0 )); }else{ Debug.out( "Failed to cache simple state" ); } int fc = csw.file_count; if ( fc > 0 ){ cache.put( "fc", new Long( fc )); } }else{ if ( t instanceof TorrentUtils.torrentDelegate ){ // torrent is already 'fixed up' so no harm in directly grabbing stuff for export as already loaded cache.put( "simple", new Long(t.isSimpleTorrent()?1:0 )); cache.put( "fc", t.getFileCount()); }else{ Debug.out( "Hmm, torrent isn't cache-state-wrapper, it is " + t ); } } cache.put( "dp", new Long( discard_pieces?1:0 )); return( cache ); } protected static List exportGroup( TOTorrentAnnounceURLGroup group ) { TOTorrentAnnounceURLSet[] sets = group.getAnnounceURLSets(); List result = new ArrayList(); for (int i=0;i<sets.length;i++){ TOTorrentAnnounceURLSet set = sets[i]; URL[] urls = set.getAnnounceURLs(); if ( urls.length > 0 ){ List s = new ArrayList( urls.length ); for (int j=0;j<urls.length;j++){ s.add( urls[j].toExternalForm()); } result.add( s ); } } return( result ); } protected cacheGroup importGroup( List l ) throws Exception { return( new cacheGroup( l )); } protected class cacheGroup implements TOTorrentAnnounceURLGroup { private TOTorrentAnnounceURLSet[] sets; protected cacheGroup( List group ) throws Exception { sets = new TOTorrentAnnounceURLSet[ group.size() ]; for (int i = 0; i < sets.length; i++){ List set = (List) group.get(i); URL[] urls = new URL[set.size()]; for (int j = 0; j < urls.length; j++){ urls[j] = StringInterner.internURL(new URL(new String((byte[]) set.get(j), "UTF-8"))); } sets[i] = new cacheSet(urls); } } public TOTorrentAnnounceURLSet[] getAnnounceURLSets() { if ( announce_group == null && fixup()){ return delegate.getAnnounceURLGroup().getAnnounceURLSets(); } return( sets ); } void fixGroup() { TOTorrentAnnounceURLSet[] realSets = delegate.getAnnounceURLGroup().getAnnounceURLSets(); if ( realSets.length != sets.length ){ // not a major issue - since we now allow the cached groups and persisted ones to // drift (see DNS changes) the cached ones will be accurate and the persisted ones // will be re-adjusted should they become visible // Debug.out("Cached announce group state does not match real state"); }else{ for (int i=0;i<realSets.length;i++){ if ( sets[i] instanceof cacheSet ){ ((cacheSet)sets[i]).delegateSet = realSets[i]; } } sets = null; } } public void setAnnounceURLSets( TOTorrentAnnounceURLSet[] toSet ) { if ( fixup()){ TOTorrentAnnounceURLSet[] modToSet = new TOTorrentAnnounceURLSet[toSet.length]; for (int i = 0; i < toSet.length; i++){ TOTorrentAnnounceURLSet set = toSet[i]; if ( set instanceof cacheSet ){ modToSet[i] = ((cacheSet) set).delegateSet; } if ( modToSet[i] == null ){ modToSet[i] = set; } } delegate.getAnnounceURLGroup().setAnnounceURLSets( modToSet ); } } public TOTorrentAnnounceURLSet createAnnounceURLSet( URL[] urls ) { if ( fixup()){ return( delegate.getAnnounceURLGroup().createAnnounceURLSet( urls )); } return( null ); } protected class cacheSet implements TOTorrentAnnounceURLSet { private URL[] urls; private TOTorrentAnnounceURLSet delegateSet; public cacheSet(URL[] urls) { this.urls = urls; } public URL[] getAnnounceURLs() { if ( announce_group == null && fixup() && delegateSet != null ){ return delegateSet.getAnnounceURLs(); } return( urls ); } public void setAnnounceURLs( URL[] toSet ) { if ( fixup() && delegateSet != null ){ delegateSet.setAnnounceURLs( toSet ); }else{ urls = toSet; } } } } protected void clearCache() { cache = null; } protected boolean fixup() { try{ if ( delegate == null ){ synchronized( this ){ if ( delegate == null ){ // System.out.println( "Fixing up " + this ); if ( fixup_failure != null ){ throw( fixup_failure ); } delegate = loadRealState(); if ( discard_fluff ){ delegate.setDiscardFluff( discard_fluff ); } if ( cache != null ){ Debug.out( "Cache miss forced fixup" ); } cache = null; // join cache view back up with real state to save memory as the one // we've just read is irrelevant due to the cache values being // used if ( cache_attributes != null ){ delegate.setAdditionalMapProperty( ATTRIBUTE_KEY, cache_attributes ); cache_attributes = null; } if ( cache_azp != null ){ delegate.setAdditionalMapProperty( AZUREUS_PROPERTIES_KEY, cache_azp ); cache_azp = null; } announce_url = null; if ( announce_group != null ){ announce_group.fixGroup(); announce_group = null; } } } } return( true ); }catch( TOTorrentException e ){ fixup_failure = e; if ( download_manager != null ){ download_manager.setTorrentInvalid( Debug.getNestedExceptionMessage( e )); }else{ if ( !logged_failure ){ logged_failure = true; Debug.out( "Torrent can't be loaded: " + Debug.getNestedExceptionMessage( e )); } } } return( false ); } protected TorrentUtils.ExtendedTorrent loadRealState() throws TOTorrentException { // System.out.println("loadReal: " + torrent_file + " dp=" + discard_pieces + ": " + Debug.getCompressedStackTrace().substring(114)); if ( !SUPPRESS_FIXUP_ERRORS && Constants.isCVSVersion() ){ if ( Thread.currentThread().isDaemon()){ // Debug.outNoStack( "Fixup on thread " + Thread.currentThread().getName() + ": " + Debug.getCompressedStackTrace()); }else{ Debug.outNoStack( Debug.getCompressedStackTrace( new Exception(){public String toString(){ return( "Premature fixup?" );}}, 2, 10, true ), true); } } File saved_file = getStateFile( torrent_hash_wrapper.getBytes() ); if ( saved_file.exists()){ try{ return( TorrentUtils.readDelegateFromFile( saved_file, discard_pieces )); }catch( Throwable e ){ Debug.out( "Failed to load download state for " + saved_file ); } } // try reading from original TOTorrent original_torrent = TorrentUtils.readFromFile( new File(torrent_file), true ); torrent_hash_wrapper = original_torrent.getHashWrapper(); saved_file = getStateFile( torrent_hash_wrapper.getBytes()); if ( saved_file.exists()){ try{ return( TorrentUtils.readDelegateFromFile( saved_file, discard_pieces )); }catch( Throwable e ){ Debug.out( "Failed to load download state for " + saved_file ); } } // we must copy the torrent as we want one independent from the // original (someone might still have references to the original // and do stuff like write it somewhere else which would screw us // up) TorrentUtils.copyToFile( original_torrent, saved_file ); return( TorrentUtils.readDelegateFromFile( saved_file, discard_pieces )); } public byte[] getName() { Map c = cache; if ( c != null ){ byte[] name = (byte[])c.get( "name" ); if (name != null) { return name; } } if ( fixup()){ return( delegate.getName()); } // Does grabbing the nested exception message always give us something useful? // My experience is that we just get an empty string here... return(("Error - " + Debug.getNestedExceptionMessage( fixup_failure )).getBytes()); } public String getUTF8Name() { Map c = cache; if ( c != null ){ byte[] name = (byte[])c.get( "utf8name" ); if (name != null) { String utf8name; try { utf8name = new String(name, "utf8"); } catch (UnsupportedEncodingException e) { return null; } if (utf8name.length() == 0) { return null; } return utf8name; } } if (fixup()) { return delegate.getUTF8Name(); } return null; } public boolean isSimpleTorrent() { if ( simple_torrent != null ){ return( simple_torrent.booleanValue()); } if ( fixup()){ boolean st = delegate.isSimpleTorrent(); simple_torrent = new Boolean( st ); return( st ); } return( false ); } public byte[] getComment() { Map c = cache; if ( c != null ){ return((byte[])c.get( "comment" )); } if ( fixup()){ return( delegate.getComment()); } return( null ); } public void setComment( String comment ) { if ( fixup()){ delegate.setComment( comment ); } } public long getCreationDate() { if ( fixup()){ return( delegate.getCreationDate()); } return( 0 ); } public void setCreationDate( long date ) { if ( fixup()){ delegate.setCreationDate( date ); } } public byte[] getCreatedBy() { Map c = cache; if ( c != null ){ return((byte[])c.get( "createdby" )); } if ( fixup()){ return( delegate.getCreatedBy()); } return( null ); } public void setCreatedBy( byte[] cb ) { if ( fixup()){ delegate.setCreatedBy( cb ); } } public boolean isCreated() { if ( fixup()){ return( delegate.isCreated()); } return( false ); } public boolean isDecentralised() { return( TorrentUtils.isDecentralised( getAnnounceURL())); } public URL getAnnounceURL() { if ( announce_url != null ){ return( announce_url ); } if ( fixup()){ return( delegate.getAnnounceURL()); } return( null ); } public boolean setAnnounceURL( URL url ) { if ( announce_url != null ){ if ( announce_url.toExternalForm().equals( url.toExternalForm())){ return( false ); } } if ( fixup()){ return( delegate.setAnnounceURL( url )); }else{ announce_url = url; } return( false ); } public TOTorrentAnnounceURLGroup getAnnounceURLGroup() { if ( announce_group != null ){ return( announce_group ); } if ( fixup()){ return( delegate.getAnnounceURLGroup()); } return( null ); } public byte[][] getPieces() throws TOTorrentException { if ( fixup()){ return( delegate.getPieces()); } throw( fixup_failure ); } public void setPieces( byte[][] pieces ) throws TOTorrentException { if ( fixup()){ delegate.setPieces( pieces ); return; } throw( fixup_failure ); } public byte[][] peekPieces() throws TOTorrentException { if ( fixup()){ return( delegate.peekPieces()); } throw( fixup_failure ); } public void setDiscardFluff( boolean discard ) { discard_fluff = discard; if ( delegate != null ){ delegate.setDiscardFluff( discard_fluff ); } } public long getPieceLength() { if ( fixup()){ return( delegate.getPieceLength()); } return( 0 ); } public int getNumberOfPieces() { if ( fixup()){ return( delegate.getNumberOfPieces()); } return( 0 ); } public long getSize() { if ( size > 0 ){ return( size ); } if ( fixup()){ size = delegate.getSize(); return( size ); } return( 0 ); } public int getFileCount() { if ( file_count == 0 ){ if ( fixup()){ file_count= delegate.getFileCount(); } } return( file_count ); } public TOTorrentFile[] getFiles() { if ( fixup()){ return( delegate.getFiles()); } return( new TOTorrentFile[0] ); } public byte[] getHash() throws TOTorrentException { // optimise this return( torrent_hash_wrapper.getBytes()); } public HashWrapper getHashWrapper() throws TOTorrentException { return( torrent_hash_wrapper ); } public void setHashOverride( byte[] hash ) throws TOTorrentException { throw( new TOTorrentException( "Not supported", TOTorrentException.RT_HASH_FAILS )); } public boolean hasSameHashAs( TOTorrent other ) { try{ byte[] other_hash = other.getHash(); return( Arrays.equals( getHash(), other_hash )); }catch( TOTorrentException e ){ Debug.printStackTrace( e ); return( false ); } } public boolean getPrivate() { if ( fixup()){ return( delegate.getPrivate()); } return( false ); } public void setPrivate( boolean _private ) throws TOTorrentException { if ( fixup()){ delegate.setPrivate( _private ); } } public void setAdditionalStringProperty( String name, String value ) { if ( fixup()){ delegate.setAdditionalStringProperty( name, value ); } } public String getAdditionalStringProperty( String name ) { Map c = cache; if ( c != null && ( name.equals( "encoding") || name.equals( "torrent filename" ))){ byte[] res = (byte[])c.get( name ); if ( res == null ){ return( null ); } try{ return( new String( res, "UTF8" )); }catch( Throwable e ){ Debug.printStackTrace( e ); return( null ); } } if ( fixup()){ return( delegate.getAdditionalStringProperty( name )); } return( null ); } public void setAdditionalByteArrayProperty( String name, byte[] value ) { if ( fixup()){ delegate.setAdditionalByteArrayProperty( name, value ); } } public byte[] getAdditionalByteArrayProperty( String name ) { if ( fixup()){ return( delegate.getAdditionalByteArrayProperty( name )); } return( null ); } public void setAdditionalLongProperty( String name, Long value ) { if ( fixup()){ delegate.setAdditionalLongProperty( name, value ); } } public Long getAdditionalLongProperty( String name ) { if ( fixup()){ return( delegate.getAdditionalLongProperty( name )); } return( null ); } public void setAdditionalListProperty( String name, List value ) { if ( fixup()){ delegate.setAdditionalListProperty( name, value ); } } public List getAdditionalListProperty( String name ) { if ( fixup()){ return( delegate.getAdditionalListProperty( name )); } return( null ); } public void setAdditionalMapProperty( String name, Map value ) { if ( fixup()){ delegate.setAdditionalMapProperty( name, value ); } } public Map getAdditionalMapProperty( String name ) { Map c = cache_attributes; if ( c != null && name.equals( "attributes" )){ return( c ); } c = cache_azp; if ( c != null && name.equals( "azureus_properties" )){ return( c ); } if ( fixup()){ return( delegate.getAdditionalMapProperty( name )); } return( null ); } public Object getAdditionalProperty( String name ) { if ( fixup()){ return( delegate.getAdditionalProperty( name )); } return( null ); } public void setAdditionalProperty( String name, Object value ) { if ( fixup()){ delegate.setAdditionalProperty( name, value ); } } public void removeAdditionalProperty( String name ) { if ( fixup()){ delegate.removeAdditionalProperty( name ); } } public void removeAdditionalProperties() { if ( fixup()){ delegate.removeAdditionalProperties(); } } public void serialiseToBEncodedFile( File file ) throws TOTorrentException { if ( fixup()){ delegate.serialiseToBEncodedFile( file ); return; } throw( fixup_failure ); } public Map serialiseToMap() throws TOTorrentException { if ( fixup()){ return( delegate.serialiseToMap()); } throw( fixup_failure ); } public void serialiseToXMLFile( File file ) throws TOTorrentException { if ( fixup()){ delegate.serialiseToXMLFile( file ); return; } throw( fixup_failure ); } public void addListener( TOTorrentListener l ) { if ( fixup()){ delegate.addListener( l ); } } public void removeListener( TOTorrentListener l ) { if ( fixup()){ delegate.removeListener( l ); } } public AEMonitor getMonitor() { if ( fixup()){ return( delegate.getMonitor()); } return( null ); } public void print() { if ( fixup()){ delegate.print(); } } /* (non-Javadoc) * @see org.gudy.azureus2.core3.logging.LogRelation#getLogRelationText() */ public String getRelationText() { return "Torrent: '" + new String(getName()) + "'"; } /* (non-Javadoc) * @see org.gudy.azureus2.core3.logging.LogRelation#queryForClass(java.lang.Class) */ public Object[] getQueryableInterfaces() { // yuck try { return new Object[] { AzureusCoreFactory.getSingleton() .getGlobalManager().getDownloadManager(this) }; } catch (Exception e) { } return null; } } }