io-streams-1.5.2.2: src/System/IO/Streams/Zlib.hs
-- | Interface to @zlib@ and @gzip@ compression for 'Bytestring's and 'Builder's
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
module System.IO.Streams.Zlib
( -- * ByteString decompression
gunzip
, decompress
-- * ByteString compression
, gzip
, compress
-- * Builder compression
, gzipBuilder
, compressBuilder
-- * Compression level
, CompressionLevel(..)
, defaultCompressionLevel
) where
------------------------------------------------------------------------------
import Data.ByteString (ByteString)
import qualified Data.ByteString as S
import Data.IORef (newIORef, readIORef, writeIORef)
import Prelude hiding (read)
------------------------------------------------------------------------------
import Codec.Zlib (Deflate, Inflate, Popper, WindowBits (..), feedDeflate, feedInflate, finishDeflate, finishInflate, flushDeflate, flushInflate, initDeflate, initInflate)
import Data.ByteString.Builder (Builder, byteString)
import Data.ByteString.Builder.Extra (defaultChunkSize, flush)
import Data.ByteString.Builder.Internal (newBuffer)
------------------------------------------------------------------------------
import System.IO.Streams.Builder (unsafeBuilderStream)
import System.IO.Streams.Internal (InputStream, OutputStream, makeInputStream, makeOutputStream, read, write)
------------------------------------------------------------------------------
gzipBits :: WindowBits
gzipBits = WindowBits 31
------------------------------------------------------------------------------
compressBits :: WindowBits
compressBits = WindowBits 15
------------------------------------------------------------------------------
-- | Decompress an 'InputStream' of strict 'ByteString's from the @gzip@ format
gunzip :: InputStream ByteString -> IO (InputStream ByteString)
gunzip input = initInflate gzipBits >>= inflate input
------------------------------------------------------------------------------
-- | Decompress an 'InputStream' of strict 'ByteString's from the @zlib@ format
decompress :: InputStream ByteString -> IO (InputStream ByteString)
decompress input = initInflate compressBits >>= inflate input
------------------------------------------------------------------------------
-- Note: bytes pushed back to this input stream are not propagated back to the
-- source InputStream.
data IS = Input
| Popper Popper
| Done
inflate :: InputStream ByteString -> Inflate -> IO (InputStream ByteString)
inflate input state = do
ref <- newIORef Input
makeInputStream $ stream ref
where
stream ref = go
where
go = readIORef ref >>= \st ->
case st of
Input -> read input >>= maybe eof chunk
Popper p -> pop p
Done -> return Nothing
eof = do
x <- finishInflate state
writeIORef ref Done
if (not $ S.null x)
then return $! Just x
else return Nothing
chunk s =
if S.null s
then do
out <- flushInflate state
return $! Just out
else feedInflate state s >>= \popper -> do
writeIORef ref $ Popper popper
pop popper
pop popper = popper >>= maybe backToInput (return . Just)
backToInput = writeIORef ref Input >> read input >>= maybe eof chunk
------------------------------------------------------------------------------
deflateBuilder :: OutputStream Builder
-> Deflate
-> IO (OutputStream Builder)
deflateBuilder stream state = do
zippedStr <- makeOutputStream bytestringStream >>=
\x -> deflate x state
-- we can use unsafeBuilderStream here because zlib is going to consume the
-- stream
unsafeBuilderStream (newBuffer defaultChunkSize) zippedStr
where
bytestringStream x = write (fmap cvt x) stream
cvt s | S.null s = flush
| otherwise = byteString s
------------------------------------------------------------------------------
-- | Convert an 'OutputStream' that consumes compressed 'Builder's into an
-- 'OutputStream' that consumes uncompressed 'Builder's in the @gzip@ format
gzipBuilder :: CompressionLevel
-> OutputStream Builder
-> IO (OutputStream Builder)
gzipBuilder level output =
initDeflate (clamp level) gzipBits >>= deflateBuilder output
------------------------------------------------------------------------------
-- | Convert an 'OutputStream' that consumes compressed 'Builder's into an
-- 'OutputStream' that consumes uncompressed 'Builder's in the @zlib@ format
compressBuilder :: CompressionLevel
-> OutputStream Builder
-> IO (OutputStream Builder)
compressBuilder level output =
initDeflate (clamp level) compressBits >>= deflateBuilder output
------------------------------------------------------------------------------
deflate :: OutputStream ByteString
-> Deflate
-> IO (OutputStream ByteString)
deflate output state = makeOutputStream stream
where
stream Nothing = popAll (finishDeflate state) >> write Nothing output
stream (Just s) = do
-- Empty string means flush
if S.null s
then do
popAll (flushDeflate state)
write (Just S.empty) output
else feedDeflate state s >>= popAll
popAll popper = go
where
go = popper >>= maybe (return $! ()) (\s -> write (Just s) output >> go)
------------------------------------------------------------------------------
-- | Parameter that defines the tradeoff between speed and compression ratio
newtype CompressionLevel = CompressionLevel Int
deriving (Read, Eq, Show, Num)
------------------------------------------------------------------------------
-- | A compression level that balances speed with compression ratio
defaultCompressionLevel :: CompressionLevel
defaultCompressionLevel = CompressionLevel 5
------------------------------------------------------------------------------
clamp :: CompressionLevel -> Int
clamp (CompressionLevel x) = min 9 (max x 0)
------------------------------------------------------------------------------
-- | Convert an 'OutputStream' that consumes compressed 'ByteString's into an
-- 'OutputStream' that consumes uncompressed 'ByteString's in the @gzip@ format
gzip :: CompressionLevel
-> OutputStream ByteString
-> IO (OutputStream ByteString)
gzip level output = initDeflate (clamp level) gzipBits >>= deflate output
------------------------------------------------------------------------------
-- | Convert an 'OutputStream' that consumes compressed 'ByteString's into an
-- 'OutputStream' that consumes uncompressed 'ByteString's in the @zlib@ format
compress :: CompressionLevel
-> OutputStream ByteString
-> IO (OutputStream ByteString)
compress level output = initDeflate (clamp level) compressBits >>=
deflate output