/* * Copyright (C) 2006-2008 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.jlan.server.filesys.db; import java.io.FileNotFoundException; import java.io.IOException; import org.alfresco.jlan.debug.Debug; import org.alfresco.jlan.server.filesys.AccessDeniedException; import org.alfresco.jlan.server.filesys.FileInfo; import org.alfresco.jlan.server.filesys.FileOfflineException; import org.alfresco.jlan.server.filesys.cache.FileState; import org.alfresco.jlan.server.filesys.loader.FileLoader; import org.alfresco.jlan.server.filesys.loader.FileRequest; import org.alfresco.jlan.server.filesys.loader.FileSegment; import org.alfresco.jlan.server.filesys.loader.FileSegmentInfo; import org.alfresco.jlan.server.filesys.loader.SingleFileRequest; /** * Cached Data Network File Class * * <p>Caches the file data in the local filesystem in a temporary area. * * @author gkspencer */ public class CachedNetworkFile extends DBNetworkFile { // Maximum time to wait for file data protected static final long DataLoadWaitTime = 20000L; // 20 seconds protected static final long DataPollSleepTime = 250L; // milliseconds // Debug enable flag private static final boolean DEBUG = false; // File segment holding a local copy of the file data protected FileSegment m_cacheFile; // Read request details protected long m_lastReadPos = -1L; protected int m_lastReadLen = -1; protected int m_seqReads; // Sequential access only flag protected boolean m_seqOnly; /** * Class constructor * * @param name String * @param fid int * @param stid int * @param did int * @param state FileState * @param segment FileSegment * @param loader FileLoader */ public CachedNetworkFile(String name, int fid, int stid, int did, FileState state, FileSegment segment, FileLoader loader) { super(name,fid,stid,did); // Set the file segment and memory segment list m_cacheFile = segment; // Save the file state setFileState(state); // Set the associated file loader setLoader(loader); } /** * Return the associated file segment * * @return FileSegment */ public final FileSegment getFileSegment() { return m_cacheFile; } /** * Determine if the file will only be accessed sequentially * * @return boolean */ public final boolean isSequentialOnly() { return m_seqOnly; } /** * Set the sequential access only flag * * @param seq boolean */ public final void setSequentialOnly(boolean seq) { m_seqOnly = seq; } /** * Open the file * * @param createFlag boolean * @exception IOException */ public void openFile(boolean createFlag) throws IOException { // Open the file segment temporary file try { // Check if the temporary file exists if ( m_cacheFile.fileExists() == false) { // Create the temporary file m_cacheFile.createTemporaryFile(); // DEBUG if ( DEBUG) Debug.println("CachedNetworkFile.openFile() created " + m_cacheFile.getTemporaryFile()); } // Open the temporary file m_cacheFile.openFile(); } catch (FileNotFoundException ex) { if ( DEBUG) { Debug.println("openFile() FAILED name=" + getFullName() + ", error=" + ex.toString()); Debug.println(" fstate=" + getFileState()); Debug.println(" cacheFile=" + m_cacheFile); } // Rethrow the exception throw ex; } } /** * 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 IOException { // DEBUG if ( DEBUG) Debug.println("CachedNetworkFile.readFile() offset=" + fileOff + ", len=" + len); // Determine if this is a sequential read boolean seqRead = false; if ( m_lastReadPos != -1L && fileOff == (m_lastReadPos + m_lastReadLen)) { // Indicate that this is a sequential read seqRead = true; m_seqReads++; } // Check for a file segment error if ( m_cacheFile.hasStatus() == FileSegmentInfo.Error) { // DEBUG if ( DEBUG) Debug.println("CachedNetworkFile file segment error"); // Indicate no more data throw new IOException("Load file error - " + getFullName()); } // Update the last read position/length m_lastReadPos = fileOff; m_lastReadLen = len; // Check if the file data has been loaded if ( m_cacheFile.hasStatus() == FileSegmentInfo.Initial && m_cacheFile.isQueued() == false) { // Check if the temporary file exists if ( m_cacheFile.fileExists() == false) { // Create the temporary file m_cacheFile.createTemporaryFile(); } synchronized ( getFileState()) { // Queue a file data load request if ( m_cacheFile.isQueued() == false) getLoader().queueFileRequest( createFileRequest( FileRequest.LOAD)); } } // Check if the file data is available, or still loading int rdlen = 0; if ( m_cacheFile.isDataAvailable()) { // DEBUG if ( DEBUG) Debug.println("CachedNetworkFile DataAvailable Read, file=" + getName() + ", fid=" + getFileId()); // Read the file using the file segment rdlen = m_cacheFile.readBytes(buf, len, pos, fileOff); // DEBUG if ( rdlen <= 0) { // DEBUG if ( DEBUG) { Debug.println("CachedNetworkFile.readFile() name=" + getFullName()); Debug.println(" State=" + getFileState()); Debug.println(" Segment=" + getFileSegment()); Debug.println(" Size=" + getFileSize()); } m_cacheFile.closeFile(); m_cacheFile.openFile(); rdlen = m_cacheFile.readBytes(buf, len, pos, fileOff); } // Return the length of data read return rdlen; } // Wait for the required amount of data to be written to the temporary file long waitTime = 0L; boolean readDone = false; boolean dataAvailable = false; while ( readDone == false && waitTime < DataLoadWaitTime && ( m_cacheFile.isDataLoading() || m_cacheFile.isDataAvailable())) { // Check if there is enough data available to satisfy the read request dataAvailable = m_cacheFile.isDataAvailable(); if ( dataAvailable == false) { // File loader thread is still loading the file data, check the file length to see if // there is enough data to satisfy the read request. Check that there is more data available // than required as the loader may have extended the file and still be writing the last block // of data. long fileLen = m_cacheFile.getReadableLength(); if ( fileLen != -1 && (fileLen + 0xFFFF) > (fileOff + len)) rdlen = m_cacheFile.readBytes(buf, len, pos, fileOff); } else rdlen = m_cacheFile.readBytes(buf, len, pos, fileOff); // Check if the read was successful if ( rdlen > 0) { // Indicate that the required data has been read if ( dataAvailable == false && rdlen < len) readDone = false; else readDone = true; } else if ( dataAvailable == true) { // No more data available readDone = true; } else { // Wait for some data to be loaded try { // Indicate that an I/O is pending on this file setIOPending(true); // Wait for the file data to be loaded, or for large files the wait may timeout and we can check // if there is enough data available to satisfy the current read request. long startTime = System.currentTimeMillis(); m_cacheFile.waitForData(DataPollSleepTime); long endTime = System.currentTimeMillis(); // Update the total data wait time waitTime += (endTime - startTime); // Clear the I/O pending flag setIOPending(false); // DEBUG if ( DEBUG) Debug.println("CachedNetworkFile waited " + (endTime - startTime) + "ms for data, available=" + m_cacheFile.getReadableLength()); } catch (Exception ex) { } // Check for a file load error if ( m_cacheFile.hasLoadError()) throw new IOException("Load file error - " + getFullName()); // DEBUG if ( DEBUG) { Debug.println("m_cacheFile=" + m_cacheFile + ", rdlen=" + rdlen); Debug.println("fstate=" + getFileState()); } } } // Check for a file load error if ( m_cacheFile.hasLoadError()) throw new IOException("Load file error - " + getFullName()); // DEBUG if ( DEBUG) Debug.println("CachedNetworkFile.readFile() Waited " + waitTime + "ms for data"); // Check if the required data has been read if ( readDone == true) { // Return the length of data read, may be shorter than requested return rdlen; } else { // DEBUG if ( DEBUG) { Debug.println("ReadFault fname=" + getFullName() + ", fid=" + getFileId()); Debug.println(" " + m_cacheFile.toString()); } throw new FileOfflineException("File data not available"); } } /** * 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 IOException { // Check if the file is writeable if ( getGrantedAccess() == READONLY) throw new AccessDeniedException("File is read-only"); // Write the file using the file segment m_cacheFile.writeBytes(buf, len, pos, offset); // Update the write count for the file incrementWriteCount(); // Update the cached file size long fileLen = m_cacheFile.getFileLength(); if ( fileLen != -1L) updateFileSize(fileLen, -1L); } /** * Flush any buffered output to the file * * @throws IOException */ public void flushFile() throws IOException { // Flush all buffered output, if the file is open if ( m_cacheFile != null && m_cacheFile.isOpen()) m_cacheFile.flush(); } /** * 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 { return 0; } /** * Truncate the file to the specified file size * * @param siz long * @exception IOException */ public void truncateFile(long siz) throws IOException { // Check if the file is writeable if ( getGrantedAccess() == READONLY) throw new AccessDeniedException("File is read-only"); // Truncate the file m_cacheFile.truncate(siz); // Update the cached file size updateFileSize(siz,siz); // Update the write count for the file incrementWriteCount(); } /** * Close the file */ public void closeFile() { // Close the associated file segment temporary file if ( m_cacheFile != null) { try { // Update the file length if ( m_cacheFile.isDataAvailable() && m_cacheFile.isOpen()) { long fileSize = m_cacheFile.getFileLength(); if ( fileSize != -1L) setFileSize(fileSize); } // Close the file m_cacheFile.closeFile(); // DEBUG if ( DEBUG) Debug.println("CachedNetworkFile.closeFile()"); } catch ( IOException ex) { if ( DEBUG) { Debug.println("**** Error closing file " + getName() + ", fid=" + getFileId() + " ****"); Debug.println(ex); } } } } /** * Update the cached file information file size * * @param siz long * @param alloc long */ protected final void updateFileSize(long siz, long alloc) { // Get the cached file information, if available if ( hasFileState()) { // Get the cached file information for this file FileInfo finfo = (FileInfo) getFileState().findAttribute(FileState.FileInformation); if ( finfo != null && finfo.getSize() != siz) { // Update the file size and allocation size finfo.setSize(siz); if ( alloc != -1L || finfo.getSize() > finfo.getAllocationSize()) finfo.setAllocationSize(alloc); } } // Update the open file size setFileSize( siz); } /** * Create a file load or save request. This method may be overridden to allow extending of the SingleFileRequest * class. * * @param typ int * @return FileRequest */ protected FileRequest createFileRequest( int typ) { // Create a file load or save request return new SingleFileRequest( typ, getFileId(), getStreamId(), m_cacheFile.getInfo(), getFullNameStream(), getFileState()); } /** * Determine if network file debug output is enabled * * @return boolean */ protected final boolean hasDebug() { return DEBUG; } /** * Object is about to be garbage collected */ protected void finalize () { // Make sure the file is closed if ( m_cacheFile != null && m_cacheFile.isOpen()) { try { m_cacheFile.closeFile(); m_cacheFile = null; } catch (Exception ex) { Debug.println(ex); } } } }