package com.limegroup.gnutella.altlocs; import java.io.IOException; import java.util.Set; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.limewire.io.GUID; import org.limewire.io.IpPort; import org.limewire.listener.EventListener; import com.google.inject.Inject; import com.google.inject.Singleton; import com.google.inject.name.Named; import com.limegroup.gnutella.DownloadManagerEvent; import com.limegroup.gnutella.PushEndpoint; import com.limegroup.gnutella.URN; import com.limegroup.gnutella.Downloader.DownloadState; import com.limegroup.gnutella.browser.MagnetOptions; import com.limegroup.gnutella.dht.db.PushEndpointService; import com.limegroup.gnutella.dht.db.SearchListener; import com.limegroup.gnutella.downloader.CoreDownloader; import com.limegroup.gnutella.downloader.DownloadStateEvent; import com.limegroup.gnutella.downloader.MagnetDownloader; /** * Listens for {@link DownloadManagerEvent} and registers itself as an event listener * on {@link MagnetDownloader}. After registration and when a downloader goes into * {@link DownloadState#QUEUED}, it will peform a search for alternate locations * asking its {@link PushEndpointManagerImpl} for endpoints. * <p> * Push endpoints where the external address and port equal its only push proxy * are assumed to be non-firewalled hosts and a {@link DirectAltLoc} is created * for them. */ @Singleton public class DownloaderGuidAlternateLocationFinder implements EventListener<DownloadManagerEvent> { private static final Log LOG = LogFactory.getLog(DownloaderGuidAlternateLocationFinder.class); private final PushEndpointService pushEndpointManager; private final AlternateLocationFactory alternateLocationFactory; private final AltLocManager altLocManager; /** * Package access for testing. */ final EventListener<DownloadStateEvent> downloadStatusListener = new EventListener<DownloadStateEvent>() { public void handleEvent(DownloadStateEvent event) { if (LOG.isDebugEnabled()) { LOG.debug("per download event received: " + event); } handleStatusEvent(event); } }; @Inject public DownloaderGuidAlternateLocationFinder(@Named("pushEndpointManager") PushEndpointService pushEndpointManager, AlternateLocationFactory alternateLocationFactory, AltLocManager altLocManager) { this.pushEndpointManager = pushEndpointManager; this.alternateLocationFactory = alternateLocationFactory; this.altLocManager = altLocManager; } public void handleEvent(DownloadManagerEvent event) { if (LOG.isDebugEnabled()) { LOG.debug("event received: " + event); } switch (event.getType()) { case ADDED: CoreDownloader downloader = event.getData(); if (downloader instanceof MagnetDownloader) { MagnetDownloader magnetDownloader = (MagnetDownloader)downloader; // subscribe for status events so we can search when waiting for user magnetDownloader.addListener(downloadStatusListener); } break; case REMOVED: downloader = event.getData(); if (downloader instanceof MagnetDownloader) { downloader.removeListener(downloadStatusListener); } break; } } private long getSize(MagnetDownloader downloader) { long size = downloader.getMagnet().getFileSize(); if (size == -1) { size = downloader.getContentLength(); } return size; } private void searchForPushEndpoints(MagnetDownloader magnetDownloader) { MagnetOptions magnet = magnetDownloader.getMagnet(); URN sha1Urn = magnet.getSHA1Urn(); if (sha1Urn == null) { LOG.debug("no sha1 urn"); return; } long size = getSize(magnetDownloader); if (size == -1) { LOG.debug("no file size"); return; } searchForPushEndpoints(sha1Urn, magnet.getGUIDUrns()); } void handleStatusEvent(DownloadStateEvent event) { switch (event.getType()) { case GAVE_UP: case WAITING_FOR_USER: CoreDownloader downloader = event.getSource(); if (downloader instanceof MagnetDownloader) { MagnetDownloader magnetDownloader = (MagnetDownloader)downloader; searchForPushEndpoints(magnetDownloader); } break; } } void searchForPushEndpoints(URN sha1Urn, Set<URN> guidUrns) { if (LOG.isDebugEnabled()) { LOG.debug("Searching for guid urns: " + guidUrns); } for (URN guidUrn : guidUrns) { try { GUID guid = new GUID(guidUrn.getNamespaceSpecificString()); pushEndpointManager.findPushEndpoint(guid, new PushendpointHandler(sha1Urn)); } catch (IllegalArgumentException iae) { if (LOG.isErrorEnabled()) { LOG.error("invalid hex string of guid", iae); } } } } private class PushendpointHandler implements SearchListener<PushEndpoint> { private final URN sha1; public PushendpointHandler(URN sha1Urn) { this.sha1 = sha1Urn; } public void handleResult(PushEndpoint result) { if (LOG.isDebugEnabled()) { LOG.debug("endpoint found: " + result); } // TODO instantly create alternate locations for all sha1s that have the same guid urn as a source IpPort ipPort = result.getValidExternalAddress(); // if the external address is the same as the push proxy, it's a non-firewalled source if (ipPort != null && result.getProxies().size() == 1 && result.getProxies().contains(ipPort)) { try { LOG.debug("creating direct altloc"); AlternateLocation alternateLocation = alternateLocationFactory.createDirectAltLoc(ipPort, sha1); // adding to alt loc manager will notify the downloader of the new alternate location altLocManager.add(alternateLocation, null); } catch (IOException ie) { if (LOG.isErrorEnabled()) { LOG.error("error creating direct alt loc from " + ipPort, ie); } } } else { LOG.debug("creating push altloc"); AlternateLocation alternateLocation = alternateLocationFactory.createPushAltLoc(result, sha1); // adding to alt loc manager will notify the downloader of the new alternate location altLocManager.add(alternateLocation, null); } } public void searchFailed() { // ignoring unsuccessful searches } } }