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 &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 
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