Home | History | Annotate | Download | only in fat
      1 /*
      2  * Copyright (C) 2009,2010 Matthias Treydte <mt (at) waldheinz.de>
      3  *
      4  * This library is free software; you can redistribute it and/or modify it
      5  * under the terms of the GNU Lesser General Public License as published
      6  * by the Free Software Foundation; either version 2.1 of the License, or
      7  * (at your option) any later version.
      8  *
      9  * This library is distributed in the hope that it will be useful, but
     10  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
     11  * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
     12  * License for more details.
     13  *
     14  * You should have received a copy of the GNU Lesser General Public License
     15  * along with this library; If not, write to the Free Software Foundation, Inc.,
     16  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
     17  */
     18 
     19 package de.waldheinz.fs.fat;
     20 
     21 import de.waldheinz.fs.AbstractFsObject;
     22 import java.io.IOException;
     23 import de.waldheinz.fs.FsFile;
     24 import de.waldheinz.fs.ReadOnlyException;
     25 import java.io.EOFException;
     26 import java.nio.ByteBuffer;
     27 
     28 /**
     29  * The in-memory representation of a single file (chain of clusters) on a
     30  * FAT file system.
     31  *
     32  * @author Matthias Treydte &lt;waldheinz at gmail.com&gt;
     33  * @since 0.6
     34  */
     35 public final class FatFile extends AbstractFsObject implements FsFile {
     36     private final FatDirectoryEntry entry;
     37     private final ClusterChain chain;
     38 
     39     private FatFile(FatDirectoryEntry myEntry, ClusterChain chain) {
     40         super(myEntry.isReadOnly());
     41 
     42         this.entry = myEntry;
     43         this.chain = chain;
     44     }
     45 
     46     static FatFile get(Fat fat, FatDirectoryEntry entry)
     47             throws IOException {
     48 
     49         if (entry.isDirectory())
     50             throw new IllegalArgumentException(entry + " is a directory");
     51 
     52         final ClusterChain cc = new ClusterChain(
     53                 fat, entry.getStartCluster(), entry.isReadonlyFlag());
     54 
     55         if (entry.getLength() > cc.getLengthOnDisk()) throw new IOException(
     56                 "entry is larger than associated cluster chain");
     57 
     58         return new FatFile(entry, cc);
     59     }
     60 
     61     /**
     62      * Returns the length of this file in bytes. This is the length that
     63      * is stored in the directory entry that is associated with this file.
     64      *
     65      * @return long the length that is recorded for this file
     66      */
     67     @Override
     68     public long getLength() {
     69         checkValid();
     70 
     71         return entry.getLength();
     72     }
     73 
     74     /**
     75      * Sets the size (in bytes) of this file. Because
     76      * {@link #write(long, java.nio.ByteBuffer) writing} to the file will grow
     77      * it automatically if needed, this method is mainly usefull for truncating
     78      * a file.
     79      *
     80      * @param length the new length of the file in bytes
     81      * @throws ReadOnlyException if this file is read-only
     82      * @throws IOException on error updating the file size
     83      */
     84     @Override
     85     public void setLength(long length) throws ReadOnlyException, IOException {
     86         checkWritable();
     87 
     88         if (getLength() == length) return;
     89 
     90         updateTimeStamps(true);
     91         chain.setSize(length);
     92 
     93         this.entry.setStartCluster(chain.getStartCluster());
     94         this.entry.setLength(length);
     95     }
     96 
     97     /**
     98      * <p>
     99      * {@inheritDoc}
    100      * </p><p>
    101      * Unless this file is {@link #isReadOnly() read-ony}, this method also
    102      * updates the "last accessed" field in the directory entry that is
    103      * associated with this file.
    104      * </p>
    105      *
    106      * @param offset {@inheritDoc}
    107      * @param dest {@inheritDoc}
    108      * @see FatDirectoryEntry#setLastAccessed(long)
    109      */
    110     @Override
    111     public void read(long offset, ByteBuffer dest) throws IOException {
    112         checkValid();
    113 
    114         final int len = dest.remaining();
    115 
    116         if (len == 0) return;
    117 
    118         if (offset + len > getLength()) {
    119             throw new EOFException();
    120         }
    121 
    122         if (!isReadOnly()) {
    123             updateTimeStamps(false);
    124         }
    125 
    126         chain.readData(offset, dest);
    127     }
    128 
    129     /**
    130      * <p>
    131      * {@inheritDoc}
    132      * </p><p>
    133      * If the data to be written extends beyond the current
    134      * {@link #getLength() length} of this file, an attempt is made to
    135      * {@link #setLength(long) grow} the file so that the data will fit.
    136      * Additionally, this method updates the "last accessed" and "last modified"
    137      * fields on the directory entry that is associated with this file.
    138      * </p>
    139      *
    140      * @param offset {@inheritDoc}
    141      * @param srcBuf {@inheritDoc}
    142      */
    143     @Override
    144     public void write(long offset, ByteBuffer srcBuf)
    145             throws ReadOnlyException, IOException {
    146 
    147         checkWritable();
    148 
    149         updateTimeStamps(true);
    150 
    151         final long lastByte = offset + srcBuf.remaining();
    152 
    153         if (lastByte > getLength()) {
    154             setLength(lastByte);
    155         }
    156 
    157         chain.writeData(offset, srcBuf);
    158     }
    159 
    160     private void updateTimeStamps(boolean write) {
    161         final long now = System.currentTimeMillis();
    162         entry.setLastAccessed(now);
    163 
    164         if (write) {
    165             entry.setLastModified(now);
    166         }
    167     }
    168 
    169     /**
    170      * Has no effect besides possibly throwing an {@code ReadOnlyException}. To
    171      * make sure that all data is written out to disk use the
    172      * {@link FatFileSystem#flush()} method.
    173      *
    174      * @throws ReadOnlyException if this {@code FatFile} is read-only
    175      */
    176     @Override
    177     public void flush() throws ReadOnlyException {
    178         checkWritable();
    179 
    180         /* nothing else to do */
    181     }
    182 
    183     /**
    184      * Returns the {@code ClusterChain} that holds the contents of
    185      * this {@code FatFile}.
    186      *
    187      * @return the file's {@code ClusterChain}
    188      */
    189     ClusterChain getChain() {
    190         checkValid();
    191 
    192         return chain;
    193     }
    194 
    195     /**
    196      * Returns a human-readable string representation of this {@code FatFile},
    197      * mainly for debugging purposes.
    198      *
    199      * @return a string describing this {@code FatFile}
    200      */
    201     @Override
    202     public String toString() {
    203         return getClass().getSimpleName() + " [length=" + getLength() +
    204                 ", first cluster=" + chain.getStartCluster() + "]";
    205     }
    206 
    207 }
    208