package com.limegroup.bittorrent;
import java.io.File;
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import org.limewire.bittorrent.LimeWireTorrentProperties;
import org.limewire.bittorrent.Torrent;
import org.limewire.bittorrent.TorrentEvent;
import org.limewire.bittorrent.TorrentEventType;
import org.limewire.bittorrent.TorrentManager;
import org.limewire.bittorrent.TorrentPeer;
import org.limewire.bittorrent.TorrentState;
import org.limewire.bittorrent.TorrentStatus;
import org.limewire.bittorrent.util.TorrentUtil;
import org.limewire.concurrent.ManagedThread;
import org.limewire.core.api.transfer.SourceInfo;
import org.limewire.listener.EventListener;
import com.limegroup.gnutella.ActivityCallback;
import com.limegroup.gnutella.InsufficientDataException;
import com.limegroup.gnutella.URN;
import com.limegroup.gnutella.Uploader;
import com.limegroup.gnutella.library.FileDesc;
import com.limegroup.gnutella.uploader.UploadType;
/**
* Wraps the Torrent class in the Uplaoder interface to enable the gui to treat
* the torrent uploader as a normal uploader.
*/
public class BTUploader implements Uploader, EventListener<TorrentEvent> {
private final ActivityCallback activityCallback;
private final Torrent torrent;
private final AtomicBoolean cancelled = new AtomicBoolean(false);
private final AtomicBoolean finished = new AtomicBoolean(false);
private volatile URN urn = null;
private final TorrentUploadManager torrentUploadManager;
private final TorrentManager torrentManager;
public BTUploader(Torrent torrent, ActivityCallback activityCallback,
TorrentUploadManager torrentUploadManager, TorrentManager torrentManager) {
this.torrent = torrent;
this.activityCallback = activityCallback;
this.torrentUploadManager = torrentUploadManager;
this.torrentManager = torrentManager;
}
public void registerTorrentListener() {
torrent.addListener(this);
}
@Override
public void handleEvent(TorrentEvent event) {
if (event.getType() == TorrentEventType.STOPPED) {
if (!finished.get()) {
cancel();
} else {
remove();
}
} else if (event.getType() == TorrentEventType.STATUS_CHANGED) {
// considered to be finished uploading if seed ratio has been
// reached
boolean finished = torrent.isFinished();
float seedRatio = torrent.getSeedRatio();
TorrentStatus status = torrent.getStatus();
int seedTime = status != null ? status.getSeedingTime() : 0;
float targetSeedRatio = torrent.getProperty(LimeWireTorrentProperties.MAX_SEED_RATIO_LIMIT, -1f);
targetSeedRatio = (targetSeedRatio < 0) ? torrentManager.getTorrentManagerSettings().getSeedRatioLimit() : targetSeedRatio;
int targetSeedTime = torrent.getProperty(LimeWireTorrentProperties.MAX_SEED_TIME_RATIO_LIMIT, -1);
targetSeedTime = (targetSeedTime < 0) ? torrentManager.getTorrentManagerSettings().getSeedTimeLimit() : targetSeedTime;
if (finished && (seedRatio >= targetSeedRatio || seedTime >= targetSeedTime)) {
this.finished.set(true);
torrent.stop();
}
}
}
private void cancel() {
cancelled.set(true);
remove();
}
private void remove() {
torrent.removeListener(this);
torrentUploadManager.removeMemento(torrent);
activityCallback.uploadComplete(this);
};
@Override
public void stop() {
new ManagedThread(new Runnable() {
@Override
public void run() {
torrent.stop();
}
}, "BTUploader Stop Torrent").start();
cancel();
}
@Override
public String getFileName() {
return torrent.getName();
}
@Override
public long getFileSize() {
TorrentStatus status = torrent.getStatus();
long fileSize = status != null ? status.getTotalWanted() : -1;
return fileSize;
}
@Override
public FileDesc getFileDesc() {
return null;
}
@Override
public int getIndex() {
// negative will make sure it never conflicts with regular uploads
return 0 - Math.abs(hashCode());
}
@Override
public long amountUploaded() {
return torrent.getTotalUploaded();
}
@Override
public long getTotalAmountUploaded() {
return torrent.getTotalUploaded();
}
@Override
public String getHost() {
return BITTORRENT_UPLOAD;
}
@Override
public UploadStatus getState() {
if (cancelled.get()) {
return UploadStatus.CANCELLED;
}
if (finished.get()) {
return UploadStatus.COMPLETE;
}
TorrentStatus status = torrent.getStatus();
if (status == null) {
return UploadStatus.CONNECTING;
}
if (!torrent.isStarted()) {
return UploadStatus.QUEUED;
}
if (status.isError()) {
return UploadStatus.UNAVAILABLE_RANGE;
}
if (status.isPaused()) {
return UploadStatus.PAUSED;
} else {
TorrentState state = status.getState();
switch (state) {
case DOWNLOADING:
case FINISHED:
case SEEDING:
return UploadStatus.UPLOADING;
case QUEUED_FOR_CHECKING:
case CHECKING_FILES:
case DOWNLOADING_METADATA:
case ALLOCATING:
return UploadStatus.CONNECTING;
default:
throw new UnsupportedOperationException("Unknown state: " + state);
}
}
}
@Override
public UploadStatus getLastTransferState() {
return getState();
}
@Override
public boolean isBrowseHostEnabled() {
return false;
}
@Override
public int getGnutellaPort() {
return 0;
}
@Override
public String getUserAgent() {
return BITTORRENT_UPLOAD;
}
@Override
public int getQueuePosition() {
return 0;
}
@Override
public boolean isInactive() {
if (torrent.getStatus().isPaused() || torrent.getStatus().isFinished()) {
return true;
}
return false;
}
@Override
public void measureBandwidth() {
// uneeded using libtorrent rate
}
@Override
public float getMeasuredBandwidth() throws InsufficientDataException {
return (torrent.getUploadRate() / 1024);
}
@Override
public float getAverageBandwidth() {
// Unused
return (torrent.getUploadRate() / 1024);
}
@Override
public String getCustomIconDescriptor() {
return BITTORRENT_UPLOAD;
}
@Override
public UploadType getUploadType() {
return UploadType.SHARED_FILE;
}
@Override
public boolean isTLSCapable() {
return false;
}
@Override
public String getAddress() {
return "torrent upload";
}
@Override
public InetAddress getInetAddress() {
return null;
}
@Override
public int getPort() {
return -1;
}
@Override
public InetSocketAddress getInetSocketAddress() {
return null;
}
@Override
public String getAddressDescription() {
return null;
}
/**
* Returns a collection of completed files for this uploader.
*/
public Collection<File> getCompleteFiles() {
return TorrentUtil.buildTorrentFiles(torrent,
torrent.getTorrentDataFile().getParentFile());
}
@Override
public File getFile() {
return torrent.getTorrentDataFile();
}
@Override
public URN getUrn() {
if (urn == null) {
synchronized (this) {
if (urn == null) {
try {
urn = URN.createSha1UrnFromHex(torrent.getSha1());
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
return urn;
}
@Override
public int getNumUploadConnections() {
return torrent.getNumConnections();
}
@Override
public String getPresenceId() {
return null;
}
@Override
public float getSeedRatio() {
return torrent.getSeedRatio();
}
/**
* Returns the Torrent associated with this uploader.
*/
public Torrent getTorrent() {
return torrent;
}
@Override
public void pause() {
if(torrent.isFinished()) {
torrent.setAutoManaged(false);
}
torrent.pause();
}
@Override
public void resume() {
if(torrent.isFinished()) {
torrent.setAutoManaged(true);
}
torrent.resume();
}
@Override
public List<SourceInfo> getTransferDetails() {
List<TorrentPeer> peers = torrent.getTorrentPeers();
List<SourceInfo> sourceInfoList = new ArrayList<SourceInfo>(peers.size());
for (TorrentPeer peer : peers) {
sourceInfoList.add(new TorrentSourceInfoAdapter(peer));
}
return sourceInfoList;
}
}