Package org.gudy.azureus2.core3.peer.impl.control

Source Code of org.gudy.azureus2.core3.peer.impl.control.PEPeerControlImpl

/*
* Created by Olivier Chalouhi
* Modified Apr 13, 2004 by Alon Rohter
* Heavily modified Sep 2005 by Joseph Bridgewater
* 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.peer.impl.control;


import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.UnknownHostException;
import java.nio.ByteBuffer;
import java.util.*;

import org.gudy.azureus2.core3.config.COConfigurationManager;
import org.gudy.azureus2.core3.config.ParameterListener;
import org.gudy.azureus2.core3.disk.*;
import org.gudy.azureus2.core3.internat.MessageText;
import org.gudy.azureus2.core3.ipfilter.*;
import org.gudy.azureus2.core3.logging.*;
import org.gudy.azureus2.core3.peer.*;
import org.gudy.azureus2.core3.peer.impl.*;
import org.gudy.azureus2.core3.peer.util.PeerIdentityDataID;
import org.gudy.azureus2.core3.peer.util.PeerIdentityManager;
import org.gudy.azureus2.core3.peer.util.PeerUtils;
import org.gudy.azureus2.core3.torrent.TOTorrentException;
import org.gudy.azureus2.core3.tracker.client.TRTrackerAnnouncer;
import org.gudy.azureus2.core3.tracker.client.TRTrackerAnnouncerResponse;
import org.gudy.azureus2.core3.tracker.client.TRTrackerAnnouncerResponsePeer;
import org.gudy.azureus2.core3.tracker.client.TRTrackerScraperResponse;
import org.gudy.azureus2.core3.util.*;
import org.gudy.azureus2.plugins.download.DownloadAnnounceResultPeer;
import org.gudy.azureus2.plugins.network.Connection;
import org.gudy.azureus2.plugins.network.OutgoingMessageQueue;
import org.gudy.azureus2.plugins.peers.Peer;
import org.gudy.azureus2.plugins.peers.PeerDescriptor;

import com.aelitis.azureus.core.networkmanager.LimitedRateGroup;
import com.aelitis.azureus.core.networkmanager.admin.NetworkAdmin;
import com.aelitis.azureus.core.networkmanager.impl.tcp.TCPConnectionManager;
import com.aelitis.azureus.core.networkmanager.impl.tcp.TCPNetworkManager;
import com.aelitis.azureus.core.networkmanager.impl.udp.UDPNetworkManager;
import com.aelitis.azureus.core.peermanager.control.PeerControlInstance;
import com.aelitis.azureus.core.peermanager.control.PeerControlScheduler;
import com.aelitis.azureus.core.peermanager.control.PeerControlSchedulerFactory;
import com.aelitis.azureus.core.peermanager.nat.PeerNATInitiator;
import com.aelitis.azureus.core.peermanager.nat.PeerNATTraversalAdapter;
import com.aelitis.azureus.core.peermanager.nat.PeerNATTraverser;
import com.aelitis.azureus.core.peermanager.peerdb.*;
import com.aelitis.azureus.core.peermanager.piecepicker.PiecePicker;
import com.aelitis.azureus.core.peermanager.piecepicker.PiecePickerFactory;
import com.aelitis.azureus.core.peermanager.unchoker.Unchoker;
import com.aelitis.azureus.core.peermanager.unchoker.UnchokerFactory;
import com.aelitis.azureus.core.peermanager.unchoker.UnchokerUtil;
import com.aelitis.azureus.core.peermanager.uploadslots.UploadHelper;
import com.aelitis.azureus.core.peermanager.uploadslots.UploadSlotManager;
import com.aelitis.azureus.core.tracker.TrackerPeerSource;
import com.aelitis.azureus.core.tracker.TrackerPeerSourceAdapter;
import com.aelitis.azureus.core.util.FeatureAvailability;
import com.aelitis.azureus.core.util.bloom.BloomFilter;
import com.aelitis.azureus.core.util.bloom.BloomFilterFactory;

/**
* manages all peer transports for a torrent
*
* @author MjrTom
*      2005/Oct/08: Numerous changes for new piece-picking. Also
*            a few optimizations and multi-thread cleanups
*      2006/Jan/02: refactoring piece picking related code
*/

