/*
* Copyright (C) 2006 Steve Ratcliffe
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program 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 General Public License for more details.
*
*
* Author: Steve Ratcliffe
* Create date: 26-Nov-2006
*/
package uk.me.parabola.imgfmt.sys;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import uk.me.parabola.imgfmt.FileExistsException;
import uk.me.parabola.imgfmt.Utils;
import uk.me.parabola.imgfmt.fs.DirectoryEntry;
import uk.me.parabola.imgfmt.fs.ImgChannel;
import uk.me.parabola.log.Logger;
/**
* The directory. There is only one directory and it contains the
* file names and block information. On disk each entry is a
* multiple of the block size.
*
* @author Steve Ratcliffe
*/
class Directory {
private static final Logger log = Logger.getLogger(Directory.class);
//private final FileChannel file;
private ImgChannel chan;
private final BlockManager headerBlockManager;
private final int startEntry;
private long startPos;
// The list of files themselves.
private final Map<String, DirectoryEntry> entries = new LinkedHashMap<String, DirectoryEntry>();
Directory(BlockManager headerBlockManager, int startEntry) {
this.headerBlockManager = headerBlockManager;
this.startEntry = startEntry;
}
/**
* Create a new file in the directory.
*
* @param name The file name. Must be 8+3 characters.
* @param blockManager To allocate blocks for the created file entry.
* @return The new directory entity.
* @throws FileExistsException If the entry already
* exists.
*/
Dirent create(String name, BlockManager blockManager) throws FileExistsException {
// Check to see if it is already there.
if (entries.get(name) != null)
throw new FileExistsException("File " + name + " already exists");
Dirent ent;
if (name.equals(ImgFS.DIRECTORY_FILE_NAME)) {
ent = new HeaderDirent(name, blockManager);
} else {
ent = new Dirent(name, blockManager);
}
addEntry(ent);
return ent;
}
/**
* Initialise the directory for reading the file. The whole directory
* is read in.
*
* @throws IOException If it cannot be read.
*/
void readInit(byte xorByte) throws IOException {
assert chan != null;
ByteBuffer buf = ByteBuffer.allocate(512);
buf.order(ByteOrder.LITTLE_ENDIAN);
chan.position(startPos);
Dirent current = null;
while ((chan.read(buf)) > 0) {
buf.flip();
if(xorByte != 0) {
byte[] bufBytes = buf.array();
for(int i = 0; i < bufBytes.length; ++i)
bufBytes[i] ^= xorByte;
}
int used = buf.get(Dirent.OFF_FILE_USED);
if (used != 1)
continue;
String name = Utils.bytesToString(buf, Dirent.OFF_NAME, Dirent.MAX_FILE_LEN);
String ext = Utils.bytesToString(buf, Dirent.OFF_EXT, Dirent.MAX_EXT_LEN);
log.debug("readinit name", name, ext);
int flag = buf.get(Dirent.OFF_FLAG);
int part = buf.get(Dirent.OFF_FILE_PART) & 0xff;
if (flag == 3 && current == null) {
current = (Dirent) entries.get(ImgFS.DIRECTORY_FILE_NAME);
current.initBlocks(buf);
} else if (part == 0) {
current = create(name + '.' + ext, headerBlockManager);
current.initBlocks(buf);
} else {
assert current != null;
current.initBlocks(buf);
}
buf.clear();
}
}
/**
* Write out the directory to the file. The file should be correctly
* positioned by the caller.
*
* @throws IOException If there is a problem writing out any
* of the directory entries.
*/
public void sync() throws IOException {
// The first entry can't really be written until the rest of the directory is
// so we have to step through once to calculate the size and then again
// to write it out.
int headerEntries = 0;
for (DirectoryEntry dir : entries.values()) {
Dirent ent = (Dirent) dir;
log.debug("ent size", ent.getSize());
int n = ent.numberHeaderBlocks();
headerEntries += n;
}
// Save the current position
long dirPosition = chan.position();
int blockSize = headerBlockManager.getBlockSize();
// Get the number of blocks required for the directory entry representing the header.
// First calculate the number of blocks required for the directory entries.
int headerBlocks = (int) Math.ceil((startEntry + 1.0 + headerEntries) * DirectoryEntry.ENTRY_SIZE / blockSize);
int forHeader = (headerBlocks + DirectoryEntry.ENTRY_SIZE - 1)/DirectoryEntry.ENTRY_SIZE;
log.debug("header blocks needed", forHeader);
// There is nothing really wrong with larger values (perhaps, I don't
// know for sure!) but the code is written to make it 1, so make sure that it is.
assert forHeader == 1;
// Write the blocks that will will contain the header blocks.
chan.position(dirPosition + (long) forHeader * DirectoryEntry.ENTRY_SIZE);
for (DirectoryEntry dir : entries.values()) {
Dirent ent = (Dirent) dir;
if (!ent.isSpecial()) {
log.debug("wrting ", dir.getFullName(), " at ", chan.position());
log.debug("ent size", ent.getSize());
ent.sync(chan);
}
}
long end = (long) blockSize * headerBlockManager.getMaxBlock();
ByteBuffer buf = ByteBuffer.allocate((int) (end - chan.position()));
for (int i = 0; i < buf.capacity(); i++)
buf.put((byte) 0);
buf.flip();
chan.write(buf);
// Now go back and write in the directory entry for the header.
chan.position(dirPosition);
Dirent ent = (Dirent) entries.values().iterator().next();
log.debug("ent header size", ent.getSize());
ent.sync(chan);
}
/**
* Get the entries. Used for listing the directory.
*
* @return A list of the directory entries. They will be in the same
* order as in the file.
*/
public List<DirectoryEntry> getEntries() {
return new ArrayList<DirectoryEntry>(entries.values());
}
/**
* Add an entry to the directory.
*
* @param ent The entry to add.
*/
private void addEntry(DirectoryEntry ent) {
entries.put(ent.getFullName(), ent);
}
public void setFile(ImgChannel chan) {
this.chan = chan;
}
public void setStartPos(long startPos) {
this.startPos = startPos;
}
public DirectoryEntry lookup(String name) {
return entries.get(name);
}
}