/* * 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 *

* 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 String currentUser = m_authComponent.getCurrentUserName(); try { 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; } finally { m_authComponent.setCurrentUser(currentUser); } } /** * 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 String currentUser = m_authComponent.getCurrentUserName(); try { 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 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; } finally { m_authComponent.setCurrentUser(currentUser); } } /** * Get the properties for a store * * @param storeName * String * @return Map */ protected final Map getAVMStoreProperties(String storeName) { // Use the system user as the authenticated context to get the AVM store properties String currentUser = m_authComponent.getCurrentUserName(); try { m_authComponent.setCurrentUser(m_authComponent.getSystemUserName()); // Wrap the service request in a transaction UserTransaction tx = m_transactionService.getUserTransaction(false); Map 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; } finally { m_authComponent.setCurrentUser(currentUser); } } /** * 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()); // Start a transaction if the file has been updated if ( file.getWriteCount() > 0) sess.beginWriteTransaction( m_transactionService); // 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 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()); info.setChangeDateTime( 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; String relPath = 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 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 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, FileName.DOS_SEPERATOR_STR + storeDesc.getName())); } } } 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 relPath = str.toString(); fstate = avmCtx.getStateTable().findFileState( relPath); 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 str.append( FileName.DOS_SEPERATOR); str.append( AVMPath.VersionNameHead); fstate.addPseudoFile( new VersionPseudoFile( AVMPath.VersionNameHead, str.toString())); // Add a pseudo file for the version root folder str.setLength( relPath.length() + 1); str.append( AVMPath.VersionsFolder); fstate.addPseudoFile( new DummyFolderPseudoFile( AVMPath.VersionsFolder, str.toString())); } 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 relPath = str.toString(); fstate = avmCtx.getStateTable().findFileState( relPath); 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 str.append( FileName.DOS_SEPERATOR); str.append( AVMPath.DataFolder); fstate.addPseudoFile( new DummyFolderPseudoFile( AVMPath.DataFolder, str.toString())); // Add a pseudo file for the metadata pseudo folder str.setLength( relPath.length() + 1); str.append( AVMPath.MetaDataFolder); fstate.addPseudoFile( new DummyFolderPseudoFile( AVMPath.MetaDataFolder, str.toString())); } 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 relPath = str.toString(); fstate = avmCtx.getStateTable().findFileState( relPath, 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 str.append( FileName.DOS_SEPERATOR); str.append( verStr.toString()); fstate.addPseudoFile( new VersionPseudoFile( verStr.toString(), str.toString())); // Get the list of versions for the store List 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(); str.setLength( relPath.length() + 1); str.append( verName); // Add the version pseudo folder fstate.addPseudoFile( new VersionPseudoFile ( verName, verDesc, str.toString())); } } } } 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 relPath = str.toString(); fstate = avmCtx.getStateTable().findFileState( relPath); 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 str.append( FileName.DOS_SEPERATOR); str.append( AVMPath.DataFolder); fstate.addPseudoFile( new DummyFolderPseudoFile( AVMPath.DataFolder, str.toString())); // Add a pseudo file for the metadata pseudo folder str.setLength( relPath.length() + 1); str.append( AVMPath.MetaDataFolder); fstate.addPseudoFile( new DummyFolderPseudoFile( AVMPath.MetaDataFolder, str.toString())); } 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); } } } } }