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.FsDirectoryEntry; 24 import de.waldheinz.fs.ReadOnlyException; 25 import java.io.IOException; 26 27 /** 28 * Represents an entry in a {@link FatLfnDirectory}. Besides implementing the 29 * {@link FsDirectoryEntry} interface for FAT file systems, it allows access 30 * to the {@link #setArchiveFlag(boolean) archive}, 31 * {@link #setHiddenFlag(boolean) hidden}, 32 * {@link #setReadOnlyFlag(boolean) read-only} and 33 * {@link #setSystemFlag(boolean) system} flags specifed for the FAT file 34 * system. 35 * 36 * @author Matthias Treydte <waldheinz at gmail.com> 37 * @since 0.6 38 */ 39 public final class FatLfnDirectoryEntry 40 extends AbstractFsObject 41 implements FsDirectoryEntry { 42 43 final FatDirectoryEntry realEntry; 44 45 private FatLfnDirectory parent; 46 private String fileName; 47 FatLfnDirectoryEntry(String name, ShortName sn, FatLfnDirectory parent, boolean directory)48 FatLfnDirectoryEntry(String name, ShortName sn, 49 FatLfnDirectory parent, boolean directory) { 50 51 super(false); 52 53 this.parent = parent; 54 this.fileName = name; 55 56 final long now = System.currentTimeMillis(); 57 this.realEntry = FatDirectoryEntry.create(directory); 58 this.realEntry.setShortName(sn); 59 this.realEntry.setCreated(now); 60 this.realEntry.setLastAccessed(now); 61 } 62 FatLfnDirectoryEntry(FatLfnDirectory parent, FatDirectoryEntry realEntry, String fileName)63 FatLfnDirectoryEntry(FatLfnDirectory parent, 64 FatDirectoryEntry realEntry, String fileName) { 65 66 super(parent.isReadOnly()); 67 68 this.parent = parent; 69 this.realEntry = realEntry; 70 this.fileName = fileName; 71 } 72 extract( FatLfnDirectory dir, int offset, int len)73 static FatLfnDirectoryEntry extract( 74 FatLfnDirectory dir, int offset, int len) { 75 76 final FatDirectoryEntry realEntry = dir.dir.getEntry(offset + len - 1); 77 final String fileName; 78 79 if (len == 1) { 80 /* this is just an old plain 8.3 entry */ 81 fileName = realEntry.getShortName().asSimpleString(); 82 } else { 83 /* stored in reverse order */ 84 final StringBuilder name = new StringBuilder(13 * (len - 1)); 85 86 for (int i = len - 2; i >= 0; i--) { 87 FatDirectoryEntry entry = dir.dir.getEntry(i + offset); 88 name.append(entry.getLfnPart()); 89 } 90 91 fileName = name.toString().trim(); 92 } 93 94 return new FatLfnDirectoryEntry(dir, realEntry, fileName); 95 } 96 97 /** 98 * Returns if this directory entry has the FAT "hidden" flag set. 99 * 100 * @return if this is a hidden directory entry 101 * @see #setHiddenFlag(boolean) 102 */ isHiddenFlag()103 public boolean isHiddenFlag() { 104 return this.realEntry.isHiddenFlag(); 105 } 106 107 /** 108 * Sets the "hidden" flag on this {@code FatLfnDirectoryEntry} to the 109 * specified value. 110 * 111 * @param hidden if this entry should have the hidden flag set 112 * @throws ReadOnlyException if this entry is read-only 113 * @see #isHiddenFlag() 114 */ setHiddenFlag(boolean hidden)115 public void setHiddenFlag(boolean hidden) throws ReadOnlyException { 116 checkWritable(); 117 118 this.realEntry.setHiddenFlag(hidden); 119 } 120 121 /** 122 * Returns if this directory entry has the FAT "system" flag set. 123 * 124 * @return if this is a "system" directory entry 125 * @see #setSystemFlag(boolean) 126 */ isSystemFlag()127 public boolean isSystemFlag() { 128 return this.realEntry.isSystemFlag(); 129 } 130 131 /** 132 * Sets the "system" flag on this {@code FatLfnDirectoryEntry} to the 133 * specified value. 134 * 135 * @param systemEntry if this entry should have the system flag set 136 * @throws ReadOnlyException if this entry is read-only 137 * @see #isSystemFlag() 138 */ setSystemFlag(boolean systemEntry)139 public void setSystemFlag(boolean systemEntry) throws ReadOnlyException { 140 checkWritable(); 141 142 this.realEntry.setSystemFlag(systemEntry); 143 } 144 145 /** 146 * Returns if this directory entry has the FAT "read-only" flag set. This 147 * entry may still modified if {@link #isReadOnly()} returns {@code true}. 148 * 149 * @return if this entry has the read-only flag set 150 * @see #setReadOnlyFlag(boolean) 151 */ isReadOnlyFlag()152 public boolean isReadOnlyFlag() { 153 return this.realEntry.isReadonlyFlag(); 154 } 155 156 /** 157 * Sets the "read only" flag on this {@code FatLfnDirectoryEntry} to the 158 * specified value. This method only modifies the read-only flag as 159 * specified by the FAT file system, which is essentially ignored by the 160 * fat32-lib. The true indicator if it is possible to alter this 161 * 162 * @param readOnly if this entry should be flagged as read only 163 * @throws ReadOnlyException if this entry is read-only as given by 164 * {@link #isReadOnly()} method 165 * @see #isReadOnlyFlag() 166 */ setReadOnlyFlag(boolean readOnly)167 public void setReadOnlyFlag(boolean readOnly) throws ReadOnlyException { 168 checkWritable(); 169 170 this.realEntry.setReadonlyFlag(readOnly); 171 } 172 173 /** 174 * Returns if this directory entry has the FAT "archive" flag set. 175 * 176 * @return if this entry has the archive flag set 177 */ isArchiveFlag()178 public boolean isArchiveFlag() { 179 return this.realEntry.isArchiveFlag(); 180 } 181 182 /** 183 * Sets the "archive" flag on this {@code FatLfnDirectoryEntry} to the 184 * specified value. 185 * 186 * @param archive if this entry should have the archive flag set 187 * @throws ReadOnlyException if this entry is 188 * {@link #isReadOnly() read-only} 189 */ setArchiveFlag(boolean archive)190 public void setArchiveFlag(boolean archive) throws ReadOnlyException { 191 checkWritable(); 192 193 this.realEntry.setArchiveFlag(archive); 194 } 195 totalEntrySize()196 private int totalEntrySize() { 197 int result = (fileName.length() / 13) + 1; 198 199 if ((fileName.length() % 13) != 0) { 200 result++; 201 } 202 203 return result; 204 } 205 compactForm()206 FatDirectoryEntry[] compactForm() { 207 if (this.realEntry.getShortName().equals(ShortName.DOT) || 208 this.realEntry.getShortName().equals(ShortName.DOT_DOT) || 209 this.realEntry.hasShortNameOnly) { 210 /* the dot entries must not have a LFN */ 211 return new FatDirectoryEntry[]{this.realEntry}; 212 } 213 214 final int totalEntrySize = totalEntrySize(); 215 216 final FatDirectoryEntry[] entries = 217 new FatDirectoryEntry[totalEntrySize]; 218 219 final byte checkSum = this.realEntry.getShortName().checkSum(); 220 int j = 0; 221 222 for (int i = totalEntrySize - 2; i > 0; i--) { 223 entries[i] = createPart(fileName.substring(j * 13, j * 13 + 13), 224 j + 1, checkSum, false); 225 j++; 226 } 227 228 entries[0] = createPart(fileName.substring(j * 13), 229 j + 1, checkSum, true); 230 231 entries[totalEntrySize - 1] = this.realEntry; 232 233 return entries; 234 } 235 236 @Override getName()237 public String getName() { 238 checkValid(); 239 240 return fileName; 241 } 242 243 @Override setName(String newName)244 public void setName(String newName) throws IOException { 245 checkWritable(); 246 247 if (!this.parent.isFreeName(newName)) { 248 throw new IOException( 249 "the name \"" + newName + "\" is already in use"); 250 } 251 252 this.parent.unlinkEntry(this); 253 this.fileName = newName; 254 this.parent.linkEntry(this); 255 } 256 257 /** 258 * Moves this entry to a new directory under the specified name. 259 * 260 * @param target the direcrory where this entry should be moved to 261 * @param newName the new name under which this entry will be accessible 262 * in the target directory 263 * @throws IOException on error moving this entry 264 * @throws ReadOnlyException if this directory is read-only 265 */ moveTo(FatLfnDirectory target, String newName)266 public void moveTo(FatLfnDirectory target, String newName) 267 throws IOException, ReadOnlyException { 268 269 checkWritable(); 270 271 if (!target.isFreeName(newName)) { 272 throw new IOException( 273 "the name \"" + newName + "\" is already in use"); 274 } 275 276 this.parent.unlinkEntry(this); 277 this.parent = target; 278 this.fileName = newName; 279 this.parent.linkEntry(this); 280 } 281 282 @Override setLastModified(long lastModified)283 public void setLastModified(long lastModified) { 284 checkWritable(); 285 realEntry.setLastModified(lastModified); 286 } 287 288 @Override getFile()289 public FatFile getFile() throws IOException { 290 return parent.getFile(realEntry); 291 } 292 293 @Override getDirectory()294 public FatLfnDirectory getDirectory() throws IOException { 295 return parent.getDirectory(realEntry); 296 } 297 298 @Override toString()299 public String toString() { 300 return "LFN = " + fileName + " / SFN = " + realEntry.getShortName(); 301 } 302 createPart(String subName, int ordinal, byte checkSum, boolean isLast)303 private static FatDirectoryEntry createPart(String subName, 304 int ordinal, byte checkSum, boolean isLast) { 305 306 final char[] unicodechar = new char[13]; 307 subName.getChars(0, subName.length(), unicodechar, 0); 308 309 for (int i=subName.length(); i < 13; i++) { 310 if (i==subName.length()) { 311 unicodechar[i] = 0x0000; 312 } else { 313 unicodechar[i] = 0xffff; 314 } 315 } 316 317 final byte[] rawData = new byte[FatDirectoryEntry.SIZE]; 318 319 if (isLast) { 320 LittleEndian.setInt8(rawData, 0, ordinal + (1 << 6)); 321 } else { 322 LittleEndian.setInt8(rawData, 0, ordinal); 323 } 324 325 LittleEndian.setInt16(rawData, 1, unicodechar[0]); 326 LittleEndian.setInt16(rawData, 3, unicodechar[1]); 327 LittleEndian.setInt16(rawData, 5, unicodechar[2]); 328 LittleEndian.setInt16(rawData, 7, unicodechar[3]); 329 LittleEndian.setInt16(rawData, 9, unicodechar[4]); 330 LittleEndian.setInt8(rawData, 11, 0x0f); // this is the hidden 331 // attribute tag for 332 // lfn 333 LittleEndian.setInt8(rawData, 12, 0); // reserved 334 LittleEndian.setInt8(rawData, 13, checkSum); // checksum 335 LittleEndian.setInt16(rawData, 14, unicodechar[5]); 336 LittleEndian.setInt16(rawData, 16, unicodechar[6]); 337 LittleEndian.setInt16(rawData, 18, unicodechar[7]); 338 LittleEndian.setInt16(rawData, 20, unicodechar[8]); 339 LittleEndian.setInt16(rawData, 22, unicodechar[9]); 340 LittleEndian.setInt16(rawData, 24, unicodechar[10]); 341 LittleEndian.setInt16(rawData, 26, 0); // sector... unused 342 LittleEndian.setInt16(rawData, 28, unicodechar[11]); 343 LittleEndian.setInt16(rawData, 30, unicodechar[12]); 344 345 return new FatDirectoryEntry(rawData, false); 346 } 347 348 @Override getLastModified()349 public long getLastModified() throws IOException { 350 return realEntry.getLastModified(); 351 } 352 353 @Override getCreated()354 public long getCreated() throws IOException { 355 return realEntry.getCreated(); 356 } 357 358 @Override getLastAccessed()359 public long getLastAccessed() throws IOException { 360 return realEntry.getLastAccessed(); 361 } 362 363 @Override isFile()364 public boolean isFile() { 365 return realEntry.isFile(); 366 } 367 368 @Override isDirectory()369 public boolean isDirectory() { 370 return realEntry.isDirectory(); 371 } 372 373 @Override isDirty()374 public boolean isDirty() { 375 return realEntry.isDirty(); 376 } 377 378 } 379