package com.limegroup.gnutella;
import java.io.IOException;
import java.net.Socket;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Properties;
import java.util.Set;
import java.util.StringTokenizer;
import com.util.LOG;
import com.limegroup.gnutella.connection.ConnectionChecker;
import com.limegroup.gnutella.filters.IPFilter;
import com.limegroup.gnutella.handshaking.BadHandshakeException;
import com.limegroup.gnutella.handshaking.HandshakeResponse;
import com.limegroup.gnutella.handshaking.HeaderNames;
import com.limegroup.gnutella.handshaking.NoGnutellaOkException;
import com.limegroup.gnutella.messages.Message;
import com.limegroup.gnutella.messages.PingRequest;
import com.limegroup.gnutella.messages.vendor.QueryStatusResponse;
import com.limegroup.gnutella.messages.vendor.TCPConnectBackVendorMessage;
import com.limegroup.gnutella.messages.vendor.UDPConnectBackVendorMessage;
import com.limegroup.gnutella.security.Authenticator;
import com.limegroup.gnutella.settings.ApplicationSettings;
import com.limegroup.gnutella.settings.ConnectionSettings;
import com.limegroup.gnutella.util.DataUtils;
import com.limegroup.gnutella.util.ManagedThread;
import com.limegroup.gnutella.util.NetworkUtils;
import com.limegroup.gnutella.util.Sockets;
import com.limegroup.gnutella.messages.ByeRequest;
/**
* The list of all ManagedConnection's. Provides a factory method for creating
* user-requested outgoing connections, accepts incoming connections, and
* fetches "automatic" outgoing connections as needed. Creates threads for
* handling these connections when appropriate.
*
* Because this is the only list of all connections, it plays an important role
* in message broadcasting. For this reason, the code is highly tuned to avoid
* locking in the getInitializedConnections() methods. Adding and removing
* connections is a slower operation.<p>
*
* LimeWire follows the following connection strategy:<br>
* As a leaf, LimeWire will ONLY connect to 'good' Ultrapeers. The definition
* of good is constantly changing. For a current view of 'good', review
* HandshakeResponse.isGoodUltrapeer(). LimeWire leaves will NOT deny
* a connection to an ultrapeer even if they've reached their maximum
* desired number of connections (currently 4). This means that if 5
* connections resolve simultaneously, the leaf will remain connected to all 5.
* <br>
* As an Ultrapeer, LimeWire will seek outgoing connections for 5 less than
* the number of it's desired peer slots. This is done so that newcomers
* on the network have a better chance of finding an ultrapeer with a slot
* open. LimeWire ultrapeers will allow ANY other ultrapeer to connect to it,
* and to ensure that the network does not become too LimeWire-centric, it
* reserves 3 slots for non-LimeWire peers. LimeWire ultrapeers will allow
* ANY leaf to connect, so long as there are atleast 15 slots open. Beyond
* that number, LimeWire will only allow 'good' leaves. To see what consitutes
* a good leave, view HandshakeResponse.isGoodLeaf(). To ensure that the
* network does not remain too LimeWire-centric, it reserves 3 slots for
* non-LimeWire leaves.<p>
*
* ConnectionManager has methods to get up and downstream bandwidth, but it
* doesn't quite fit the BandwidthTracker interface.
*/
public class ConnectionManager {
/**
* Timestamp for the last time the user selected to disconnect.
*/
private volatile long _disconnectTime = -1;
/**
* Timestamp for the time we began automatically connecting. We stop
* trying to automatically connect if the user has disconnected since that
* time.
*/
private volatile long _automaticConnectTime = 0;
/**
* Flag for whether or not the auto-connection process is in effect.
*/
private volatile boolean _automaticallyConnecting;
/**
* Timestamp of our last successful connection.
*/
private volatile long _lastSuccessfulConnect = 0;
/**
* Timestamp of the last time we checked to verify that the user has a live
* Internet connection.
*/
private volatile long _lastConnectionCheck = 0;
/**
* Counter for the number of connection attempts we've made.
*/
private volatile static int _connectionAttempts;
/**
* The number of Ultrapeer connections to ideally maintain as an Ultrapeer.
*/
public static final int ULTRAPEER_CONNECTIONS =
32;
/**
* The number of connections leaves should maintain to Ultrapeers.
*/
public static final int PREFERRED_CONNECTIONS_FOR_LEAF = 3;
/**
* To number of connections leaves should maintain when idle.
*/
public static final int PREFERRED_CONNECTIONS_FOR_LEAF_WHEN_IDLE = 1;
/**
* The number of leaf connections reserved for non LimeWire clients.
* This is done to ensure that the network is not solely LimeWire centric.
*/
public static final int RESERVED_NON_LIMEWIRE_LEAVES = 2;
/**
* The number of ultrapeer connections reserved for non LimeWire clients.
* This is done to ensure that the network is not solely LimeWire centric.
*/
public static final int RESERVED_NON_LIMEWIRE_PEERS = 3;
/**
* The current number of connections we want to maintain.
*/
private volatile int _preferredConnections = -1;
/**
* Reference to the <tt>HostCatcher</tt> for retrieving host data as well
* as adding host data.
*/
private HostCatcher _catcher;
/** Threads trying to maintain the NUM_CONNECTIONS.
* LOCKING: obtain this. */
private final List /* of ConnectionFetcher */ _fetchers =
new ArrayList();
/** Connections that have been fetched but not initialized. I don't
* know the relation between _initializingFetchedConnections and
* _connections (see below). LOCKING: obtain this. */
private final List /* of ManagedConnection */ _initializingFetchedConnections =
new ArrayList();
/**
* dedicated ConnectionFetcher used by leafs to fetch a
* locale matching connection
* NOTE: currently this is only used by leafs which will try
* to connect to one connection which matches the locale of the
* client.
*/
private ConnectionFetcher _dedicatedPrefFetcher;
/**
* boolean to check if a locale matching connection is needed.
*/
private volatile boolean _needPref = true;
/**
* boolean of whether or not the interruption of the prefFetcher thread
* has been scheduled.
*/
private boolean _needPrefInterrupterScheduled = false;
/**
* List of all connections. The core data structures are lists, which allow
* fast iteration for message broadcast purposes. Actually we keep a couple
* of lists: the list of all initialized and uninitialized connections
* (_connections), the list of all initialized non-leaf connections
* (_initializedConnections), and the list of all initialized leaf connections
* (_initializedClientConnections).
*
* INVARIANT: neither _connections, _initializedConnections, nor
* _initializedClientConnections contains any duplicates.
* INVARIANT: for all c in _initializedConnections,
* c.isSupernodeClientConnection()==false
* INVARIANT: for all c in _initializedClientConnections,
* c.isSupernodeClientConnection()==true
* COROLLARY: the intersection of _initializedClientConnections
* and _initializedConnections is the empty set
* INVARIANT: _initializedConnections is a subset of _connections
* INVARIANT: _initializedClientConnections is a subset of _connections
* INVARIANT: _shieldedConnections is the number of connections
* in _initializedConnections for which isClientSupernodeConnection()
* is true.
* INVARIANT: _nonLimeWireLeaves is the number of connections
* in _initializedClientConnections for which isLimeWire is false
* INVARIANT: _nonLimeWirePeers is the number of connections
* in _initializedConnections for which isLimeWire is false
*
* LOCKING: _connections, _initializedConnections and
* _initializedClientConnections MUST NOT BE MUTATED. Instead they should
* be replaced as necessary with new copies. Before replacing the
* structures, obtain this' monitor. This avoids lock overhead when
* message broadcasting, though it makes adding/removing connections
* much slower.
*/
//TODO:: why not use sets here??
private volatile List /* of ManagedConnection */
_connections = new ArrayList();
private volatile List /* of ManagedConnection */
_initializedConnections = new ArrayList();
private volatile List /* of ManagedConnection */
_initializedClientConnections = new ArrayList();
private volatile int _shieldedConnections = 0;
private volatile int _nonLimeWireLeaves = 0;
private volatile int _nonLimeWirePeers = 0;
/** number of peers that matches the local locale pref. */
private volatile int _localeMatchingPeers = 0;
/**
* For authenticating users
*/
private final Authenticator _authenticator;
/**
* The current measured upstream bandwidth.
*/
private volatile float _measuredUpstreamBandwidth = 0.f;
/**
* The current measured downstream bandwidth.
*/
private volatile float _measuredDownstreamBandwidth = 0.f;
/**
* Constructs a ConnectionManager. Must call initialize before using.
* @param authenticator Authenticator instance for authenticating users
*/
public ConnectionManager(Authenticator authenticator) {
_authenticator = authenticator;
}
/**
* Links the ConnectionManager up with the other back end pieces and
* launches the ConnectionWatchdog and the initial ConnectionFetchers.
*/
public void initialize() {
_catcher = RouterService.getHostCatcher();
}
/**
* Create a new connection, blocking until it's initialized, but launching
* a new thread to do the message loop.
*/
public ManagedConnection createConnectionBlocking(String hostname,
int portnum)
throws IOException {
ManagedConnection c =
new ManagedConnection(hostname, portnum);
// Initialize synchronously
initializeExternallyGeneratedConnection(c);
// Kick off a thread for the message loop.
Thread conn =
new ManagedThread(new OutgoingConnector(c, false), "OutgoingConnector");
conn.setDaemon(true);
conn.start();
return c;
}
/**
* Create a new connection, allowing it to initialize and loop for messages
* on a new thread.
*/
public void createConnectionAsynchronously(
String hostname, int portnum) {
Runnable outgoingRunner =
new OutgoingConnector(new ManagedConnection(hostname, portnum),
true);
// Initialize and loop for messages on another thread.
Thread outgoingConnectionRunner =
new ManagedThread(outgoingRunner, "OutgoingConnectionThread");
outgoingConnectionRunner.setDaemon(true);
outgoingConnectionRunner.start();
}
/**
* Create an incoming connection. This method starts the message loop,
* so it will block for a long time. Make sure the thread that calls
* this method is suitable doing a connection message loop.
* If there are already too many connections in the manager, this method
* will launch a RejectConnection to send pongs for other hosts.
*/
void acceptConnection(Socket socket) {
//1. Initialize connection. It's always safe to recommend new headers.
Thread.currentThread().setName("IncommingConnectionThread");
ManagedConnection connection = new ManagedConnection(socket);
try {
initializeExternallyGeneratedConnection(connection);
} catch (IOException e) {
connection.close();
return;
}
try {
startConnection(connection);
} catch(IOException e) {
// we could not start the connection for some reason --
// this can easily happen, for example, if the connection
// just drops
}
}
/**
* Removes the specified connection from currently active connections, also
* removing this connection from routing tables and modifying active
* connection fetchers accordingly.
*
* @param mc the <tt>ManagedConnection</tt> instance to remove
*/
public synchronized void remove(ManagedConnection mc) {
mc.setByeReason(ByeRequest.EXIT_NORMAL,"Normal exit\n");
removeInternal(mc);
adjustConnectionFetchers();
}
/**
* Returns whether this (probably) has a connection to the given host. This
* method is currently implemented by iterating through all connections and
* comparing addresses but not ports. (Incoming connections use ephemeral
* ports.) As a result, this test may conservatively return true even if
* this is not connected to <tt>host</tt>. Likewise, it may it mistakenly
* return false if <tt>host</tt> is a multihomed system. In the future,
* additional connection headers may make the test more precise.
*
* @return true if this is probably connected to <tt>host</tt>
*/
boolean isConnectedTo(String hostName) {
//A clone of the list of all connections, both initialized and
//uninitialized, leaves and unrouted. If Java could be prevented from
//making certain code transformations, it would be safe to replace the
//call to "getConnections()" with "_connections", thus avoiding a clone.
//(Remember that _connections is never mutated.)
List connections=getConnections();
for (Iterator iter=connections.iterator(); iter.hasNext(); ) {
ManagedConnection mc = (ManagedConnection)iter.next();
if (mc.getAddress().equals(hostName))
return true;
}
return false;
}
/**
* @return the number of connections, which is greater than or equal
* to the number of initialized connections.
*/
public int getNumConnections() {
return _connections.size();
}
/**
* @return the number of initialized connections, which is less than or
* equals to the number of connections.
*/
public int getNumInitializedConnections() {
return _initializedConnections.size();
}
/**
*@return the number of initialized connections for which
* isClientSupernodeConnection is true.
*/
public int getNumClientSupernodeConnections() {
return _shieldedConnections;
}
/**
* @return the number of free non-leaf slots.
*/
public int getNumFreeNonLeafSlots() {
return _preferredConnections - getNumInitializedConnections();
}
/**
* @return the number of free non-leaf slots that LimeWires can connect to.
*/
public int getNumFreeLimeWireNonLeafSlots() {
return Math.max(0,
getNumFreeNonLeafSlots()
- Math.max(0, RESERVED_NON_LIMEWIRE_PEERS - _nonLimeWirePeers)
- getNumLimeWireLocalePrefSlots()
);
}
/**
* Returns true if we've made a locale-matching connection (or don't
* want any at all).
*/
public boolean isLocaleMatched() {
return !ConnectionSettings.USE_LOCALE_PREF ||
_localeMatchingPeers != 0;
}
/**
* @return the number of locale reserved slots to be filled
*
* An ultrapeer may not have Free LimeWire Non Leaf Slots but may still
* have free slots that are reserved for locales
*/
public int getNumLimeWireLocalePrefSlots() {
return Math.max(0, ConnectionSettings.NUM_LOCALE_PREF
- _localeMatchingPeers);
}
/**
* Determines if we've reached our maximum number of preferred connections.
*/
public boolean isFullyConnected() {
return _initializedConnections.size() >= _preferredConnections;
}
/**
* Returns whether or not the client has an established connection with
* another Gnutella client.
*
* @return <tt>true</tt> if the client is currently connected to
* another Gnutella client, <tt>false</tt> otherwise
*/
public boolean isConnected() {
return ((_initializedClientConnections.size() > 0) ||
(_initializedConnections.size() > 0));
}
/**
* Returns whether or not we are currently attempting to connect to the
* network.
*/
public boolean isConnecting() {
if(_disconnectTime != 0)
return false;
if(isConnected())
return false;
synchronized(this) {
return _fetchers.size() != 0 ||
_initializingFetchedConnections.size() != 0;
}
}
/**
* Takes a snapshot of the upstream and downstream bandwidth since the last
* call to measureBandwidth.
* @see BandwidthTracker#measureBandwidth
*/
public void measureBandwidth() {
float upstream=0.f;
float downstream=0.f;
List connections = getInitializedConnections();
for (Iterator iter=connections.iterator(); iter.hasNext(); ) {
ManagedConnection mc=(ManagedConnection)iter.next();
mc.measureBandwidth();
upstream+=mc.getMeasuredUpstreamBandwidth();
downstream+=mc.getMeasuredDownstreamBandwidth();
}
_measuredUpstreamBandwidth=upstream;
_measuredDownstreamBandwidth=downstream;
}
/**
* Returns the upstream bandwidth between the last two calls to
* measureBandwidth.
* @see BandwidthTracker#measureBandwidth
*/
public float getMeasuredUpstreamBandwidth() {
return _measuredUpstreamBandwidth;
}
/**
* Returns the downstream bandwidth between the last two calls to
* measureBandwidth.
* @see BandwidthTracker#measureBandwidth
*/
public float getMeasuredDownstreamBandwidth() {
return _measuredDownstreamBandwidth;
}
/**
* Checks if the connection received can be accepted,
* based upon the type of connection (e.g. client, ultrapeer,
* temporary etc).
* @param c The connection we received, for which to
* test if we have incoming slot.
* @return true, if we have incoming slot for the connection received,
* false otherwise
*/
private boolean allowConnection(ManagedConnection c) {
if(!c.receivedHeaders()) return false;
return allowConnection(c.headers(), false);
}
/**
* Checks if the connection received can be accepted,
* based upon the type of connection (e.g. client, ultrapeer,
* temporary etc).
* @param c The connection we received, for which to
* test if we have incoming slot.
* @return true, if we have incoming slot for the connection received,
* false otherwise
*/
public boolean allowConnection(HandshakeResponse hr) {
return allowConnection(hr, !hr.isUltrapeer());
}
/**
* Returns true if this has slots for an incoming connection, <b>without
* accounting for this' ultrapeer capabilities</b>. More specifically:
* <ul>
* <li>if ultrapeerHeader==null, returns true if this has space for an
* unrouted old-style connection.
* <li>if ultrapeerHeader.equals("true"), returns true if this has slots
* for a leaf connection.
* <li>if ultrapeerHeader.equals("false"), returns true if this has slots
* for an ultrapeer connection.
* </ul>
*
* <tt>useragentHeader</tt> is used to prefer LimeWire and certain trusted
* vendors. <tt>outgoing</tt> is currently unused, but may be used to
* prefer incoming or outgoing connections in the forward.
*
* @param outgoing true if this is an outgoing connection; true if incoming
* @param ultrapeerHeader the value of the X-Ultrapeer header, or null
* if it was not written
* @param useragentHeader the value of the User-Agent header, or null if
* it was not written
* @return true if a connection of the given type is allowed
*/
public boolean allowConnection(HandshakeResponse hr, boolean leaf) {
//Old versions of LimeWire used to prefer incoming connections over
//outgoing. The rationale was that a large number of hosts were
//firewalled, so those who weren't had to make extra space for them.
//With the introduction of ultrapeers, this is not an issue; all
//firewalled hosts become leaf nodes. Hence we make no distinction
//between incoming and outgoing.
//
//At one point we would actively kill old-fashioned unrouted connections
//for ultrapeers. Later, we preferred ultrapeers to old-fashioned
//connections as follows: if the HostCatcher had marked ultrapeer pongs,
//we never allowed more than DESIRED_OLD_CONNECTIONS old
//connections--incoming or outgoing.
//
//Now we simply prefer connections by vendor, which has some of the same
//effect. We use BearShare's clumping algorithm. Let N be the
//keep-alive and K be RESERVED_GOOD_CONNECTIONS. (In BearShare's
//implementation, K=1.) Allow any connections in for the first N-K
//slots. But only allow good vendors for the last K slots. In other
//words, accept a connection C if there are fewer than N connections and
//one of the following is true: C is a good vendor or there are fewer
//than N-K connections. With time, this converges on all good
//connections.
int limeAttempts = ConnectionSettings.LIME_ATTEMPTS;
//Don't allow anything if disconnected.
if (_preferredConnections <=0 ) {
return false;
//If a leaf (shielded or not), check rules as such.
} else {
// require ultrapeer.
if(!hr.isUltrapeer())
return false;
// If it's not good, or it's the first few attempts & not a LimeWire,
// never allow it.
if(!hr.isGoodUltrapeer() ||
(Sockets.getAttempts() < limeAttempts && !hr.isLimeWire())) {
return false;
// if we have slots, allow it.
} else if (_shieldedConnections < _preferredConnections) {
// if it matched our preference, we don't need to preference
// anymore.
if(checkLocale(hr.getLocalePref()))
_needPref = false;
return true;
} else {
// if we were still trying to get a locale connection
// and this one matches, allow it, 'cause no one else matches.
// (we would have turned _needPref off if someone matched.)
if(_needPref && checkLocale(hr.getLocalePref()))
return true;
// don't allow it.
return false;
}
}
}
/**
* Provides handle to the authenticator instance
* @return Handle to the authenticator
*/
public Authenticator getAuthenticator(){
return _authenticator;
}
/**
* @requires returned value not modified
* @effects returns a list of this' initialized connections. <b>This
* exposes the representation of this, but is needed in some cases
* as an optimization.</b> All lookup values in the returned value
* are guaranteed to run in linear time.
*/
public List getInitializedConnections() {
return _initializedConnections;
}
/**
* return a list of initialized connection that matches the parameter
* String loc.
* create a new linkedlist to return.
*/
public List getInitializedConnectionsMatchLocale(String loc) {
List matches = new LinkedList();
for(Iterator itr= _initializedConnections.iterator();
itr.hasNext();) {
Connection conn = (Connection)itr.next();
if(loc.equals(conn.getLocalePref()))
matches.add(conn);
}
return matches;
}
/**
* @requires returned value not modified
* @effects returns a list of this' initialized connections. <b>This
* exposes the representation of this, but is needed in some cases
* as an optimization.</b> All lookup values in the returned value
* are guaranteed to run in linear time.
*/
public List getInitializedClientConnections() {
return _initializedClientConnections;
}
/**
* return a list of initialized client connection that matches the parameter
* String loc.
* create a new linkedlist to return.
*/
public List getInitializedClientConnectionsMatchLocale(String loc) {
List matches = new LinkedList();
for(Iterator itr= _initializedClientConnections.iterator();
itr.hasNext();) {
Connection conn = (Connection)itr.next();
if(loc.equals(conn.getLocalePref()))
matches.add(conn);
}
return matches;
}
/**
* @return all of this' connections.
*/
public List getConnections() {
return _connections;
}
/**
* Accessor for the <tt>Set</tt> of push proxies for this node. If
* there are no push proxies available, or if this node is an Ultrapeer,
* this will return an empty <tt>Set</tt>.
*
* @return a <tt>Set</tt> of push proxies with a maximum size of 4
*
* TODO: should the set of pushproxy UPs be cached and updated as
* connections are killed and created?
*/
public Set getPushProxies() {
// this should be fast since leaves don't maintain a lot of
// connections and the test for proxy support is cached boolean
// value
Iterator ultrapeers = getInitializedConnections().iterator();
Set proxies = new HashSet();
while (ultrapeers.hasNext() && (proxies.size() < 4)) {
ManagedConnection currMC = (ManagedConnection)ultrapeers.next();
if (currMC.getPushProxyPort() >= 0)
proxies.add(currMC);
}
return proxies;
}
/**
* Sends a TCPConnectBack request to (up to) 2 connected Ultrapeers.
* @returns false if no requests were sent, otherwise true.
*/
public boolean sendTCPConnectBackRequests() {
int sent = 0;
final Message cb = new TCPConnectBackVendorMessage(RouterService.getPort());
List peers = new ArrayList(getInitializedConnections());
Collections.shuffle(peers);
for(Iterator i = peers.iterator(); i.hasNext() && sent < 5; ) {
ManagedConnection currMC = (ManagedConnection)i.next();
if (currMC.remoteHostSupportsTCPRedirect() >= 0) {
currMC.send(cb);
sent++;
}
}
return (sent > 0);
}
/**
* Sends a UDPConnectBack request to (up to) 4 (and at least 2)
* connected Ultrapeers.
* @returns false if no requests were sent, otherwise true.
*/
public boolean sendUDPConnectBackRequests(GUID cbGuid) {
int sent = 0;
final Message cb =
new UDPConnectBackVendorMessage(RouterService.getPort(), cbGuid);
List peers = new ArrayList(getInitializedConnections());
Collections.shuffle(peers);
for(Iterator i = peers.iterator(); i.hasNext() && sent < 5; ) {
ManagedConnection currMC = (ManagedConnection)i.next();
if (currMC.remoteHostSupportsUDPConnectBack() >= 0) {
currMC.send(cb);
sent++;
}
}
return (sent > 0);
}
/**
* Sends a QueryStatusResponse message to as many Ultrapeers as possible.
*
* @param
*/
public void updateQueryStatus(QueryStatusResponse stat) {
// this should be fast since leaves don't maintain a lot of
// connections and the test for query status response is a cached
// value
Iterator ultrapeers = getInitializedConnections().iterator();
while (ultrapeers.hasNext()) {
ManagedConnection currMC = (ManagedConnection)ultrapeers.next();
if (currMC.remoteHostSupportsLeafGuidance() >= 0)
currMC.send(stat);
}
}
/**
* Returns the <tt>Endpoint</tt> for an Ultrapeer connected via TCP,
* if available.
*
* @return the <tt>Endpoint</tt> for an Ultrapeer connected via TCP if
* there is one, otherwise returns <tt>null</tt>
*/
public Endpoint getConnectedGUESSUltrapeer() {
for(Iterator iter=_initializedConnections.iterator(); iter.hasNext();) {
ManagedConnection connection = (ManagedConnection)iter.next();
if(connection.isSupernodeConnection() &&
connection.isGUESSUltrapeer()) {
return new Endpoint(connection.getInetAddress().getAddress(),
connection.getPort());
}
}
return null;
}
/** Returns a <tt>List<tt> of Ultrapeers connected via TCP that are GUESS
* enabled.
*
* @return A non-null List of GUESS enabled, TCP connected Ultrapeers. The
* are represented as ManagedConnections.
*/
public List getConnectedGUESSUltrapeers() {
List retList = new ArrayList();
for(Iterator iter=_initializedConnections.iterator(); iter.hasNext();) {
ManagedConnection connection = (ManagedConnection)iter.next();
if(connection.isSupernodeConnection() &&
connection.isGUESSUltrapeer())
retList.add(connection);
}
return retList;
}
/**
* Adds an initializing connection.
* Should only be called from a thread that has this' monitor.
* This is called from initializeExternallyGeneratedConnection
* and initializeFetchedConnection, both times from within a
* synchronized(this) block.
*/
private void connectionInitializing(Connection c) {
//REPLACE _connections with the list _connections+[c]
List newConnections=new ArrayList(_connections);
newConnections.add(c);
_connections = Collections.unmodifiableList(newConnections);
}
/**
* Adds an incoming connection to the list of connections. Note that
* the incoming connection has already been initialized before
* this method is invoked.
* Should only be called from a thread that has this' monitor.
* This is called from initializeExternallyGeneratedConnection, for
* incoming connections
*/
private void connectionInitializingIncoming(ManagedConnection c) {
connectionInitializing(c);
}
/**
* Marks a connection fully initialized, but only if that connection wasn't
* removed from the list of open connections during its initialization.
* Should only be called from a thread that has this' monitor.
*/
private boolean connectionInitialized(ManagedConnection c) {
LOG.error("enter connectionInitialized");
if(_connections.contains(c)) {
// build the queues and start the output runner.
// this MUST be done before _initializedConnections
// or _initializedClientConnections has added this
// connection to its list. otherwise, messages may
// attempt to be sent to the connection before it has
// set up its output queues.
c.buildAndStartQueues();
//update the appropriate list of connections
if(!c.isSupernodeClientConnection()){
//REPLACE _initializedConnections with the list
//_initializedConnections+[c]
List newConnections=new ArrayList(_initializedConnections);
newConnections.add(c);
_initializedConnections =
Collections.unmodifiableList(newConnections);
//maintain invariant
if(c.isClientSupernodeConnection())
_shieldedConnections++;
if(!c.isLimeWire())
_nonLimeWirePeers++;
if(checkLocale(c.getLocalePref()))
_localeMatchingPeers++;
} else {
//REPLACE _initializedClientConnections with the list
//_initializedClientConnections+[c]
List newConnections
=new ArrayList(_initializedClientConnections);
newConnections.add(c);
_initializedClientConnections =
Collections.unmodifiableList(newConnections);
if(!c.isLimeWire())
_nonLimeWireLeaves++;
}
// do any post-connection initialization that may involve sending.
c.postInit();
// sending the ping request.
sendInitialPingRequest(c);
return true;
}
return false;
}
/**
* Disconnects from the network. Closes all connections and sets
* the number of connections to zero.
*/
public synchronized void disconnect() {
_disconnectTime = System.currentTimeMillis();
_preferredConnections = 0;
adjustConnectionFetchers(); // kill them all
//2. Remove all connections.
for (Iterator iter=getConnections().iterator();
iter.hasNext(); ) {
ManagedConnection c=(ManagedConnection)iter.next();
remove(c);
//add the endpoint to hostcatcher
if (c.isSupernodeConnection()) {
//add to catcher with the locale info.
_catcher.add(new Endpoint(c.getInetAddress().getHostAddress(),
c.getPort()), true, c.getLocalePref());
}
}
Sockets.clearAttempts();
}
/**
* Connects to the network. Ensures the number of messaging connections
* is non-zero and recontacts the pong server as needed.
*/
public synchronized void connect() {
// Reset the disconnect time to be a long time ago.
_disconnectTime = 0;
// Ignore this call if we're already connected
// or not initialized yet.
if(isConnected() || _catcher == null) {
return;
}
_connectionAttempts = 0;
_lastConnectionCheck = 0;
_lastSuccessfulConnect = 0;
// Notify HostCatcher that we've connected.
_catcher.expire();
// Set the number of connections we want to maintain
setPreferredConnections();
// tell the catcher to start pinging people.
_catcher.sendUDPPings();
}
/**
* Sends the initial ping request to a newly initialized connection. The
* ttl of the PingRequest will be 1 if we don't need any connections.
* Otherwise, the ttl = max ttl.
*/
private void sendInitialPingRequest(ManagedConnection connection) {
if(connection.supportsPongCaching()) return;
//We need to compare how many connections we have to the keep alive to
//determine whether to send a broadcast ping or a handshake ping,
//initially. However, in this case, we can't check the number of
//connection fetchers currently operating, as that would always then
//send a handshake ping, since we're always adjusting the connection
//fetchers to have the difference between keep alive and num of
//connections.
PingRequest pr;
if (getNumInitializedConnections() >= _preferredConnections)
pr = new PingRequest((byte)1);
else
pr = new PingRequest((byte)4);
connection.send(pr);
//Ensure that the initial ping request is written in a timely fashion.
try {
connection.flush();
} catch (IOException e) {
/* close it later */
//LOG.logSp("flush ping " + e.getMessage());
}
}
/**
* An unsynchronized version of remove, meant to be used when the monitor
* is already held. This version does not kick off ConnectionFetchers;
* only the externally exposed version of remove does that.
*/
private void removeInternal(ManagedConnection c) {
// 1a) Remove from the initialized connections list and clean up the
// stuff associated with initialized connections. For efficiency
// reasons, this must be done before (2) so packets are not forwarded
// to dead connections (which results in lots of thrown exceptions).
if(!c.isSupernodeClientConnection()){
int i=_initializedConnections.indexOf(c);
if (i != -1) {
//REPLACE _initializedConnections with the list
//_initializedConnections-[c]
List newConnections=new ArrayList();
newConnections.addAll(_initializedConnections);
newConnections.remove(c);
_initializedConnections =
Collections.unmodifiableList(newConnections);
//maintain invariant
if(c.isClientSupernodeConnection())
_shieldedConnections--;
if(!c.isLimeWire())
_nonLimeWirePeers--;
if(checkLocale(c.getLocalePref()))
_localeMatchingPeers--;
}
}else{
//check in _initializedClientConnections
int i=_initializedClientConnections.indexOf(c);
if (i != -1) {
//REPLACE _initializedClientConnections with the list
//_initializedClientConnections-[c]
List newConnections=new ArrayList();
newConnections.addAll(_initializedClientConnections);
newConnections.remove(c);
_initializedClientConnections =
Collections.unmodifiableList(newConnections);
if(!c.isLimeWire())
_nonLimeWireLeaves--;
}
}
// 1b) Remove from the all connections list and clean up the
// stuff associated all connections
int i=_connections.indexOf(c);
if (i != -1) {
//REPLACE _connections with the list _connections-[c]
List newConnections=new ArrayList(_connections);
newConnections.remove(c);
_connections = Collections.unmodifiableList(newConnections);
}
// 2) Ensure that the connection is closed. This must be done before
// step (3) to ensure that dead connections are not added to the route
// table, resulting in dangling references.
c.close();
// 3) Clean up route tables.
RouterService.getMessageRouter().removeConnection(c);
// 4) Notify the listener
RouterService.getCallback().connectionClosed(c);
}
/**
* Stabilizes connections by removing extraneous ones.
*
* This will remove the connections that we've been connected to
* for the shortest amount of time.
*/
private synchronized void stabilizeConnections() {
while(getNumInitializedConnections() > _preferredConnections) {
ManagedConnection newest = null;
for(Iterator i = _initializedConnections.iterator(); i.hasNext();){
ManagedConnection c = (ManagedConnection)i.next();
// first see if this is a non-limewire connection and cut it off
// unless it is our only connection left
if (!c.isLimeWire()) {
LOG.error("remove not lime?");
newest = c;
break;
}
if(newest == null ||
c.getConnectionTime() > newest.getConnectionTime())
newest = c;
}
if(newest != null) {
LOG.error("remove in stabilizeConnections " + newest.getAddress());
remove(newest);
}
}
adjustConnectionFetchers();
}
/**
* Starts or stops connection fetchers to maintain the invariant
* that numConnections + numFetchers >= _preferredConnections
*
* _preferredConnections - numConnections - numFetchers is called the need.
* This method is called whenever the need changes:
* 1. setPreferredConnections() -- _preferredConnections changes
* 2. remove(Connection) -- numConnections drops.
* 3. initializeExternallyGeneratedConnection() --
* numConnections rises.
* 4. initialization error in initializeFetchedConnection() --
* numConnections drops when removeInternal is called.
* Note that adjustConnectionFetchers is not called when a connection is
* successfully fetched from the host catcher. numConnections rises,
* but numFetchers drops, so need is unchanged.
*
* Only call this method when the monitor is held.
*/
private void adjustConnectionFetchers() {
if(ConnectionSettings.USE_LOCALE_PREF) {
//if it's a leaf and locale preferencing is on
//we will create a dedicated preference fetcher
//that tries to fetch a connection that matches the
//clients locale
if( _needPref
&& !_needPrefInterrupterScheduled
&& _dedicatedPrefFetcher == null) {
_dedicatedPrefFetcher = new ConnectionFetcher(true);
Runnable interrupted = new Runnable() {
public void run() {
synchronized(ConnectionManager.this) {
// always finish once this runs.
_needPref = false;
if (_dedicatedPrefFetcher == null)
return;
_dedicatedPrefFetcher.interrupt();
_dedicatedPrefFetcher = null;
}
}
};
_needPrefInterrupterScheduled = true;
// shut off this guy if he didn't have any luck
RouterService.schedule(interrupted, 15 * 1000, 0);
}
}
int goodConnections = getNumInitializedConnections();
int neededConnections = _preferredConnections - goodConnections;
//Now how many fetchers do we need? To increase parallelism, we
//allocate 3 fetchers per connection, but no more than 10 fetchers.
//(Too much parallelism increases chance of simultaneous connects,
//resulting in too many connections.) Note that we assume that all
//connections being fetched right now will become ultrapeers.
int multiple;
// The end result of the following logic, assuming _preferredConnections
// is 32 for Ultrapeers, is:
// When we have 22 active peer connections, we fetch
// (27-current)*1 connections.
// All other times, for Ultrapeers, we will fetch
// (32-current)*3, up to a maximum of 20.
// For leaves, assuming they maintin 4 Ultrapeers,
// we will fetch (4-current)*2 connections.
// If we have not accepted incoming, fetch 3 times
// as many connections as we need.
// We must also check if we're actively being a Ultrapeer because
// it's possible we may have turned off acceptedIncoming while
// being an Ultrapeer.
if( !RouterService.acceptedIncomingConnection()) {
multiple = 3;
}
// Otherwise, if we're not ultrapeer capable,
// or have not become an Ultrapeer to anyone,
// also fetch 3 times as many connections as we need.
// It is critical that active ultrapeers do not use a multiple of 3
// without reducing neededConnections, otherwise LimeWire would
// continue connecting and rejecting connections forever.
else {
multiple = 3;
}
int need = Math.min(10, multiple*neededConnections)
- _fetchers.size()
- _initializingFetchedConnections.size();
// LOG.logSp("need " + need + " fetcher " + _fetchers.size() + " init " + _initializingFetchedConnections.size());
// Start connection fetchers as necessary
while(need > 0) {
// This kicks off the thread for the fetcher
_fetchers.add(new ConnectionFetcher());
need--;
}
// Stop ConnectionFetchers as necessary, but it's possible there
// aren't enough fetchers to stop. In this case, close some of the
// connections started by ConnectionFetchers.
int lastFetcherIndex = _fetchers.size();
while((need < 0) && (lastFetcherIndex > 0)) {
ConnectionFetcher fetcher = (ConnectionFetcher)
_fetchers.remove(--lastFetcherIndex);
fetcher.interrupt();
need++;
}
int lastInitializingConnectionIndex =
_initializingFetchedConnections.size();
while((need < 0) && (lastInitializingConnectionIndex > 0)) {
ManagedConnection connection = (ManagedConnection)
_initializingFetchedConnections.remove(
--lastInitializingConnectionIndex);
LOG.logSp("remove in connectionFether" + connection.getAddress());
removeInternal(connection);
need++;
}
}
/**
* Initializes an outgoing connection created by a ConnectionFetcher
* Throws any of the exceptions listed in Connection.initialize on
* failure; no cleanup is necessary in this case.
*
* @exception IOException we were unable to establish a TCP connection
* to the host
* @exception NoGnutellaOkException we were able to establish a
* messaging connection but were rejected
* @exception BadHandshakeException some other problem establishing
* the connection, e.g., the server responded with HTTP, closed the
* the connection during handshaking, etc.
* @see com.limegroup.gnutella.Connection#initialize(int)
*/
private void initializeFetchedConnection(ManagedConnection mc,
ConnectionFetcher fetcher)
throws NoGnutellaOkException, BadHandshakeException, IOException {
synchronized(this) {
if(fetcher.isInterrupted()) {
// Externally generated interrupt.
// The interrupting thread has recorded the
// death of the fetcher, so throw IOException.
// (This prevents fetcher from continuing!)
throw new IOException("connection fetcher");
}
_initializingFetchedConnections.add(mc);
if(fetcher == _dedicatedPrefFetcher)
_dedicatedPrefFetcher = null;
else
_fetchers.remove(fetcher);
connectionInitializing(mc);
// No need to adjust connection fetchers here. We haven't changed
// the need for connections; we've just replaced a ConnectionFetcher
// with a Connection.
}
RouterService.getCallback().connectionInitializing(mc);
try {
mc.initialize();
} catch(IOException e) {
synchronized(ConnectionManager.this) {
_initializingFetchedConnections.remove(mc);
//LOG.logSp("close in initializeFetchedConnection" + mc.getAddress() + e.getMessage());
removeInternal(mc);
// We've removed a connection, so the need for connections went
// up. We may need to launch a fetcher.
adjustConnectionFetchers();
}
throw e;
}
finally {
//if the connection received headers, process the headers to
//take steps based on the headers
processConnectionHeaders(mc);
}
completeConnectionInitialization(mc, true);
}
/**
* Processes the headers received during connection handshake and updates
* itself with any useful information contained in those headers.
* Also may change its state based upon the headers.
* @param headers The headers to be processed
* @param connection The connection on which we received the headers
*/
private void processConnectionHeaders(Connection connection){
if(!connection.receivedHeaders()) {
return;
}
//get the connection headers
Properties headers = connection.headers().props();
//return if no headers to process
if(headers == null) return;
//update the addresses in the host cache (in case we received some
//in the headers)
updateHostCache(connection.headers());
//get remote address. If the more modern "Listen-IP" header is
//not included, try the old-fashioned "X-My-Address".
String remoteAddress
= headers.getProperty(HeaderNames.LISTEN_IP);
if (remoteAddress==null)
remoteAddress
= headers.getProperty(HeaderNames.X_MY_ADDRESS);
//set the remote port if not outgoing connection (as for the outgoing
//connection, we already know the port at which remote host listens)
if((remoteAddress != null) && (!connection.isOutgoing())) {
int colonIndex = remoteAddress.indexOf(':');
if(colonIndex == -1) return;
colonIndex++;
if(colonIndex > remoteAddress.length()) return;
try {
int port =
Integer.parseInt(
remoteAddress.substring(colonIndex).trim());
if(NetworkUtils.isValidPort(port)) {
// for incoming connections, set the port based on what it's
// connection headers say the listening port is
connection.setListeningPort(port);
}
} catch(NumberFormatException e){
// should nothappen though if the other client is well-coded
}
}
}
/**
* Returns true if this can safely switch from Ultrapeer to leaf mode.
* Typically this means that we are an Ultrapeer and have no leaf
* connections.
*
* @return <tt>true</tt> if we will allow ourselves to become a leaf,
* otherwise <tt>false</tt>
*/
public boolean allowLeafDemotion() {
return true;
}
/**
* Adds the X-Try-Ultrapeer hosts from the connection headers to the
* host cache.
*
* @param headers the connection headers received
*/
private void updateHostCache(HandshakeResponse headers) {
if(!headers.hasXTryUltrapeers()) return;
//get the ultrapeers, and add those to the host cache
String hostAddresses = headers.getXTryUltrapeers();
//tokenize to retrieve individual addresses
StringTokenizer st = new StringTokenizer(hostAddresses,
Constants.ENTRY_SEPARATOR);
List hosts = new ArrayList(st.countTokens());
while(st.hasMoreTokens()){
String address = st.nextToken().trim();
try {
Endpoint e = new Endpoint(address);
hosts.add(e);
} catch(IllegalArgumentException iae){
continue;
}
}
_catcher.add(hosts);
}
/**
* Initializes an outgoing connection created by createConnection or any
* incomingConnection. If this is an incoming connection and there are no
* slots available, rejects it and throws IOException.
*
* @throws IOException on failure. No cleanup is necessary if this happens.
*/
private void initializeExternallyGeneratedConnection(ManagedConnection c)
throws IOException {
//For outgoing connections add it to the GUI and the fetcher lists now.
//For incoming, we'll do this below after checking incoming connection
//slots. This keeps reject connections from appearing in the GUI, as
//well as improving performance slightly.
if (c.isOutgoing()) {
synchronized(this) {
connectionInitializing(c);
// We've added a connection, so the need for connections went
// down.
adjustConnectionFetchers();
}
RouterService.getCallback().connectionInitializing(c);
}
try {
c.initialize();
} catch(IOException e) {
LOG.error("close on initializeExternallyGeneratedConnection" + c.getAddress() + e.getMessage());
remove(c);
throw e;
}
finally {
//if the connection received headers, process the headers to
//take steps based on the headers
processConnectionHeaders(c);
}
//If there's not space for the connection, reject it. This mechanism
//works for Gnutella 0.4 connections, as well as some odd cases for 0.6
//connections. Sometimes ManagedConnections are handled by headers
//directly.
if (!c.isOutgoing() && !allowConnection(c)) {
c.loopToReject();
//No need to remove, since it hasn't been added to any lists.
throw new IOException("No space for connection");
}
//For incoming connections, add it to the GUI. For outgoing connections
//this was done at the top of the method. See note there.
if (! c.isOutgoing()) {
synchronized(this) {
connectionInitializingIncoming(c);
// We've added a connection, so the need for connections went
// down.
adjustConnectionFetchers();
}
RouterService.getCallback().connectionInitializing(c);
}
completeConnectionInitialization(c, false);
}
/**
* Performs the steps necessary to complete connection initialization.
*
* @param mc the <tt>ManagedConnection</tt> to finish initializing
* @param fetched Specifies whether or not this connection is was fetched
* by a connection fetcher. If so, this removes that connection from
* the list of fetched connections being initialized, keeping the
* connection fetcher data in sync
*/
private void completeConnectionInitialization(ManagedConnection mc,
boolean fetched) {
synchronized(this) {
if(fetched) {
_initializingFetchedConnections.remove(mc);
}
// If the connection was killed while initializing, we shouldn't
// announce its initialization
boolean connectionOpen = connectionInitialized(mc);
if(connectionOpen) {
RouterService.getCallback().connectionInitialized(mc);
RouterService.resendWhenConnect(mc);
setPreferredConnections();
}
}
}
/**
* Gets the number of preferred connections to maintain.
*/
public int getPreferredConnectionCount() {
return _preferredConnections;
}
/**
* Determines if we're attempting to maintain the idle connection count.
*/
public boolean isConnectionIdle() {
return
_preferredConnections == PREFERRED_CONNECTIONS_FOR_LEAF_WHEN_IDLE;
}
/**
* Sets the maximum number of connections we'll maintain.
*/
private void setPreferredConnections() {
// if we're disconnected, do nothing.
if(_disconnectTime != 0)
return;
int oldPreferred = _preferredConnections;
_preferredConnections = PREFERRED_CONNECTIONS_FOR_LEAF;
if(oldPreferred != _preferredConnections)
stabilizeConnections();
}
//
// End connection list management functions
//
//
// Begin connection launching thread inner classes
//
/**
* This thread does the initialization and the message loop for
* ManagedConnections created through createConnectionAsynchronously and
* createConnectionBlocking
*/
private class OutgoingConnector implements Runnable {
private final ManagedConnection _connection;
private final boolean _doInitialization;
/**
* Creates a new <tt>OutgoingConnector</tt> instance that will
* attempt to create a connection to the specified host.
*
* @param connection the host to connect to
*/
public OutgoingConnector(ManagedConnection connection,
boolean initialize) {
_connection = connection;
_doInitialization = initialize;
}
public void run() {
try {
if(_doInitialization) {
initializeExternallyGeneratedConnection(_connection);
}
startConnection(_connection);
} catch(IOException ignored) {}
}
}
/**
* Runs standard calls that should be made whenever a connection is fully
* established and should wait for messages.
*
* @param conn the <tt>ManagedConnection</tt> instance to start
* @throws <tt>IOException</tt> if there is an excpetion while looping
* for messages
*/
private void startConnection(ManagedConnection conn) throws IOException {
Thread.currentThread().setName("MessageLoopingThread");
// this can throw IOException
conn.loopForMessages();
}
/**
* Asynchronously fetches a connection from hostcatcher, then does
* then initialization and message loop.
*
* The ConnectionFetcher is responsible for recording its instantiation
* by adding itself to the fetchers list. It is responsible for recording
* its death by removing itself from the fetchers list only if it
* "interrupts itself", that is, only if it establishes a connection. If
* the thread is interrupted externally, the interrupting thread is
* responsible for recording the death.
*/
private class ConnectionFetcher extends ManagedThread {
//set if this connectionfetcher is a preferencing fetcher
private boolean _pref = false;
/**
* Tries to add a connection. Should only be called from a thread
* that has the enclosing ConnectionManager's monitor. This method
* is only called from adjustConnectionFetcher's, which has the same
* locking requirement.
*/
public ConnectionFetcher() {
this(false);
}
public ConnectionFetcher(boolean pref) {
setName("ConnectionFetcher");
_pref = pref;
// Kick off the thread.
setDaemon(true);
start();
}
// Try a single connection
@Override
public void managedRun() {
try {
// Wait for an endpoint.
Endpoint endpoint = null;
do {
endpoint = _catcher.getAnEndpoint();
} while ( !IPFilter.instance().allow(endpoint.getAddress()) ||
isConnectedTo(endpoint.getAddress()) );
//Assert.that(endpoint != null);
_connectionAttempts++;
ManagedConnection connection = new ManagedConnection(
endpoint.getAddress(), endpoint.getPort());
//set preferencing
connection.setLocalePreferencing(_pref);
// If we've been trying to connect for awhile, check to make
// sure the user's internet connection is live. We only do
// this if we're not already connected, have not made any
// successful connections recently, and have not checked the
// user's connection in the last little while or have very
// few hosts left to try.
long curTime = System.currentTimeMillis();
if(!isConnected() &&
_connectionAttempts > 40 &&
((curTime-_lastSuccessfulConnect)>4000) &&
((curTime-_lastConnectionCheck)>60*60*1000)) {
_connectionAttempts = 0;
_lastConnectionCheck = curTime;
//LOG.logSp("checking for live connection");
ConnectionChecker.checkForLiveConnection();
}
//Try to connect, recording success or failure so HostCatcher
//can update connection history. Note that we declare
//success if we were able to establish the TCP connection
//but couldn't handshake (NoGnutellaOkException).
try {
initializeFetchedConnection(connection, this);
_lastSuccessfulConnect = System.currentTimeMillis();
_catcher.doneWithConnect(endpoint, true);
if(_pref) // if pref connection succeeded
_needPref = false;
} catch (NoGnutellaOkException e) {
_lastSuccessfulConnect = System.currentTimeMillis();
if(e.getCode() == HandshakeResponse.LOCALE_NO_MATCH) {
//if it failed because of a locale matching issue
//readd to hostcatcher??
_catcher.add(endpoint, true,
connection.getLocalePref());
}
else {
_catcher.doneWithConnect(endpoint, true);
_catcher.putHostOnProbation(endpoint);
}
throw e;
} catch (IOException e) {
_catcher.doneWithConnect(endpoint, false);
_catcher.expireHost(endpoint);
throw e;
}
LOG.logSp("before startConnection" + connection.getAddress());
startConnection(connection);
} catch(IOException e) {
LOG.logSp(e.getMessage());
} catch (InterruptedException e) {
// Externally generated interrupt.
// The interrupting thread has recorded the
// death of the fetcher, so just return.
return;
} catch(Throwable e) {
//Internal error!
ErrorService.error(e);
}
}
public String toString() {
return "ConnectionFetcher";
}
}
/**
* This method notifies the connection manager that the user does not have
* a live connection to the Internet to the best of our determination.
* In this case, we notify the user with a message and maintain any
* Gnutella hosts we have already tried instead of discarding them.
*/
public void noInternetConnection() {
if(_automaticallyConnecting) {
// We've already notified the user about their connection and we're
// alread retrying automatically, so just return.
return;
}
// Kill all of the ConnectionFetchers.
disconnect();
// Try to reconnect in 10 seconds, and then every minute after
// that.
RouterService.schedule(new Runnable() {
public void run() {
// If the last time the user disconnected is more recent
// than when we started automatically connecting, just
// return without trying to connect. Note that the
// disconnect time is reset if the user selects to connect.
if(_automaticConnectTime < _disconnectTime) {
return;
}
if(!RouterService.isConnected()) {
// Try to re-connect. Note this call resets the time
// for our last check for a live connection, so we may
// hit web servers again to check for a live connection.
connect();
}
}
}, 10*1000, 2*60*1000);
_automaticConnectTime = System.currentTimeMillis();
_automaticallyConnecting = true;
recoverHosts();
}
/**
* Utility method that tells the host catcher to recover hosts from disk
* if it doesn't have enough hosts.
*/
private void recoverHosts() {
// Notify the HostCatcher that it should keep any hosts it has already
// used instead of discarding them.
// The HostCatcher can be null in testing.
if(_catcher != null && _catcher.getNumHosts() < 100) {
_catcher.recoverHosts();
}
}
/**
* Utility method to see if the passed in locale matches
* that of the local client. As of now, we assume that
* those clients not advertising locale as english locale
*/
private boolean checkLocale(String loc) {
if(loc == null)
loc = /** assume english if locale is not given... */
ApplicationSettings.DEFAULT_LOCALE;
return ApplicationSettings.LANGUAGE.equals(loc);
}
}