diff --git a/source/java/org/alfresco/filesys/smb/server/repo/CifsHelper.java b/source/java/org/alfresco/filesys/smb/server/repo/CifsHelper.java index 9d6647ded6..b910f0939d 100644 --- a/source/java/org/alfresco/filesys/smb/server/repo/CifsHelper.java +++ b/source/java/org/alfresco/filesys/smb/server/repo/CifsHelper.java @@ -63,6 +63,10 @@ public class CifsHelper private MimetypeService mimetypeService; private PermissionService permissionService; + // Mark locked files as offline + + private boolean lockedFilesAsOffline; + /** * Class constructor */ @@ -95,6 +99,26 @@ public class CifsHelper this.permissionService = permissionService; } + /** + * Enable marking of locked files as offline + * + * @param ena boolean + */ + public final void setMarkLockedFilesAsOffline(boolean ena) + { + lockedFilesAsOffline = ena; + } + + /** + * Check if locked files should be marked as offline + * + * @return boolean + */ + public final boolean hasLockedFilesAsOffline() + { + return lockedFilesAsOffline; + } + /** * @param serviceRegistry for repo connection * @param nodeRef @@ -170,7 +194,9 @@ public class CifsHelper else { Map nodeProperties = fileFolderInfo.getProperties(); - // get the file size + + // Get the file size from the content + ContentData contentData = (ContentData) nodeProperties.get(ContentModel.PROP_CONTENT); long size = 0L; if (contentData != null) @@ -180,8 +206,23 @@ public class CifsHelper fileInfo.setSize(size); // Set the allocation size by rounding up the size to a 512 byte block boundary + if ( size > 0) fileInfo.setAllocationSize((size + 512L) & 0xFFFFFFFFFFFFFE00L); + + // Check the lock status of the file + + if ( hasLockedFilesAsOffline()) + { + String lockTypeStr = (String) nodeProperties.get(ContentModel.PROP_LOCK_TYPE); + + if ( lockTypeStr != null) + { + // File is locked so mark it as offline + + fileInfo.setFileAttributes(fileInfo.getFileAttributes() + FileAttribute.NTOffline); + } + } } // created @@ -205,16 +246,21 @@ public class CifsHelper fileInfo.setFileName(name); } - // read/write access + // Read/write access + if ( permissionService.hasPermission(nodeRef, PermissionService.WRITE) == AccessStatus.DENIED) fileInfo.setFileAttributes(fileInfo.getFileAttributes() + FileAttribute.ReadOnly); + + // Debug - // done if (logger.isDebugEnabled()) { logger.debug("Fetched file info: \n" + " info: " + fileInfo); } + + // Return the file information + return fileInfo; } diff --git a/source/java/org/alfresco/filesys/smb/server/repo/ContentContext.java b/source/java/org/alfresco/filesys/smb/server/repo/ContentContext.java index e8e91f614b..4658f4f29a 100644 --- a/source/java/org/alfresco/filesys/smb/server/repo/ContentContext.java +++ b/source/java/org/alfresco/filesys/smb/server/repo/ContentContext.java @@ -17,6 +17,7 @@ package org.alfresco.filesys.smb.server.repo; import org.alfresco.filesys.server.filesys.*; +import org.alfresco.filesys.smb.server.repo.pseudo.PseudoFile; import org.alfresco.service.cmr.repository.*; /** @@ -41,6 +42,10 @@ public class ContentContext extends DiskDeviceContext private FileStateTable m_stateTable; + // Drag and drop pseudo file + + private PseudoFile m_dragAndDropApp; + /** * Class constructor * @@ -134,4 +139,34 @@ public class ContentContext extends DiskDeviceContext else if ( m_stateTable == null) m_stateTable = new FileStateTable(); } + + /** + * Determine if the drag and drop pseudo file has been configured + * + * @return boolean + */ + public final boolean hasDragAndDropApp() + { + return m_dragAndDropApp != null ? true : false; + } + + /** + * Return the drag and drop pseudo file + * + * @return PseudoFile + */ + public final PseudoFile getDragAndDropApp() + { + return m_dragAndDropApp; + } + + /** + * Set the drag and drop application details + * + * @param dragDropApp PseudoFile + */ + public final void setDragAndDropApp(PseudoFile dragDropApp) + { + m_dragAndDropApp = dragDropApp; + } } diff --git a/source/java/org/alfresco/filesys/smb/server/repo/ContentDiskDriver.java b/source/java/org/alfresco/filesys/smb/server/repo/ContentDiskDriver.java index ef9c1ac4a2..eeffe910f6 100644 --- a/source/java/org/alfresco/filesys/smb/server/repo/ContentDiskDriver.java +++ b/source/java/org/alfresco/filesys/smb/server/repo/ContentDiskDriver.java @@ -25,12 +25,10 @@ import javax.transaction.UserTransaction; import org.alfresco.config.ConfigElement; import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.filesys.server.SrvSession; -import org.alfresco.filesys.server.auth.SrvAuthenticator; 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.AccessMode; -import org.alfresco.filesys.server.filesys.DiskDeviceContext; import org.alfresco.filesys.server.filesys.DiskInterface; import org.alfresco.filesys.server.filesys.FileInfo; import org.alfresco.filesys.server.filesys.FileName; @@ -41,21 +39,21 @@ import org.alfresco.filesys.server.filesys.FileSystem; import org.alfresco.filesys.server.filesys.IOControlNotImplementedException; import org.alfresco.filesys.server.filesys.IOCtlInterface; import org.alfresco.filesys.server.filesys.NetworkFile; -import org.alfresco.filesys.server.filesys.NotifyChange; 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.smb.NTIOCtl; import org.alfresco.filesys.smb.SMBException; import org.alfresco.filesys.smb.SMBStatus; import org.alfresco.filesys.smb.SharingMode; import org.alfresco.filesys.smb.server.repo.FileState.FileStateStatus; +import org.alfresco.filesys.smb.server.repo.pseudo.ContentPseudoFileImpl; +import org.alfresco.filesys.smb.server.repo.pseudo.PseudoFile; +import org.alfresco.filesys.smb.server.repo.pseudo.PseudoFileInterface; +import org.alfresco.filesys.smb.server.repo.pseudo.PseudoNetworkFile; import org.alfresco.filesys.util.DataBuffer; import org.alfresco.model.ContentModel; import org.alfresco.service.cmr.coci.CheckOutCheckInService; -import org.alfresco.service.cmr.lock.LockType; import org.alfresco.service.cmr.lock.NodeLockedException; -import org.alfresco.service.cmr.repository.ContentData; import org.alfresco.service.cmr.repository.ContentService; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; @@ -85,6 +83,7 @@ public class ContentDiskDriver implements DiskInterface, IOCtlInterface private static final String KEY_STORE = "store"; private static final String KEY_ROOT_PATH = "rootPath"; + private static final String KEY_RELATIVE_PATH = "relativePath"; // Services and helpers @@ -102,6 +101,10 @@ public class ContentDiskDriver implements DiskInterface, IOCtlInterface private IOControlHandler m_ioHandler; + // Pseudo files interface + + private PseudoFileInterface m_pseudoFiles; + /** * Class constructor * @@ -312,6 +315,32 @@ public class ContentDiskDriver implements DiskInterface, IOCtlInterface m_ioHandler = (IOControlHandler) ioctlObj; m_ioHandler.initialize( this, cifsHelper, transactionService, nodeService, checkInOutService); } + + // Initialize the drag and drop pseudo application, if the I/O support has been enabled + + if ( m_ioHandler != null) + { + ConfigElement dragDropElem = cfg.getChild( "dragAndDrop"); + if ( dragDropElem != null) + { + // Get the pseudo file name and path to the actual file on the local filesystem + + ConfigElement pseudoName = dragDropElem.getChild( "filename"); + ConfigElement appPath = dragDropElem.getChild( "path"); + + if ( pseudoName != null && appPath != null) + { + // Create the pseudo file for the drag and drop application + + PseudoFile dragDropPseudo = new PseudoFile( pseudoName.getValue(), appPath.getValue()); + context.setDragAndDropApp( dragDropPseudo); + + // Enable pseudo file support + + m_pseudoFiles = new ContentPseudoFileImpl(); + } + } + } } catch (Exception ex) { @@ -319,11 +348,45 @@ public class ContentDiskDriver implements DiskInterface, IOCtlInterface logger.debug("No I/O control handler available"); } + // Check if locked files should be marked as offline + + ConfigElement offlineFiles = cfg.getChild( "offlineFiles"); + if ( offlineFiles != null) + { + // Enable marking locked files as offline + + cifsHelper.setMarkLockedFilesAsOffline( true); + + // Logging + + logger.info("Locked files will be marked as offline"); + } + // Return the context for this shared filesystem return context; } + /** + * Check if pseudo file support is enabled + * + * @return boolean + */ + public final boolean hasPseudoFileInterface() + { + return m_pseudoFiles != null ? true : false; + } + + /** + * Return the pseudo file support implementation + * + * @return PseudoFileInterface + */ + public final PseudoFileInterface getPseudoFileInterface() + { + return m_pseudoFiles; + } + /** * Determine if the disk device is read-only. * @@ -348,17 +411,39 @@ public class ContentDiskDriver implements DiskInterface, IOCtlInterface */ public FileInfo getFileInformation(SrvSession session, TreeConnection tree, String path) throws IOException { - // get the device root + // Get the device root ContentContext ctx = (ContentContext) tree.getContext(); NodeRef infoParentNodeRef = ctx.getRootNode(); + + if ( path == null) + path = ""; + String infoPath = path; try { + // Check if the path is to a pseudo file + + FileInfo finfo = null; + + if ( hasPseudoFileInterface()) + { + // Get the pseudo file + + PseudoFile pfile = getPseudoFileInterface().getPseudoFile( session, tree, path); + if ( pfile != null) + { + // DEBUG + + if ( logger.isDebugEnabled()) + logger.debug("getInfo using pseudo file info for " + path); + return pfile.getFileInfo(); + } + } + // Get the node ref for the path, chances are there is a file state in the cache - FileInfo finfo = null; NodeRef nodeRef = getNodeForPath(tree, infoPath); if ( nodeRef != null) { @@ -378,7 +463,8 @@ public class ContentDiskDriver implements DiskInterface, IOCtlInterface if ( finfo == null) { - String[] paths = FileName.splitPath(path); + String[] paths = FileName.splitPath( path); + if ( paths[0] != null && paths[0].length() > 1) { // Find the node ref for the folder being searched @@ -402,6 +488,7 @@ public class ContentDiskDriver implements DiskInterface, IOCtlInterface finfo = cifsHelper.getFileInformation(infoParentNodeRef, infoPath); // DEBUG + if (logger.isDebugEnabled()) { logger.debug("Getting file information: \n" + @@ -411,6 +498,7 @@ public class ContentDiskDriver implements DiskInterface, IOCtlInterface } // Return the file information + return finfo; } catch (FileNotFoundException e) @@ -468,6 +556,7 @@ public class ContentDiskDriver implements DiskInterface, IOCtlInterface String searchFileSpec = searchPath; NodeRef searchRootNodeRef = ctx.getRootNode(); + FileState searchFolderState = null; // Create the transaction @@ -483,6 +572,17 @@ public class ContentDiskDriver implements DiskInterface, IOCtlInterface String[] paths = FileName.splitPath(searchPath); if ( paths[0] != null && paths[0].length() > 1) { + // Get the file state for the folder being searched + + searchFolderState = getStateForPath(tree, paths[0]); + if ( searchFolderState == null) + searchFolderState = ctx.getStateTable().findFileState( paths[0], true, true); + + // Add pseudo files to the folder being searched + + if ( hasPseudoFileInterface()) + getPseudoFileInterface().addPseudoFilesToFolder( sess, tree, paths[0]); + // Find the node ref for the folder being searched NodeRef nodeRef = getNodeForPath(tree, paths[0]); @@ -503,7 +603,7 @@ public class ContentDiskDriver implements DiskInterface, IOCtlInterface // Start the search SearchContext searchCtx = ContentSearchContext.search(cifsHelper, searchRootNodeRef, - searchFileSpec, attributes); + searchFileSpec, attributes, searchFolderState); // done @@ -641,6 +741,24 @@ public class ContentDiskDriver implements DiskInterface, IOCtlInterface // Get the node for the path ContentContext ctx = (ContentContext) tree.getContext(); + + // Check if pseudo files are enabled + + if ( hasPseudoFileInterface()) + { + // Check if the path is to a pseudo file + + PseudoFile pfile = getPseudoFileInterface().getPseudoFile( sess, tree, params.getPath()); + if ( pfile != null) + { + // Create a network file to access the pseudo file data + + return new PseudoNetworkFile( pfile.getFileName(), pfile.getFilePath(), params.getPath()); + } + } + + // Not a pseudo file, try and open a normal file/folder node + NodeRef nodeRef = getNodeForPath(tree, params.getPath()); // Check permissions on the file/folder node @@ -1081,12 +1199,15 @@ public class ContentDiskDriver implements DiskInterface, IOCtlInterface file.closeFile(); - // remove the node if necessary - if (file.hasDeleteOnClose()) + // Remove the node if marked for delete + + if (file.hasDeleteOnClose() && file instanceof ContentNetworkFile) { ContentNetworkFile contentNetFile = (ContentNetworkFile) file; NodeRef nodeRef = contentNetFile.getNodeRef(); - // we don't know how long the network file has had the reference, so check for existence + + // We don't know how long the network file has had the reference, so check for existence + if (nodeService.exists(nodeRef)) { try @@ -1581,6 +1702,34 @@ public class ContentDiskDriver implements DiskInterface, IOCtlInterface return cifsHelper.getNodeRef(ctx.getRootNode(), path); } + /** + * Get the file state for the specified path + * + * @param tree TreeConnection + * @param path String + * @return FileState + * @exception FileNotFoundException + */ + public FileState getStateForPath(TreeConnection tree, String path) + throws FileNotFoundException + { + // Check if there is a cached state for the path + + ContentContext ctx = (ContentContext) tree.getContext(); + FileState fstate = null; + + if ( ctx.hasStateTable()) + { + // Get the file state for a file/folder + + fstate = ctx.getStateTable().findFileState(path); + } + + // Return the file state + + return fstate; + } + /** * Connection opened to this disk device * diff --git a/source/java/org/alfresco/filesys/smb/server/repo/ContentSearchContext.java b/source/java/org/alfresco/filesys/smb/server/repo/ContentSearchContext.java index b029316161..9023cbabe0 100644 --- a/source/java/org/alfresco/filesys/smb/server/repo/ContentSearchContext.java +++ b/source/java/org/alfresco/filesys/smb/server/repo/ContentSearchContext.java @@ -20,7 +20,11 @@ import java.io.FileNotFoundException; import java.util.List; import org.alfresco.filesys.server.filesys.FileInfo; +import org.alfresco.filesys.server.filesys.FileName; import org.alfresco.filesys.server.filesys.SearchContext; +import org.alfresco.filesys.smb.server.repo.pseudo.PseudoFile; +import org.alfresco.filesys.smb.server.repo.pseudo.PseudoFileList; +import org.alfresco.filesys.util.WildCard; import org.alfresco.service.cmr.repository.NodeRef; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -39,6 +43,10 @@ public class ContentSearchContext extends SearchContext private CifsHelper cifsHelper; private List results; private int index = -1; + + // Pseudo file list blended into a wildcard folder search + + private PseudoFileList pseudoList; /** * Performs a search against the direct children of the given node. @@ -51,19 +59,64 @@ public class ContentSearchContext extends SearchContext * @param searchRootNodeRef the node whos children are to be searched * @param searchStr the search string relative to the search root node * @param attributes the search attributes, e.g. searching for folders, etc + * @param searchFolderState File state of the folder being searched * @return Returns a search context with the results of the search */ public static ContentSearchContext search( CifsHelper cifsHelper, NodeRef searchRootNodeRef, String searchStr, - int attributes) + int attributes, + FileState searchFolderState) { - // perform the search - List results = cifsHelper.getNodeRefs(searchRootNodeRef, searchStr); + // Perform the search - // build the search context to store the results - ContentSearchContext searchCtx = new ContentSearchContext(cifsHelper, results, searchStr); + List results = cifsHelper.getNodeRefs(searchRootNodeRef, searchStr); + + // Check if there are any pseudo files for the folder being searched. + + PseudoFileList pseudoList = null; + + if ( searchFolderState != null && searchFolderState.hasPseudoFiles()) + { + // If it is a wildcard search use all pseudo files + + if ( WildCard.containsWildcards(searchStr)) + { + // Check if the folder has any associated pseudo files + + pseudoList = searchFolderState.getPseudoFileList(); + } + else if ( results == null || results.size() == 0) + { + // Check if the required file is in the pseudo file list + + String fname = searchStr; + if ( fname.indexOf(FileName.DOS_SEPERATOR) != -1) + { + String[] paths = FileName.splitPath( searchStr); + fname = paths[1]; + } + + if ( fname != null) + { + // Search for a matching pseudo file + + PseudoFile pfile = searchFolderState.getPseudoFileList().findFile( fname, true); + if ( pfile != null) + { + // Create a file list with the required file + + pseudoList = new PseudoFileList(); + pseudoList.addFile( pfile); + } + } + } + } + + // Build the search context to store the results + + ContentSearchContext searchCtx = new ContentSearchContext(cifsHelper, results, searchStr, pseudoList); // done if (logger.isDebugEnabled()) @@ -81,14 +134,21 @@ public class ContentSearchContext extends SearchContext private ContentSearchContext( CifsHelper cifsHelper, List results, - String searchStr) + String searchStr, + PseudoFileList pseudoList) { super(); super.setSearchString(searchStr); this.cifsHelper = cifsHelper; - this.results = results; + this.results = results; + this.pseudoList = pseudoList; } + /** + * Return the search as a string + * + * @return String + */ public String toString() { StringBuilder sb = new StringBuilder(60); @@ -108,6 +168,10 @@ public class ContentSearchContext extends SearchContext @Override public synchronized boolean hasMoreFiles() { + // Pseudo files are returned first + + if ( pseudoList != null && index < (pseudoList.numberOfFiles() - 1)) + return true; return index < (results.size() -1); } @@ -115,29 +179,67 @@ public class ContentSearchContext extends SearchContext public synchronized boolean nextFileInfo(FileInfo info) { // check if there is anything else to return + if (!hasMoreFiles()) - { return false; - } - // increment the index - index++; - // get the next file info - NodeRef nextNodeRef = results.get(index); - // get the file info + // Increment the index + + index++; + + // If the pseudo file list is valid return the pseudo files first + + if ( pseudoList != null) + { + if ( index < pseudoList.numberOfFiles()) + { + PseudoFile pfile = pseudoList.getFileAt( index); + if ( pfile != null) + { + // Get the file information for the pseudo file + + FileInfo pinfo = pfile.getFileInfo(); + + // Copy the file information to the callers file info + + info.copyFrom( pinfo); + return true; + } + } + else + { + // Switch to the main file list + + pseudoList = null; + index = 0; + + if ( results == null || results.size() == 0) + return false; + } + } + + // Get the next file info from the node search + + NodeRef nextNodeRef = results.get(index); + try { + // Get the file information and copy across to the callers file info + FileInfo nextInfo = cifsHelper.getFileInformation(nextNodeRef, ""); - // copy to info handle info.copyFrom(nextInfo); - // success + // Indicate that the file information is valid + return true; } catch (FileNotFoundException e) { - return false; } + + // File information is not valid + + return false; } @Override diff --git a/source/java/org/alfresco/filesys/smb/server/repo/FileState.java b/source/java/org/alfresco/filesys/smb/server/repo/FileState.java index 74f9b851b8..7e51004010 100644 --- a/source/java/org/alfresco/filesys/smb/server/repo/FileState.java +++ b/source/java/org/alfresco/filesys/smb/server/repo/FileState.java @@ -24,6 +24,8 @@ 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.smb.SharingMode; +import org.alfresco.filesys.smb.server.repo.pseudo.PseudoFile; +import org.alfresco.filesys.smb.server.repo.pseudo.PseudoFileList; import org.alfresco.service.cmr.repository.NodeRef; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -82,6 +84,10 @@ public class FileState private FileState m_newNameState; + // Pseudo file list + + private PseudoFileList m_pseudoFiles; + /** * Class constructor * @@ -242,13 +248,7 @@ public class FileState */ public final synchronized int incrementOpenCount() { - m_openCount++; - - // Debug - - // if ( logger.isDebugEnabled() && m_openCount > 1) - // logger.debug("@@@@@ File open name=" + getPath() + ", count=" + m_openCount); - return m_openCount; + return m_openCount++; } /** @@ -316,6 +316,38 @@ public class FileState { return m_newNameState; } + + /** + * Determine if a folder has pseudo files associated with it + * + * @return boolean + */ + public final boolean hasPseudoFiles() + { + return m_pseudoFiles != null ? true : false; + } + + /** + * Return the pseudo file list + * + * @return PseudoFileList + */ + public final PseudoFileList getPseudoFileList() + { + return m_pseudoFiles; + } + + /** + * Add a pseudo file to this folder + * + * @param pfile PseudoFile + */ + public final void addPseudoFile(PseudoFile pfile) + { + if ( m_pseudoFiles == null) + m_pseudoFiles = new PseudoFileList(); + m_pseudoFiles.addFile( pfile); + } /** * Set the file status @@ -612,6 +644,14 @@ public class FileState else str.append("Null"); + if ( isDirectory()) + { + str.append(",Pseudo="); + if ( hasPseudoFiles()) + str.append(getPseudoFileList().numberOfFiles()); + else + str.append(0); + } str.append("]"); return str.toString(); diff --git a/source/java/org/alfresco/filesys/smb/server/repo/pseudo/ContentPseudoFileImpl.java b/source/java/org/alfresco/filesys/smb/server/repo/pseudo/ContentPseudoFileImpl.java new file mode 100644 index 0000000000..206d970c38 --- /dev/null +++ b/source/java/org/alfresco/filesys/smb/server/repo/pseudo/ContentPseudoFileImpl.java @@ -0,0 +1,182 @@ +/* + * Copyright (C) 2005 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.smb.server.repo.pseudo; + +import org.alfresco.filesys.server.SrvSession; +import org.alfresco.filesys.server.filesys.DiskDeviceContext; +import org.alfresco.filesys.server.filesys.DiskInterface; +import org.alfresco.filesys.server.filesys.FileName; +import org.alfresco.filesys.server.filesys.TreeConnection; +import org.alfresco.filesys.smb.server.repo.ContentContext; +import org.alfresco.filesys.smb.server.repo.ContentDiskDriver; +import org.alfresco.filesys.smb.server.repo.FileState; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * Content Filesystem Driver Pseudo File Implementation + * + *

