package org.limewire.core.impl.upload;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import org.limewire.core.api.Category;
import org.limewire.core.api.FilePropertyKey;
import org.limewire.core.api.URN;
import org.limewire.core.api.endpoint.RemoteHost;
import org.limewire.core.api.file.CategoryManager;
import org.limewire.core.api.transfer.SourceInfo;
import org.limewire.core.api.upload.UploadItem;
import org.limewire.core.api.upload.UploadState;
import org.limewire.core.impl.util.FilePropertyKeyPopulator;
import org.limewire.friend.api.FriendPresence;
import org.limewire.friend.api.feature.LimewireFeature;
import org.limewire.listener.SwingSafePropertyChangeSupport;
import org.limewire.util.FileUtils;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
import com.limegroup.bittorrent.BTUploader;
import com.limegroup.gnutella.InsufficientDataException;
import com.limegroup.gnutella.Uploader;
import com.limegroup.gnutella.Uploader.UploadStatus;
import com.limegroup.gnutella.library.FileDesc;
import com.limegroup.gnutella.uploader.UploadType;
class CoreUploadItem implements UploadItem {
static interface Factory {
CoreUploadItem create(@Assisted Uploader uploader, @Assisted FriendPresence friendPresence);
}
private final Uploader uploader;
private final FriendPresence friendPresence;
private final PropertyChangeSupport support = new SwingSafePropertyChangeSupport(this);
private final CategoryManager categoryManager;
public final static long UNKNOWN_TIME = Long.MAX_VALUE;
private final UploadItemType uploadItemType;
private boolean isFinished = false;
private UploadRemoteHost uploadRemoteHost;
private final long startTime;
private volatile long totalAmountUploaded;
private volatile float uploadSpeed;
@Inject
CoreUploadItem(@Assisted Uploader uploader, @Assisted FriendPresence friendPresence, CategoryManager categoryManager) {
this.categoryManager = categoryManager;
this.uploader = uploader;
this.friendPresence = friendPresence;
uploadItemType = uploader instanceof BTUploader ? UploadItemType.BITTORRENT : UploadItemType.GNUTELLA;
startTime = System.currentTimeMillis();
}
@Override
public void cancel() {
uploader.stop();
fireDataChanged();
}
@Override
public String getFileName() {
return uploader.getFileName();
}
@Override
public long getFileSize() {
return uploader.getFileSize();
}
Uploader getUploader() {
return uploader;
}
@Override
public UploadState getState() {
switch (getUploaderStatus()) {
case CANCELLED:
return UploadState.CANCELED;
case COMPLETE:
if(uploader.getUploadType() == UploadType.BROWSE_HOST){
return UploadState.BROWSE_HOST_DONE;
}
return UploadState.DONE;
case CONNECTING:
case UPLOADING:
case THEX_REQUEST:
case PUSH_PROXY:
case UPDATE_FILE:
return UploadState.UPLOADING;
case QUEUED:
return UploadState.QUEUED;
case PAUSED:
return UploadState.PAUSED;
case INTERRUPTED:
case FILE_NOT_FOUND:
case UNAVAILABLE_RANGE:
case MALFORMED_REQUEST:
return UploadState.REQUEST_ERROR;
case LIMIT_REACHED:
case BANNED_GREEDY:
case FREELOADER:
return UploadState.LIMIT_REACHED;
case BROWSE_HOST:
return UploadState.BROWSE_HOST;
}
throw new IllegalStateException("Unknown Upload status : " + uploader.getState());
}
private UploadStatus getUploaderStatus() {
// Revert to the prior state if we are at an intermediate "complete"
// state. (This means that a particular chunk finished, but more
// will come.)
// We use isFinished to tell us when it's finished, because that is
// set when finish() is called, which is only called when the entire
// upload has finished.
// The intermediate connecting state is okay even if bytes have been
// read because getState() reports it as an uploading state.
UploadStatus state = uploader.getState();
UploadStatus lastState = uploader.getLastTransferState();
if (state == UploadStatus.COMPLETE && !isFinished) {
state = lastState;
}
// Reset the current state to be the lastState if we're complete now,
// but our last transfer wasn't uploading, queued, or thex.
if(uploader.getUploadType() != UploadType.BROWSE_HOST &&
state == UploadStatus.COMPLETE &&
lastState != UploadStatus.UPLOADING &&
lastState != UploadStatus.QUEUED &&
lastState != UploadStatus.THEX_REQUEST) {
state = lastState;
}
return state;
}
@Override
public long getTotalAmountUploaded() {
return totalAmountUploaded;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((uploader == null) ? 0 : uploader.hashCode());
return result;
}
/**
* Tests if the Uploaders from construction are equal
*/
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
CoreUploadItem other = (CoreUploadItem) obj;
if (uploader == null) {
if (other.uploader != null)
return false;
} else if (!uploader.equals(other.uploader))
return false;
return true;
}
@Override
public void addPropertyChangeListener(PropertyChangeListener listener) {
support.addPropertyChangeListener(listener);
}
@Override
public void removePropertyChangeListener(PropertyChangeListener listener) {
support.removePropertyChangeListener(listener);
}
void fireDataChanged() {
support.firePropertyChange("state", null, getState());
}
/**
* Updates amount uploaded and upload speed.
*/
void refresh() {
// Refresh amount uploaded.
totalAmountUploaded = uploader.getTotalAmountUploaded();
// Refresh upload speed.
try {
uploadSpeed = uploader.getMeasuredBandwidth();
} catch (InsufficientDataException e) {
uploadSpeed = 0;
}
// Fire event to update UI.
fireDataChanged();
}
@Override
public Category getCategory() {
if(uploadItemType == UploadItemType.BITTORRENT)
return Category.TORRENT;
else
return categoryManager.getCategoryForFilename(getFileName());
}
@Override
public RemoteHost getRemoteHost() {
if(uploadRemoteHost == null)
uploadRemoteHost = new UploadRemoteHost();
return uploadRemoteHost;
}
@Override
public BrowseType getBrowseType(){
if (getState() != UploadState.BROWSE_HOST && getState() != UploadState.BROWSE_HOST_DONE){
return BrowseType.NONE;
}
if ("".equals(getFileName())){
return BrowseType.P2P;
}
return BrowseType.FRIEND;
}
@Override
public String toString(){
return "CoreUploadItem: " + getFileName() + ", " + getState();
}
@Override
public int getQueuePosition() {
return uploader.getQueuePosition();
}
@Override
public float getUploadSpeed() {
return uploadSpeed;
}
@Override
public long getRemainingUploadTime() {
float speed = getUploadSpeed();
if (speed > 0) {
double remaining = (getFileSize() - getTotalAmountUploaded()) / 1024.0;
return (long) (remaining / speed);
} else {
return UNKNOWN_TIME;
}
}
@Override
public Object getProperty(FilePropertyKey property) {
FileDesc fd = uploader.getFileDesc();
switch(property) {
case NAME:
return (fd == null) ? null : FileUtils.getFilenameNoExtension(fd.getFileName());
case DATE_CREATED:
if(fd == null) {
return null;
} else {
long ct = fd.lastModified();
return ct == -1 ? null : ct;
}
case FILE_SIZE:
return (fd == null) ? null : fd.getFileSize();
case TORRENT:
if(UploadItemType.BITTORRENT == uploadItemType) {
BTUploader btUploader = (BTUploader)uploader;
return btUploader.getTorrent();
} else {
return null;
}
case USERAGENT:
return uploader.getUserAgent();
default:
if(fd == null) {
return null;
} else {
Category category = categoryManager.getCategoryForFilename(fd.getFileName());
return FilePropertyKeyPopulator.get(category, property, fd.getXMLDocument());
}
}
}
@Override
public String getPropertyString(FilePropertyKey key) {
Object value = getProperty(key);
if (value != null) {
String stringValue = value.toString();
return stringValue;
} else {
return null;
}
}
@Override
public URN getUrn() {
com.limegroup.gnutella.URN urn = uploader.getUrn();
if(urn != null) {
return urn;
}
return null;
}
@Override
public Collection<File> getCompleteFiles() {
List<File> files = new ArrayList<File>();
if (uploader instanceof BTUploader) {
BTUploader btUploader = (BTUploader) uploader;
files.addAll(btUploader.getCompleteFiles());
} else {
files.add(uploader.getFile());
}
return files;
}
@Override
public File getFile() {
return uploader.getFile();
}
@Override
public String getRenderName() {
return friendPresence.getFriend().getRenderName();
}
@Override
public UploadItemType getUploadItemType() {
return uploadItemType;
}
@Override
public int getNumUploadConnections() {
return uploader.getNumUploadConnections();
}
@Override
public long getStartTime() {
return startTime;
}
/**
* Called when upload is finished. This enables the DONE state. This method
* is necessary to present false DONE states.
*/
void finish(){
isFinished = true;
fireDataChanged();
}
@Override
public float getSeedRatio() {
return uploader.getSeedRatio();
}
@Override
public boolean isFinished() {
if (uploader instanceof BTUploader) {
// Return torrent indicator.
return ((BTUploader) uploader).getTorrent().isFinished();
} else {
// Determine using state for non-torrents.
UploadState state = getState();
return (state == UploadState.DONE || state == UploadState.BROWSE_HOST_DONE);
}
}
@Override
public boolean isStarted() {
if (uploader instanceof BTUploader) {
// Return torrent indicator.
return ((BTUploader) uploader).getTorrent().isStarted();
} else {
// Always true for non-torrents.
return true;
}
}
@Override
public void pause() {
uploader.pause();
}
@Override
public void resume() {
uploader.resume();
}
/**
* Creates a RemoteHost for this uploader. This allows browses on the
* person uploading this file.
*/
private class UploadRemoteHost implements RemoteHost {
@Override
public FriendPresence getFriendPresence() {
return friendPresence;
}
@Override
public boolean isBrowseHostEnabled() {
if(friendPresence.getFriend().isAnonymous()) {
return uploader.isBrowseHostEnabled();
} else {
//ensure friend/user still logged in through LW
return friendPresence.hasFeatures(LimewireFeature.ID);
}
}
@Override
public boolean isChatEnabled() {
if(friendPresence.getFriend().isAnonymous()) {
return false;
}else { //TODO: this isn't entirely correct. Friend could have signed
// ouf of LW but still be logged in through other service allowing chat
return friendPresence.hasFeatures(LimewireFeature.ID);
}
}
@Override
public boolean isSharingEnabled() {
if(friendPresence.getFriend().isAnonymous()) {
return false;
} else {
//ensure friend/user still logged in through LW
return friendPresence.hasFeatures(LimewireFeature.ID);
}
}
}
@Override
public List<SourceInfo> getTransferDetails() {
return uploader.getTransferDetails();
}
}