package com.limegroup.gnutella; import java.io.BufferedInputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Set; import com.limegroup.gnutella.altlocs.AlternateLocation; import com.limegroup.gnutella.altlocs.AlternateLocationCollection; import com.limegroup.gnutella.altlocs.AlternateLocationCollector; import com.limegroup.gnutella.altlocs.DirectAltLoc; import com.limegroup.gnutella.settings.SharingSettings; import com.limegroup.gnutella.xml.LimeXMLDocument; import com.limegroup.gnutella.util.I18NConvert; /** * This class contains data for an individual shared file. It also provides * various utility methods for checking against the encapsulated data.<p> * * Constructing a FileDesc is usually done in two steps, which allows the caller * to avoid holding a lock when hashing a file: * <pre> * Set urns=FileDesc.calculateAndCacheURN(file); * FileDesc fd=new FileDesc(file, urns, index); * </pre> */ public class FileDesc implements AlternateLocationCollector { /** * Constant for the index of this <tt>FileDesc</tt> instance in the * shared file data structure. */ private final int _index; /** * The absolute path for the file. */ private final String _path; /** * The name of the file, as returned by File.getName(). */ private final String _name; /** * The size of the file, casted to an <tt>int</tt>. */ private final int _size; /** * The modification time of the file, which can be updated. */ private long _modTime; /** * Constant <tt>Set</tt> of <tt>URN</tt> instances for the file. This * is immutable. */ private final Set /* of URNS */ URNS; /** * Constant for the <tt>File</tt> instance. */ private final File FILE; /** * The constant SHA1 <tt>URN</tt> instance. */ private final URN SHA1_URN; /** * The LimeXMLDocs associated with this FileDesc. * * Never add or remove from this list. Always create a new list. * This has extra overhead when adding/removing, but makes synchronization * much much easier. */ private volatile List /* of LimeXMLDocument */ _limeXMLDocs = Collections.EMPTY_LIST; /** * The collection of alternate locations for the file. */ protected AlternateLocationCollection ALT_LOCS; /** * The collection of firewalled alternate locations for the file. */ protected AlternateLocationCollection PUSH_ALT_LOCS; /** * The number of hits this file has recieved. */ private int _hits; /** * The number of times this file has had attempted uploads */ private int _attemptedUploads; /** * The number of times this file has had completed uploads */ private int _completedUploads; /** * Constructs a new <tt>FileDesc</tt> instance from the specified * <tt>File</tt> class and the associated urns. * * @param file the <tt>File</tt> instance to use for constructing the * <tt>FileDesc</tt> * @param urns the return value from calculateAndCacheURN(file); * an unmodifiable <tt>Set</tt> of <tt>URN</tt>'s. * @param index the index in the FileManager * @see calculateAndCacheURN */ public FileDesc(File file, Set urns, int index) { if((file == null)) { throw new NullPointerException("cannot create a FileDesc with "+ "a null File"); } if(index < 0) { throw new IndexOutOfBoundsException("negative values not "+ "permitted in FileDesc: " + index); } if(urns == null) { throw new NullPointerException("cannot create a FileDesc with "+ "a null URN Set"); } // make a defensive copy FILE = new File(file.getAbsolutePath()); _index = index; _name = I18NConvert.instance().compose(FILE.getName()); _path = FILE.getAbsolutePath(); //TODO: right method? _size = (int)FILE.length(); _modTime = FILE.lastModified(); URNS = Collections.unmodifiableSet(urns); SHA1_URN = extractSHA1(); if(SHA1_URN == null) { throw new IllegalArgumentException("no SHA1 URN"); } ALT_LOCS = AlternateLocationCollection.create(SHA1_URN); PUSH_ALT_LOCS = AlternateLocationCollection.create(SHA1_URN); _hits = 0; // Starts off with 0 hits } /** * Returns whether or not this <tt>FileDesc</tt> has any urns. * * @return <tt>true</tt> if this <tt>FileDesc</tt> has urns, * <tt>false</tt> otherwise */ public boolean hasUrns() { return !URNS.isEmpty(); } /** * Returns the index of this file in our file data structure. * * @return the index of this file in our file data structure */ public int getIndex() { return _index; } /** * Returns the size of the file on disk, in bytes. * * @return the size of the file on disk, in bytes */ public long getSize() { return _size; } /** * Returns the name of this file. * * @return the name of this file */ public String getName() { return _name; } /** * Returns the last modification time for the file according to this * <tt>FileDesc</tt> instance. * * @return the modification time for the file */ public long lastModified() { return _modTime; } // inherit doc comment public URN getSHA1Urn() { return SHA1_URN; } /** * Extracts the SHA1 URN from the set of urns. */ private URN extractSHA1() { Iterator iter = URNS.iterator(); while(iter.hasNext()) { URN urn = (URN)iter.next(); if(urn.isSHA1()) { return urn; } } // this should never happen!! return null; } /** * Returns the <tt>File</tt> instance for this <tt>FileDesc</tt>. * * @return the <tt>File</tt> instance for this <tt>FileDesc</tt> */ public File getFile() { return FILE; } /** * Returns a new <tt>Set</tt> instance containing the <tt>URN</tt>s * for the this <tt>FileDesc</tt>. The <tt>Set</tt> instance * returned is immutable. * * @return a new <tt>Set</tt> of <tt>URN</tt>s for this * <tt>FileDesc</tt> */ public Set getUrns() { return URNS; } /** * Returns the absolute path of the file represented wrapped by this * <tt>FileDesc</tt>. * * @return the absolute path of the file */ public String getPath() { return FILE.getAbsolutePath(); } /** * Adds a LimeXMLDocument to this FileDesc. */ public void addLimeXMLDocument(LimeXMLDocument doc) { List newDocs = new ArrayList(_limeXMLDocs.size() + 1); newDocs.addAll(_limeXMLDocs); newDocs.add(doc); doc.setIdentifier(FILE); _limeXMLDocs = newDocs; } /** * Replaces one LimeXMLDocument with another. */ public boolean replaceLimeXMLDocument(LimeXMLDocument oldDoc, LimeXMLDocument newDoc) { int index = _limeXMLDocs.indexOf(oldDoc); if( index == -1 ) return false; List newDocs = new ArrayList(_limeXMLDocs); Object removed = newDocs.remove(index); Assert.that(removed == oldDoc, "wrong doc removed!"); newDocs.add(newDoc); newDoc.setIdentifier(FILE); _limeXMLDocs = newDocs; return true; } /** * Removes a LimeXMLDocument from the FileDesc. */ public boolean removeLimeXMLDocument(LimeXMLDocument toRemove) { if( _limeXMLDocs.size() == 0 ) return false; List newDocs = new ArrayList(_limeXMLDocs); boolean removed = newDocs.remove(toRemove); _limeXMLDocs = newDocs; return removed; } /** * Returns the LimeXMLDocuments for this FileDesc. */ public List getLimeXMLDocuments() { return _limeXMLDocs; } /** * @return the <tt>AlternateLocationCollection</tt> for this * <tt>FileDesc</tt> instance, which can be empty, or <tt>null</tt> * if it is not initialized */ public AlternateLocationCollection getAlternateLocationCollection() { return ALT_LOCS; } public AlternateLocationCollection getPushAlternateLocationCollection() { return PUSH_ALT_LOCS; } /** * Implements <tt>AlternateLocationCollector</tt> interface. * * @throws <tt>NullPointerException</tt> if the argument is <tt>null</tt> * @throws <tt>IllegalArgumentException</tt> if the alternate location * has a different SHA1 than this file, or if its sha1 is <tt>null</tt> */ public boolean add(AlternateLocation al) { if(al == null) { throw new NullPointerException("cannot accept null alt locs"); } URN sha1 = al.getSHA1Urn(); if(sha1 == null) { throw new IllegalArgumentException("sha1 cannot be null"); } if(!sha1.equals(SHA1_URN)) { throw new IllegalArgumentException("URN does not match:\n"+ SHA1_URN+"\n"+sha1); } if (al instanceof DirectAltLoc) return ALT_LOCS.add(al); else return PUSH_ALT_LOCS.add(al); } /** * @throws <tt>NullPointerException</tt> if the argument is <tt>null</tt> * @throws <tt>IllegalArgumentException</tt> if the alternate location * has a different SHA1 than this file, or if its sha1 is <tt>null</tt> */ public boolean remove(AlternateLocation al) { if(al == null) throw new NullPointerException("cannot accept null alt locs"); URN sha1 = al.getSHA1Urn(); if(sha1 == null) throw new IllegalArgumentException("sha1 cannot be null"); if(!sha1.equals(SHA1_URN)) throw new IllegalArgumentException("URN does not match:\n"+ SHA1_URN+"\n"+sha1); if (al instanceof DirectAltLoc) return ALT_LOCS.remove(al); else return PUSH_ALT_LOCS.remove(al); } /** * Implements the <tt>AlternateLocationCollector</tt> interface. * Adds the specified <tt>AlternateLocationCollection</tt> to this * collection. * * @param alc the <tt>AlternateLocationCollection</tt> to add * @throws <tt>NullPointerException</tt> if <tt>alc</tt> is * <tt>null</tt> * @throws <tt>IllegalArgumentException</tt> if the SHA1 of the * collection to add does not match the collection of <tt>this</tt> */ public int addAll(AlternateLocationCollection alc) { if(alc == null) { throw new NullPointerException("cannot accept null alt loc coll"); } if(!alc.getSHA1Urn().equals(SHA1_URN)) { throw new IllegalArgumentException("SHA1 does not match:\n"+ SHA1_URN+"\n"+alc.getSHA1Urn()); } int added =0; for (Iterator iter= alc.iterator();iter.hasNext();) if (add((AlternateLocation)iter.next())) added++; return added; } // implements AlternateLocationCollector interface public boolean hasAlternateLocations() { return ALT_LOCS.hasAlternateLocations() || PUSH_ALT_LOCS.hasAlternateLocations(); } // implements AlternateLocationCollector interface public int getAltLocsSize() { return ALT_LOCS.getAltLocsSize() + PUSH_ALT_LOCS.getAltLocsSize(); } /** * Determine whether or not the given <tt>URN</tt> instance is * contained in this <tt>FileDesc</tt>. * * @param urn the <tt>URN</tt> instance to check for * @return <tt>true</tt> if the <tt>URN</tt> is a valid <tt>URN</tt> * for this file, <tt>false</tt> otherwise */ public boolean containsUrn(URN urn) { if(urn == null) { throw new NullPointerException("null URNS not allowed in containsUrn"); } // now check if given urn matches Iterator iter = URNS.iterator(); while(iter.hasNext()){ if (urn.equals((URN)iter.next())) { return true; } } // no match return false; } /** * Increase & return the new hit count. * @return the new hit count */ public int incrementHitCount() { return ++_hits; } /** * @return the current hit count */ public int getHitCount() { return _hits; } /** * Increase & return the new attempted uploads * @return the new attempted upload count */ public int incrementAttemptedUploads() { return ++_attemptedUploads; } /** * @return the current attempted uploads */ public int getAttemptedUploads() { return _attemptedUploads; } /** * Increase & return the new completed uploads * @return the new completed upload count */ public int incrementCompletedUploads() { return ++_completedUploads; } /** * @return the current completed uploads */ public int getCompletedUploads() { return _completedUploads; } /** * Opens an input stream to the <tt>File</tt> instance for this * <tt>FileDesc</tt>. * * @return an <tt>InputStream</tt> to the <tt>File</tt> instance * @throws <tt>FileNotFoundException</tt> if the file represented * by the <tt>File</tt> instance could not be found */ public InputStream createInputStream() throws FileNotFoundException { return new BufferedInputStream(new FileInputStream(FILE)); } /** * Utility method for toString that converts the specified * <tt>Iterator</tt>'s items to a string. * * @param i the <tt>Iterator</tt> to convert * @return the contents of the set as a comma-delimited string */ private String listInformation(Iterator i) { StringBuffer stuff = new StringBuffer(); for(; i.hasNext(); ) { stuff.append(i.next().toString()); if( i.hasNext() ) stuff.append(", "); } return stuff.toString(); } // overrides Object.toString to provide a more useful description public String toString() { return ("FileDesc:\r\n"+ "name: "+_name+"\r\n"+ "index: "+_index+"\r\n"+ "path: "+_path+"\r\n"+ "size: "+_size+"\r\n"+ "modTime: "+_modTime+"\r\n"+ "File: "+FILE+"\r\n"+ "urns: "+listInformation(URNS.iterator())+"\r\n"+ "docs: "+ (_limeXMLDocs == null ? "null" : listInformation(_limeXMLDocs.iterator()) ) +"\r\n"+ "alt locs: "+ALT_LOCS+"\r\n"+ "push locs: "+PUSH_ALT_LOCS+"\r\n"); } }