/* * This file is part of DrFTPD, Distributed FTP Daemon. * * DrFTPD is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * DrFTPD is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with DrFTPD; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package org.drftpd.vfs; import java.beans.DefaultPersistenceDelegate; import java.beans.XMLEncoder; import java.io.FileNotFoundException; import java.io.IOException; import java.util.Map; import java.util.TreeMap; import java.util.concurrent.atomic.AtomicInteger; import org.apache.log4j.Logger; import org.drftpd.dynamicdata.Key; import org.drftpd.dynamicdata.KeyNotFoundException; import org.drftpd.dynamicdata.KeyedMap; import org.drftpd.exceptions.FileExistsException; import org.drftpd.io.PermissionDeniedException; import org.drftpd.master.CommitManager; import org.drftpd.master.Commitable; /** * VirtualFileSystemInode is an abstract class used to handle basic functions * of files/dirs/links and to keep an hierarchy/organization of the FS. */ public abstract class VirtualFileSystemInode implements Commitable { protected static final Logger logger = Logger .getLogger(VirtualFileSystemInode.class); /** * @return the VirtualFileSystem instance. */ public static VirtualFileSystem getVFS() { return VirtualFileSystem.getVirtualFileSystem(); } protected transient String _name; protected transient VirtualFileSystemDirectory _parent; protected String _username; protected String _group; protected KeyedMap<Key<?>, Object> _keyedMap = new KeyedMap<Key<?>, Object>(); protected KeyedMap<Key<?>, Object> _pluginMap = new KeyedMap<Key<?>, Object>(); protected Map<String,Object> _untypedPluginMap = new TreeMap<String,Object>(); protected long _lastModified; protected long _creationTime; private transient boolean _inodeLoaded; public String descriptiveName() { return getPath(); } public void writeToDisk() throws IOException { VirtualFileSystem.getVirtualFileSystem().writeInode(this); } public VirtualFileSystemInode(String user, String group) { _username = user; _group = group; _lastModified = System.currentTimeMillis(); _creationTime = _lastModified; } /** * Need to ensure that this is called after each (non-transient) change to * the Inode.<br> * When called, this method will save the Inode data to the disk. */ public void commit() { //logger.debug("Committing " + getPath()); CommitManager.getCommitManager().add(this); } /** * Deletes a file, directory, or link, RemoteSlave handles issues with * slaves being offline and queued deletes */ public void delete() { logger.info("delete(" + this + ")"); String path = getPath(); VirtualFileSystem.getVirtualFileSystem().deleteInode(getPath()); _parent.removeChild(this); CommitManager.getCommitManager().remove(this); getVFS().notifyInodeDeleted(this, path); } /** * @return the owner primary group. */ public String getGroup() { return _group; } /** * @return the KeyedMap containing the Dynamic Data. */ public KeyedMap<Key<?>, Object> getKeyedMap() { return _keyedMap; } /** * @return when the file was last modified. */ public long getLastModified() { return _lastModified; } /** * @return when the file was created. */ public long getCreationTime() { return _creationTime; } /** * @return the file name. */ public String getName() { return _name; } /** * @return parent dir of the file/directory/link. */ public VirtualFileSystemDirectory getParent() { return _parent; } /** * @return Returns the full path. */ protected String getPath() { if (this instanceof VirtualFileSystemRoot) { return VirtualFileSystem.separator; } if (getParent() instanceof VirtualFileSystemRoot) { return VirtualFileSystem.separator + getName(); } return getParent().getPath() + VirtualFileSystem.separator + getName(); } /** * @return Returns the size of the dir/file/link. */ public abstract long getSize(); /** * Set the size of the dir/file/link. */ public abstract void setSize(long l); /** * Sets that the inode has been fully loaded from disk */ public void inodeLoadCompleted() { _inodeLoaded = true; } /** * Returns whether the inode has been fully loaded from disk */ public boolean isInodeLoaded() { return _inodeLoaded; } /** * @return the owner username. */ public String getUsername() { return _username; } public boolean isDirectory() { return this instanceof VirtualFileSystemDirectory; } public boolean isFile() { return this instanceof VirtualFileSystemFile; } public boolean isLink() { return this instanceof VirtualFileSystemLink; } /** * Renames this Inode. * @param destination * @throws FileExistsException */ protected void rename(String destination) throws FileExistsException { if (!destination.startsWith(VirtualFileSystem.separator)) { throw new IllegalArgumentException(destination + " is a relative path and it should be a full path"); } try { VirtualFileSystemInode destinationInode = getVFS().getInodeByPath(destination); if (!destinationInode.equals(this)) { throw new FileExistsException(destination + "already exists"); } } catch (FileNotFoundException e) { // This is good } VirtualFileSystemDirectory destinationDir = null; try { destinationDir = (VirtualFileSystemDirectory) getVFS().getInodeByPath(VirtualFileSystem.stripLast(destination)); } catch (FileNotFoundException e) { throw new RuntimeException("Error in logic, this should not happen", e); } // Ensure source/destination is flushed to ondisk VFS in case either is newly created CommitManager.getCommitManager().flushImmediate(destinationDir); CommitManager.getCommitManager().flushImmediate(this); String fileString = "rename(" + this + ")"; String sourcePath = getPath(); _parent.removeChild(this); try { VirtualFileSystem.getVirtualFileSystem().renameInode( this.getPath(), destinationDir.getPath() + VirtualFileSystem.separator + VirtualFileSystem.getLast(destination)); } catch (FileNotFoundException e) { throw new RuntimeException("Tried to rename a file that does not exist: " + getPath(), e); } catch (PermissionDeniedException e) { throw new RuntimeException("FileSystemError", e); } _name = VirtualFileSystem.getLast(destination); _parent = destinationDir; _parent.addChild(this, true); fileString = fileString + ",(" + this + ")"; logger.info(fileString); getVFS().notifyInodeRenamed(sourcePath,this); } /** * @param group * Sets the group which owns the Inode. */ public void setGroup(String group) { _group = group; if (isInodeLoaded()) { commit(); getVFS().notifyOwnershipChanged(this, getUsername(), _group); } } public void setKeyedMap(KeyedMap<Key<?>, Object> data) { _keyedMap = data; } /** * @param modified * Set when the file was last modified. */ public void setLastModified(long modified) { if (_lastModified != modified) { _lastModified = modified; if (isInodeLoaded()) { commit(); getVFS().notifyLastModifiedChanged(this,_lastModified); } } } /** * @param created * Set when the file was created. */ public void setCreationTime(long created) { if (_creationTime != created) { _creationTime = created; if (isInodeLoaded()) { commit(); } } } /** * Sets the Inode name. */ protected void setName(String name) { _name = name; } public void setParent(VirtualFileSystemDirectory directory) { _parent = directory; } protected void setupXML(XMLEncoder enc) { enc.setPersistenceDelegate(Key.class, new DefaultPersistenceDelegate(new String[] { "owner", "key" })); } /** * @param user * The user to set. */ public void setUsername(String user) { _username = user; if (isInodeLoaded()) { commit(); getVFS().notifyOwnershipChanged(this, _username, getGroup()); } } public KeyedMap<Key<?>, Object> getPluginMap() { return _pluginMap; } public void setPluginMap(KeyedMap<Key<?>, Object> data) { _pluginMap = data; } public Map<String,Object> getUntypedPluginMap() { return _untypedPluginMap; } public void setUntypedPluginMap(Map<String,Object> data) { _untypedPluginMap = data; } protected <T> void addPluginMetaData(Key<T> key, T object) { _pluginMap.setObject(key,object); commit(); getVFS().notifyInodeRefresh(this, false); } @SuppressWarnings("unchecked") protected <T> T removePluginMetaData(Key<T> key) { T value = (T)_pluginMap.remove(key); commit(); getVFS().notifyInodeRefresh(this, false); return value; } public <T> T getPluginMetaData(Key<T> key) throws KeyNotFoundException { return _pluginMap.getObject(key); } protected synchronized <T> void addUntypedPluginMetaData(String key, T object) { _untypedPluginMap.put(key,object); commit(); } @SuppressWarnings("unchecked") protected <T> T removeUntypedPluginMetaData(String key) { T value = (T)_untypedPluginMap.remove(key); if (value != null) { commit(); } return value; } @SuppressWarnings("unchecked") public <T> T getUntypedPluginMetaData(String key) { T value = (T)_untypedPluginMap.get(key); return value; } /* * (non-Javadoc) * * @see java.lang.Object#toString() */ @Override public String toString() { StringBuffer ret = new StringBuffer(); ret.append("[path=" + getPath() + "]"); ret.append("[user,group=" + getUsername() + "," + getGroup() + "]"); ret.append("[lastModified=" + getLastModified() + "]"); ret.append("[size=" + getSize() + "]"); return ret.toString(); } @Override public boolean equals(Object obj) { if (!(obj instanceof VirtualFileSystemInode)) return false; return ((VirtualFileSystemInode) obj).getPath().equalsIgnoreCase(getPath()); } protected abstract Map<String,AtomicInteger> getSlaveRefCounts(); /** * Publish a refresh notification for this inode */ protected void refresh(boolean sync) { getVFS().notifyInodeRefresh(this, sync); } }