Home | History | Annotate | Download | only in fat
      1 /*
      2  * Copyright (C) 2003-2009 JNode.org
      3  *               2009,2010 Matthias Treydte <mt (at) 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 &lt;waldheinz at gmail.com&gt;
     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 
     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 
     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 
     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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    190     public void setArchiveFlag(boolean archive) throws ReadOnlyException {
    191         checkWritable();
    192 
    193         this.realEntry.setArchiveFlag(archive);
    194     }
    195 
    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 
    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
    237     public String getName() {
    238         checkValid();
    239 
    240         return fileName;
    241     }
    242 
    243     @Override
    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      */
    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
    283     public void setLastModified(long lastModified) {
    284         checkWritable();
    285         realEntry.setLastModified(lastModified);
    286     }
    287 
    288     @Override
    289     public FatFile getFile() throws IOException {
    290         return parent.getFile(realEntry);
    291     }
    292 
    293     @Override
    294     public FatLfnDirectory getDirectory() throws IOException {
    295         return parent.getDirectory(realEntry);
    296     }
    297 
    298     @Override
    299     public String toString() {
    300         return "LFN = " + fileName + " / SFN = " + realEntry.getShortName();
    301     }
    302 
    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
    349     public long getLastModified() throws IOException {
    350         return realEntry.getLastModified();
    351     }
    352 
    353     @Override
    354     public long getCreated() throws IOException {
    355         return realEntry.getCreated();
    356     }
    357 
    358     @Override
    359     public long getLastAccessed() throws IOException {
    360         return realEntry.getLastAccessed();
    361     }
    362 
    363     @Override
    364     public boolean isFile() {
    365         return realEntry.isFile();
    366     }
    367 
    368     @Override
    369     public boolean isDirectory() {
    370         return realEntry.isDirectory();
    371     }
    372 
    373     @Override
    374     public boolean isDirty() {
    375         return realEntry.isDirty();
    376     }
    377 
    378 }
    379