package org.limewire.core.impl.upload;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.io.File;
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.upload.UploadErrorState;
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.limegroup.bittorrent.BTUploader;
import com.limegroup.gnutella.CategoryConverter;
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 {
private final Uploader uploader;
private final FriendPresence friendPresence;
private final PropertyChangeSupport support = new SwingSafePropertyChangeSupport(this);
public final static long UNKNOWN_TIME = Long.MAX_VALUE;
private final UploadItemType uploadItemType;
private boolean isFinished = false;
private UploadRemoteHost uploadRemoteHost;
public CoreUploadItem(Uploader uploader, FriendPresence friendPresence) {
this.uploader = uploader;
this.friendPresence = friendPresence;
uploadItemType = uploader instanceof BTUploader ? UploadItemType.BITTORRENT : UploadItemType.GNUTELLA;
}
@Override
public void cancel() {
uploader.stop();
fireDataChanged();
}
@Override
public String getFileName() {
return uploader.getFileName();
}
@Override
public long getFileSize() {
return uploader.getFileSize();
}
@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 WAITING_REQUESTS:
return UploadState.WAITING;
case LIMIT_REACHED:
case INTERRUPTED:
case FILE_NOT_FOUND:
case UNAVAILABLE_RANGE:
case MALFORMED_REQUEST:
case SUSPENDED:
case BANNED_GREEDY:
case FREELOADER:
return UploadState.UNABLE_TO_UPLOAD;
case BROWSE_HOST:
return UploadState.BROWSE_HOST;
}
throw new IllegalStateException("Unknown Upload status : " + uploader.getState());
}
private UploadStatus getUploaderStatus() {
// do not change the status if we are at an intermediary
// complete or connecting state.
// (meaning that this particular chunk finished, but more will come)
// we use isFinished to tell us when it's finished, because that is
// set when remove is called, which is only called when the entire
// upload has finished.
// we use getTotalAmountUploaded to know if a byte has been read
// (which would mean we're not connecting anymore)
UploadStatus state = uploader.getState();
UploadStatus lastState = uploader.getLastTransferState();
if ( (state == UploadStatus.COMPLETE && !isFinished) ||
(state == UploadStatus.CONNECTING &&
uploader.getTotalAmountUploaded() != 0)
) {
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 uploader.getTotalAmountUploaded();
}
@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());
}
@Override
public Category getCategory() {
return CategoryConverter.categoryForFileName(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() {
try {
uploader.measureBandwidth();
return uploader.getMeasuredBandwidth();
} catch (InsufficientDataException e) {
return 0;
}
}
@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 UploadErrorState getErrorState() {
switch (uploader.getState()) {
case LIMIT_REACHED:
case BANNED_GREEDY:
case FREELOADER:
return UploadErrorState.LIMIT_REACHED;
case INTERRUPTED:
case SUSPENDED:
case WAITING_REQUESTS:
return UploadErrorState.INTERRUPTED;
case FILE_NOT_FOUND:
case MALFORMED_REQUEST:
case UNAVAILABLE_RANGE:
return UploadErrorState.FILE_ERROR;
}
return UploadErrorState.NO_ERROR;
}
@Override
public Object getProperty(FilePropertyKey property) {
FileDesc fd = uploader.getFileDesc();
if(fd != null) {
switch(property) {
case NAME:
return FileUtils.getFilenameNoExtension(fd.getFileName());
case DATE_CREATED:
long ct = fd.lastModified();
return ct == -1 ? null : ct;
case FILE_SIZE:
return fd.getFileSize();
default:
Category category = CategoryConverter.categoryForFileName(fd.getFileName());
return FilePropertyKeyPopulator.get(category, property, fd.getXMLDocument());
}
} else {
return null;
}
}
@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 File getFile() {
return uploader.getFile();
}
@Override
public UploadItemType getUploadItemType() {
return uploadItemType;
}
@Override
public int getNumUploadConnections() {
return uploader.getNumUploadConnections();
}
/**
* Called when upload is finished. This enables the DONE state. This method
* is necessary to present false DONE states.
*/
void finish(){
isFinished = true;
fireDataChanged();
}
/**
* 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 float getSeedRatio() {
return uploader.getSeedRatio();
}
}