import std.file; import core.memory; import std.stdio; import std.stream; /** the BlockNo type is used to specify block number. */ typedef ulong BlockNo; /** the DskAddr type is used to specify file byte offset. */ typedef ulong DskAddr; /** A BlockFile is a file of fixed size blocks with a file header. It knows nothing * about the internal structure of those blocks, but each block has a header */ class BlockFile { BufferedFile bf; struct FileHead { uint sig; ushort blockSize; } FileHead fh; /** This is the signature of a BlockFile. It should be overridden by subclasses * I'm using a date (yyyymmdd) ~ a 2 digit sequence #. Descendent classes should * override this value. */ protected uint fhSig = 2009012901; struct BlockHead { ushort sig; BlockNo num; } BlockHead bh; /** The block signature is used to denote the start of a block. It's an arbitrary * value whose only purpose is to facilitate recovery after a crash */ protected ushort bhSig = cast(ushort)314195; string filName; /** Params: * filNam: the name of the file to be used as a string, either in absolute path * form of relative to the current directory. * blkSiz: if this is a new file, then the size of data block (excluding block * header) that will be written in the file. If the file already exists * then this parameter will be ignored. (So check later if it matters * to you!) */ this (in string filNam, in ushort blkSiz) in { assert (filNam.length > 0); } out { assert (bf !is null); assert (bf.readable); assert (bf.writeable); assert (bf.seekable); } body { if (exists (filNam) ) { if (isfile (filNam) ) openExistingFile (filNam); else throw new Exception ("\"" ~ filNam ~ "\n exists on disk but is not a file."); } else { fh.sig = fhSig; fh.blockSize = blkSiz; createFile (filNam); } writefln ("Exiting BlockFile::this"); } ~this() { close; } void close() { if (bf !is null) bf.close; bf = null; } private void createFile (in string filNam) { bf = new BufferedFile (filNam, FileMode.In | FileMode.Out); } DskAddr dskAddr (BlockNo num) in { assert (num > 0); } out (result) { assert (result - 1 <= bf.size); } body { DskAddr d = cast(DskAddr)(fh.sizeof + (num - 1) * fh.blockSize); return d; } private void openExistingFile (in string filNam) { bf = new BufferedFile (filNam, FileMode.In | FileMode.Out); } void position (BlockNo num) { DskAddr d = dskAddr (num); bf.position(d); } private void readFileHead() in { assert (bf.size > fh.sizeof); } body { bf.position (0); auto cnt = bf.readBlock ( cast(void *)&fh, fh.sizeof); if (cnt != fh.sizeof) throw new Exception ("In \"" ~ filName ~ "\"read the wrong # of bytes for " ~ "the file header."); if (fh.sig != fhSig) throw new Exception ("\"" ~ filName ~ "\" is not a BlockFile."); } void readBlock (BlockNo blkNo) { position (blkNo); auto cnt = bf.readBlock (cast(void *)&bh, bh.sizeof); if (cnt != bh.sizeof) throw new Exception ("In \"" ~ filName ~ "\"read the wrong # of bytes for " ~ "the header of block " ~ blkNo.stringof); if (bh.sig != bhSig) throw new Exception ("\"" ~ filName ~ ":blk " ~ blkNo.stringof ~ " the block has a bad signature."); if (bh.num != blkNo) throw new Exception ("\"" ~ filName ~ ":blk " ~ blkNo.stringof ~ " the block has a bad block # == " ~ bh.num.stringof); } private void writeFileHead() { bf.position (0); auto cnt = bf.writeBlock ( cast(void *)&fh, fh.sizeof); if (cnt != fh.sizeof) throw new Exception ("In \"" ~ filName ~ "\"Wrote the wrong # of bytes for " ~ "the file header."); } } void main() { try { BlockFile bf; bf = new BlockFile ("test.bf", 4096); writefln ("before close"); bf.close; bf = null; GC.collect; writefln ("after close"); BlockFile cf = new BlockFile ("test.bf", 4096); writefln ("after second open"); } catch (Exception e) { writefln ("Caught Exception ", e); } }