package com.limegroup.gnutella.gui.search; import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import javax.swing.Icon; import com.limegroup.gnutella.Assert; import com.limegroup.gnutella.RemoteFileDesc; import com.limegroup.gnutella.RouterService; import com.limegroup.gnutella.SavedFileManager; import com.limegroup.gnutella.URN; import com.limegroup.gnutella.gui.GUIMediator; import com.limegroup.gnutella.gui.IconManager; import com.limegroup.gnutella.gui.tables.AbstractDataLine; import com.limegroup.gnutella.gui.tables.IconAndNameHolder; import com.limegroup.gnutella.gui.tables.IconAndNameHolderImpl; import com.limegroup.gnutella.gui.tables.LimeTableColumn; import com.limegroup.gnutella.gui.tables.SizeHolder; import com.limegroup.gnutella.gui.xml.XMLUtils; import com.limegroup.gnutella.licenses.License; import com.limegroup.gnutella.licenses.LicenseFactory; import com.limegroup.gnutella.messages.SecureMessage; import com.limegroup.gnutella.search.HostData; import com.limegroup.gnutella.settings.SearchSettings; import com.limegroup.gnutella.util.CommonUtils; import com.limegroup.gnutella.util.NameValue; import com.limegroup.gnutella.xml.LimeXMLDocument; /** * A single line of a search result. */ public final class TableLine extends AbstractDataLine { /** * The SearchTableColumns. */ private final SearchTableColumns COLUMNS; /** * The SearchResult that created this particular line. */ private SearchResult RESULT; /** * The list of other SearchResults that match this line. */ private List _otherResults; /** * The SHA1 of this line. */ private URN _sha1; /** * The media type of this document. */ private NamedMediaType _mediaType; /** * The set of other locations that have this result. */ private Set _alts; /** * Whether or not this file is saved in the library. */ private boolean _savedFile; /** * Whether or not this file is incomplete. */ private boolean _incompleteFile; /** * Whether or not this file was downloading the last time we checked. */ private boolean _downloading; /** Whether or not this result is a secure result. */ private boolean _secure; /** * The speed of this line. */ private ResultSpeed _speed = null; /** * The quality of this line. */ private int _quality; /** * A chat enabled host if there is one. */ private RemoteFileDesc _chatHost; /** * A browse enabled host if there is one. */ private RemoteFileDesc _browseHost; /** * A non firewalled host if there is one. */ private RemoteFileDesc _nonFirewalledHost; /** * The LimeXMLDocument for this line. */ private LimeXMLDocument _doc; /** * The location of this line. */ private EndpointHolder _location = null; /** * The date this was added to the network. */ private long _addedOn; /** * The last spam rating this TableLine had */ private float _lastRating = -1f; /** License info. */ private int _licenseState = License.NO_LICENSE; private String _licenseName = null; public TableLine(SearchTableColumns stc) { COLUMNS = stc; } /** * Initializes this line with the specified search result. */ public void initialize(Object init) { super.initialize(init); SearchResult sr = (SearchResult)init; RemoteFileDesc rfd = sr.getRemoteFileDesc(); HostData data = sr.getHostData(); Set alts = sr.getAlts(); RESULT = sr; _doc = rfd.getXMLDocument(); _sha1 = rfd.getSHA1Urn(); if(_doc != null) _mediaType = NamedMediaType.getFromDescription( _doc.getSchemaDescription()); else _mediaType = NamedMediaType.getFromExtension(getExtension()); _speed = new ResultSpeed(rfd.getSpeed(), data.isMeasuredSpeed()); _quality = rfd.getQuality(); _secure = rfd.getSecureStatus() == SecureMessage.SECURE; if (rfd.chatEnabled()) { _chatHost = rfd; } if (rfd.browseHostEnabled()) { _browseHost = rfd; } if (!rfd.isFirewalled()) { _nonFirewalledHost = rfd; } _location = new EndpointHolder( rfd.getHost(), rfd.getPort(), rfd.isReplyToMulticast()); _addedOn = rfd.getCreationTime(); if(alts != null && !alts.isEmpty()) { if(_alts == null) _alts = new HashSet(); _alts.addAll(alts); sr.clearAlts(); _location.addHosts(alts); } updateLicense(); updateFileStatus(); } /** * Adds a new SearchResult to this TableLine. */ void addNewResult(SearchResult sr, MetadataModel mm) { RemoteFileDesc rfd = sr.getRemoteFileDesc(); HostData data = sr.getHostData(); Set alts = sr.getAlts(); URN resultSHA1 = RESULT.getRemoteFileDesc().getSHA1Urn(); URN thisSHA1 = rfd.getSHA1Urn(); if(resultSHA1 == null) Assert.that(thisSHA1 == null); else Assert.that(resultSHA1.equals(thisSHA1)); if(_otherResults == null) _otherResults = new LinkedList(); _otherResults.add(sr); // mark that we need to recalculate the rating _lastRating = -1f; if(alts != null && !alts.isEmpty()) { if(_alts == null) _alts = new HashSet(); _alts.addAll(alts); sr.clearAlts(); _location.addHosts(alts); } _location.addHost(rfd.getHost(), rfd.getPort()); // Set the speed correctly. ResultSpeed newSpeed = new ResultSpeed(rfd.getSpeed(), data.isMeasuredSpeed()); // if we're changing a property, update the metadata model. if(_speed.compareTo(newSpeed) < 0) { if(mm != null) mm.updateProperty(MetadataModel.SPEED, _speed, newSpeed, this); _speed = newSpeed; } // Set the quality correctly. _quality = Math.max(rfd.getQuality(), _quality); _secure |= rfd.getSecureStatus() == SecureMessage.SECURE; if(rfd.getCreationTime() > 0) _addedOn = Math.min(_addedOn, rfd.getCreationTime()); // Set chat host correctly. if (_chatHost == null && rfd.chatEnabled()) { _chatHost = rfd; } // Set browse host correctly. if (_browseHost == null && rfd.browseHostEnabled()) { _browseHost = rfd; } if (_nonFirewalledHost == null && !rfd.isFirewalled()) { _nonFirewalledHost = rfd; } updateXMLDocument(rfd.getXMLDocument(), mm); } /** * Updates the XMLDocument and the MetadataModel. */ private void updateXMLDocument(LimeXMLDocument newDoc, MetadataModel mm) { // If nothing new, nothing to do. if(newDoc == null) return; // If no document exists, just set it to be the new doc if(_doc == null) { _doc = newDoc; updateLicense(); if(mm != null) { _mediaType = NamedMediaType.getFromDescription( _doc.getSchemaDescription()); mm.addNewDocument(_doc, this); } return; } // Otherwise, if a document does exist in the group, see if the line // has extra fields that can be added to the group. // Must have the same schema... if(!_doc.getSchemaURI().equals(newDoc.getSchemaURI())) return; Set oldKeys = _doc.getNameSet(); Set newKeys = newDoc.getNameSet(); // if the we already have everything in new, do nothing if(oldKeys.containsAll(newKeys)) return; // Now we want to add the values of newKeys that weren't // already in oldKeys. newKeys = new HashSet(newKeys); newKeys.removeAll(oldKeys); // newKeys now only has brand new elements. Map newMap = new HashMap(oldKeys.size() + newKeys.size()); for(Iterator i = _doc.getNameValueSet().iterator(); i.hasNext();) { Map.Entry entry = (Map.Entry)i.next(); newMap.put(entry.getKey(), entry.getValue()); } for(Iterator i = newKeys.iterator(); i.hasNext();) { String key = (String)i.next(); String value = newDoc.getValue(key); newMap.put(key, value); if(mm != null) mm.addField(key, value, this); } _doc = new LimeXMLDocument(newMap.entrySet(), _doc.getSchemaURI()); updateLicense(); } /** * Updates the file status of this line. */ private void updateFileStatus() { if(_sha1 != null) { _savedFile = RouterService.getFileManager().isUrnShared(_sha1); _incompleteFile = RouterService.getDownloadManager().isIncomplete(_sha1); } else { _savedFile = false; _incompleteFile = false; } if(!_savedFile) { _savedFile = SavedFileManager.instance().isSaved(_sha1, getFilename()); } } /** * Updates cached data about this line. */ public void update() { updateLicense(); _lastRating = -1f; } /** * Updates the license status. */ private void updateLicense() { if(_doc != null && _sha1 != null) { String licenseString = _doc.getLicenseString(); if(licenseString != null) { if(LicenseFactory.isVerifiedAndValid(_sha1, licenseString)) _licenseState = License.VERIFIED; else _licenseState = License.UNVERIFIED; _licenseName = LicenseFactory.getLicenseName(licenseString); } } } /** Determines if this TableLine is secure. */ boolean isSecure() { return _secure; } /** * Determines if a license is available. */ boolean isLicenseAvailable() { return _licenseState != License.NO_LICENSE; } /** * Gets the license associated with this line. */ License getLicense() { if(_doc != null && _sha1 != null) return _doc.getLicense(); else return null; } /** * Gets the SHA1 urn of this line. */ URN getSHA1Urn() { return _sha1; } /** * Gets the speed of this line. */ ResultSpeed getSpeed() { return _speed; } /** * Gets the creation time. */ Date getAddedOn() { if(_addedOn > 0) return new Date(_addedOn); else return null; } /** * Gets the quality of this line. */ int getQuality() { RemoteFileDesc rfd = RESULT.getRemoteFileDesc(); boolean downloading = rfd.isDownloading(); if(downloading != _downloading) updateFileStatus(); _downloading = downloading; if(_savedFile) return QualityRenderer.SAVED_FILE_QUALITY; else if(downloading) return QualityRenderer.DOWNLOADING_FILE_QUALITY; else if(_incompleteFile) return QualityRenderer.INCOMPLETE_FILE_QUALITY; else if (_secure) return QualityRenderer.SECURE_QUALITY; else if (SearchSettings.ENABLE_SPAM_FILTER.getValue() && SpamFilter.isAboveSpamThreshold(this)) return QualityRenderer.SPAM_FILE_QUALITY; else return _quality; } /** * Returns the NamedMediaType. */ NamedMediaType getNamedMediaType() { return _mediaType; } /** * Gets the LimeXMLDocument for this line. */ LimeXMLDocument getXMLDocument() { return _doc; } /** * Gets the EndpointHolder holding locations. */ EndpointHolder getLocation() { return _location; } /** * Gets the other results for this line. */ List getOtherResults() { return _otherResults == null ? Collections.EMPTY_LIST : _otherResults; } /** * Gets the alternate locations for this line. */ Set getAlts() { return _alts == null ? Collections.EMPTY_SET : _alts; } /** * Gets the number of locations this line holds. */ int getLocationCount() { return _location.numLocations(); } /** * Returns the license name of null if File(s) have no license */ String getLicenseName() { return _licenseName; } /** * Determines whether or not chat is enabled. */ boolean isChatEnabled() { return _chatHost != null; } /** * Determines whether or not browse host is enabled. */ boolean isBrowseHostEnabled() { return _browseHost != null; } /** * Determines whether there is a non firewalled host for this result. */ boolean hasNonFirewalledRFD() { return _nonFirewalledHost != null; } /** * Determines if this line is launchable. */ boolean isLaunchable() { return _doc != null && _doc.getAction() != null && !"".equals(_doc.getAction()); } /** * Gets the filename without the extension. */ String getFilenameNoExtension() { return RESULT.getFilenameNoExtension(); } /** * Returns the icon & extension. */ IconAndNameHolder getIconAndExtension() { String ext = getExtension(); return new IconAndNameHolderImpl( IconManager.instance().getIconForExtension(ext), ext); } /** * Returns the icon. */ Icon getIcon() { String ext = getExtension(); return IconManager.instance().getIconForExtension(ext); } /** * Returns the extension of this result. */ String getExtension() { return RESULT.getExtension(); } /** * Returns this filename, as passed to the constructor. Limitation: * if the original filename was "a.", the returned value will be * "a". */ String getFilename() { return RESULT.getRemoteFileDesc().getFileName(); } /** * Gets the size of this TableLine. */ int getSize() { return RESULT.getSize(); } /** * Returns the vendor code of the result. */ String getVendor() { return RESULT.getRemoteFileDesc().getVendor(); } /** * Gets the LimeTableColumn for this column. */ public LimeTableColumn getColumn(int idx) { return COLUMNS.getColumn(idx); } /** * Returns the number of columns. */ public int getColumnCount() { return SearchTableColumns.COLUMN_COUNT; } /** * Determines if the column is dynamic. */ public boolean isDynamic(int idx) { return false; } /** * Determines if the column is clippable. */ public boolean isClippable(int idx) { switch(idx) { case SearchTableColumns.QUALITY_IDX: case SearchTableColumns.COUNT_IDX: case SearchTableColumns.ICON_IDX: case SearchTableColumns.CHAT_IDX: case SearchTableColumns.LICENSE_IDX: return false; default: return true; } } public int getTypeAheadColumn() { return SearchTableColumns.NAME_IDX; } /** * Gets the value for the specified idx. */ public Object getValueAt(int index){ switch (index) { case SearchTableColumns.QUALITY_IDX: return new Integer(getQuality()); case SearchTableColumns.COUNT_IDX: int count = _location.numLocations(); if(count == 1) return null; else return new Integer(count); case SearchTableColumns.ICON_IDX: return getIcon(); case SearchTableColumns.NAME_IDX: return new ResultNameHolder(this); case SearchTableColumns.TYPE_IDX: return getExtension(); case SearchTableColumns.SIZE_IDX: return new SizeHolder(getSize()); case SearchTableColumns.SPEED_IDX: return getSpeed(); case SearchTableColumns.CHAT_IDX: return isChatEnabled() ? Boolean.TRUE : Boolean.FALSE; case SearchTableColumns.LOCATION_IDX: return getLocation(); case SearchTableColumns.VENDOR_IDX: return RESULT.getRemoteFileDesc().getVendor(); case SearchTableColumns.ADDED_IDX: return getAddedOn(); case SearchTableColumns.LICENSE_IDX: return new NameValue.ComparableByName(_licenseName, new Integer(_licenseState)); case SearchTableColumns.SPAM_IDX: return new Float(getSpamRating()); default: if(_doc == null) return null; // Look up the value in the doc. // The id of the LimeTableColumn is the field. LimeTableColumn ltc = getColumn(index); return _doc.getValue(ltc.getId()); } } /** * Returns the XMLDocument as a tool tip. */ public String[] getToolTipArray(int col) { // only works on windows, which gives good toString descriptions // of its native file icons. if(col == SearchTableColumns.ICON_IDX && CommonUtils.isWindows()) { Icon icon = getIcon(); if(icon != null) return new String[] { icon.toString() }; else return null; } // if we're on the location column and we've got multiple results, // list them all out. if(col == SearchTableColumns.LOCATION_IDX && getLocationCount() > 1) { StringBuffer sb = new StringBuffer(3 * 23); List retList = new LinkedList(); Iterator iter = _location.getHosts().iterator(); for(int i = 0; iter.hasNext(); i++) { if(i == 3) { i = 0; retList.add(sb.toString()); sb = new StringBuffer(3 * 23); } sb.append(iter.next()); if(iter.hasNext()) sb.append(", "); else retList.add(sb.toString()); } return (String[])retList.toArray(new String[retList.size()]); } List tips = new LinkedList(); if(isSecure()) { tips.add(GUIMediator.getStringResource("SEARCH_RESULT_SECURE_TIP")); tips.add(""); } if(_doc != null) tips.addAll(XMLUtils.getDisplayList(_doc)); if (!tips.isEmpty() ) { // if it had data, display the filename in the tooltip also. tips.add(0, getFilenameNoExtension()); return (String[])tips.toArray(new String[tips.size()]); } else { return null; } } /** * Gets the main result's host. */ String getHostname() { return RESULT.getRemoteFileDesc().getHost(); } /** * Gets all RemoteFileDescs for this line. */ RemoteFileDesc[] getAllRemoteFileDescs() { int size = getOtherResults().size() + 1; RemoteFileDesc[] rfds = new RemoteFileDesc[size]; rfds[0] = RESULT.getRemoteFileDesc(); int j = 1; for(Iterator i = getOtherResults().iterator(); i.hasNext(); j++) rfds[j] = ((SearchResult)i.next()).getRemoteFileDesc(); return rfds; } /** * Does a chat. */ void doChat() { if (_chatHost != null && _chatHost.getHost() != null && _chatHost.getPort() != -1) { RouterService.createChat(_chatHost.getHost(), _chatHost.getPort()); } } /** * Returns the rfd of the search result for which this download was enabled * @return */ RemoteFileDesc getRemoteFileDesc() { return RESULT.getRemoteFileDesc(); } /** * Gets the spam rating */ float getSpamRating() { if (_lastRating == -1f) { _lastRating = RESULT.getRemoteFileDesc().getSpamRating(); int num = 1; if (_otherResults != null) { for (Iterator iter = _otherResults.iterator(); iter.hasNext(); num++) _lastRating += ((SearchResult) iter.next()).getRemoteFileDesc() .getSpamRating(); } _lastRating = _lastRating / num; } return _lastRating; } /** * Gets the first browse-host enabled RFD or <code>null</code>. */ RemoteFileDesc getBrowseHostEnabledRFD() { return _browseHost; } /** * Returns the first non-firewalled rfd for this result or <code>null</code>. */ RemoteFileDesc getNonFirewalledRFD() { return _nonFirewalledHost; } /** * Returns the first chat enabled rfd for this result or <code>null</code>. */ RemoteFileDesc getChatEnabledRFD() { return _chatHost; } }