/*
 * Copyright (C) 2005-2007 Alfresco Software Limited.
 *
 * This program 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.

 * This program 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 this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 * As a special exception to the terms and conditions of version 2.0 of
 * the GPL, you may redistribute this Program in connection with Free/Libre
 * and Open Source Software ("FLOSS") applications as described in Alfresco's
 * FLOSS exception.  You should have recieved a copy of the text describing
 * the FLOSS exception, and it is also available here:
 * http://www.alfresco.com/legal/licensing" */

package org.alfresco.filesys.avm;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedMap;
import java.util.StringTokenizer;

import javax.transaction.UserTransaction;

import org.alfresco.config.ConfigElement;
import org.alfresco.filesys.alfresco.AlfrescoDiskDriver;
import org.alfresco.filesys.server.SrvSession;
import org.alfresco.filesys.server.core.DeviceContext;
import org.alfresco.filesys.server.core.DeviceContextException;
import org.alfresco.filesys.server.core.DeviceInterface;
import org.alfresco.filesys.server.filesys.AccessDeniedException;
import org.alfresco.filesys.server.filesys.DirectoryNotEmptyException;
import org.alfresco.filesys.server.filesys.DiskInterface;
import org.alfresco.filesys.server.filesys.FileAttribute;
import org.alfresco.filesys.server.filesys.FileExistsException;
import org.alfresco.filesys.server.filesys.FileInfo;
import org.alfresco.filesys.server.filesys.FileName;
import org.alfresco.filesys.server.filesys.FileOpenParams;
import org.alfresco.filesys.server.filesys.FileStatus;
import org.alfresco.filesys.server.filesys.NetworkFile;
import org.alfresco.filesys.server.filesys.PathNotFoundException;
import org.alfresco.filesys.server.filesys.SearchContext;
import org.alfresco.filesys.server.filesys.TreeConnection;
import org.alfresco.filesys.server.pseudo.PseudoFile;
import org.alfresco.filesys.server.pseudo.PseudoFileList;
import org.alfresco.filesys.server.pseudo.PseudoFolderNetworkFile;
import org.alfresco.filesys.server.state.FileState;
import org.alfresco.filesys.util.StringList;
import org.alfresco.filesys.util.WildCard;
import org.alfresco.repo.avm.CreateStoreTxnListener;
import org.alfresco.repo.avm.CreateVersionTxnListener;
import org.alfresco.repo.avm.PurgeStoreTxnListener;
import org.alfresco.repo.avm.PurgeVersionTxnListener;
import org.alfresco.repo.domain.PropertyValue;
import org.alfresco.repo.security.authentication.AuthenticationComponent;
import org.alfresco.service.cmr.avm.AVMBadArgumentException;
import org.alfresco.service.cmr.avm.AVMExistsException;
import org.alfresco.service.cmr.avm.AVMNodeDescriptor;
import org.alfresco.service.cmr.avm.AVMNotFoundException;
import org.alfresco.service.cmr.avm.AVMService;
import org.alfresco.service.cmr.avm.AVMStoreDescriptor;
import org.alfresco.service.cmr.avm.AVMWrongTypeException;
import org.alfresco.service.cmr.avm.VersionDescriptor;
import org.alfresco.service.cmr.repository.MimetypeService;
import org.alfresco.service.cmr.security.AuthenticationService;
import org.alfresco.service.namespace.QName;
import org.alfresco.service.transaction.TransactionService;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * AVM Repository Filesystem Driver Class
 * 
 * <p>Provides a filesystem interface for various protocols such as SMB/CIFS and FTP.
 * 
 * @author GKSpencer
 */
public class AVMDiskDriver extends AlfrescoDiskDriver implements DiskInterface {

    // Logging
    
    private static final Log logger = LogFactory.getLog(AVMDiskDriver.class);
    
    // Configuration key names
    
    private static final String KEY_STORE 		= "storePath";
    private static final String KEY_VERSION		= "version";
    private static final String KEY_CREATE		= "createStore";

    // AVM path seperator
    
    public static final char AVM_SEPERATOR			= '/';
    public static final String AVM_SEPERATOR_STR	= "/";
    
    // Services and helpers
    
    private AVMService m_avmService;
    private TransactionService m_transactionService;
    private MimetypeService m_mimetypeService;
    
    private AuthenticationComponent m_authComponent;
    private AuthenticationService m_authService;
    
    // AVM listeners
    
    private CreateStoreTxnListener m_createStoreListener;
    private PurgeStoreTxnListener m_purgeStoreListener;
    
    private CreateVersionTxnListener m_createVerListener;
    private PurgeVersionTxnListener m_purgeVerListener;
    
    /**
     * Default constructor
     */
    public AVMDiskDriver()
    {
    }
    
    /**
     * Return the AVM service
     * 
     * @return AVMService
     */
    public final AVMService getAvmService()
    {
    	return m_avmService;
    }
    
    /**
     * Return the authentication service
     * 
     * @return AuthenticationService
     */
    public final AuthenticationService getAuthenticationService()
    {
    	return m_authService;
    }
    
    /**
     * Return the transaction service
     * 
     * @return TransactionService
     */
    public final TransactionService getTransactionService()
    {
    	return m_transactionService;
    }
    
    /**
     * Set the AVM service
     * 
     * @param avmService AVMService
     */
    public void setAvmService(AVMService avmService)
    {
    	m_avmService = avmService;
    }
    
    /**
     * Set the transaction service
     * 
     * @param transactionService the transaction service
     */
    public void setTransactionService(TransactionService transactionService)
    {
        m_transactionService = transactionService;
    }

    /**
     * Set the authentication component
     * 
     * @param authComponent AuthenticationComponent
     */
    public void setAuthenticationComponent(AuthenticationComponent authComponent)
    {
        m_authComponent = authComponent;
    }

    /**
     * Set the authentication service
     * 
     * @param authService AuthenticationService
     */
    public void setAuthenticationService(AuthenticationService authService)
    {
    	m_authService = authService;
    }
    
    /**
     * Set the mimetype service
     * 
     * @param mimetypeService MimetypeService
     */
    public void setMimetypeService(MimetypeService mimetypeService)
    {
        m_mimetypeService = mimetypeService;
    }
    
    /**
     * Set the create store listener
     * 
     * @param createStoreListener CreateStoreTxnListener
     */
    public void setCreateStoreListener(CreateStoreTxnListener createStoreListener)
    {
    	m_createStoreListener = createStoreListener;
    }
    
    /**
     * Set the purge store listener
     * 
     * @param purgeStoreListener PurgeStoreTxnListener
     */
    public void setPurgeStoreListener(PurgeStoreTxnListener purgeStoreListener)
    {
    	m_purgeStoreListener = purgeStoreListener;
    }
    
    /**
     * Set the create version listener
     * 
     * @param createVersionListener CreateVersionTxnListener
     */
    public void setCreateVersionListener(CreateVersionTxnListener createVersionListener)
    {
    	m_createVerListener = createVersionListener;
    }
    
    /**
     * Set the purge version listener
     * 
     * @param purgeVersionListener PurgeVersionTxnListener
     */
    public void setPurgeVersionListener(PurgeVersionTxnListener purgeVersionListener)
    {
    	m_purgeVerListener = purgeVersionListener;
    }
    