@SuppressWarnings("serial")
public class
PEPeerControlImpl
extends LogRelation
implements   PEPeerControl, ParameterListener, DiskManagerWriteRequestListener, PeerControlInstance, PeerNATInitiator,
DiskManagerCheckRequestListener, IPFilterListener
{
  private static final LogIDs LOGID = LogIDs.PEER;

  private static final boolean TEST_PERIODIC_SEEDING_SCAN_FAIL_HANDLING  = false;
 
  static{
    if ( TEST_PERIODIC_SEEDING_SCAN_FAIL_HANDLING ){
      Debug.out( "**** test periodic scan failure enabled ****" );
    }
  }
 
  private static final int WARNINGS_LIMIT = 2;

  private static final int  CHECK_REASON_DOWNLOADED    = 1;
  private static final int  CHECK_REASON_COMPLETE    = 2;
  private static final int  CHECK_REASON_SCAN      = 3;
  private static final int  CHECK_REASON_SEEDING_CHECK    = 4;
  private static final int  CHECK_REASON_BAD_PIECE_CHECK  = 5;

  private static final int  SEED_CHECK_WAIT_MARKER  = 65526;

    // config
 
  private static boolean   disconnect_seeds_when_seeding;
  private static boolean   enable_seeding_piece_rechecks;
  private static int    stalled_piece_timeout;
  private static boolean   fast_unchoke_new_peers;
    private static float  ban_peer_discard_ratio;
    private static int    ban_peer_discard_min_kb;
    private static boolean  udp_fallback_for_failed_connection;
    private static boolean  udp_fallback_for_dropped_connection; 
    private static boolean  udp_probe_enabled;
    private static boolean  hide_a_piece;
    private static boolean  prefer_udp_default;
   
  static{
   
    COConfigurationManager.addAndFireParameterListeners(
      new String[]{
        "Disconnect Seed",
        "Seeding Piece Check Recheck Enable",
        "peercontrol.stalled.piece.write.timeout",
        "Peer.Fast.Initial.Unchoke.Enabled",
           "Ip Filter Ban Discard Ratio",
           "Ip Filter Ban Discard Min KB",
           "peercontrol.udp.fallback.connect.fail",
           "peercontrol.udp.fallback.connect.drop",
           "peercontrol.udp.probe.enable",
           "peercontrol.hide.piece",
           "peercontrol.prefer.udp",
      },
      new ParameterListener()
      {
        public void
        parameterChanged(
          String name )
        {
          disconnect_seeds_when_seeding     = COConfigurationManager.getBooleanParameter("Disconnect Seed");
          enable_seeding_piece_rechecks     = COConfigurationManager.getBooleanParameter("Seeding Piece Check Recheck Enable");
          stalled_piece_timeout        = COConfigurationManager.getIntParameter( "peercontrol.stalled.piece.write.timeout", 60*1000 );
          fast_unchoke_new_peers         = COConfigurationManager.getBooleanParameter( "Peer.Fast.Initial.Unchoke.Enabled" );
          ban_peer_discard_ratio        = COConfigurationManager.getFloatParameter( "Ip Filter Ban Discard Ratio" );
          ban_peer_discard_min_kb        = COConfigurationManager.getIntParameter( "Ip Filter Ban Discard Min KB" );
          udp_fallback_for_failed_connection  = COConfigurationManager.getBooleanParameter( "peercontrol.udp.fallback.connect.fail" );
          udp_fallback_for_dropped_connection  = COConfigurationManager.getBooleanParameter( "peercontrol.udp.fallback.connect.drop" );       
          udp_probe_enabled          = COConfigurationManager.getBooleanParameter( "peercontrol.udp.probe.enable" );       
          hide_a_piece            = COConfigurationManager.getBooleanParameter( "peercontrol.hide.piece" );
         
          if ( hide_a_piece ){
           
            disconnect_seeds_when_seeding = false;
          }
         
          prefer_udp_default            = COConfigurationManager.getBooleanParameter( "peercontrol.prefer.udp" );
        }
      });
  }
 
  private static IpFilter ip_filter = IpFilterManagerFactory.getSingleton().getIPFilter();

  private volatile boolean  is_running     = false
  private volatile boolean  is_destroyed   = false

  private volatile ArrayList<PEPeer>  peer_transports_cow = new ArrayList<PEPeer>()// Copy on write!
  private final AEMonitor     peer_transports_mon  = new AEMonitor( "PEPeerControl:PT");

  protected final PEPeerManagerAdapter  adapter;
  private final DiskManager           disk_mgr;
  private final DiskManagerPiece[]    dm_pieces;

  private PEPeerManager.StatsReceiver  stats_receiver;
 
  private final PiecePicker  piecePicker;
  private long        lastNeededUndonePieceChange;

  /** literally seeding as in 100% torrent complete */
  private boolean       seeding_mode; 
  private boolean        restart_initiated;

  private final int       _nbPieces;     //how many pieces in the torrent
  private PEPieceImpl[]  pePieces;      //pieces that are currently in progress
  private int        nbPiecesActive;  // how many pieces are currently in progress

  private int        nbPeersSnubbed;

  private PeerIdentityDataID  _hash;
  private final byte[]        _myPeerId;
  private PEPeerManagerStats        _stats;
  //private final TRTrackerAnnouncer _tracker;
  //  private int _maxUploads;
  private int    _seeds, _peers,_remotesTCPNoLan, _remotesUDPNoLan;
  private int   _tcpPendingConnections, _tcpConnectingConnections;
  private long last_remote_time;
  private long  _timeStarted;
  private long  _timeStartedSeeding = -1;
  private long  _timeFinished;
  private Average  _averageReceptionSpeed;

  private long mainloop_loop_count;

  private static final int MAINLOOP_ONE_SECOND_INTERVAL = 1000 / PeerControlScheduler.SCHEDULE_PERIOD_MILLIS;
  private static final int MAINLOOP_FIVE_SECOND_INTERVAL = MAINLOOP_ONE_SECOND_INTERVAL * 5;
  private static final int MAINLOOP_TEN_SECOND_INTERVAL = MAINLOOP_ONE_SECOND_INTERVAL * 10;
  private static final int MAINLOOP_THIRTY_SECOND_INTERVAL = MAINLOOP_ONE_SECOND_INTERVAL * 30;
  private static final int MAINLOOP_SIXTY_SECOND_INTERVAL = MAINLOOP_ONE_SECOND_INTERVAL * 60;
  private static final int MAINLOOP_TEN_MINUTE_INTERVAL = MAINLOOP_SIXTY_SECOND_INTERVAL * 10;


  private volatile ArrayList<PEPeerManagerListener> peer_manager_listeners_cow = new ArrayList<PEPeerManagerListener>()//copy on write


  private final List<Object[]>  piece_check_result_list       = new ArrayList<Object[]>();
  private final AEMonitor  piece_check_result_list_mon    = new AEMonitor( "PEPeerControl:PCRL");

  private boolean       superSeedMode;
  private int         superSeedModeCurrentPiece;
  private int         superSeedModeNumberOfAnnounces;
  private SuperSeedPiece[]  superSeedPieces;

  private final int      hidden_piece;
 
  private final AEMonitor     this_mon = new AEMonitor( "PEPeerControl");

  private long    ip_filter_last_update_time;

  private Map<Object,Object>      user_data;

  private Unchoker    unchoker;

  private List<Object[]>  external_rate_limiters_cow;
 
  private int  bytes_queued_for_upload;
  private int  connections_with_queued_data;
  private int  connections_with_queued_data_blocked;
  private int  connections_unchoked;
 

  private List<PEPeer> sweepList = Collections.emptyList();
  private int nextPEXSweepIndex = 0;


  private final UploadHelper upload_helper = new UploadHelper() {   
    public int getPriority() {     
      return UploadHelper.PRIORITY_NORMAL;  //TODO also must call UploadSlotManager.getSingleton().updateHelper( upload_helper ); on priority change
    }

    public ArrayList<PEPeer> getAllPeers() {
      return( peer_transports_cow );
    }   

    public boolean isSeeding() {
      return seeding_mode;
    }   
  };



  private PeerDatabase  peer_database = PeerDatabaseFactory.createPeerDatabase();

  private int        bad_piece_reported    = -1;
 
  private int        next_rescan_piece    = -1;
  private long      rescan_piece_time    = -1;

  private long      last_eta;
  private long      last_eta_calculation;

  private static final int MAX_UDP_CONNECTIONS    = 16;

  private static final int PENDING_NAT_TRAVERSAL_MAX  = 32;
  private static final int MAX_UDP_TRAVERSAL_COUNT  = 3;
 
  private static final String  PEER_NAT_TRAVERSE_DONE_KEY  = PEPeerControlImpl.class.getName() + "::nat_trav_done";
 
  private Map<String,PEPeerTransport>  pending_nat_traversals =
    new LinkedHashMap<String,PEPeerTransport>(PENDING_NAT_TRAVERSAL_MAX,0.75f,true)
  {
    protected boolean
    removeEldestEntry(
      Map.Entry<String,PEPeerTransport> eldest)
    {
      return size() > PENDING_NAT_TRAVERSAL_MAX;
    }
  }

  private int udp_traversal_count;

  private static final int UDP_RECONNECT_MAX      = 16;
 
  private Map<String,PEPeerTransport>  udp_reconnects =
    new LinkedHashMap<String,PEPeerTransport>(UDP_RECONNECT_MAX,0.75f,true)
  {
    protected boolean
    removeEldestEntry(
      Map.Entry<String,PEPeerTransport> eldest)
    {
      return size() > UDP_RECONNECT_MAX;
    }
  }

  private static final int UDP_RECONNECT_MIN_MILLIS  = 10*1000;
  private long  last_udp_reconnect;
 
  private boolean  prefer_udp;

  private static final int    PREFER_UDP_BLOOM_SIZE  = 10000;
  private volatile BloomFilter  prefer_udp_bloom;
 
  private final LimitedRateGroup upload_limited_rate_group = new LimitedRateGroup() {
    public String
    getName()
    {
      return( "per_dl_up: " + getDisplayName());
    }
    public int getRateLimitBytesPerSecond() {
      return adapter.getUploadRateLimitBytesPerSecond();
    }
  };

  private final LimitedRateGroup download_limited_rate_group = new LimitedRateGroup() {
    public String
    getName()
    {
      return( "per_dl_down: " + getDisplayName());
    }
    public int getRateLimitBytesPerSecond() {
      return adapter.getDownloadRateLimitBytesPerSecond();
    }
  };

  private final int  partition_id;
 
  public
  PEPeerControlImpl(
    byte[]          _peer_id,
    PEPeerManagerAdapter   _adapter,
    DiskManager       _diskManager,
    int            _partition_id )
  {
    _myPeerId    = _peer_id;
    adapter     = _adapter;
    disk_mgr     = _diskManager;
    partition_id  = _partition_id;
   
    _nbPieces =disk_mgr.getNbPieces();
    dm_pieces =disk_mgr.getPieces();
   
    pePieces = new PEPieceImpl[_nbPieces];

    hidden_piece = hide_a_piece?((int)(Math.abs(adapter.getRandomSeed())%_nbPieces)):-1;

    if ( hidden_piece >= 0 ){
   
      System.out.println( "Hidden piece for " + getDisplayName() + " = " + hidden_piece );
    }
   
    piecePicker = PiecePickerFactory.create( this );

    COConfigurationManager.addParameterListener("Ip Filter Enabled", this);

    ip_filter.addListener( this );

  }


  public void
  start()
  {
    //This torrent Hash
    try
    {

      _hash = PeerIdentityManager.createDataID( disk_mgr.getTorrent().getHash());

    } catch (TOTorrentException e)
    {

      // this should never happen
      Debug.printStackTrace( e );

      _hash = PeerIdentityManager.createDataID( new byte[20] );
    }

    // the recovered active pieces
    for (int i =0; i <_nbPieces; i++ )
    {
      final DiskManagerPiece dmPiece =dm_pieces[i];
      if (!dmPiece.isDone() &&dmPiece.getNbWritten() >0)
      {
        addPiece(new PEPieceImpl(this, dmPiece, 0), i, true, null );
      }
    }

    //The peer connections
    peer_transports_cow = new ArrayList();

    //BtManager is threaded, this variable represents the
    // current loop iteration. It's used by some components only called
    // at some specific times.
    mainloop_loop_count = 0;

    //The current tracker state
    //this could be start or update

    _averageReceptionSpeed = Average.getInstance(1000, 30);

    // the stats
    _stats =new PEPeerManagerStatsImpl(this);

    superSeedMode = (COConfigurationManager.getBooleanParameter("Use Super Seeding") && this.getRemaining() == 0);

    superSeedModeCurrentPiece = 0;

    if (superSeedMode)
    {
      initialiseSuperSeedMode();
    }

    // initial check on finished state - future checks are driven by piece check results

    // Moved out of mainLoop() so that it runs immediately, possibly changing
    // the state to seeding.

    checkFinished( true );

    UploadSlotManager.getSingleton().registerHelper( upload_helper );

    lastNeededUndonePieceChange =Long.MIN_VALUE;
    _timeStarted = SystemTime.getCurrentTime();

    is_running = true;

    // activate after marked as running as we may synchronously add connections here due to pending activations

    adapter.getPeerManagerRegistration().activate( this );

    PeerNATTraverser.getSingleton().register( this );

    PeerControlSchedulerFactory.getSingleton(partition_id).register(this);
  }

  public void stopAll()
  {
    is_running =false;

    UploadSlotManager.getSingleton().deregisterHelper( upload_helper );

    PeerControlSchedulerFactory.getSingleton(partition_id).unregister(this);

    PeerNATTraverser.getSingleton().unregister( this );

    // remove legacy controller activation

    adapter.getPeerManagerRegistration().deactivate();

    closeAndRemoveAllPeers("download stopped", false);

    // clear pieces
    for (int i =0; i <_nbPieces; i++ )
    {
      if (pePieces[i] !=null)
        removePiece(pePieces[i], i);
    }

    // 5. Remove listeners
    COConfigurationManager.removeParameterListener("Ip Filter Enabled", this);

    ip_filter.removeListener(this);

    piecePicker.destroy();
   
    final ArrayList<PEPeerManagerListener> peer_manager_listeners = peer_manager_listeners_cow;

    for( int i=0; i < peer_manager_listeners.size(); i++ ) {
     
      ((PEPeerManagerListener)peer_manager_listeners.get(i)).destroyed();
    }
   
    sweepList = Collections.emptyList();
   
    pending_nat_traversals.clear();
   
    udp_reconnects.clear();
   
    is_destroyed = true;
  }

  public int
  getPartitionID()
  {
    return( partition_id );
  }
 
  public boolean
  isDestroyed()
  {
    return( is_destroyed );
  }
 
  public DiskManager getDiskManager() {  return disk_mgr;   }
  public PiecePicker getPiecePicker()
  {
    return piecePicker;
  }

  public PEPeerManagerAdapter  getAdapter(){ return( adapter ); }

  public String getDisplayName(){ return( adapter.getDisplayName()); }

  public String
  getName()
  {
    return( getDisplayName());
  }
 
  public void
  schedule()
  {
    try{
        // first off update the stats so they can be used by subsequent steps
     
      updateStats();

      updateTrackerAnnounceInterval();
     
      doConnectionChecks();
     
      processPieceChecks();

      // note that seeding_mode -> torrent totally downloaded, not just non-dnd files
      // complete, so there is no change of a new piece appearing done by a means such as
      // background periodic file rescans

      if ( !seeding_mode ){

        checkCompletedPieces(); //check to see if we've completed anything else
      }

      checkBadPieces();

      checkInterested();      // see if need to recheck Interested on all peers

      piecePicker.updateAvailability();

      checkCompletionState()// pick up changes in completion caused by dnd file changes

      checkSeeds();

      if(!seeding_mode) {
        // if we're not finished

        checkRequests();

        piecePicker.allocateRequests();

        checkRescan();
        checkSpeedAndReserved();

        check99PercentBug();
      }


      updatePeersInSuperSeedMode();
      doUnchokes();

    }catch (Throwable e) {

      Debug.printStackTrace( e );
    }
    mainloop_loop_count++;
  }



  /**
   * A private method that does analysis of the result sent by the tracker.
   * It will mainly open new connections with peers provided
   * and set the timeToWait variable according to the tracker response.
   * @param tracker_response
   */

  private void
  analyseTrackerResponse(
      TRTrackerAnnouncerResponse  tracker_response )
  {
    // tracker_response.print();
    final TRTrackerAnnouncerResponsePeer[]  peers = tracker_response.getPeers();

    if ( peers != null ){
      addPeersFromTracker( tracker_response.getPeers())
    }

    final Map extensions = tracker_response.getExtensions();

    if (extensions != null ){
      addExtendedPeersFromTracker( extensions );
    }
  }

  public void
  processTrackerResponse(
      TRTrackerAnnouncerResponse  response )
  {
    // only process new peers if we're still running
    if ( is_running ){
      analyseTrackerResponse( response );
    }
  }

  private void
  addExtendedPeersFromTracker(
      Map    extensions )
  {
    final Map  protocols = (Map)extensions.get("protocols");

    if ( protocols != null ){

      System.out.println( "PEPeerControl: tracker response contained protocol extensions");

      final Iterator protocol_it = protocols.keySet().iterator();

      while( protocol_it.hasNext()){

        final String  protocol_name = (String)protocol_it.next();

        final Map  protocol = (Map)protocols.get(protocol_name);

        final List  transports = PEPeerTransportFactory.createExtendedTransports( this, protocol_name, protocol );

        for (int i=0;i<transports.size();i++){

          final PEPeer  transport = (PEPeer)transports.get(i);

          addPeer( transport );
        }
      }
    }
  }

  public List<PEPeer>
  getPeers()
  {
    return( peer_transports_cow );
  }

  public List<PEPeer>
  getPeers(
    String  address )
  {   
    List<PEPeer>  result = new ArrayList<PEPeer>();

    Iterator<PEPeer>  it = peer_transports_cow.iterator();

    while( it.hasNext()){

      PEPeerTransport  peer = (PEPeerTransport)it.next();

      if ( peer.getIp().equals( address )){

        result.add( peer );
      }
    }

    return( result );
  }

  public int
  getPendingPeerCount()
  {
    return( peer_database.getDiscoveredPeerCount());
  }
 
  public PeerDescriptor[]
    getPendingPeers()
    {
      return((PeerDescriptor[])peer_database.getDiscoveredPeers());
    }
 
  public PeerDescriptor[]
  getPendingPeers(
    String  address )
  {
    return((PeerDescriptor[])peer_database.getDiscoveredPeers( address ));
  }

  public void
  addPeer(
      PEPeer    _transport )
  {
    if ( !( _transport instanceof PEPeerTransport )){

      throw( new RuntimeException("invalid class"));
    }

    final PEPeerTransport  transport = (PEPeerTransport)_transport;

    if (!ip_filter.isInRange(transport.getIp(), getDisplayName(), getTorrentHash())) {

      final ArrayList peer_transports = peer_transports_cow;

      if ( !peer_transports.contains(transport)){

        addToPeerTransports( transport );

        transport.start();

      }else{
        Debug.out( "addPeer():: peer_transports.contains(transport): SHOULD NEVER HAPPEN !" );
        transport.closeConnection( "already connected" );
      }
    }else{

      transport.closeConnection( "IP address blocked by filters" );
    }
  }

  protected byte[]
  getTorrentHash()
  {
    try{
      return( disk_mgr.getTorrent().getHash());

    }catch( Throwable e ){
     
      return( null );
    }
  }
  public void
  removePeer(
      PEPeer  _transport )
  {
    removePeer( _transport, "remove peer" );
  }

  public void
  removePeer(
      PEPeer  _transport,
      String  reason )
  {
    if ( !( _transport instanceof PEPeerTransport )){

      throw( new RuntimeException("invalid class"));
    }

    PEPeerTransport  transport = (PEPeerTransport)_transport;

    closeAndRemovePeer( transport, reason, true );
  }

  private void closeAndRemovePeer( PEPeerTransport peer, String reason, boolean log_if_not_found )
  {
    boolean removed =false;

    // copy-on-write semantics
    try{
      peer_transports_mon.enter();

      if ( peer_transports_cow.contains( peer )){

        final ArrayList new_peer_transports = new ArrayList( peer_transports_cow );

        new_peer_transports.remove(peer);
        peer_transports_cow =new_peer_transports;
        removed =true;
      }
    }
    finally{
      peer_transports_mon.exit();
    }

    if( removed ) {
      peer.closeConnection( reason );
      peerRemoved(peer);    //notify listeners     
    }
    else {
      if ( log_if_not_found ){
        // we know this happens due to timing issues... Debug.out( "closeAndRemovePeer(): peer not removed" );
      }
    }
  }

  private void closeAndRemoveAllPeers( String reason, boolean reconnect ) {
    ArrayList peer_transports;

    try{
      peer_transports_mon.enter();

      peer_transports = peer_transports_cow;

      peer_transports_cow = new ArrayList( 0 )
    }
    finally{
      peer_transports_mon.exit();
    }

    for( int i=0; i < peer_transports.size(); i++ ) {
      final PEPeerTransport peer = (PEPeerTransport)peer_transports.get( i );

      try{

        peer.closeConnection( reason );

      }catch( Throwable e ){

        // if something goes wrong with the close process (there's a bug in there somewhere whereby
        // we occasionally get NPEs then we want to make sure we carry on and close the rest

        Debug.printStackTrace(e);
      }

      try{
        peerRemoved( peer )//notify listeners

      }catch( Throwable e ){

        Debug.printStackTrace(e);
      }
    }

    if( reconnect ) {
      for( int i=0; i < peer_transports.size(); i++ ) {
        final PEPeerTransport peer = (PEPeerTransport)peer_transports.get( i );

        PEPeerTransport  reconnected_peer = peer.reconnect(false, false);
      }
    }
  }




  public void
  addPeer(
    String     ip_address,
    int      tcp_port,
    int      udp_port,
    boolean   use_crypto,
    Map      user_data )
  {
    final byte type = use_crypto ? PeerItemFactory.HANDSHAKE_TYPE_CRYPTO : PeerItemFactory.HANDSHAKE_TYPE_PLAIN;
    final PeerItem peer_item = PeerItemFactory.createPeerItem( ip_address, tcp_port, PeerItem.convertSourceID( PEPeerSource.PS_PLUGIN ), type, udp_port, PeerItemFactory.CRYPTO_LEVEL_1, 0 );

    byte  crypto_level = PeerItemFactory.CRYPTO_LEVEL_1;

    if( !isAlreadyConnected( peer_item ) ) {

      String fail_reason;

      boolean  tcp_ok = TCPNetworkManager.TCP_OUTGOING_ENABLED && tcp_port > 0;
      boolean udp_ok = UDPNetworkManager.UDP_OUTGOING_ENABLED && udp_port > 0;
     
      if (   tcp_ok && !( ( prefer_udp || prefer_udp_default ) && udp_ok )){

        fail_reason = makeNewOutgoingConnection( PEPeerSource.PS_PLUGIN, ip_address, tcp_port, udp_port, true, use_crypto, crypto_level, user_data )//directly inject the the imported peer

      }else if ( udp_ok ){

        fail_reason = makeNewOutgoingConnection( PEPeerSource.PS_PLUGIN, ip_address, tcp_port, udp_port, false, use_crypto, crypto_level, user_data )//directly inject the the imported peer

      }else{

        fail_reason = "No usable protocol";
      }

      if( fail_reason != null Debug.out( "Injected peer " + ip_address + ":" + tcp_port + " was not added - " + fail_reason );
    }
  }



  private void
  addPeersFromTracker(
    TRTrackerAnnouncerResponsePeer[]    peers )
  {

    for (int i = 0; i < peers.length; i++){
      final TRTrackerAnnouncerResponsePeer  peer = peers[i];

      final ArrayList peer_transports = peer_transports_cow;

      boolean already_connected = false;

      for( int x=0; x < peer_transports.size(); x++ ) {
        final PEPeerTransport transport = (PEPeerTransport)peer_transports.get( x );

        // allow loopback connects for co-located proxy-based connections and testing

        if( peer.getAddress().equals( transport.getIp() )){

          final boolean same_allowed = COConfigurationManager.getBooleanParameter( "Allow Same IP Peers" ) ||
         
          transport.getIp().equals( "127.0.0.1" );

          if( !same_allowed || peer.getPort() == transport.getPort() ) {
            already_connected = true;
            break;
          }
        }
      }

      if( already_connected continue;

      if( peer_database != null ){
       
        byte type = peer.getProtocol() == DownloadAnnounceResultPeer.PROTOCOL_CRYPT ? PeerItemFactory.HANDSHAKE_TYPE_CRYPTO : PeerItemFactory.HANDSHAKE_TYPE_PLAIN;

        byte crypto_level = peer.getAZVersion() < TRTrackerAnnouncer.AZ_TRACKER_VERSION_3?PeerItemFactory.CRYPTO_LEVEL_1:PeerItemFactory.CRYPTO_LEVEL_2;

        PeerItem item = PeerItemFactory.createPeerItem(
            peer.getAddress(),
            peer.getPort(),
            PeerItem.convertSourceID( peer.getSource() ),
            type,
            peer.getUDPPort(),
            crypto_level,
            peer.getUploadSpeed());

        peerDiscovered( null, item );
       
        peer_database.addDiscoveredPeer( item );
      }

      int  http_port = peer.getHTTPPort();

      if ( http_port != 0 && !seeding_mode ){

        adapter.addHTTPSeed( peer.getAddress(), http_port );
      }
    }
  }


  /**
   * Request a new outgoing peer connection.
   * @param address ip of remote peer
   * @param port remote peer listen port
   * @return null if the connection was added to the transport list, reason if rejected
   */
 
  private String
  makeNewOutgoingConnection(
      String  peer_source,
      String   address,
      int     tcp_port,
      int      udp_port,
      boolean    use_tcp,
      boolean   require_crypto,
      byte    crypto_level,
      Map      user_data )
  {   
    //make sure this connection isn't filtered
  
    if( ip_filter.isInRange( address, getDisplayName(), getTorrentHash())) {
      return "IPFilter block";
    }

    //make sure we need a new connection
    final int needed = getMaxNewConnectionsAllowed();

    boolean  is_priority_connection = false;
   
    if ( user_data != null ){
     
      Boolean pc = (Boolean)user_data.get( Peer.PR_PRIORITY_CONNECTION );
     
      if ( pc != null && pc.booleanValue()){
       
        is_priority_connection = true;
      }
    }
   
    if( needed == 0 ){

      if (   peer_source != PEPeerSource.PS_PLUGIN ||
          !doOptimisticDisconnect(
              AddressUtils.isLANLocalAddress( address ) != AddressUtils.LAN_LOCAL_NO,
              is_priority_connection )){

        return "Too many connections";
      }
    }

    //make sure not already connected to the same IP address; allow loopback connects for co-located proxy-based connections and testing
    final boolean same_allowed = COConfigurationManager.getBooleanParameter( "Allow Same IP Peers" ) || address.equals( "127.0.0.1" );
    if( !same_allowed && PeerIdentityManager.containsIPAddress( _hash, address ) ){ 
      return "Already connected to IP";
    }

    if( PeerUtils.ignorePeerPort( tcp_port ) ) {
      if (Logger.isEnabled())
        Logger.log(new LogEvent(disk_mgr.getTorrent(), LOGID,
            "Skipping connect with " + address + ":" + tcp_port
            + " as peer port is in ignore list."));
      return "TCP port in ignore list";
    }

      //start the connection
   
    PEPeerTransport real = PEPeerTransportFactory.createTransport( this, peer_source, address, tcp_port, udp_port, use_tcp, require_crypto, crypto_level, user_data );

    addToPeerTransports( real );
   
    return null;
  }


  /**
   * A private method that checks if PEPieces being downloaded are finished
   * If all blocks from a PEPiece are written to disk, this method will
   * queue the piece for hash check.
   * Elsewhere, if it passes sha-1 check, it will be marked as downloaded,
   * otherwise, it will unmark it as fully downloaded, so blocks can be retreived again.
   */
  private void checkCompletedPieces() {
    if ((mainloop_loop_count %MAINLOOP_ONE_SECOND_INTERVAL) !=0)
      return;

    //for every piece
    for (int i = 0; i <_nbPieces; i++) {
      final DiskManagerPiece dmPiece =dm_pieces[i];
      //if piece is completly written, not already checking, and not Done
      if (dmPiece.isNeedsCheck())
      {
        //check the piece from the disk
        dmPiece.setChecking();

        DiskManagerCheckRequest req =
          disk_mgr.createCheckRequest(
              i, new Integer(CHECK_REASON_DOWNLOADED));

        req.setAdHoc( false );

        disk_mgr.enqueueCheckRequestreq, this );
      }
    }
  }

  /** Checks given piece to see if it's active but empty, and if so deactivates it.
   * @param pieceNumber to check
   * @return true if the piece was removed and is no longer active (pePiece ==null)
   */
  private boolean
  checkEmptyPiece(
    final int pieceNumber )
  {
        if ( piecePicker.isInEndGameMode()){
           
      return false;   // be sure to not remove pieces in EGM
        }
       
    final PEPiece pePiece =pePieces[pieceNumber];
    final DiskManagerPiece dmPiece =dm_pieces[pieceNumber];
   
    if ( pePiece == null || pePiece.isRequested())
      return false;
   
    if (dmPiece.getNbWritten() >0 ||pePiece.getNbUnrequested() < pePiece.getNbBlocks() ||pePiece.getReservedBy() !=null)
      return false;
 
      // reset in case dmpiece is in some skanky state
   
    pePiece.reset();
   
    removePiece(pePiece, pieceNumber);
    return true;
  }

  /**
   * Check if a piece's Speed is too fast for it to be getting new data
   * and if a reserved pieced failed to get data within 120 seconds
   */
  private void checkSpeedAndReserved()
  {
    // only check every 5 seconds
    if(mainloop_loop_count % MAINLOOP_FIVE_SECOND_INTERVAL != 0)
      return;

    final int        nbPieces  =_nbPieces;
    final PEPieceImpl[] pieces =pePieces;
    //for every piece
    for (int i =0; i <nbPieces; i++)
    {
      // placed before null-check in case it really removes a piece
      checkEmptyPiece(i);


      final PEPieceImpl pePiece =pieces[i];
      // these checks are only against pieces being downloaded
      // yet needing requests still/again
      if (pePiece !=null)
      {
        final long timeSinceActivity =pePiece.getTimeSinceLastActivity()/1000;       

        int pieceSpeed =pePiece.getSpeed();
        // block write speed slower than piece speed
        if (pieceSpeed > 0 && timeSinceActivity*pieceSpeed*0.25 > DiskManager.BLOCK_SIZE/1024)
        {
          if(pePiece.getNbUnrequested() > 2)
            pePiece.setSpeed(pieceSpeed-1);
          else
            pePiece.setSpeed(0);
        }


        if(timeSinceActivity > 120)
        {
          pePiece.setSpeed(0);
          // has reserved piece gone stagnant?
          final String reservingPeer =pePiece.getReservedBy();
          if(reservingPeer !=null)
          {
            final PEPeerTransport pt = getTransportFromAddress(reservingPeer);
            // Peer is too slow; Ban them and unallocate the piece
            // but, banning is no good for peer types that get pieces reserved
            // to them for other reasons, such as special seed peers
            if (needsMD5CheckOnCompletion(i))
              badPeerDetected(reservingPeer, i);
            else if (pt != null)
              closeAndRemovePeer(pt, "Reserved piece data timeout; 120 seconds", true);

            pePiece.setReservedBy(null);
          }

          if (!piecePicker.isInEndGameMode()){
            pePiece.checkRequests();
          }

          checkEmptyPiece(i);
        }

      }
    }
  }

  private void
  check99PercentBug()
  {
    // there's a bug whereby pieces are left downloaded but never written. might have been fixed by
    // changes to the "write result" logic, however as a stop gap I'm adding code to scan for such
    // stuck pieces and reset them

    if ( mainloop_loop_count % MAINLOOP_SIXTY_SECOND_INTERVAL == 0 ) {

      long  now = SystemTime.getCurrentTime();

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

        PEPiece  pe_piece = pePieces[ i ];

        if ( pe_piece != null ){

          DiskManagerPiece  dm_piece = dm_pieces[i];

          if ( !dm_piece.isDone()){

            if ( pe_piece.isDownloaded()){

              if ( now - pe_piece.getLastDownloadTime(now) > stalled_piece_timeout ){

                // people with *very* slow disk writes can trigger this (I've been talking to a user
                // with a SAN that has .5 second write latencies when checking a file at the same time
                // this means that when dowloading > 32K/sec things start backing up). Eventually the
                // write controller will start blocking the network thread to prevent unlimited
                // queueing but until that time we need to handle this situation slightly better)

                // if there are any outstanding requests for this piece then leave it alone

                if ( !( disk_mgr.hasOutstandingWriteRequestForPiece( i ) ||
                    disk_mgr.hasOutstandingReadRequestForPiece( i ) ||
                    disk_mgr.hasOutstandingCheckRequestForPiece( i ) )){

                  Debug.out( "Fully downloaded piece stalled pending write, resetting p_piece " + i );

                  pe_piece.reset();
                }
              }
            }
          }
        }

      }
    }
  }

  private void checkInterested()
  {
    if ( (mainloop_loop_count %MAINLOOP_ONE_SECOND_INTERVAL) != 0 ){
      return;
    }

    if (lastNeededUndonePieceChange >=piecePicker.getNeededUndonePieceChange())
      return;

    lastNeededUndonePieceChange =piecePicker.getNeededUndonePieceChange();

    final ArrayList peer_transports = peer_transports_cow;
    int cntPeersSnubbed =0// recount # snubbed peers while we're at it
    for (int i =0; i <peer_transports.size(); i++)
    {
      final PEPeerTransport peer =(PEPeerTransport)peer_transports.get(i);
      peer.checkInterested();
      if (peer.isSnubbed())
        cntPeersSnubbed++;
    }
    setNbPeersSnubbed(cntPeersSnubbed);
  }


  /**
   * Private method to process the results given by DiskManager's
   * piece checking thread via asyncPieceChecked(..)
   */
  private void
  processPieceChecks()
  {
    if ( piece_check_result_list.size() > 0 ){

      final List pieces;

      // process complete piece results

      try{
        piece_check_result_list_mon.enter();

        pieces = new ArrayList( piece_check_result_list );

        piece_check_result_list.clear();

      }finally{

        piece_check_result_list_mon.exit();
      }

      final Iterator it = pieces.iterator();

      while (it.hasNext()) {

        final Object[]  data = (Object[])it.next();

        processPieceCheckResult((DiskManagerCheckRequest)data[0],((Integer)data[1]).intValue());

      }
    }
  }

  private void
  checkBadPieces()
  {
    if ( mainloop_loop_count % MAINLOOP_SIXTY_SECOND_INTERVAL == 0 ){

      if ( bad_piece_reported != -1 ){
       
        DiskManagerCheckRequest  req =
          disk_mgr.createCheckRequest(
            bad_piece_reported,
            new Integer( CHECK_REASON_BAD_PIECE_CHECK ))
       
        req.setLowPriority( true );
       
           if ( Logger.isEnabled()){
            
          Logger.log(
              new LogEvent(
                disk_mgr.getTorrent(), LOGID,
                "Rescanning reported-bad piece " + bad_piece_reported ));
             
           }
          
        bad_piece_reported  = -1;

        try{
          disk_mgr.enqueueCheckRequest( req, this );
         
        }catch( Throwable e ){
         
         
          Debug.printStackTrace(e);
        }
      }
    }   
  }
 
  private void
  checkRescan()
  {
    if ( rescan_piece_time == 0 ){

      // pending a piece completion

      return;
    }

    if ( next_rescan_piece == -1 ){

      if ( mainloop_loop_count % MAINLOOP_FIVE_SECOND_INTERVAL == 0 ){

        if ( adapter.isPeriodicRescanEnabled()){

          next_rescan_piece  = 0;
        }
      }
    }else{

      if ( mainloop_loop_count % MAINLOOP_TEN_MINUTE_INTERVAL == 0 ){

        if ( !adapter.isPeriodicRescanEnabled()){

          next_rescan_piece  = -1;
        }
      }
    }

    if ( next_rescan_piece == -1 ){

      return;
    }

    // delay as required

    final long  now = SystemTime.getCurrentTime();

    if ( rescan_piece_time > now ){

      rescan_piece_time  = now;
    }

    // 250K/sec limit

    final long  piece_size = disk_mgr.getPieceLength();

    final long  millis_per_piece = piece_size / 250;

    if ( now - rescan_piece_time < millis_per_piece ){

      return;
    }

    while( next_rescan_piece != -1 ){

      int  this_piece = next_rescan_piece;

      next_rescan_piece++;

      if ( next_rescan_piece == _nbPieces ){

        next_rescan_piece  = -1;
      }
     
        // this functionality is to pick up pieces that have been downloaded OUTSIDE of
        // Azureus - e.g. when two torrents are sharing a single file. Hence the check on
        // the piece NOT being done

      if ( pePieces[this_piece] == null && !dm_pieces[this_piece].isDone() && dm_pieces[this_piece].isNeeded()){

        DiskManagerCheckRequest  req =
          disk_mgr.createCheckRequest(
              this_piece,
              new Integer( CHECK_REASON_SCAN ))

        req.setLowPriority( true );

        if ( Logger.isEnabled()){

          Logger.log(
              new LogEvent(
                  disk_mgr.getTorrent(), LOGID,
                  "Rescanning piece " + this_piece ));

        }

        rescan_piece_time  = 0// mark as check piece in process

        try{
          disk_mgr.enqueueCheckRequest( req, this );

        }catch( Throwable e ){

          rescan_piece_time  = now;

          Debug.printStackTrace(e);
        }

        break;
      }
    }
  }

  public void
  badPieceReported(
    PEPeerTransport    originator,
    int          piece_number )
  {
    Debug.outNoStack( getDisplayName() + ": bad piece #" + piece_number + " reported by " + originator.getIp());
   
    if ( piece_number < 0 || piece_number >= _nbPieces ){
     
      return;
    }
   
    bad_piece_reported = piece_number;
  }
 
  public void
  setStatsReceiver(
    PEPeerManager.StatsReceiver  receiver )
  {
    stats_receiver = receiver;
  }
 
  public void
  statsRequest(
    PEPeerTransport   originator,
    Map         request )
  {
    Map    reply = new HashMap();
   
    adapter.statsRequest( originator, request, reply );
   
    if ( reply.size() > 0 ){
     
      originator.sendStatsReply( reply );
    }
  }
 
  public void
  statsReply(
    PEPeerTransport   originator,
    Map         reply )
  { 
    PEPeerManager.StatsReceiver receiver = stats_receiver;
   
    if ( receiver != null ){
     
      receiver.receiveStats( originator, reply );
    }
  }

  /**
   * This method checks if the downloading process is finished.
   *
   */
  private void
  checkFinished(
      boolean start_of_day )
  {
    final boolean all_pieces_done =disk_mgr.getRemainingExcludingDND() ==0;

    if (all_pieces_done)
    {
      seeding_mode  = true;
     
      prefer_udp_bloom = null;
     
      piecePicker.clearEndGameChunks();

      if (!start_of_day)
        adapter.setStateFinishing();

      _timeFinished = SystemTime.getCurrentTime();
      final ArrayList peer_transports = peer_transports_cow;

      //remove previous snubbing
      for (int i =0; i <peer_transports.size(); i++ )
      {
        final PEPeerTransport pc = (PEPeerTransport) peer_transports.get(i);
        pc.setSnubbed(false);
      }
      setNbPeersSnubbed(0);

      final boolean checkPieces =COConfigurationManager.getBooleanParameter( "Check Pieces on Completion" );

      //re-check all pieces to make sure they are not corrupt, but only if we weren't already complete
      if (checkPieces &&!start_of_day)
      {
        final DiskManagerCheckRequest req =disk_mgr.createCheckRequest(-1, new Integer(CHECK_REASON_COMPLETE));
        disk_mgr.enqueueCompleteRecheckRequest(req, this);
      }

      _timeStartedSeeding = SystemTime.getCurrentTime();

      try{
        disk_mgr.saveResumeData(false);
       
      }catch( Throwable e ){
        Debug.out( "Failed to save resume data", e );
      }
      adapter.setStateSeeding( start_of_day );
      disk_mgr.downloadEnded();
    } else
    {
      seeding_mode = false;
    }
  }

  protected void
  checkCompletionState()
  {
    if ( mainloop_loop_count % MAINLOOP_ONE_SECOND_INTERVAL != 0 ){

      return;
    }

    boolean dm_done = disk_mgr.getRemainingExcludingDND() == 0;

    if ( seeding_mode ){

      if ( !dm_done ){

        seeding_mode = false;

        _timeStartedSeeding = -1;
        _timeFinished    = 0;

        Logger.log(
            new LogEventdisk_mgr.getTorrent(), LOGID,
                "Turning off seeding mode for PEPeerManager"));
      }
    }else{

      if ( dm_done ){

        checkFinished( false );

        if ( seeding_mode ){

          Logger.log(
              new LogEventdisk_mgr.getTorrent(), LOGID,
                  "Turning on seeding mode for PEPeerManager"));
        }
      }
    }
  }

  /**
   * This method will locate expired requests on peers, will cancel them,
   * and mark the peer as snubbed if we haven't received usefull data from
   * them within the last 60 seconds
   */
  private void checkRequests()
  {
    // to be honest I don't see why this can't be 5 seconds, but I'm trying 1 second
    // now as the existing 0.1 second is crazy given we're checking for events that occur
    // at 60+ second intervals

    if ( mainloop_loop_count % MAINLOOP_ONE_SECOND_INTERVAL != 0 ){

      return;
    }

    final long now =SystemTime.getCurrentTime();

    //for every connection
    final ArrayList peer_transports = peer_transports_cow;
    for (int i =peer_transports.size() -1; i >=0 ; i--)
    {
      final PEPeerTransport pc =(PEPeerTransport)peer_transports.get(i);
      if (pc.getPeerState() ==PEPeer.TRANSFERING)
      {
        final List expired = pc.getExpiredRequests();
        if (expired !=null &&expired.size() >0)
        {   // now we know there's a request that's > 60 seconds old
          final boolean isSeed =pc.isSeed();
          // snub peers that haven't sent any good data for a minute
          final long timeSinceGoodData =pc.getTimeSinceGoodDataReceived();
          if (timeSinceGoodData <0 ||timeSinceGoodData >60 *1000)
            pc.setSnubbed(true);

          //Only cancel first request if more than 2 mins have passed
          DiskManagerReadRequest request =(DiskManagerReadRequest) expired.get(0);

          final long timeSinceData =pc.getTimeSinceLastDataMessageReceived();
          final boolean noData =(timeSinceData <0) ||timeSinceData >(1000 *(isSeed ?120 :60));
          final long timeSinceOldestRequest = now - request.getTimeCreated(now);


          //for every expired request                             
          for (int j = (timeSinceOldestRequest >120 *1000 && noData? 0 : 1; j < expired.size(); j++)
          {
            //get the request object
            request =(DiskManagerReadRequest) expired.get(j);
            //Only cancel first request if more than 2 mins have passed
            pc.sendCancel(request);        //cancel the request object
            //get the piece number
            final int pieceNumber = request.getPieceNumber();
            PEPiece  pe_piece = pePieces[pieceNumber];
            //unmark the request on the block
            if ( pe_piece != null )
              pe_piece.clearRequested(request.getOffset() /DiskManager.BLOCK_SIZE);
            // remove piece if empty so peers can choose something else, except in end game
            if (!piecePicker.isInEndGameMode())
              checkEmptyPiece(pieceNumber);
          }
        }
      } 
    }
  }

  private void
  updateTrackerAnnounceInterval()
  {
    if ( mainloop_loop_count % MAINLOOP_FIVE_SECOND_INTERVAL != 0 ){
      return;
    }

    final int WANT_LIMIT = 100;

    int num_wanted = getMaxNewConnectionsAllowed();

    final boolean has_remote = adapter.isNATHealthy();
    if( has_remote ) {
      //is not firewalled, so can accept incoming connections,
      //which means no need to continually keep asking the tracker for peers
      num_wanted = (int)(num_wanted / 1.5);
    }

    if ( num_wanted < 0 || num_wanted > WANT_LIMIT ) {
      num_wanted = WANT_LIMIT;
    }

    int current_connection_count = PeerIdentityManager.getIdentityCount( _hash );

    final TRTrackerScraperResponse tsr = adapter.getTrackerScrapeResponse();

    if( tsr != null && tsr.isValid() ) {  //we've got valid scrape info
      final int num_seeds = tsr.getSeeds();  
      final int num_peers = tsr.getPeers();

      final int swarm_size;

      if( seeding_mode ) {
        //Only use peer count when seeding, as other seeds are unconnectable.
        //Since trackers return peers randomly (some of which will be seeds),
        //backoff by the seed2peer ratio since we're given only that many peers
        //on average each announce.
        final float ratio = (float)num_peers / (num_seeds + num_peers);
        swarm_size = (int)(num_peers * ratio);
      }
      else {
        swarm_size = num_peers + num_seeds;
      }

      if( swarm_size < num_wanted ) {  //lower limit to swarm size if necessary
        num_wanted = swarm_size;
      }
    }

    if( num_wanted < 1 ) {  //we dont need any more connections
      adapter.setTrackerRefreshDelayOverrides( 100 )//use normal announce interval
      return;
    }

    if( current_connection_count == 0 current_connection_count = 1//fudge it :)

    final int current_percent = (current_connection_count * 100) / (current_connection_count + num_wanted);

    adapter.setTrackerRefreshDelayOverrides( current_percent )//set dynamic interval override
  }

  public boolean
  hasDownloadablePiece()
  {
    return( piecePicker.hasDownloadablePiece());
  }

  public int
  getBytesQueuedForUpload()
  {
    return( bytes_queued_for_upload );
  }
 
  public int 
  getNbPeersWithUploadQueued()
  {
    return( connections_with_queued_data );
  }
     
  public int 
  getNbPeersWithUploadBlocked()
  {
    return( connections_with_queued_data_blocked );
  }
 
  public int
  getNbPeersUnchoked()
  {
    return( connections_unchoked );
  }
 
  public int[] getAvailability()
  {
    return piecePicker.getAvailability();
  }

  //this only gets called when the My Torrents view is displayed
  public float getMinAvailability()
  {
    return piecePicker.getMinAvailability();
  }

  public float getAvgAvail()
  {
    return piecePicker.getAvgAvail();
  }

  public long getAvailWentBadTime()
  {
    return( piecePicker.getAvailWentBadTime());
  }
 
  public void addPeerTransport( PEPeerTransport transport ) {
    if (!ip_filter.isInRange(transport.getIp(), getDisplayName(), getTorrentHash())) {
      final ArrayList peer_transports = peer_transports_cow;

      if (!peer_transports.contains( transport )) {
        addToPeerTransports(transport);   
      }
      else{
        Debug.out( "addPeerTransport():: peer_transports.contains(transport): SHOULD NEVER HAPPEN !" );       
        transport.closeConnection( "already connected" );
      }
    }
    else {
      transport.closeConnection( "IP address blocked by filters" );
    }
  }


  /**
   * Do all peer choke/unchoke processing.
   */
  private void doUnchokes() { 

    // logic below is either 1 second or 10 secondly, bail out early id neither

    if( !UploadSlotManager.AUTO_SLOT_ENABLE ) {     //manual per-torrent unchoke slot mode

      if( mainloop_loop_count % MAINLOOP_ONE_SECOND_INTERVAL != 0 ) {
        return;
      }

      final int max_to_unchoke = adapter.getMaxUploads()//how many simultaneous uploads we should consider
      final ArrayList peer_transports = peer_transports_cow;

      //determine proper unchoker
      if( seeding_mode ) {
        if( unchoker == null || !(unchoker.isSeedingUnchoker()) ) {
          unchoker = UnchokerFactory.getSingleton().getUnchoker( true );
        }
      }
      else {
        if( unchoker == null || unchoker.isSeedingUnchoker()) {
          unchoker = UnchokerFactory.getSingleton().getUnchoker( false );
        }
      }

        //do main choke/unchoke update every 10 secs
     
      if ( mainloop_loop_count % MAINLOOP_TEN_SECOND_INTERVAL == 0 ){

        final boolean refresh = mainloop_loop_count % MAINLOOP_THIRTY_SECOND_INTERVAL == 0;

        unchoker.calculateUnchokes( max_to_unchoke, peer_transports, refresh, adapter.hasPriorityConnection());

        ArrayList  chokes     = unchoker.getChokes();
        ArrayList  unchokes  = unchoker.getUnchokes();
       
        addFastUnchokes( unchokes );
       
        UnchokerUtil.performChokes( chokes, unchokes );
       
      }else if ( mainloop_loop_count % MAINLOOP_ONE_SECOND_INTERVAL == 0 ) {  //do quick unchoke check every 1 sec

        ArrayList unchokes = unchoker.getImmediateUnchokes( max_to_unchoke, peer_transports );

        addFastUnchokes( unchokes );
       
        UnchokerUtil.performChokes( null, unchokes );
      }
    }
  }

  private void
  addFastUnchokes(
    ArrayList  peers_to_unchoke )
  {
    for( Iterator it=peer_transports_cow.iterator();it.hasNext();) {

      PEPeerTransport peer = (PEPeerTransport)it.next();

      if (   peer.getConnectionState() != PEPeerTransport.CONNECTION_FULLY_ESTABLISHED ||
          !UnchokerUtil.isUnchokable( peer, true ) ||
          peers_to_unchoke.contains( peer )){
       
        continue;
      }

      if( peer.isLANLocal()){
     
        peers_to_unchoke.add( peer );
       
      }else if (   fast_unchoke_new_peers &&
            peer.getData( "fast_unchoke_done" ) == null ){

        peer.setData( "fast_unchoke_done", "" );
       
        peers_to_unchoke.add( peer );
      }
    }
  }
 
//  send the have requests out
  private void sendHave(int pieceNumber) {
    //fo
    final ArrayList peer_transports = peer_transports_cow;

    for (int i = 0; i < peer_transports.size(); i++) {
      //get a peer connection
      final PEPeerTransport pc = (PEPeerTransport) peer_transports.get(i);
      //send the have message
      pc.sendHave(pieceNumber);
    }

  }

  // Method that checks if we are connected to another seed, and if so, disconnect from him.
  private void checkSeeds() {
    //proceed on mainloop 1 second intervals if we're a seed and we want to force disconnects
    if ((mainloop_loop_count % MAINLOOP_ONE_SECOND_INTERVAL) != 0)
      return;

    if (!disconnect_seeds_when_seeding ){
      return;
    }

    ArrayList to_close = null;

    final ArrayList peer_transports = peer_transports_cow;
    for (int i = 0; i < peer_transports.size(); i++) {
      final PEPeerTransport pc = (PEPeerTransport) peer_transports.get(i);

      if (pc != null && pc.getPeerState() == PEPeer.TRANSFERING && ((isSeeding() && pc.isSeed()) || pc.isRelativeSeed())) {
        if( to_close == null to_close = new ArrayList();
        to_close.add( pc );
      }
    }

    if( to_close != null ) {   
      for( int i=0; i < to_close.size(); i++ ) {       
        closeAndRemovePeer( (PEPeerTransport)to_close.get(i), "disconnect other seed when seeding", false );
      }
    }
  }




  private void updateStats() {  

    if ( (mainloop_loop_count %MAINLOOP_ONE_SECOND_INTERVAL) != 0 ){
      return;
    }

    //calculate seeds vs peers
    final ArrayList<PEPeer> peer_transports = peer_transports_cow;

    int  new_pending_tcp_connections   = 0;
    int new_connecting_tcp_connections  = 0;
   
    int  new_seeds = 0;
    int new_peers = 0;
    int new_tcp_incoming   = 0;
    int new_udp_incoming    = 0;
   
    int  bytes_queued   = 0;
    int  con_queued    = 0;
    int con_blocked    = 0;
    int con_unchoked  = 0;
   
    for ( Iterator<PEPeer> it=peer_transports.iterator();it.hasNext();){
     
      final PEPeerTransport pc = (PEPeerTransport) it.next();
     
      if ( pc.getPeerState() == PEPeer.TRANSFERING) {
       
        if ( !pc.isChokedByMe()){
         
          con_unchoked++;
        }
       
        Connection connection = pc.getPluginConnection();
       
        if ( connection != null ){
         
          OutgoingMessageQueue mq = connection.getOutgoingMessageQueue();
         
          int q = mq.getDataQueuedBytes() + mq.getProtocolQueuedBytes();
         
          bytes_queued += q;
         
          if ( q > 0 ){
           
            con_queued++;
           
            if ( mq.isBlocked()){
             
              con_blocked++;
            }
          }
        }
       
        if (pc.isSeed())
          new_seeds++;
        else
          new_peers++;

        if ( pc.isIncoming() && !pc.isLANLocal()){
                   
          if ( pc.isTCP() ) {
       
            new_tcp_incoming++;
           
          }else{
           
            new_udp_incoming++;
          }
        }
      }else{       
        if ( pc.isTCP()){
         
          int c_state = pc.getConnectionState();

          if ( c_state == PEPeerTransport.CONNECTION_PENDING ){
           
            new_pending_tcp_connections++;
           
          }else if ( c_state == PEPeerTransport.CONNECTION_CONNECTING ){
           
            new_connecting_tcp_connections++;
          }
        }
      }
    }

    _seeds = new_seeds;
    _peers = new_peers;
    _remotesTCPNoLan = new_tcp_incoming;
    _remotesUDPNoLan = new_udp_incoming;
    _tcpPendingConnections = new_pending_tcp_connections;
    _tcpConnectingConnections = new_connecting_tcp_connections;
   
    bytes_queued_for_upload         = bytes_queued;
    connections_with_queued_data      = con_queued;
    connections_with_queued_data_blocked  = con_blocked;
    connections_unchoked          = con_unchoked;
  }
  /**
   * The way to unmark a request as being downloaded, or also
   * called by Peer connections objects when connection is closed or choked
   * @param request a DiskManagerReadRequest holding details of what was canceled
   */
  public void requestCanceled(DiskManagerReadRequest request)
  {
    final int pieceNumber =request.getPieceNumber()//get the piece number
    PEPiece pe_piece = pePieces[pieceNumber];
    if (pe_piece != null ){
      pe_piece.clearRequested(request.getOffset() /DiskManager.BLOCK_SIZE);
    }
  }


  public PEPeerControl
  getControl()
  {
    return( this );
  }

  public byte[][]
  getSecrets(
    int  crypto_level )
  {
    return( adapter.getSecrets( crypto_level ));
  }

//  get the hash value
  public byte[] getHash() {
    return _hash.getDataID();
  }

  public PeerIdentityDataID
  getPeerIdentityDataID()
  {
    return( _hash );
  }

//  get the peer id value
  public byte[] getPeerId() {
    return _myPeerId;
  }

//  get the remaining percentage
  public long getRemaining() {
    return disk_mgr.getRemaining();
  }


  public void discarded(PEPeer peer, int length) {
    if (length > 0){
      _stats.discarded(peer, length);
     
        // discards are more likely during end-game-mode
     
      if ( ban_peer_discard_ratio > 0 && !( piecePicker.isInEndGameMode() || piecePicker.hasEndGameModeBeenAbandoned())){
       
        long  received   = peer.getStats().getTotalDataBytesReceived();
        long  discarded  = peer.getStats().getTotalBytesDiscarded();
       
        long  non_discarded = received - discarded;
       
        if ( non_discarded < 0 ){
         
          non_discarded = 0;
        }
       
        if ( discarded >= ban_peer_discard_min_kb * 1024 ){
               
          if (   non_discarded == 0 ||
              ((float)discarded) / non_discarded >= ban_peer_discard_ratio ){
                       
            badPeerDetected( peer.getIp(), -1 );
          }
        }
      }
    }
  }

  public void dataBytesReceived(PEPeer peer, int length) {
    if (length > 0) {
      _stats.dataBytesReceived(peer,length);

      _averageReceptionSpeed.addValue(length);
    }
  }


  public void protocolBytesReceived(PEPeer peer,  int length ) {
    if (length > 0) {
      _stats.protocolBytesReceived(peer,length);
    }
  }

  public void dataBytesSent(PEPeer peer, int length) {
    if (length > 0) {
      _stats.dataBytesSent(peer, length );
    }
  }


  public void protocolBytesSent( PEPeer peer, int length ) {
    if (length > 0) {
      _stats.protocolBytesSent(peer,length);
    }
  }

  /** DiskManagerWriteRequestListener message
   * @see org.gudy.azureus2.core3.disk.DiskManagerWriteRequestListener
   */
  public void writeCompleted(DiskManagerWriteRequest request)
  {
    final int pieceNumber =request.getPieceNumber();

    DiskManagerPiece  dm_piece = dm_pieces[pieceNumber];

    if (!dm_piece.isDone()){

      final PEPiece pePiece =pePieces[pieceNumber];

      if (pePiece !=null){

        pePiece.setWritten((PEPeer)request.getUserData(), request.getOffset() /DiskManager.BLOCK_SIZE );

      }else{

        // this is a way of fixing a 99.9% bug where a dmpiece is left in a
        // fully downloaded state with the underlying pe_piece null. Possible explanation is
        // that a slow peer sends an entire piece at around the time a pe_piece gets reset
        // due to inactivity.

        // we also get here when recovering data that has come in late after the piece has
        // been abandoned

        dm_piece.setWritten( request.getOffset() /DiskManager.BLOCK_SIZE );
      }
    }
  }

  public void
  writeFailed(
      DiskManagerWriteRequest   request,
      Throwable           cause )
  {
    // if the write has failed then the download will have been stopped so there is no need to try
    // and reset the piece
  }

 
    /**
     *
     * @param data_offset  MUST be block aligned within the TORRENT
     * @param data
     * @return    buffers not yet written
     * @throws Exception
     */
 
  public DirectByteBuffer[]
  write(
    long          data_offset,
    DirectByteBuffer[]    data )
 
    throws Exception
  {
    if ( data_offset % DiskManager.BLOCK_SIZE != 0 ){
     
      throw( new Exception( "data must start at a block offset" ));
    }
   
    int  piece_length = disk_mgr.getPieceLength();
   
    int  data_length = 0;
   
    for( DirectByteBuffer buffer: data ){
     
      data_length += buffer.remaining( DirectByteBuffer.SS_DW );
    }
   
    long  written = 0;
   
    int    buffer_number = 0;
   
    for ( int i=0;i<data_length;i+=DiskManager.BLOCK_SIZE ){
     
      int  rem = data_length - i;
     
      if ( rem > DiskManager.BLOCK_SIZE ){
       
        rem = DiskManager.BLOCK_SIZE;
      }
     
      int  piece_number = (int)( data_offset / piece_length );
     
      DiskManagerPiece dm_piece = dm_pieces[piece_number];

      int  block_number = (int)(data_offset - ( piece_number * piece_length )) / DiskManager.BLOCK_SIZE;
     
      int block_size = dm_piece.getBlockSize( block_number );
     
      if ( rem < block_size ){
       
        break;
      }
     
      DirectByteBuffer chunk = DirectByteBufferPool.getBuffer( DirectByteBuffer.AL_BT_PIECE, block_size );
     
     
      writeBlock( piece_number, block_number*DiskManager.BLOCK_SIZE, chunk, null, true );
     
      written     += rem;
      data_offset    += rem;
    }
   
    List<DirectByteBuffer>  unwritten = new ArrayList<DirectByteBuffer>();
   
    for (int i=buffer_number;i<data.length;i++){
     
      if (data[i].hasRemaining( DirectByteBuffer.SS_DW )){
       
        unwritten.add( data[i] );
       
      }else{
       
        data[i].returnToPool();
      }
    }
   
    return( unwritten.toArray( new DirectByteBuffer[ unwritten.size()] ));
  }
 
   
  /** This method will queue up a dism manager write request for the block if the block is not already written.
   * It will send out cancels for the block to all peer either if in end-game mode, or per cancel param
   * @param pieceNumber to potentialy write to
   * @param offset within piece to queue write for
   * @param data to be writen
   * @param sender peer that sent this data
   * @param cancel if cancels definatly need to be sent to all peers for this request
   */
  public void writeBlock(int pieceNumber, int offset, DirectByteBuffer data, PEPeer sender, boolean cancel)
  {
    final int blockNumber =offset /DiskManager.BLOCK_SIZE;
    final DiskManagerPiece dmPiece =dm_pieces[pieceNumber];
    if (dmPiece.isWritten(blockNumber))
    {
      data.returnToPool();
      return;
    }

    PEPiece  pe_piece = pePieces[ pieceNumber ];

    if ( pe_piece != null ){

      pe_piece.setDownloaded( offset );
    }

    final DiskManagerWriteRequest request =disk_mgr.createWriteRequest(pieceNumber, offset, data, sender);
    disk_mgr.enqueueWriteRequest(request, this );
    // In case we are in endGame mode, remove the block from the chunk list
    if (piecePicker.isInEndGameMode())
      piecePicker.removeFromEndGameModeChunks(pieceNumber, offset);
    if (cancel ||piecePicker.isInEndGameMode())
    {   // cancel any matching outstanding download requests
      //For all connections cancel the request
      final ArrayList peer_transports = peer_transports_cow;
      for (int i=0;i<peer_transports.size();i++)
      {
        final PEPeerTransport connection =(PEPeerTransport) peer_transports.get(i);
        final DiskManagerReadRequest dmr =disk_mgr.createReadRequest(pieceNumber, offset, dmPiece.getBlockSize(blockNumber));
        connection.sendCancel( dmr );
      }
    }
  }


//  /**
//  * This method is only called when a block is received after the initial request expired,
//  * but the data has not yet been fulfilled by any other peer, so we use the block data anyway
//  * instead of throwing it away, and cancel any outstanding requests for that block that might have
//  * been sent after initial expiry.
//  */
//  public void writeBlockAndCancelOutstanding(int pieceNumber, int offset, DirectByteBuffer data,PEPeer sender) {
//  final int blockNumber =offset /DiskManager.BLOCK_SIZE;
//  final DiskManagerPiece dmPiece =dm_pieces[pieceNumber];
//  if (dmPiece.isWritten(blockNumber))
//  {
//  data.returnToPool();
//  return;
//  }
//  DiskManagerWriteRequest request =disk_mgr.createWriteRequest(pieceNumber, offset, data, sender);
//  disk_mgr.enqueueWriteRequest(request, this);

//  // cancel any matching outstanding download requests
//  List peer_transports =peer_transports_cow;
//  for (int i =0; i <peer_transports.size(); i++)
//  {
//  PEPeerTransport connection =(PEPeerTransport) peer_transports.get(i);
//  DiskManagerReadRequest dmr =disk_mgr.createReadRequest(pieceNumber, offset, dmPiece.getBlockSize(blockNumber));
//  connection.sendCancel(dmr);
//  }
//  }


  public boolean isWritten(int piece_number, int offset)
  {
    return dm_pieces[piece_number].isWritten(offset /DiskManager.BLOCK_SIZE);
  }

  public boolean
  validateReadRequest(
      PEPeerTransport  originator,
      int        pieceNumber,
      int       offset,
      int       length)
  {
    if ( disk_mgr.checkBlockConsistencyForRead(originator.getClient() + ": " + originator.getIp(), true, pieceNumber, offset, length)){

      if ( enable_seeding_piece_rechecks && isSeeding()){

        DiskManagerPiece  dm_piece = dm_pieces[pieceNumber];

        int  read_count = dm_piece.getReadCount()&0xffff;

        if ( read_count < SEED_CHECK_WAIT_MARKER - 1 ){

          read_count++;

          dm_piece.setReadCount((short)read_count );
        }
      }

      return( true );
    }else{

      return( false );
    }
  }

  public boolean
  validateHintRequest(
      PEPeerTransport  originator,
      int        pieceNumber,
      int       offset,
      int       length)
  {
    return( disk_mgr.checkBlockConsistencyForHint(originator.getClient() + ": " + originator.getIp(),pieceNumber, offset, length ));
  }

  public boolean
  validatePieceReply(
    PEPeerTransport    originator,
    int         pieceNumber,
    int         offset,
    DirectByteBuffer   data)
  {
    return disk_mgr.checkBlockConsistencyForWrite(originator.getClient() + ": " + originator.getIp(),pieceNumber, offset, data);
  }

  public int getAvailability(int pieceNumber)
  {
    return piecePicker.getAvailability(pieceNumber);
 

  public void havePiece(int pieceNumber, int pieceLength, PEPeer pcOrigin) {
    piecePicker.addHavePiece(pcOrigin, pieceNumber);
    _stats.haveNewPiece(pieceLength);

    if(superSeedMode) {
      superSeedPieces[pieceNumber].peerHasPiece(pcOrigin);
      if(pieceNumber == pcOrigin.getUniqueAnnounce()) {
        pcOrigin.setUniqueAnnounce(-1);
        superSeedModeNumberOfAnnounces--;
      }     
    }
    int availability =piecePicker.getAvailability(pieceNumber) -1;
    if (availability < 4) {
      if (dm_pieces[pieceNumber].isDone())
        availability--;
      if (availability <= 0)
        return;
      //for all peers

      final ArrayList peer_transports = peer_transports_cow;

      for (int i = peer_transports.size() - 1; i >= 0; i--) {
        final PEPeerTransport pc = (PEPeerTransport) peer_transports.get(i);
        if (pc !=pcOrigin &&pc.getPeerState() ==PEPeer.TRANSFERING &&pc.isPieceAvailable(pieceNumber))
          ((PEPeerStatsImpl)pc.getStats()).statisticalSentPiece(pieceLength / availability);
      }
    }
  }

  public int getPieceLength(int pieceNumber) {
       
    return disk_mgr.getPieceLength(pieceNumber);

  }

  public int
  getNbPeers()
  {
    return _peers;
  }

  public int getNbSeeds()
  {
    return _seeds;
  }

  public int getNbRemoteTCPConnections()
  {
    return _remotesTCPNoLan;
  }

  public int getNbRemoteUDPConnections()
  {
    return _remotesUDPNoLan;
  }
 
  public long getLastRemoteConnectionTime()
  {
    return( last_remote_time );
  }

  public PEPeerManagerStats getStats() {
    return _stats;
  }

  public int
  getNbPeersStalledPendingLoad()
  {
    int  res = 0;

    Iterator it = peer_transports_cow.iterator();

    while( it.hasNext()){

      PEPeerTransport transport = (PEPeerTransport)it.next();

      if ( transport.isStalledPendingLoad()){

        res ++;
      }
    }

    return( res );
  }

  /**
   * Returns the ETA time in seconds.
   * If the returned time is 0, the download is complete.
   * If the returned time is negative, the download
   * is complete and it took -xxx time to complete.
   */
  public long
  getETA()
  { 
    final long  now = SystemTime.getCurrentTime();

    if ( now < last_eta_calculation || now - last_eta_calculation > 900 ){

      long dataRemaining = disk_mgr.getRemainingExcludingDND();

      if ( dataRemaining > 0 ){

        int writtenNotChecked = 0;

        for (int i = 0; i < _nbPieces; i++)
        {
          if (dm_pieces[i].isInteresting()){
            writtenNotChecked +=dm_pieces[i].getNbWritten() *DiskManager.BLOCK_SIZE;
          }
        }

        dataRemaining = dataRemaining - writtenNotChecked;

        if  (dataRemaining < 0 ){
          dataRemaining  = 0;
        }
      }

      long  result;

      if (dataRemaining == 0) {
        final long timeElapsed = (_timeFinished - _timeStarted)/1000;
        //if time was spent downloading....return the time as negative
        if(timeElapsed > 1){
          result = timeElapsed * -1;
        }else{
          result = 0;
        }
      }else{

        final long averageSpeed = _averageReceptionSpeed.getAverage();
        long lETA = (averageSpeed == 0) ? Constants.CRAPPY_INFINITE_AS_LONG : dataRemaining / averageSpeed;
        // stop the flickering of ETA from "Finished" to "x seconds" when we are
        // just about complete, but the data rate is jumpy.
        if (lETA == 0)
          lETA = 1;
        result = lETA;
      }

      last_eta        = result;
      last_eta_calculation  = now;
    }

    return( last_eta );
  }

  public boolean
  isRTA()
  {
    return( piecePicker.getRTAProviders().size() > 0 );
  }
 
  private void
  addToPeerTransports(
    PEPeerTransport    peer )
  {
    boolean added = false;

    List limiters;

    try{
      peer_transports_mon.enter();

      // if it is already disconnected (synchronous failure during connect
      // for example) don't add it

      if ( peer.getPeerState() == PEPeer.DISCONNECTED ){

        return;
      }

      if( peer_transports_cow.contains( peer ) ){
        Debug.out( "Transport added twice" );
        return//we do not want to close it
      }

      if( is_running ) {
        //copy-on-write semantics
        final ArrayList new_peer_transports = new ArrayList(peer_transports_cow.size() +1);

        new_peer_transports.addAll( peer_transports_cow );

        new_peer_transports.add( peer );

        peer_transports_cow = new_peer_transports;

        added = true;
      }

      limiters = external_rate_limiters_cow;
    }
    finally{
      peer_transports_mon.exit();
    }

    if( added ) {
      boolean incoming = peer.isIncoming();
     
      _stats.haveNewConnection( incoming );
     
      if ( incoming ){
        long  connect_time = SystemTime.getCurrentTime();

        if ( connect_time > last_remote_time ){

          last_remote_time = connect_time;
        }
      }

      if ( limiters != null ){

        for (int i=0;i<limiters.size();i++){

          Object[]  entry = (Object[])limiters.get(i);

          peer.addRateLimiter((LimitedRateGroup)entry[0],((Boolean)entry[1]).booleanValue());
        }
      }

      peerAdded( peer );
    }
    else {
      peer.closeConnection( "PeerTransport added when manager not running" );
    }
  }

  public void
  addRateLimiter(
    LimitedRateGroup  group,
    boolean        upload )
  {
    List<PEPeer>  transports;

    try{
      peer_transports_mon.enter();

      ArrayList<Object[]>  new_limiters = new ArrayList<Object[]>( 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;

      transports = peer_transports_cow;

    }finally{

      peer_transports_mon.exit();
   

    for (int i=0;i<transports.size();i++){

      transports.get(i).addRateLimiter( group, upload );
    }
  }

  public void
  removeRateLimiter(
    LimitedRateGroup  group,
    boolean        upload )
  {
    List  transports;

    try{
      peer_transports_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;
        }
      }

      transports = peer_transports_cow;

    }finally{

      peer_transports_mon.exit();
   

    for (int i=0;i<transports.size();i++){

      ((PEPeerTransport)transports.get(i)).removeRateLimiter( group, upload );
   
  }

  public int
  getUploadRateLimitBytesPerSecond()
  {
    return( adapter.getUploadRateLimitBytesPerSecond());
  }

  public int
  getDownloadRateLimitBytesPerSecond()
  {
    return( adapter.getDownloadRateLimitBytesPerSecond());
  }

    //  the peer calls this method itself in closeConnection() to notify this manager
 
  public void
  peerConnectionClosed(
    PEPeerTransport   peer,
    boolean       connect_failed,
    boolean        network_failed )
  {
    boolean  connection_found = false;
   
    boolean tcpReconnect = false;
    boolean ipv6reconnect = false;
   
    try{
      peer_transports_mon.enter();
     
      int udpPort = peer.getUDPListenPort();
     
      boolean canTryUDP = UDPNetworkManager.UDP_OUTGOING_ENABLED && peer.getUDPListenPort() > 0;
      boolean canTryIpv6 = NetworkAdmin.getSingleton().hasIPV6Potential(true) && peer.getAlternativeIPv6() != null;

      if ( is_running ){

        PeerItem peer_item = peer.getPeerItemIdentity();
        PeerItem self_item = peer_database.getSelfPeer();
       

        if ( self_item == null || !self_item.equals( peer_item )){

          String  ip = peer.getIp();
          boolean wasIPv6;
          try
          {
            wasIPv6 = InetAddress.getByName(ip) instanceof Inet6Address;
          } catch (UnknownHostException e)
          {
            wasIPv6 = false;
            // something is fishy about the old address, don't try to reconnect with v6
            canTryIpv6 = false;
          }
         
          //System.out.println("netfail="+network_failed+", connfail="+connect_failed+", can6="+canTryIpv6+", was6="+wasIPv6);
         
          String  key = ip + ":" + udpPort;

          if ( peer.isTCP()){
         
            if ( connect_failed ){
             
                // TCP connect failure, try UDP later if necessary
           
              if ( canTryUDP && udp_fallback_for_failed_connection ){
               
                pending_nat_traversals.put(key, peer);
              } else if (canTryIpv6 && !wasIPv6)
              {
                tcpReconnect = true;
                ipv6reconnect = true;
              }
            }else if (   canTryUDP &&
                  udp_fallback_for_dropped_connection &&
                  network_failed &&
                  seeding_mode &&
                  peer.isInterested() &&
                  !peer.isSeed() && !peer.isRelativeSeed() &&
                  peer.getStats().getEstimatedSecondsToCompletion() > 60 &&
                  FeatureAvailability.isUDPPeerReconnectEnabled()){
           
              if (Logger.isEnabled()){
                Logger.log(new LogEvent(peer, LOGID, LogEvent.LT_WARNING, "Unexpected stream closure detected, attempting recovery"));
              }
             
                // System.out.println( "Premature close of stream: " + getDisplayName() + "/" + peer.getIp());
             
              udp_reconnects.put( key, peer );
             
            }else if network_failed &&
                  peer.isSafeForReconnect() &&
                  !(seeding_mode && (peer.isSeed() || peer.isRelativeSeed() || peer.getStats().getEstimatedSecondsToCompletion() < 60)) &&
                  getMaxConnections() > 0 &&
                  getMaxNewConnectionsAllowed() > getMaxConnections() / 3 &&
                  FeatureAvailability.isGeneralPeerReconnectEnabled()){
         
              tcpReconnect = true;             
            }
          }else if ( connect_failed ){
           
              // UDP connect failure
         
            if ( udp_fallback_for_failed_connection ){
             
              if ( peer.getData(PEER_NAT_TRAVERSE_DONE_KEY) == null){
               
                // System.out.println( "Direct reconnect failed, attempting NAT traversal" );
               
                pending_nat_traversals.put(key, peer);
              }
            }
          }
        }
      }

      if( peer_transports_cow.contains( peer )) {
        final ArrayList new_peer_transports = new ArrayList( peer_transports_cow );
        new_peer_transports.remove(peer);
        peer_transports_cow = new_peer_transports;
        connection_found  = true;
      }
    }
    finally{
      peer_transports_mon.exit();
    }

    if ( connection_found ){
      if( peer.getPeerState() != PEPeer.DISCONNECTED ) {
        System.out.println( "peer.getPeerState() != PEPeer.DISCONNECTED: " +peer.getPeerState() );
      }

      peerRemoved( peer )//notify listeners
    }
   
    if(tcpReconnect)
      peer.reconnect(false, ipv6reconnect);
  }




  public void
  peerAdded(
      PEPeer pc)
  {
    adapter.addPeer(pc)//async downloadmanager notification

    //sync peermanager notification
    final ArrayList peer_manager_listeners = peer_manager_listeners_cow;

    for( int i=0; i < peer_manager_listeners.size(); i++ ) {
      ((PEPeerManagerListener)peer_manager_listeners.get(i)).peerAdded( this, pc );
    }
  }


  public void
  peerRemoved(
      PEPeer pc)
  {
    if (   is_running &&
        !seeding_mode &&
        ( prefer_udp || prefer_udp_default )){
             
      int udp = pc.getUDPListenPort();
     
      if ( udp != 0 && udp == pc.getTCPListenPort()){
       
        BloomFilter filter = prefer_udp_bloom;
       
        if ( filter == null ){
         
          filter = prefer_udp_bloom = BloomFilterFactory.createAddOnly( PREFER_UDP_BLOOM_SIZE );       
        }
       
        if ( filter.getEntryCount() < PREFER_UDP_BLOOM_SIZE / 10 ){
                     
          filter.add( pc.getIp().getBytes());
        }
      }
    }

    final int piece = pc.getUniqueAnnounce();
    if(piece != -1 && superSeedMode ) {
      superSeedModeNumberOfAnnounces--;
      superSeedPieces[piece].peerLeft();
    }

    int[]  reserved_pieces = pc.getReservedPieceNumbers();

    if ( reserved_pieces != null ){

      for ( int reserved_piece: reserved_pieces ){
       
        PEPiece  pe_piece = pePieces[reserved_piece];
 
        if ( pe_piece != null ){
 
          String  reserved_by = pe_piece.getReservedBy();
 
          if ( reserved_by != null && reserved_by.equals( pc.getIp())){
 
            pe_piece.setReservedBy( null );
          }
        }
      }
    }

    adapter.removePeer(pc)//async downloadmanager notification

    //sync peermanager notification
    final ArrayList peer_manager_listeners = peer_manager_listeners_cow;

    for( int i=0; i < peer_manager_listeners.size(); i++ ) {
      ((PEPeerManagerListener)peer_manager_listeners.get(i)).peerRemoved( this, pc );
    }
  }

  /** Don't pass a null to this method. All activations of pieces must go through here.
   * @param piece PEPiece invoked; notifications of it's invocation need to be done
   * @param pieceNumber of the PEPiece
   */
  public void addPiece(final PEPiece piece, final int pieceNumber, PEPeer for_peer )
  {
    addPiece( piece, pieceNumber, false, for_peer );
  }

  protected void addPiece(final PEPiece piece, final int pieceNumber, final boolean force_add, PEPeer for_peer )
  {
    pePieces[pieceNumber] =(PEPieceImpl)piece;
    nbPiecesActive++;
    if ( is_running || force_add ){
      // deal with possible piece addition by scheduler loop after closdown started
      adapter.addPiece(piece);
    }
   
    final ArrayList peer_manager_listeners = peer_manager_listeners_cow;

    for( int i=0; i < peer_manager_listeners.size(); i++ ) {
      try{
        ((PEPeerManagerListener)peer_manager_listeners.get(i)).pieceAdded(this, piece, for_peer );
           
      }catch( Throwable e ){
       
        Debug.printStackTrace(e);
      }
    }
  }

  /** Sends messages to listeners that the piece is no longer active.  All closing
   * out (deactivation) of pieces must go through here. The piece will be null upon return.
   * @param pePiece PEPiece to remove
   * @param pieceNumber int
   */
  public void removePiece(PEPiece pePiece, int pieceNumber) {
    if ( pePiece != null ){
    adapter.removePiece(pePiece);
    } else {
      pePiece = pePieces[pieceNumber];
    }
    pePieces[pieceNumber] =null;
    nbPiecesActive--;
   
    if (pePiece == null) {
      Debug.outNoStack("Trying to remove piece " + pieceNumber + " when piece is null");
      return;
    }
   
    final ArrayList peer_manager_listeners = peer_manager_listeners_cow;

    for( int i=0; i < peer_manager_listeners.size(); i++ ) {
      try{
        ((PEPeerManagerListener)peer_manager_listeners.get(i)).pieceRemoved(this, pePiece );
           
      }catch( Throwable e ){
       
        Debug.printStackTrace(e);
      }
    }
  }

  public int getNbActivePieces()
  {
    return nbPiecesActive;
  }

  public String getElapsedTime() {
    return TimeFormatter.format((SystemTime.getCurrentTime() - _timeStarted) / 1000);
  }

//  Returns time started in ms
  public long getTimeStarted() {
    return _timeStarted;
  }

  public long getTimeStartedSeeding() {
    return _timeStartedSeeding;
  }

  private byte[] computeMd5Hash(DirectByteBuffer buffer)
  {      
    BrokenMd5Hasher md5   = new BrokenMd5Hasher();

    md5.reset();
    final int position = buffer.position(DirectByteBuffer.SS_DW);
    md5.update(buffer.getBuffer(DirectByteBuffer.SS_DW));
    buffer.position(DirectByteBuffer.SS_DW, position);
    ByteBuffer md5Result  = ByteBuffer.allocate(16);
    md5Result.position(0);
    md5.finalDigest( md5Result );

    final byte[] result =new byte[16];
    md5Result.position(0);
    for (int i =0; i <result.length; i++ )
    {
      result[i] = md5Result.get();
    }  

    return result;   
  }

  private void MD5CheckPiece(PEPiece piece, boolean correct)
  {
    final String[] writers =piece.getWriters();
    int offset = 0;
    for (int i =0; i <writers.length; i++ )
    {
      final int length =piece.getBlockSize(i);
      final String peer =writers[i];
      if (peer !=null)
      {
        DirectByteBuffer buffer =disk_mgr.readBlock(piece.getPieceNumber(), offset, length);

        if (buffer !=null)
        {
          final byte[] hash =computeMd5Hash(buffer);
          buffer.returnToPool();
          buffer = null;
          piece.addWrite(i,peer,hash,correct)
        }
      }
      offset += length;
    }       
  }

  public void checkCompleted(DiskManagerCheckRequest request, boolean passed)
  {
    if ( TEST_PERIODIC_SEEDING_SCAN_FAIL_HANDLING && ((Integer) request.getUserData()).intValue() == CHECK_REASON_SEEDING_CHECK ){
     
      passed = false
    }
   
    try
    {
      piece_check_result_list_mon.enter();

      piece_check_result_list.add(new Object[]{request, new Integer( passed?1:0 )});
    } finally
    {
      piece_check_result_list_mon.exit();
    }
  }

  public void checkCancelled(DiskManagerCheckRequest request)
  {
    try
    {
      piece_check_result_list_mon.enter();

      piece_check_result_list.add(new Object[]{request, new Integer( 2 )});

    } finally
    {
      piece_check_result_list_mon.exit();
    }
  }

  public void checkFailed(DiskManagerCheckRequest request, Throwable cause)
  {
    try
    {
      piece_check_result_list_mon.enter();

      piece_check_result_list.add(new Object[]{request, new Integer( 0 )});

    } finally
    {
      piece_check_result_list_mon.exit();
    }   
  }

  public boolean needsMD5CheckOnCompletion(int pieceNumber)
  {
    final PEPieceImpl piece = pePieces[pieceNumber];   
    if(piece == null)
      return false;
    return piece.getPieceWrites().size() > 0;
  }

  private void processPieceCheckResult(DiskManagerCheckRequest request, int outcome)
  {
    final int check_type =((Integer) request.getUserData()).intValue();

    try{

      final int pieceNumber =request.getPieceNumber();

      // System.out.println( "processPieceCheckResult(" + _finished + "/" + recheck_on_completion + "):" + pieceNumber +
      // "/" + piece + " - " + result );

      // passed = 1, failed = 0, cancelled = 2

      if (check_type ==CHECK_REASON_COMPLETE){
        // this is a recheck, so don't send HAVE msgs
       
        if ( outcome == 0 ){
       
          // piece failed; restart the download afresh
          Debug.out(getDisplayName() + ": Piece #" +pieceNumber +" failed final re-check. Re-downloading...");

          if (!restart_initiated){

            restart_initiated  = true;
            adapter.restartDownload(true);
          }
        }

        return;

      }else if ( check_type == CHECK_REASON_SEEDING_CHECK || check_type == CHECK_REASON_BAD_PIECE_CHECK ){

        if ( outcome == 0 ){
         
          if ( check_type == CHECK_REASON_SEEDING_CHECK ){

          Debug.out(getDisplayName() + "Piece #" +pieceNumber +" failed recheck while seeding. Re-downloading...");
 
          }else{
           
            Debug.out(getDisplayName() + "Piece #" +pieceNumber +" failed recheck after being reported as bad. Re-downloading...");
          }

          Logger.log(new LogAlert(this, LogAlert.REPEATABLE, LogAlert.AT_ERROR,
              "Download '" + getDisplayName() + "': piece " + pieceNumber
                  + " has been corrupted, re-downloading"));

          if ( !restart_initiated ){

            restart_initiated = true;

            adapter.restartDownload(true);
          }
        }

        return;
      }

      // piece can be null when running a recheck on completion
        // actually, give the above code I don't think this is true anymore...
     
      final PEPieceImpl pePiece =pePieces[pieceNumber];

      if ( outcome == 1 ){
     
      //  the piece has been written correctly
        
        try{
          if (pePiece !=null){
         
            if (needsMD5CheckOnCompletion(pieceNumber)){
            MD5CheckPiece(pePiece, true);
            }

            final List list =pePiece.getPieceWrites();
           
            if (list.size() >0 ){
           
              //For each Block
              for (int i =0; i <pePiece.getNbBlocks(); i++ ){

                //System.out.println("Processing block " + i);
                //Find out the correct hash
                final List listPerBlock =pePiece.getPieceWrites(i);
                byte[] correctHash = null;
                //PEPeer correctSender = null;
                Iterator iterPerBlock = listPerBlock.iterator();
                while (iterPerBlock.hasNext())
                {
                  final PEPieceWriteImpl write =(PEPieceWriteImpl) iterPerBlock.next();
                  if (write.isCorrect())
                  {
                    correctHash = write.getHash();
                    //correctSender = write.getSender();
                  }
                }
                //System.out.println("Correct Hash " + correctHash);
                //If it's found                      
                if (correctHash !=null)
                {
                  iterPerBlock = listPerBlock.iterator();
                  while (iterPerBlock.hasNext())
                  {
                    final PEPieceWriteImpl write =(PEPieceWriteImpl) iterPerBlock.next();
                    if (!Arrays.equals(write.getHash(), correctHash))
                    {
                      //Bad peer found here
                      badPeerDetected(write.getSender(),pieceNumber);
                    }
                  }
                }             
              }           
            }
          }
        }finally{
            // regardless of any possible failure above, tidy up correctly
         
        removePiece(pePiece, pieceNumber);

        // send all clients a have message
        sendHave(pieceNumber)//XXX: if Done isn't set yet, might refuse to send this piece
        }
      }else if ( outcome == 0 ){
   
        //    the piece is corrupt
         
        if ( pePiece != null ){

          try{
          MD5CheckPiece(pePiece, false);

          final String[] writers =pePiece.getWriters();
          final List uniqueWriters =new ArrayList();
          final int[] writesPerWriter =new int[writers.length];
          for (int i =0; i <writers.length; i++ )
          {
            final String writer =writers[i];
            if (writer !=null)
            {
              int writerId = uniqueWriters.indexOf(writer);
              if (writerId ==-1)
              {
                uniqueWriters.add(writer);
                writerId = uniqueWriters.size() - 1;
              }
              writesPerWriter[writerId]++;
            }
          }
          final int nbWriters =uniqueWriters.size();
          if (nbWriters ==1)
          {
            //Very simple case, only 1 peer contributed for that piece,
            //so, let's mark it as a bad peer
             
              String  bad_ip = (String)uniqueWriters.get(0);
             
              PEPeerTransport bad_peer = getTransportFromAddress( bad_ip );
           
              if ( bad_peer != null ){
               
                bad_peer.sendBadPiece( pieceNumber );
              }
             
              badPeerDetected( bad_ip, pieceNumber);

            //and let's reset the whole piece
            pePiece.reset();
             
            }else if ( nbWriters > 1 ){
           
            int maxWrites = 0;
            String bestWriter =null;
             
              PEPeerTransport  bestWriter_transport = null;
 
              for (int i =0; i <uniqueWriters.size(); i++ ){
             
              final int writes =writesPerWriter[i];
               
                if (writes > maxWrites ){
               
                final String writer =(String) uniqueWriters.get(i);
                 
                  PEPeerTransport pt =getTransportFromAddress(writer);
                 
                  if pt !=null &&
                      pt.getReservedPieceNumbers() == null &&
                      !ip_filter.isInRange(writer, getDisplayName(),getTorrentHash())){
                 
                  bestWriter = writer;
                  maxWrites = writes;
                   
                    bestWriter_transport  = pt;
                }
              }
            }
             
              if ( bestWriter !=null ){
 
                pePiece.setReservedBy(bestWriter);
               
                bestWriter_transport.addReservedPieceNumber(pePiece.getPieceNumber());
               
                pePiece.setRequestable();
               
                for (int i =0; i <pePiece.getNbBlocks(); i++ ){
               
                // If the block was contributed by someone else
                 
                  if (writers[i] == null ||!writers[i].equals( bestWriter )){
                                     
                  pePiece.reDownloadBlock(i);
                }
              }
              }else{
             
              //In all cases, reset the piece
              pePiece.reset();
            }           
            }else{
           
            //In all cases, reset the piece
            pePiece.reset();
          }


          //if we are in end-game mode, we need to re-add all the piece chunks
          //to the list of chunks needing to be downloaded
          piecePicker.addEndGameChunks(pePiece);
          _stats.hashFailed(pePiece.getLength());
         
          }catch( Throwable e ){
           
            Debug.printStackTrace(e);
           
              // anything craps out in the above code, ensure we tidy up
           
            pePiece.reset();
        }
        }else{
         
            // no active piece for some reason, clear down DM piece anyway
         
          Debug.out(getDisplayName() + "Piece #" +pieceNumber +" failed check and no active piece, resetting..." );

          dm_pieces[pieceNumber].reset();
        }
      }else{
       
        // cancelled, download stopped
      }
    }finally{
     
      if (check_type ==CHECK_REASON_SCAN ){
        rescan_piece_time  = SystemTime.getCurrentTime();
      }

      if (!seeding_mode ){
        checkFinished( false );  
    }
  }
  }

 
  private void badPeerDetected(String ip,  int piece_number )
  {
    boolean hash_fail = piece_number >= 0;
   
      // note that peer can be NULL but things still work in the main
   
    PEPeerTransport peer = getTransportFromAddress(ip);
   
    if ( hash_fail && peer != null ){
     
      Iterator<PEPeerManagerListener> it = peer_manager_listeners_cow.iterator();

      while( it.hasNext()){
       
        try{
          it.next().peerSentBadData( this, peer, piece_number );
             
        }catch( Throwable e ){
         
          Debug.printStackTrace(e);
        }
      }
    }
    // Debug.out("Bad Peer Detected: " + peerIP + " [" + peer.getClient() + "]");

    IpFilterManager filter_manager =IpFilterManagerFactory.getSingleton();

    //Ban fist to avoid a fast reco of the bad peer
   
    int nbWarnings = filter_manager.getBadIps().addWarningForIp(ip);

    boolean  disconnect_peer = false;

    // no need to reset the bad chunk count as the peer is going to be disconnected and
    // if it comes back it'll start afresh
   
    // warning limit only applies to hash-fails, discards cause immediate action
   
    if ( nbWarnings > WARNINGS_LIMIT ){
   
      if (COConfigurationManager.getBooleanParameter("Ip Filter Enable Banning")){
     
        // if a block-ban occurred, check other connections
       
        if (ip_filter.ban(ip, getDisplayName(), false )){
       
          checkForBannedConnections();
        }

        // Trace the ban
        if (Logger.isEnabled()){
          Logger.log(new LogEvent(peer, LOGID, LogEvent.LT_ERROR, ip +" : has been banned and won't be able "
            +"to connect until you restart azureus"));
        }
       
        disconnect_peer = true;
      }
    }else if ( !hash_fail ){
     
        // for failures due to excessive discard we boot the peer anyway
     
      disconnect_peer  = true;
     
    }
   
    if ( disconnect_peer ){
     
      if ( peer != null ){

          final int ps =peer.getPeerState();

          // might have been through here very recently and already started closing
          // the peer (due to multiple bad blocks being found from same peer when checking piece)
          if (!(ps ==PEPeer.CLOSING ||ps ==PEPeer.DISCONNECTED))
          {
            //  Close connection
          closeAndRemovePeer(peer, "has sent too many " + (hash_fail?"bad pieces":"discarded blocks") + ", " +WARNINGS_LIMIT +" max.", true);       
        }
      }   
    }
  }

  public PEPiece[] getPieces()
  {
    return pePieces;
  }

  public PEPiece getPiece(int pieceNumber)
  {
    return pePieces[pieceNumber];
  }

  public PEPeerStats
  createPeerStats(
      PEPeer  owner )
  {
    return( new PEPeerStatsImpl( owner ));
  }


  public DiskManagerReadRequest
  createDiskManagerRequest(
      int pieceNumber,
      int offset,
      int length )
  {
    return( disk_mgr.createReadRequest( pieceNumber, offset, length ));
  }

  public boolean
  requestExists(
      String      peer_ip,
      int        piece_number,
      int        offset,
      int        length )
  {
    ArrayList peer_transports = peer_transports_cow;

    DiskManagerReadRequest  request = null;

    for (int i=0; i < peer_transports.size(); i++) {

      PEPeerTransport conn = (PEPeerTransport)peer_transports.get( i );

      if ( conn.getIp().equals( peer_ip )){

        if ( request == null ){

          request = createDiskManagerRequest( piece_number, offset, length );         
        }

        if ( conn.getRequestIndex( request ) != -1 ){

          return( true );
        }
      }
    }

    return( false );
  }

  public boolean
  seedPieceRecheck()
  {
    if ( !( enable_seeding_piece_rechecks || isSeeding())){

      return( false );
    }

    int  max_reads     = 0;
    int  max_reads_index = 0;

    if ( TEST_PERIODIC_SEEDING_SCAN_FAIL_HANDLING ){
     
      max_reads_index = (int)(Math.random()*dm_pieces.length);;
      max_reads = dm_pieces[ max_reads_index ].getNbBlocks()*3;

    }else{

      for (int i=0;i<dm_pieces.length;i++){
 
          // skip dnd pieces
       
        DiskManagerPiece  dm_piece = dm_pieces[i];
       
        if ( !dm_piece.isDone()){
         
          continue;
        }
       
        int  num = dm_piece.getReadCount()&0xffff;
 
        if ( num > SEED_CHECK_WAIT_MARKER ){
 
          // recently been checked, skip for a while
 
            num--;
 
          if ( num == SEED_CHECK_WAIT_MARKER ){
 
            num = 0;
          }
 
          dm_piece.setReadCount((short)num);
 
        }else{
 
          if ( num > max_reads ){
 
            max_reads     = num;
            max_reads_index = i;
          }
        }
      }
    }
   
    if ( max_reads > 0 ){

      DiskManagerPiece  dm_piece = dm_pieces[ max_reads_index ];

      // if the piece has been read 3 times (well, assuming each block is read once,
      // which is obviously wrong, but...)

      if ( max_reads >= dm_piece.getNbBlocks() * 3 ){

        DiskManagerCheckRequest  req = disk_mgr.createCheckRequest( max_reads_index, new Integer( CHECK_REASON_SEEDING_CHECK ) );

        req.setAdHoc( true );

        req.setLowPriority( true );

        if (Logger.isEnabled())
          Logger.log(new LogEvent(disk_mgr.getTorrent(), LOGID,
              "Rechecking piece " + max_reads_index + " while seeding as most active"));

        disk_mgr.enqueueCheckRequest( req, this );

        dm_piece.setReadCount((short)65535);

        // clear out existing, non delayed pieces so we start counting piece activity
        // again

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

          if ( i != max_reads_index ){

            int  num = dm_pieces[i].getReadCount()&0xffff;

            if ( num < SEED_CHECK_WAIT_MARKER ){

              dm_pieces[i].setReadCount((short)0 );
            }
          }
        }

        return( true );
      }
    }

    return( false );
  }

  public void
  addListener(
      PEPeerManagerListener  l )
  {
    try{
      this_mon.enter();

      //copy on write
      final ArrayList peer_manager_listeners = new ArrayList( peer_manager_listeners_cow.size() + 1 );     
      peer_manager_listeners.addAll( peer_manager_listeners_cow );
      peer_manager_listeners.add( l );
      peer_manager_listeners_cow = peer_manager_listeners;

    }finally{

      this_mon.exit();
    }
  }

  public void
  removeListener(
      PEPeerManagerListener  l )
  {
    try{
      this_mon.enter();

      //copy on write
      final ArrayList peer_manager_listeners = new ArrayList( peer_manager_listeners_cow );
      peer_manager_listeners.remove( l );
      peer_manager_listeners_cow = peer_manager_listeners;

    }finally{

      this_mon.exit();
    }
  }


  public void
  parameterChanged(
      String parameterName)
  {  
    if ( parameterName.equals("Ip Filter Enabled")){

      checkForBannedConnections();
    }
  }

  private void
  checkForBannedConnections()
  {
    if ( ip_filter.isEnabled()){  //if ipfiltering is enabled, remove any existing filtered connections     
      ArrayList to_close = null;

      final ArrayList  peer_transports = peer_transports_cow;
   
    String name   = getDisplayName();
    byte[]  hash  = getTorrentHash();
   
      for (int i=0; i < peer_transports.size(); i++) {
        final PEPeerTransport conn = (PEPeerTransport)peer_transports.get( i );

        if ( ip_filter.isInRange( conn.getIp(), name, hash )) {         
          if( to_close == null to_close = new ArrayList();
          to_close.add( conn );
        }
      }

      if( to_close != null ) {   
        for( int i=0; i < to_close.size(); i++ ) {       
          closeAndRemovePeer( (PEPeerTransport)to_close.get(i), "IPFilter banned IP address", true );
        }
      }
    }
  }

  public boolean
  isSeeding()
  {
    return( seeding_mode );
  }

  public boolean isInEndGameMode() {
    return piecePicker.isInEndGameMode();
  }

  public boolean isSuperSeedMode() {
    return superSeedMode;
  }

  public boolean
  canToggleSuperSeedMode()
  {
    if ( superSeedMode ){

      return( true );
    }

    return( superSeedPieces == null && this.getRemaining() ==);
  }

  public void
  setSuperSeedMode(
      boolean _superSeedMode)
  {
    if ( _superSeedMode == superSeedMode ){

      return;
    }

    boolean  kick_peers = false;

    if ( _superSeedMode ){

      if ( superSeedPieces == null && this.getRemaining() ==){

        superSeedMode = true;

        initialiseSuperSeedMode();

        kick_peers  = true;
      }
    }else{

      superSeedMode = false;

      kick_peers  = true;
    }

    if ( kick_peers ){

      // turning on/off super-seeding, gotta kick all connected peers so they get the
      // "right" bitfield

      ArrayList peer_transports = peer_transports_cow;

      for (int i=0; i < peer_transports.size(); i++) {

        PEPeerTransport conn = (PEPeerTransport)peer_transports.get( i );

        closeAndRemovePeer( conn, "Turning on super-seeding", false );
      }
    }
  }   

  private void
  initialiseSuperSeedMode()
  {
    superSeedPieces = new SuperSeedPiece[_nbPieces];
    for(int i = 0 ; i < _nbPieces ; i++) {
      superSeedPieces[i] = new SuperSeedPiece(this,i);
    }
  }

  private void updatePeersInSuperSeedMode() {
    if(!superSeedMode) {
      return;
   

    //Refresh the update time in case this is needed
    for(int i = 0 ; i < superSeedPieces.length ; i++) {
      superSeedPieces[i].updateTime();
    }

    //Use the same number of announces than unchoke
    int nbUnchoke = adapter.getMaxUploads();
    if(superSeedModeNumberOfAnnounces >= 2 * nbUnchoke)
      return;


    //Find an available Peer
    PEPeer selectedPeer = null;
    List sortedPeers = null;

    final ArrayList  peer_transports = peer_transports_cow;

    sortedPeers = new ArrayList(peer_transports.size());
    Iterator iter = peer_transports.iterator();
    while(iter.hasNext()) {
      sortedPeers.add(new SuperSeedPeer((PEPeer)iter.next()));
    }     

    Collections.sort(sortedPeers);
    iter = sortedPeers.iterator();
    while(iter.hasNext()) {
      final PEPeer peer = ((SuperSeedPeer)iter.next()).peer;
      if((peer.getUniqueAnnounce() == -1) && (peer.getPeerState() == PEPeer.TRANSFERING)) {
        selectedPeer = peer;
        break;
      }
    }     

    if(selectedPeer == null ||selectedPeer.getPeerState() >=PEPeer.CLOSING)
      return;

    if(selectedPeer.getUploadHint() == 0) {
      //Set to infinite
      selectedPeer.setUploadHint(Constants.CRAPPY_INFINITY_AS_INT);
    }

    //Find a piece
    boolean found = false;
    SuperSeedPiece piece = null;
    boolean loopdone = false// add loop status
   
    while(!found) {
      piece = superSeedPieces[superSeedModeCurrentPiece];
      if(piece.getLevel() > 0) {
        piece = null;
        superSeedModeCurrentPiece++;
        if(superSeedModeCurrentPiece >= _nbPieces) {
          superSeedModeCurrentPiece = 0;
         
          if (loopdone) {  // if already been here, has been full loop through pieces, quit
            //quit superseed mode
            superSeedMode = false;
            closeAndRemoveAllPeers( "quiting SuperSeed mode", true );
            return;
          }
          else {                                                                                 
            // loopdone==false --> first time here --> go through the pieces                   
            // for a second time to check if reserved pieces have got freed due to peers leaving
            loopdone = true;                                                                   
          }       
        }
      } else {
        found = true;
      }       
    }

    if(piece == null) {
      return;
    }

    //If this peer already has this piece, return (shouldn't happen)
    if(selectedPeer.isPieceAvailable(piece.getPieceNumber())) {
      return;
    }

    selectedPeer.setUniqueAnnounce(piece.getPieceNumber());
    superSeedModeNumberOfAnnounces++;
    piece.pieceRevealedToPeer();
    ((PEPeerTransport)selectedPeer).sendHave(piece.getPieceNumber());   
  }

  public void updateSuperSeedPiece(PEPeer peer,int pieceNumber) {
    // currently this gets only called from bitfield scan function in PEPeerTransportProtocol
    if (!superSeedMode)
      return;
    superSeedPieces[pieceNumber].peerHasPiece(null);
    if(peer.getUniqueAnnounce() == pieceNumber)
    {
      peer.setUniqueAnnounce(-1);
      superSeedModeNumberOfAnnounces--;       
    }
  }

  public boolean
  isExtendedMessagingEnabled()
  {
    return( adapter.isExtendedMessagingEnabled());
  }

  public boolean
  isPeerExchangeEnabled()
  {
    return( adapter.isPeerExchangeEnabled());
  }

  public LimitedRateGroup getUploadLimitedRateGroup() {  return upload_limited_rate_group;  }

  public LimitedRateGroup getDownloadLimitedRateGroup() {  return download_limited_rate_group;  }


  /** To retreive arbitrary objects against this object. */
  public Object
  getData(
      String key)
  {
    try{
      this_mon.enter();

      if (user_data == null) return null;

      return user_data.get(key);

    }finally{

      this_mon.exit();
    }
  }

  /** To store arbitrary objects against a control. */

  public void
  setData(
      String key,
      Object value)
  {
    try{
      this_mon.enter();

      if (user_data == null) {
        user_data = new HashMap();
      }
      if (value == null) {
        if (user_data.containsKey(key))
          user_data.remove(key);
      } else {
        user_data.put(key, value);
      }
    }finally{
      this_mon.exit();
    }
  }

  public int
  getConnectTimeout(
    int    ct_def )
  {
    if ( ct_def <= 0 ){
     
      return( ct_def );
    }
   
    if ( seeding_mode ){
     
        // seeding mode connections are already de-prioritised so nothing to do
     
      return( ct_def );
    }
   
    int max_sim_con = TCPConnectionManager.MAX_SIMULTANIOUS_CONNECT_ATTEMPTS;
   
      // high, let's not mess with things
   
    if ( max_sim_con >= 50 ){
     
      return( ct_def );
    }
   
      // we have somewhat limited outbound connection limits, see if it makes sense to
      // reduce the connect timeout to prevent connection stall due to a bunch getting
      // stuck 'connecting' for a long time and stalling us
   
    int  connected      = _seeds + _peers;
    int  connecting      = _tcpConnectingConnections;
    int queued        = _tcpPendingConnections;
   
    int  not_yet_connected   = peer_database.getDiscoveredPeerCount();
   
    int  max = getMaxConnections();
   
    int  potential = connecting + queued + not_yet_connected;
   
    /*
    System.out.println(
        "connected=" + connected +
        ", queued=" + queued +
        ", connecting=" + connecting +
        ", queued=" + queued +
        ", not_yet=" + not_yet_connected +
        ", max=" + max );
    */
   
      // not many peers -> don't amend
   
    int  lower_limit = max/4;
   
    if ( potential <= lower_limit || max == lower_limit ){
   
      return( ct_def );
    }
   
      // if we got lots of potential, use minimum delay
   
    final int MIN_CT = 7500;
   
    if ( potential >= max ){
     
      return( MIN_CT );
    }
 
      // scale between MIN and ct_def
   
    int pos   = potential - lower_limit;
    int  scale   = max - lower_limit;
     
    int res =  MIN_CT + ( ct_def - MIN_CT )*(scale - pos )/scale;
   
    // System.out.println( "scaled->" + res );
   
    return( res );
  }
 
  private void doConnectionChecks()
  {
    //every 1 second
    if ( mainloop_loop_count % MAINLOOP_ONE_SECOND_INTERVAL == 0 ){
      final ArrayList peer_transports = peer_transports_cow;

      int num_waiting_establishments = 0;

      int udp_connections = 0;

      for( int i=0; i < peer_transports.size(); i++ ) {
        final PEPeerTransport transport = (PEPeerTransport)peer_transports.get( i );

        //update waiting count
        final int state = transport.getConnectionState();
        if( state == PEPeerTransport.CONNECTION_PENDING || state == PEPeerTransport.CONNECTION_CONNECTING ) {
          num_waiting_establishments++;
        }

        if ( !transport.isTCP()){

          udp_connections++;
        }
      }

      int  allowed_seeds = getMaxSeedConnections();

      if ( allowed_seeds > 0 ){

        int  to_disconnect = _seeds - allowed_seeds;

        if ( to_disconnect > 0 ){

          // seeds are limited by people trying to get a reasonable upload by connecting
          // to leechers where possible. disconnect seeds from end of list to prevent
          // cycling of seeds

          for( int i=peer_transports.size()-1; i >= 0 && to_disconnect > 0; i-- ){

            final PEPeerTransport transport = (PEPeerTransport)peer_transports.get( i );

            if ( transport.isSeed()){

              closeAndRemovePeer( transport, "Too many seeds", false );

              to_disconnect--;
            }
          }
        }
      }

      //pass from storage to connector
      int allowed = getMaxNewConnectionsAllowed();

      if( allowed < 0 || allowed > 1000 allowed = 1000//ensure a very upper limit so it doesnt get out of control when using PEX

      if( adapter.isNATHealthy()) {  //if unfirewalled, leave slots avail for remote connections
        final int free = getMaxConnections() / 20//leave 5%
        allowed = allowed - free;
      }

      if( allowed > 0 ) {
        //try and connect only as many as necessary
       
        final int wanted = TCPConnectionManager.MAX_SIMULTANIOUS_CONNECT_ATTEMPTS - num_waiting_establishments;
       
        if( wanted > allowed ) {
          num_waiting_establishments += wanted - allowed;
        }

        int  remaining = allowed;
       
        int  tcp_remaining = TCPNetworkManager.getSingleton().getConnectDisconnectManager().getMaxOutboundPermitted();
       
        int  udp_remaining = UDPNetworkManager.getSingleton().getConnectionManager().getMaxOutboundPermitted();

        //load stored peer-infos to be established
        while( num_waiting_establishments < TCPConnectionManager.MAX_SIMULTANIOUS_CONNECT_ATTEMPTS && ( tcp_remaining > 0 || udp_remaining > 0 )){         
          if( !is_running break;         

          final PeerItem item = peer_database.getNextOptimisticConnectPeer();

          if( item == null || !is_running break;

          final PeerItem self = peer_database.getSelfPeer();
          if( self != null && self.equals( item ) ) {
            continue;
          }

          if( !isAlreadyConnected( item ) ) {
            final String source = PeerItem.convertSourceString( item.getSource() );

            final boolean use_crypto = item.getHandshakeType() == PeerItemFactory.HANDSHAKE_TYPE_CRYPTO;

            int  tcp_port = item.getTCPPort();
            int  udp_port = item.getUDPPort();
           
            if ( udp_port == 0 && udp_probe_enabled ){
             
                // for probing we assume udp port same as tcp
             
              udp_port = tcp_port;
            }
           
            boolean  prefer_udp_overall = prefer_udp || prefer_udp_default;
           
            if ( prefer_udp_overall && udp_port == 0 ){
           
                // see if we have previous record of this address as udp connectable
             
              byte[]  address = item.getIP().getBytes();
             
              BloomFilter  bloom = prefer_udp_bloom;
             
              if ( bloom != null && bloom.contains( address )){
                               
                udp_port = tcp_port;
              }
            }
           
            boolean  tcp_ok = TCPNetworkManager.TCP_OUTGOING_ENABLED && tcp_port > 0 && tcp_remaining > 0;
            boolean udp_ok = UDPNetworkManager.UDP_OUTGOING_ENABLED && udp_port > 0 && udp_remaining > 0;

            if ( tcp_ok && !( prefer_udp_overall && udp_ok )){

              if ( makeNewOutgoingConnection( source, item.getAddressString(), tcp_port, udp_port, true, use_crypto, item.getCryptoLevel(), null) == null) {
               
                tcp_remaining--;

                num_waiting_establishments++;
                remaining--;
              }
            }else if ( udp_ok ){

              if ( makeNewOutgoingConnection( source, item.getAddressString(), tcp_port, udp_port, false, use_crypto, item.getCryptoLevel(), null) == null) {
                 
                udp_remaining--;

                num_waiting_establishments++;

                remaining--;
              }
            }
          }         
        }

        if (   UDPNetworkManager.UDP_OUTGOING_ENABLED &&
            remaining > 0 &&
            udp_remaining > 0 &&
            udp_connections < MAX_UDP_CONNECTIONS ){
         
          doUDPConnectionChecks( remaining );
        }
      }
    }

    //every 5 seconds
    if ( mainloop_loop_count % MAINLOOP_FIVE_SECOND_INTERVAL == 0 ) {
      final ArrayList peer_transports = peer_transports_cow;

      for( int i=0; i < peer_transports.size(); i++ ) {
        final PEPeerTransport transport = (PEPeerTransport)peer_transports.get( i );

        //check for timeouts
        if( transport.doTimeoutChecks() )  continue;

        //keep-alive check
        transport.doKeepAliveCheck();

        //speed tuning check
        transport.doPerformanceTuningCheck();
      }
    }

    // every 10 seconds check for connected + banned peers
    if ( mainloop_loop_count % MAINLOOP_TEN_SECOND_INTERVAL == 0 )
    {
      final long  last_update = ip_filter.getLastUpdateTime();
      if ( last_update != ip_filter_last_update_time )
      {
        ip_filter_last_update_time  = last_update;
        checkForBannedConnections();
      }
    }

    //every 30 seconds
    if ( mainloop_loop_count % MAINLOOP_THIRTY_SECOND_INTERVAL == 0 ) {
      //if we're at our connection limit, time out the least-useful
      //one so we can establish a possibly-better new connection
      optimisticDisconnectCount = 0;
      if( getMaxNewConnectionsAllowed() == 0 ) {  //we've reached limit       
        doOptimisticDisconnect( false, false );
      }
    }


    //sweep over all peers in a 60 second timespan
    float percentage = ((mainloop_loop_count % MAINLOOP_SIXTY_SECOND_INTERVAL) + 1F) / (1F *MAINLOOP_SIXTY_SECOND_INTERVAL);
    int goal;
    if(mainloop_loop_count % MAINLOOP_SIXTY_SECOND_INTERVAL == 0)
    {
      goal = 0;
      sweepList = peer_transports_cow;
    } else
      goal = (int)Math.floor(percentage * sweepList.size());
   
    for( int i=nextPEXSweepIndex; i < goal && i < sweepList.size(); i++) {
      //System.out.println(mainloop_loop_count+" %:"+percentage+" start:"+nextPEXSweepIndex+" current:"+i+" <"+goal+"/"+sweepList.size());
      final PEPeerTransport peer = (PEPeerTransport)sweepList.get( i );
      peer.updatePeerExchange();
    }

    nextPEXSweepIndex = goal;
  }
 
  private void
  doUDPConnectionChecks(
    int    number )
  {
    List  new_connections = null;
   
    try{
      peer_transports_mon.enter();

      long  now = SystemTime.getCurrentTime();
     
      if ( udp_reconnects.size() > 0 && now - last_udp_reconnect >= UDP_RECONNECT_MIN_MILLIS ){
       
        last_udp_reconnect = now;
       
        Iterator it = udp_reconnects.values().iterator();
       
        PEPeerTransport  peer = (PEPeerTransport)it.next();

        it.remove();

        if (Logger.isEnabled()){
          Logger.log(new LogEvent(this, LOGID, LogEvent.LT_INFORMATION, "Reconnecting to previous failed peer " + peer.getPeerItemIdentity().getAddressString()));
        }

        if ( new_connections == null ){
         
          new_connections = new ArrayList();
        }
       
        new_connections.add( peer );

        number--;
       
        if ( number <= 0 ){
         
          return;
        }
      }
     
      if ( pending_nat_traversals.size() == 0 ){

        return;
      }

      int max = MAX_UDP_TRAVERSAL_COUNT;

      // bigger the swarm, less chance of doing it

      if ( seeding_mode ){

        if ( _peers > 8 ){

          max = 0;

        }else{

          max = 1;
        }
      }else if ( _seeds > 8 ){

        max = 0;

      }else if ( _seeds > 4 ){

        max = 1;
      }

      int  avail = max - udp_traversal_count;

      int  to_do = Math.min( number, avail );

      Iterator  it = pending_nat_traversals.values().iterator();

      while( to_do > 0 && it.hasNext()){

        to_do--;

        final PEPeerTransport  peer = (PEPeerTransport)it.next();

        it.remove();

        PeerNATTraverser.getSingleton().create(
            this,
            new InetSocketAddress( peer.getPeerItemIdentity().getAddressString(), peer.getPeerItemIdentity().getUDPPort() ),
            new PeerNATTraversalAdapter()
            {
              private boolean  done;

              public void
              success(
                  InetSocketAddress  target )
              {
                complete();
               
                PEPeerTransport newTransport = peer.reconnect(true, false);
               
                if( newTransport != null ){
                 
                  newTransport.setData(PEER_NAT_TRAVERSE_DONE_KEY, "");
                }
              }

              public void
              failed()
              {
                complete();
              }

              protected void
              complete()
              {
                try{
                  peer_transports_mon.enter();

                  if ( !done ){

                    done = true;

                    udp_traversal_count--;
                  }
                }finally{

                  peer_transports_mon.exit();
                }
              }
            });

        udp_traversal_count++;
      }
    }finally{

      peer_transports_mon.exit();
     
      if ( new_connections != null ){
       
        for (int i=0;i<new_connections.size();i++){
     
          PEPeerTransport  peer_item = (PEPeerTransport)new_connections.get(i);

            // don't call when holding monitor - deadlock potential
          peer_item.reconnect(true, false);

        }
      }
    }
  }
 
  // counter is reset every 30s by doConnectionChecks()
  private int optimisticDisconnectCount = 0;

  public boolean doOptimisticDisconnect( boolean  pending_lan_local_peer, boolean force )
  {
   
    final ArrayList peer_transports = peer_transports_cow;
    PEPeerTransport max_transport = null;
    PEPeerTransport max_seed_transport    = null;
    PEPeerTransport max_non_lan_transport   = null;

    long max_time = 0;
    long max_seed_time     = 0;
    long max_non_lan_time  = 0;
   

    List<Long> activeConnectionTimes = new ArrayList<Long>(peer_transports.size());

    int  lan_peer_count  = 0;

    for( int i=0; i < peer_transports.size(); i++ ) {
      final PEPeerTransport peer = (PEPeerTransport)peer_transports.get( i );

      if( peer.getConnectionState() == PEPeerTransport.CONNECTION_FULLY_ESTABLISHED ) {

        final long timeSinceConnection =peer.getTimeSinceConnectionEstablished();
        final long timeSinceSentData =peer.getTimeSinceLastDataMessageSent();       
       
        activeConnectionTimes.add(timeSinceConnection);
       
        long peerTestTime = 0;
        if( seeding_mode){
          if( timeSinceSentData != -1 )
            peerTestTime = timeSinceSentData;  //ensure we've sent them at least one data message to qualify for drop
        }else{
          final long timeSinceGoodData =peer.getTimeSinceGoodDataReceived();
         

          if( timeSinceGoodData == -1 )
            peerTestTime +=timeSinceConnection;   //never received
          else
            peerTestTime +=timeSinceGoodData;

          // try to drop unInteresting in favor of Interesting connections
          if (!peer.isInteresting())
          {
            if (!peer.isInterested())   // if mutually unInterested, really try to drop the connection
              peerTestTime +=timeSinceConnection +timeSinceSentData;   // if we never sent, it will subtract 1, which is good
            else
              peerTestTime +=(timeSinceConnection -timeSinceSentData); // try to give interested peers a chance to get data

            peerTestTime *=2;
          }

          peerTestTime +=peer.getSnubbedTime();
        }

        if( !peer.isIncoming() ){
          peerTestTime = peerTestTime * 2;   //prefer to drop a local connection, to make room for more remotes
        }

        if ( peer.isLANLocal()){

          lan_peer_count++;

        }else{

          if( peerTestTime > max_non_lan_time ) {
            max_non_lan_time = peerTestTime;
            max_non_lan_transport = peer;
          }
        }



        // anti-leech checks
        if( !seeding_mode ) {

          // remove long-term snubbed peers with higher probability
          peerTestTime += peer.getSnubbedTime();
          if(peer.getSnubbedTime() > 2*60) {peerTestTime*=1.5;}

          PEPeerStats pestats = peer.getStats();
          // everybody has deserverd a chance of half an MB transferred data
          if(pestats.getTotalDataBytesReceived()+pestats.getTotalDataBytesSent() > 1024*512 ) {
            boolean goodPeer = true;
           
            // we don't like snubbed peers with a negative gain
            if( peer.isSnubbed() && pestats.getTotalDataBytesReceived() < pestats.getTotalDataBytesSent() ) {
              peerTestTime *= 1.5;
              goodPeer = false;
            }
            // we don't like peers with a very bad ratio (10:1)
            if( pestats.getTotalDataBytesSent() > pestats.getTotalDataBytesReceived() * 10 ) {
              peerTestTime *= 2;
              goodPeer = false;
            }
            // modify based on discarded : overall downloaded ratio
            if( pestats.getTotalDataBytesReceived() > 0 && pestats.getTotalBytesDiscarded() > 0 ) {
              peerTestTime = (long)(peerTestTime *( 1.0+((double)pestats.getTotalBytesDiscarded()/(double)pestats.getTotalDataBytesReceived())));
            }
           
            // prefer peers that do some work, let the churn happen with peers that did nothing
            if(goodPeer)
              peerTestTime *= 0.7;             
          }
        }


        if( peerTestTime > max_time ) {
          max_time = peerTestTime;
          max_transport = peer;
        }

        if ( peer.isSeed() || peer.isRelativeSeed()){

          if( peerTestTime > max_seed_time ) {
            max_seed_time = peerTestTime;
            max_seed_transport = peer;
          }
        }
      }
    }
   
    long medianConnectionTime;
   
    if ( activeConnectionTimes.size() > 0 ){
      Collections.sort(activeConnectionTimes);
      medianConnectionTime = activeConnectionTimes.get(activeConnectionTimes.size()/2);
    }else{
      medianConnectionTime = 0;
    }
   
    // allow 1 disconnect every 30s per 30 peers; 2 at least every 30s
    int maxOptimistics = Math.max(getMaxConnections()/30,2);
   
    // avoid unnecessary churn, e.g.
    if(!pending_lan_local_peer && !force && optimisticDisconnectCount >= maxOptimistics && medianConnectionTime < 5*60*1000)
      return false;
   
   
    // don't boot lan peers if we can help it (unless we have a few of them)

    if ( max_transport != null ){

      final int LAN_PEER_MAX  = 4;

      if ( max_transport.isLANLocal() && lan_peer_count < LAN_PEER_MAX && max_non_lan_transport != null ){

        // override lan local max with non-lan local max

        max_transport  = max_non_lan_transport;
        max_time    = max_non_lan_time;
      }

      // if we have a seed limit, kick seeds in preference to non-seeds

      if( getMaxSeedConnections() > 0 && max_seed_transport != null && max_time > 5*60*1000 ) {
        closeAndRemovePeer( max_seed_transport, "timed out by doOptimisticDisconnect()", true );
        optimisticDisconnectCount++;
        return true;
      }

      if( max_transport != null && max_time > 5 *60*1000 ) {  //ensure a 5 min minimum test time
        closeAndRemovePeer( max_transport, "timed out by doOptimisticDisconnect()", true );
        optimisticDisconnectCount++;
        return true;
      }

      // kick worst peers to accomodate lan peer

      if ( pending_lan_local_peer && lan_peer_count < LAN_PEER_MAX ){
        closeAndRemovePeer( max_transport, "making space for LAN peer in doOptimisticDisconnect()", true );
        optimisticDisconnectCount++;
        return true;
      }
     
      if ( force ){
               
        closeAndRemovePeer( max_transport, "force removal of worst peer in doOptimisticDisconnect()", true );
       
        return true;
      }
    }else if ( force ){
     
      if ( peer_transports.size() > 0 ){
       
        PEPeerTransport pt = (PEPeerTransport)peer_transports.get( new Random().nextInt( peer_transports.size()));
       
        closeAndRemovePeer( pt, "force removal of random peer in doOptimisticDisconnect()", true );
       
        return true;
      }
    }

    return false;
  }


  public PeerExchangerItem createPeerExchangeConnection( final PEPeerTransport base_peer ) {
    if( base_peer.getTCPListenPort() > 0 ) {  //only accept peers whose remote port is known
      final PeerItem peer =
        PeerItemFactory.createPeerItem( base_peer.getIp(),
            base_peer.getTCPListenPort(),
            PeerItemFactory.PEER_SOURCE_PEER_EXCHANGE,
            base_peer.getPeerItemIdentity().getHandshakeType(),
            base_peer.getUDPListenPort(),
            PeerItemFactory.CRYPTO_LEVEL_1,
            0 );

      return peer_database.registerPeerConnection( peer, new PeerExchangerItem.Helper(){
        public boolean
        isSeed()
        {
          return base_peer.isSeed()
        }
      });
    }

    return null;
  }



  private boolean isAlreadyConnected( PeerItem peer_id ) {
    final ArrayList peer_transports = peer_transports_cow;
    for( int i=0; i < peer_transports.size(); i++ ) {
      final PEPeerTransport peer = (PEPeerTransport)peer_transports.get( i );
      if( peer.getPeerItemIdentity().equals( peer_id ) )  return true;
    }
    return false;
  }


  public void peerVerifiedAsSelf( PEPeerTransport self ) {
    if( self.getTCPListenPort() > 0 ) {  //only accept self if remote port is known
      final PeerItem peer = PeerItemFactory.createPeerItem( self.getIp(), self.getTCPListenPort(),
          PeerItem.convertSourceID( self.getPeerSource() ), self.getPeerItemIdentity().getHandshakeType(), self.getUDPListenPort(),PeerItemFactory.CRYPTO_LEVEL_CURRENT, 0 );
      peer_database.setSelfPeer( peer );
    }
  }

  public boolean
  canIPBeBanned(
      String ip )
  {
    return true;
  }
 
  public boolean
  canIPBeBlocked(
    String  ip,
    byte[]  torrent_hash )
  {
    return true;
  }
 
  public long
  getHiddenBytes()
  {
    if ( hidden_piece < 0 ){
     
      return( 0 );
    }
   
    return( dm_pieces[hidden_piece].getLength());
  }
 
  public int
  getHiddenPiece()
  {
    return( hidden_piece );
  }
 
  public void IPBlockedListChanged(IpFilter filter) {
    Iterator  it = peer_transports_cow.iterator();
   
    String  name   = getDisplayName();
    byte[]  hash  = getTorrentHash();
   
    while( it.hasNext()){
      try {
        PEPeerTransport  peer = (PEPeerTransport)it.next();
       
        if (filter.isInRange(peer.getIp(), name, hash )) {
          peer.closeConnection( "IP address blocked by filters" );
        }
      } catch (Exception e) {
      }
    }
  }

  public void IPBanned(BannedIp ip)
  {
    for (int i =0; i <_nbPieces; i++ )
    {
      if (pePieces[i] !=null)
        pePieces[i].reDownloadBlocks(ip.getIp());
    }
  }


  public int getAverageCompletionInThousandNotation()
  {
    final ArrayList peer_transports = peer_transports_cow;

    if (peer_transports !=null)
    {
      final long total =disk_mgr.getTotalLength();

      final int my_completion = total == 0
      ? 1000
          : (int) ((1000 * (total - disk_mgr.getRemainingExcludingDND())) / total);

      int sum = my_completion == 1000 ? 0 : my_completion;  //add in our own percentage if not seeding
      int num = my_completion == 1000 ? 0 : 1;

      for (int i =0; i <peer_transports.size(); i++ )
      {
        final PEPeer peer =(PEPeer) peer_transports.get(i);

        if (peer.getPeerState() ==PEPeer.TRANSFERING &&!peer.isSeed())
        {
          num++;
          sum += peer.getPercentDoneInThousandNotation();
        }
      }

      return num > 0 ? sum / num : 0;
    }

    return -1;
  }

  public int
  getMaxConnections()
  {
    return( adapter.getMaxConnections());
  }

  public int
  getMaxSeedConnections()
  {
    return( adapter.getMaxSeedConnections());
  }

  public int
  getMaxNewConnectionsAllowed()
  {
    final int  dl_max = getMaxConnections();

    final int  allowed_peers = PeerUtils.numNewConnectionsAllowed(getPeerIdentityDataID(), dl_max );

    return( allowed_peers );
  }
 
  public int getSchedulePriority() {
    return isSeeding() ? Integer.MAX_VALUE : adapter.getPosition();
  }

  public boolean
  hasPotentialConnections()
  {
    return( pending_nat_traversals.size() + peer_database.getDiscoveredPeerCount() > 0 );
  }
 
  public String
  getRelationText()
  {
    return( adapter.getLogRelation().getRelationText());
  }

  public Object[]
                getQueryableInterfaces()
  {
    return( adapter.getLogRelation().getQueryableInterfaces());
  }



  public PEPeerTransport getTransportFromIdentity( byte[] peer_id ) {
    final ArrayList  peer_transports = peer_transports_cow;       
    for( int i=0; i < peer_transports.size(); i++ ) {
      final PEPeerTransport conn = (PEPeerTransport)peer_transports.get( i );     
      if( Arrays.equals( peer_id, conn.getId() ) )   return conn;
    }
    return null;
  }


  /* peer item is not reliable for general use
  public PEPeerTransport getTransportFromPeerItem(PeerItem peerItem)
  {
    ArrayList peer_transports =peer_transports_cow;
    for (int i =0; i <peer_transports.size(); i++)
    {
      PEPeerTransport pt =(PEPeerTransport) peer_transports.get(i);
      if (pt.getPeerItemIdentity().equals(peerItem))
        return pt;
    }
    return null;
  }
   */

  public PEPeerTransport getTransportFromAddress(String peer)
  {
    final ArrayList peer_transports =peer_transports_cow;
    for (int i =0; i <peer_transports.size(); i++)
    {
      final PEPeerTransport pt =(PEPeerTransport) peer_transports.get(i);
      if (peer.equals(pt.getIp()))
        return pt;
    }
    return null;
  }

  // Snubbed peers accounting
  public void incNbPeersSnubbed()
  {
    nbPeersSnubbed++;
  }

  public void decNbPeersSnubbed()
  {
    nbPeersSnubbed--;
  }

  public void setNbPeersSnubbed(int n)
  {
    nbPeersSnubbed =n;
  }

  public int getNbPeersSnubbed()
  {
    return nbPeersSnubbed;
  }
 
  public boolean
  getPreferUDP()
  {
    return( prefer_udp );
  }
 
  public void
  setPreferUDP(
    boolean  prefer )
  {
     prefer_udp = prefer;
  }

  public boolean
  isPeerSourceEnabled(
    String peer_source )
  {
    return( adapter.isPeerSourceEnabled( peer_source ));
  }
 
  public void
  peerDiscovered(
    PEPeerTransport    finder,
    PeerItem      pi )
  {
    final ArrayList peer_manager_listeners = peer_manager_listeners_cow;

    for( int i=0; i < peer_manager_listeners.size(); i++ ) {
      try{
        ((PEPeerManagerListener)peer_manager_listeners.get(i)).peerDiscovered( this, pi, finder );
           
      }catch( Throwable e ){
       
        Debug.printStackTrace(e);
      }
    }
  }
 
  public TrackerPeerSource
  getTrackerPeerSource()
  {
    return(
      new TrackerPeerSourceAdapter()
      {
        public int
        getType()
        {
          return( TP_PEX );
        }
       
        public int
        getStatus()
        {
          return( isPeerExchangeEnabled()?ST_ONLINE:ST_DISABLED );
        }
       
        public String
        getName()
        {
          return(
            MessageText.getString( "tps.pex.details",
              new String[]{
                String.valueOf( peer_transports_cow.size()),
                String.valueOf( peer_database.getExchangedPeerCount()),
                String.valueOf( peer_database.getDiscoveredPeerCount())}));
        }
       
        public int
        getPeers()
        {
          return( peer_database.getExchangedPeersUsed());
        }
      });
  }
 
  public void
  generateEvidence(
      IndentWriter    writer )
  {
    writer.println( "PeerManager: seeding=" + seeding_mode );

    writer.println(
        "    udp_fb=" + pending_nat_traversals.size() +
        ",udp_tc=" + udp_traversal_count +
        ",pd=[" + peer_database.getString() + "]");
   
    String  pending_udp = "";
   
    try{
      peer_transports_mon.enter();

      Iterator  it = pending_nat_traversals.values().iterator();
     
      while( it.hasNext()){
     
        PEPeerTransport peer = (PEPeerTransport)it.next();
     
        pending_udp += (pending_udp.length()==0?"":",") + peer.getPeerItemIdentity().getAddressString() + ":" + peer.getPeerItemIdentity().getUDPPort();
      }
    }finally{
     
      peer_transports_mon.exit();
    }
   
    if ( pending_udp.length() > 0 ){
     
      writer.println( "    pending_udp=" + pending_udp );
    }
   
    List  traversals = PeerNATTraverser.getSingleton().getTraversals( this );
   
    String  active_udp = "";
   
    Iterator it = traversals.iterator();
   
    while( it.hasNext()){
     
      InetSocketAddress ad = (InetSocketAddress)it.next();
     
      active_udp += (active_udp.length()==0?"":",") + ad.getAddress().getHostAddress() + ":" + ad.getPort();
    }
   
    if ( active_udp.length() > 0 ){
     
      writer.println( "    active_udp=" + active_udp );
    }
   
    if ( !seeding_mode ){

      writer.println( "  Active Pieces" );

      int  num_active = 0;

      try{
        writer.indent();

        String  str  = "";
        int    num  = 0;

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

          PEPiece  piece = pePieces[i];

          if ( piece != null ){

            num_active++;

            str += (str.length()==0?"":",") + "#" + i + " " + dm_pieces[i].getString() + ": " + piece.getString();

            num++;

            if ( num == 20 ){

              writer.println( str );
              str = "";
              num  = 0;
            }
          }
        }

        if ( num > 0 ){
          writer.println(str);
        }

      }finally{

        writer.exdent();
      }

      if ( num_active == 0 ){

        writer.println( "  Inactive Pieces (excluding done/skipped)" );

        try{
          writer.indent();

          String  str  = "";
          int    num  = 0;

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

            DiskManagerPiece  dm_piece = dm_pieces[i];

            if ( dm_piece.isInteresting()){

              str += (str.length()==0?"":",") + "#" + i + " " + dm_pieces[i].getString();

              num++;

              if ( num == 20 ){

                writer.println( str );
                str = "";
                num  = 0;
              }
            }
          }

          if ( num > 0 ){

            writer.println(str);
          }

        }finally{

          writer.exdent();
        }
      }

      piecePicker.generateEvidence( writer );
    }

    try{
      peer_transports_mon.enter();

      writer.println( "Peers: total = " + peer_transports_cow.size());

      writer.indent();

      try{
        writer.indent();

        it = peer_transports_cow.iterator();

        while( it.hasNext()){

          PEPeerTransport  peer = (PEPeerTransport)it.next();

          peer.generateEvidence( writer );
        }
      }finally{

        writer.exdent();
      }
    }finally{

      peer_transports_mon.exit();

      writer.exdent();
    }
   
    disk_mgr.generateEvidence( writer );
  }
}
TOP

Related Classes of org.gudy.azureus2.core3.peer.impl.control.PEPeerControlImpl

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.