package com.limegroup.gnutella.library;
import static com.limegroup.gnutella.Constants.MAX_FILE_SIZE;
import java.io.File;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import org.limewire.core.settings.DHTSettings;
import org.limewire.listener.EventListener;
import org.limewire.listener.SourcedEventMulticaster;
import org.limewire.util.I18NConvert;
import org.limewire.util.Objects;
import com.limegroup.gnutella.URN;
import com.limegroup.gnutella.UrnSet;
import com.limegroup.gnutella.licenses.License;
import com.limegroup.gnutella.licenses.LicenseFactory;
import com.limegroup.gnutella.licenses.LicenseType;
import com.limegroup.gnutella.routing.HashFunction;
import com.limegroup.gnutella.xml.LimeXMLDocument;
/**
* This class contains data for an individual shared file. It also provides
* various utility methods for checking against the encapsulated data.<p>
*/
class FileDescImpl implements FileDesc {
/**
* Constant for the index of this <tt>FileDesc</tt> instance in the
* shared file data structure.
*/
private final int index;
private volatile UrnSet urns;
/**
* Constant for the <tt>File</tt> instance.
*/
private final File file;
/**
* The License, if one exists, for this FileDesc.
*/
private License _license;
/**
* The LimeXMLDocs associated with this FileDesc.
*/
private final CopyOnWriteArrayList<LimeXMLDocument> _limeXMLDocs = new CopyOnWriteArrayList<LimeXMLDocument>();
/**
* The size of the associated File.
*/
private final long fileSize;
private final long lastModified;
/**
* 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 time when the last attempt was made to upload this file
*/
private long lastAttemptedUploadTime = System.currentTimeMillis();
/**
* The number of times this file has had completed uploads
*/
private int _completedUploads;
/** True if this is a store file. */
private volatile boolean storeFile;
/** True if this can be shared. */
private volatile boolean isShareable = true;
private final SourcedEventMulticaster<FileDescChangeEvent, FileDesc> multicaster;
private final RareFileStrategy rareFileStrategy;
private final LicenseFactory licenseFactory;
private final ConcurrentHashMap<String, Object> clientProperties =
new ConcurrentHashMap<String, Object>(4, 0.75f, 1); // non-default initialCapacity,
// concurrencyLevel, saves
// ~1k memory / file.
// consider sizing even smaller,
// using other Map impls, or
// eliminating the use of a
// Map altogether
/**
* 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 URNs to associate with this FileDesc
* @param index the index in the FileManager
*/
FileDescImpl(RareFileStrategy rareFileStrategy, LicenseFactory licenseFactory,
SourcedEventMulticaster<FileDescChangeEvent, FileDesc> multicaster,
File file,
Set<? extends URN> urns,
int index) {
if(index < 0) {
throw new IndexOutOfBoundsException("negative index (" + index + ") not permitted in FileDesc");
}
fileSize = file.length();
assert fileSize >= 0 && fileSize <= MAX_FILE_SIZE : "invalid size "+fileSize+" of file "+file;
Objects.nonNull(urns, "urns");
this.rareFileStrategy = rareFileStrategy;
this.multicaster = multicaster;
this.licenseFactory = licenseFactory;
this.file = Objects.nonNull(file, "file");
this.index = index;
this.urns = UrnSet.unmodifiableSet(urns);
this.lastModified = file.lastModified();
hits = 0; // Starts off with 0 hits
}
@Override
public boolean isRareFile() {
return rareFileStrategy.isRareFile(this);
}
/* (non-Javadoc)
* @see com.limegroup.gnutella.library.FileDesc#hasUrns()
*/
public boolean hasUrns() {
return !urns.isEmpty();
}
/* (non-Javadoc)
* @see com.limegroup.gnutella.library.FileDesc#getIndex()
*/
public int getIndex() {
return index;
}
/* (non-Javadoc)
* @see com.limegroup.gnutella.library.FileDesc#getFileSize()
*/
public long getFileSize() {
return fileSize;
}
/* (non-Javadoc)
* @see com.limegroup.gnutella.library.FileDesc#getFileName()
*/
public String getFileName() {
return I18NConvert.instance().compose(file.getName());
}
/* (non-Javadoc)
* @see com.limegroup.gnutella.library.FileDesc#lastModified()
*/
public long lastModified() {
return lastModified;
}
/* (non-Javadoc)
* @see com.limegroup.gnutella.library.FileDesc#getFile()
*/
public File getFile() {
return file;
}
/* (non-Javadoc)
* @see com.limegroup.gnutella.library.FileDesc#getTTROOTUrn()
*/
@Override
public URN getTTROOTUrn() {
return urns.getTTRoot();
}
/* (non-Javadoc)
* @see com.limegroup.gnutella.library.FileDesc#getSHA1Urn()
*/
@Override
public URN getSHA1Urn() {
return urns.getSHA1();
}
@Override
public URN getNMS1Urn() {
return urns.getNMS1();
}
/* (non-Javadoc)
* @see com.limegroup.gnutella.library.FileDesc#addUrn(com.limegroup.gnutella.URN)
*/
public void addUrn(URN urn) {
boolean contained = urns.contains(urn);
if(!contained) {
UrnSet newSet = UrnSet.modifiableSet(urns);
newSet.add(urn);
urns = UrnSet.unmodifiableSet(newSet);
if(multicaster != null && urn.isTTRoot()) {
multicaster.handleEvent(new FileDescChangeEvent(this, FileDescChangeEvent.Type.TT_ROOT_ADDED, urn));
}
if(multicaster != null && urn.isNMS1()) {
multicaster.handleEvent(new FileDescChangeEvent(this, FileDescChangeEvent.Type.NMS1_ADDED, urn));
}
}
}
/* (non-Javadoc)
* @see com.limegroup.gnutella.library.FileDesc#getUrns()
*/
public Set<URN> getUrns() {
return urns;
}
/* (non-Javadoc)
* @see com.limegroup.gnutella.library.FileDesc#getPath()
*/
public String getPath() {
return file.getAbsolutePath();
}
/* (non-Javadoc)
* @see com.limegroup.gnutella.library.FileDesc#addLimeXMLDocument(com.limegroup.gnutella.xml.LimeXMLDocument)
*/
public void addLimeXMLDocument(LimeXMLDocument doc) {
_limeXMLDocs.add(doc);
doc.initIdentifier(file);
assignLicense(doc);
}
/* (non-Javadoc)
* @see com.limegroup.gnutella.library.FileDesc#replaceLimeXMLDocument(com.limegroup.gnutella.xml.LimeXMLDocument, com.limegroup.gnutella.xml.LimeXMLDocument)
*/
public boolean replaceLimeXMLDocument(LimeXMLDocument oldDoc,
LimeXMLDocument newDoc) {
synchronized(_limeXMLDocs) {
int index = _limeXMLDocs.indexOf(oldDoc);
if( index == -1 )
return false;
_limeXMLDocs.set(index, newDoc);
}
newDoc.initIdentifier(file);
assignLicense(newDoc);
return true;
}
private void assignLicense(LimeXMLDocument doc) {
_license = null;
if(doc.isLicenseAvailable()) {
String license = doc.getLicenseString();
if(license != null) {
_license = licenseFactory.create(license);
} else {
_license = null;
}
} else {
_license = null;
}
storeFile = doc.getLicenseString() != null &&
(doc.getLicenseString().equals(LicenseType.LIMEWIRE_STORE_PURCHASE.name()) ||
doc.getLicenseString().equals(LicenseType.LIMEWIRE_STORE_RESHAREABLE.name()));
if(doc.getLicenseString() != null &&
doc.getLicenseString().equals(LicenseType.LIMEWIRE_STORE_PURCHASE.name())) {
isShareable = false;
}
}
/* (non-Javadoc)
* @see com.limegroup.gnutella.library.FileDesc#removeLimeXMLDocument(com.limegroup.gnutella.xml.LimeXMLDocument)
*/
public boolean removeLimeXMLDocument(LimeXMLDocument toRemove) {
if (!_limeXMLDocs.remove(toRemove))
return false;
if(_license != null && toRemove.isLicenseAvailable())
_license = null;
return true;
}
/* (non-Javadoc)
* @see com.limegroup.gnutella.library.FileDesc#getLimeXMLDocuments()
*/
public List<LimeXMLDocument> getLimeXMLDocuments() {
return _limeXMLDocs;
}
/* (non-Javadoc)
* @see com.limegroup.gnutella.library.FileDesc#getXMLDocument()
*/
public LimeXMLDocument getXMLDocument() {
List<LimeXMLDocument> docs = getLimeXMLDocuments();
return docs.isEmpty() ? null : docs.get(0);
}
/* (non-Javadoc)
* @see com.limegroup.gnutella.library.FileDesc#getXMLDocument(java.lang.String)
*/
public LimeXMLDocument getXMLDocument(String schemaURI) {
for(LimeXMLDocument doc : getLimeXMLDocuments()) {
if (doc.getSchemaURI().equalsIgnoreCase(schemaURI))
return doc;
}
return null;
}
/* (non-Javadoc)
* @see com.limegroup.gnutella.library.FileDesc#isLicensed()
*/
public boolean isLicensed() {
return _license != null;
}
/* (non-Javadoc)
* @see com.limegroup.gnutella.library.FileDesc#getLicense()
*/
public License getLicense() {
return _license;
}
/* (non-Javadoc)
* @see com.limegroup.gnutella.library.FileDesc#containsUrn(com.limegroup.gnutella.URN)
*/
public boolean containsUrn(URN urn) {
return urns.contains(urn);
}
/* (non-Javadoc)
* @see com.limegroup.gnutella.library.FileDesc#incrementHitCount()
*/
public int incrementHitCount() {
return ++hits;
}
/* (non-Javadoc)
* @see com.limegroup.gnutella.library.FileDesc#getHitCount()
*/
public int getHitCount() {
return hits;
}
/* (non-Javadoc)
* @see com.limegroup.gnutella.library.FileDesc#incrementAttemptedUploads()
*/
public synchronized int incrementAttemptedUploads() {
lastAttemptedUploadTime = System.currentTimeMillis();
return ++_attemptedUploads;
}
/* (non-Javadoc)
* @see com.limegroup.gnutella.library.FileDesc#getAttemptedUploads()
*/
public synchronized int getAttemptedUploads() {
return _attemptedUploads;
}
/* (non-Javadoc)
* @see com.limegroup.gnutella.library.FileDesc#getLastAttemptedUploadTime()
*/
public synchronized long getLastAttemptedUploadTime() {
return lastAttemptedUploadTime;
}
/* (non-Javadoc)
* @see com.limegroup.gnutella.library.FileDesc#incrementCompletedUploads()
*/
public int incrementCompletedUploads() {
return ++_completedUploads;
}
/* (non-Javadoc)
* @see com.limegroup.gnutella.library.FileDesc#getCompletedUploads()
*/
public int getCompletedUploads() {
return _completedUploads;
}
// overrides Object.toString to provide a more useful description
@Override
public String toString() {
return ("FileDesc:\r\n"+
"name: "+getFileName()+"\r\n"+
"index: "+index+"\r\n"+
"path: "+getPath()+"\r\n"+
"size: "+getFileSize()+"\r\n"+
"modTime: "+lastModified()+"\r\n"+
"File: "+file+"\r\n"+
"urns: "+urns+"\r\n"+
"docs: "+ _limeXMLDocs+"\r\n");
}
/* (non-Javadoc)
* @see com.limegroup.gnutella.library.FileDesc#lookup(java.lang.String)
*/
public String lookup(String key) {
if (key == null)
return null;
if ("hits".equals(key))
return String.valueOf(getHitCount());
else if ("ups".equals(key))
return String.valueOf(getAttemptedUploads());
else if ("cups".equals(key))
return String.valueOf(getCompletedUploads());
else if ("lastup".equals(key))
return String.valueOf(System.currentTimeMillis() - getLastAttemptedUploadTime());
else if ("licensed".equals(key))
return String.valueOf(isLicensed());
else if ("atUpSet".equals(key))
return DHTSettings.RARE_FILE_ATTEMPTED_UPLOADS.getValueAsString();
else if ("cUpSet".equals(key))
return DHTSettings.RARE_FILE_COMPLETED_UPLOADS.getValueAsString();
else if ("rftSet".equals(key))
return DHTSettings.RARE_FILE_TIME.getValueAsString();
else if ("hasXML".equals(key))
return String.valueOf(getXMLDocument() != null);
else if ("size".equals(key))
return String.valueOf(getFileSize());
else if ("lastM".equals(key))
return String.valueOf(lastModified());
else if ("numKW".equals(key))
return String.valueOf(HashFunction.keywords(getPath()).length);
else if ("numKWP".equals(key))
return String.valueOf(HashFunction.getPrefixes(HashFunction.keywords(getPath())).length);
else if (key.startsWith("xml_") && getXMLDocument() != null) {
key = key.substring(4,key.length());
return getXMLDocument().lookup(key);
// Note: Removed 'firewalled' check -- might not be necessary, but
// should see if other ways to re-add can be done.
}
return null;
}
/* (non-Javadoc)
* @see com.limegroup.gnutella.library.FileDesc#isStoreFile()
*/
public boolean isStoreFile() {
return storeFile;
}
public boolean isShareable() {
return isShareable;
}
@Override
public void addListener(EventListener<FileDescChangeEvent> listener) {
multicaster.addListener(this, listener);
}
@Override
public boolean removeListener(EventListener<FileDescChangeEvent> listener) {
return multicaster.removeListener(this, listener);
}
@Override
public Object getClientProperty(String property) {
return clientProperties.get(property);
}
@Override
public void putClientProperty(String property, Object value) {
clientProperties.put(property, value);
}
}