    /**
     * Parse and validate the parameter string and create a device context object for this instance
     * of the shared device.
     * 
     * @param devIface DeviceInterface
     * @param name String
     * @param cfg ConfigElement
     * @return DeviceContext
     * @exception DeviceContextException
     */
    public DeviceContext createContext(DeviceInterface devIface, String name, ConfigElement cfg)
    	throws DeviceContextException
    {
        // Use the system user as the authenticated context for the filesystem initialization
        
        m_authComponent.setCurrentUser( m_authComponent.getSystemUserName());
        
        // Wrap the initialization in a transaction
        
        UserTransaction tx = m_transactionService.getUserTransaction(false);

        AVMContext context = null;
        
        try
        {
            // Start the transaction
            
            if ( tx != null)
                tx.begin();
            
            // Check if the share is a virtualization view
            
            ConfigElement virtElem = cfg.getChild( "virtualView");
            if ( virtElem != null)
            {
            	// Check if sandboxes should be shown in the virtualization view
            	
            	boolean showSandboxes = cfg.getChild( "showAllSandboxes") != null ? true : false;
            	
            	// Create the context
            	
            	context = new AVMContext( name, showSandboxes, this);
            	
	            // Enable file state caching
	            
	            context.enableStateTable( true, getStateReaper());
	            
	            // Plug the virtualization view context into the various store/version call back listeners
	            // so that store/version pseudo folders can be kept in sync with AVM
	            
	            m_createStoreListener.addCallback( context);
	            m_purgeStoreListener.addCallback( context);
	            
	            m_createVerListener.addCallback( context);
	            m_purgeVerListener.addCallback( context);
            }
            else
            {
	            // Get the store path
	            
	            ConfigElement storeElement = cfg.getChild(KEY_STORE);
	            if (storeElement == null || storeElement.getValue() == null || storeElement.getValue().length() == 0)
	                throw new DeviceContextException("Device missing init value: " + KEY_STORE);
	            
	            String storePath = storeElement.getValue();
	
	            // Get the version if specified, or default to the head version
	            
	            int version = AVMContext.VERSION_HEAD;
	            
	            ConfigElement versionElem = cfg.getChild(KEY_VERSION);
	            if ( versionElem != null)
	            {
	            	// Check if the version is valid
	            	
	            	if ( versionElem.getValue() == null || versionElem.getValue().length() == 0)
	            		throw new DeviceContextException("Store version not specified");
	            	
	            	// Validate the version id
	            	
	            	try
	            	{
	            		version = Integer.parseInt( versionElem.getValue());
	            	}
	            	catch ( NumberFormatException ex)
	            	{
	            		throw new DeviceContextException("Invalid store version specified, " + versionElem.getValue());
	            	}
	            	
	            	// Range check the version id
	            	
	            	if ( version < 0 && version != AVMContext.VERSION_HEAD)
	            		throw new DeviceContextException("Invalid store version id specified, " + version);
	            }
	            
	            // Check if the create flag is enabled
	            
	            ConfigElement createStore = cfg.getChild( KEY_CREATE);

	            // Validate the store path
	            
	            AVMNodeDescriptor rootNode = m_avmService.lookup( version, storePath);
	            if ( rootNode == null)
	            {
	            	// Check if the store should be created
	            	
	            	if ( createStore == null || version != AVMContext.VERSION_HEAD)
	            		throw new DeviceContextException("Invalid store path/version, " + storePath + " (" + version + ")");
	            	
	            	// Parse the store path
	            	
	            	String storeName = null;
	            	String path = null;
	            	
	            	int pos = storePath.indexOf(":/");
	            	if ( pos != -1)
	            	{
	            		storeName = storePath.substring(0, pos);
	            		if ( storePath.length() > pos)
	            			path = storePath.substring(pos + 2);
	            	}
	            	else
	            		storeName = storePath;
	            	
	            	// Check if the store exists
	            	
	            	AVMStoreDescriptor storeDesc = null;
	            	
	            	try
	            	{
	            		storeDesc = m_avmService.getStore( storeName);
	            	}
	            	catch (AVMNotFoundException ex)
	            	{
	            	}
	            	
	            	// Create a new store if it does not exist
	            	
	            	if ( storeDesc == null)
	            		m_avmService.createStore( storeName);
	            	
	            	// Check if there is an optional path
	            	
	            	if ( path != null)
	            	{
	            		// Split the path
	            		
	            		StringTokenizer tokens = new StringTokenizer( path, AVMPath.AVM_SEPERATOR_STR);
	            		StringList paths = new StringList();
	            		
	            		while ( tokens.hasMoreTokens())
	            			paths.addString( tokens.nextToken());
	            		
	            		// Create the path, or folders that do not exist
	            		
	            		AVMPath curPath = new AVMPath( storeName, version, FileName.DOS_SEPERATOR_STR);
	            		AVMNodeDescriptor curDesc = m_avmService.lookup( curPath.getVersion(), curPath.getAVMPath());

            			// Walk the path checking creating each folder as required
	            			
	            		for ( int i = 0; i < paths.numberOfStrings(); i++)
	            		{
	            			AVMNodeDescriptor nextDesc = null;
	            			
	            			try
	            			{
	            				// Check if the child folder exists
		            			
		            			nextDesc = m_avmService.lookup( curDesc, paths.getStringAt( i));
		            		}
		            		catch ( AVMNotFoundException ex)
		            		{
		            		}
		            		
		            		// Check if the folder exists
		            		
		            		if ( nextDesc == null)
		            		{
		            			// Create the new folder
		            			
		            			m_avmService.createDirectory( curPath.getAVMPath(), paths.getStringAt( i));
		            			
		            			// Get the details of the new folder
		            			
		            			nextDesc = m_avmService.lookup( curDesc, paths.getStringAt( i));
		            		}
		            		else if ( nextDesc.isFile())
		            			throw new DeviceContextException("Path element error, not a folder, " + paths.getStringAt( i));
		            		
		            		// Step to the next level
		            		
		            		curPath.parsePath( storeName, version, curPath.getRelativePath() + paths.getStringAt( i) + FileName.DOS_SEPERATOR_STR);
		            		curDesc = nextDesc;
	            		}
	            	}
	            	
	            	// Validate the store path again
	            	
	            	rootNode = m_avmService.lookup( version, storePath);
	            	if ( rootNode == null)
	            		throw new DeviceContextException("Failed to create new store " + storePath);
	            }
	            
	            // Create the context
	            
	            context = new AVMContext( name, storePath, version);

	            // Enable file state caching
	            
	            context.enableStateTable( true, getStateReaper());
            }

            // Commit the transaction
            
            tx.commit();
            tx = null;
        }
        catch (Exception ex)
        {
            logger.error("Error during create context", ex);
            
            // Rethrow the exception
            
            throw new DeviceContextException("Driver setup error, " + ex.getMessage());
        }
        finally
        {
            // If there is an active transaction then roll it back
            
            if ( tx != null)
            {
                try
                {
                    tx.rollback();
                }
                catch (Exception ex)
                {
                    logger.warn("Failed to rollback transaction", ex);
                }
            }
        }

        // Return the context for this shared filesystem
        
        return context;
    }

    /**
     * Return a list of the available AVM store names
     * 
     * @return StringList
     */
    public final StringList getAVMStoreNames()
    {
        // Use the system user as the authenticated context to get the AVM store list
        
        m_authComponent.setCurrentUser( m_authComponent.getSystemUserName());
        
        // Wrap the service request in a transaction
        
        UserTransaction tx = m_transactionService.getUserTransaction(false);

        StringList storeNames = new StringList();
        
        try
        {
            // Start the transaction
            
            if ( tx != null)
                tx.begin();

            // Get the list of AVM stores
            
            List<AVMStoreDescriptor> storeList = m_avmService.getStores();
            
            if ( storeList != null)
            {
            	for ( AVMStoreDescriptor storeDesc : storeList)
            		storeNames.addString( storeDesc.getName());
            }

            // Commit the transaction
            
            tx.commit();
            tx = null;
        }
        catch (Exception ex)
        {
	        logger.error("Error getting store names", ex);
	    }
	    finally
	    {
	        // If there is an active transaction then roll it back
	        
	        if ( tx != null)
	        {
	            try
	            {
	                tx.rollback();
	            }
	            catch (Exception ex)
	            {
	                logger.warn("Failed to rollback transaction", ex);
	            }
	        }
	    }
	    
	    // Return the list of AVM store names
	    
	    return storeNames;
    }
    
