comparison 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
comparison
equal deleted inserted replaced
131:5825d48b27d1 132:1700239cab2e
1 module FileBucket;
2
3 private import tango.io.FilePath,
4 tango.io.FileConduit;
5
6 private import tango.core.Exception;
7
8 /******************************************************************************
9
10 FileBucket implements a simple mechanism to store and recover a
11 large quantity of data for the duration of the hosting process.
12 It is intended to act as a local-cache for a remote data-source,
13 or as a spillover area for large in-memory cache instances.
14
15 Note that any and all stored data is rendered invalid the moment
16 a FileBucket object is garbage-collected.
17
18 The implementation follows a fixed-capacity record scheme, where
19 content can be rewritten in-place until said capacity is reached.
20 At such time, the altered content is moved to a larger capacity
21 record at end-of-file, and a hole remains at the prior location.
22 These holes are not collected, since the lifespan of a FileBucket
23 is limited to that of the host process.
24
25 All index keys must be unique. Writing to the FileBucket with an
26 existing key will overwrite any previous content. What follows
27 is a contrived example:
28
29 ---
30 char[] text = "this is a test";
31
32 auto bucket = new FileBucket (new FilePath("bucket.bin"), FileBucket.HalfK);
33
34 // insert some data, and retrieve it again
35 bucket.put ("a key", text);
36 char[] b = cast(char[]) bucket.get ("a key");
37
38 assert (b == text);
39 bucket.close;
40 ---
41
42 ******************************************************************************/
43
44 class FileBucket
45 {
46 /**********************************************************************
47
48 Define the capacity (block-size) of each record
49
50 **********************************************************************/
51
52 struct BlockSize
53 {
54 int capacity;
55 }
56
57 // basic capacity for each record
58 private FilePath path;
59
60 // basic capacity for each record
61 private BlockSize block;
62
63 // where content is stored
64 private FileConduit file;
65
66 // pointers to file records
67 private Record[char[]] map;
68
69 // current file size
70 private long fileSize;
71
72 // current file usage
73 private long waterLine;
74
75 // supported block sizes
76 public static const BlockSize EighthK = {128-1},
77 HalfK = {512-1},
78 OneK = {1024*1-1},
79 TwoK = {1024*2-1},
80 FourK = {1024*4-1},
81 EightK = {1024*8-1},
82 SixteenK = {1024*16-1},
83 ThirtyTwoK = {1024*32-1},
84 SixtyFourK = {1024*64-1};
85
86
87 /**********************************************************************
88
89 Construct a FileBucket with the provided path and record-
90 size. Selecting a record size that roughly matches the
91 serialized content will limit 'thrashing'.
92
93 **********************************************************************/
94
95 this (char[] path, BlockSize block)
96 {
97 this (new FilePath(path), block);
98 }
99
100 /**********************************************************************
101
102 Construct a FileBucket with the provided path, record-size,
103 and inital record count. The latter causes records to be
104 pre-allocated, saving a certain amount of growth activity.
105 Selecting a record size that roughly matches the serialized
106 content will limit 'thrashing'.
107
108 **********************************************************************/
109
110 this (FilePath path, BlockSize block, uint initialRecords = 100)
111 {
112 this.path = path;
113 this.block = block;
114
115 // open a storage file
116 file = new FileConduit (path, FileConduit.ReadWriteCreate);
117
118 // set initial file size (can be zero)
119 fileSize = initialRecords * block.capacity;
120 file.seek (fileSize);
121 file.truncate ();
122 }
123
124 /**********************************************************************
125
126 Return the block-size in use for this FileBucket
127
128 **********************************************************************/
129
130 int getBufferSize ()
131 {
132 return block.capacity+1;
133 }
134
135 /**********************************************************************
136
137 Return where the FileBucket is located
138
139 **********************************************************************/
140
141 FilePath getFilePath ()
142 {
143 return path;
144 }
145
146 /**********************************************************************
147
148 Return the currently populated size of this FileBucket
149
150 **********************************************************************/
151
152 synchronized long length ()
153 {
154 return waterLine;
155 }
156
157 /**********************************************************************
158
159 Return the serialized data for the provided key. Returns
160 null if the key was not found.
161
162 **********************************************************************/
163
164 synchronized void[] get (char[] key)
165 {
166 Record r = null;
167
168 if (key in map)
169 {
170 r = map [key];
171 return r.read (this);
172 }
173 return null;
174 }
175
176 /**********************************************************************
177
178 Remove the provided key from this FileBucket.
179
180 **********************************************************************/
181
182 synchronized void remove (char[] key)
183 {
184 map.remove(key);
185 }
186
187 /**********************************************************************
188
189 Write a serialized block of data, and associate it with
190 the provided key. All keys must be unique, and it is the
191 responsibility of the programmer to ensure this. Reusing
192 an existing key will overwrite previous data.
193
194 Note that data is allowed to grow within the occupied
195 bucket until it becomes larger than the allocated space.
196 When this happens, the data is moved to a larger bucket
197 at the file tail.
198
199 **********************************************************************/
200
201 synchronized void put (char[] key, void[] data)
202 {
203 Record* r = key in map;
204
205 if (r is null)
206 {
207 auto rr = new Record;
208 map [key] = rr;
209 r = &rr;
210 }
211 r.write (this, data, block);
212 }
213
214 /**********************************************************************
215
216 Close this FileBucket -- all content is lost.
217
218 **********************************************************************/
219
220 synchronized void close ()
221 {
222 if (file)
223 {
224 file.detach;
225 file = null;
226 map = null;
227 }
228 }
229
230 /**********************************************************************
231
232 Each Record takes up a number of 'pages' within the file.
233 The size of these pages is determined by the BlockSize
234 provided during FileBucket construction. Additional space
235 at the end of each block is potentially wasted, but enables
236 content to grow in size without creating a myriad of holes.
237
238 **********************************************************************/
239
240 private static class Record
241 {
242 private long offset;
243 private int length,
244 capacity = -1;
245
246 /**************************************************************
247
248 **************************************************************/
249
250 private static void eof (FileBucket bucket)
251 {
252 throw new IOException ("Unexpected EOF in FileBucket '"~bucket.path.toString()~"'");
253 }
254
255 /**************************************************************
256
257 This should be protected from thread-contention at
258 a higher level.
259
260 **************************************************************/
261
262 void[] read (FileBucket bucket)
263 {
264 void[] data = new ubyte [length];
265
266 bucket.file.seek (offset);
267 if (bucket.file.read (data) != length)
268 eof (bucket);
269
270 return data;
271 }
272
273 /**************************************************************
274
275 This should be protected from thread-contention at
276 a higher level.
277
278 **************************************************************/
279
280 void write (FileBucket bucket, void[] data, BlockSize block)
281 {
282 length = data.length;
283
284 // create new slot if we exceed capacity
285 if (length > capacity)
286 createBucket (bucket, length, block);
287
288 // locate to start of content
289 bucket.file.seek (offset);
290
291 // write content
292 if (bucket.file.write (data) != length)
293 eof (bucket);
294 }
295
296 /**************************************************************
297
298 **************************************************************/
299
300 void createBucket (FileBucket bucket, int bytes, BlockSize block)
301 {
302 offset = bucket.waterLine;
303 capacity = (bytes + block.capacity) & ~block.capacity;
304
305 bucket.waterLine += capacity;
306 if (bucket.waterLine > bucket.fileSize)
307 {
308 // grow the filesize
309 bucket.fileSize = bucket.waterLine * 2;
310
311 // expand the physical file size
312 bucket.file.seek (bucket.fileSize);
313 bucket.file.truncate ();
314 }
315 }
316 }
317 }
318
319