1 /* 2 * Copyright (C) 2003-2009 JNode.org 3 * 2009,2010 Matthias Treydte <mt@waldheinz.de> 4 * 5 * This library is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU Lesser General Public License as published 7 * by the Free Software Foundation; either version 2.1 of the License, or 8 * (at your option) any later version. 9 * 10 * This library is distributed in the hope that it will be useful, but 11 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 12 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 13 * License for more details. 14 * 15 * You should have received a copy of the GNU Lesser General Public License 16 * along with this library; If not, write to the Free Software Foundation, Inc., 17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 18 */ 19 20 package de.waldheinz.fs.fat; 21 22 import de.waldheinz.fs.AbstractFsObject; 23 import de.waldheinz.fs.FsDirectory; 24 import de.waldheinz.fs.FsDirectoryEntry; 25 import java.io.IOException; 26 import java.util.ArrayList; 27 import java.util.Arrays; 28 import java.util.HashSet; 29 import java.util.LinkedHashMap; 30 import java.util.Iterator; 31 import java.util.Map; 32 import java.util.Set; 33 34 /** 35 * The {@link FsDirectory} implementation for FAT file systems. This 36 * implementation aims to fully comply to the FAT specification, including 37 * the quite complex naming system regarding the long file names (LFNs) and 38 * their corresponding 8+3 short file names. This also means that an 39 * {@code FatLfnDirectory} is case-preserving but <em>not</em> case-sensitive. 40 * 41 * @author gbin 42 * @author Matthias Treydte <waldheinz at gmail.com> 43 * @since 0.6 44 */ 45 public final class FatLfnDirectory 46 extends AbstractFsObject 47 implements FsDirectory { 48 49 /** 50 * This set is used to check if a file name is already in use in this 51 * directory. The FAT specification says that file names must be unique 52 * ignoring the case, so this set contains all names converted to 53 * lower-case, and all checks must be performed using lower-case strings. 54 */ 55 private final Set<String> usedNames; 56 private final Fat fat; 57 private final Map<ShortName, FatLfnDirectoryEntry> shortNameIndex; 58 private final Map<String, FatLfnDirectoryEntry> longNameIndex; 59 private final Map<FatDirectoryEntry, FatFile> entryToFile; 60 private final Map<FatDirectoryEntry, FatLfnDirectory> entryToDirectory; 61 private Dummy83BufferGenerator dbg; 62 63 final AbstractDirectory dir; 64 FatLfnDirectory(AbstractDirectory dir, Fat fat, boolean readOnly)65 FatLfnDirectory(AbstractDirectory dir, Fat fat, boolean readOnly) 66 throws IOException { 67 68 super(readOnly); 69 70 if ((dir == null) || (fat == null)) throw new NullPointerException(); 71 72 this.fat = fat; 73 this.dir = dir; 74 75 this.shortNameIndex = 76 new LinkedHashMap<ShortName, FatLfnDirectoryEntry>(); 77 78 this.longNameIndex = 79 new LinkedHashMap<String, FatLfnDirectoryEntry>(); 80 81 this.entryToFile = 82 new LinkedHashMap<FatDirectoryEntry, FatFile>(); 83 84 this.entryToDirectory = 85 new LinkedHashMap<FatDirectoryEntry, FatLfnDirectory>(); 86 87 this.usedNames = new HashSet<String>(); 88 this.dbg = new Dummy83BufferGenerator(); 89 90 parseLfn(); 91 } 92 getFile(FatDirectoryEntry entry)93 FatFile getFile(FatDirectoryEntry entry) throws IOException { 94 FatFile file = entryToFile.get(entry); 95 96 if (file == null) { 97 file = FatFile.get(fat, entry); 98 entryToFile.put(entry, file); 99 } 100 101 return file; 102 } 103 getDirectory(FatDirectoryEntry entry)104 FatLfnDirectory getDirectory(FatDirectoryEntry entry) throws IOException { 105 FatLfnDirectory result = entryToDirectory.get(entry); 106 107 if (result == null) { 108 final ClusterChainDirectory storage = read(entry, fat); 109 result = new FatLfnDirectory(storage, fat, isReadOnly()); 110 entryToDirectory.put(entry, result); 111 } 112 113 return result; 114 } 115 116 /** 117 * <p> 118 * {@inheritDoc} 119 * </p><p> 120 * According to the FAT file system specification, leading and trailing 121 * spaces in the {@code name} are ignored by this method. 122 * </p> 123 * 124 * @param name {@inheritDoc} 125 * @return {@inheritDoc} 126 * @throws IOException {@inheritDoc} 127 */ 128 @Override addFile(String name)129 public FatLfnDirectoryEntry addFile(String name) throws IOException { 130 checkWritable(); 131 checkUniqueName(name); 132 133 name = name.trim(); 134 final ShortName sn = makeShortName(name, false); 135 136 final FatLfnDirectoryEntry entry = 137 new FatLfnDirectoryEntry(name, sn, this, false); 138 139 dir.addEntries(entry.compactForm()); 140 141 shortNameIndex.put(sn, entry); 142 longNameIndex.put(name.toLowerCase(), entry); 143 144 getFile(entry.realEntry); 145 146 dir.setDirty(); 147 return entry; 148 } 149 isFreeName(String name)150 boolean isFreeName(String name) { 151 return true; 152 } 153 checkUniqueName(String name)154 private void checkUniqueName(String name) throws IOException { 155 } 156 freeUniqueName(String name)157 private void freeUniqueName(String name) { 158 } 159 makeShortName(String name, boolean isDirectory)160 private ShortName makeShortName(String name, boolean isDirectory) throws IOException { 161 final ShortName result; 162 163 try { 164 result = dbg.generate83BufferNew(name); 165 } catch (IllegalArgumentException ex) { 166 throw new IOException( 167 "could not generate short name for \"" + name + "\"", ex); 168 } 169 return result; 170 } 171 172 /** 173 * <p> 174 * {@inheritDoc} 175 * </p><p> 176 * According to the FAT file system specification, leading and trailing 177 * spaces in the {@code name} are ignored by this method. 178 * </p> 179 * 180 * @param name {@inheritDoc} 181 * @return {@inheritDoc} 182 * @throws IOException {@inheritDoc} 183 */ 184 @Override addDirectory(String name)185 public FatLfnDirectoryEntry addDirectory(String name) throws IOException { 186 checkWritable(); 187 checkUniqueName(name); 188 189 name = name.trim(); 190 final ShortName sn = makeShortName(name, true); 191 final FatDirectoryEntry real = dir.createSub(fat); 192 real.setShortName(sn); 193 final FatLfnDirectoryEntry e = 194 new FatLfnDirectoryEntry(this, real, name); 195 196 try { 197 dir.addEntries(e.compactForm()); 198 } catch (IOException ex) { 199 final ClusterChain cc = 200 new ClusterChain(fat, real.getStartCluster(), false); 201 cc.setChainLength(0); 202 dir.removeEntry(real); 203 throw ex; 204 } 205 206 shortNameIndex.put(sn, e); 207 longNameIndex.put(name.toLowerCase(), e); 208 209 getDirectory(real); 210 211 flush(); 212 return e; 213 } 214 215 /** 216 * <p> 217 * {@inheritDoc} 218 * </p><p> 219 * According to the FAT file system specification, leading and trailing 220 * spaces in the {@code name} are ignored by this method. 221 * </p> 222 * 223 * @param name {@inheritDoc} 224 * @return {@inheritDoc} 225 */ 226 @Override getEntry(String name)227 public FatLfnDirectoryEntry getEntry(String name) { 228 name = name.trim().toLowerCase(); 229 230 final FatLfnDirectoryEntry entry = longNameIndex.get(name); 231 232 if (entry == null) { 233 if (!ShortName.canConvert(name)) return null; 234 return shortNameIndex.get(ShortName.get(name)); 235 } else { 236 return entry; 237 } 238 } 239 parseLfn()240 private void parseLfn() throws IOException { 241 int i = 0; 242 final int size = dir.getEntryCount(); 243 244 while (i < size) { 245 // jump over empty entries 246 while (i < size && dir.getEntry(i) == null) { 247 i++; 248 } 249 250 if (i >= size) { 251 break; 252 } 253 254 int offset = i; // beginning of the entry 255 // check when we reach a real entry 256 while (dir.getEntry(i).isLfnEntry()) { 257 i++; 258 if (i >= size) { 259 // This is a cutted entry, forgive it 260 break; 261 } 262 } 263 264 if (i >= size) { 265 // This is a cutted entry, forgive it 266 break; 267 } 268 269 final FatLfnDirectoryEntry current = 270 FatLfnDirectoryEntry.extract(this, offset, ++i - offset); 271 272 if (!current.realEntry.isDeleted() && current.isValid()) { 273 checkUniqueName(current.getName()); 274 275 shortNameIndex.put(current.realEntry.getShortName(), current); 276 longNameIndex.put(current.getName().toLowerCase(), current); 277 } 278 } 279 } 280 updateLFN()281 private void updateLFN() throws IOException { 282 ArrayList<FatDirectoryEntry> dest = 283 new ArrayList<FatDirectoryEntry>(); 284 285 for (FatLfnDirectoryEntry currentEntry : shortNameIndex.values()) { 286 FatDirectoryEntry[] encoded = currentEntry.compactForm(); 287 dest.addAll(Arrays.asList(encoded)); 288 } 289 290 final int size = dest.size(); 291 292 dir.changeSize(size); 293 dir.setEntries(dest); 294 } 295 296 @Override flush()297 public void flush() throws IOException { 298 checkWritable(); 299 300 for (FatFile f : entryToFile.values()) { 301 f.flush(); 302 } 303 304 for (FatLfnDirectory d : entryToDirectory.values()) { 305 d.flush(); 306 } 307 308 updateLFN(); 309 dir.flush(); 310 } 311 312 @Override iterator()313 public Iterator<FsDirectoryEntry> iterator() { 314 return new Iterator<FsDirectoryEntry>() { 315 316 final Iterator<FatLfnDirectoryEntry> it = 317 shortNameIndex.values().iterator(); 318 319 @Override 320 public boolean hasNext() { 321 return it.hasNext(); 322 } 323 324 @Override 325 public FsDirectoryEntry next() { 326 return it.next(); 327 } 328 329 /** 330 * @see java.util.Iterator#remove() 331 */ 332 @Override 333 public void remove() { 334 throw new UnsupportedOperationException(); 335 } 336 }; 337 } 338 339 /** 340 * Remove the entry with the given name from this directory. 341 * 342 * @param name the name of the entry to remove 343 * @throws IOException on error removing the entry 344 * @throws IllegalArgumentException on an attempt to remove the dot entries 345 */ 346 @Override remove(String name)347 public void remove(String name) 348 throws IOException, IllegalArgumentException { 349 350 checkWritable(); 351 352 final FatLfnDirectoryEntry entry = getEntry(name); 353 if (entry == null) return; 354 355 unlinkEntry(entry); 356 357 final ClusterChain cc = new ClusterChain( 358 fat, entry.realEntry.getStartCluster(), false); 359 360 cc.setChainLength(0); 361 362 freeUniqueName(name); 363 updateLFN(); 364 } 365 366 /** 367 * Unlinks the specified entry from this directory without actually 368 * deleting it. 369 * 370 * @param e the entry to be unlinked 371 * @see #linkEntry(de.waldheinz.fs.fat.FatLfnDirectoryEntry) 372 */ unlinkEntry(FatLfnDirectoryEntry entry)373 void unlinkEntry(FatLfnDirectoryEntry entry) { 374 final ShortName sn = entry.realEntry.getShortName(); 375 376 if (sn.equals(ShortName.DOT) || sn.equals(ShortName.DOT_DOT)) throw 377 new IllegalArgumentException( 378 "the dot entries can not be removed"); 379 380 final String lowerName = entry.getName().toLowerCase(); 381 382 assert (this.longNameIndex.containsKey(lowerName)); 383 this.longNameIndex.remove(lowerName); 384 385 assert (this.shortNameIndex.containsKey(sn)); 386 this.shortNameIndex.remove(sn); 387 388 if (entry.isFile()) { 389 this.entryToFile.remove(entry.realEntry); 390 } else { 391 this.entryToDirectory.remove(entry.realEntry); 392 } 393 } 394 395 /** 396 * Links the specified entry to this directory, updating the entrie's 397 * short name. 398 * 399 * @param entry the entry to be linked (added) to this directory 400 * @see #unlinkEntry(de.waldheinz.fs.fat.FatLfnDirectoryEntry) 401 */ linkEntry(FatLfnDirectoryEntry entry)402 void linkEntry(FatLfnDirectoryEntry entry) throws IOException { 403 checkUniqueName(entry.getName()); 404 ShortName name; 405 name = this.dbg.generate83BufferNew(entry.getName()); 406 entry.realEntry.setShortName(name); 407 408 this.longNameIndex.put(entry.getName().toLowerCase(), entry); 409 this.shortNameIndex.put(entry.realEntry.getShortName(), entry); 410 411 updateLFN(); 412 } 413 414 @Override toString()415 public String toString() { 416 return getClass().getSimpleName() + 417 " [size=" + shortNameIndex.size() + //NOI18N 418 ", dir=" + dir + "]"; //NOI18N 419 } 420 read(FatDirectoryEntry entry, Fat fat)421 private static ClusterChainDirectory read(FatDirectoryEntry entry, Fat fat) 422 throws IOException { 423 424 if (!entry.isDirectory()) throw 425 new IllegalArgumentException(entry + " is no directory"); 426 427 final ClusterChain chain = new ClusterChain( 428 fat, entry.getStartCluster(), 429 entry.isReadonlyFlag()); 430 431 final ClusterChainDirectory result = 432 new ClusterChainDirectory(chain, false); 433 434 result.read(); 435 return result; 436 } 437 438 } 439