    /**
     * Get the properties for a store
     * 
     * @param storeName String
     * @return Map<QName, PropertyValue>
     */
    protected final Map<QName, PropertyValue> getAVMStoreProperties( String storeName)
    {
        // Use the system user as the authenticated context to get the AVM store properties
        
        m_authComponent.setCurrentUser( m_authComponent.getSystemUserName());
        
        // Wrap the service request in a transaction
        
        UserTransaction tx = m_transactionService.getUserTransaction(false);

        Map<QName, PropertyValue> properties = null;
        
        try
        {
            // Start the transaction
            
            if ( tx != null)
                tx.begin();

            // Get the list of properties for AVM store
            
            properties = m_avmService.getStoreProperties( storeName);

            // Commit the transaction
            
            tx.commit();
            tx = null;
        }
        catch (Exception ex)
        {
	        logger.error("Error getting store properties", ex);
	    }
	    finally
	    {
	        // If there is an active transaction then roll it back
	        
	        if ( tx != null)
	        {
	            try
	            {
	                tx.rollback();
	            }
	            catch (Exception ex)
	            {
	                logger.warn("Failed to rollback transaction", ex);
	            }
	        }
	    }
	    
	    // Return the list of AVM store properties
	    
	    return properties;
    }
    
    /**
     * Build the full store path for a file/folder using the share relative path
     * 
     * @param ctx AVMContext
     * @param path String
     * @return AVMPath
     */
    protected final AVMPath buildStorePath( AVMContext ctx, String path)
    {
    	// Check if the AVM filesystem is a normal or virtualization view

    	AVMPath avmPath = null;
    	
    	if ( ctx.isVirtualizationView())
    	{
    		// Create a path for the virtualization view
    		
    		avmPath = new AVMPath( path);
    	}
    	else
    	{
    		// Create a path to a single store/version
    		
    		avmPath = new AVMPath( ctx.getStorePath(), ctx.isVersion(), path);
    	}
    	
    	// Return the path
    	
    	return avmPath;
    }
    
	/**
     * Close the file.
     * 
     * @param sess Server session
     * @param tree Tree connection.
     * @param file Network file context.
     * @exception java.io.IOException If an error occurs.
     */
    public void closeFile(SrvSession sess, TreeConnection tree, NetworkFile file)
    	throws java.io.IOException
    {
    	// DEBUG
    	
    	if ( logger.isDebugEnabled())
    		logger.debug("Close file " + file.getFullName());
    	
        //	Close the file
        
        file.closeFile();

        //	Check if the file/directory is marked for delete
      	
      	if ( file.hasDeleteOnClose()) {
      		
      		//	Check for a file or directory
      		
      		if ( file.isDirectory())
      			deleteDirectory(sess, tree, file.getFullName());
      		else
      			deleteFile(sess, tree, file.getFullName());
      	}
    }

    /**
     * Create a new directory on this file system.
     * 
     * @param sess Server session
     * @param tree Tree connection.
     * @param params Directory create parameters
     * @exception java.io.IOException If an error occurs.
     */
    public void createDirectory(SrvSession sess, TreeConnection tree, FileOpenParams params)
    	throws java.io.IOException
    {
    	// Check if the filesystem is writable
    	
    	AVMContext ctx = (AVMContext) tree.getContext();
    	if ( ctx.isVersion() != AVMContext.VERSION_HEAD)
    		throw new AccessDeniedException("Cannot create " + params.getPath() + ", filesys not writable");
    	
    	// Split the path to get the new folder name and relative path
    	
    	String[] paths = FileName.splitPath( params.getPath());
    	
    	// Convert the relative path to a store path
    	
    	AVMPath storePath = buildStorePath( ctx, paths[0]);
    	
    	// DEBUG
    	
    	if ( logger.isDebugEnabled())
    		logger.debug("Create directory params=" + params + ", storePath=" + storePath + ", name=" + paths[1]);
    	
    	// Check if the filesystem is the virtualization view
    	
    	if ( ctx.isVirtualizationView() && storePath.isReadOnlyPseudoPath())
    	{
    		throw new AccessDeniedException( "Cannot create folder in store/version layer, " + params.getPath());
    	}
    	
    	// Create a new file
    	
    	sess.beginWriteTransaction( m_transactionService);
    	
    	try
    	{
    		// Create the new file entry

    		m_avmService.createDirectory( storePath.getAVMPath(), paths[1]);
    	}
    	catch ( AVMExistsException ex)
    	{
    		throw new FileExistsException( params.getPath());
    	}
    	catch ( AVMNotFoundException ex)
    	{
    		throw new FileNotFoundException( params.getPath());
    	}
    	catch ( AVMWrongTypeException ex)
    	{
    		throw new FileNotFoundException( params.getPath());
    	}
    	catch ( AVMBadArgumentException ex)
    	{
    		throw new FileNotFoundException( params.getPath());
    	}
    }
    
    /**
     * Create a new file on the file system.
     * 
     * @param sess Server session
     * @param tree Tree connection
     * @param params File create parameters
     * @return NetworkFile
     * @exception java.io.IOException If an error occurs.
     */
    public NetworkFile createFile(SrvSession sess, TreeConnection tree, FileOpenParams params)
    	throws java.io.IOException
    {
    	// Check if the filesystem is writable
    	
    	AVMContext ctx = (AVMContext) tree.getContext();
    	
    	// Split the path to get the file name and relative path
    	
    	String[] paths = FileName.splitPath( params.getPath());
    	
    	// Convert the relative path to a store path
    	
    	AVMPath storePath = buildStorePath( ctx, paths[0]);
    	
    	// DEBUG
    	
    	if ( logger.isDebugEnabled())
    		logger.debug("Create file params=" + params + ", storePath=" + storePath + ", name=" + paths[1]);
    	
    	// Check if the filesystem is the virtualization view
    	
    	if ( ctx.isVirtualizationView() && storePath.isReadOnlyPseudoPath())
    	{
    		throw new AccessDeniedException( "Cannot create file in store/version layer, " + params.getPath());
    	}
    	else if ( storePath.getVersion() != AVMContext.VERSION_HEAD)
    	{
    		throw new AccessDeniedException("Cannot create " + params.getPath() + ", filesys not writable");
    	}
    	
    	// Create a new file
    	
    	sess.beginWriteTransaction( m_transactionService);
    	
    	AVMNetworkFile netFile = null;
    	
    	try
    	{
    		// Create the new file entry

    		m_avmService.createFile( storePath.getAVMPath(), paths[1]).close();

    		// Get the new file details
    		
    		AVMPath fileStorePath = buildStorePath( ctx, params.getPath());
    		AVMNodeDescriptor nodeDesc = m_avmService.lookup( fileStorePath.getVersion(), fileStorePath.getAVMPath());

    		if ( nodeDesc != null)
	    	{
	    	    //	Create the network file object for the new file
	    	    
	    	    netFile = new AVMNetworkFile( nodeDesc, fileStorePath.getAVMPath(), fileStorePath.getVersion(), m_avmService);
    	    	netFile.setGrantedAccess(NetworkFile.READWRITE);
	    	    netFile.setFullName(params.getPath());
	    	    
	    	    netFile.setFileId( fileStorePath.generateFileId());
	    	    
	    	    // Set the mime-type for the new file
	    	    
	    	    netFile.setMimeType( m_mimetypeService.guessMimetype( paths[1]));
	    	}
    	}
    	catch ( AVMExistsException ex)
    	{
    		throw new FileExistsException( params.getPath());
    	}
    	catch ( AVMNotFoundException ex)
    	{
    		throw new FileNotFoundException( params.getPath());
    	}
    	catch ( AVMWrongTypeException ex)
    	{
    		throw new FileNotFoundException( params.getPath());
    	}
    	catch ( AVMBadArgumentException ex)
    	{
    		throw new FileNotFoundException( params.getPath());
    	}
    	
    	// Return the file
    	
    	return netFile;
    }
    
