package org.limewire.core.impl.browse;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.net.URI;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.ExecutionException;
import javax.swing.Icon;
import org.limewire.concurrent.ListeningFuture;
import org.limewire.core.api.URN;
import org.limewire.core.api.browse.BrowseListener;
import org.limewire.core.api.download.DownloadException;
import org.limewire.core.api.download.DownloadItem;
import org.limewire.core.api.download.DownloadListManager;
import org.limewire.core.api.download.DownloadState;
import org.limewire.core.api.search.SearchResult;
import org.limewire.core.impl.CoreGlueModule;
import org.limewire.core.impl.search.QueryReplyListenerList;
import org.limewire.friend.api.Friend;
import org.limewire.friend.api.FriendConnection;
import org.limewire.friend.api.FriendConnectionConfiguration;
import org.limewire.friend.api.FriendConnectionFactory;
import org.limewire.friend.api.FriendPresence;
import org.limewire.friend.api.Network;
import org.limewire.friend.api.RosterEvent;
import org.limewire.friend.api.feature.AddressFeature;
import org.limewire.friend.api.feature.FeatureEvent;
import org.limewire.gnutella.tests.LimeTestCase;
import org.limewire.inject.GuiceUtils;
import org.limewire.io.UnresolvedIpPort;
import org.limewire.lifecycle.ServiceRegistry;
import org.limewire.listener.EventListener;
import org.limewire.listener.ListenerSupport;
import org.limewire.logging.Log;
import org.limewire.logging.LogFactory;
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.Key;
import com.google.inject.Module;
import com.google.inject.Stage;
import com.google.inject.TypeLiteral;
import com.limegroup.gnutella.LimeWireCoreModule;
import com.limegroup.gnutella.SearchServices;
/**
*
* Test browse/downloads over a real life network.
*/
public class FriendBrowseDownloadRUDPTest extends LimeTestCase {
private static Log LOG = LogFactory.getLog(FriendBrowseDownloadRUDPTest.class);
private static final int SECONDS_TO_WAIT = 15;
private static final String USERNAME_1 = "limenetworktest1@gmail.com";
private static final String FRIEND = "limenetworktest2@gmail.com";
private static final String PASSWORD_1 = "limebuddy123";
private static final String SERVICE = "gmail.com";
private FriendConnection conn;
private ServiceRegistry registry;
protected FriendConnectionFactory friendConnectionFactory;
protected Injector injector;
public FriendBrowseDownloadRUDPTest(String name) {
super(name);
}
@Override
protected void setUp() throws Exception {
injector = createInjector(getModules());
registry = injector.getInstance(ServiceRegistry.class);
friendConnectionFactory = injector.getInstance(FriendConnectionFactory.class);
registry.initialize();
registry.start();
FriendConnectionConfiguration config = getDefaultXmppConnectionConfig(USERNAME_1, PASSWORD_1, SERVICE);
ListeningFuture<FriendConnection> loginTask = friendConnectionFactory.login(config);
try {
conn = loginTask.get(SECONDS_TO_WAIT, TimeUnit.SECONDS);
} catch (TimeoutException e) {
fail("Login attempt timed out");
} catch (ExecutionException e) {
fail("Error logging in", e.getCause());
}
}
@Override
public void tearDown() throws Exception {
conn.logout();
registry.stop();
}
private Injector createInjector(Module... modules) {
Injector injector = Guice.createInjector(Stage.DEVELOPMENT, modules);
GuiceUtils.loadEagerSingletons(injector);
return injector;
}
private FriendConnectionConfiguration getDefaultXmppConnectionConfig(final String userName, final String passwd,
final String serviceName) {
return new FriendConnectionConfiguration() {
@Override public boolean isDebugEnabled() { return true; }
@Override public String getUserInputLocalID() { return userName; }
@Override public String getPassword() { return passwd; }
@Override public String getLabel() { return getServiceName(); }
@Override public String getServiceName() { return serviceName; }
@Override public String getResource() { return "LimeWire"; }
@Override public EventListener<RosterEvent> getRosterListener() { return null; }
@Override public String getCanonicalizedLocalID() { return getUserInputLocalID(); }
@Override public String getNetworkName() { return getServiceName(); }
@Override public List<UnresolvedIpPort> getDefaultServers() { return UnresolvedIpPort.EMPTY_LIST;}
@Override public Type getType() { return Network.Type.XMPP;}
@Override public Icon getIcon() { return null;}
@Override
public Object getAttribute(String key) {
return null;
}
@Override
public void setAttribute(String key, Object property) {
}
};
}
private Module[] getModules() {
List<Module> modules = new ArrayList<Module>();
modules.add(new LimeWireCoreModule());
modules.add(new CoreGlueModule());
return modules.toArray(new Module[modules.size()]);
}
/**
* Tests browsing a friend, and downloading a file from one of the browse results
*
*/
public void testBrowseDownloadFromFriendBehindFirewall() throws Exception {
waitForFeature(AddressFeature.ID, FRIEND);
Collection<FriendPresence> presences = conn.getFriend(FRIEND).getPresences().values();
assertEquals(1, presences.size());
FriendPresence presence = presences.iterator().next();
SearchServices searchServices = injector.getInstance(SearchServices.class);
QueryReplyListenerList queryReplyListenerList = injector.getInstance(QueryReplyListenerList.class);
CoreBrowse coreBrowse = new CoreBrowse(presence, searchServices, queryReplyListenerList);
BrowseResultsCollector browser = new BrowseResultsCollector();
coreBrowse.start(browser);
Map<String, SearchResult> browseResults = waitForBrowse(browser);
// checking browse results
assertEquals(4, browseResults.size());
assertEquals("urn:sha1:M44YAZ2RDV2A5ZF4XLWGCTWIXE63F7M2", browseResults.get("2610431487_a7f2e127e7.jpg").getUrn().toString());
assertEquals("urn:sha1:DQCI4IOVHEHNR3BWOUKANM2S2AMVUVFN", browseResults.get("2611264598_f5b419bbd1.jpg").getUrn().toString());
assertEquals("urn:sha1:TGYN5XX62CLPKGMINUL2ZU3GVBMWTXEQ", browseResults.get("2611264634_c650e4fd81.jpg").getUrn().toString());
assertEquals("urn:sha1:FEZP6EIMEPRXUEHSRQNEBJB3JNYBUNEC", browseResults.get("2611264936_6103872e4d.jpg").getUrn().toString());
// test downloading from buddy
String fileNameToDownload = "2611264598_f5b419bbd1.jpg";
URN urnOfFile = browseResults.get(fileNameToDownload).getUrn();
SearchResult resultToDownload = browseResults.get(fileNameToDownload);
List<SearchResult> toDownload = Collections.singletonList(resultToDownload);
DownloadListManager downloader = injector.getInstance(DownloadListManager.class);
DownloadItem dlItem = downloader.getDownloadItem(urnOfFile);
try {
dlItem = downloader.addDownload(null, toDownload);
waitForDownloadCompletion(dlItem);
} catch (DownloadException e) {
e.printStackTrace();
fail("Failed to save the file: " + e.getMessage());
}
// check size of downloaded file
assertEquals(142682L, dlItem.getTotalSize());
}
private Map<String, SearchResult> waitForBrowse(BrowseResultsCollector browse) throws Exception {
return browse.waitForBrowseToFinish();
}
private void waitForDownloadCompletion(final DownloadItem dlItem) throws Exception {
// check to see if download is already done.
if (dlItem.getState() == DownloadState.DONE) {
return;
}
final CountDownLatch latch = new CountDownLatch(1);
int downloadTimeLimit = SECONDS_TO_WAIT;
dlItem.addPropertyChangeListener(new PropertyChangeListener() {
public void propertyChange(PropertyChangeEvent evt) {
if ("state".equals(evt.getPropertyName())) {
DownloadState state = (DownloadState) evt.getNewValue();
if (state == DownloadState.DONE) {
latch.countDown();
}
LOG.debugf("current download state: {0}", state);
}
}
});
assertTrue("Download not completed within " + downloadTimeLimit + " seconds",
latch.await(downloadTimeLimit, TimeUnit.SECONDS));
}
private void waitForFeature(final URI featureUri, final String friendId) throws Exception {
int featureWaitTimeout = SECONDS_TO_WAIT;
final CountDownLatch latch = new CountDownLatch(1);
final ListenerSupport<FeatureEvent> featureListener =
injector.getInstance(Key.get(new TypeLiteral<ListenerSupport<FeatureEvent>>() {
}));
final EventListener<FeatureEvent> listener = new EventListener<FeatureEvent>() {
public void handleEvent(FeatureEvent event) {
if (event.getType() == FeatureEvent.Type.ADDED &&
event.getData().getID().equals(featureUri) &&
event.getSource().getFriend().getId().equals(friendId)) {
latch.countDown();
featureListener.removeListener(this);
}
}
};
featureListener.addListener(listener);
// check for the feature in case it already came in prior to listener being added
Friend friend = conn.getFriend(friendId);
if (friend != null && !friend.getPresences().isEmpty()) {
// friend already signed in
FriendPresence presence = conn.getFriend(friendId).getPresences().values().iterator().next();
if (presence.getFeature(featureUri) != null) {
return;
}
}
// wait at most 5 seconds for feature to be detected
assertTrue("Feature " + featureUri + " not detected in " + featureWaitTimeout + " seconds.",
latch.await(featureWaitTimeout, TimeUnit.SECONDS));
}
private class BrowseResultsCollector implements BrowseListener {
private final Map<String, SearchResult> searchResults = new ConcurrentHashMap<String, SearchResult>();
private final CountDownLatch latch = new CountDownLatch(1);
public void handleBrowseResult(SearchResult searchResult) {
searchResults.put(searchResult.getFileName(), searchResult);
}
public void browseFinished(boolean success) {
latch.countDown();
}
public Map<String, SearchResult> waitForBrowseToFinish() throws InterruptedException {
assertTrue("Browse not completed within " + SECONDS_TO_WAIT + " seconds",
latch.await(SECONDS_TO_WAIT, TimeUnit.SECONDS));
return searchResults;
}
}
}