/* * $Id$ * * Copyright (C) 2003-2015 JNode.org * * This library is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation; either version 2.1 of the License, or * (at your option) any later version. * * This library is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public * License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library; If not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ package org.jnode.fs.fat; import java.io.IOException; import java.util.Date; import org.jnode.fs.FSAccessRights; import org.jnode.fs.FSDirectory; import org.jnode.fs.FSEntry; import org.jnode.fs.FSEntryCreated; import org.jnode.fs.FSEntryLastAccessed; import org.jnode.fs.FSFile; import org.jnode.fs.spi.UnixFSAccessRights; import org.jnode.fs.util.DosUtils; import org.jnode.util.LittleEndian; import org.jnode.util.NumberUtils; /** * @author epr */ public class FatDirEntry extends FatBasicDirEntry implements FSEntry, FSEntryCreated, FSEntryLastAccessed { /** * The ID for this entry. */ private final String id; /** * Name of this entry */ private String name; /** * Extension of this entry */ private String ext; /** * Has this entry been deleted? */ private boolean deleted; /** * Is this entry not used? */ private boolean unused; /** * Flags of this entry */ private int flags; /** * Time of creation. */ private long created; /** * Time of last modification. */ private long lastModified; /** * Time of last access. */ private long lastAccessed; /** * First cluster of the data of this entry */ private int startCluster; /** * Length in bytes of the data of this entry */ private long length; /** * Has this entry been changed and not yet flushed to disk? */ private boolean _dirty; /** * Directory this entry is a part of */ private final AbstractDirectory parent; /** * access rights of the entry */ private final FSAccessRights rights; public static FatBasicDirEntry fatDirEntryFactory(AbstractDirectory dir, byte[] src, int offset) { int flags = LittleEndian.getUInt8(src, offset + 0x0b); boolean r = (flags & F_READONLY) != 0; boolean h = (flags & F_HIDDEN) != 0; boolean s = (flags & F_SYSTEM) != 0; boolean v = (flags & F_LABEL) != 0; if (r && h && s && v) { // this is a LFN entry, don't need to parse it! return new FatLfnDirEntry(dir, src, offset); } FatDirEntry entry = new FatDirEntry(dir, src, offset); return entry; } /** * Create a new entry * * @param dir */ public FatDirEntry(AbstractDirectory dir) { this(dir, "", ""); } /** * Create a new entry * * @param dir * @param name * @param ext */ public FatDirEntry(AbstractDirectory dir, String name, String ext) { super(dir); this.parent = dir; setName(name); setExt(ext); this.flags = F_ARCHIVE; this.created = this.lastModified = this.lastAccessed = System.currentTimeMillis(); this._dirty = false; this.rights = new UnixFSAccessRights(getFileSystem()); id = name; } /** * Create a new entry from a FAT directory image. * * @param dir * @param src * @param offset */ public FatDirEntry(AbstractDirectory dir, byte[] src, int offset) { super(dir, src, offset); this.parent = dir; id = Integer.toString(offset / FatConstants.DIR_ENTRY_SIZE); unused = (src[offset] == 0); deleted = (LittleEndian.getUInt8(src, offset) == 0xe5); char[] nameArr = new char[8]; for (int i = 0; i < nameArr.length; i++) { nameArr[i] = (char) LittleEndian.getUInt8(src, offset + i); } if (LittleEndian.getUInt8(src, offset) == 0x05) { nameArr[0] = (char) 0xe5; } setName(new String(nameArr).trim()); char[] extArr = new char[3]; for (int i = 0; i < extArr.length; i++) { extArr[i] = (char) LittleEndian.getUInt8(src, offset + 0x08 + i); } setExt(new String(extArr).trim()); this.flags = LittleEndian.getUInt8(src, offset + 0x0b); this.created = DosUtils.decodeDateTime(LittleEndian.getUInt16(src, offset + 0x10), LittleEndian.getUInt16(src, offset + 0x0e)); this.lastModified = DosUtils.decodeDateTime(LittleEndian.getUInt16(src, offset + 0x18), LittleEndian.getUInt16(src, offset + 0x16)); this.lastAccessed = DosUtils.decodeDateTime(LittleEndian.getUInt16(src, offset + 0x12), 0); // time not stored this.startCluster = LittleEndian.getUInt16(src, offset + 0x1a); this.length = LittleEndian.getUInt32(src, offset + 0x1c); this._dirty = false; this.rights = new UnixFSAccessRights(getFileSystem()); } /** * Returns the attribute. * * @return int */ public int getFlags() { return flags; } public long getCreated() { return created; } public long getLastModified() { return lastModified; } public long getLastAccessed() { return lastAccessed; } /** * Returns the deleted. * * @return boolean */ public boolean isDeleted() { return deleted; } /** * Returns the ext. * * @return String */ public String getExt() { return ext; } @Override public String getId() { return id; } public String getName() { if (ext.length() > 0) { return name + "." + ext; } else { return name; } } /** * Returns the length. * * @return long */ public long getLength() { return length; } /** * Returns the name. * * @return String */ public String getNameOnly() { return name; } /** * Returns the startCluster. * * @return int */ public int getStartCluster() { return startCluster; } /** * Returns the unused. * * @return boolean */ public boolean isUnused() { return unused; } /** * Sets the flags. * * @param flags */ public void setFlags(int flags) { this.flags = flags; setDirty(); } public void setCreated(long created) { this.created = created; setDirty(); } public void setLastModified(long lastModified) { this.lastModified = lastModified; setDirty(); } public void setLastAccessed(long lastAccessed) { this.lastAccessed = lastAccessed; setDirty(); } /** * Sets the deleted. * * @param deleted The deleted to set */ public void setDeleted(boolean deleted) { this.deleted = deleted; setDirty(); } /** * Sets the ext. * * @param ext The ext to set */ public void setExt(String ext) { FatUtils.checkValidExt(ext); this.ext = ext; setDirty(); } /** * Updates the length of the entry. This method is called by * FatFile.setLength. * * @param newLength The length to set */ public synchronized void updateLength(long newLength) { // System.out.println("updateLength(" + newLength + ") on " + // getName()); this.length = newLength; setDirty(); } /** * Gets the single instance of the file connected to this entry. Returns * null if the file is 0 bytes long * * @return File */ public FSFile getFile() throws IOException { if (isFile()) { return getFatFile(); } else { throw new IOException("Not a file"); } } /** * Gets the directory this entry refers to. This method can only be called * if <code>isDirectory</code> returns true. */ public FSDirectory getDirectory() throws IOException { if (isDirectory()) { return getFatFile().getDirectory(); } else { throw new IOException("Not a directory"); } } /** * Gets the single instance of the file connected to this entry. Returns * null if the file is 0 bytes long * * @return File */ public FatFile getFatFile() { return getFatFileSystem().getFile(this); } /** * Sets the name. * * @param name The name to set */ public void setName(String name) { FatUtils.checkValidName(name); this.name = name; setDirty(); } /** * Sets the startCluster. * * @param startCluster The startCluster to set */ protected void setStartCluster(int startCluster) { this.startCluster = startCluster; setDirty(); } /** * Sets the unused. * * @param unused The unused to set */ public void setUnused(boolean unused) { this.unused = unused; setDirty(); } public boolean isReadonly() { return ((flags & F_READONLY) != 0); } public void setReadonly() { setFlags(flags | F_READONLY); } public boolean isHidden() { return ((flags & F_HIDDEN) != 0); } public void setHidden() { setFlags(flags | F_HIDDEN); } public boolean isSystem() { return ((flags & F_SYSTEM) != 0); } public void setSystem() { setFlags(flags | F_SYSTEM); } public boolean isLabel() { return ((flags & F_LABEL) != 0); } public void setLabel() { setFlags(F_LABEL); } /** * Does this entry refer to a file? * * @see org.jnode.fs.FSEntry#isFile() */ public boolean isFile() { return (!(isDirectory() || isLabel())); } /** * Does this entry refer to a directory? * * @see org.jnode.fs.FSEntry#isDirectory() */ public boolean isDirectory() { return ((flags & F_DIRECTORY) != 0); } public void setDirectory() { setFlags(F_DIRECTORY); } public boolean isArchive() { return ((flags & F_ARCHIVE) != 0); } public void setArchive() { setFlags(flags | F_ARCHIVE); } /** * Write my contents to the given byte-array * * @param dest * @param offset */ public void write(byte[] dest, int offset) { // System.out.println("FatDir entry write at" + offset); if (unused) { dest[offset] = 0; } else if (deleted) { dest[offset] = (byte) 0xe5; } for (int i = 0; i < 8; i++) { char ch; if (i < name.length()) { ch = Character.toUpperCase(name.charAt(i)); if (ch == 0xe5) { ch = (char) 0x05; } } else { ch = ' '; } dest[offset + i] = (byte) ch; } for (int i = 0; i < 3; i++) { char ch; if (i < ext.length()) { ch = Character.toUpperCase(ext.charAt(i)); } else { ch = ' '; } dest[offset + 0x08 + i] = (byte) ch; } LittleEndian.setInt8(dest, offset + 0x0b, flags); LittleEndian.setInt16(dest, offset + 0x0e, DosUtils.encodeTime(created)); LittleEndian.setInt16(dest, offset + 0x10, DosUtils.encodeDate(created)); LittleEndian.setInt16(dest, offset + 0x12, DosUtils.encodeDate(lastAccessed)); LittleEndian.setInt16(dest, offset + 0x16, DosUtils.encodeTime(lastModified)); LittleEndian.setInt16(dest, offset + 0x18, DosUtils.encodeDate(lastModified)); LittleEndian.setInt16(dest, offset + 0x1a, startCluster); LittleEndian.setInt32(dest, offset + 0x1c, (int) length); this._dirty = false; } public String toString() { StringBuilder b = new StringBuilder(64); b.append(getName()); b.append(" attr="); if (isReadonly()) { b.append('R'); } if (isHidden()) { b.append('H'); } if (isSystem()) { b.append('S'); } if (isLabel()) { b.append('L'); } if (isDirectory()) { b.append('D'); } if (isArchive()) { b.append('A'); } b.append("(0x"); b.append(NumberUtils.hex(flags, 2)); b.append(")"); b.append(" created="); b.append(new Date(getCreated())); b.append(" lastModified="); b.append(new Date(getLastModified())); b.append(" lastAccessed="); b.append(new Date(getLastAccessed())); b.append(" startCluster="); b.append(getStartCluster()); b.append(" length="); b.append(getLength()); if (deleted) { b.append(" deleted"); } return b.toString(); } /** * Returns the dirty. * * @return boolean */ public final boolean isDirty() { return _dirty; } protected final void setDirty() { this._dirty = true; parent.setDirty(); } /** * @return The directory this entry belongs to. */ public FSDirectory getParent() { return parent; } /** * Gets the access rights for this entry. * * @throws IOException */ public FSAccessRights getAccessRights() throws IOException { return rights; } }