package com.limegroup.gnutella.downloader;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.TreeMap;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.limewire.io.GUID;
import org.limewire.io.NetworkInstanceUtils;
import com.google.inject.Inject;
import com.limegroup.gnutella.RemoteFileDesc;
/** Keeps track of who needs a push, and who should be notified of of success or failure. */
public class PushList {
private static final Log LOG = LogFactory.getLog(PushList.class);
/** Map of clientGUID -> List of potential pushes with that GUID. */
private final TreeMap<byte[], List<Push>> pushers = new TreeMap<byte[], List<Push>>(GUID.GUID_BYTE_COMPARATOR);
private final NetworkInstanceUtils networkInstanceUtils;
@Inject
public PushList(NetworkInstanceUtils networkInstanceUtils) {
this.networkInstanceUtils = networkInstanceUtils;
}
/** Adds a host that wants to be notified of a push. */
public void addPushHost(PushDetails details, HTTPConnectObserver observer) {
if(LOG.isDebugEnabled())
LOG.debug("Adding observer for details: " + details);
synchronized(pushers) {
byte[] clientGUID = details.getClientGUID();
List<Push> perGUID = pushers.get(clientGUID);
if(perGUID == null) {
perGUID = new LinkedList<Push>();
pushers.put(clientGUID, perGUID);
}
perGUID.add(new Push(details, observer));
}
}
/** Returns the exact observer that was added with this PushDetails. */
public HTTPConnectObserver getExactHostFor(PushDetails details) {
if(LOG.isDebugEnabled())
LOG.debug("Retrieving exact match for details: " + details);
synchronized(pushers) {
byte[] clientGUID = details.getClientGUID();
List<Push> perGUID = pushers.get(clientGUID);
if(perGUID == null) {
LOG.debug("No pushes waiting on those exact details.");
return null;
} else {
Push best = getExactHost(perGUID, details);
if(perGUID.isEmpty())
pushers.remove(clientGUID);
if(best != null)
return best.observer;
else
return null;
}
}
}
/** Returns an HTTPConnectObserver for the given clientGUID & Socket. */
public HTTPConnectObserver getHostFor(byte[] clientGUID, String address) {
if(LOG.isDebugEnabled())
LOG.debug("Retrieving best match for address: " + address + ", guid: " + new GUID(clientGUID));
synchronized(pushers) {
List<Push> perGUID = pushers.get(clientGUID);
if(perGUID == null) {
LOG.debug("No pushes waiting on that GUID.");
return null;
} else {
Push best = getBestHost(perGUID, address);
if(perGUID.isEmpty())
pushers.remove(clientGUID);
if(best != null)
return best.observer;
else
return null;
}
}
}
/** Returns all existing HTTPConnectObservers and clears the list. */
public List<HTTPConnectObserver> getAllAndClear() {
List<HTTPConnectObserver> allConnectors = new LinkedList<HTTPConnectObserver>();
synchronized(pushers) {
for(List<Push> list : pushers.values()) {
if(list != null) {
for(Push next : list) {
allConnectors.add(next.observer);
}
}
}
pushers.clear();
}
return allConnectors;
}
/** Returns the first matching Push in the list, or a random one if none match. */
private Push getBestHost(List<? extends Push> hosts, String address) {
if(hosts.isEmpty())
return null;
// First try to find one that exactly matches the IP address.
for(Iterator<? extends Push> i = hosts.iterator(); i.hasNext(); ) {
Push next = i.next();
if(next.details.getAddress().equals(address)) {
LOG.debug("Found an exact match!");
i.remove();
return next;
}
}
// Then try and find the first private address.
LOG.debug("No exact match, using first private|bogus address.");
for(Iterator<? extends Push> i = hosts.iterator(); i.hasNext();) {
Push next = i.next();
if(networkInstanceUtils.isPrivateAddress(next.details.getAddress()) ||
next.details.getAddress().equals(RemoteFileDesc.BOGUS_IP)) {
i.remove();
return next;
}
}
LOG.debug("No private address to use!");
return null;
}
/** Returns the exact Push in the list. */
private Push getExactHost(List<? extends Push> hosts, PushDetails details) {
if(hosts.isEmpty())
return null;
// First try to find one that exactly matches the IP address.
for(Iterator<? extends Push> i = hosts.iterator(); i.hasNext(); ) {
Push next = i.next();
if(next.details.equals(details)) {
i.remove();
return next;
}
}
LOG.debug("No exact match!");
return null;
}
/** A push-type struct. */
private static class Push {
private final PushDetails details;
private final HTTPConnectObserver observer;
Push(PushDetails details, HTTPConnectObserver observer) {
this.details = details;
this.observer = observer;
}
}
}