package com.limegroup.gnutella.gui.download; import java.io.File; import java.util.Iterator; import javax.swing.Icon; import com.limegroup.gnutella.Assert; import com.limegroup.gnutella.Downloader; import com.limegroup.gnutella.Endpoint; import com.limegroup.gnutella.InsufficientDataException; import com.limegroup.gnutella.downloader.VerifyingFile; import com.limegroup.gnutella.gui.GUIMediator; import com.limegroup.gnutella.gui.GUIUtils; import com.limegroup.gnutella.gui.IconManager; import com.limegroup.gnutella.gui.tables.AbstractDataLine; import com.limegroup.gnutella.gui.tables.CenteredHolder; import com.limegroup.gnutella.gui.tables.ChatHolder; import com.limegroup.gnutella.gui.tables.FileTransfer; import com.limegroup.gnutella.gui.tables.IconAndNameHolder; import com.limegroup.gnutella.gui.tables.IconAndNameHolderImpl; import com.limegroup.gnutella.gui.tables.LazyFileTransfer; import com.limegroup.gnutella.gui.tables.LimeTableColumn; import com.limegroup.gnutella.gui.tables.ProgressBarHolder; import com.limegroup.gnutella.gui.tables.SizeHolder; import com.limegroup.gnutella.gui.tables.SpeedRenderer; import com.limegroup.gnutella.gui.tables.TimeRemainingHolder; import com.limegroup.gnutella.util.CommonUtils; /** * This class handles all of the data for a single download, representing * one "line" in the download window. It continually updates the * displayed data for the download from the contained <tt>Downloader</tt> * instance. */ public final class DownloadDataLine extends AbstractDataLine implements LazyFileTransfer { /** * Constant for the <tt>Downloader</tt> instance for this download. */ private Downloader DOWNLOADER; /** * Constant for the "queued" download state. */ private static final String QUEUED_STATE = GUIMediator.getStringResource("DOWNLOAD_STATUS_QUEUED"); /** * Constant for the "connecting" download state. */ private static final String CONNECTING_STATE = GUIMediator.getStringResource("DOWNLOAD_STATUS_CONNECTING"); /** * Constant for the "waiting" download state. */ private static final String WAITING_STATE = GUIMediator.getStringResource("DOWNLOAD_STATUS_WAITING"); /** * Constant for the "complete" download state. */ private static final String COMPLETE_STATE = GUIMediator.getStringResource("DOWNLOAD_STATUS_COMPLETE"); /** * Constant for the "aborted" download state. */ private static final String ABORTED_STATE = GUIMediator.getStringResource("DOWNLOAD_STATUS_ABORTED"); /** * Constant for the "failed" download state. */ private static final String FAILED_STATE = GUIMediator.getStringResource("DOWNLOAD_STATUS_FAILED"); /** * Constant for the "downloading" download state. */ private static final String DOWNLOADING_STATE = GUIMediator.getStringResource("DOWNLOAD_STATUS_DOWNLOADING"); /** * Constant for the "Could Not Move to Library" download state. * TODO: change this to a more generic disk problem message */ private static final String LIBRARY_MOVE_FAILED_STATE = GUIMediator.getStringResource("DOWNLOAD_STATUS_LIBRARY_MOVE_FAILED"); /** * Constant for the "Corrupt File" download state. */ private static final String CORRUPT_FILE_STATE = GUIMediator.getStringResource("DOWNLOAD_STATUS_CORRUPT_FILE"); /** * Constant for the "Waiting for Results" download state. */ private static final String REQUERY_WAITING_STATE_START = GUIMediator.getStringResource("DOWNLOAD_STATUS_WAITING_FOR_REQUERY_START"); /** * Constant for the "Waiting for Results" download state. */ private static final String REQUERY_WAITING_STATE_END = GUIMediator.getStringResource("DOWNLOAD_STATUS_WAITING_FOR_REQUERY_END"); /** * Constant for the "Waiting for Results" download state. */ private static final String REQUERY_WAITING_FOR_USER = GUIMediator.getStringResource("DOWNLOAD_STATUS_WAITING_FOR_USER"); /** * Constant for the "Waiting for Connections" download state. */ private static final String WAITING_FOR_CONNECTIONS_STATE = GUIMediator.getStringResource("DOWNLOAD_STATUS_WAITING_FOR_CONN"); /** * Constant for the "Remote Queued" download state */ private static final String REMOTE_QUEUED_STATE = GUIMediator.getStringResource("DOWNLOAD_STATUS_REMOTE_QUEUED"); /** * Constant for the "Hashing" download state */ private static final String HASHING_STATE = GUIMediator.getStringResource("DOWNLOAD_STATUS_HASHING"); /** * Constant for the "Saving" download state */ private static final String SAVING_STATE = GUIMediator.getStringResource("DOWNLOAD_STATUS_SAVING"); /** * Constant for the "Identifying Corruption" download state */ private static final String IDENTIFY_CORRUPTION_STATE = GUIMediator.getStringResource("DOWNLOAD_STATUS_IDENTIFY_CORRUPTION"); /** * Constant for the "Pausing" download state. */ private static final String PAUSING_STATE = GUIMediator.getStringResource("DOWNLOAD_STATUS_PAUSING"); /** * Constant for the "Paused" download state. */ private static final String PAUSED_STATE = GUIMediator.getStringResource("DOWNLOAD_STATUS_PAUSED"); /** Constant for 'Invalid' download state. */ private static final String INVALID_STATE = GUIMediator.getStringResource("DOWNLOAD_STATUS_INVALID"); /** * Constant for "average bandwidth" tip */ private static final String AVERAGE_BANDWIDTH = GUIMediator.getStringResource("GENERAL_AVERAGE_BANDWIDTH"); /** * Constant for the "alternate locations" tip */ private static final String ALTERNATE_LOCATIONS = GUIMediator.getStringResource("GENERAL_ALTERNATE_LOCATIONS"); /** * Constant for the "invalid alternate locations" tip */ private static final String INVALID_ALTERNATE_LOCATIONS = GUIMediator.getStringResource("GENERAL_INVALID_ALTERNATE_LOCATIONS"); /** * Constant for "Started on" tip */ private static final String STARTED_ON = GUIMediator.getStringResource("GENERAL_STARTED_ON"); /** * Constant for "Finished on" tip */ private static final String FINISHED_ON = GUIMediator.getStringResource("GENERAL_FINISHED_ON"); /** * Constant for "Time Spent" tip */ private static final String TIME_SPENT = GUIMediator.getStringResource("GENERAL_TIME_SPENT"); /** * Constant for "Chunks" tip */ private static final String CHUNKS = GUIMediator.getStringResource("DOWNLOAD_CHUNKS"); /** * Constant for "Lost By Corruption" tip */ private static final String LOST = GUIMediator.getStringResource("DOWNLOAD_LOST"); /** * Constant for "KB" tip word */ private static final String KB = GUIMediator.getStringResource("GENERAL_UNIT_KILOBYTES"); /** * Constant for the " hosts" message. */ private static final String HOSTS_LABEL = GUIMediator.getStringResource("DOWNLOAD_HOSTS_LABEL"); private static final String HOST_LABEL = GUIMediator.getStringResource("DOWNLOAD_HOST_LABEL"); /** * Constant for the 'gave up' tooltip message. */ private static final String[] GAVE_UP_MESSAGE = { GUIMediator.getStringResource("DOWNLOAD_AWAITING_SOURCES") }; /** Constant for the 'invalid' tooltip msg. */ private static final String[] INVALID_MESSAGE = { GUIMediator.getStringResource("DOWNLOAD_INVALID_EXPLAIN") }; /** * Constant for the number of possible hosts this downloader has */ private static final String POSSIBLE_HOSTS = GUIMediator.getStringResource("DOWNLOAD_POSSIBLE_HOSTS"); private static final String BUSY_HOSTS = GUIMediator.getStringResource("DOWNLOAD_BUSY_HOSTS"); private static final String QUEUED_HOSTS = GUIMediator.getStringResource("DOWNLOAD_QUEUED_HOSTS"); private static final String DOWNLOAD_SEARCHING = GUIMediator.getStringResource("DOWNLOAD_SEARCHING"); private static final String SEEDING = GUIMediator.getStringResource("DOWNLOAD_STATUS_SEEDING"); private static final String WAITING_FOR_TRACKER = GUIMediator.getStringResource("DOWNLOAD_STATUS_WAITING_FOR_TRACKER"); /** * Variable for the name of the file being downloaded. */ private String _fileName; /** * Variable for the status of the download. */ private String _status; /** * Variable for the amount of the file that has been read. */ private long _amountRead = 0; /** * Variable for the progress made in the progressbar. */ private int _progress; /** * Variable for whether or not chat is enabled. */ private boolean _chatEnabled; /** * Variable for whether or not browse is enabled. */ private boolean _browseEnabled; /** * Variable for the size of the download. */ private long _size = -1; /** * Variable for the speed of the download. */ private double _speed; /** * Variable for how much time is left. */ private int _timeLeft; /** * Variable for the time this download started */ private long _startTime; /** * Variable for the time this download ended. */ private long _endTime; /** * The current vendor we are downloading from. */ private String _vendor; /** * Stores the current state of this download, as of the last update. * This is the state the everything should work off of to avoid the * <tt>Downloader</tt> instance being in a different state than * this data line. */ private int _state; /** * Whether or not we've cleaned up this line. */ private boolean _cleaned = false; /** * Column index for priority. */ static final int PRIORITY_INDEX = 0; private static final LimeTableColumn PRIORITY_COLUMN = new LimeTableColumn(PRIORITY_INDEX, "DOWNLOAD_PRIORITY_COLUMN", 40, false, CenteredHolder.class); /** * Column index for the file name. */ static final int FILE_INDEX = 1; private static final LimeTableColumn FILE_COLUMN = new LimeTableColumn(FILE_INDEX, "DOWNLOAD_NAME_COLUMN", 201, true, IconAndNameHolder.class); /** * Column index for the file size. */ static final int SIZE_INDEX = 2; private static final LimeTableColumn SIZE_COLUMN = new LimeTableColumn(SIZE_INDEX, "DOWNLOAD_SIZE_COLUMN", 65, true, SizeHolder.class); /** * Column index for the file download status. */ static final int STATUS_INDEX = 3; private static final LimeTableColumn STATUS_COLUMN = new LimeTableColumn(STATUS_INDEX, "DOWNLOAD_STATUS_COLUMN", 152, true, String.class); /** * Column index for whether or not the uploader is chat-enabled. */ static final int CHAT_INDEX = 4; private static final LimeTableColumn CHAT_COLUMN = new LimeTableColumn(CHAT_INDEX, "DOWNLOAD_CHAT_COLUMN", 10, false, ChatHolder.class); /** * Column index for the progress of the download. */ static final int PROGRESS_INDEX = 5; private static final LimeTableColumn PROGRESS_COLUMN = new LimeTableColumn(PROGRESS_INDEX, "DOWNLOAD_PROGRESS_COLUMN", 71, true, ProgressBarHolder.class); /** * Column index for the download speed. */ static final int SPEED_INDEX = 6; private static final LimeTableColumn SPEED_COLUMN = new LimeTableColumn(SPEED_INDEX, "DOWNLOAD_SPEED_COLUMN", 58, true, SpeedRenderer.class); /** * Column index for the download time remaining. */ static final int TIME_INDEX = 7; private static final LimeTableColumn TIME_COLUMN = new LimeTableColumn(TIME_INDEX, "DOWNLOAD_TIME_REMAINING_COLUMN", 49, true, TimeRemainingHolder.class); /** * Column index for the vendor of the downloader. */ static final int VENDOR_INDEX = 8; private static final LimeTableColumn VENDOR_COLUMN = new LimeTableColumn(VENDOR_INDEX, "DOWNLOAD_SERVER_COLUMN", 20, false, String.class); /** * Number of columns to display */ static final int NUMBER_OF_COLUMNS = 9; // Implements DataLine interface public int getColumnCount() { return NUMBER_OF_COLUMNS; } /** * Must initialize data. * * @param downloader the <tt>Downloader</tt> * that provides access to * information about the download */ public void initialize(Object downloader) { super.initialize(downloader); DOWNLOADER = (Downloader)downloader; _startTime = System.currentTimeMillis(); _endTime = -1; _size = DOWNLOADER.getContentLength(); // don't cache filename anymore, since we allow renames henceforth _fileName = DOWNLOADER.getSaveFile().getName(); if (_fileName==null) //TODO: does this ever happen with an downloader? _fileName=""; _status = ""; _chatEnabled = false; _browseEnabled = false; update(); } /** * Tell the downloader to close its sockets. */ public void cleanup() { GUIMediator.instance().schedule(new Runnable() { public void run() { DOWNLOADER.stop(); } }); _cleaned = true; } /** * Determines if this was cleaned up. */ public boolean isCleaned() { return _cleaned; } /** * Gets the file if the download was completed. */ public File getFile() { if(!CommonUtils.isWindows()) return DOWNLOADER.getFile(); else { if(DOWNLOADER.isCompleted()) return DOWNLOADER.getFile(); else return null; } } /** * Lazily gets the file -- constructs it only if necessary. */ public FileTransfer getLazyFile() { return new FileTransfer() { public File getFile() { return DOWNLOADER.getDownloadFragment(); } }; } /** * Returns the <tt>Object</tt> stored at the specified column in this * line of data. * * @param index the index of the column to retrieve data from * @return the <tt>Object</tt> stored at that index * @implements DataLine interface */ public Object getValueAt(int index) { switch(index) { case PRIORITY_INDEX: if(DOWNLOADER.isPaused()) return PriorityHolder.PAUSED_P; if(DOWNLOADER.isInactive()) return new PriorityHolder(DOWNLOADER.getInactivePriority()); else if(DOWNLOADER.isCompleted()) return PriorityHolder.COMPLETE_P; else return PriorityHolder.ACTIVE_P; case FILE_INDEX: Icon icon = IconManager.instance().getIconForFile(DOWNLOADER.getFile()); return new IconAndNameHolderImpl(icon, _fileName); case SIZE_INDEX: return new SizeHolder(_size); case STATUS_INDEX: return _status; case CHAT_INDEX: return _chatEnabled ? Boolean.TRUE : Boolean.FALSE; case PROGRESS_INDEX: return new Integer(_progress); case SPEED_INDEX: return new Double(_speed); case TIME_INDEX: return new TimeRemainingHolder(_timeLeft); case VENDOR_INDEX: return _vendor; } return null; } /** * @implements DataLine interface */ public LimeTableColumn getColumn(int idx) { switch(idx) { case PRIORITY_INDEX: return PRIORITY_COLUMN; case FILE_INDEX: return FILE_COLUMN; case SIZE_INDEX: return SIZE_COLUMN; case STATUS_INDEX: return STATUS_COLUMN; case CHAT_INDEX: return CHAT_COLUMN; case PROGRESS_INDEX: return PROGRESS_COLUMN; case SPEED_INDEX: return SPEED_COLUMN; case TIME_INDEX: return TIME_COLUMN; case VENDOR_INDEX: return VENDOR_COLUMN; } return null; } public boolean isClippable(int idx) { switch(idx) { case CHAT_INDEX: case PROGRESS_INDEX: return false; default: return true; } } public int getTypeAheadColumn() { return FILE_INDEX; } public boolean isTooltipRequired(int col) { return _state == Downloader.INVALID; } public String[] getToolTipArray(int col) { // give a new message if we gave up if( _state == Downloader.GAVE_UP ) return GAVE_UP_MESSAGE; if(_state == Downloader.INVALID ) return INVALID_MESSAGE; String[] info = new String[11]; String bandwidth = AVERAGE_BANDWIDTH + ": " + GUIUtils.rate2speed( DOWNLOADER.getAverageBandwidth() ); String numHosts = POSSIBLE_HOSTS + ": " + DOWNLOADER.getPossibleHostCount(); String busyHosts = BUSY_HOSTS + ": " +DOWNLOADER.getBusyHostCount(); String queuedHosts=QUEUED_HOSTS + ": "+DOWNLOADER.getQueuedHostCount(); String numLocs = ALTERNATE_LOCATIONS + ": " + DOWNLOADER.getNumberOfAlternateLocations(); String numInvalidLocs = INVALID_ALTERNATE_LOCATIONS + ": " + DOWNLOADER.getNumberOfInvalidAlternateLocations(); int chunkSize = DOWNLOADER.getChunkSize(); String numChunks,lost; int totalPending = VerifyingFile.getNumPendingItems(); synchronized(DOWNLOADER) { numChunks = CHUNKS + ": "+DOWNLOADER.getAmountVerified() / chunkSize +"/"+ DOWNLOADER.getAmountRead() / chunkSize+ "["+ DOWNLOADER.getAmountPending()+"|"+totalPending+"]"+ "/"+ (DOWNLOADER.getContentLength() + chunkSize -1) / chunkSize + ", "+chunkSize/1024+KB; lost = LOST+": "+DOWNLOADER.getAmountLost()/1024+KB; } info[0] = STARTED_ON + " " + GUIUtils.msec2DateTime( _startTime ); if( _endTime != -1 ) { info[1] = FINISHED_ON + " " + GUIUtils.msec2DateTime( _endTime ); info[2] = TIME_SPENT + ": " + CommonUtils.seconds2time( (int)((_endTime - _startTime) / 1000 ) ); info[3] = ""; info[4] = bandwidth; info[5] = numHosts; info[6] = busyHosts; info[7] = queuedHosts; info[8] = numLocs; info[9] = numInvalidLocs; info[10] = lost; } else { info[1] = TIME_SPENT + ": " + CommonUtils.seconds2time( (int) ((System.currentTimeMillis() - _startTime) / 1000 ) ); info[2] = ""; info[3] = bandwidth; info[4] = numHosts; info[5] = busyHosts; info[6] = queuedHosts; info[7] = numLocs; info[8] = numInvalidLocs; info[9] = numChunks; info[10] = lost;} return info; } public boolean isDynamic(int idx) { switch(idx) { case PRIORITY_INDEX: case STATUS_INDEX: case PROGRESS_INDEX: case SPEED_INDEX: case TIME_INDEX: case VENDOR_INDEX: return true; } return false; } /** * Returns the total size in bytes of the file being downloaded. * * @return the total size in bytes of the file being downloaded */ long getLength() { return _size; } /** * Returns name of the file being downloaded. * @return name of the downloaded file. */ public String getFileName() { return _fileName; } /** * Returns whether or not the <tt>Downloader</tt> for this download * is equal to the one passed in. * * @return <tt>true</tt> if the passed-in downloader is equal to the * <tt>Downloader</tt> for this download, <tt>false</tt> otherwise */ boolean containsDownloader(Downloader downloader) { return DOWNLOADER.equals(downloader); } /** * Returns the <tt>Downloader</tt> associated with this download. * * @return the <tt>Downloader</tt> associated with this download */ Downloader getDownloader() { return DOWNLOADER; } /** * Return the state of the Downloader * * @return the state of the downloader */ int getState() { return _state; } /** * Returns whether or not the download has completed. * * @return <tt>true</tt> if the download is complete, <tt>false</tt> otherwise */ boolean isCompleted() { return _state == Downloader.COMPLETE; } /** * Returns whether or not chat is enabled for this download. * * @return <tt>true</tt> if the host we're downloading from is chattable, * <tt>false</tt> otherwise */ boolean getChatEnabled() { return _chatEnabled; } /** * Returns whether or not browse is enabled for this download. * * @return <tt>true</tt> if the host we're downloading from is browsable, * <tt>false</tt> otherwise */ boolean getBrowseEnabled() { return _browseEnabled; } /** * Updates all of the data for this download, obtaining fresh information * from the contained <tt>Downloader</tt> instance. * * @implements DataLine interface */ public void update() { synchronized(DOWNLOADER) { // always get new file name it might have changed _fileName = DOWNLOADER.getSaveFile().getName(); _speed = -1; if ( _size == -1 ) _size = DOWNLOADER.getContentLength(); _amountRead = DOWNLOADER.getAmountRead(); _chatEnabled = DOWNLOADER.hasChatEnabledHost(); _browseEnabled = DOWNLOADER.hasBrowseEnabledHost(); _timeLeft = 0; //note: we *always* want to update progress // specifically for when the user has downloaded stuff, // closed the app, and then re-opened the app. //previously, because progress was only set while downloading //or corrupted, the GUI would display 0 progress, even //though it actually had progress. double d = (double)_amountRead/(double)_size; _progress = (int)(d*100); this.updateStatus(); // downloads can go from inactive to active through resuming. if ( !this.isInactive() ) _endTime = -1; } } /** * Updates the status of the download based on the state stored in the * <tt>Downloader</tt> instance for this <tt>DownloadDataLine</tt>. */ private void updateStatus() { final String lastVendor = _vendor; _vendor = ""; _state = DOWNLOADER.getState(); boolean paused = DOWNLOADER.isPaused(); if(paused && _state != Downloader.PAUSED && !DOWNLOADER.isCompleted()) { _status = PAUSING_STATE; return; } switch (_state) { case Downloader.QUEUED: _status = QUEUED_STATE; break; case Downloader.CONNECTING: _status = CONNECTING_STATE; break; case Downloader.BUSY: _status = WAITING_STATE; break; case Downloader.HASHING: _status = HASHING_STATE; break; case Downloader.SAVING: _status = SAVING_STATE; break; case Downloader.COMPLETE: _status = COMPLETE_STATE; _progress = 100; break; case Downloader.ABORTED: _status = ABORTED_STATE; break; case Downloader.GAVE_UP: _status = FAILED_STATE; break; case Downloader.IDENTIFY_CORRUPTION: _status = IDENTIFY_CORRUPTION_STATE; break; case Downloader.RECOVERY_FAILED: _status = "Recovery Failed"; break; case Downloader.DOWNLOADING: _vendor = lastVendor; updateHostCount(DOWNLOADER); try { _speed = (double)DOWNLOADER.getMeasuredBandwidth(); } catch(InsufficientDataException ide) { _speed = 0; } // If we have a valid rate (can't compute if rate is 0), // then determine how much time (in seconds) is remaining. if ( _speed > 0) { double kbLeft = (((double)_size/1024.0) - ((double)_amountRead/1024.0)); _timeLeft = (int)(kbLeft / _speed); } break; case Downloader.DISK_PROBLEM: _status = LIBRARY_MOVE_FAILED_STATE; _progress = 100; break; case Downloader.CORRUPT_FILE: _status = CORRUPT_FILE_STATE; break; case Downloader.WAITING_FOR_RESULTS: int stateTime=DOWNLOADER.getRemainingStateTime(); _status = (REQUERY_WAITING_STATE_START+" "+stateTime +REQUERY_WAITING_STATE_END); break; case Downloader.ITERATIVE_GUESSING: _status = DOWNLOAD_SEARCHING; break; case Downloader.WAITING_FOR_USER: _status = REQUERY_WAITING_FOR_USER; break; case Downloader.WAITING_FOR_CONNECTIONS: _status = WAITING_FOR_CONNECTIONS_STATE; break; case Downloader.REMOTE_QUEUED: _status = REMOTE_QUEUED_STATE+" "+DOWNLOADER.getQueuePosition(); _vendor = DOWNLOADER.getVendor(); break; case Downloader.PAUSED: _status = PAUSED_STATE; break; case Downloader.INVALID: _status = INVALID_STATE; break; case Downloader.SEEDING: _status = SEEDING; break; case Downloader.WAITING_FOR_TRACKER: _status = WAITING_FOR_TRACKER; break; default: Assert.that(false, "Unknown status "+DOWNLOADER.getState()+" of downloader"); } } /** * Returns a human-readable description of the address(es) from * which d is downloading. */ private void updateHostCount(Downloader d) { int count = d.getNumHosts(); // we are in between chunks with this host, // use the previous count so-as not to confuse // the user. if (count == 0) { // don't change anything. return; } if (count==1) { _status = DOWNLOADING_STATE + " " + count + " "+ HOST_LABEL; _vendor = d.getVendor(); } else { _status = DOWNLOADING_STATE + " " + count + " " + HOSTS_LABEL; _vendor = d.getVendor(); } } /** * Returns whether or not this download is in what * is considered an "inactive" * state, such as completeed, aborted, failed, etc. * * @return <tt>true</tt> if this download is in an inactive state, * <tt>false</tt> otherwise */ boolean isInactive() { return (_state == Downloader.COMPLETE || _state == Downloader.ABORTED || _state == Downloader.GAVE_UP || _state == Downloader.DISK_PROBLEM || _state == Downloader.CORRUPT_FILE); } /** * Determines if the downloader is in what it considers an inactive state. */ boolean isDownloaderInactive() { return DOWNLOADER.isInactive(); } /** * Returns whether or not the * download for this line is currently downloading * * @return <tt>true</tt> if this download is currently downloading, * <tt>false</tt> otherwise */ boolean isDownloading() { return _state == Downloader.DOWNLOADING; } /** * Sets the time this download ended. */ void setEndTime(long time) { _endTime = time; } }