    /**
     * Delete the directory from the filesystem.
     * 
     * @param sess Server session
     * @param tree Tree connection
     * @param dir Directory name.
     * @exception java.io.IOException The exception description.
     */
    public void deleteDirectory(SrvSession sess, TreeConnection tree, String dir)
    	throws java.io.IOException
    {
    	// Convert the relative path to a store path
    	
    	AVMContext ctx = (AVMContext) tree.getContext();
    	AVMPath storePath = buildStorePath( ctx, dir);
    	
    	// DEBUG
    	
    	if ( logger.isDebugEnabled())
    		logger.debug("Delete directory, path=" + dir + ", storePath=" + storePath);
    	
    	// Check if the filesystem is the virtualization view
    	
    	if ( ctx.isVirtualizationView() && storePath.isPseudoPath())
    	{
    		throw new AccessDeniedException( "Cannot delete pseudo folder, " + dir);
    	}
    	
    	// Make sure the path is to a folder before deleting it
    	
    	sess.beginWriteTransaction( m_transactionService);
    	
    	try
    	{
    		AVMNodeDescriptor nodeDesc = m_avmService.lookup( storePath.getVersion(), storePath.getAVMPath());
	    	if ( nodeDesc != null)
	    	{
	    		// Check that we are deleting a folder
	    		
	    		if ( nodeDesc.isDirectory())
	    		{
	    			// Make sure the directory is empty
	    			
	    			SortedMap<String, AVMNodeDescriptor> fileList = m_avmService.getDirectoryListing( nodeDesc);
	    			if ( fileList != null && fileList.size() > 0)
	    				throw new DirectoryNotEmptyException( dir);
	    			
	    			// Delete the folder
	    			
	    			m_avmService.removeNode( storePath.getAVMPath());
	    		}
	    		else
	    			throw new IOException( "Delete directory path is not a directory, " + dir);
	    	}
    	}
    	catch ( AVMNotFoundException ex)
    	{
    		throw new IOException( "Directory not found, " + dir);
    	}
    	catch ( AVMWrongTypeException ex)
    	{
    		throw new IOException( "Invalid path, " + dir);
    	}
    }

    /**
     * Delete the specified file.
     * 
     * @param sess Server session
     * @param tree Tree connection
     * @param file NetworkFile
     * @exception java.io.IOException The exception description.
     */
    public void deleteFile(SrvSession sess, TreeConnection tree, String name)
    	throws java.io.IOException
    {
    	// Convert the relative path to a store path
    	
    	AVMContext ctx = (AVMContext) tree.getContext();
    	AVMPath storePath = buildStorePath( ctx, name);
    	
    	// DEBUG
    	
    	if ( logger.isDebugEnabled())
    		logger.debug("Delete file, path=" + name + ", storePath=" + storePath);
    	
    	// Check if the filesystem is the virtualization view
    	
    	if ( ctx.isVirtualizationView() && storePath.isPseudoPath())
    	{
    		throw new AccessDeniedException( "Cannot delete pseudo file, " + name);
    	}
    	
    	// Make sure the path is to a file before deleting it
    	
    	sess.beginWriteTransaction( m_transactionService);
    	
    	try
    	{
    		AVMNodeDescriptor nodeDesc = m_avmService.lookup( storePath.getVersion(), storePath.getAVMPath());
	    	if ( nodeDesc != null)
	    	{
	    		// Check that we are deleting a file
	    		
	    		if ( nodeDesc.isFile())
	    		{
	    			// Delete the file
	    			
	    			m_avmService.removeNode( storePath.getAVMPath());
	    		}
	    		else
	    			throw new IOException( "Delete file path is not a file, " + name);
	    	}
    	}
    	catch ( AVMNotFoundException ex)
    	{
    		throw new IOException( "File not found, " + name);
    	}
    	catch ( AVMWrongTypeException ex)
    	{
    		throw new IOException( "Invalid path, " + name);
    	}
    }

    /**
     * Check if the specified file exists, and whether it is a file or directory.
     * 
     * @param sess Server session
     * @param tree Tree connection
     * @param name java.lang.String
     * @return int
     * @see FileStatus
     */
    public int fileExists(SrvSession sess, TreeConnection tree, String name)
    {
    	// Convert the relative path to a store path
    	
    	AVMContext ctx = (AVMContext) tree.getContext();
    	AVMPath storePath = buildStorePath( ctx, name);
    	
    	// DEBUG
    	
    	if ( logger.isDebugEnabled())
    		logger.debug("File exists check, path=" + name + ", storePath=" + storePath);
    	
    	// Check if the path is valid
    	
    	int status = FileStatus.NotExist;
    	
    	if ( storePath.isValid() == false)
    		return status;
    	
    	// Check if the filesystem is the virtualization view
    	
    	if ( ctx.isVirtualizationView() && storePath.isReadOnlyPseudoPath())
    	{
    		// Find the file state for the pseudo folder
    		
    		FileState fstate = findPseudoState( storePath, ctx);
    		
    		if ( fstate != null)
    		{
				// DEBUG
				
				if ( logger.isDebugEnabled())
					logger.debug( "  Found pseudo file " + fstate);
				
    			// Check if the pseudo file is a file or folder
    			
    			if ( fstate.isDirectory())
    				status = FileStatus.DirectoryExists;
    			else
    				status = FileStatus.FileExists;
    		}
   			else
   			{
   				// Invalid pseudo file path
   				
   				status = FileStatus.NotExist;
    		}
    		
    		// Return the file status
    		
    		return status;
    	}
    	
    	// Search for the file/folder
    	
    	sess.beginReadTransaction( m_transactionService);
    	
    	AVMNodeDescriptor nodeDesc = m_avmService.lookup( storePath.getVersion(), storePath.getAVMPath());
    	
    	if ( nodeDesc != null)
    	{
    		// Check if the path is to a file or folder
    		
    		if ( nodeDesc.isDirectory())
    			status = FileStatus.DirectoryExists;
    		else
    			status = FileStatus.FileExists;
    	}
    	
    	// Return the file status
    	
    	return status;
    }

    /**
     * Flush any buffered output for the specified file.
     * 
     * @param sess Server session
     * @param tree Tree connection
     * @param file Network file context.
     * @exception java.io.IOException The exception description.
     */
    public void flushFile(SrvSession sess, TreeConnection tree, NetworkFile file)
    	throws java.io.IOException
    {
        //	Flush the file
        
        file.flushFile();
    }

    /**
     * Get the file information for the specified file.
     * 
     * @param sess Server session
     * @param tree Tree connection
     * @param name File name/path that information is required for.
     * @return File information if valid, else null
     * @exception java.io.IOException The exception description.
     */
    public FileInfo getFileInformation(SrvSession sess, TreeConnection tree, String name)
    	throws java.io.IOException
    {
    	// Convert the relative path to a store path
    	
    	AVMContext ctx = (AVMContext) tree.getContext();
    	AVMPath storePath = buildStorePath( ctx, name);
    	
    	// DEBUG
    	
    	if ( logger.isDebugEnabled())
    		logger.debug("Get file information, path=" + name + ", storePath=" + storePath);

    	// Check if hte path is valid
    	
    	if ( storePath.isValid() == false)
    		throw new FileNotFoundException( name);
    	
    	// Check if the filesystem is the virtualization view
    	
    	if ( ctx.isVirtualizationView() && storePath.isReadOnlyPseudoPath())
    	{
    		// Check if the search path is for the root, a store or version folder
    		
    		if ( storePath.isRootPath())
    		{
    			// Return dummy file informatiom for the root folder
    			
    			return new FileInfo( name, 0L, FileAttribute.Directory);
    		}
    		else
    		{
    			// Find the pseudo file for the store/version folder
    			
    			PseudoFile psFile = findPseudoFolder( storePath, ctx);
    			if ( psFile != null)
    			{
    				// DEBUG
    				
    				if ( logger.isDebugEnabled())
    					logger.debug( "  Found pseudo file " + psFile);
    				return psFile.getFileInfo();
    			}
    			else
    				throw new FileNotFoundException( name);
    		}
    	}
    	
    	// Search for the file/folder
    	
    	sess.beginReadTransaction( m_transactionService);
    	
    	FileInfo info = null;
    	
    	try
    	{
	    	AVMNodeDescriptor nodeDesc = m_avmService.lookup( storePath.getVersion(), storePath.getAVMPath());
	    	
	    	if ( nodeDesc != null)
	    	{
	    		// Create, and fill in, the file information
	    		
	    		info = new FileInfo();
	        	
	        	info.setFileName( nodeDesc.getName());
	        	
	        	if ( nodeDesc.isFile())
	        	{
	        		info.setFileSize( nodeDesc.getLength());
	        		info.setAllocationSize((nodeDesc.getLength() + 512L) & 0xFFFFFFFFFFFFFE00L);
	        	}
	        	else
	        		info.setFileSize( 0L);
	
	        	info.setAccessDateTime( nodeDesc.getAccessDate());
	        	info.setCreationDateTime( nodeDesc.getCreateDate());
	        	info.setModifyDateTime( nodeDesc.getModDate());
	
	        	// Build the file attributes
	        	
	        	int attr = 0;
	        	
	        	if ( nodeDesc.isDirectory())
	        		attr += FileAttribute.Directory;
	        	
	        	if ( nodeDesc.getName().startsWith( ".") ||
	        			nodeDesc.getName().equalsIgnoreCase( "Desktop.ini") ||
	        			nodeDesc.getName().equalsIgnoreCase( "Thumbs.db"))
	        		attr += FileAttribute.Hidden;
	        	
	        	// Mark the file/folder as read-only if not the head version
	        	
	        	if ( ctx.isVersion() != AVMContext.VERSION_HEAD)
	        		attr += FileAttribute.ReadOnly;
	        	
	        	info.setFileAttributes( attr);

	        	// Set the file id
	        	
	        	info.setFileId( storePath.generateFileId());
	        	
	        	// DEBUG
	        	
	        	if ( logger.isDebugEnabled())
	        		logger.debug("  File info=" + info);
	    	}
    	}
    	catch ( AVMNotFoundException ex)
    	{
    		throw new FileNotFoundException( name);
    	}
    	catch ( AVMWrongTypeException ex)
    	{
    		throw new PathNotFoundException( name);
    	}
    	
    	// Return the file information
    	
    	return info;
    }

