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 <waldheinz at gmail.com> 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