/* * Copyright (C) 2009,2010 Matthias Treydte * * This library is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation; either version 2.1 of the License, or * (at your option) any later version. * * This library 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 Lesser General Public * License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library; If not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ package de.waldheinz.fs.fat; import de.waldheinz.fs.AbstractFsObject; import java.io.IOException; import de.waldheinz.fs.FsFile; import de.waldheinz.fs.ReadOnlyException; import java.io.EOFException; import java.nio.ByteBuffer; /** * The in-memory representation of a single file (chain of clusters) on a * FAT file system. * * @author Matthias Treydte <waldheinz at gmail.com> * @since 0.6 */ public final class FatFile extends AbstractFsObject implements FsFile { private final FatDirectoryEntry entry; private final ClusterChain chain; private FatFile(FatDirectoryEntry myEntry, ClusterChain chain) { super(myEntry.isReadOnly()); this.entry = myEntry; this.chain = chain; } static FatFile get(Fat fat, FatDirectoryEntry entry) throws IOException { if (entry.isDirectory()) throw new IllegalArgumentException(entry + " is a directory"); final ClusterChain cc = new ClusterChain( fat, entry.getStartCluster(), entry.isReadonlyFlag()); if (entry.getLength() > cc.getLengthOnDisk()) throw new IOException( "entry is larger than associated cluster chain"); return new FatFile(entry, cc); } /** * Returns the length of this file in bytes. This is the length that * is stored in the directory entry that is associated with this file. * * @return long the length that is recorded for this file */ @Override public long getLength() { checkValid(); return entry.getLength(); } /** * Sets the size (in bytes) of this file. Because * {@link #write(long, java.nio.ByteBuffer) writing} to the file will grow * it automatically if needed, this method is mainly usefull for truncating * a file. * * @param length the new length of the file in bytes * @throws ReadOnlyException if this file is read-only * @throws IOException on error updating the file size */ @Override public void setLength(long length) throws ReadOnlyException, IOException { checkWritable(); if (getLength() == length) return; updateTimeStamps(true); chain.setSize(length); this.entry.setStartCluster(chain.getStartCluster()); this.entry.setLength(length); } /** *

* {@inheritDoc} *

* Unless this file is {@link #isReadOnly() read-ony}, this method also * updates the "last accessed" field in the directory entry that is * associated with this file. *

* * @param offset {@inheritDoc} * @param dest {@inheritDoc} * @see FatDirectoryEntry#setLastAccessed(long) */ @Override public void read(long offset, ByteBuffer dest) throws IOException { checkValid(); final int len = dest.remaining(); if (len == 0) return; if (offset + len > getLength()) { throw new EOFException(); } if (!isReadOnly()) { updateTimeStamps(false); } chain.readData(offset, dest); } /** *

* {@inheritDoc} *

* If the data to be written extends beyond the current * {@link #getLength() length} of this file, an attempt is made to * {@link #setLength(long) grow} the file so that the data will fit. * Additionally, this method updates the "last accessed" and "last modified" * fields on the directory entry that is associated with this file. *

* * @param offset {@inheritDoc} * @param srcBuf {@inheritDoc} */ @Override public void write(long offset, ByteBuffer srcBuf) throws ReadOnlyException, IOException { checkWritable(); updateTimeStamps(true); final long lastByte = offset + srcBuf.remaining(); if (lastByte > getLength()) { setLength(lastByte); } chain.writeData(offset, srcBuf); } private void updateTimeStamps(boolean write) { final long now = System.currentTimeMillis(); entry.setLastAccessed(now); if (write) { entry.setLastModified(now); } } /** * Has no effect besides possibly throwing an {@code ReadOnlyException}. To * make sure that all data is written out to disk use the * {@link FatFileSystem#flush()} method. * * @throws ReadOnlyException if this {@code FatFile} is read-only */ @Override public void flush() throws ReadOnlyException { checkWritable(); /* nothing else to do */ } /** * Returns the {@code ClusterChain} that holds the contents of * this {@code FatFile}. * * @return the file's {@code ClusterChain} */ ClusterChain getChain() { checkValid(); return chain; } /** * Returns a human-readable string representation of this {@code FatFile}, * mainly for debugging purposes. * * @return a string describing this {@code FatFile} */ @Override public String toString() { return getClass().getSimpleName() + " [length=" + getLength() + ", first cluster=" + chain.getStartCluster() + "]"; } }