package com.limegroup.gnutella.downloader; import java.io.File; import java.io.IOException; import java.util.HashMap; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import org.limewire.core.api.Category; import org.limewire.core.api.download.DownloadException; import org.limewire.core.api.download.SaveLocationManager; import org.limewire.core.api.download.DownloadException.ErrorCode; import org.limewire.core.api.file.CategoryManager; import org.limewire.core.settings.SharingSettings; import org.limewire.io.InvalidDataException; import org.limewire.util.CommonUtils; import org.limewire.util.FileUtils; import org.limewire.util.Objects; import com.limegroup.bittorrent.BTDownloader; import com.limegroup.gnutella.downloader.serial.DownloadMemento; /** * A basic implementation of CoreDownloader. * * Subclasses still need to do the heavy-lifting. */ public abstract class AbstractCoreDownloader implements CoreDownloader { /** * The current priority of this download -- only valid if inactive. * Has no bearing on the download itself, and is used only so that the * download doesn't have to be indexed in DownloadManager's inactive list * every second, for GUI updates. */ private volatile int inactivePriority; /** * A map of attributes associated with the download. The attributes * may be used by GUI, to keep some additional information about * the download. */ private Map<String, Attribute> attributes = new ConcurrentHashMap<String, Attribute>(); /** * The save file this download should be saved too. * If null, the subclass should return a suggested location. */ private File saveFile; /** The default fileName this should use. */ private String defaultFileName; private final SaveLocationManager saveLocationManager; private final CategoryManager categoryManager; protected AbstractCoreDownloader(SaveLocationManager saveLocationManager, CategoryManager categoryManager) { this.saveLocationManager = Objects.nonNull(saveLocationManager, "saveLocationManager"); this.categoryManager = Objects.nonNull(categoryManager, "categoryManager"); } /* (non-Javadoc) * @see com.limegroup.gnutella.downloader.CoreDownloader#setInactivePriority(int) */ public void setInactivePriority(int priority) { inactivePriority = priority; } /* (non-Javadoc) * @see com.limegroup.gnutella.downloader.CoreDownloader#getInactivePriority() */ public int getInactivePriority() { return inactivePriority; } /* (non-Javadoc) * @see com.limegroup.gnutella.downloader.CoreDownloader#setAttribute(java.lang.String, java.io.Serializable) */ public Object setAttribute(String key, Object value, boolean serialize) { return attributes.put( key, new Attribute(serialize, value) ); } /* (non-Javadoc) * @see com.limegroup.gnutella.downloader.CoreDownloader#getAttribute(java.lang.String) */ public Object getAttribute(String key) { Attribute attr = attributes.get(key); if(attr != null) return attr.getObject(); else return null; } /* (non-Javadoc) * @see com.limegroup.gnutella.downloader.CoreDownloader#removeAttribute(java.lang.String) */ public Object removeAttribute(String key) { return attributes.remove( key ); } /* (non-Javadoc) * @see com.limegroup.gnutella.downloader.CoreDownloader#conflicts(java.io.File) */ public boolean conflictsSaveFile(File saveFile) { return getSaveFile().equals(saveFile); } /* (non-Javadoc) * @see com.limegroup.gnutella.downloader.CoreDownloader#setSaveFile(java.io.File, java.lang.String, boolean) */ public void setSaveFile(File saveDirectory, String fileName, boolean overwrite) throws DownloadException { if (fileName == null) { fileName = getDefaultFileName(); } Category category = null; if(fileName != null) { category = categoryManager.getCategoryForFilename(fileName); } if (saveDirectory == null) { saveDirectory = SharingSettings.getSaveDirectory(category); } // Forcibly create the save directory, if it doesn't exist. if(!saveDirectory.exists()) { saveDirectory.mkdirs(); } try { fileName = CommonUtils.convertFileName(saveDirectory, fileName); } catch (IOException ie) { // if not a directory, give precedence to error messages below if (saveDirectory.isDirectory()) { throw new DownloadException(ErrorCode.PATH_NAME_TOO_LONG, saveDirectory); } } if (!saveDirectory.isDirectory()) { if (saveDirectory.exists()) { throw new DownloadException(ErrorCode.NOT_A_DIRECTORY, saveDirectory); } throw new DownloadException(ErrorCode.DIRECTORY_DOES_NOT_EXIST, saveDirectory); } File candidateFile = new File(saveDirectory, fileName); try { if (!FileUtils.isReallyParent(saveDirectory, candidateFile)) throw new DownloadException(ErrorCode.SECURITY_VIOLATION, candidateFile); } catch (IOException e) { throw new DownloadException(ErrorCode.FILESYSTEM_ERROR, candidateFile); } if (! FileUtils.setWriteable(saveDirectory)) throw new DownloadException(ErrorCode.DIRECTORY_NOT_WRITEABLE,saveDirectory); if (candidateFile.exists()) { if (!candidateFile.isFile() && !(this instanceof BTDownloader)) throw new DownloadException(ErrorCode.FILE_NOT_REGULAR, candidateFile); if (!overwrite) throw new DownloadException(ErrorCode.FILE_ALREADY_EXISTS, candidateFile); } // check if another existing download is being saved to this download // we ignore the overwrite flag on purpose in this case if (saveLocationManager.isSaveLocationTaken(candidateFile)) { throw new DownloadException(ErrorCode.FILE_IS_ALREADY_DOWNLOADED_TO, candidateFile); } // Passed sanity checks, so save file synchronized (this) { if (!isRelocatable()) throw new DownloadException(ErrorCode.FILE_ALREADY_SAVED, candidateFile); setSaveFileInternal(candidateFile); } } protected synchronized void setSaveFileInternal(File saveFile) { this.saveFile = saveFile; } public synchronized File getSaveFile() { if(saveFile == null) return getDefaultSaveFile(); else return saveFile; } /** A default location where this file should be saved, if no explicit saveFile is set. */ protected abstract File getDefaultSaveFile(); /** * Returns the value for the key {@link CoreDownloader#DEFAULT_FILENAME} from * the properties map. * <p> * Subclasses should put the name into the map or override this * method. */ protected synchronized String getDefaultFileName() { assert defaultFileName != null : "no default filename initialized!"; return CommonUtils.convertFileName(defaultFileName); } /** Returns true if a defaultFileName is set. */ protected synchronized boolean hasDefaultFileName() { return defaultFileName != null; } /** Sets the default filename this will use. */ protected synchronized void setDefaultFileName(String defaultFileName) { this.defaultFileName = defaultFileName; } public synchronized void initFromMemento(DownloadMemento memento) throws InvalidDataException { this.saveFile = memento.getSaveFile(); setDefaultFileName(memento.getDefaultFileName()); if(memento.getAttributes() != null) { for(Map.Entry<String, Object> entry : memento.getAttributes().entrySet()) { attributes.put(entry.getKey(), new Attribute(true, entry.getValue())); } } } public final synchronized DownloadMemento toMemento() { DownloadMemento memento = createMemento(); fillInMemento(memento); return memento; } /** Constructs the correct type of memento. */ protected abstract DownloadMemento createMemento(); /** Fills in all data this class wants to store. */ protected void fillInMemento(DownloadMemento memento) { memento.setDownloadType(getDownloadType()); memento.setSaveFile(saveFile); memento.setDefaultFileName(defaultFileName); Map<String, Object> saveAttributes = new HashMap<String, Object>(attributes.size()); for(Map.Entry<String, Attribute> entry : attributes.entrySet()) { if(entry.getValue().isSerialize()) saveAttributes.put(entry.getKey(), entry.getValue().getObject()); } memento.setAttributes(saveAttributes); } /** A wrapper for an attribute. */ private static class Attribute { private final boolean serialize; private final Object object; public Attribute(boolean serialize, Object object) { this.serialize = serialize; this.object = object; } public boolean isSerialize() { return serialize; } public Object getObject() { return object; } } @Override public boolean isMementoSupported() { return true; } }