    /**
     * Determine if the disk device is read-only.
     * 
     * @param sess Server session
     * @param ctx Device context
     * @return boolean
     * @exception java.io.IOException If an error occurs.
     */
    public boolean isReadOnly(SrvSession sess, DeviceContext ctx)
    	throws java.io.IOException
    {
    	// Check if the version indicates the head version, only the head is writable
    	
    	AVMContext avmCtx = (AVMContext) ctx;
    	return avmCtx.isVersion() == AVMContext.VERSION_HEAD ? true : false;
    }

    /**
     * Open a file on the file system.
     * 
     * @param sess Server session
     * @param tree Tree connection
     * @param params File open parameters
     * @return NetworkFile
     * @exception java.io.IOException If an error occurs.
     */
    public NetworkFile openFile(SrvSession sess, TreeConnection tree, FileOpenParams params)
    	throws java.io.IOException
    {
    	// Convert the relative path to a store path
    	
    	AVMContext ctx = (AVMContext) tree.getContext();
    	AVMPath storePath = buildStorePath( ctx, params.getPath());
    	
    	// DEBUG
    	
    	if ( logger.isDebugEnabled())
    		logger.debug("Open file params=" + params + ", storePath=" + storePath);
    	
    	// Check if the filesystem is the virtualization view
    	
    	if ( ctx.isVirtualizationView() && storePath.isReadOnlyPseudoPath())
    	{
    		// Check if the path is for the root, a store or version folder
    		
    		if ( storePath.isRootPath())
    		{
    			// Return a dummy file for the root folder
    			
    			return new PseudoFolderNetworkFile( FileName.DOS_SEPERATOR_STR);
    		}
    		else
    		{
    			// Find the pseudo file for the store/version folder
    			
    			PseudoFile psFile = findPseudoFolder( storePath, ctx);
    			if ( psFile != null)
    			{
    				// DEBUG
    				
    				if ( logger.isDebugEnabled())
    					logger.debug( "  Found pseudo file " + psFile);
    				return psFile.getFile( params.getPath());
    			}
    			else
    				return null;
    		}
    	}
    	
    	// Search for the file/folder
    	
    	sess.beginReadTransaction( m_transactionService);
    	
    	AVMNetworkFile netFile = null;
    	
    	try
    	{
    		// Get the details of the file/folder

    		AVMNodeDescriptor nodeDesc = m_avmService.lookup( storePath.getVersion(), storePath.getAVMPath());
    	
	    	if ( nodeDesc != null)
	    	{
	    	    //	Check if the filesystem is read-only and write access has been requested
	    	    
	    	    if ( storePath.getVersion() != AVMContext.VERSION_HEAD && ( params.isReadWriteAccess() || params.isWriteOnlyAccess()))
	    	      throw new AccessDeniedException("File " + params.getPath() + " is read-only");
	    	    
	    	    //	Create the network file object for the opened file/folder
	    	    
	    	    netFile = new AVMNetworkFile( nodeDesc, storePath.getAVMPath(), storePath.getVersion(), m_avmService);
	    	    
	    	    if ( params.isReadOnlyAccess() || storePath.getVersion() != AVMContext.VERSION_HEAD)
	    	    	netFile.setGrantedAccess(NetworkFile.READONLY);
	    		else
	    	    	netFile.setGrantedAccess(NetworkFile.READWRITE);
	    	    	
	    	    netFile.setFullName(params.getPath());
	    	    netFile.setFileId( storePath.generateFileId());
	    	    
	    	    
	    	    // Set the mime-type for the new file
	    	    
	    	    netFile.setMimeType( m_mimetypeService.guessMimetype( params.getPath()));
	    	}
	    	else
	    		throw new FileNotFoundException( params.getPath());
    	}
    	catch ( AVMNotFoundException ex)
    	{
    		throw new FileNotFoundException( params.getPath());
    	}
    	catch ( AVMWrongTypeException ex)
    	{
    		throw new FileNotFoundException( params.getPath());
    	}
    	
    	// Return the file
    	
    	return netFile;
    }

    /**
     * Read a block of data from the specified file.
     * 
     * @param sess Session details
     * @param tree Tree connection
     * @param file Network file
     * @param buf Buffer to return data to
     * @param bufPos Starting position in the return buffer
     * @param siz Maximum size of data to return
     * @param filePos File offset to read data
     * @return Number of bytes read
     * @exception java.io.IOException The exception description.
     */
    public int readFile(SrvSession sess, TreeConnection tree, NetworkFile file, byte[] buf, int bufPos, int siz,
    					long filePos)
    	throws java.io.IOException
    {
  	  	// Check if the file is a directory
    	
		if ( file.isDirectory())
			throw new AccessDeniedException();
      
    	// If the content channel is not open for the file then start a transaction
    	
    	AVMNetworkFile avmFile = (AVMNetworkFile) file;
    	
    	if ( avmFile.hasContentChannel() == false)
    		sess.beginReadTransaction( m_transactionService);
    	
		// Read the file

		int rdlen = file.readFile(buf, siz, bufPos, filePos);

		// If we have reached end of file return a zero length read

		if (rdlen == -1)
			rdlen = 0;

		//  Return the actual read length

		return rdlen;
    }

    /**
     * Rename the specified file.
     * 
     * @param sess Server session
     * @param tree Tree connection
     * @param oldName java.lang.String
     * @param newName java.lang.String
     * @exception java.io.IOException The exception description.
     */
    public void renameFile(SrvSession sess, TreeConnection tree, String oldName, String newName)
    	throws java.io.IOException
    {
    	// Split the relative paths into parent and file/folder name pairs
    	
    	AVMContext ctx = (AVMContext) tree.getContext();
    	
    	String[] oldPaths = FileName.splitPath( oldName);
    	String[] newPaths = FileName.splitPath( newName);
    	
    	// Convert the parent paths to store paths
    	
    	AVMPath oldAVMPath = buildStorePath( ctx, oldPaths[0]);
    	AVMPath newAVMPath = buildStorePath( ctx, newPaths[0]);
    	
    	// DEBUG
    	
    	if ( logger.isDebugEnabled())
    	{
    		logger.debug("Rename from path=" + oldPaths[0] + ", name=" + oldPaths[1]);
    		logger.debug("        new path=" + newPaths[0] + ", name=" + newPaths[1]);
    	}

    	// Check if the filesystem is the virtualization view
    	
    	if ( ctx.isVirtualizationView() && oldAVMPath.isReadOnlyPseudoPath())
    	{
    		throw new AccessDeniedException( "Cannot rename folder in store/version layer, " + oldName);
    	}
    	
    	// Start a transaction for the rename
    	
    	sess.beginWriteTransaction( m_transactionService);
    	
    	try
    	{
    		// Rename the file/folder
    		
    		m_avmService.rename( oldAVMPath.getAVMPath(), oldPaths[1], newAVMPath.getAVMPath(), newPaths[1]);
    	}
    	catch ( AVMNotFoundException ex)
    	{
    		throw new IOException( "Source not found, " + oldName);
    	}
    	catch ( AVMWrongTypeException ex)
    	{
    		throw new IOException( "Invalid path, " + oldName);
    	}
    	catch ( AVMExistsException ex)
    	{
    		throw new FileExistsException( "Destination exists, " + newName);
    	}
    }

