/*
* 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
);
}
}