Mercurial > projects > ldc
diff tango/example/conduits/FileBucket.d @ 132:1700239cab2e trunk
[svn r136] MAJOR UNSTABLE UPDATE!!!
Initial commit after moving to Tango instead of Phobos.
Lots of bugfixes...
This build is not suitable for most things.
author | lindquist |
---|---|
date | Fri, 11 Jan 2008 17:57:40 +0100 |
parents | |
children |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tango/example/conduits/FileBucket.d Fri Jan 11 17:57:40 2008 +0100 @@ -0,0 +1,319 @@ +module FileBucket; + +private import tango.io.FilePath, + tango.io.FileConduit; + +private import tango.core.Exception; + +/****************************************************************************** + + FileBucket implements a simple mechanism to store and recover a + large quantity of data for the duration of the hosting process. + It is intended to act as a local-cache for a remote data-source, + or as a spillover area for large in-memory cache instances. + + Note that any and all stored data is rendered invalid the moment + a FileBucket object is garbage-collected. + + The implementation follows a fixed-capacity record scheme, where + content can be rewritten in-place until said capacity is reached. + At such time, the altered content is moved to a larger capacity + record at end-of-file, and a hole remains at the prior location. + These holes are not collected, since the lifespan of a FileBucket + is limited to that of the host process. + + All index keys must be unique. Writing to the FileBucket with an + existing key will overwrite any previous content. What follows + is a contrived example: + + --- + char[] text = "this is a test"; + + auto bucket = new FileBucket (new FilePath("bucket.bin"), FileBucket.HalfK); + + // insert some data, and retrieve it again + bucket.put ("a key", text); + char[] b = cast(char[]) bucket.get ("a key"); + + assert (b == text); + bucket.close; + --- + +******************************************************************************/ + +class FileBucket +{ + /********************************************************************** + + Define the capacity (block-size) of each record + + **********************************************************************/ + + struct BlockSize + { + int capacity; + } + + // basic capacity for each record + private FilePath path; + + // basic capacity for each record + private BlockSize block; + + // where content is stored + private FileConduit file; + + // pointers to file records + private Record[char[]] map; + + // current file size + private long fileSize; + + // current file usage + private long waterLine; + + // supported block sizes + public static const BlockSize EighthK = {128-1}, + HalfK = {512-1}, + OneK = {1024*1-1}, + TwoK = {1024*2-1}, + FourK = {1024*4-1}, + EightK = {1024*8-1}, + SixteenK = {1024*16-1}, + ThirtyTwoK = {1024*32-1}, + SixtyFourK = {1024*64-1}; + + + /********************************************************************** + + Construct a FileBucket with the provided path and record- + size. Selecting a record size that roughly matches the + serialized content will limit 'thrashing'. + + **********************************************************************/ + + this (char[] path, BlockSize block) + { + this (new FilePath(path), block); + } + + /********************************************************************** + + Construct a FileBucket with the provided path, record-size, + and inital record count. The latter causes records to be + pre-allocated, saving a certain amount of growth activity. + Selecting a record size that roughly matches the serialized + content will limit 'thrashing'. + + **********************************************************************/ + + this (FilePath path, BlockSize block, uint initialRecords = 100) + { + this.path = path; + this.block = block; + + // open a storage file + file = new FileConduit (path, FileConduit.ReadWriteCreate); + + // set initial file size (can be zero) + fileSize = initialRecords * block.capacity; + file.seek (fileSize); + file.truncate (); + } + + /********************************************************************** + + Return the block-size in use for this FileBucket + + **********************************************************************/ + + int getBufferSize () + { + return block.capacity+1; + } + + /********************************************************************** + + Return where the FileBucket is located + + **********************************************************************/ + + FilePath getFilePath () + { + return path; + } + + /********************************************************************** + + Return the currently populated size of this FileBucket + + **********************************************************************/ + + synchronized long length () + { + return waterLine; + } + + /********************************************************************** + + Return the serialized data for the provided key. Returns + null if the key was not found. + + **********************************************************************/ + + synchronized void[] get (char[] key) + { + Record r = null; + + if (key in map) + { + r = map [key]; + return r.read (this); + } + return null; + } + + /********************************************************************** + + Remove the provided key from this FileBucket. + + **********************************************************************/ + + synchronized void remove (char[] key) + { + map.remove(key); + } + + /********************************************************************** + + Write a serialized block of data, and associate it with + the provided key. All keys must be unique, and it is the + responsibility of the programmer to ensure this. Reusing + an existing key will overwrite previous data. + + Note that data is allowed to grow within the occupied + bucket until it becomes larger than the allocated space. + When this happens, the data is moved to a larger bucket + at the file tail. + + **********************************************************************/ + + synchronized void put (char[] key, void[] data) + { + Record* r = key in map; + + if (r is null) + { + auto rr = new Record; + map [key] = rr; + r = &rr; + } + r.write (this, data, block); + } + + /********************************************************************** + + Close this FileBucket -- all content is lost. + + **********************************************************************/ + + synchronized void close () + { + if (file) + { + file.detach; + file = null; + map = null; + } + } + + /********************************************************************** + + Each Record takes up a number of 'pages' within the file. + The size of these pages is determined by the BlockSize + provided during FileBucket construction. Additional space + at the end of each block is potentially wasted, but enables + content to grow in size without creating a myriad of holes. + + **********************************************************************/ + + private static class Record + { + private long offset; + private int length, + capacity = -1; + + /************************************************************** + + **************************************************************/ + + private static void eof (FileBucket bucket) + { + throw new IOException ("Unexpected EOF in FileBucket '"~bucket.path.toString()~"'"); + } + + /************************************************************** + + This should be protected from thread-contention at + a higher level. + + **************************************************************/ + + void[] read (FileBucket bucket) + { + void[] data = new ubyte [length]; + + bucket.file.seek (offset); + if (bucket.file.read (data) != length) + eof (bucket); + + return data; + } + + /************************************************************** + + This should be protected from thread-contention at + a higher level. + + **************************************************************/ + + void write (FileBucket bucket, void[] data, BlockSize block) + { + length = data.length; + + // create new slot if we exceed capacity + if (length > capacity) + createBucket (bucket, length, block); + + // locate to start of content + bucket.file.seek (offset); + + // write content + if (bucket.file.write (data) != length) + eof (bucket); + } + + /************************************************************** + + **************************************************************/ + + void createBucket (FileBucket bucket, int bytes, BlockSize block) + { + offset = bucket.waterLine; + capacity = (bytes + block.capacity) & ~block.capacity; + + bucket.waterLine += capacity; + if (bucket.waterLine > bucket.fileSize) + { + // grow the filesize + bucket.fileSize = bucket.waterLine * 2; + + // expand the physical file size + bucket.file.seek (bucket.fileSize); + bucket.file.truncate (); + } + } + } +} + +