    /**
     * Seek to the specified file position.
     * 
     * @param sess Server session
     * @param tree Tree connection
     * @param file Network file.
     * @param pos Position to seek to.
     * @param typ Seek type.
     * @return New file position, relative to the start of file.
     */
    public long seekFile(SrvSession sess, TreeConnection tree, NetworkFile file, long pos, int typ)
    	throws java.io.IOException
    {
  	  	// Check if the file is a directory
    	
		if ( file.isDirectory())
			throw new AccessDeniedException();
      
    	// If the content channel is not open for the file then start a transaction
    	
    	AVMNetworkFile avmFile = (AVMNetworkFile) file;
    	
    	if ( avmFile.hasContentChannel() == false)
    		sess.beginReadTransaction( m_transactionService);
    	
		// Set the file position

		return file.seekFile(pos, typ);
    }

    /**
     * Set the file information for the specified file.
     * 
     * @param sess Server session
     * @param tree Tree connection
     * @param name java.lang.String
     * @param info FileInfo
     * @exception java.io.IOException The exception description.
     */
    public void setFileInformation(SrvSession sess, TreeConnection tree, String name, FileInfo info)
    	throws java.io.IOException
    {
        // Check if the file is being marked for deletion, check if the file is writable
        
        if ( info.hasSetFlag(FileInfo.SetDeleteOnClose) && info.hasDeleteOnClose())
        {
        	// If this is not the head version then it's not writable
        	
        	AVMContext avmCtx = (AVMContext) tree.getContext();
        	if ( avmCtx.isVersion() != AVMContext.VERSION_HEAD)
        		throw new AccessDeniedException( "Store not writable, cannot set delete on close");
        }
    }

    /**
     * Start a new search on the filesystem using the specified searchPath that may contain
     * wildcards.
     * 
     * @param sess Server session
     * @param tree Tree connection
     * @param searchPath File(s) to search for, may include wildcards.
     * @param attrib Attributes of the file(s) to search for, see class SMBFileAttribute.
     * @return SearchContext
     * @exception java.io.FileNotFoundException If the search could not be started.
     */
    public SearchContext startSearch(SrvSession sess, TreeConnection tree, String searchPath, int attrib)
    	throws java.io.FileNotFoundException
    {
    	// Access the AVM context
    	
    	AVMContext avmCtx = (AVMContext) tree.getContext();
    	
    	// DEBUG
    	
    	if ( logger.isDebugEnabled())
    		logger.debug("Start search path=" + searchPath);

    	// Split the search path into relative path and search name
    	
    	String[] paths = FileName.splitPath( searchPath);
    	
    	// Build the store path to the folder being searched
    	
    	AVMPath storePath = buildStorePath( avmCtx, paths[0]);
    	
    	// Check if the filesystem is the virtualization view
    	
    	if ( avmCtx.isVirtualizationView())
    	{
    		// Check for a search of a pseudo folder

    		if (storePath.isReadOnlyPseudoPath())
	    	{
	    		// Get the file state for the folder being searched
	
	    		FileState fstate = findPseudoState( storePath, avmCtx);
	
	    		if ( fstate != null)
	    		{
	    			// Get the pseudo file list for the parent directory
	    			
	    			PseudoFileList searchList = fstate.getPseudoFileList();
	    			
		   			// Check if this is a single file or wildcard search
		    			
		   			if ( WildCard.containsWildcards( searchPath))
		   			{
		   				// Check if the pseudo file list is valid
		   				
		   				if ( searchList == null)
		   					searchList = new PseudoFileList();
		   				
		   	    		// Create the search context, wildcard filter will take care of secondary filtering of the
		   	    		// folder listing
		    	    		
		   	    		WildCard wildCardFilter = new WildCard( paths[1], false);
		   	    		return new PseudoFileListSearchContext( searchList, attrib, wildCardFilter);
		   			}
		   			else
		   			{
		   				// Search the pseudo file list for the required file
		    				
		   				PseudoFile pseudoFile = searchList.findFile( paths[1], false);
		   				if ( pseudoFile != null)
		   				{
		   					// Create a search context using the single file details
		    					
		   					PseudoFileList singleList = new PseudoFileList();
		   					singleList.addFile( pseudoFile);
		    					
		       	    		return new PseudoFileListSearchContext( singleList, attrib, null);
		   				}
		   			}
	    		}
	    		
	   			// File not found
	    			
	   			throw new FileNotFoundException( searchPath);
	    	}
	    	else if ( storePath.isLevel() == AVMPath.LevelId.HeadMetaData || storePath.isLevel() == AVMPath.LevelId.VersionMetaData)
	    	{
	    		// Return an empty file list for now
	    		
	    		PseudoFileList metaFiles = new PseudoFileList();
	    		
	    		return new PseudoFileListSearchContext( metaFiles, attrib, null);
	    	}
    	}

    	// Check if the path is a wildcard search
    	
		sess.beginReadTransaction( m_transactionService);
    	SearchContext context = null;
    	
    	if ( WildCard.containsWildcards( searchPath))
    	{
	    	// Get the file listing for the folder
	    	
	    	AVMNodeDescriptor[] fileList = m_avmService.getDirectoryListingArray( storePath.getVersion(), storePath.getAVMPath(), false);
	    	
	    	// Create the search context
	    	
	    	if ( fileList != null) {

	    		// DEBUG
	    		
	    		if ( logger.isDebugEnabled())
	    			logger.debug("  Wildcard search returned " + fileList.length + " files");
	    		
	    		// Create the search context, wildcard filter will take care of secondary filtering of the
	    		// folder listing
	    		
	    		WildCard wildCardFilter = new WildCard( paths[1], false);
	    		context = new AVMSearchContext( fileList, attrib, wildCardFilter, storePath.getRelativePath());
	    	}
    	}
    	else
    	{
    		// Single file/folder search, convert the path to a store path
    		
    		storePath = buildStorePath( avmCtx, searchPath);
    		
    		// Get the single file/folder details
    		
    		AVMNodeDescriptor nodeDesc = m_avmService.lookup( storePath.getVersion(), storePath.getAVMPath());
    		
    		if ( nodeDesc != null)
    		{
    			// Create the search context for the single file/folder
    			
    			context = new AVMSingleFileSearchContext( nodeDesc, storePath.getRelativePath());
    		}
    		
    	}
    	
    	// Return the search context
    	
    	return context;
    }

    /**
     * Truncate a file to the specified size
     * 
     * @param sess Server session
     * @param tree Tree connection
     * @param file Network file details
     * @param siz New file length
     * @exception java.io.IOException The exception description.
     */
    public void truncateFile(SrvSession sess, TreeConnection tree, NetworkFile file, long siz)
    	throws java.io.IOException
    {
        // Check if the file is a directory, or only has read access

		if ( file.getGrantedAccess() == NetworkFile.READONLY)
			throw new AccessDeniedException();
		
    	// If the content channel is not open for the file then start a transaction
    	
    	AVMNetworkFile avmFile = (AVMNetworkFile) file;
    	
    	if ( avmFile.hasContentChannel() == false || avmFile.isWritable() == false)
    		sess.beginWriteTransaction( m_transactionService);
    	
  	  	// Truncate or extend the file
  	  
  	  	file.truncateFile(siz);
  	  	file.flushFile();
    }

