package com.limegroup.gnutella.uploader; import java.io.IOException; import java.net.InetAddress; import java.net.InetSocketAddress; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.limewire.nio.ssl.SSLUtils; import com.limegroup.gnutella.InsufficientDataException; import com.limegroup.gnutella.Uploader; import com.limegroup.gnutella.library.FileDesc; import com.limegroup.gnutella.library.LibraryUtils; import com.limegroup.gnutella.statistics.TcpBandwidthStatistics; import com.limegroup.gnutella.statistics.TcpBandwidthStatistics.StatisticType; /** * Provides an implementation of the {@link Uploader} interface. */ public abstract class AbstractUploader implements Uploader { private static final Log LOG = LogFactory.getLog(AbstractUploader.class); private final HTTPUploadSession session; /** * The number of bytes that were transferred in previous sessions. * <p> * Note: Obtain {@link #bwLock} before accessing. */ private long totalAmountUploadedBefore; /** * The number of bytes transfered by all requests represented by this * instance. * <p> * Note: Obtain {@link #bwLock} before accessing. */ private long totalAmountUploaded; /** * The number of bytes transfered for the current request. * <p> * Note: Obtain {@link #bwLock} before accessing. */ private long amountUploaded; /** * Lock for bandwidth tracking related fields. */ private final Object bwLock = new Object(); private boolean ignoreTotalAmountUploaded; private long fileSize; private String userAgent; private final String filename; private UploadStatus state = UploadStatus.CONNECTING; private UploadStatus lastTransferState; private boolean firstReply; private boolean browseHostEnabled; /** * True if this is a forcibly shared network file. */ private boolean forcedShare = false; /** * True if this is an uploader with high priority. */ private boolean priorityShare = false; /** The descriptor of the file being uploaded. */ private FileDesc fileDesc; private int index; private String host; private int port = -1; /** The upload type of this uploader. */ private UploadType uploadType; private final TcpBandwidthStatistics tcpBandwidthStatistics; // private Exception previousStateSetter = null; public AbstractUploader(String fileName, HTTPUploadSession session, TcpBandwidthStatistics tcpBandwidthStatistics) { this.session = session; this.filename = fileName; this.firstReply = true; this.tcpBandwidthStatistics = tcpBandwidthStatistics; } /** * Reinitializes this uploader for a new request. */ public void reinitialize() { setState(UploadStatus.CONNECTING); host = null; port = -1; synchronized (bwLock) { totalAmountUploadedBefore = 0; if (!ignoreTotalAmountUploaded) { totalAmountUploaded += amountUploaded; } ignoreTotalAmountUploaded = false; amountUploaded = 0; } firstReply = false; } /** * Sets the file that is being uploaded. * * @param fd the file being uploaded * @throws IOException if the file cannot be read from the disk. */ public void setFileDesc(FileDesc fd) { if (LOG.isDebugEnabled()) LOG.debug("Setting file description for " + this + ": " + fd); this.fileDesc = fd; this.forcedShare = LibraryUtils.isForcedShare(fd); this.priorityShare = LibraryUtils .isApplicationSpecialShare(fd.getFile()); this.index = fd.getIndex(); setFileSize(fd.getFileSize()); } public void setState(UploadStatus state) { if (this.state == state) { IllegalStateException ise = new IllegalStateException(); // ise.initCause(previousStateSetter); throw ise; } this.lastTransferState = this.state; this.state = state; // this.previousStateSetter = new Exception(); } /** * Returns the queued position if queued. */ public int getQueuePosition() { if (lastTransferState != UploadStatus.QUEUED || state == UploadStatus.INTERRUPTED) return -1; else return session.positionInQueue(); } /** * Sets the number of bytes that have been uploaded for this upload. This is * expected to restart from 0 for each chunk of an HTTP/1.1 transfer. * * @param amount the number of bytes that have been uploaded */ void setAmountUploaded(long amount) { synchronized (bwLock) { addAmountUploaded((int) (amount - amountUploaded)); } } /** * Increases the amount of uploaded bytes. * * @param written number of bytes transferred */ public void addAmountUploaded(int written) { assert written >= 0; if (written > 0 && tcpBandwidthStatistics != null) { if (isForcedShare()) tcpBandwidthStatistics.getStatistic(StatisticType.HTTP_BODY_INNETWORK_UPSTREAM).addData(written); else tcpBandwidthStatistics.getStatistic(StatisticType.HTTP_BODY_UPSTREAM).addData(written); } synchronized (bwLock) { amountUploaded += written; } } /** * Returns whether or not this upload is in what is considered an "inactive" * state, such as completed or aborted. * * @return <tt>true</tt> if this upload is in an inactive state, * <tt>false</tt> otherwise */ public boolean isInactive() { switch (state) { case COMPLETE: case INTERRUPTED: return true; default: return false; } } public long getFileSize() { return fileSize; } public int getIndex() { return index; } public String getFileName() { return this.filename; } public UploadStatus getState() { return state; } public UploadStatus getLastTransferState() { return lastTransferState; } public String getHost() { return (host != null) ? host : session.getHost(); } public boolean isBrowseHostEnabled() { return browseHostEnabled; } public int getGnutellaPort() { return port; } public String getUserAgent() { return userAgent; } public boolean isForcedShare() { return forcedShare; } public boolean isPriorityShare() { return priorityShare; } /** * Returns true, if this is this uploader represents the first request. */ protected boolean isFirstReply() { return firstReply; } public long amountUploaded() { synchronized (bwLock) { return amountUploaded; } } public long getTotalAmountUploaded() { synchronized (bwLock) { if (ignoreTotalAmountUploaded) return amountUploaded; else if (totalAmountUploadedBefore > 0) return totalAmountUploadedBefore + amountUploaded; else return totalAmountUploaded + amountUploaded; } } public FileDesc getFileDesc() { return fileDesc; } public void measureBandwidth() { // FIXME type conversion int written; synchronized (bwLock) { written = (int) (totalAmountUploaded + amountUploaded); } session.measureBandwidth(written); } public float getMeasuredBandwidth() throws InsufficientDataException { return session.getMeasuredBandwidth(); } public float getAverageBandwidth() { return session.getAverageBandwidth(); } public String getCustomIconDescriptor() { return null; } public UploadType getUploadType() { return uploadType; } /** * Sets the type returned by {@link #getUploadType()}. */ public void setUploadType(UploadType type) { uploadType = type; } /** * Sets the flag returned by {@link #isBrowseHostEnabled()}. */ public void setBrowseHostEnabled(boolean browseHostEnabled) { this.browseHostEnabled = browseHostEnabled; } /** * Sets the port returned by {@link #getGnutellaPort()}. */ public void setGnutellaPort(int port) { this.port = port; } /** * Sets the amount uploaded in previous sessions. If * <code>totalAmountReadBefore</code> is != 0, * {@link #getTotalAmountUploaded()} will use that value to calculate the * total amount uploaded instead of relying on the value maintained by this * uploader. */ public void setTotalAmountUploadedBefore(int totalAmountReadBefore) { synchronized (bwLock) { this.totalAmountUploadedBefore = totalAmountReadBefore; } } /** * Sets the user agent returned by {@link #getUserAgent()}. */ public void setUserAgent(String userAgent) { this.userAgent = userAgent; } @Override public String toString() { return getClass().getName() + "[host=" + getHost() + ",index=" + index + ",filename=" + filename + ",state=" + state + ",lastTransferState=" + lastTransferState + "]"; } /** * Returns the upload session that is associated with the connection. */ public HTTPUploadSession getSession() { return session; } /** * Returns the file size returned by {@link #getFileSize()}. * * @param fileSize must be greater than 0 */ public void setFileSize(long fileSize) { if (fileSize <= 0) { throw new IllegalArgumentException("illegal file size: " + fileSize); } this.fileSize = fileSize; } /** * Returns true, if the amount uploaded in previous sessions is not returned * by {@link #getTotalAmountUploaded()}. * * @see #setIgnoreTotalAmountUploaded(boolean) */ public boolean getIgnoreTotalAmountUploaded() { return ignoreTotalAmountUploaded; } /** * If set to true, the amount uploaded in previous sessions is not returned * by {@link #getTotalAmountUploaded()}. * <p> * Note: This is reset to <code>false</code> by {@link #reinitialize()}. * * @see #getIgnoreTotalAmountUploaded() */ public void setIgnoreTotalAmountUploaded(boolean ignoreTotalAmountUploaded) { this.ignoreTotalAmountUploaded = ignoreTotalAmountUploaded; } public void setHost(String host) { this.host = host; } /** Returns true if the socket this is on is currently using TLS. */ public boolean isTLSCapable() { return SSLUtils.isTLSEnabled(getSession().getIOSession().getSocket()); } /** Returns the address of the host that initiated this upload. */ public String getAddress() { return getSession().getHost(); } /** Returns the address of the host that initiated this session. */ public InetAddress getInetAddress() { return getSession().getIOSession().getSocket().getInetAddress(); } /** Returns a combination of getInetAddress and getPort. */ public InetSocketAddress getInetSocketAddress() { return new InetSocketAddress(getInetAddress(), getPort()); } @Override public String getAddressDescription() { return getInetSocketAddress().toString(); } /** Returns the Gnutella Port, if one was provided. Otherwise, the remote port from the socket. */ public int getPort() { int gnutellaPort = getGnutellaPort(); if(gnutellaPort != -1) return gnutellaPort; else return ((InetSocketAddress)getSession().getIOSession().getRemoteAddress()).getPort(); } }