package com.limegroup.gnutella; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import org.limewire.collection.BucketQueue; import org.limewire.core.settings.ApplicationSettings; import com.google.inject.Inject; import com.google.inject.Singleton; import com.limegroup.gnutella.messages.PingReply; /** * This class caches pongs from the network. Caching pongs saves considerable * bandwidth because only a controlled number of pings are sent to maintain * adequate host data, with Ultrapeers caching and responding to pings with * the best pongs available. */ @Singleton public final class PongCacherImpl implements PongCacher { /** * <tt>BucketQueue</tt> holding pongs separated by hops. * The map is of String (locale) to BucketQueue (Pongs per Hop) */ private final Map<String, BucketQueue<PingReply>> PONGS = new HashMap<String, BucketQueue<PingReply>>(); private final ConnectionServices connectionServices; @Inject public PongCacherImpl(ConnectionServices connectionServices) { this.connectionServices = connectionServices; } /** * Accessor for testing. */ Map<String, BucketQueue<PingReply>> getPongMap() { return PONGS; } /** * Accessor for the <tt>Set</tt> of cached pongs. This <tt>List</tt> * is unmodifiable and will throw <tt>IllegalOperationException</tt> if * it is modified. * * @return the <tt>List</tt> of cached pongs -- continually updated */ public List<PingReply> getBestPongs(String loc) { synchronized(PONGS) { List<PingReply> pongs = new LinkedList<PingReply>(); //list to return long curTime = System.currentTimeMillis(); //first we try to populate "pongs" with those pongs //that match the locale List<PingReply> removeList = addBestPongs(loc, pongs, curTime, 0); //remove all stale pongs that were reported for the //locale removePongs(loc, removeList); //if the locale that we were searching for was not the default //"en" locale and we do not have enough pongs in the list //then populate the list "pongs" with the default locale pongs if(!ApplicationSettings.DEFAULT_LOCALE.get().equals(loc) && pongs.size() < NUM_HOPS) { //get the best pongs for default locale removeList = addBestPongs(ApplicationSettings.DEFAULT_LOCALE.get(), pongs, curTime, pongs.size()); //remove any pongs that were reported as stale pongs removePongs(ApplicationSettings.DEFAULT_LOCALE.get(), removeList); } return pongs; } } /** * adds good pongs to the passed in list "pongs" and * return a list of pongs that should be removed. */ private List<PingReply> addBestPongs(String loc, List<PingReply> pongs, long curTime, int hops) { //set the expire time to be used. //if the locale that is passed in is "en" then just use the //normal expire time otherwise use the longer expire time //so we can have some memory of non english locales int exp_time = (ApplicationSettings.DEFAULT_LOCALE.get().equals(loc))? EXPIRE_TIME : EXPIRE_TIME_LOC; //check if there are any pongs of the specific locale stored //in PONGS. List<PingReply> remove = null; if(PONGS.containsKey(loc)) { //get all the pongs that are of the specific locale and //make sure that they are not stale BucketQueue<PingReply> bq = PONGS.get(loc); Iterator<PingReply> iter = bq.iterator(); for(;iter.hasNext() && hops < NUM_HOPS; hops++) { PingReply pr = iter.next(); //if the pongs are stale put into the remove list //to be returned. Didn't pass in the remove list //into this function because we may never see stale //pongs so we won't need to new a linkedlist //this may be a premature and unnecessary opt. if(curTime - pr.getCreationTime() > exp_time) { if(remove == null) remove = new LinkedList<PingReply>(); remove.add(pr); } else { pongs.add(pr); } } } return remove; } /** * removes the pongs with the specified locale and those * that are in the passed in list l */ private void removePongs(String loc, List<PingReply> l) { if(l != null) { BucketQueue<PingReply> bq = PONGS.get(loc); for(PingReply pr : l) { bq.removeAll(pr); } } } /** * Adds the specified <tt>PingReply</tt> instance to the cache of pongs. * * @param pr the <tt>PingReply</tt> to add */ public void addPong(PingReply pr) { // if we're not an Ultrapeer, we don't care about caching the pong if (!connectionServices.isSupernode()) return; // Make sure we don't cache pongs that aren't from Ultrapeers. if(!pr.isUltrapeer()) return; // if the hops are too high, ignore it if(pr.getHops() >= NUM_HOPS) return; synchronized(PONGS) { //check the map for the locale and create or retrieve the set if(PONGS.containsKey(pr.getClientLocale())) { BucketQueue<PingReply> bq = PONGS.get(pr.getClientLocale()); bq.insert(pr, pr.getHops()); } else { BucketQueue<PingReply> bq = new BucketQueue<PingReply>(NUM_HOPS, NUM_PONGS_PER_HOP); bq.insert(pr, pr.getHops()); PONGS.put(pr.getClientLocale(), bq); } } } }