Package org.gudy.azureus2.core3.download.impl

Source Code of org.gudy.azureus2.core3.download.impl.DownloadManagerController

/*
* Created on 29-Jul-2005
* Created by Paul Gardner
* Copyright (C) 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.File;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.util.*;

import org.gudy.azureus2.core3.config.COConfigurationManager;
import org.gudy.azureus2.core3.disk.*;
import org.gudy.azureus2.core3.download.DownloadManager;
import org.gudy.azureus2.core3.download.DownloadManagerDiskListener;
import org.gudy.azureus2.core3.download.DownloadManagerState;
import org.gudy.azureus2.core3.download.ForceRecheckListener;
import org.gudy.azureus2.core3.global.GlobalManager;
import org.gudy.azureus2.core3.global.GlobalManagerStats;
import org.gudy.azureus2.core3.internat.MessageText;
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.*;
import org.gudy.azureus2.core3.torrent.TOTorrent;
import org.gudy.azureus2.core3.torrent.TOTorrentException;
import org.gudy.azureus2.core3.torrent.TOTorrentFile;
import org.gudy.azureus2.core3.tracker.client.TRTrackerAnnouncer;
import org.gudy.azureus2.core3.tracker.client.TRTrackerAnnouncerDataProvider;
import org.gudy.azureus2.core3.tracker.client.TRTrackerScraperResponse;
import org.gudy.azureus2.core3.util.*;
import org.gudy.azureus2.plugins.network.ConnectionManager;

import com.aelitis.azureus.core.AzureusCoreFactory;
import com.aelitis.azureus.core.networkmanager.LimitedRateGroup;
import com.aelitis.azureus.core.networkmanager.NetworkConnection;
import com.aelitis.azureus.core.networkmanager.NetworkManager;
import com.aelitis.azureus.core.peermanager.PeerManager;
import com.aelitis.azureus.core.peermanager.PeerManagerRegistration;
import com.aelitis.azureus.core.peermanager.PeerManagerRegistrationAdapter;
import com.aelitis.azureus.core.peermanager.peerdb.PeerItemFactory;
import com.aelitis.azureus.core.util.bloom.BloomFilter;
import com.aelitis.azureus.core.util.bloom.BloomFilterFactory;
import com.aelitis.azureus.plugins.extseed.ExternalSeedPeer;
import com.aelitis.azureus.plugins.extseed.ExternalSeedPlugin;

public class
DownloadManagerController
  extends LogRelation
  implements PEPeerManagerAdapter, PeerManagerRegistrationAdapter
{
  private static long STATE_FLAG_HASDND = 0x01;
  private static long STATE_FLAG_COMPLETE_NO_DND = 0x02;
 
  private static long skeleton_builds;
 
  private static ExternalSeedPlugin  ext_seed_plugin;
  private static boolean        ext_seed_plugin_tried;
 
  protected static ExternalSeedPlugin
  getExternalSeedPlugin()
  {
    if ( !ext_seed_plugin_tried ){
     
      ext_seed_plugin_tried  = true;
     
      try {
        org.gudy.azureus2.plugins.PluginInterface ext_pi = AzureusCoreFactory.getSingleton().getPluginManager().getPluginInterfaceByClass(ExternalSeedPlugin.class);
        if (ext_pi != null) {
          ext_seed_plugin = (ExternalSeedPlugin)ext_pi.getPlugin();
        }
 
      }catch (Throwable e){
 
        Debug.printStackTrace( e );
      }
    }
   
    return( ext_seed_plugin );
  }
 
    // DISK listeners
 
  private static final int LDT_DL_ADDED    = 1;
  private static final int LDT_DL_REMOVED    = 2;

  private static ListenerManager  disk_listeners_agregator   = ListenerManager.createAsyncManager(
      "DMC:DiskListenAgregatorDispatcher",
      new ListenerManagerDispatcher()
      {
        public void
        dispatch(
          Object    _listener,
          int      type,
          Object    value )
        {
          DownloadManagerDiskListener  listener = (DownloadManagerDiskListener)_listener;
         
          if ( type == LDT_DL_ADDED ){
           
            listener.diskManagerAdded((DiskManager)value);
           
          }else if ( type == LDT_DL_REMOVED ){
           
            listener.diskManagerRemoved((DiskManager)value);
          }
        }
      })
 
  private ListenerManager  disk_listeners   = ListenerManager.createManager(
      "DMC:DiskListenDispatcher",
      new ListenerManagerDispatcher()
      {
        public void
        dispatch(
          Object    listener,
          int      type,
          Object    value )
        {
          disk_listeners_agregator.dispatch( listener, type, value );
        }
      });
 
  private AEMonitor  disk_listeners_mon  = new AEMonitor( "DownloadManagerController:DL" );
 
  private AEMonitor  control_mon    = new AEMonitor( "DownloadManagerController" );
  private AEMonitor  state_mon    = new AEMonitor( "DownloadManagerController:State" );
  private AEMonitor  facade_mon    = new AEMonitor( "DownloadManagerController:Facade" );
 
  private DownloadManagerImpl      download_manager;
  private DownloadManagerStatsImpl  stats;
 
    // these are volatile as we want to ensure that if a state is read it is always the
    // most up to date value available (as we don't synchronize state read - see below
    // for comments)
 
  private volatile int    state_set_by_method = DownloadManager.STATE_START_OF_DAY;
  private volatile int    substate;
  private volatile boolean   force_start;

    // to try and ensure people don't start using disk_manager without properly considering its
    // access implications we've given it a silly name
 
  private volatile DiskManager       disk_manager_use_accessors;
  private DiskManagerListener        disk_manager_listener_use_accessors;
 
  private FileInfoFacadeSet    fileFacadeSet = new FileInfoFacadeSet();
  private boolean          files_facade_destroyed;
 
  private boolean          cached_complete_excluding_dnd;
  private boolean          cached_has_dnd_files;
  private boolean         cached_values_set;
 
  private PeerManagerRegistration  peer_manager_registration;
  private PEPeerManager       peer_manager;
 
  private List  external_rate_limiters_cow;
 
  private String errorDetail;

  private GlobalManagerStats    global_stats;
 
  private boolean bInitialized = false;
 
  private long data_send_rate_at_close;
 
  private static final int      ACTIVATION_REBUILD_TIME    = 10*60*1000;
  private static final int      BLOOM_SIZE          = 64;
  private volatile BloomFilter    activation_bloom;
  private volatile long        activation_bloom_create_time  = SystemTime.getCurrentTime();
  private volatile int        activation_count;
  private volatile long        activation_count_time;
 
  private boolean   piece_checking_enabled  = true;
 
  private long    priority_connection_count;
 
  private static final int        HTTP_SEEDS_MAX  = 64;
  private LinkedList<ExternalSeedPeer>  http_seeds = new LinkedList<ExternalSeedPeer>();
 
  protected
  DownloadManagerController(
    DownloadManagerImpl  _download_manager )
  {
    download_manager = _download_manager;
   
    GlobalManager  gm = download_manager.getGlobalManager();
         
    global_stats = gm.getStats();
   
    stats  = (DownloadManagerStatsImpl)download_manager.getStats();

    cached_values_set = false;
  }
 
  protected void
  setInitialState(
    int  initial_state )
  {
      // only take note if there's been no errors
   
    bInitialized = true;
   
    if ( getState() == DownloadManager.STATE_START_OF_DAY ){
     
      setState( initial_state, true );
    }
   
 
    TOTorrent torrent = download_manager.getTorrent();

    if (torrent != null) {
     
      try{
        peer_manager_registration = PeerManager.getSingleton().registerLegacyManager( torrent.getHashWrapper(), this );
       
      }catch( TOTorrentException e ){
       
        Debug.printStackTrace(e);
      }
    }
     
    DownloadManagerState state = download_manager.getDownloadState();
    if (state.parameterExists(DownloadManagerState.PARAM_DND_FLAGS)) {
      long flags = state.getLongParameter(DownloadManagerState.PARAM_DND_FLAGS);
      cached_complete_excluding_dnd = (flags & STATE_FLAG_COMPLETE_NO_DND) != 0;
      cached_has_dnd_files = (flags & STATE_FLAG_HASDND) != 0;
      cached_values_set = true;
    }
  }

  public void
  startDownload(
    TRTrackerAnnouncer  tracker_client )
  {
    DiskManager  dm;
   
    try{
      control_mon.enter();
   
      if ( getState() != DownloadManager.STATE_READY ){
     
        Debug.out( "DownloadManagerController::startDownload state must be ready, " + getState());
       
        setFailed( "Inconsistent download state: startDownload, state = " + getState());

        return;
      }
     
       if ( tracker_client == null ){
         
          Debug.out( "DownloadManagerController:startDownload: tracker_client is null" );
         
            // one day we should really do a proper state machine for this. In the meantime...
            // probably caused by a "stop" during initialisation, I've reproduced it once or twice
            // in my life... Tidy things up so we don't sit here in a READ state that can't
            // be started.
         
          stopIt( DownloadManager.STATE_STOPPED, false, false );
         
          return;
        }
         
      if ( peer_manager != null ){
       
        Debug.out( "DownloadManagerController::startDownload: peer manager not null" );
       
          // try and continue....
       
        peer_manager.stopAll();
       
        DownloadManagerRateController.removePeerManager( peer_manager );
       
        peer_manager  = null;
      }
     
      dm  = getDiskManager();
     
      if ( dm == null ){
       
        Debug.out( "DownloadManagerController::startDownload: disk manager is null" );
       
        return;
      }
   
      setState( DownloadManager.STATE_DOWNLOADING, false );
   
    }finally{
     
      control_mon.exit();
 
    }
   
      // make sure it is started before making it "visible"
   
    final PEPeerManager temp = PEPeerManagerFactory.create( tracker_client.getPeerId(), this, dm );
   
    download_manager.informWillBeStarted( temp );
   
    temp.start();
 
       //The connection to the tracker
   
    tracker_client.setAnnounceDataProvider(
          new TRTrackerAnnouncerDataProvider()
          {
            private long  last_reported_total_received;
            private long  last_reported_total_received_data;
            private long  last_reported_total_received_discard;
            private long  last_reported_total_received_failed;
           
            public String
          getName()
            {
              return( getDisplayName());
            }
           
            public long
            getTotalSent()
            {
              return(temp.getStats().getTotalDataBytesSentNoLan());
            }
            public long
            getTotalReceived()
            {
              long received   = temp.getStats().getTotalDataBytesReceivedNoLan();
              long discarded   = temp.getStats().getTotalDiscarded();
              long failed    = temp.getStats().getTotalHashFailBytes();
             
              long verified = received - ( discarded + failed );

              verified -= temp.getHiddenBytes();
             
                // ensure we don't go backwards. due to lack of atomicity of updates and possible
                // miscounting somewhere we have seen this occur
             
              if ( verified < last_reported_total_received ){
               
                verified = last_reported_total_received;
               
                  // use -1 as indicator that we've reported this event
               
                if ( last_reported_total_received_data != -1 ){
                   
                  /*
                  Debug.out(
                      getDisplayName() + ": decrease in overall downloaded - " +
                      "data=" + received + "/" + last_reported_total_received_data +
                      ",discard=" + discarded + "/" + last_reported_total_received_discard +
                      ",fail=" + failed + "/" + last_reported_total_received_failed );
                  */
                 
                  last_reported_total_received_data = -1;
                }
              }else{
               
                last_reported_total_received = verified;
             
                last_reported_total_received_data    = received;
                last_reported_total_received_discard  = discarded;
                last_reported_total_received_failed    = failed;
              }
             
              return( verified < 0?0:verified );
            }
           
            public long
            getRemaining()
            {
              return( Math.max( temp.getRemaining(), temp.getHiddenBytes()));
            }
         
            public long
            getFailedHashCheck()
            {
              return( temp.getStats().getTotalHashFailBytes());
            }
           
          public String
          getExtensions()
          {
            return( getTrackerClientExtensions());
          }
         
          public int
          getMaxNewConnectionsAllowed()
          {
            return( temp.getMaxNewConnectionsAllowed());
          }
         
          public int
          getPendingConnectionCount()
          {
            return( temp.getPendingPeerCount());
          }
         
          public int
          getConnectedConnectionCount()
          {
            return( temp.getNbPeers() + temp.getNbSeeds());
          }
         
          public int
          getUploadSpeedKBSec(
            boolean estimate )
          {
            long  current_local = stats.getDataSendRate();
           
            if ( estimate ){
             
                // see if we have an old value from previous stop/start
             
              current_local = data_send_rate_at_close;
             
              if ( current_local == 0 ){
               
                int current_global   = global_stats.getDataSendRate();
               
                int  old_global    = global_stats.getDataSendRateAtClose();
             
                if ( current_global < old_global ){
                 
                  current_global = old_global;
                }
           
                List managers = download_manager.getGlobalManager().getDownloadManagers();
               
                int  num_dls = 0;
               
                  // be optimistic and share out the bytes between non-seeds
               
                for (int i=0;i<managers.size();i++){
                 
                  DownloadManager  dm = (DownloadManager)managers.get(i);
                 
                  if ( dm.getStats().getDownloadCompleted( false ) == 1000 ){
                   
                    continue;
                  }
                 
                  int  state = dm.getState();
                 
                  if (   state != DownloadManager.STATE_ERROR &&
                      state != DownloadManager.STATE_STOPPING &&
                      state != DownloadManager.STATE_STOPPED ){
                   
                    num_dls++;
                  }
                }
               
                if ( num_dls == 0 ){
                 
                  current_local = current_global;
                }else{
                 
                  current_local = current_global/num_dls;
                }
              }
            }
                       
            return((int)((current_local+1023)/1024 ));
          }
         
          public int
          getCryptoLevel()
          {
            return( download_manager.getCryptoLevel());
          }
         
          public void
          setPeerSources(
            String[]  allowed_sources )
          {
            DownloadManagerState  dms = download_manager.getDownloadState();
           
            String[]  sources = PEPeerSource.PS_SOURCES;
           
            for (int i=0;i<sources.length;i++){
             
              String  s = sources[i];
             
              boolean  ok = false;
             
              for (int j=0;j<allowed_sources.length;j++){
               
                if ( s.equals( allowed_sources[j] )){
                 
                  ok = true;
                 
                  break;
                }
              }
             
              if ( !ok ){
               
                dms.setPeerSourcePermitted( s, false );
              }
            }
           
            PEPeerManager pm = getPeerManager();
           
            if ( pm != null ){
             
              Set<String>  allowed = new HashSet<String>();
             
              allowed.addAll( Arrays.asList( allowed_sources ));
             
              Iterator<PEPeer> it = pm.getPeers().iterator();
           
              while( it.hasNext()){
               
                PEPeer peer = it.next();
               
                if ( !allowed.contains( peer.getPeerSource())){
                 
                  pm.removePeer( peer, "Peer source not permitted" );
                }
              }
            }
          }
         
          public boolean
          isPeerSourceEnabled(
            String    peer_source )
          {
            return( DownloadManagerController.this.isPeerSourceEnabled( peer_source ));
          }
          });
     
   
    List  limiters;
   
    try{
      control_mon.enter();
   
      peer_manager = temp;

      DownloadManagerRateController.addPeerManager( peer_manager );
     
      limiters = external_rate_limiters_cow;
     
    }finally{
     
      control_mon.exit();
    }
   
    if ( limiters != null ){
     
      for (int i=0;i<limiters.size();i++){
       
        Object[]  entry = (Object[])limiters.get(i);
       
        temp.addRateLimiter((LimitedRateGroup)entry[0],((Boolean)entry[1]).booleanValue());
      }
    }
   
    // Inform only after peer_manager.start(), because it
    // may have switched it to STATE_SEEDING (in which case we don't need to
    // inform).
   
    if (getState() == DownloadManager.STATE_DOWNLOADING) {
     
      download_manager.informStateChanged();
    }

    download_manager.informStarted( temp );
  }
 
 
 

  public void
  initializeDiskManager(
    final boolean  open_for_seeding )
  {
    initializeDiskManagerSupport(
      DownloadManager.STATE_INITIALIZED,
      new DiskManagerListener()
          {
            public void
            stateChanged(
              int   oldDMState,
              int    newDMState )
            {
              DiskManager  dm;
             
              try{
                control_mon.enter();
             
                dm = getDiskManager();

                if ( dm == null ){
               
                    // already been cleared down
                 
                  return;
                }
               
              }finally{
                control_mon.exit();
              }
             
              try{
                if ( newDMState == DiskManager.FAULTY ){
                 
                  setFailed( dm.getErrorMessage());           
                 }
                 
                if ( oldDMState == DiskManager.CHECKING && newDMState != DiskManager.CHECKING ){
                   
                    // good time to trigger minimum file info fixup as the disk manager's
                    // files are now in a good state

                  fileFacadeSet.makeSureFilesFacadeFilled(true);

                  stats.setDownloadCompleted(stats.getDownloadCompleted(true));
                   
                  download_manager.setAssumedComplete(isDownloadComplete(false));
                }
                   
                if ( newDMState == DiskManager.READY ){
                 
                  int  completed = stats.getDownloadCompleted(false);
                                   
                  if (   stats.getTotalDataBytesReceived() == 0 &&
                      stats.getTotalDataBytesSent() == 0 &&
                      stats.getSecondsDownloading() == 0 ){

                    if ( completed < 1000 ){
                   
                      if ( open_for_seeding ){
                       
                        setFailed( "File check failed" );
                       
                        download_manager.getDownloadState().clearResumeData();
                       
                      }else{
                       
                        // make up some sensible "downloaded" figure for torrents that have been re-added to Azureus
                        // and resumed
                   
                                                    
                          // assume downloaded = uploaded, optimistic but at least results in
                          // future share ratios relevant to amount up/down from now on
                          // see bug 1077060
                         
                        long  amount_downloaded = (completed*dm.getTotalLength())/1000;
                         
                       stats.setSavedDownloadedUploaded( amount_downloaded, amount_downloaded );
                      }
                    }else{               
                        // see GlobalManager for comment on this
                     
                      int  dl_copies = COConfigurationManager.getIntParameter("StartStopManager_iAddForSeedingDLCopyCount");
                         
                    if ( dl_copies > 0 ){
                     
                        stats.setSavedDownloadedUploaded( download_manager.getSize()*dl_copies, stats.getTotalDataBytesSent());
                      }
                   
                        download_manager.getDownloadState().setFlag( DownloadManagerState.FLAG_ONLY_EVER_SEEDED, true );
                    }
                      }
                 
                  /* all initialization should be done here (Disk- and DownloadManager).
                   * assume this download is complete and won't recieve any modifications until it is stopped again
                   * or the user fiddles on the knobs
                   * discard fluff once tentatively, will save memory for many active, seeding torrent-cases
                   */
                  if(completed == 1000)                  
                    download_manager.getDownloadState().discardFluff();
                }
              }finally{
                               
                download_manager.informStateChanged();
              }
            }

                  public void
          filePriorityChanged(
            DiskManagerFileInfo  file )
                  { 
                    download_manager.informPriorityChange( file );
                  }
                 
                   public void
                pieceDoneChanged(
                  DiskManagerPiece  piece )
                {              
                }
                  
                public void
                fileAccessModeChanged(
                  DiskManagerFileInfo    file,
                  int            old_mode,
                  int            new_mode )
                {
                }
          });
  }
 
  protected void
  initializeDiskManagerSupport(
    int            initialising_state,
    DiskManagerListener    listener )
  {
    try{
      control_mon.enter();
   
      int  entry_state = getState();
       
      if (   entry_state != DownloadManager.STATE_WAITING &&
          entry_state != DownloadManager.STATE_STOPPED &&
          entry_state != DownloadManager.STATE_QUEUED &&
          entry_state != DownloadManager.STATE_ERROR ){
         
        Debug.out( "DownloadManagerController::initializeDiskManager: Illegal initialize state, " + entry_state );
       
        setFailed( "Inconsistent download state: initSupport, state = " + entry_state );
       
        return;
      }
 
      DiskManager  old_dm = getDiskManager();
      
      if ( old_dm != null ){
       
        Debug.out( "DownloadManagerController::initializeDiskManager: disk manager is not null" );
       
          // we shouldn't get here but try to recover the situation
       
        old_dm.stop( false );
       
        setDiskManager( null, null );
      }
   
      errorDetail  = "";
         
      setState( initialising_state, false );
             
        DiskManager dm = DiskManagerFactory.create( download_manager.getTorrent(), download_manager);
           
          setDiskManager( dm, listener );
                     
    }finally{
     
      control_mon.exit();
   
      download_manager.informStateChanged();
    }
  }
       
  public boolean
  canForceRecheck()
  {
      int state = getState();
         
        // gotta check error + disk manager state as error can be generated by both
        // an overall error or a running disk manager in faulty state
     
      return(    (state == DownloadManager.STATE_STOPPED ) ||
                   (state == DownloadManager.STATE_QUEUED ) ||
                   (state == DownloadManager.STATE_ERROR && getDiskManager() == null));
  }

  public void
  forceRecheck(final ForceRecheckListener l)
  {
    try{
      control_mon.enter();
   
      if ( getDiskManager() != null || !canForceRecheck() ){
       
        Debug.out( "DownloadManagerController::forceRecheck: illegal entry state" );
       
        return;
      }
     
      final int start_state = DownloadManagerController.this.getState();
                   
        // remove resume data
     
        download_manager.getDownloadState().clearResumeData();
             
          // For extra protection from a plugin stopping a checking torrent,
          // fake a forced start.
             
        final boolean wasForceStarted = force_start;
             
        force_start = true;
     
        // if a file has been deleted we want this recheck to recreate the file and mark
        // it as 0%, not fail the recheck. Otherwise the only way of recovering is to remove and
        // re-add the torrent
           
        download_manager.setDataAlreadyAllocated( false );
             
        initializeDiskManagerSupport(
          DownloadManager.STATE_CHECKING,
          new forceRecheckDiskManagerListener(wasForceStarted, start_state, l));
       
    }finally{
     
      control_mon.exit();
    }
  }     
 
   public void
    setPieceCheckingEnabled(
      boolean enabled )
   {
     piece_checking_enabled  = enabled;
    
     DiskManager dm = getDiskManager();
  
     if ( dm != null ){
      
       dm.setPieceCheckingEnabled( enabled );
     }
   }
  
  public void
  stopIt(
    int         _stateAfterStopping,
    final boolean     remove_torrent,
    final boolean     remove_data )
  {   
    long  current_up = stats.getDataSendRate();
   
    if ( current_up != 0 ){
     
      data_send_rate_at_close = current_up;
    }
   
    boolean closing = _stateAfterStopping == DownloadManager.STATE_CLOSED;
   
    if ( closing ){
     
      _stateAfterStopping = DownloadManager.STATE_STOPPED;
    }
   
    final int stateAfterStopping  = _stateAfterStopping;
   
    try{
      control_mon.enter();
   
      int  state = getState();
     
      if (   state == DownloadManager.STATE_STOPPED ||
          ( state == DownloadManager.STATE_ERROR && getDiskManager() == null )) {
     
        //already in stopped state, just do removals if necessary
       
        if( remove_data ){
         
          download_manager.deleteDataFiles();
        }
       
        if( remove_torrent ){
         
          download_manager.deleteTorrentFile();
        }
       
        setState( _stateAfterStopping, false );
       
        return;
      }

   
      if ( state == DownloadManager.STATE_STOPPING){
   
        return;
      }
   
      setSubState( _stateAfterStopping );
     
      setState( DownloadManager.STATE_STOPPING, false );


        // this will run synchronously but on a non-daemon thread so that it will under
          // normal circumstances complete, even if we're closing
   

      final  AESemaphore nd_sem = new AESemaphore( "DM:DownloadManager.NDTR" );
     
      NonDaemonTaskRunner.runAsync(
        new NonDaemonTask()
        {
          public Object
          run()
          {
            nd_sem.reserve();
           
            return( null );
          }
         
          public String
          getName()
          {
            return( "Stopping '" + getDisplayName() + "'" );
          }
         
        });
           
      try{       
        try{
                     
          if ( peer_manager != null ){
           
            peer_manager.stopAll();
           
            stats.saveSessionTotals();
           
            DownloadManagerRateController.removePeerManager( peer_manager );
          }
         
            // do this even if null as it also triggers tracker actions
         
          download_manager.informStopped( peer_manager, stateAfterStopping==DownloadManager.STATE_QUEUED );
                     
          peer_manager  = null;

          DiskManager  dm = getDiskManager();
         
          if ( dm != null ){
           
            boolean went_async = dm.stop( closing );

            if ( went_async ){
             
              int  wait_count = 0;

              // Delay by 10ms, hoping for really short stop
              Thread.sleep(10);

              while( !dm.isStopped()){
             
                wait_count++;
               
                if ( wait_count > 2*60*10 ){
                 
                  Debug.out( "Download stop took too long to complete" );
                 
                  break;
                 
                }else if ( wait_count % 200 == 0 ){
                 
                  Debug.out( "Waiting for download to stop - elapsed=" + wait_count + " sec" );
                }
               
                Thread.sleep(100);
              }
            }
           
            stats.setCompleted(stats.getCompleted());
            stats.setDownloadCompleted(stats.getDownloadCompleted(true));
           
                // we don't want to update the torrent if we're seeding
           
            if ( !download_manager.getAssumedComplete()){
              download_manager.getDownloadState().save();
            }                   
                           
            setDiskManager( null, null );
          }
       
         }finally{
               
           force_start = false;
        
           if( remove_data ){
          
               download_manager.deleteDataFiles();
           }
          
           if( remove_torrent ){
            
             download_manager.deleteTorrentFile();
           }
        
           List<ExternalSeedPeer> to_remove = new ArrayList<ExternalSeedPeer>();

           synchronized( http_seeds ){

             to_remove.addAll( http_seeds );
            
             http_seeds.clear();
           }
         
           for ( ExternalSeedPeer peer: to_remove ){
            
             peer.remove();
           }
          
               // only update the state if things haven't gone wrong
          
           if ( getState() == DownloadManager.STATE_STOPPING ){
            
             setState( stateAfterStopping, true );
           }
         }
      }finally{
       
        nd_sem.release();
      }
     
    }catch( Throwable e ){
     
      Debug.printStackTrace( e );
   
    }finally{
   
      control_mon.exit();
     
      download_manager.informStateChanged();
    }
  }

  protected void
  setStateWaiting()
  {
    setState(DownloadManager.STATE_WAITING, true );
  }
 
    public void
    setStateFinishing()
    {
      setState(DownloadManager.STATE_FINISHING, true);
    }

  public void
  setStateDownloading()
  { 
    if (getState() == DownloadManager.STATE_SEEDING) {
      setState(DownloadManager.STATE_DOWNLOADING, true);
    } else if (getState() != DownloadManager.STATE_DOWNLOADING) {
      Logger.log(new LogEvent(this, LogIDs.CORE, LogEvent.LT_WARNING,
          "Trying to set state to downloading when state is not seeding"));
    }
  }
 

    public void
    setStateSeeding(
      boolean  never_downloaded )
  {
    // should already be finishing, but make sure (if it already is, there
    // won't be a trigger)
    setStateFinishing();

    download_manager.downloadEnded(never_downloaded);

    setState(DownloadManager.STATE_SEEDING, true);
  }
 
    public boolean
    isStateSeeding()
    {
      return( getState() == DownloadManager.STATE_SEEDING );
    }
   
    protected void
    setStateQueued()
    {
      setState(DownloadManager.STATE_QUEUED, true);
    }
 
    public int
    getState()
    {
      if ( state_set_by_method != DownloadManager.STATE_INITIALIZED ){
   
        return( state_set_by_method );
      }
 
        // we don't want to synchronize here as there are potential deadlock problems
        // regarding the DownloadManager::addListener call invoking this method while
        // holding the listeners monitor.
        //
      DiskManager  dm = getDiskManager();
      
      if ( dm == null){
     
        return DownloadManager.STATE_INITIALIZED;
      }
   
      int diskManagerState = dm.getState();

    if (diskManagerState == DiskManager.INITIALIZING){
   
      return DownloadManager.STATE_INITIALIZED;
     
    }else if (diskManagerState == DiskManager.ALLOCATING){
     
      return DownloadManager.STATE_ALLOCATING;
     
    }else if (diskManagerState == DiskManager.CHECKING){
     
      return DownloadManager.STATE_CHECKING;
     
    }else if (diskManagerState == DiskManager.READY){
     
      return DownloadManager.STATE_READY;
     
    }else if (diskManagerState == DiskManager.FAULTY){
     
      return DownloadManager.STATE_ERROR;
    }
     
    return DownloadManager.STATE_ERROR;
    }
 
  protected int
    getSubState()
    {
    if ( state_set_by_method == DownloadManager.STATE_STOPPING ){
     
      return( substate );
    }else{
     
      return( getState());
    }
    }

  private void
  setSubState(
    int  ss )
  {
    substate  = ss;
  }
 
  /**
   * @param _state
   * @param _inform_changed trigger informStateChange (which may not trigger
   *                        listeners if state hasn't changed since last trigger)
   */
    private void
    setState(
      int     _state,
      boolean    _inform_changed )
    {  
        // we bring this call out of the monitor block to prevent a potential deadlock whereby we chain
        // state_mon -> control_mon (there exist numerous dependencies control_mon -> state_mon...
     
      boolean  call_filesExist  = false;
     
       try{
        state_mon.enter();
     
        int  old_state = state_set_by_method;
     
      // note: there is a DIFFERENCE between the state held on the DownloadManager and
        // that reported via getState as getState incorporated DiskManager states when
        // the DownloadManager is INITIALIZED
        //System.out.println( "DM:setState - " + _state );
     
        if ( old_state != _state ){
       
          state_set_by_method = _state;
               
          if ( state_set_by_method != DownloadManager.STATE_QUEUED ){
           
              // only maintain this while queued
           
            activation_bloom = null;
           
            if ( state_set_by_method == DownloadManager.STATE_STOPPED ){
           
              activation_count = 0;
            }
          }
         
          if (state_set_by_method == DownloadManager.STATE_QUEUED ){
         

            // don't pick up errors regarding missing data while queued.
            // We'll do that when the torrent starts.  Saves time at startup
            // pick up any errors regarding missing data for queued SEEDING torrents
//            if (  download_manager.getAssumedComplete()){
//           
//              call_filesExist  = true;
//            }
         
          }else if ( state_set_by_method == DownloadManager.STATE_ERROR ){
       
              // the process of attempting to start the torrent may have left some empty
              // directories created, some users take exception to this.
              // the most straight forward way of remedying this is to delete such empty
              // folders here
         
            TOTorrent  torrent = download_manager.getTorrent();
       
            if ( torrent != null && !torrent.isSimpleTorrent()){
 
              File  save_dir_file  = download_manager.getAbsoluteSaveLocation();
 
              if ( save_dir_file != null && save_dir_file.exists() && save_dir_file.isDirectory()){
             
                TorrentUtils.recursiveEmptyDirDelete( save_dir_file, false );
              }
            }
          }
        }
      }finally{
       
        state_mon.exit();
      }
       
      if ( call_filesExist ){
       
        filesExist( true );
      }
     
      if ( _inform_changed ){
       
        download_manager.informStateChanged();
      }
    }
 
   /**
     * Stops the current download, then restarts it again.
     */
   
  public void
  restartDownload(boolean forceRecheck)
  {
    boolean  was_force_start = isForceStart();
         
    stopIt( DownloadManager.STATE_STOPPED, false, false );
     
    if (forceRecheck) {
      download_manager.getDownloadState().clearResumeData();
    }

    download_manager.initialize();
     
    if ( was_force_start ){
       
      setForceStart(true);
    }
  } 
  
  protected void
  destroy()
  {
    if ( peer_manager_registration != null ){
     
      peer_manager_registration.unregister();
     
      peer_manager_registration  = null;
    }
   
    fileFacadeSet.destroyFileInfo();
  }
 
  public boolean
  isPeerSourceEnabled(
    String    peer_source )
  {
    return( download_manager.getDownloadState().isPeerSourceEnabled( peer_source ));
  }
 
    // secrets for inbound connections, support all
 
  public byte[][]
  getSecrets()
  {
    TOTorrent  torrent = download_manager.getTorrent();
       
    try{
      byte[]  secret1 = torrent.getHash();
     
      try{
       
        byte[]  secret2   = getSecret2( torrent );
     
        return( new byte[][]{ secret1, secret2 });
       
      }catch( Throwable e ){
       
        Debug.printStackTrace( e );
       
        return( new byte[][]{ secret1 } );
      }
     
    }catch( Throwable e ){
     
      Debug.printStackTrace( e );
     
      return( new byte[0][] );
    }
  }
 
    // secrets for outbound connections, based on level of target
 
  public byte[][]
    getSecrets(
      int  crypto_level )
  {
    TOTorrent  torrent = download_manager.getTorrent();

    try{
      byte[]  secret;
     
      if ( crypto_level == PeerItemFactory.CRYPTO_LEVEL_1 ){
       
        secret = torrent.getHash();
       
      }else{
       
        secret = getSecret2( torrent );
      }
     
      return( new byte[][]{ secret });
     
    }catch( Throwable e ){
     
      Debug.printStackTrace( e );
     
      return( new byte[0][] );
    }
  }
 
  protected byte[]
  getSecret2(
    TOTorrent  torrent )
 
    throws TOTorrentException
  {
    Map  secrets_map = download_manager.getDownloadState().getMapAttribute( DownloadManagerState.AT_SECRETS );
   
    if ( secrets_map == null ){
     
      secrets_map = new HashMap();
     
    }else{
     
        // clone as we can't just update the returned values
     
      secrets_map = new LightHashMap(secrets_map);
    }
   
    if ( secrets_map.size() == 0 ){
   
      secrets_map.put( "p1", torrent.getPieces()[0] );
       
      download_manager.getDownloadState().setMapAttribute( DownloadManagerState.AT_SECRETS, secrets_map );
    }
   
    return((byte[])secrets_map.get( "p1" ));
  }
 
  public boolean
  manualRoute(
    NetworkConnection connection )
  {
    return false;
  }
 
  public long
  getRandomSeed()
  {
    return( download_manager.getDownloadState().getLongParameter( DownloadManagerState.PARAM_RANDOM_SEED ));
  }
 
  public void
  addRateLimiter(
    LimitedRateGroup  group,
    boolean        upload )
  {
    PEPeerManager  pm;
   
    try{
      control_mon.enter();
     
      ArrayList  new_limiters = new ArrayList( external_rate_limiters_cow==null?1:external_rate_limiters_cow.size()+1);
     
      if ( external_rate_limiters_cow != null ){
       
        new_limiters.addAll( external_rate_limiters_cow );
      }
     
      new_limiters.add( new Object[]{ group, new Boolean( upload )});
     
      external_rate_limiters_cow = new_limiters;
     
      pm  = peer_manager;
     
    }finally{
     
      control_mon.exit();
   
   
    if ( pm != null ){
     
      pm.addRateLimiter(group, upload);
    }
  }
 
  public void
  removeRateLimiter(
    LimitedRateGroup  group,
    boolean        upload )
  {
    PEPeerManager  pm;
   
    try{
      control_mon.enter();
     
      if ( external_rate_limiters_cow != null ){
       
        ArrayList  new_limiters = new ArrayList( external_rate_limiters_cow.size()-1);
       
        for (int i=0;i<external_rate_limiters_cow.size();i++){
         
          Object[]  entry = (Object[])external_rate_limiters_cow.get(i);
         
          if ( entry[0] != group ){
           
            new_limiters.add( entry );
          }
        }
       
        if ( new_limiters.size() == 0 ){
         
          external_rate_limiters_cow = null;
         
        }else{
       
          external_rate_limiters_cow = new_limiters;
        }
      }
     
      pm  = peer_manager;
     
    }finally{
     
      control_mon.exit();
   
 
    if ( pm != null ){
     
      pm.removeRateLimiter(group, upload);
    }
  }
 
  public void
  enqueueReadRequest(
    PEPeer              peer,
    DiskManagerReadRequest       request,
    DiskManagerReadRequestListener   listener )
  {
    getDiskManager().enqueueReadRequest( request, listener );
  }
 
  public boolean
  activateRequest(
    InetSocketAddress  address )
  {
    if ( getState() == DownloadManager.STATE_QUEUED ){
     
      BloomFilter  bloom = activation_bloom;
     
      if ( bloom == null ){
       
        activation_bloom = bloom = BloomFilterFactory.createAddRemove4Bit( BLOOM_SIZE );
      }
     
      byte[]  address_bytes = address.getAddress().getAddress();
         
      int  hit_count = bloom.add( address_bytes );
     
      if ( hit_count > 5 ){
       
        Logger.log(
            new LogEvent(
              this,
              LogIDs.CORE,
              LogEvent.LT_WARNING,
              "Activate request for " + getDisplayName() + " from " + address + " denied as too many recently received" ));

        return( false );
      }
     
      Logger.log(new LogEvent(this, LogIDs.CORE, "Activate request for " + getDisplayName() + " from " + address ));

      long  now = SystemTime.getCurrentTime();

        // we don't really care about the bloom filter filling up and giving false positives
        // as activation events should be fairly rare
     
      if ( now < activation_bloom_create_time || now - activation_bloom_create_time > ACTIVATION_REBUILD_TIME ){
       
        activation_bloom = BloomFilterFactory.createAddRemove4Bit( BLOOM_SIZE );
       
        activation_bloom_create_time  = now;
      }
     
      activation_count = bloom.getEntryCount();
     
      activation_count_time = now;
     
      return( download_manager.activateRequest( activation_count ));
    }
   
    return( false );
  }
 
  public void
  deactivateRequest(
    InetSocketAddress  address )
  {
    BloomFilter  bloom = activation_bloom;
   
    if ( bloom != null ){
   
      byte[]  address_bytes = address.getAddress().getAddress();

      int  count = bloom.count( address_bytes);
     
      for (int i=0;i<count;i++){
       
        bloom.remove( address_bytes );
      }
     
      activation_count = bloom.getEntryCount();
    }
  }
 
  public int
  getActivationCount()
  {
      // in the absence of any new activations we persist the last count for the activation rebuild
      // period
   
    long  now = SystemTime.getCurrentTime();

    if ( now < activation_count_time ){
     
      activation_count_time = now;
     
    }else if ( now - activation_count_time > ACTIVATION_REBUILD_TIME ){
     
      activation_count = 0;
    }

    return( activation_count );
  }
 
  public PeerManagerRegistration
  getPeerManagerRegistration()
  {
    return( peer_manager_registration );
  }
 
    public boolean
    isForceStart()
    {
      return( force_start );
  }

  public void
  setForceStart(
    boolean _force_start)
  {
    try{
      state_mon.enter();
   
      if ( force_start != _force_start ){
         
        force_start = _force_start;
         
        int  state = getState();
       
        if force_start &&
            state == DownloadManager.STATE_STOPPED ||
              state == DownloadManager.STATE_QUEUED )) {
         
            // Start it!  (Which will cause a stateChanged to trigger)
         
          setState(DownloadManager.STATE_WAITING, false );           
        }
        }
    }finally{
     
      state_mon.exit();
    }
   
      // "state" includes the force-start setting
   
    download_manager.informStateChanged();
  }
 
  protected void
  setFailed(
    String    reason )
  {
    if ( reason != null ){
     
      errorDetail = reason;
    }
   
    stopIt( DownloadManager.STATE_ERROR, false, false );
  }

 
  public boolean
  filesExist(
    boolean  expected_to_be_allocated )
  {
    if ( !expected_to_be_allocated ){
     
      if ( !download_manager.isDataAlreadyAllocated()){
       
        return( false );
      }
    }
   
    DiskManager dm = getDiskManager();

    if (dm != null) {
      return dm.filesExist();
    }

    fileFacadeSet.makeSureFilesFacadeFilled(false);
   
    DiskManagerFileInfo[] files = fileFacadeSet.getFiles();

    for (int i = 0; i < files.length; i++) {
      DiskManagerFileInfo fileInfo = files[i];
      if (!fileInfo.isSkipped()) {
        File file = fileInfo.getFile(true);
        try {
          if (!file.exists()) {
           
            // For multi-file torrents, complain if the save directory is missing.
            if (!this.download_manager.getTorrent().isSimpleTorrent()) {
              File save_path = this.download_manager.getAbsoluteSaveLocation();
              if (FileUtil.isAncestorOf(save_path, file) && !save_path.exists()) {
                file = save_path; // We're going to abort very soon, so it's OK to overwrite this.
              }
            }
                           
            setFailed(MessageText.getString("DownloadManager.error.datamissing")
                + " " + file);
            return false;
           
          } else if (fileInfo.getLength() < file.length()) {
           
              // file may be incremental creation - don't complain if too small
           
              // don't bitch if the user is happy with this
           
            if ( !COConfigurationManager.getBooleanParameter("File.truncate.if.too.large")){
             
              setFailed(MessageText.getString("DownloadManager.error.badsize")
                  + " " + file + "(" + fileInfo.getLength() + "/" + file.length() + ")");
             
             
              return false;
            }
          }
        } catch (Exception e) {
          setFailed(e.getMessage());
          return false;
        }
      }
    }

    return true;
  }
 
  public DiskManagerFileInfoSet getDiskManagerFileInfoSet()
  {
    fileFacadeSet.makeSureFilesFacadeFilled(false);
   
    return fileFacadeSet;
  }
 
  /**
   * @deprecated use getDiskManagerFileInfoSet() instead
   */
     public DiskManagerFileInfo[]
    getDiskManagerFileInfo()
     {
       fileFacadeSet.makeSureFilesFacadeFilled(false);
     
       return( fileFacadeSet.getFiles() );
     }
 
  protected void
  fileInfoChanged()
  {
    fileFacadeSet.makeSureFilesFacadeFilled(true);
  }
 
  protected void
  filePrioritiesChanged(List files)
  {
    if (!cached_values_set) {
      fileFacadeSet.makeSureFilesFacadeFilled(false);
    }

    // no need to calculate completeness if there are no DND files and the
    // file being changed is not DND
    if (!cached_has_dnd_files && files.size() == 1 && !((DiskManagerFileInfo)files.get(0)).isSkipped()){
      return;
    }
    // if it's more than one file just do the scan anyway
    fileFacadeSet.makeSureFilesFacadeFilled(false);
    calculateCompleteness( fileFacadeSet.facadeFiles );
  }
 
  protected void
  calculateCompleteness(
    DiskManagerFileInfo[]  active )
  {
    boolean complete_excluding_dnd = true;

    boolean has_dnd_files = false;

    for (int i = 0; i < active.length; i++) {

      DiskManagerFileInfo file = active[i];

      if ( file.isSkipped()){

        has_dnd_files = true;
       
      }else if (file.getDownloaded() != file.getLength()) {

        complete_excluding_dnd = false;
       
      }
     
      if(has_dnd_files && !complete_excluding_dnd)
        break; // we can bail out early
    }

    cached_complete_excluding_dnd = complete_excluding_dnd;
    cached_has_dnd_files = has_dnd_files;
    cached_values_set = true;
    DownloadManagerState state = download_manager.getDownloadState();
    long flags = (cached_complete_excluding_dnd ? STATE_FLAG_COMPLETE_NO_DND : 0) |
                 (cached_has_dnd_files ? STATE_FLAG_HASDND : 0);
    state.setLongParameter(DownloadManagerState.PARAM_DND_FLAGS, flags);
  }
 
  /**
   * Determine if the download is complete, excluding DND files.  This
   * function is mostly cached when there is a DiskManager.
   *
   * @return completion state
   */
  protected boolean isDownloadComplete(boolean bIncludeDND) {
    if (!cached_values_set) {
      fileFacadeSet.makeSureFilesFacadeFilled(false);
    }

    // The calculate from stats doesn't take into consideration DND
    // So, if we have no DND files, use calculation from stats, which
    // remembers things like whether the file was once complete
    if (!cached_has_dnd_files) {
      return stats.getDownloadCompleted(false) == 1000;
    }

    // We have DND files.  If we have an existing diskmanager, then it
    // will have better information than the stats object.
    DiskManager dm = getDiskManager();

    //System.out.println(dm + ":" + (dm == null ? "null" : dm.getState() + ";" + dm.getRemainingExcludingDND()));
    if (dm != null) {

      int dm_state = dm.getState();

      if (dm_state == DiskManager.READY) {
        long remaining = bIncludeDND ? dm.getRemaining()
            : dm.getRemainingExcludingDND();
        return remaining == 0;
      }
    }

    // No DiskManager or it's in a bad state for us.
    // Assumed: We have DND files
    if (bIncludeDND) {
      // Want to include DND files in calculation, which there some, which
      // means completion MUST be false
      return false;
    }

    // Have DND files, bad DiskManager, and we don't want to include DND files
    return cached_complete_excluding_dnd;
  }
 
  protected PEPeerManager
  getPeerManager()
  {
    return( peer_manager );
  }
 
  protected DiskManager
  getDiskManager()
  {
    return( disk_manager_use_accessors );
  }
 
  protected String
  getErrorDetail()
  {
    return( errorDetail );
  }
 
   protected void
    setDiskManager(
      DiskManager      new_disk_manager,
      DiskManagerListener  new_disk_manager_listener )
    {
     if ( new_disk_manager != null ){
      
       new_disk_manager.setPieceCheckingEnabled( piece_checking_enabled );
     }
    
       try{
        disk_listeners_mon.enter();
       
        DiskManager  old_disk_manager = disk_manager_use_accessors;
       
          // remove any old listeners in case the diskmanager is still running async
       
        if ( old_disk_manager != null && disk_manager_listener_use_accessors != null ){
         
          old_disk_manager.removeListener( disk_manager_listener_use_accessors );
        }
       
        disk_manager_use_accessors      = new_disk_manager;
        disk_manager_listener_use_accessors  = new_disk_manager_listener;
       
      if ( new_disk_manager != null ){
        
         new_disk_manager.addListener( new_disk_manager_listener );
       }
       
          // whether going from none->active or the other way, indicate that the file info
          // has changed
       
        fileInfoChanged();
       
        if ( new_disk_manager == null && old_disk_manager != null ){
                   
          disk_listeners.dispatch( LDT_DL_REMOVED, old_disk_manager );
         
        }else if ( new_disk_manager != null && old_disk_manager == null ){
         
          disk_listeners.dispatch( LDT_DL_ADDED, new_disk_manager );
         
        }else{
       
          Debug.out( "inconsistent DiskManager state - " + new_disk_manager + "/" + old_disk_manager  );
        }
       
      }finally{
       
        disk_listeners_mon.exit();
     
    }
   
  public void
  addDiskListener(
    DownloadManagerDiskListener  listener )
  {
     try{
        disk_listeners_mon.enter();
       
        disk_listeners.addListener( listener );
       
        DiskManager  dm = getDiskManager();
       
      if ( dm != null ){
   
        disk_listeners.dispatch( listener, LDT_DL_ADDED, dm );
      }
      }finally{
       
        disk_listeners_mon.exit();
      }   
  }
   
  public void
  removeDiskListener(
    DownloadManagerDiskListener  listener )
  {
     try{
        disk_listeners_mon.enter();

        disk_listeners.removeListener( listener );
       
     }finally{
       
        disk_listeners_mon.exit();
      }
  }
 
  public long getDiskListenerCount() {
    return disk_listeners.size();
  }
 
  public String
  getDisplayName()
  {
    return( download_manager.getDisplayName());
  }
 
  public int
  getUploadRateLimitBytesPerSecond()
  {
    return( download_manager.getEffectiveUploadRateLimitBytesPerSecond());
  }
 
  public int
  getDownloadRateLimitBytesPerSecond()
  {
    return( stats.getDownloadRateLimitBytesPerSecond());
  }
 
    // these per-download rates are not easy to implement as we either have per-peer limits or global limits, with the download-limits being implemented
    // by adding them to all peers as peer-limits. So for the moment we stick with global (non-lan) limits
 
  public int
  getPermittedBytesToReceive()
  {
    return( NetworkManager.getSingleton().getRateHandler( false, false ).getCurrentNumBytesAllowed());
  }
 
  public void
  permittedReceiveBytesUsed(
    int bytes )
  {
    NetworkManager.getSingleton().getRateHandler( false, false ).bytesProcessed( bytes );
  }
 
  public int
  getPermittedBytesToSend()
  {
    return( NetworkManager.getSingleton().getRateHandler( true, false ).getCurrentNumBytesAllowed());
  }
 
  public void 
  permittedSendBytesUsed
    int bytes )
  {
    NetworkManager.getSingleton().getRateHandler( true, false ).bytesProcessed( bytes );
  }
 
  public int
  getMaxUploads()
  {
    return( download_manager.getEffectiveMaxUploads());
  }
 
  public int
  getMaxConnections()
  {
    int  result;
   
    if ( download_manager.isMaxConnectionsWhenSeedingEnabled() && isStateSeeding()){
     
      result = download_manager.getMaxConnectionsWhenSeeding();
     
    }else{
     
      result = download_manager.getMaxConnections();
    }
   
    return( result );
  }
 
  public int
  getMaxSeedConnections()
  {
    return( download_manager.getMaxSeedConnections());
  }
 
  public boolean
  isExtendedMessagingEnabled()
  {
    return( download_manager.isExtendedMessagingEnabled());
  }
 
  public boolean
  isPeerExchangeEnabled()
  {
    return( download_manager.getDownloadState().isPeerSourceEnabled( PEPeerSource.PS_OTHER_PEER ));
  }
 
  public int
  getCryptoLevel()
  {
    return( download_manager.getCryptoLevel());
  }
 
  public boolean
  isPeriodicRescanEnabled()
  {
    return( download_manager.getDownloadState().getFlag( DownloadManagerState.FLAG_SCAN_INCOMPLETE_PIECES ));
  }
 
  public TRTrackerScraperResponse
  getTrackerScrapeResponse()
  {
    return( download_manager.getTrackerScrapeResponse());
  }
 
  public String
  getTrackerClientExtensions()
  {
    return( download_manager.getDownloadState().getTrackerClientExtensions());
  }
 
  public void
  setTrackerRefreshDelayOverrides(
    int  percent )
  {
    download_manager.setTrackerRefreshDelayOverrides( percent );
  }
 
  public boolean
  isNATHealthy()
  {
    return( download_manager.getNATStatus() == ConnectionManager.NAT_OK );
  }
 
  public void
  addPeer(
    PEPeer  peer )
  {
    download_manager.addPeer( peer );
  }
 
  public void
  removePeer(
    PEPeer  peer )
  { 
    download_manager.removePeer( peer );
  }
 
  public void
  addPiece(
    PEPiece  piece )
  {
    download_manager.addPiece( piece );
  }
 
  public void
  removePiece(
    PEPiece  piece )
  {
    download_manager.removePiece( piece );
  }
 
  public void
  discarded(
    PEPeer    peer,
    int      bytes )
  {
    if ( global_stats != null ){
     
      global_stats.discarded( bytes );
    }
  }
 
  public void
  protocolBytesReceived(
    PEPeer    peer,
    int      bytes )
  {
    if ( global_stats != null ){
     
      global_stats.protocolBytesReceived( bytes, peer.isLANLocal());
    }
  }
 
  public void
  dataBytesReceived(
    PEPeer    peer,
    int      bytes )
  {
    if ( global_stats != null ){
     
      global_stats.dataBytesReceived( bytes, peer.isLANLocal());
    }
  }
 
  public void
  protocolBytesSent(
    PEPeer    peer,
    int      bytes )
  {
    if ( global_stats != null ){
     
      global_stats.protocolBytesSent( bytes, peer.isLANLocal());
    }
  }
 
  public void
  dataBytesSent(
    PEPeer    peer,
    int      bytes )
  {
    if ( global_stats != null ){
     
      global_stats.dataBytesSent( bytes, peer.isLANLocal());
    }
  }
 
  public int getPosition() {
    return download_manager.getPosition();
  }
 
  public void
  statsRequest(
    PEPeer     originator,
    Map     request,
    Map      reply )
  { 
    GlobalManager  gm = download_manager.getGlobalManager();

    gm.statsRequest( request, reply );
   
    Map  info = new HashMap();
   
    reply.put( "dl", info );
   
    try{
      info.put( "u_lim", new Long( getUploadRateLimitBytesPerSecond()));
      info.put( "d_lim", new Long( getDownloadRateLimitBytesPerSecond()));
     
      info.put( "u_rate", new Long( stats.getProtocolSendRate() + stats.getDataSendRate()));
      info.put( "d_rate", new Long( stats.getProtocolReceiveRate() + stats.getDataReceiveRate()));
     
      info.put( "u_slot", new Long( getMaxUploads()));
      info.put( "c_max", new Long( getMaxConnections()));
     
      info.put( "c_leech", new Long( download_manager.getNbPeers()));
      info.put( "c_seed", new Long( download_manager.getNbSeeds()));
     
      PEPeerManager pm = peer_manager;
     
      if ( pm != null ){
       
        info.put( "c_rem", pm.getNbRemoteTCPConnections());
       
        List<PEPeer> peers = pm.getPeers();
       
        List<Long>  slot_up = new ArrayList<Long>();
       
        info.put( "slot_up", slot_up );
       
        for ( PEPeer p: peers ){
         
          if ( !p.isChokedByMe()){
           
            long up = p.getStats().getDataSendRate() + p.getStats().getProtocolSendRate();
           
            slot_up.add( up );
          }
        }
      }
    }catch( Throwable e ){
    }
  }
 
  public void
  addHTTPSeed(
    String  address,
    int    port )
  {
    ExternalSeedPlugin  plugin = getExternalSeedPlugin();

    try{
      if ( plugin != null ){
       
        Map config = new HashMap();
 
        List urls = new ArrayList();
       
        String  seed_url = "http://" + UrlUtils.convertIPV6Host(address) + ":" + port + "/webseed";
       
        urls.add( seed_url.getBytes());
 
        config.put("httpseeds", urls);
 
        Map params = new HashMap();
 
        params.put( "supports_503", new Long(0));
        params.put( "transient", new Long(1));
 
        config.put("httpseeds-params", params);
 
        List<ExternalSeedPeer> new_seeds = plugin.addSeed( org.gudy.azureus2.pluginsimpl.local.download.DownloadManagerImpl.getDownloadStatic( download_manager ), config);
       
        if ( new_seeds.size() > 0 ){
         
          List<ExternalSeedPeer> to_remove = new ArrayList<ExternalSeedPeer>();
         
          synchronized( http_seeds ){
           
            http_seeds.addAll( new_seeds );
           
            while( http_seeds.size() > HTTP_SEEDS_MAX ){
             
              ExternalSeedPeer x = http_seeds.removeFirst();
             
              to_remove.add( x );
            }
          }
         
          for (ExternalSeedPeer peer: to_remove ){
           
            peer.remove();
          }
        }
      }
    }catch( Throwable e ){
     
      Debug.printStackTrace(e);
    }
  }
 
  public void
  priorityConnectionChanged(
    boolean  added )
  {
    synchronized( this ){
     
      if ( added ){
       
        priority_connection_count++;
       
      }else{
       
        priority_connection_count--;
      }
    }
  }
 
  public boolean
  hasPriorityConnection()
  {
    synchronized( this ){
     
      return( priority_connection_count > 0 );
    }
  }
 
  public String
  getDescription()
  {
    return( download_manager.getDisplayName());
  }
 
  public LogRelation
  getLogRelation()
  {
    return( this );
  }
 
  public String
  getRelationText()
  {
    return( download_manager.getRelationText());
  }

  public Object[]
  getQueryableInterfaces()
  {
    List  interfaces = new ArrayList();
   
    Object[]  intf = download_manager.getQueryableInterfaces();
   
    for (int i=0;i<intf.length;i++){
     
      interfaces.add( intf[i] );
    }
   
    interfaces.add( download_manager );
   
    DiskManager  dm = getDiskManager();
   
    if ( dm != null ){
     
      interfaces.add( dm );
    }
   
    return( interfaces.toArray());
  }
 
  protected class FileInfoFacadeSet implements DiskManagerFileInfoSet {

    DiskManagerFileInfoSet delegate;
    fileInfoFacade[] facadeFiles = new fileInfoFacade[0]// default before torrent avail

    public DiskManagerFileInfo[] getFiles() {
      return facadeFiles;
    }

    public int nbFiles() {
      if (delegate == null) {
        return 0;
      }
      return delegate.nbFiles();
    }

    public void setPriority(int[] toChange) {
      delegate.setPriority(toChange);
    }

    public void setSkipped(boolean[] toChange, boolean setSkipped) {
      delegate.setSkipped(toChange, setSkipped);
    }

    public boolean[] setStorageTypes(boolean[] toChange, int newStroageType) {
      return delegate.setStorageTypes(toChange, newStroageType);
    }

    /** XXX Don't call me, call makeSureFilesFacadeFilled() */
    protected void fixupFileInfo(fileInfoFacade[] info) {

      // too early in initialisation sequence to action this - it'll get reinvoked later anyway
      if (info.length == 0) return;
     
      final List delayed_prio_changes = new ArrayList(0);
     
      try {
        facade_mon.enter();
        if (files_facade_destroyedreturn;

        DiskManager dm = DownloadManagerController.this.getDiskManager();
        DiskManagerFileInfoSet active = null;
       
        if (dm != null) {
          int dm_state = dm.getState();
          // grab the live file info if available
          if (dm_state == DiskManager.CHECKING || dm_state == DiskManager.READY)
            active = dm.getFileSet();

        }
        if (active == null) {
          final boolean[] initialising = { true };
          // chance of recursion with this listener as the file-priority-changed is triggered
          // synchronously during construction and this can cause a listener to reenter the
          // incomplete fixup logic here + instantiate new skeletons.....
          try {
            skeleton_builds++;
            if (skeleton_builds % 1000 == 0) {
              Debug.outNoStack("Skeleton builds: " + skeleton_builds);
            }
            active = DiskManagerFactory.getFileInfoSkeleton(download_manager, new DiskManagerListener() {
              public void stateChanged(int oldState, int newState) {}

              public void filePriorityChanged(DiskManagerFileInfo file) {
                if (initialising[0]) {
                  delayed_prio_changes.add(file);
                } else {
                  download_manager.informPriorityChange(file);
                }
              }

              public void pieceDoneChanged(DiskManagerPiece piece) {}

              public void fileAccessModeChanged(DiskManagerFileInfo file, int old_mode, int new_mode) {}
            });
          } finally {
            initialising[0] = false;
          }
          calculateCompleteness(active.getFiles());
        }
       
        DiskManagerFileInfo[] activeFiles = active.getFiles();
       
        for (int i = 0; i < info.length; i++)
          info[i].setDelegate(activeFiles[i]);
       
        delegate = active;
       
      } finally {
        facade_mon.exit();
      }
     
      fileFacadeSet.facadeFiles = info;
      download_manager.informPrioritiesChange(delayed_prio_changes);

      delayed_prio_changes.clear();
    }
   
    private void makeSureFilesFacadeFilled(boolean refresh) {
      if (!bInitialized) return; // too early

      if (facadeFiles.length == 0) {
        fileInfoFacade[] newFacadeFiles = new fileInfoFacade[download_manager.getTorrent() == null
            ? 0 : download_manager.getTorrent().getFiles().length];

        for (int i = 0; i < newFacadeFiles.length; i++)
          newFacadeFiles[i] = new fileInfoFacade();
       
        // no need to set facadeFiles, it gets set to newFacadeFiles in fixup
        fileFacadeSet.fixupFileInfo(newFacadeFiles);
      } else if (refresh) {
        fixupFileInfo(facadeFiles);
      }
    }
   
   
    protected void destroyFileInfo() {
      try {
        facade_mon.enter();
        if (fileFacadeSet == null || files_facade_destroyed)
          return;

        files_facade_destroyed = true;

        for (int i = 0; i < facadeFiles.length; i++)
          facadeFiles[i].close();
      } finally {
        facade_mon.exit();
      }
    }
  }
 
  protected class
  fileInfoFacade
    implements DiskManagerFileInfo
  {
    private DiskManagerFileInfo    delegate;
   
    private List          listeners;
   
    protected
    fileInfoFacade()
    {
    }
   
    protected void
    setDelegate(
      DiskManagerFileInfo    _delegate )
    {
      if ( _delegate == delegate ){
       
        return;
      }
     
      if ( delegate != null ){
       
        delegate.close();
      }

       delegate = _delegate;
        
         // transfer any existing listeners across
      
         if ( listeners != null ){
          
           for (int i=0;i<listeners.size();i++){
            
             delegate.addListener((DiskManagerFileInfoListener)listeners.get(i));
           }
         }
    }

    public void
    setPriority(
      int b )
    {
      delegate.setPriority(b);
    }
   
    public void
    setSkipped(
      boolean b)
    {
      delegate.setSkipped(b);
    }
    
   
    public boolean
    setLink(
      File  link_destination )
    {
      return( delegate.setLink( link_destination ));
    }

    public boolean
    setLinkAtomic(
      File  link_destination )
    {
      return( delegate.setLinkAtomic( link_destination ));
    }
   
    public File
    getLink()
    {
      return( delegate.getLink());
    }
   
    public boolean
    setStorageType(
      int    type )
    {
      return( delegate.setStorageType( type ));
    }
   
    public int
    getStorageType()
    {
      return( delegate.getStorageType());
    }
   
      
    public int
    getAccessMode()
    {
      return( delegate.getAccessMode());
    }
   
    public long
    getDownloaded()
    {
      return( delegate.getDownloaded());
    }
   
    public String
    getExtension()
    {
      return( delegate.getExtension());
    }
     
    public int
    getFirstPieceNumber()
    {
      return( delegate.getFirstPieceNumber());
    }
   
    public int
    getLastPieceNumber()
    {
      return( delegate.getLastPieceNumber());
    }
   
    public long
    getLength()
    {
      return( delegate.getLength());
    }
     
    public int
    getNbPieces()
    {
      return( delegate.getNbPieces());
    }
       
    public int
    getPriority()
    {
      return( delegate.getPriority());
    }
   
    public boolean
    isSkipped()
    {
      return( delegate.isSkipped());
    }
   
    public int 
    getIndex()
    {
      return( delegate.getIndex());
    }
   
    public DiskManager
    getDiskManager()
    {
      return( delegate.getDiskManager());
    }
   
    public DownloadManager 
    getDownloadManager()
    {
      return( download_manager );
    }
   
    public File
    getFile( boolean follow_link )
    {
      return( delegate.getFile( follow_link ));
    }
   
    public TOTorrentFile
    getTorrentFile()
    {
      return( delegate.getTorrentFile());
    }
   
    public void
    flushCache()
   
      throws  Exception
    {
      try{
        facade_mon.enter();
       
        delegate.flushCache();
       
      }finally{
       
        facade_mon.exit();
      }
    }
   
    public DirectByteBuffer
    read(
      long  offset,
      int    length )
   
      throws IOException
    {
      try{
        facade_mon.enter();
     
        return( delegate.read( offset, length ));
       
      }finally{
       
        facade_mon.exit();
      }
    }
   
    public void
    close()
    {
      try{
        facade_mon.enter();

        delegate.close();
       
      }finally{
       
        facade_mon.exit();
      }
    }
   
    public void
    addListener(
      DiskManagerFileInfoListener  listener )
    {
      if ( listeners == null ){
       
        listeners = new ArrayList();
      }
     
      listeners.add( listener );
     
      delegate.addListener( listener );
    }
   
    public void
    removeListener(
      DiskManagerFileInfoListener  listener )
    {
      listeners.remove( listener );
     
      delegate.removeListener( listener );
    }
  }
 
  public void generateEvidence(IndentWriter writer) {
    writer.println("DownloadManager Controller:");
   
    writer.indent();
    try {
      writer.println("cached info: complete w/o DND="
          + cached_complete_excluding_dnd + "; hasDND? " + cached_has_dnd_files);

      writer.println("Complete w/DND? " + isDownloadComplete(true)
          + "; w/o DND? " + isDownloadComplete(false));

      writer.println("filesFacade length: " + fileFacadeSet.nbFiles());

      if (force_start) {
        writer.println("Force Start");
      }

      writer.println("FilesExist? " + filesExist(download_manager.isDataAlreadyAllocated()));
     
    } finally {
      writer.exdent();
    }
  }
 
  public class forceRecheckDiskManagerListener
    implements DiskManagerListener
  {
    private final boolean wasForceStarted;

    private final int start_state;

    private final ForceRecheckListener l;

    public forceRecheckDiskManagerListener(boolean wasForceStarted,
        int start_state, ForceRecheckListener l) {
      this.wasForceStarted = wasForceStarted;
      this.start_state = start_state;
      this.l = l;
    }

    public void stateChanged(int oldDMState, int newDMState) {
      try {
        control_mon.enter();

        if (getDiskManager() == null) {

          // already closed down via stop

          download_manager.setAssumedComplete(false);

          if (l != null) {
            l.forceRecheckComplete(download_manager);
          }

          return;
        }
      } finally {

        control_mon.exit();
      }

     
      if (newDMState == DiskManager.CHECKING) {

        fileFacadeSet.makeSureFilesFacadeFilled(true);
      }

      if (newDMState == DiskManager.READY || newDMState == DiskManager.FAULTY) {

        force_start = wasForceStarted;

        stats.setDownloadCompleted(stats.getDownloadCompleted(true));

        if (newDMState == DiskManager.READY) {

          try {
            boolean only_seeding = false;
            boolean update_only_seeding = false;

            try {
              control_mon.enter();

              DiskManager dm = getDiskManager();

              if (dm != null) {

                dm.stop(false);

                only_seeding = dm.getRemainingExcludingDND() == 0;

                update_only_seeding = true;

                setDiskManager(null, null);

                if (start_state == DownloadManager.STATE_ERROR) {

                  setState(DownloadManager.STATE_STOPPED, false);

                } else {

                  setState(start_state, false);
                }
              }
            } finally {

              control_mon.exit();

              download_manager.informStateChanged();
            }

            // careful here, don't want to update seeding while holding monitor
            // as potential deadlock

            if (update_only_seeding) {

              download_manager.setAssumedComplete(only_seeding);
            }

          } catch (Exception e) {

            setFailed("Resume data save fails: "
                + Debug.getNestedExceptionMessage(e));
          }
        } else { // Faulty

          try {
            control_mon.enter();

            DiskManager dm = getDiskManager();

            if (dm != null) {

              dm.stop(false);

              setDiskManager(null, null);

              setFailed(dm.getErrorMessage());
            }
          } finally {

            control_mon.exit();
          }

          download_manager.setAssumedComplete(false);

        }
        if (l != null) {
          l.forceRecheckComplete(download_manager);
        }
      }
    }

    public void filePriorityChanged(DiskManagerFileInfo file) {
      download_manager.informPriorityChange(file);
    }

    public void pieceDoneChanged(DiskManagerPiece piece) {
    }

    public void fileAccessModeChanged(DiskManagerFileInfo file, int old_mode,
        int new_mode) {
    }
  }
}
TOP

Related Classes of org.gudy.azureus2.core3.download.impl.DownloadManagerController

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.