package com.limegroup.gnutella.malware;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.net.URI;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.AbortableHttpRequest;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.client.params.HttpClientParams;
import org.apache.http.params.HttpConnectionParams;
import org.apache.http.params.HttpParams;
import org.limewire.core.api.download.DownloadException;
import org.limewire.core.api.download.DownloadPiecesInfo;
import org.limewire.core.api.download.SaveLocationManager;
import org.limewire.core.api.file.CategoryManager;
import org.limewire.core.api.malware.AntivirusUpdateType;
import org.limewire.core.api.transfer.SourceInfo;
import org.limewire.core.settings.DownloadSettings;
import org.limewire.core.settings.SharingSettings;
import org.limewire.io.Address;
import org.limewire.io.GUID;
import org.limewire.io.IOUtils;
import org.limewire.io.InvalidDataException;
import org.limewire.listener.EventListener;
import org.limewire.listener.EventListenerList;
import org.limewire.logging.Log;
import org.limewire.logging.LogFactory;
import org.limewire.util.FileUtils;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.name.Named;
import com.limegroup.gnutella.BandwidthTrackerImpl;
import com.limegroup.gnutella.DownloadManager;
import com.limegroup.gnutella.InsufficientDataException;
import com.limegroup.gnutella.RemoteFileDesc;
import com.limegroup.gnutella.URN;
import com.limegroup.gnutella.downloader.AbstractCoreDownloader;
import com.limegroup.gnutella.downloader.DownloadStateEvent;
import com.limegroup.gnutella.downloader.DownloaderType;
import com.limegroup.gnutella.downloader.serial.DownloadMemento;
import com.limegroup.gnutella.http.HTTPHeaderName;
import com.limegroup.gnutella.http.HTTPUtils;
import com.limegroup.gnutella.http.HttpClientListener;
import com.limegroup.gnutella.http.HttpExecutor;
import com.limegroup.gnutella.util.LimeWireUtils;
class VirusDefinitionDownloaderImpl extends AbstractCoreDownloader implements VirusDefinitionDownloader {
private static final Log LOG =
LogFactory.getLog(VirusDefinitionDownloaderImpl.class);
private static final int TIMEOUT = 5000;
private static final String AVG_LW_USER_AGENT = "AVGINETSDK-LIMEWIRE";
private final DownloadManager downloadManager;
private final HttpExecutor httpExecutor;
private final EventListenerList<DownloadStateEvent> eventListenerList;
private final BandwidthTrackerImpl bandwidthTracker;
private final Provider<HttpParams> defaultParams;
private volatile URI uri = null;
private volatile File incompleteFile = null;
private volatile VirusDefinitionHandler handler = null;
private volatile DownloadState state = DownloadState.QUEUED;
private final AtomicReference<AbortableHttpRequest> activeRequest = new AtomicReference<AbortableHttpRequest>();
private volatile long contentLength = 0;
private volatile long amountRead = 0;
private volatile long amountWritten = 0;
private volatile boolean aborted = false;
private File mementoFile;
private File mementoBackupFile;
@Inject
public VirusDefinitionDownloaderImpl(SaveLocationManager saveLocationManager,
CategoryManager categoryManager,
DownloadManager downloadManager,
HttpExecutor httpExecutor,
@Named("defaults") Provider<HttpParams> defaultParams) {
super(saveLocationManager, categoryManager);
this.downloadManager = downloadManager;
this.httpExecutor = httpExecutor;
this.eventListenerList = new EventListenerList<DownloadStateEvent>();
this.bandwidthTracker = new BandwidthTrackerImpl();
this.defaultParams = defaultParams;
}
@Override
public synchronized void initFromMemento(DownloadMemento memento) throws InvalidDataException {
super.initFromMemento(memento);
VirusDefinitionDownloadMemento vm = (VirusDefinitionDownloadMemento)memento;
this.amountWritten = vm.getAmountWritten();
this.uri = vm.getUri();
this.incompleteFile = vm.getIncompleteFile();
DownloadSettings.NUM_AV_MEMENTOS_RESUMED.set(DownloadSettings.NUM_AV_MEMENTOS_RESUMED.get() + 1);
}
URI getUri() {
return uri;
}
long getAmountWritten() {
return amountWritten;
}
/** Gets the incomplete file this uses. */
File getIncompleteFile() {
return incompleteFile;
}
void setAmountWritten(long amountWritten) {
this.amountWritten = amountWritten;
}
@Override
public void setUriAndName(URI uri, String name) {
this.uri = uri;
setDefaultFileName(name);
incompleteFile = new File(SharingSettings.INCOMPLETE_DIRECTORY.get(), name);
}
@Override
public void fetch(VirusDefinitionHandler handler, File mementoFile, File mementoBackupFile) {
assert uri != null;
assert incompleteFile != null;
assert getDefaultFileName() != null;
if(LOG.isDebugEnabled())
LOG.debug("Fetching " + uri);
this.handler = handler;
this.mementoFile = mementoFile;
this.mementoBackupFile = mementoBackupFile;
downloadManager.addNewDownloader(this);
}
private void failDownload(DownloadState failureState) {
// If this was aborted, override the state.
if(aborted) {
failureState = DownloadState.ABORTED;
}
// if the prior state was active, then proceed to failure state.
if(state == DownloadState.QUEUED || state == DownloadState.DOWNLOADING) {
state = failureState;
if(handler != null) {
handler.downloadFailed(incompleteFile);
}
deleteIncompleteFiles();
eventListenerList.broadcast(new DownloadStateEvent(this, state));
downloadManager.remove(this, true);
}
}
@Override
public void discardUnscannedPreview(boolean delete) {
}
@Override
public long getAmountLost() {
return 0;
}
@Override
public int getAmountPending() {
return 0;
}
@Override
public long getAmountRead() {
return amountRead;
}
@Override
public long getAmountVerified() {
return 0;
}
@Override
public List<RemoteFileDesc> getRemoteFileDescs() {
return Collections.emptyList();
}
@Override
public int getChunkSize() {
return 1;
}
@Override
public long getContentLength() {
return contentLength;
}
@Override
public File getDownloadFragment(ScanListener listener) {
return null;
}
@Override
public File getFile() {
return getSaveFile();
}
@Override
public int getNumHosts() {
return 0;
}
@Override
public List<Address> getSourcesAsAddresses() {
return Collections.emptyList();
}
@Override
public List<SourceInfo> getSourcesDetails() {
return Collections.emptyList();
}
@Override
public int getQueuePosition() {
return 0;
}
@Override
public int getQueuedHostCount() {
return 0;
}
@Override
public int getRemainingStateTime() {
return 0;
}
@Override
public URN getSha1Urn() {
return null;
}
@Override
public File getSaveFile() {
return incompleteFile;
}
@Override
public void setSaveFile(File saveDirectory, String fileName,
boolean overwrite) throws DownloadException {
}
@Override
public DownloadState getState() {
return state;
}
@Override
public boolean isCompleted() {
return state == DownloadState.COMPLETE;
}
@Override
public boolean isLaunchable() {
return false;
}
@Override
public boolean isPaused() {
return false;
}
@Override
public boolean isRelocatable() {
return false;
}
@Override
public void pause() {
}
@Override
public boolean resume() {
return false;
}
@Override
public void stop() {
aborted = true;
AbortableHttpRequest req = activeRequest.getAndSet(null);
if(req != null) {
req.abort();
}
failDownload(DownloadState.ABORTED);
}
@Override
public float getAverageBandwidth() {
return bandwidthTracker.getAverageBandwidth();
}
@Override
public float getMeasuredBandwidth() throws InsufficientDataException {
return bandwidthTracker.getMeasuredBandwidth();
}
@Override
public void measureBandwidth() {
bandwidthTracker.measureBandwidth(amountRead);
}
@Override
public void finish() {
}
@Override
public GUID getQueryGUID() {
return null;
}
@Override
public void handleInactivity() {
}
@Override
public void initialize() {
}
@Override
public boolean isAlive() {
return state == DownloadState.DOWNLOADING;
}
@Override
public boolean isQueuable() {
return false;
}
@Override
public void setInactivePriority(int priority) {
}
@Override
public boolean shouldBeRemoved() {
return isCompleted() ||
state == DownloadState.ABORTED ||
state == DownloadState.INVALID ||
state == DownloadState.UNABLE_TO_CONNECT;
}
@Override
public boolean shouldBeRestarted() {
return state == DownloadState.QUEUED;
}
@Override
public void startDownload() {
HttpGet get = new HttpGet(uri.toString());
get.addHeader("User-Agent", AVG_LW_USER_AGENT + " " + LimeWireUtils.getLimeWireVersion());
get.addHeader(HTTPHeaderName.CONNECTION.httpStringValue(), "close");
if(amountWritten > 0) {
LOG.debugf("Adding Range header starting at {0}", amountWritten);
get.addHeader(HTTPHeaderName.RANGE.httpStringValue(), "bytes=" + amountWritten + "-");
}
// LOG.debugf("Sending GET {0}, {1}", get.getRequestLine(), Arrays.asList(get.getAllHeaders()));
HttpParams params = defaultParams.get();
HttpConnectionParams.setConnectionTimeout(params, TIMEOUT);
HttpConnectionParams.setSoTimeout(params, TIMEOUT);
HttpClientParams.setRedirecting(params, true);
state = DownloadState.DOWNLOADING;
activeRequest.set(get);
eventListenerList.broadcast(new DownloadStateEvent(this, state));
httpExecutor.execute(get, params, new Listener());
}
@Override
public DownloaderType getDownloadType() {
return DownloaderType.ANTIVIRUS;
}
@Override
public void addListener(EventListener<DownloadStateEvent> listener) {
eventListenerList.addListener(listener);
}
@Override
public boolean removeListener(EventListener<DownloadStateEvent> listener) {
return eventListenerList.removeListener(listener);
}
@Override
public boolean isMementoSupported() {
return false;
}
@Override
protected DownloadMemento createMemento() {
VirusDefinitionDownloadMemento memento = new VirusDefinitionDownloadMemento();
memento.setUri(uri);
memento.setAmountWritten(amountWritten);
memento.setIncompleteFile(incompleteFile);
memento.setAntivirusUpdateType(AntivirusUpdateType.FULL);
return memento;
}
@Override
protected void fillInMemento(DownloadMemento memento) {
super.fillInMemento(memento);
// override some default values.
memento.setSaveFile(getSaveFile());
memento.setDefaultFileName(getDefaultFileName());
}
@Override
protected File getDefaultSaveFile() {
return getSaveFile();
}
@Override
public boolean conflicts(URN urn, long fileSize, File... files) {
return false;
}
@Override
public boolean conflictsWithIncompleteFile(File incomplete) {
return false;
}
@Override
public void deleteIncompleteFiles() {
if(incompleteFile != null) {
FileUtils.forceDelete(incompleteFile);
}
if(mementoFile != null) {
FileUtils.forceDelete(mementoFile);
}
if(mementoBackupFile != null) {
FileUtils.forceDelete(mementoBackupFile);
}
}
private void saveMemento() {
if(mementoFile != null) {
LOG.debugf("Writing memento to {0}", mementoFile);
DownloadMemento memento = toMemento();
FileUtils.writeWithBackupFile(memento, mementoBackupFile, mementoFile, LOG);
}
}
@Override
public DownloadPiecesInfo getPieceInfo() {
return null;
}
private class Listener implements HttpClientListener {
@Override
public boolean requestFailed(HttpUriRequest method, HttpResponse response, IOException e) {
activeRequest.set(null);
failDownload(DownloadState.UNABLE_TO_CONNECT);
return false;
}
@Override
public boolean requestComplete(HttpUriRequest method, HttpResponse response) {
try {
int status = response.getStatusLine().getStatusCode();
if (status < 200 || status >= 300) {
LOG.debugf("Bad status code: {0}", status);
failDownload(DownloadState.UNABLE_TO_CONNECT);
return false;
}
HttpEntity entity = response.getEntity();
if (entity == null) {
LOG.error("Invalid response: no entity");
failDownload(DownloadState.INVALID);
return false;
}
long length = entity.getContentLength();
contentLength = length > 0 ? length : 0;
incompleteFile.getParentFile().mkdirs();
InputStream downloadStream = null;
RandomAccessFile outputStream = null;
boolean downloadCompleted = false;
try {
long startPoint = HTTPUtils.getStartPoint(response);
if(startPoint > amountWritten) {
throw new IOException("must start at or before amount written");
}
LOG.debugf("Seeking ahead to {0}, amountWritten was {1}", startPoint, amountWritten);
outputStream = new RandomAccessFile(incompleteFile, "rw");
outputStream.seek(startPoint);
amountWritten = startPoint;
downloadStream = entity.getContent();
int read = 0;
byte[] buf = new byte[2048];
long lastRecord = System.currentTimeMillis();
while ((read = downloadStream.read(buf, 0, buf.length)) != -1) {
amountRead += read;
outputStream.write(buf, 0, read);
amountWritten += read;
long now = System.currentTimeMillis();
if(TimeUnit.MILLISECONDS.toSeconds(now - lastRecord) > 1) {
// If more than a second has passed, since we last
// recorded, save a memento of our progress.
lastRecord = now;
saveMemento();
}
}
downloadCompleted = true;
} catch (IOException e) {
LOG.debugf(e, "error downloading virus definitions from {0}", uri);
} finally {
IOUtils.close(downloadStream);
IOUtils.close(outputStream);
}
if (downloadCompleted) {
//handler.downloadSucceeded() applies the update and takes some time. Broadcast the applying state first.
state = DownloadState.APPLYING_ANTIVIRUS_DEFINITION_UPDATE;
eventListenerList.broadcast(new DownloadStateEvent(VirusDefinitionDownloaderImpl.this, state));
handler.downloadSucceeded(incompleteFile);
//we are complete now that the update has been applied
state = DownloadState.COMPLETE;
deleteIncompleteFiles();
eventListenerList.broadcast(new DownloadStateEvent(VirusDefinitionDownloaderImpl.this, state));
downloadManager.remove(VirusDefinitionDownloaderImpl.this, true);
} else {
failDownload(DownloadState.INVALID);
}
return false;
} finally {
httpExecutor.releaseResources(response);
activeRequest.set(null);
}
}
@Override
public boolean allowRequest(HttpUriRequest request) {
return true;
}
}
}