    /**
     * Write a block of data to the file.
     * 
     * @param sess Server session
     * @param tree Tree connection
     * @param file Network file details
     * @param buf byte[] Data to be written
     * @param bufoff Offset within the buffer that the data starts
     * @param siz int Data length
     * @param fileoff Position within the file that the data is to be written.
     * @return Number of bytes actually written
     * @exception java.io.IOException The exception description.
     */
    public int writeFile(SrvSession sess, TreeConnection tree, NetworkFile file, byte[] buf, int bufoff, int siz,
            				long fileoff)
    	throws java.io.IOException
    {
        // Check if the file is a directory, or only has read access

		if ( file.isDirectory() || file.getGrantedAccess() == NetworkFile.READONLY)
			throw new AccessDeniedException();

    	// If the content channel is not open for the file, or the channel is not writable, then start a transaction
    	
    	AVMNetworkFile avmFile = (AVMNetworkFile) file;
    	
    	if ( avmFile.hasContentChannel() == false || avmFile.isWritable() == false)
    		sess.beginWriteTransaction( m_transactionService);
    	
		// Write the data to the file
		      
		file.writeFile(buf, siz, bufoff, fileoff);

		//  Return the actual write length

		return siz;
    }
    
    /**
     * Connection opened to this disk device
     * 
     * @param sess Server session
     * @param tree Tree connection
     */
    public void treeClosed(SrvSession sess, TreeConnection tree)
    {
        // Nothing to do
    }

    /**
     * Connection closed to this device
     * 
     * @param sess Server session
     * @param tree Tree connection
     */
    public void treeOpened(SrvSession sess, TreeConnection tree)
    {
        // Nothing to do
    }
    
    /**
     * Find the pseudo file for a virtual path
     * 
     * @param avmPath AVMPath
     * @param avmCtx AVMContext
     * @return PseudoFile
     */
    private final PseudoFile findPseudoFolder( AVMPath avmPath, AVMContext avmCtx)
    {
    	return findPseudoFolder( avmPath, avmCtx, true);
    }
    
    /**
     * Find the pseudo file for a virtual path
     * 
     * @param avmPath AVMPath
     * @param avmCtx AVMContext
     * @param generateStates boolean
     * @return PseudoFile
     */
    private final PseudoFile findPseudoFolder( AVMPath avmPath, AVMContext avmCtx, boolean generateStates)
    {
    	// Check if the path is to a store pseudo folder

    	if ( avmPath.isRootPath())
    		return null;
    	
    	// Get the file state for the parent of the required folder
    	
    	FileState fstate = null;
    	StringBuilder str = null;
    	PseudoFile psFile = null;
    	
    	switch ( avmPath.isLevel())
    	{
    		// Store root folder
    	
	    	case StoreRoot:
	    		
	    		// Get the root folder file state
	    		
	    		fstate = avmCtx.getStateTable().findFileState( FileName.DOS_SEPERATOR_STR);
	    		
	    		if ( fstate != null && fstate.hasPseudoFiles())
	    			psFile = fstate.getPseudoFileList().findFile( avmPath.getStoreName(), false);
	    		break;
	    		
	    	// Versions root or Head folder
	    		
	    	case VersionRoot:
	    	case Head:
	    		
	    		// Create a path to the parent store
	    		
	    		str = new StringBuilder();
	    		
	    		str.append( FileName.DOS_SEPERATOR);
	    		str.append( avmPath.getStoreName());
	    		
	    		// Find/create the file state for the store
	    		
	    		AVMPath storePath = new AVMPath( str.toString());
	    		fstate = findPseudoState( storePath, avmCtx);

	    		// Find the version root or head pseudo folder
	    		
	    		if ( fstate != null)
	    		{
	    			if ( avmPath.isLevel() == AVMPath.LevelId.Head)
	    				psFile = fstate.getPseudoFileList().findFile( AVMPath.VersionNameHead, true);
	    			else
	    				psFile = fstate.getPseudoFileList().findFile( AVMPath.VersionsFolder, true);
	    		}
	    		break;
	    		
	    	// Version folder
	    		
	    	case Version:

	    		// Create a path to the versions folder
	    		
	    		str = new StringBuilder();
	    		
	    		str.append( FileName.DOS_SEPERATOR);
	    		str.append( avmPath.getStoreName());
	    		str.append( FileName.DOS_SEPERATOR);
	    		str.append( AVMPath.VersionsFolder);
	    		
	    		// Find/create the file state for the store
	    		
	    		AVMPath verrootPath = new AVMPath( str.toString());
	    		fstate = findPseudoState( verrootPath, avmCtx);

	    		// Find the version pseudo file
	    		
	    		if ( fstate != null)
	    		{
	    			// Build the version folder name string
		    		
		    		str.setLength( 0);
		    		
		    		str.append( AVMPath.VersionFolderPrefix);
		    		str.append( avmPath.getVersion());
		    		
	    			// find the version folder pseduo file
	    			
	    			psFile = fstate.getPseudoFileList().findFile( str.toString(), true);
	    		}
	    		break;
	    		
	    	// Head data or metadata folder
	    		
	    	case HeadData:
	    	case HeadMetaData:

	    		// Create a path to the head folder
	    		
	    		str = new StringBuilder();
	    		
	    		str.append( FileName.DOS_SEPERATOR);
	    		str.append( avmPath.getStoreName());
	    		str.append( FileName.DOS_SEPERATOR);
	    		str.append( AVMPath.VersionNameHead);
	    		
	    		// Find/create the file state for the store
	    		
	    		AVMPath headPath = new AVMPath( str.toString());
	    		fstate = findPseudoState( headPath, avmCtx);

	    		// Find the data or metadata pseudo folder
	    		
	    		if ( fstate != null)
	    		{
	    			// Find the pseudo folder
	    			
	    			if ( avmPath.isLevel() == AVMPath.LevelId.HeadData)
	    			{
	    				psFile = fstate.getPseudoFileList().findFile( AVMPath.DataFolder, true);
	    			}
	    			else
	    			{
	    				psFile = fstate.getPseudoFileList().findFile( AVMPath.MetaDataFolder, true);
	    			}
	    		}
	    		break;

	    	// Version data or metadata folder
	    		
	    	case VersionData:
	    	case VersionMetaData:

	    		// Create a path to the version folder
	    		
	    		str = new StringBuilder();
	    		
	    		str.append( FileName.DOS_SEPERATOR);
	    		str.append( avmPath.getStoreName());
	    		str.append( FileName.DOS_SEPERATOR);
	    		str.append( AVMPath.VersionFolderPrefix);
	    		str.append( avmPath.getVersion());
	    		
	    		// Find/create the file state for the store
	    		
	    		AVMPath verPath = new AVMPath( str.toString());
	    		fstate = findPseudoState( verPath, avmCtx);

	    		// Find the data or metadata pseudo folder
	    		
	    		if ( fstate != null)
	    		{
	    			// Find the pseudo folder
	    			
	    			if ( avmPath.isLevel() == AVMPath.LevelId.VersionData)
	    			{
	    				psFile = fstate.getPseudoFileList().findFile( AVMPath.DataFolder, true);
	    			}
	    			else
	    			{
	    				psFile = fstate.getPseudoFileList().findFile( AVMPath.MetaDataFolder, true);
	    			}
	    		}
	    		break;
    	}

    	// Check if the pseudo file was not found but file states should be generated
    	
    	if ( psFile == null && generateStates == true)
    	{
    		// Generate the file states for the path, this is required if a request is made to a path without
    		// walking the folder tree
    		
    		generatePseudoFolders( avmPath, avmCtx);
    		
    		// Try and find the pseudo file again
    		
    		psFile = findPseudoFolder( avmPath, avmCtx, false);
    	}
    	
		// Return the pseudo file, or null if not found
		
		return psFile;
    }
    
