package com.limegroup.gnutella.downloader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import junit.framework.Test;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import com.limegroup.gnutella.ActivityCallback;
import com.limegroup.gnutella.Downloader;
import com.limegroup.gnutella.Endpoint;
import com.limegroup.gnutella.ErrorService;
import com.limegroup.gnutella.FileDesc;
import com.limegroup.gnutella.FileManager;
import com.limegroup.gnutella.GUID;
import com.limegroup.gnutella.MessageRouter;
import com.limegroup.gnutella.PushEndpoint;
import com.limegroup.gnutella.RemoteFileDesc;
import com.limegroup.gnutella.RouterService;
import com.limegroup.gnutella.SaveLocationException;
import com.limegroup.gnutella.UDPService;
import com.limegroup.gnutella.URN;
import com.limegroup.gnutella.altlocs.AlternateLocation;
import com.limegroup.gnutella.altlocs.AlternateLocationCollection;
import com.limegroup.gnutella.messages.QueryRequest;
import com.limegroup.gnutella.settings.ConnectionSettings;
import com.limegroup.gnutella.stubs.ActivityCallbackStub;
import com.limegroup.gnutella.stubs.ConnectionManagerStub;
import com.limegroup.gnutella.stubs.DownloadManagerStub;
import com.limegroup.gnutella.stubs.FileDescStub;
import com.limegroup.gnutella.stubs.FileManagerStub;
import com.limegroup.gnutella.stubs.IncompleteFileDescStub;
import com.limegroup.gnutella.stubs.MessageRouterStub;
import com.limegroup.gnutella.util.CommonUtils;
import com.limegroup.gnutella.util.IpPort;
import com.limegroup.gnutella.util.IpPortImpl;
import com.limegroup.gnutella.util.PrivilegedAccessor;
public class ManagedDownloaderTest extends com.limegroup.gnutella.util.BaseTestCase {
private static final Log LOG = LogFactory.getLog(ManagedDownloaderTest.class);
final static int PORT=6666;
private DownloadManagerStub manager;
private FileManager fileman;
private ActivityCallback callback;
private MessageRouter router;
public ManagedDownloaderTest(String name) {
super(name);
}
public static void main(java.lang.String[] args) {
junit.textui.TestRunner.run(suite());
}
public static Test suite() {
return buildTestSuite(ManagedDownloaderTest.class);
}
public static void globalSetUp() throws Exception{
ConnectionManagerStub cmStub = new ConnectionManagerStub() {
public boolean isConnected() {
return true;
}
};
PrivilegedAccessor.setValue(RouterService.class,"manager",cmStub);
assertTrue(RouterService.isConnected());
}
public void setUp() throws Exception {
ConnectionSettings.EVER_ACCEPTED_INCOMING.setValue(true);
ConnectionSettings.LOCAL_IS_PRIVATE.setValue(false);
manager = new DownloadManagerStub();
fileman = new FileManagerStub();
callback = new ActivityCallbackStub();
router = new MessageRouterStub();
manager.initialize(callback, router, fileman);
RouterService.getAltlocManager().purge();
PrivilegedAccessor.setValue(RouterService.class,"callback",callback);
PrivilegedAccessor.setValue(RouterService.class,"router",router);
}
/**
* tests if firewalled altlocs are added to the file descriptor
* but not sent to uploaders. (i.e. the informMesh method)
*/
public void testFirewalledLocs() throws Exception {
LOG.info("testing firewalled locations");
PrivilegedAccessor.setValue(RouterService.getAcceptor(),
"_acceptedIncoming",new Boolean(true));
assertTrue(RouterService.acceptedIncomingConnection());
//first make sure we are sharing an incomplete file
URN partialURN =
URN.createSHA1Urn("urn:sha1:PLSTHIPQGSSZTS5FJUPAKUZWUGYQYPFE");
AlternateLocationCollection
col = AlternateLocationCollection.create(partialURN);
IncompleteFileDescStub partialDesc =
new IncompleteFileDescStub("incomplete",partialURN,3);
partialDesc.setAlternateLocationCollection(col);
Map urnMap = new HashMap(); urnMap.put(partialURN,partialDesc);
List descList = new LinkedList();descList.add(partialDesc);
File f = new File("incomplete");
Map fileMap = new HashMap();fileMap.put(f,partialDesc);
FileManagerStub newFMStub = new FileManagerStub(urnMap,descList);
newFMStub.setFiles(fileMap);
PrivilegedAccessor.setValue(RouterService.class,"fileManager",newFMStub);
// then create an rfd from a firewalled host
RemoteFileDesc rfd =
newPushRFD("incomplete","urn:sha1:PLSTHIPQGSSZTS5FJUPAKUZWUGYQYPFE",GUID.makeGuid());
//test that currently we have no altlocs for the incomplete file
FileDesc test = RouterService.getFileManager().getFileDescForUrn(partialURN);
assertEquals(0,RouterService.getAltlocManager().getNumLocs(test.getSHA1Urn()));
//add one fake downloader to the downloader list
Endpoint e = new Endpoint("1.2.3.5",12345);
RemoteFileDesc other = new RemoteFileDesc(newRFD("incomplete"),e);
AltlocDownloaderStub fakeDownloader = new AltlocDownloaderStub(other);
TestManagedDownloader md = new TestManagedDownloader(new RemoteFileDesc[]{rfd});
AltLocWorkerStub worker = new AltLocWorkerStub(md,rfd,fakeDownloader);
List l = new LinkedList();l.add(worker);
md.initialize(manager,newFMStub,callback);
md.setDloaders(l);
md.setSHA1(partialURN);
md.setFM(newFMStub);
md.setIncompleteFile(f);
//and see if it behaves correctly
md.informMesh(rfd,true);
//make sure the downloader did not get notified
assertFalse(fakeDownloader._addedFailed);
assertFalse(fakeDownloader._addedSuccessfull);
//the altloc should have been added to the file descriptor
test = RouterService.getFileManager().getFileDescForUrn(partialURN);
assertEquals(1,RouterService.getAltlocManager().getNumLocs(test.getSHA1Urn()));
//now repeat the test, pretending the uploader wants push altlocs
fakeDownloader.setWantsFalts(true);
//and see if it behaves correctly
md.informMesh(rfd,true);
//make sure the downloader did get notified
assertFalse(fakeDownloader._addedFailed);
assertTrue(fakeDownloader._addedSuccessfull);
//make sure the file was added to the file descriptor
test = RouterService.getFileManager().getFileDescForUrn(partialURN);
assertEquals(1,RouterService.getAltlocManager().getNumLocs(test.getSHA1Urn()));
//rince and repeat, saying this was a bad altloc.
//it should be sent to the other downloaders.
fakeDownloader._addedSuccessfull=false;
md.informMesh(rfd,false);
//make sure the downloader did get notified
assertTrue(fakeDownloader._addedFailed);
assertFalse(fakeDownloader._addedSuccessfull);
//make sure the altloc is demoted now
assertEquals(0,RouterService.getAltlocManager().getNumLocs(test.getSHA1Urn()));
PrivilegedAccessor.setValue(RouterService.getAcceptor(),
"_acceptedIncoming",new Boolean(false));
assertFalse(RouterService.acceptedIncomingConnection());
}
// requeries are gone now - now we only have user-driven queries (sort of
// like requeries but not automatic.
public void testNewRequery() throws Exception {
LOG.info("testing new requery");
RemoteFileDesc[] rfds={
newRFD("Susheel_Daswani_Neil_Daswani.txt",
"urn:sha1:GLSTHIPQGSSZTS5FJUPAKPZWUGYQYPFB"),
newRFD("Susheel/cool\\Daswani.txt",
"urn:sha1:LSTHGIPQGSSZTS5FJUPAKPZWUGYQYPFB"),
newRFD("Sumeet (Susheel) Anurag (Daswani)Chris.txt"),
newRFD("Susheel/cool\\Daswani.txt",
"urn:sha1:XXXXGIPQGXXXXS5FJUPAKPZWUGYQYPFB") //ignored
};
TestManagedDownloader downloader=new TestManagedDownloader(rfds);
QueryRequest qr=downloader.newRequery2();
assertNotNull("Couldn't make query", qr);
assertTrue(qr.getQuery().equals("neil daswani susheel") ||
qr.getQuery().equals("neil susheel daswani") ||
qr.getQuery().equals("daswani neil susheel") ||
qr.getQuery().equals("daswani susheel neil") ||
qr.getQuery().equals("susheel neil daswani") ||
qr.getQuery().equals("susheel daswani neil"));
// minspeed mask | firewalled | xml = 224
assertEquals(224, qr.getMinSpeed());
// the guid should be a lime guid but not a lime requery guid
assertTrue((GUID.isLimeGUID(qr.getGUID())) &&
!(GUID.isLimeRequeryGUID(qr.getGUID())));
assertTrue((qr.getRichQuery() == null) ||
(qr.getRichQuery().equals("")));
Set urns=qr.getQueryUrns();
assertEquals(0, urns.size());
UDPService.instance().setReceiveSolicited(true);
qr=downloader.newRequery2();
// minspeed mask | firewalled | xml | firewall transfer = 226
assertEquals(226, qr.getMinSpeed());
}
/** Catches a bug with earlier keyword intersection code. */
public void testNewRequery2() throws Exception {
LOG.info("testing new requery 2");
RemoteFileDesc[] rfds={ newRFD("LimeWire again LimeWire the 34") };
TestManagedDownloader downloader=new TestManagedDownloader(rfds);
QueryRequest qr=downloader.newRequery2();
assertEquals("limewire again", qr.getQuery());
assertEquals(new HashSet(), qr.getQueryUrns());
}
/** Tests that the progress is retained for deserialized downloaders. */
public void testSerializedProgress() throws Exception {
LOG.info("test serialized progress");
IncompleteFileManager ifm=new IncompleteFileManager();
RemoteFileDesc rfd=newRFD("some file.txt",FileDescStub.DEFAULT_URN.toString());
File incompleteFile=ifm.getFile(rfd);
int amountDownloaded=100;
VerifyingFile vf=new VerifyingFile(1024);
vf.addInterval(new Interval(0, amountDownloaded-1)); //inclusive
ifm.addEntry(incompleteFile, vf);
//Start downloader, make it sure requeries, etc.
ManagedDownloader downloader =
new ManagedDownloader(new RemoteFileDesc[] { rfd }, ifm, null);
downloader.initialize(manager, fileman, callback);
requestStart(downloader);
try { Thread.sleep(200); } catch (InterruptedException e) { }
//assertEquals(Downloader.WAITING_FOR_RESULTS, downloader.getState());
assertEquals(amountDownloaded, downloader.getAmountRead());
try {
//Serialize it!
ByteArrayOutputStream baos=new ByteArrayOutputStream();
ObjectOutputStream out=new ObjectOutputStream(baos);
out.writeObject(downloader);
out.flush(); out.close();
downloader.stop();
//Deserialize it as a different instance. Initialize.
ObjectInputStream in=new ObjectInputStream(
new ByteArrayInputStream(baos.toByteArray()));
downloader=(ManagedDownloader)in.readObject();
in.close();
downloader.initialize(manager, fileman, callback);
requestStart(downloader);
} catch (IOException e) {
fail("Couldn't serialize", e);
}
//Check same state as before serialization.
try {Thread.sleep(500);}catch(InterruptedException ignroed){}
//assertEquals(Downloader.WAITING_FOR_RESULTS, downloader.getState());
assertEquals(amountDownloaded, downloader.getAmountRead());
downloader.stop();
Thread.sleep(1000);
}
/** Tests that the progress is not 0% when resume button is hit while
* requerying. This was caused by the call to cleanup() from
* tryAllDownloads3() and was reported by Sam Berlin.
* Changed after requeries have been shut off (requery-expunge-branch).
*/
public void testRequeryProgress() throws Exception {
LOG.info("test requery progress");
TestUploader uploader=null;
ManagedDownloader downloader=null;
try {
//Start uploader and download.
uploader=new TestUploader("ManagedDownloaderTest", PORT);
uploader.stopAfter(100);
uploader.setSendThexTreeHeader(false);
uploader.setSendThexTree(false);
downloader=
new ManagedDownloader(
new RemoteFileDesc[] {newRFD("another testfile.txt",FileDescStub.DEFAULT_URN.toString())},
new IncompleteFileManager(), null);
downloader.initialize(manager,
fileman,
callback);
requestStart(downloader);
//Wait for it to download until error, need to wait
Thread.sleep(140000);
// no more auto requeries - so the download should be waiting for
// input from the user
assertEquals("should have read 100 bytes", 100,
downloader.getAmountRead());
assertEquals("should be waiting for user",
Downloader.WAITING_FOR_USER,
downloader.getState());
//Hit resume, make sure progress not erased.
downloader.resume();
try { Thread.sleep(2000); } catch (InterruptedException e) { }
// you would think we'd expect wating for results here, but instead
// we will wait for connections (since we have no one to query)
assertEquals(Downloader.WAITING_FOR_CONNECTIONS,
downloader.getState());
assertEquals(100,
downloader.getAmountRead());
} finally {
if (uploader!=null)
uploader.stopThread();
if (downloader!=null)
downloader.stop();
}
}
public void testSetSaveFileExceptions() throws Exception {
RemoteFileDesc[] rfds = new RemoteFileDesc[] { newRFD("download") };
File file = File.createTempFile("existing", "file");
file.deleteOnExit();
File noWritePermissionDir = null;
if (CommonUtils.isLinux()) {
noWritePermissionDir = new File("/");
}
else if (CommonUtils.isWindows()) {
// doesn't work on
// noWritePermissionDir = new File("C:\\WINDOWS\\");
}
if (noWritePermissionDir != null) {
try {
ManagedDownloader dl = new ManagedDownloader(rfds,
new IncompleteFileManager(), new GUID(GUID.makeGuid()),
noWritePermissionDir, "does not matter", false);
fail("No exception thrown for dir " + noWritePermissionDir);
}
catch (SaveLocationException sle) {
assertEquals("Should have no write permissions",
SaveLocationException.DIRECTORY_NOT_WRITEABLE,
sle.getErrorCode());
}
}
try {
ManagedDownloader dl = new ManagedDownloader(rfds,
new IncompleteFileManager(), new GUID(GUID.makeGuid()),
new File("/non existent directory"), null, false);
fail("No exception thrown");
}
catch (SaveLocationException sle) {
assertEquals("Error code should be: directory does not exist",
SaveLocationException.DIRECTORY_DOES_NOT_EXIST,
sle.getErrorCode());
}
try {
ManagedDownloader dl = new ManagedDownloader(rfds,
new IncompleteFileManager(), new GUID(GUID.makeGuid()),
file.getParentFile(), file.getName(), false);
fail("No exception thrown");
}
catch (SaveLocationException sle) {
assertEquals("Error code should be: file exists",
SaveLocationException.FILE_ALREADY_EXISTS,
sle.getErrorCode());
}
try {
// should not throw an exception because of overwrite
new ManagedDownloader(rfds, new IncompleteFileManager(),
new GUID(GUID.makeGuid()), file.getParentFile(), file.getName(),
true);
}
catch (SaveLocationException sle) {
fail("There shouldn't have been an exception of type " + sle.getErrorCode());
}
try {
File f = File.createTempFile("notadirectory", "file");
f.deleteOnExit();
ManagedDownloader dl = new ManagedDownloader(rfds,
new IncompleteFileManager(), new GUID(GUID.makeGuid()),
f, null, false);
fail("No exception thrown");
}
catch (SaveLocationException sle) {
assertEquals("Error code should be: file not a directory",
SaveLocationException.NOT_A_DIRECTORY,
sle.getErrorCode());
}
try {
ManagedDownloader dl = new ManagedDownloader(rfds,
new IncompleteFileManager(), new GUID(GUID.makeGuid()),
null, "./", false);
fail("No exception thrown");
}
catch (SaveLocationException sle) {
assertEquals("Error code should be: file not regular",
SaveLocationException.FILE_NOT_REGULAR,
sle.getErrorCode());
}
try {
ManagedDownloader dl = new ManagedDownloader(rfds,
new IncompleteFileManager(), new GUID(GUID.makeGuid()),
null, "../myfile.txt", false);
fail("No exception thrown");
}
catch (SaveLocationException sle) {
// assertEquals("Error code should be: security violation",
// SaveLocationException.SECURITY_VIOLATION,
// sle.getErrorCode());
}
try {
// should not throw an exception
new ManagedDownloader(rfds, new IncompleteFileManager(),
new GUID(GUID.makeGuid()), null, null, false);
}
catch (SaveLocationException sle) {
fail("There shouldn't have been an exception of type " + sle.getErrorCode());
}
// already downloading based on filename and same size
try {
manager.download(rfds, Collections.EMPTY_LIST,
new GUID(GUID.makeGuid()), false, null, null);
}
catch (SaveLocationException sle) {
fail("There should not have been an exception of type " + sle.getErrorCode());
}
try {
manager.download(rfds, Collections.EMPTY_LIST,
new GUID(GUID.makeGuid()), false, null, null);
fail("No exception thrown");
}
catch (SaveLocationException sle) {
assertEquals("Error code should be: already downloading",
SaveLocationException.FILE_ALREADY_DOWNLOADING,
sle.getErrorCode());
}
// already downloading based on hash
RemoteFileDesc[] hashRFDS = new RemoteFileDesc[] {
newRFD("dl", "urn:sha1:GLSTHIPQGSSZTS5FJUPAKPZWUGYQYPFB")
};
try {
manager.download(hashRFDS, Collections.EMPTY_LIST,
new GUID(GUID.makeGuid()), false, null, null);
}
catch (SaveLocationException sle) {
fail("There should not have been an exception of type " + sle.getErrorCode());
}
try {
manager.download(hashRFDS, Collections.EMPTY_LIST,
new GUID(GUID.makeGuid()), false, null, null);
fail("No exception thrown");
}
catch (SaveLocationException sle) {
assertEquals("Error code should be: already downloading",
SaveLocationException.FILE_ALREADY_DOWNLOADING,
sle.getErrorCode());
}
// other download is already being saved to the same file with different hashes
try {
RemoteFileDesc[] fds = new RemoteFileDesc[] {
newRFD("savedto", "urn:sha1:QLSTHIPQGSSZTS5FJUPAKOZWUGZQYPFB")
};
manager.download(fds, Collections.EMPTY_LIST,
new GUID(GUID.makeGuid()), false, null, "alreadysavedto");
}
catch (SaveLocationException sle) {
fail("There should not have been an exception of type " + sle.getErrorCode());
}
try {
RemoteFileDesc[] fds = new RemoteFileDesc[] {
newRFD("otherfd", "urn:sha1:PLSTHIPQGSSZTS5FJUPAKOZWUGZQYPFB")
};
manager.download(fds, Collections.EMPTY_LIST,
new GUID(GUID.makeGuid()), false, null, "alreadysavedto");
}
catch (SaveLocationException sle) {
assertEquals("Error code should be: already being saved to",
SaveLocationException.FILE_IS_ALREADY_DOWNLOADED_TO,
sle.getErrorCode());
}
// TODO SaveLocationException.FILE_ALREADY_SAVED
// SaveLocationException.FILESYSTEM_ERROR is not really reproducible
}
private static RemoteFileDesc newRFD(String name) {
return newRFD(name, null);
}
private static RemoteFileDesc newRFD(String name, String hash) {
Set urns=null;
if (hash!=null) {
urns=new HashSet(1);
try {
urns.add(URN.createSHA1Urn(hash));
} catch (IOException e) {
fail("Couldn't create URN", e);
}
}
return new RemoteFileDesc("127.0.0.1", PORT, 13l,
name, 1024,
new byte[16], 56, false, 4, true, null, urns,
false, false,"",0,null, -1);
}
private void requestStart(ManagedDownloader dl) throws Exception {
List waiting = (List)PrivilegedAccessor.getValue(manager, "waiting");
List active = (List)PrivilegedAccessor.getValue(manager, "active");
synchronized(manager) {
waiting.remove(dl);
active.add(dl);
dl.startDownload();
}
}
private static RemoteFileDesc newPushRFD(String name,String hash,byte [] guid)
throws Exception {
IpPort ppi =
new IpPortImpl("1.2.3.10",2000);
Set ppis = new TreeSet(IpPort.COMPARATOR);ppis.add(ppi);
PushEndpoint pe = new PushEndpoint(guid,ppis);
pe.updateProxies(true);
return new RemoteFileDesc(newRFD(name,hash),pe);
}
/** Provides access to protected methods. */
private static class TestManagedDownloader extends ManagedDownloader {
public TestManagedDownloader(RemoteFileDesc[] files) {
super(files, new IncompleteFileManager(), null);
}
public QueryRequest newRequery2() throws CantResumeException {
return super.newRequery(2);
}
/**
* allows overriding of the list of current downloaders
*/
public void setDloaders(List l) {
try {
PrivilegedAccessor.setValue(this,"_activeWorkers",l);
}catch(Exception e) {
ErrorService.error(e);
}
}
public void setSHA1(URN sha1) throws Exception{
PrivilegedAccessor.setValue(this,"downloadSHA1",sha1);
}
public void setFM(FileManager fm) throws Exception{
PrivilegedAccessor.setValue(this,"fileManager",fm);
}
public void setIncompleteFile(File f) throws Exception {
PrivilegedAccessor.setValue(this,"incompleteFile",f);
}
}
static class AltlocDownloaderStub extends HTTPDownloaderStub {
boolean _stubFalts;
RemoteFileDesc rfd;
public AltlocDownloaderStub(RemoteFileDesc fd){
super(fd,null);
rfd =fd;
}
public boolean _addedFailed,_addedSuccessfull;
public void addFailedAltLoc(AlternateLocation loc) {
_addedFailed = true;
}
public void addSuccessfulAltLoc(AlternateLocation loc) {
_addedSuccessfull=true;
}
public RemoteFileDesc getRemoteFileDesc() {
return rfd;
}
public void setWantsFalts(boolean doesIt) {
_stubFalts=doesIt;
}
public boolean wantsFalts(){
return _stubFalts;
}
}
static class AltLocWorkerStub extends DownloadWorkerStub {
AltlocDownloaderStub alt;
public AltLocWorkerStub(ManagedDownloader manager, RemoteFileDesc rfd, AltlocDownloaderStub alt) {
super(manager,rfd);
this.alt = alt;
}
public HTTPDownloader getDownloader() {
return alt;
}
}
}