Mercurial > projects > ldc
comparison tango/tango/io/compress/ZlibStream.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 /******************************************************************************* | |
2 | |
3 copyright: Copyright (C) 2007 Daniel Keep. All rights reserved. | |
4 | |
5 license: BSD style: $(LICENSE) | |
6 | |
7 version: Initial release: July 2007 | |
8 | |
9 author: Daniel Keep | |
10 | |
11 history: Added support for "window bits", needed for Zip support. | |
12 | |
13 *******************************************************************************/ | |
14 | |
15 module tango.io.compress.ZlibStream; | |
16 | |
17 private import tango.io.compress.c.zlib; | |
18 | |
19 private import tango.stdc.stringz : fromUtf8z; | |
20 | |
21 private import tango.core.Exception : IOException; | |
22 | |
23 private import tango.io.Conduit : InputFilter, OutputFilter; | |
24 | |
25 private import tango.io.model.IConduit : InputStream, OutputStream, IConduit; | |
26 | |
27 | |
28 /* This constant controls the size of the input/output buffers we use | |
29 * internally. This should be a fairly sane value (it's suggested by the zlib | |
30 * documentation), that should only need changing for memory-constrained | |
31 * platforms/use cases. | |
32 * | |
33 * An alternative would be to make the chunk size a template parameter to the | |
34 * filters themselves, but Tango already has more than enough template | |
35 * parameters getting in the way :) | |
36 */ | |
37 | |
38 private const CHUNKSIZE = 256 * 1024; | |
39 | |
40 /******************************************************************************* | |
41 | |
42 This input filter can be used to perform decompression of zlib streams. | |
43 | |
44 *******************************************************************************/ | |
45 | |
46 class ZlibInput : InputFilter | |
47 { | |
48 private | |
49 { | |
50 /* Used to make sure we don't try to perform operations on a dead | |
51 * stream. */ | |
52 bool zs_valid = false; | |
53 | |
54 z_stream zs; | |
55 ubyte[] in_chunk; | |
56 } | |
57 | |
58 /*************************************************************************** | |
59 | |
60 Constructs a new zlib decompression filter. You need to pass in the | |
61 stream that the decompression filter will read from. If you are using | |
62 this filter with a conduit, the idiom to use is: | |
63 | |
64 --- | |
65 auto input = new ZlibInput(myConduit.input)); | |
66 input.read(myContent); | |
67 --- | |
68 | |
69 The optional windowBits parameter is the base two logarithm of the | |
70 window size, and should be in the range 8-15, defaulting to 15 if not | |
71 specified. Additionally, the windowBits parameter may be negative to | |
72 indicate that zlib should omit the standard zlib header and trailer, | |
73 with the window size being -windowBits. | |
74 | |
75 ***************************************************************************/ | |
76 | |
77 this(InputStream stream) | |
78 { | |
79 super (stream); | |
80 in_chunk = new ubyte[CHUNKSIZE]; | |
81 | |
82 // Allocate inflate state | |
83 with( zs ) | |
84 { | |
85 zalloc = null; | |
86 zfree = null; | |
87 opaque = null; | |
88 avail_in = 0; | |
89 next_in = null; | |
90 } | |
91 | |
92 auto ret = inflateInit(&zs); | |
93 if( ret != Z_OK ) | |
94 throw new ZlibException(ret); | |
95 | |
96 zs_valid = true; | |
97 } | |
98 | |
99 /// ditto | |
100 this(InputStream stream, int windowBits) | |
101 { | |
102 super (stream); | |
103 in_chunk = new ubyte[CHUNKSIZE]; | |
104 | |
105 // Allocate inflate state | |
106 with( zs ) | |
107 { | |
108 zalloc = null; | |
109 zfree = null; | |
110 opaque = null; | |
111 avail_in = 0; | |
112 next_in = null; | |
113 } | |
114 | |
115 auto ret = inflateInit2(&zs, windowBits); | |
116 if( ret != Z_OK ) | |
117 throw new ZlibException(ret); | |
118 | |
119 zs_valid = true; | |
120 } | |
121 | |
122 ~this() | |
123 { | |
124 if( zs_valid ) | |
125 kill_zs(); | |
126 } | |
127 | |
128 /*************************************************************************** | |
129 | |
130 Decompresses data from the underlying conduit into a target array. | |
131 | |
132 Returns the number of bytes stored into dst, which may be less than | |
133 requested. | |
134 | |
135 ***************************************************************************/ | |
136 | |
137 override uint read(void[] dst) | |
138 { | |
139 if( !zs_valid ) | |
140 return IConduit.Eof; | |
141 | |
142 // Check to see if we've run out of input data. If we have, get some | |
143 // more. | |
144 if( zs.avail_in == 0 ) | |
145 { | |
146 auto len = host.read(in_chunk); | |
147 if( len == IConduit.Eof ) | |
148 return IConduit.Eof; | |
149 | |
150 zs.avail_in = len; | |
151 zs.next_in = in_chunk.ptr; | |
152 } | |
153 | |
154 // We'll tell zlib to inflate straight into the target array. | |
155 zs.avail_out = dst.length; | |
156 zs.next_out = cast(ubyte*)dst.ptr; | |
157 auto ret = inflate(&zs, Z_NO_FLUSH); | |
158 | |
159 switch( ret ) | |
160 { | |
161 case Z_NEED_DICT: | |
162 // Whilst not technically an error, this should never happen | |
163 // for general-use code, so treat it as an error. | |
164 case Z_DATA_ERROR: | |
165 case Z_MEM_ERROR: | |
166 kill_zs(); | |
167 throw new ZlibException(ret); | |
168 | |
169 case Z_STREAM_END: | |
170 // zlib stream is finished; kill the stream so we don't try to | |
171 // read from it again. | |
172 kill_zs(); | |
173 break; | |
174 | |
175 default: | |
176 } | |
177 | |
178 return dst.length - zs.avail_out; | |
179 } | |
180 | |
181 /*************************************************************************** | |
182 | |
183 Clear any buffered content. No-op. | |
184 | |
185 ***************************************************************************/ | |
186 | |
187 override InputStream clear() | |
188 { | |
189 check_valid(); | |
190 | |
191 // TODO: What should this method do? We don't do any heap allocation, | |
192 // so there's really nothing to clear... For now, just invalidate the | |
193 // stream... | |
194 kill_zs(); | |
195 | |
196 super.clear(); | |
197 return this; | |
198 } | |
199 | |
200 // This function kills the stream: it deallocates the internal state, and | |
201 // unsets the zs_valid flag. | |
202 private void kill_zs() | |
203 { | |
204 check_valid(); | |
205 | |
206 inflateEnd(&zs); | |
207 zs_valid = false; | |
208 } | |
209 | |
210 // Asserts that the stream is still valid and usable (except that this | |
211 // check doesn't get elided with -release). | |
212 private void check_valid() | |
213 { | |
214 if( !zs_valid ) | |
215 throw new ZlibClosedException; | |
216 } | |
217 } | |
218 | |
219 /******************************************************************************* | |
220 | |
221 This output filter can be used to perform compression of data into a zlib | |
222 stream. | |
223 | |
224 *******************************************************************************/ | |
225 | |
226 class ZlibOutput : OutputFilter | |
227 { | |
228 /*************************************************************************** | |
229 | |
230 This enumeration represents several pre-defined compression levels. | |
231 | |
232 None instructs zlib to perform no compression whatsoever, and simply | |
233 store the data stream. Note that this actually expands the stream | |
234 slightly to accommodate the zlib stream metadata. | |
235 | |
236 Fast instructs zlib to perform a minimal amount of compression, Best | |
237 indicates that you want the maximum level of compression and Normal | |
238 (the default level) is a compromise between the two. The exact | |
239 compression level Normal represents is determined by the underlying | |
240 zlib library, but is typically level 6. | |
241 | |
242 Any integer between -1 and 9 inclusive may be used as a level, | |
243 although the symbols in this enumeration should suffice for most | |
244 use-cases. | |
245 | |
246 ***************************************************************************/ | |
247 | |
248 enum Level : int | |
249 { | |
250 Normal = -1, | |
251 None = 0, | |
252 Fast = 1, | |
253 Best = 9 | |
254 } | |
255 | |
256 private | |
257 { | |
258 bool zs_valid = false; | |
259 z_stream zs; | |
260 ubyte[] out_chunk; | |
261 size_t _written = 0; | |
262 } | |
263 | |
264 /*************************************************************************** | |
265 | |
266 Constructs a new zlib compression filter. You need to pass in the | |
267 stream that the compression filter will write to. If you are using | |
268 this filter with a conduit, the idiom to use is: | |
269 | |
270 --- | |
271 auto output = new ZlibOutput(myConduit.output); | |
272 output.write(myContent); | |
273 --- | |
274 | |
275 The optional windowBits parameter is the base two logarithm of the | |
276 window size, and should be in the range 8-15, defaulting to 15 if not | |
277 specified. Additionally, the windowBits parameter may be negative to | |
278 indicate that zlib should omit the standard zlib header and trailer, | |
279 with the window size being -windowBits. | |
280 | |
281 ***************************************************************************/ | |
282 | |
283 this(OutputStream stream, Level level = Level.Normal) | |
284 { | |
285 super(stream); | |
286 out_chunk = new ubyte[CHUNKSIZE]; | |
287 | |
288 // Allocate deflate state | |
289 with( zs ) | |
290 { | |
291 zalloc = null; | |
292 zfree = null; | |
293 opaque = null; | |
294 } | |
295 | |
296 auto ret = deflateInit(&zs, level); | |
297 if( ret != Z_OK ) | |
298 throw new ZlibException(ret); | |
299 | |
300 zs_valid = true; | |
301 } | |
302 | |
303 /// ditto | |
304 this(OutputStream stream, Level level, int windowBits) | |
305 { | |
306 super(stream); | |
307 out_chunk = new ubyte[CHUNKSIZE]; | |
308 | |
309 // Allocate deflate state | |
310 with( zs ) | |
311 { | |
312 zalloc = null; | |
313 zfree = null; | |
314 opaque = null; | |
315 } | |
316 | |
317 auto ret = deflateInit2(&zs, level, Z_DEFLATED, windowBits, 8, | |
318 Z_DEFAULT_STRATEGY); | |
319 if( ret != Z_OK ) | |
320 throw new ZlibException(ret); | |
321 | |
322 zs_valid = true; | |
323 } | |
324 | |
325 ~this() | |
326 { | |
327 if( zs_valid ) | |
328 kill_zs(); | |
329 } | |
330 | |
331 /*************************************************************************** | |
332 | |
333 Compresses the given data to the underlying conduit. | |
334 | |
335 Returns the number of bytes from src that were compressed; write | |
336 should always consume all data provided to it, although it may not be | |
337 immediately written to the underlying output stream. | |
338 | |
339 ***************************************************************************/ | |
340 | |
341 override uint write(void[] src) | |
342 { | |
343 check_valid(); | |
344 scope(failure) kill_zs(); | |
345 | |
346 zs.avail_in = src.length; | |
347 zs.next_in = cast(ubyte*)src.ptr; | |
348 | |
349 do | |
350 { | |
351 zs.avail_out = out_chunk.length; | |
352 zs.next_out = out_chunk.ptr; | |
353 | |
354 auto ret = deflate(&zs, Z_NO_FLUSH); | |
355 if( ret == Z_STREAM_ERROR ) | |
356 throw new ZlibException(ret); | |
357 | |
358 // Push the compressed bytes out to the stream, until it's either | |
359 // written them all, or choked. | |
360 auto have = out_chunk.length-zs.avail_out; | |
361 auto out_buffer = out_chunk[0..have]; | |
362 do | |
363 { | |
364 auto w = host.write(out_buffer); | |
365 if( w == IConduit.Eof ) | |
366 return w; | |
367 | |
368 out_buffer = out_buffer[w..$]; | |
369 _written += w; | |
370 } | |
371 while( out_buffer.length > 0 ); | |
372 } | |
373 // Loop while we are still using up the whole output buffer | |
374 while( zs.avail_out == 0 ); | |
375 | |
376 assert( zs.avail_in == 0, "failed to compress all provided data" ); | |
377 | |
378 return src.length; | |
379 } | |
380 | |
381 /*************************************************************************** | |
382 | |
383 This read-only property returns the number of compressed bytes that | |
384 have been written to the underlying stream. Following a call to | |
385 either close or commit, this will contain the total compressed size of | |
386 the input data stream. | |
387 | |
388 ***************************************************************************/ | |
389 | |
390 size_t written() | |
391 { | |
392 return _written; | |
393 } | |
394 | |
395 /*************************************************************************** | |
396 | |
397 commit the output | |
398 | |
399 ***************************************************************************/ | |
400 | |
401 override void close() | |
402 { | |
403 // Only commit if the stream is still open. | |
404 if( zs_valid ) commit; | |
405 | |
406 super.close; | |
407 } | |
408 | |
409 /*************************************************************************** | |
410 | |
411 Purge any buffered content. Calling this will implicitly end the zlib | |
412 stream, so it should not be called until you are finished compressing | |
413 data. Any calls to either write or commit after a compression filter | |
414 has been committed will throw an exception. | |
415 | |
416 ***************************************************************************/ | |
417 | |
418 void commit() | |
419 { | |
420 check_valid(); | |
421 scope(failure) kill_zs(); | |
422 | |
423 zs.avail_in = 0; | |
424 zs.next_in = null; | |
425 | |
426 bool finished = false; | |
427 | |
428 do | |
429 { | |
430 zs.avail_out = out_chunk.length; | |
431 zs.next_out = out_chunk.ptr; | |
432 | |
433 auto ret = deflate(&zs, Z_FINISH); | |
434 switch( ret ) | |
435 { | |
436 case Z_OK: | |
437 // Keep going | |
438 break; | |
439 | |
440 case Z_STREAM_END: | |
441 // We're done! | |
442 finished = true; | |
443 break; | |
444 | |
445 default: | |
446 throw new ZlibException(ret); | |
447 } | |
448 | |
449 auto have = out_chunk.length - zs.avail_out; | |
450 auto out_buffer = out_chunk[0..have]; | |
451 if( have > 0 ) | |
452 { | |
453 do | |
454 { | |
455 auto w = host.write(out_buffer); | |
456 if( w == IConduit.Eof ) | |
457 return w; | |
458 | |
459 out_buffer = out_buffer[w..$]; | |
460 _written += w; | |
461 } | |
462 while( out_buffer.length > 0 ); | |
463 } | |
464 } | |
465 while( !finished ); | |
466 | |
467 kill_zs(); | |
468 } | |
469 | |
470 // This function kills the stream: it deallocates the internal state, and | |
471 // unsets the zs_valid flag. | |
472 private void kill_zs() | |
473 { | |
474 check_valid(); | |
475 | |
476 deflateEnd(&zs); | |
477 zs_valid = false; | |
478 } | |
479 | |
480 // Asserts that the stream is still valid and usable (except that this | |
481 // check doesn't get elided with -release). | |
482 private void check_valid() | |
483 { | |
484 if( !zs_valid ) | |
485 throw new ZlibClosedException; | |
486 } | |
487 } | |
488 | |
489 /******************************************************************************* | |
490 | |
491 This exception is thrown if you attempt to perform a read, write or flush | |
492 operation on a closed zlib filter stream. This can occur if the input | |
493 stream has finished, or an output stream was flushed. | |
494 | |
495 *******************************************************************************/ | |
496 | |
497 class ZlibClosedException : IOException | |
498 { | |
499 this() | |
500 { | |
501 super("cannot operate on closed zlib stream"); | |
502 } | |
503 } | |
504 | |
505 /******************************************************************************* | |
506 | |
507 This exception is thrown when an error occurs in the underlying zlib | |
508 library. Where possible, it will indicate both the name of the error, and | |
509 any textural message zlib has provided. | |
510 | |
511 *******************************************************************************/ | |
512 | |
513 class ZlibException : IOException | |
514 { | |
515 this(int code) | |
516 { | |
517 super(codeName(code)); | |
518 } | |
519 | |
520 this(int code, char* msg) | |
521 { | |
522 super(codeName(code)~": "~fromUtf8z(msg)); | |
523 } | |
524 | |
525 protected char[] codeName(int code) | |
526 { | |
527 char[] name; | |
528 | |
529 switch( code ) | |
530 { | |
531 case Z_OK: name = "Z_OK"; break; | |
532 case Z_STREAM_END: name = "Z_STREAM_END"; break; | |
533 case Z_NEED_DICT: name = "Z_NEED_DICT"; break; | |
534 case Z_ERRNO: name = "Z_ERRNO"; break; | |
535 case Z_STREAM_ERROR: name = "Z_STREAM_ERROR"; break; | |
536 case Z_DATA_ERROR: name = "Z_DATA_ERROR"; break; | |
537 case Z_MEM_ERROR: name = "Z_MEM_ERROR"; break; | |
538 case Z_BUF_ERROR: name = "Z_BUF_ERROR"; break; | |
539 case Z_VERSION_ERROR: name = "Z_VERSION_ERROR"; break; | |
540 default: name = "Z_UNKNOWN"; | |
541 } | |
542 | |
543 return name; | |
544 } | |
545 } | |
546 | |
547 /* ***************************************************************************** | |
548 | |
549 This section contains a simple unit test for this module. It is hidden | |
550 behind a version statement because it introduces additional dependencies. | |
551 | |
552 ***************************************************************************** */ | |
553 | |
554 debug(UnitTest) { | |
555 | |
556 import tango.io.GrowBuffer : GrowBuffer; | |
557 | |
558 unittest | |
559 { | |
560 // One ring to rule them all, one ring to find them, | |
561 // One ring to bring them all and in the darkness bind them. | |
562 const char[] message = | |
563 "Ash nazg durbatulûk, ash nazg gimbatul, " | |
564 "ash nazg thrakatulûk, agh burzum-ishi krimpatul."; | |
565 | |
566 // This compressed data was created using Python 2.5's built in zlib | |
567 // module, with the default compression level. | |
568 const ubyte[] message_z = [ | |
569 0x78,0x9c,0x73,0x2c,0xce,0x50,0xc8,0x4b, | |
570 0xac,0x4a,0x57,0x48,0x29,0x2d,0x4a,0x4a, | |
571 0x2c,0x29,0xcd,0x39,0xbc,0x3b,0x5b,0x47, | |
572 0x21,0x11,0x26,0x9a,0x9e,0x99,0x0b,0x16, | |
573 0x45,0x12,0x2a,0xc9,0x28,0x4a,0xcc,0x46, | |
574 0xa8,0x4c,0xcf,0x50,0x48,0x2a,0x2d,0xaa, | |
575 0x2a,0xcd,0xd5,0xcd,0x2c,0xce,0xc8,0x54, | |
576 0xc8,0x2e,0xca,0xcc,0x2d,0x00,0xc9,0xea, | |
577 0x01,0x00,0x1f,0xe3,0x22,0x99]; | |
578 | |
579 scope cond_z = new GrowBuffer; | |
580 scope comp = new ZlibOutput(cond_z); | |
581 comp.write (message); | |
582 comp.close; | |
583 | |
584 assert( comp.written == message_z.length ); | |
585 | |
586 assert( message_z == cast(ubyte[])(cond_z.slice) ); | |
587 | |
588 scope decomp = new ZlibInput(cond_z); | |
589 auto buffer = new ubyte[256]; | |
590 buffer = buffer[0 .. decomp.read(buffer)]; | |
591 | |
592 assert( cast(ubyte[])message == buffer ); | |
593 } | |
594 } |