    /**
     * Find the file state for a pseudo folder path
     * 
     * @param avmPath AVMPath
     * @param avmCtx AVMContext
     * @return FileState
     */
    private final FileState findPseudoState( AVMPath avmPath, AVMContext avmCtx)
    {
    	// Make sure the is to a pseudo file/folder
    	
    	if ( avmPath.isPseudoPath() == false)
    		return null;
    	
    	// Check if the path is to a store pseudo folder
    	
    	FileState fstate = null;
    	StringBuilder str = null;

    	switch ( avmPath.isLevel())
    	{
	    	// Root of the hieararchy
	    	
	    	case Root:

	    		// Get the root path file state
				
				fstate = avmCtx.getStateTable().findFileState( FileName.DOS_SEPERATOR_STR);
				
				// Check if the root file state is valid
				
				if ( fstate == null)
				{
					// Create a file state for the root folder
					
	            	fstate = avmCtx.getStateTable().findFileState( FileName.DOS_SEPERATOR_STR, true, true);
	            	fstate.setExpiryTime( FileState.NoTimeout);
	            	
		            // Get a list of the available AVM stores
		            
		            List<AVMStoreDescriptor> storeList = m_avmService.getStores();
		            
		            if ( storeList != null && storeList.size() > 0)
		            {
		            	// Add pseudo files for the stores
		            	
		            	boolean sandbox = false;
		            	
		            	for ( AVMStoreDescriptor storeDesc : storeList)
		            	{
		            		// Get the properties for the current store
		            		
		            		Map<QName, PropertyValue> props = m_avmService.getStoreProperties( storeDesc.getName());
		            		
		            		if ( props.containsKey( AVMContext.PROP_WORKFLOWPREVIEW) || props.containsKey( AVMContext.PROP_AUTHORPREVIEW))
		            			sandbox = true;
		            		else
		            			sandbox = false;

		            		// DEBUG
		            		
		            		if ( logger.isDebugEnabled())
		            			logger.debug( "Store " + storeDesc.getName() + ", sandbox=" + sandbox);
		            		
		            		// Add a pseudo file for the current store

		            		if ( sandbox == false || avmCtx.showSandboxes() == true)
		            			fstate.addPseudoFile( new StorePseudoFile( storeDesc));
		            	}
		            }
				}
	    		break;
	    		
	    	// Store folder
	    		
	    	case StoreRoot:
	    		
				// Build the path to the parent store folder
				
				str = new StringBuilder();
				
				str.append( FileName.DOS_SEPERATOR);
				str.append( avmPath.getStoreName());
				
				// Search for the file state for the store pseudo folder
				
				fstate = avmCtx.getStateTable().findFileState( str.toString());
				
				if ( fstate == null)
				{
					// Create a file state for the store path
					
					fstate = avmCtx.getStateTable().findFileState( str.toString(), true, true);
					
					// Add a pseudo file for the head version
					
					fstate.addPseudoFile( new VersionPseudoFile( AVMPath.VersionNameHead));
					
					// Add a pseudo file for the version root folder
					
					fstate.addPseudoFile( new DummyFolderPseudoFile( AVMPath.VersionsFolder));
				}
				break;

			// Head folder
				
	    	case Head:

	    		// Build the path to the store head version folder
				
				str = new StringBuilder();
				
				str.append( FileName.DOS_SEPERATOR);
				str.append( avmPath.getStoreName());
				str.append( FileName.DOS_SEPERATOR);
				str.append( AVMPath.VersionNameHead);
				
				// Search for the file state for the store head version pseudo folder
				
				fstate = avmCtx.getStateTable().findFileState( str.toString());
				
				if ( fstate == null)
				{
					// Create a file state for the store head folder path
					
					fstate = avmCtx.getStateTable().findFileState( str.toString(), true, true);
					
					// Add a pseudo file for the data pseudo folder
					
					fstate.addPseudoFile( new DummyFolderPseudoFile( AVMPath.DataFolder));
					
					// Add a pseudo file for the metadata pseudo folder
					
					fstate.addPseudoFile( new DummyFolderPseudoFile( AVMPath.MetaDataFolder));
				}
	    		break;
	    		
			// Version root folder
				
	    	case VersionRoot:

	    		// Get the list of AVM store versions

				try
				{
					// Build the path to the parent store folder
					
					str = new StringBuilder();
					
					str.append( FileName.DOS_SEPERATOR);
					str.append( avmPath.getStoreName());
					str.append( FileName.DOS_SEPERATOR);
					str.append( AVMPath.VersionsFolder);
					
					// Create a file state for the store path
					
					fstate = avmCtx.getStateTable().findFileState( str.toString(), true, true);
					
					// Add pseudo folders if the list is empty
					
					if ( fstate.hasPseudoFiles() == false)
					{
						// Build the version folder name for the head version
						
						StringBuilder verStr = new StringBuilder( AVMPath.VersionFolderPrefix);
						verStr.append( "-1");
						
						// Add a pseudo file for the head version
						
						fstate.addPseudoFile( new VersionPseudoFile( verStr.toString()));
						
						// Get the list of versions for the store
						
						List<VersionDescriptor> verList = m_avmService.getStoreVersions( avmPath.getStoreName());
						
						// Add pseudo files for the versions to the store state
	
						if ( verList.size() > 0)
						{
							for ( VersionDescriptor verDesc : verList)
							{
								// Generate the version string
								
								String verName = null;
								
								verStr.setLength( AVMPath.VersionFolderPrefix.length());
								verStr.append( verDesc.getVersionID());
								
								verName = verStr.toString();
								
								// Add the version pseudo folder
								
								fstate.addPseudoFile( new VersionPseudoFile ( verName, verDesc));
							}
						}
					}
				}
				catch ( AVMNotFoundException ex)
				{
					// Invalid store name
				}
				break;

			// Version folder
				
	    	case Version:

	    		// Build the path to the store version folder
				
				str = new StringBuilder();
				
				str.append( FileName.DOS_SEPERATOR);
				str.append( avmPath.getStoreName());
				str.append( FileName.DOS_SEPERATOR);
				str.append( AVMPath.VersionFolderPrefix);
				str.append( avmPath.getVersion());
				
				// Search for the file state for the version pseudo folder
				
				fstate = avmCtx.getStateTable().findFileState( str.toString());
				
				if ( fstate == null)
				{
					// Create a file state for the version folder path
					
					fstate = avmCtx.getStateTable().findFileState( str.toString(), true, true);
					
					// Add a pseudo file for the data pseudo folder
					
					fstate.addPseudoFile( new DummyFolderPseudoFile( AVMPath.DataFolder));
					
					// Add a pseudo file for the metadata pseudo folder
					
					fstate.addPseudoFile( new DummyFolderPseudoFile( AVMPath.MetaDataFolder));
				}
	    		break;
		}

    	// Return the file state
	    
	    return fstate;
    }
    
    /**
     * Generate the pseudo folders for the specified path
     * 
     * @param avmPath AVMPath
     * @param avmCtx AVMContext
     */
   private final void generatePseudoFolders( AVMPath avmPath, AVMContext avmCtx)
   {
	   // Create the root file state
	   
	   AVMPath createPath = new AVMPath();
	   StringBuilder pathStr = new StringBuilder();
	   
	   pathStr.append( FileName.DOS_SEPERATOR);
	   createPath.parsePath( pathStr.toString());
	   
	   FileState rootState = findPseudoState( createPath, avmCtx);
	   
	   // Check if the path has a store name
	   
	   if ( avmPath.getStoreName() != null)
	   {
		   // Check if the store name is valid
		   
		   if ( rootState.hasPseudoFiles() && rootState.getPseudoFileList().findFile( avmPath.getStoreName(), false) != null)
		   {
			   // Create the store file state
			   
			   pathStr.append( avmPath.getStoreName());
			   pathStr.append( FileName.DOS_SEPERATOR);
			   
			   createPath.parsePath( pathStr.toString());
			   
			   findPseudoState( createPath, avmCtx);
			   
			   // Add the head and version root pseudo folders
	
			   createPath.parsePath( pathStr.toString() + AVMPath.VersionNameHead);
			   findPseudoState( createPath, avmCtx);
			   
			   createPath.parsePath( pathStr.toString() + AVMPath.VersionsFolder);
			   findPseudoState( createPath, avmCtx);
			   
			   // Check if the path is to a version folder
			   
			   if ( avmPath.isLevel().ordinal() >= AVMPath.LevelId.Version.ordinal())
			   {
				   // Build the path
				   
				   pathStr.append( AVMPath.VersionsFolder);
				   pathStr.append( FileName.DOS_SEPERATOR);
				   pathStr.append( AVMPath.VersionFolderPrefix);
				   pathStr.append( avmPath.getVersion());
				   
				   createPath.parsePath( pathStr.toString());
				   
				   // Generate the version folders
				   
				   findPseudoState( createPath, avmCtx);
			   }
		   }
	   }
   }
}