Pseudo file implementation for the content disk driver. + * + * @author gkspencer + */ +public class ContentPseudoFileImpl implements PseudoFileInterface +{ + // Logging + + private static final Log logger = LogFactory.getLog(ContentPseudoFileImpl.class); + + /** + * Check if the specified path refers to a pseudo file + * + * @param sess SrvSession + * @param tree TreeConnection + * @param path String + * @return boolean + */ + public boolean isPseudoFile(SrvSession sess, TreeConnection tree, String path) + { + // Check if the path is for a pseudo file + + ContentContext ctx = (ContentContext) tree.getContext(); + boolean isPseudo = false; + + String[] paths = FileName.splitPath( path); + FileState fstate = getStateForPath( ctx, paths[0]); + + if ( fstate != null && fstate.hasPseudoFiles()) + { + // Check if there is a matching pseudo file + + PseudoFile pfile = fstate.getPseudoFileList().findFile( paths[1], false); + if ( pfile != null) + isPseudo = true; + } + + // Return the pseudo file status + + return isPseudo; + } + + /** + * Return the pseudo file for the specified path, or null if the path is not a pseudo file + * + * @param sess SrvSession + * @param tree TreeConnection + * @param path String + * @return PseudoFile + */ + public PseudoFile getPseudoFile(SrvSession sess, TreeConnection tree, String path) + { + // Check if the path is for a pseudo file + + ContentContext ctx = (ContentContext) tree.getContext(); + + String[] paths = FileName.splitPath( path); + FileState fstate = getStateForPath( ctx, paths[0]); + + if ( fstate != null && fstate.hasPseudoFiles()) + { + // Check if there is a matching pseudo file + + PseudoFile pfile = fstate.getPseudoFileList().findFile( paths[1], false); + if ( pfile != null) + return pfile; + } + + // Not a pseudo file + + return null; + } + + /** + * Add pseudo files to a folder so that they appear in a folder search + * + * @param sess SrvSession + * @param tree TreeConnection + * @param path String + * @return int + */ + public int addPseudoFilesToFolder(SrvSession sess, TreeConnection tree, String path) + { + // Access the device context + + int pseudoCnt = 0; + ContentContext ctx = (ContentContext) tree.getContext(); + + if ( ctx.hasDragAndDropApp()) + { + // If the file state is null create a file state for the path + + FileState fstate = getStateForPath( ctx, path); + if ( fstate == null) + ctx.getStateTable().findFileState( path, true, true); + + // Check if the folder name starts with 'DRAG', if so then enable the drag and drop pseudo file + // for this folder + + String[] allPaths = FileName.splitAllPaths( path); + String lastPath = allPaths[allPaths.length - 1].toUpperCase(); + + if ( lastPath.startsWith("DRAG") && fstate.hasPseudoFiles() == false) + { + // Enable the drag and drop pseudo file + + fstate.addPseudoFile( ctx.getDragAndDropApp()); + + // Update the count of pseudo files added + + pseudoCnt++; + + // DEBUG + + if ( logger.isInfoEnabled()) + logger.info("Added drag/drop pseudo file for " + path); + } + } + + // Return the count of pseudo files added + + return pseudoCnt; + } + + /** + * Return the file state for the specified path + * + * @param ctx ContentContext + * @param path String + * @return FileState + */ + private final FileState getStateForPath(ContentContext ctx, String path) + { + // Check if there is a cached state for the path + + FileState fstate = null; + + if ( ctx.hasStateTable()) + { + // Get the file state for a file/folder + + fstate = ctx.getStateTable().findFileState(path); + } + + // Return the file state + + return fstate; + } +} diff --git a/source/java/org/alfresco/filesys/smb/server/repo/pseudo/PseudoFile.java b/source/java/org/alfresco/filesys/smb/server/repo/pseudo/PseudoFile.java new file mode 100644 index 0000000000..e194293422 --- /dev/null +++ b/source/java/org/alfresco/filesys/smb/server/repo/pseudo/PseudoFile.java @@ -0,0 +1,172 @@ +/* + * Copyright (C) 2005 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.smb.server.repo.pseudo; + +import java.io.File; + +import org.alfresco.filesys.server.filesys.FileAttribute; +import org.alfresco.filesys.server.filesys.FileInfo; + +/** + * Pseudo File Class + * + *

Creates a pseudo file entry for a folder that maps to a file outside of the usual file area but appears + * in folder listings for the owner folder. + * + * @author gkspencer + */ +public class PseudoFile +{ + // Dummy creation date/time to use for pseudo files + + private static long _creationDateTime = System.currentTimeMillis(); + + // File name for pseudo file + + private String m_fileName; + + // File flags/attributes + + private int m_fileFlags = FileAttribute.ReadOnly; + + // Path to the file data in the local filesystem + + private String m_filePath; + + // File information, used for file information/folder searches + + private FileInfo m_fileInfo; + + /** + * Class constructor + * + * @param name String + * @param path String + */ + public PseudoFile(String name, String path) + { + m_fileName = name; + m_filePath = path; + } + + /** + * Class constructor + * + * @param name String + * @param path String + * @param flags int + */ + public PseudoFile(String name, String path, int flags) + { + m_fileName = name; + m_filePath = path; + + m_fileFlags = flags; + } + + /** + * Return the pseudo file name as it will appear in folder listings + * + * @return String + */ + public final String getFileName() + { + return m_fileName; + } + + /** + * Return the path to the file data on the local filesystem + * + * @return String + */ + public final String getFilePath() + { + return m_filePath; + } + + /** + * Return the standard file attributes + * + * @return int + */ + public final int getAttributes() + { + return m_fileFlags; + } + + /** + * Return the file information for the pseudo file + * + * @return FileInfo + */ + public final FileInfo getFileInfo() + { + // Check if the file information is valid + + if ( m_fileInfo == null) { + + // Get the file details + + File localFile = new File( getFilePath()); + if ( localFile.exists()) + { + // Create the file information + + m_fileInfo = new FileInfo( getFileName(), localFile.length(), getAttributes()); + + // Set the file creation/modification times + + m_fileInfo.setModifyDateTime( localFile.lastModified()); + m_fileInfo.setCreationDateTime( _creationDateTime); + m_fileInfo.setChangeDateTime( _creationDateTime); + + // Set the allocation size, round up the actual length + + m_fileInfo.setAllocationSize(( localFile.length() + 512L) & 0xFFFFFFFFFFFFFE00L); + } + } + + // Return the file information + + return m_fileInfo; + } + + /** + * Return the pseudo file as a string + * + * @return String + */ + public String toString() + { + StringBuilder str = new StringBuilder(); + + str.append("["); + str.append(getFileName()); + str.append(","); + str.append(getFilePath()); + str.append(":"); + + if ( m_fileInfo != null) + str.append( m_fileInfo.toString()); + else + str.append("Null"); + str.append("]"); + + return str.toString(); + } +} diff --git a/source/java/org/alfresco/filesys/smb/server/repo/pseudo/PseudoFileInterface.java b/source/java/org/alfresco/filesys/smb/server/repo/pseudo/PseudoFileInterface.java new file mode 100644 index 0000000000..621d8844be --- /dev/null +++ b/source/java/org/alfresco/filesys/smb/server/repo/pseudo/PseudoFileInterface.java @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2005 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.smb.server.repo.pseudo; + +import org.alfresco.filesys.server.SrvSession; +import org.alfresco.filesys.server.filesys.DiskDeviceContext; +import org.alfresco.filesys.server.filesys.DiskInterface; +import org.alfresco.filesys.server.filesys.TreeConnection; + +/** + * Pseudo File Interface + * + *

Provides the ability to add files into the file listing of a folder. + * + * @author gkspencer + */ +public interface PseudoFileInterface +{ + /** + * Check if the specified path refers to a pseudo file + * + * @param sess SrvSession + * @param tree TreeConnection + * @param path String + * @return boolean + */ + public boolean isPseudoFile(SrvSession sess, TreeConnection tree, String path); + + /** + * Return the pseudo file for the specified path, or null if the path is not a pseudo file + * + * @param sess SrvSession + * @param tree TreeConnection + * @param path String + * @return PseudoFile + */ + public PseudoFile getPseudoFile(SrvSession sess, TreeConnection tree, String path); + + /** + * Add pseudo files to a folder so that they appear in a folder search + * + * @param sess SrvSession + * @param tree TreeConnection + * @param path String + * @return int + */ + public int addPseudoFilesToFolder(SrvSession sess, TreeConnection tree, String path); +} diff --git a/source/java/org/alfresco/filesys/smb/server/repo/pseudo/PseudoFileList.java b/source/java/org/alfresco/filesys/smb/server/repo/pseudo/PseudoFileList.java new file mode 100644 index 0000000000..489b7b3bdd --- /dev/null +++ b/source/java/org/alfresco/filesys/smb/server/repo/pseudo/PseudoFileList.java @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2005 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.smb.server.repo.pseudo; + +import java.util.ArrayList; +import java.util.List; + +/** + * Pseudo File List Class + * + *

Contains a list of pseudo file list entries for a folder. + * + * @author gkspencer + */ +public class PseudoFileList +{ + // List of pseudo files + + private List m_list; + + /** + * Default constructor + */ + public PseudoFileList() + { + m_list = new ArrayList(); + } + + /** + * Add a pseudo file to the list + * + * @param pfile PseudoFile + */ + public final void addFile( PseudoFile pfile) + { + m_list.add( pfile); + } + + /** + * Return the count of files in the list + * + * @return int + */ + public final int numberOfFiles() + { + return m_list.size(); + } + + /** + * Return the file at the specified index in the list + * + * @param idx int + * @return PseudoFile + */ + public final PseudoFile getFileAt(int idx) + { + if ( idx < m_list.size()) + return m_list.get(idx); + return null; + } + + /** + * Search for the specified pseudo file name + * + * @param fname String + * @param caseSensitive boolean + * @return PseudoFile + */ + public final PseudoFile findFile( String fname, boolean caseSensitive) + { + // Check if there are any entries in the list + + if ( m_list == null || m_list.size() == 0) + return null; + + // Search for the name match + + for ( PseudoFile pfile : m_list) + { + if ( caseSensitive && pfile.getFileName().equals( fname)) + return pfile; + else if ( pfile.getFileName().equalsIgnoreCase( fname)) + return pfile; + } + + // File not found + + return null; + } +} diff --git a/source/java/org/alfresco/filesys/smb/server/repo/pseudo/PseudoNetworkFile.java b/source/java/org/alfresco/filesys/smb/server/repo/pseudo/PseudoNetworkFile.java new file mode 100644 index 0000000000..0ae631897c --- /dev/null +++ b/source/java/org/alfresco/filesys/smb/server/repo/pseudo/PseudoNetworkFile.java @@ -0,0 +1,309 @@ +/* + * Copyright (C) 2005 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.smb.server.repo.pseudo; + +import java.io.File; +import java.io.IOException; +import java.io.RandomAccessFile; + +import org.alfresco.filesys.server.filesys.AccessDeniedException; +import org.alfresco.filesys.server.filesys.NetworkFile; +import org.alfresco.filesys.smb.SeekType; + +/** + * Pseudo File Network File Class + *

+ * Represents an open pseudo file and provides access to the file data. + * + * @author gkspencer + */ +public class PseudoNetworkFile extends NetworkFile +{ + // File details + + protected File m_file; + + // Random access file used to read/write the actual file + + protected RandomAccessFile m_io; + + // End of file flag + + protected boolean m_eof; + + /** + * Class constructor. + * + * @param name String + * @param localPath String + * @param netPath String + */ + public PseudoNetworkFile(String name, String localPath, String netPath) + { + super( name); + + // Set the file using the existing file object + + m_file = new File( localPath); + + // Set the file size + + setFileSize(m_file.length()); + m_eof = false; + + // Set the modification date/time, if available. Fake the creation date/time as it's not + // available from the File class + + long modDate = m_file.lastModified(); + setModifyDate(modDate); + setCreationDate(modDate); + + // Set the file id + + setFileId(netPath.hashCode()); + + // Set the full relative path + + setFullName( netPath); + } + + /** + * Close the network file. + */ + public void closeFile() throws java.io.IOException + { + + // Close the file, if used + + if (m_io != null) + { + + // Close the file + + m_io.close(); + m_io = null; + + // Set the last modified date/time for the file + + if (this.getWriteCount() > 0) + m_file.setLastModified(System.currentTimeMillis()); + + // Indicate that the file is closed + + setClosed(true); + } + } + + /** + * Return the current file position. + * + * @return long + */ + public long currentPosition() + { + + // Check if the file is open + + try + { + if (m_io != null) + return m_io.getFilePointer(); + } + catch (Exception ex) + { + } + return 0; + } + + /** + * Flush the file. + * + * @exception IOException + */ + public void flushFile() throws IOException + { + // Flush all buffered data + + if (m_io != null) + m_io.getFD().sync(); + } + + /** + * Determine if the end of file has been reached. + * + * @return boolean + */ + public boolean isEndOfFile() throws java.io.IOException + { + // Check if we reached end of file + + if (m_io != null && m_io.getFilePointer() == m_io.length()) + return true; + return false; + } + + /** + * Open the file. + * + * @param createFlag boolean + * @exception IOException + */ + public void openFile(boolean createFlag) throws java.io.IOException + { + + synchronized (m_file) + { + // Check if the file is open + + if (m_io == null) + { + + // Open the file, always read-only for now + + m_io = new RandomAccessFile(m_file, "r"); + + // Indicate that the file is open + + setClosed(false); + } + } + } + + /** + * 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 + { + + // Open the file, if not already open + + if (m_io == null) + openFile(false); + + // Seek to the required file position + + if (currentPosition() != fileOff) + seekFile(fileOff, SeekType.StartOfFile); + + // Read from the file + + int rdlen = m_io.read(buf, pos, len); + + // Return the actual length of data read + + return rdlen; + } + + /** + * Seek to the specified file position. + * + * @param pos long + * @param typ int + * @return long + * @exception IOException + */ + public long seekFile(long pos, int typ) throws IOException + { + + // Open the file, if not already open + + if (m_io == null) + openFile(false); + + // Check if the current file position is the required file position + + switch (typ) + { + + // From start of file + + case SeekType.StartOfFile: + if (currentPosition() != pos) + m_io.seek(pos); + break; + + // From current position + + case SeekType.CurrentPos: + m_io.seek(currentPosition() + pos); + break; + + // From end of file + + case SeekType.EndOfFile: { + long newPos = m_io.length() + pos; + m_io.seek(newPos); + } + break; + } + + // Return the new file position + + return currentPosition(); + } + + /** + * Truncate the file + * + * @param siz long + * @exception IOException + */ + public void truncateFile(long siz) throws IOException + { + // Do not allow the file to be written to + + throw new AccessDeniedException("Cannot truncate pseudo file"); + } + + /** + * Write a block of data to the file. + * + * @param buf byte[] + * @param len int + * @exception IOException + */ + public void writeFile(byte[] buf, int len, int pos) throws java.io.IOException + { + // Do not allow the file to be written to + + throw new AccessDeniedException("Cannot write to pseudo file"); + } + + /** + * Write a block of data to the file. + * + * @param buf byte[] + * @param len int + * @param pos int + * @param offset long + * @exception IOException + */ + public void writeFile(byte[] buf, int len, int pos, long offset) throws java.io.IOException + { + // Do not allow the file to be written to + + throw new AccessDeniedException("Cannot write to pseudo file"); + } +}