diff --git a/config/alfresco/network-protocol-context.xml b/config/alfresco/network-protocol-context.xml
index f5d0408e00..12a227730f 100644
--- a/config/alfresco/network-protocol-context.xml
+++ b/config/alfresco/network-protocol-context.xml
@@ -43,6 +43,9 @@
Contains per filesystem context. + * + * @author GKSpencer + */ +public class AVMContext extends DiskDeviceContext { + + // Constants + // + // Version id that indicates the head version + + public static final int VERSION_HEAD = -1; + + // Store, root path and version + + private String m_storePath; + private int m_version = VERSION_HEAD; + + /** + * Class constructor + * + * @param storePath String + * @param version int + */ + public AVMContext( String storePath, int version) + { + super( storePath + "(" + version + ")"); + + // Set the store root path, remove any trailing slash as relative paths will be appended to this value + + m_storePath = storePath; + if ( m_storePath.endsWith( "/")) + m_storePath = m_storePath.substring(0, m_storePath.length() - 1); + + // Set the store version to use + + m_version = version; + } + + /** + * Return the filesystem type, either FileSystem.TypeFAT or FileSystem.TypeNTFS. + * + * @return String + */ + public String getFilesystemType() + { + return FileSystem.TypeNTFS; + } + + /** + * Return the store path + * + * @return String + */ + public final String getStorePath() + { + return m_storePath; + } + + /** + * Return the version + * + * @return int + */ + public final int isVersion() + { + return m_version; + } + + /** + * Close the filesystem context + */ + public void CloseContext() { + + // Call the base class + + super.CloseContext(); + } +} diff --git a/source/java/org/alfresco/filesys/avm/AVMDiskDriver.java b/source/java/org/alfresco/filesys/avm/AVMDiskDriver.java new file mode 100644 index 0000000000..87fcaf5d2c --- /dev/null +++ b/source/java/org/alfresco/filesys/avm/AVMDiskDriver.java @@ -0,0 +1,1212 @@ +/* + * Copyright (C) 2006 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ + +package org.alfresco.filesys.avm; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.SortedMap; + +import javax.transaction.UserTransaction; + +import org.alfresco.config.ConfigElement; +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.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.FileSystem; +import org.alfresco.filesys.server.filesys.NetworkFile; +import org.alfresco.filesys.server.filesys.SearchContext; +import org.alfresco.filesys.server.filesys.SrvDiskInfo; +import org.alfresco.filesys.server.filesys.TreeConnection; +import org.alfresco.filesys.util.WildCard; +import org.alfresco.repo.security.authentication.AuthenticationComponent; +import org.alfresco.service.ServiceRegistry; +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.AVMWrongTypeException; +import org.alfresco.service.cmr.repository.MimetypeService; +import org.alfresco.service.cmr.security.AuthenticationService; +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 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;
+
+ // Service registry for desktop actions
+
+ private ServiceRegistry m_serviceRegistry;
+
+ /**
+ * 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;
+ }
+
+ /**
+ * Return the service registry
+ *
+ * @return ServiceRegistry
+ */
+ public final ServiceRegistry getServiceRegistry()
+ {
+ return m_serviceRegistry;
+ }
+
+ /**
+ * 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 service registry
+ *
+ * @param serviceRegistry
+ */
+ public void setServiceRegistry(ServiceRegistry serviceRegistry)
+ {
+ m_serviceRegistry = serviceRegistry;
+ }
+
+ /**
+ * 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;
+ }
+
+ /**
+ * Parse and validate the parameter string and create a device context object for this instance
+ * of the shared device.
+ *
+ * @param cfg ConfigElement
+ * @return DeviceContext
+ * @exception DeviceContextException
+ */
+ public DeviceContext createContext(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();
+
+ // 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;
+
+ // Create a new store, and the path if specified
+
+ m_avmService.createAVMStore( storeName);
+ if ( path != null)
+ {
+ // TODO:
+ }
+
+ // Validate the store path again
+
+ rootNode = m_avmService.lookup( version, storePath);
+ if ( rootNode == null)
+ throw new DeviceContextException("Failed to create new store " + storePath);
+ }
+
+ // Commit the transaction
+
+ tx.commit();
+ tx = null;
+
+ // Create the context
+
+ context = new AVMContext(storePath, version);
+
+ // Default the filesystem to look like an 80Gb sized disk with 90% free space
+
+ context.setDiskInformation(new SrvDiskInfo(2560000, 64, 512, 2304000));
+
+ // Set parameters
+
+ context.setFilesystemAttributes(FileSystem.CasePreservedNames + FileSystem.UnicodeOnDisk +
+ FileSystem.CaseSensitiveSearch);
+ }
+ 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;
+ }
+
+ /**
+ * Build the full store path for a file/folder using the share relative path
+ *
+ * @param ctx AVMContext
+ * @param path String
+ * @return String
+ */
+ protected final String buildStorePath( AVMContext ctx, String path)
+ {
+ // Build the store path
+
+ StringBuilder storePath = new StringBuilder();
+
+ storePath.append( ctx.getStorePath());
+ if ( path == null || path.length() == 0)
+ {
+ storePath.append( AVM_SEPERATOR);
+ }
+ else
+ {
+ if ( path.startsWith( FileName.DOS_SEPERATOR_STR) == false)
+ storePath.append( AVM_SEPERATOR);
+
+ storePath.append( path.replace( FileName.DOS_SEPERATOR, AVM_SEPERATOR));
+ }
+
+ return storePath.toString();
+ }
+
+ /**
+ * 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
+
+ String storePath = buildStorePath( ctx, paths[0]);
+
+ // DEBUG
+
+ if ( logger.isDebugEnabled())
+ logger.debug("Create directory params=" + params + ", storePath=" + storePath + ", name=" + paths[1]);
+
+ // Create a new file
+
+ sess.beginTransaction( m_transactionService, false);
+
+ try
+ {
+ // Create the new file entry
+
+ m_avmService.createDirectory( storePath, 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());
+ }
+ }
+
+ /**
+ * 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();
+ if ( ctx.isVersion() != AVMContext.VERSION_HEAD)
+ throw new AccessDeniedException("Cannot create " + params.getPath() + ", filesys not writable");
+
+ // 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
+
+ String storePath = buildStorePath( ctx, paths[0]);
+
+ // DEBUG
+
+ if ( logger.isDebugEnabled())
+ logger.debug("Create file params=" + params + ", storePath=" + storePath + ", name=" + paths[1]);
+
+ // Create a new file
+
+ sess.beginTransaction( m_transactionService, false);
+
+ AVMNetworkFile netFile = null;
+
+ try
+ {
+ // Create the new file entry
+
+ m_avmService.createFile( storePath, paths[1]).close();
+
+ // Get the new file details
+
+ String fileStorePath = buildStorePath( ctx, params.getPath());
+ AVMNodeDescriptor nodeDesc = m_avmService.lookup( ctx.isVersion(), fileStorePath);
+
+ if ( nodeDesc != null)
+ {
+ // Create the network file object for the new file
+
+ netFile = new AVMNetworkFile( nodeDesc, fileStorePath, ctx.isVersion(), m_avmService);
+ netFile.setGrantedAccess(NetworkFile.READWRITE);
+ netFile.setFullName(params.getPath());
+
+ // 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());
+ }
+
+ // 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();
+ String storePath = buildStorePath( ctx, dir);
+
+ // DEBUG
+
+ if ( logger.isDebugEnabled())
+ logger.debug("Delete directory, path=" + dir + ", storePath=" + storePath);
+
+ // Make sure the path is to a folder before deleting it
+
+ sess.beginTransaction( m_transactionService, false);
+
+ try
+ {
+ AVMNodeDescriptor nodeDesc = m_avmService.lookup( ctx.isVersion(), storePath);
+ if ( nodeDesc != null)
+ {
+ // Check that we are deleting a folder
+
+ if ( nodeDesc.isDirectory())
+ {
+ // Make sure the directory is empty
+
+ SortedMap Holds the details of an open file, and provides access to the file data.
+ *
+ * @author GKSpencer
+ */
+public class AVMNetworkFile extends NetworkFile {
+
+ // Logging
+
+ private static final Log logger = LogFactory.getLog(AVMNetworkFile.class);
+
+ // AVM service
+
+ private AVMService m_avmService;
+
+ // AVM path to the file/folder and store version
+
+ private String m_avmPath;
+ private int m_avmVersion;
+
+ // Flag to indicate if the file has been modified
+
+ private boolean m_modified;
+
+ // Access to the file data, flag to indicate if the file channel is writable
+
+ private FileChannel m_channel;
+ private boolean m_writable;
+
+ // Mime type, if a writer is opened
+
+ private String m_mimeType;
+
+ /**
+ * Class constructor
+ *
+ * @param details AVMNodeDescriptor
+ * @param avmPath String
+ * @param avmVersion int
+ * @param avmService AVMService
+ */
+ public AVMNetworkFile( AVMNodeDescriptor details, String avmPath, int avmVersion, AVMService avmService)
+ {
+ super( details.getName());
+
+ // Save the service, apth and version
+
+ m_avmService = avmService;
+ m_avmPath = avmPath;
+ m_avmVersion = avmVersion;
+
+ // Copy the file details
+
+ setAccessDate( details.getAccessDate());
+ setCreationDate( details.getCreateDate());
+ setModifyDate( details.getModDate());
+
+ if ( details.isFile())
+ setFileSize( details.getLength());
+ else
+ setFileSize( 0L);
+
+ int attr = 0;
+
+ if ( details.isDirectory())
+ attr += FileAttribute.Directory;
+
+ if ( avmVersion != AVMContext.VERSION_HEAD)
+ attr += FileAttribute.ReadOnly;
+
+ setAttributes( attr);
+ }
+
+ /**
+ * Check if there is an open file channel to the content
+ *
+ * @return boolean
+ */
+ public final boolean hasContentChannel()
+ {
+ return m_channel != null ? true : false;
+ }
+
+ /**
+ * Return the mime type
+ *
+ * @return String
+ */
+ public final String getMimeType()
+ {
+ return m_mimeType;
+ }
+
+ /**
+ * Set the mime type
+ *
+ * @param mimeType String
+ */
+ public final void setMimeType(String mimeType)
+ {
+ m_mimeType = mimeType;
+ }
+
+ /**
+ * Open the file
+ *
+ * @param createFlag boolean
+ * @exception IOException
+ */
+ public void openFile(boolean createFlag)
+ throws IOException
+ {
+ // Nothing to do, content is opened on first read/write
+ }
+
+ /**
+ * Read from the file.
+ *
+ * @param buf byte[]
+ * @param len int
+ * @param pos int
+ * @param fileOff long
+ * @return Length of data read.
+ * @exception IOException
+ */
+ public int readFile(byte[] buf, int len, int pos, long fileOff)
+ throws java.io.IOException
+ {
+ // DEBUG
+
+ if ( logger.isDebugEnabled())
+ logger.debug("Read file " + getName() + ", len=" + len + ", offset=" + fileOff);
+
+ // Open the channel for reading
+
+ openContent(false, false);
+
+ // Read from the channel
+
+ ByteBuffer byteBuffer = ByteBuffer.wrap(buf, pos, len);
+ int count = m_channel.read(byteBuffer, fileOff);
+ if (count < 0)
+ {
+ // Return a zero count at end of file
+
+ count = 0;
+ }
+
+ // Return the length of data read
+
+ return count;
+ }
+
+ /**
+ * Write a block of data to the file.
+ *
+ * @param buf byte[]
+ * @param len int
+ * @param pos int
+ * @param fileOff long
+ * @exception IOException
+ */
+ public void writeFile(byte[] buf, int len, int pos, long fileOff)
+ throws java.io.IOException
+ {
+ // DEBUG
+
+ if ( logger.isDebugEnabled())
+ logger.debug("Write file " + getName() + ", len=" + len + ", offset=" + fileOff);
+
+ // Open the channel for writing
+
+ openContent(true, false);
+
+ // Write to the channel
+
+ ByteBuffer byteBuffer = ByteBuffer.wrap(buf, pos, len);
+ int count = m_channel.write(byteBuffer, fileOff);
+
+ // Set modification flag
+
+ m_modified = true;
+
+ // Update the current file size
+
+ setFileSize( m_channel.size());
+ }
+
+ /**
+ * Seek to the specified file position.
+ *
+ * @param pos long
+ * @param typ int
+ * @return int
+ * @exception IOException
+ */
+ public long seekFile(long pos, int typ)
+ throws IOException
+ {
+ // Open the file, if not already open
+
+ openContent( false, false);
+
+ // Check if the current file position is the required file position
+
+ long curPos = m_channel.position();
+
+ switch (typ) {
+
+ // From start of file
+
+ case SeekType.StartOfFile :
+ if (curPos != pos)
+ m_channel.position( pos);
+ break;
+
+ // From current position
+
+ case SeekType.CurrentPos :
+ m_channel.position( curPos + pos);
+ break;
+
+ // From end of file
+
+ case SeekType.EndOfFile :
+ {
+ long newPos = m_channel.size() + pos;
+ m_channel.position(newPos);
+ }
+ break;
+ }
+
+ // Return the new file position
+
+ return m_channel.position();
+ }
+
+ /**
+ * Flush any buffered output to the file
+ *
+ * @throws IOException
+ */
+ public void flushFile()
+ throws IOException
+ {
+ // If the file channel is open for write then flush the channel
+
+ if ( m_channel != null && m_writable)
+ m_channel.force( false);
+ }
+
+ /**
+ * Truncate the file to the specified file size
+ *
+ * @param siz long
+ * @exception IOException
+ */
+ public void truncateFile(long siz)
+ throws IOException
+ {
+ // DEBUG
+
+ if ( logger.isDebugEnabled())
+ logger.debug("Truncate file " + getName() + ", size=" + siz);
+
+ // If the content data channel has not been opened yet and the requested size is zero
+ // then this is an open for overwrite so the existing content data is not copied
+
+ if ( m_channel == null && siz == 0L)
+ {
+ // Open content for overwrite, no need to copy existing content data
+
+ openContent(true, true);
+ }
+ else
+ {
+ // Normal open for write
+
+ openContent(true, false);
+
+ // Truncate or extend the channel
+
+ m_channel.truncate(siz);
+ }
+
+ // Set modification flag
+
+ m_modified = true;
+ }
+
+ /**
+ * Close the database file
+ */
+ public void closeFile()
+ throws IOException
+ {
+ // If the file is a directory or the file channel has not been opened then there is nothing to do
+
+ if ( isDirectory() || m_channel == null)
+ return;
+
+ // Close the file channel
+
+ try
+ {
+ m_channel.close();
+ m_channel = null;
+ }
+ catch ( IOException ex)
+ {
+ logger.error("Failed to close file channel for " + getName(), ex);
+ }
+ }
+
+ /**
+ * Open a file channel to the file content, switching to a writable file channel if required.
+ *
+ * @param write boolean
+ * @param trunc boolean
+ * @throws AccessDeniedException If this network file is read only
+ * @throws AlfrescoRuntimeException If this network file represents a directory
+ */
+ private void openContent(boolean write, boolean trunc)
+ throws AccessDeniedException, AlfrescoRuntimeException
+ {
+ // Check if this network file is a directory, no content to open
+
+ if ( isDirectory())
+ throw new AlfrescoRuntimeException("Unable to open channel for a directory network file: " + this);
+
+ // Check if write access is required and the current channel is read-only
+
+ long curPos = 0L;
+
+ if ( write && m_writable == false && m_channel != null)
+ {
+ // Close the existing read-only channel
+
+ try
+ {
+ // Save the current file position
+
+ curPos = m_channel.position();
+
+ // Close the read-only file channel
+
+ m_channel.close();
+ m_channel = null;
+ }
+ catch (IOException ex)
+ {
+ logger.error("Error closing read-only channel", ex);
+ }
+
+ // Debug
+
+ if ( logger.isDebugEnabled())
+ logger.debug("Switching to writable channel for " + getName());
+ }
+ else if ( m_channel != null)
+ {
+ // File channel already open
+
+ return;
+ }
+
+ // We need to create the channel
+
+ if (write && getGrantedAccess() == NetworkFile.READONLY)
+ throw new AccessDeniedException("The network file was created for read-only: " + this);
+
+ // Access the content data and get a file channel to the data
+
+ if ( write)
+ {
+ // Access the content data for write
+
+ ContentWriter cWriter = null;
+
+ try {
+
+ // Create a writer to access the file data
+
+ cWriter = m_avmService.createContentWriter( m_avmPath);
+
+ // Set the mime-type
+
+ cWriter.setMimetype( getMimeType());
+ }
+ catch (Exception ex) {
+ logger.debug( ex);
+ ex.printStackTrace();
+ }
+
+ // Indicate that we have a writable channel to the file
+
+ m_writable = true;
+
+ // Get the writable channel, do not copy existing content data if the file is to be truncated
+
+ m_channel = cWriter.getFileChannel( trunc);
+
+ // Reset the file position to match the read-only file channel position, unless we truncated the file
+
+ if ( curPos != 0L && trunc == false)
+ {
+ try
+ {
+ m_channel.position( curPos);
+ }
+ catch (IOException ex)
+ {
+ logger.error("Failed to set file position for " + getName(), ex);
+ }
+ }
+ }
+ else
+ {
+ // Access the content data for read
+
+ ContentReader cReader = m_avmService.getContentReader( m_avmVersion, m_avmPath);
+
+ // Indicate that we only have a read-only channel to the data
+
+ m_writable = false;
+
+ // Get the read-only channel
+
+ m_channel = cReader.getFileChannel();
+ }
+ }
+
+ /**
+ * Return the network file details as a string
+ *
+ * @return String
+ */
+ public String toString()
+ {
+ StringBuilder str = new StringBuilder();
+
+ str.append( "[");
+ str.append( getName());
+ str.append( ":");
+ str.append( isDirectory() ? "Dir," : "File,");
+ str.append( getFileSize());
+ str.append( "-Channel=");
+ str.append( m_channel);
+ str.append( m_writable ? ",Write" : ",Read");
+ str.append( m_modified ? ",Modified" : "");
+ str.append( "]");
+
+ return str.toString();
+ }
+}
diff --git a/source/java/org/alfresco/filesys/avm/AVMSearchContext.java b/source/java/org/alfresco/filesys/avm/AVMSearchContext.java
new file mode 100644
index 0000000000..d9ca841ab8
--- /dev/null
+++ b/source/java/org/alfresco/filesys/avm/AVMSearchContext.java
@@ -0,0 +1,269 @@
+/*
+ * Copyright (C) 2006 Alfresco, Inc.
+ *
+ * Licensed under the Mozilla Public License version 1.1
+ * with a permitted attribution clause. You may obtain a
+ * copy of the License at
+ *
+ * http://www.alfresco.org/legal/license.txt
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied. See the License for the specific
+ * language governing permissions and limitations under the
+ * License.
+ */
+
+package org.alfresco.filesys.avm;
+
+import java.util.Collection;
+import java.util.SortedMap;
+
+import org.alfresco.filesys.server.filesys.FileAttribute;
+import org.alfresco.filesys.server.filesys.FileInfo;
+import org.alfresco.filesys.server.filesys.SearchContext;
+import org.alfresco.filesys.util.WildCard;
+import org.alfresco.service.cmr.avm.AVMNodeDescriptor;
+
+/**
+ * AVM Filesystem Search Context Class
+ *
+ * Contains the details of a wildcard folder search.
+ *
+ * @author GKSpencer
+ */
+public class AVMSearchContext extends SearchContext {
+
+ // File list and current index
+
+ private AVMNodeDescriptor[] m_fileList;
+ private int m_fileIdx;
+
+ // File attributes
+
+ private int m_attrib;
+
+ // Optional wildcard filter
+
+ private WildCard m_filter;
+
+ /**
+ * Class constructor
+ *
+ * @param fileList SortedMap Contains the details of a non-wildcard file/folder search, where there is only one result to return.
+ *
+ * @author GKSpencer
+ */
+public class AVMSingleFileSearchContext extends SearchContext {
+
+ // Details of the single file/folder
+
+ private AVMNodeDescriptor m_fileDetails;
+
+ // Flag to indicate file details have been returned
+
+ private boolean m_endOfSearch;
+
+ /**
+ * Class constructor
+ *
+ * @param fileDetails AVMNodeDescriptor
+ */
+ public AVMSingleFileSearchContext( AVMNodeDescriptor fileDetails)
+ {
+ m_fileDetails = fileDetails;
+ }
+
+ /**
+ * Determine if there are more files for the active search.
+ *
+ * @return boolean
+ */
+ public boolean hasMoreFiles()
+ {
+ return m_endOfSearch == false ? true : false;
+ }
+
+ /**
+ * Return file information for the next file in the active search. Returns false if the search
+ * is complete.
+ *
+ * @param info FileInfo to return the file information.
+ * @return true if the file information is valid, else false
+ */
+ public boolean nextFileInfo(FileInfo info)
+ {
+ // Check if the file details have been returned
+
+ if ( m_endOfSearch == true)
+ return false;
+
+ // Fill in the file information details
+
+ info.setFileName( m_fileDetails.getName());
+
+ if ( m_fileDetails.isFile())
+ {
+ info.setFileSize( m_fileDetails.getLength());
+ info.setAllocationSize((m_fileDetails.getLength() + 512L) & 0xFFFFFFFFFFFFFE00L);
+ }
+ else
+ info.setFileSize( 0L);
+
+ info.setAccessDateTime( m_fileDetails.getAccessDate());
+ info.setCreationDateTime( m_fileDetails.getCreateDate());
+ info.setModifyDateTime( m_fileDetails.getModDate());
+
+ // Build the file attributes
+
+ int attr = 0;
+
+ if ( m_fileDetails.isDirectory())
+ attr += FileAttribute.Directory;
+
+ if ( m_fileDetails.getName().startsWith( ".") ||
+ m_fileDetails.getName().equalsIgnoreCase( "Desktop.ini") ||
+ m_fileDetails.getName().equalsIgnoreCase( "Thumbs.db"))
+ attr += FileAttribute.Hidden;
+
+ info.setFileAttributes( attr);
+
+ // Set the end of search flag, indicate that the file informatin is valid
+
+ m_endOfSearch = true;
+ return true;
+ }
+
+ /**
+ * Return the file name of the next file in the active search. Returns null is the search is
+ * complete.
+ *
+ * @return String
+ */
+ public String nextFileName()
+ {
+ // Check if the file details have been returned
+
+ if ( m_endOfSearch == true)
+ return null;
+
+ // Return the file/folder name, set the end of search flag
+
+ m_endOfSearch = true;
+ return m_fileDetails.getName();
+ }
+
+ /**
+ * Return the total number of file entries for this search if known, else return -1
+ *
+ * @return int
+ */
+ public int numberOfEntries()
+ {
+ return 1;
+ }
+
+ /**
+ * Return the resume id for the current file/directory in the search.
+ *
+ * @return int
+ */
+ public int getResumeId()
+ {
+ return 1;
+ }
+
+ /**
+ * Restart a search at the specified resume point.
+ *
+ * @param resumeId Resume point id.
+ * @return true if the search can be restarted, else false.
+ */
+ public boolean restartAt(int resumeId)
+ {
+ // Validate the resume id and clear the end of search flag
+
+ if ( resumeId == 1)
+ m_endOfSearch = false;
+ else
+ return false;
+ return true;
+ }
+
+ /**
+ * Restart the current search at the specified file.
+ *
+ * @param info File to restart the search at.
+ * @return true if the search can be restarted, else false.
+ */
+ public boolean restartAt(FileInfo info)
+ {
+ return true;
+ }
+}