/*
* $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.jfat;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.NoSuchElementException;
import org.jnode.fs.FSDirectory;
import org.jnode.fs.FSDirectoryId;
import org.jnode.fs.FSEntry;
public class FatDirectory extends FatEntry implements FSDirectory, FSDirectoryId {
public static final int MAXENTRIES = 65535; // 2^16-1; fatgen 1.03, page 33
private final FatTable children = new FatTable();
/**
* The map of ID -> entry.
*/
private final Map<String, FatEntry> idMap = new HashMap<String, FatEntry>();
/*
* for root directory
*/
protected FatDirectory(FatFileSystem fs) {
super(fs);
}
/*
* from a directory record;
*/
public FatDirectory(FatFileSystem fs, FatDirectory parent, FatRecord record) {
super(fs, parent, record);
}
/*
* initialize a new created directory
*/
private void initialize() throws IOException {
FatFileSystem fs = getFatFileSystem();
FatDirectory parent = getParent();
FatShortDirEntry entry = getEntry();
FatChain chain = getChain();
chain.allocateAndClear(1);
int parentCluster = parent.isRoot() ? 0 : parent.getEntry().getStartCluster();
int thisCluster = chain.getStartCluster();
FatDotDirEntry dot = new FatDotDirEntry(fs, false, entry, thisCluster);
FatDotDirEntry dotDot = new FatDotDirEntry(fs, true, entry, parentCluster);
setFatDirEntry(dot);
setFatDirEntry(dotDot);
}
/*
* this is actually a FatDirEntry factory and not a standard read method ...
* but how would you call it?
*/
public FatDirEntry getFatDirEntry(int index, boolean allowDeleted) throws IOException {
FatMarshal entry = new FatMarshal(FatDirEntry.LENGTH);
getChain().read(index * entry.length(), entry.getByteBuffer());
return createDirEntry(entry, index, allowDeleted);
}
/**
* Creates a new FAT directory entry.
*
* @param entry the FAT entry buffer.
* @param index the index of the entry.
* @param allowDeleted {@code true} to allow deleted entries to be returned, {@code false} to only allow live
* entries.
* @return the FAT directory entry.
*/
public FatDirEntry createDirEntry(FatMarshal entry, int index, boolean allowDeleted)
throws IOException {
int flag;
FatAttr attr;
flag = entry.getUInt8(0);
attr = new FatAttr(entry.getUInt8(11));
boolean free = flag == FatDirEntry.FREE;
switch (flag) {
case FatDirEntry.EOD:
return new FatDirEntry(getFatFileSystem(), entry, index, flag);
case FatDirEntry.FREE:
if (!allowDeleted) {
return new FatDirEntry(getFatFileSystem(), entry, index, flag);
} else {
// Fall through...
break;
}
case FatDirEntry.INVALID:
throw new IOException("Invalid entry for index: " + index);
}
FatDirEntry fatDirEntry;
// 0xffffffff is the end of long file name marker
if (attr.isLong() || entry.getUInt32(28) == 0xffffffffL) {
fatDirEntry = createLongDirEntry(entry, index);
} else {
fatDirEntry = createShortDirEntry(entry, index);
}
if (free) {
// Still mark deleted entries as deleted.
fatDirEntry.setFreeDirEntry(true);
}
return fatDirEntry;
}
/**
* Creates a new short directory entry.
*
* @param entry the FAT marshal entry.
* @param index the index of the entry.
* @return the short directory entry.
*/
protected FatDirEntry createShortDirEntry(FatMarshal entry, int index) {
return new FatShortDirEntry(getFatFileSystem(), entry, index);
}
/**
* Creates a new long directory entry.
*
* @param entry the FAT entry buffer.
* @param index the index of the entry.
* @return the long directory entry.
*/
protected FatDirEntry createLongDirEntry(FatMarshal entry, int index) throws IOException {
return new FatLongDirEntry(getFatFileSystem(), entry, index);
}
/*
* this instead is a "write" method: it needs a "created" entry
*/
public void setFatDirEntry(FatDirEntry entry) throws IOException {
getChain().write(entry.getIndex() * entry.length(), entry.getByteBuffer());
}
public FatDirEntry[] getFatFreeEntries(int n) throws IOException {
int i = 0;
int index = 0;
FatDirEntry entry = null;
FatDirEntry[] entries = new FatDirEntry[n];
while (i < n) {
try {
entry = getFatDirEntry(index, false);
index++;
} catch (NoSuchElementException ex) {
if (index > MAXENTRIES)
throw new IOException("Directory is full");
getChain().allocateAndClear(1);
// restart the search, fixes infinite loop
// TODO review it for a better solution
i = 0;
index = 0;
continue;
}
if (entry.isFreeDirEntry() || entry.isLastDirEntry()) {
entries[i] = entry;
i++;
} else {
i = 0;
}
}
return entries;
}
@Override
public String getDirectoryId() {
return Integer.toString(getStartCluster());
}
public boolean isDirectory() {
return true;
}
public FSDirectory getDirectory() {
return this;
}
protected FatTable getVisitedChildren() {
return children;
}
public Iterator<FSEntry> iterator() {
return new FatEntriesIterator(children, this, false);
}
/**
* Creates a new directory entry iterator.
*
* @param includeDeleted {@code true} if deleted files and directory entries should be returned, {@code false}
* otherwise.
* @return the iterator.
*/
public Iterator<FSEntry> createIterator(boolean includeDeleted) {
return new FatEntriesIterator(new FatTable(), this, includeDeleted);
}
/*
* used from a FatRootDirectory looking for its label
*/
protected void scanDirectory() {
FatEntriesFactory f = new FatEntriesFactory(this, false);
while (f.hasNextEntry())
f.createNextEntry();
}
public synchronized FSEntry getEntry(String name) {
FatEntry child = children.get(name);
if (child == null) {
FatEntriesFactory f = new FatEntriesFactory(this, false);
while (f.hasNextEntry()) {
FatEntry entry = f.createNextEntry();
if (FatUtils.compareIgnoreCase(entry.getName(), name)) {
child = children.put(entry);
break;
}
}
}
return child;
}
@Override
public FSEntry getEntryById(String id) throws IOException {
FatEntry child = idMap.get(id);
if (child == null) {
FatEntriesFactory f = new FatEntriesFactory(this, true);
while (f.hasNextEntry()) {
FatEntry entry = f.createNextEntry();
idMap.put(entry.getId(), entry);
}
return idMap.get(id);
}
return child;
}
public FatEntry getEntryByShortName(byte[] shortName) {
FatEntry child = null;
FatEntriesFactory f = new FatEntriesFactory(this, false);
while (f.hasNextEntry()) {
FatEntry entry = f.createNextEntry();
if (entry.isShortName(shortName)) {
child = entry;
break;
}
}
return child;
}
public FatEntry getEntryByName(String name) {
FatEntry child = null;
FatEntriesFactory f = new FatEntriesFactory(this, false);
while (f.hasNextEntry()) {
FatEntry entry = f.createNextEntry();
if (FatUtils.compareIgnoreCase(entry.getName(), name)) {
child = entry;
break;
}
}
return child;
}
public boolean collide(byte[] shortName) {
return !(getEntryByShortName(shortName) == null);
}
public boolean collide(String name) {
return !(getEntryByName(name) == null);
}
public boolean isEmpty() {
if (isRoot())
return false;
Iterator<FSEntry> i = iterator();
while (i.hasNext()) {
String name = i.next().getName();
if (!name.equals(".") && !name.equals(".."))
return false;
}
return true;
}
public synchronized FSEntry addFile(String name) throws IOException {
FatName fatName = new FatName(this, name);
if (collide(fatName.getLongName()))
throw new IOException("File [" + fatName.getLongName() + "] already exists");
FatRecord record = new FatRecord(this, fatName);
record.getShortEntry().setArchive();
FatFile file = new FatFile(getFatFileSystem(), this, record);
file.flush();
FatEntry entry = children.put(file);
idMap.put(entry.getId(), entry);
return entry;
}
public synchronized FSEntry addDirectory(String name) throws IOException {
FatFileSystem fs = getFatFileSystem();
FatName fatName = new FatName(this, name);
if (collide(fatName.getLongName()))
throw new IOException("File [" + fatName.getLongName() + "] already exists");
FatRecord record = new FatRecord(this, fatName);
record.getShortEntry().setDirectory();
FatDirectory dir = new FatDirectory(fs, this, record);
dir.initialize();
dir.flush();
FatEntry entry = children.put(dir);
idMap.put(entry.getId(), entry);
return entry;
}
public synchronized void remove(String name) throws IOException {
FatEntry entry = (FatEntry) getEntry(name);
if (entry == null)
throw new FileNotFoundException(name);
if (entry.isFile()) {
FatFile file = (FatFile) entry.getFile();
children.remove(entry);
file.delete();
file.freeAllClusters();
file.flush();
} else {
FatDirectory dir = (FatDirectory) entry;
if (!dir.isEmpty())
throw new UnsupportedOperationException("Directory is not empty: " + name);
children.remove(entry);
dir.delete();
dir.freeAllClusters();
dir.flush();
}
idMap.remove(entry.getId());
}
@Override
public String toString() {
return String.format("FatDirectory [%s] index:%d", getName(), getIndex());
}
public String toDebugString() {
StrWriter out = new StrWriter();
out.println("*******************************************");
out.println("FatDirectory");
out.println("*******************************************");
out.println("Index\t\t" + getIndex());
out.println(toStringValue());
out.println("Visited\t\t" + getVisitedChildren());
out.print("*******************************************");
return out.toString();
}
}