{-# LANGUAGE RankNTypes #-} {-# LANGUAGE ScopedTypeVariables #-} -- | This module exports utilities to compress and decompress @pipes@ streams -- using the zlib compression codec. -- -- If you want to compress or decompress GZip streams, use the "Pipes.GZip" -- module instead. module Pipes.Zlib ( -- * Streams decompress , decompress' , compress -- * Compression levels , CompressionLevel , defaultCompression , noCompression , bestSpeed , bestCompression , compressionLevel -- * Window size -- $ccz-re-export , Z.defaultWindowBits , windowBits ) where import Data.Streaming.Zlib as Z import Control.Exception (throwIO) import Control.Monad (unless) import qualified Data.ByteString as B import Pipes -------------------------------------------------------------------------------- -- | Decompress bytes flowing from a 'Producer'. -- -- See the "Codec.Compression.Zlib" module for details about 'Z.WindowBits'. -- -- @ -- 'decompress' :: 'MonadIO' m -- => 'Z.WindowBits' -- => 'Producer' 'B.ByteString' m r -- -> 'Producer' 'B.ByteString' m r -- @ decompress :: MonadIO m => Z.WindowBits -> Proxy x' x () B.ByteString m r -- ^ Compressed stream -> Proxy x' x () B.ByteString m r -- ^ Decompressed stream decompress wbits p0 = do inf <- liftIO $ Z.initInflate wbits r <- for p0 $ \bs -> do popper <- liftIO (Z.feedInflate inf bs) fromPopper popper bs <- liftIO $ Z.finishInflate inf unless (B.null bs) (yield bs) return r {-# INLINABLE decompress #-} -- | Decompress bytes flowing from a 'Producer', returning any leftover input -- that follows the compressed stream. decompress' :: MonadIO m => Z.WindowBits -> Producer B.ByteString m r -- ^ Compressed stream -> Producer B.ByteString m (Either (Producer B.ByteString m r) r) -- ^ Decompressed stream, ending with either leftovers or a result decompress' wbits p0 = go p0 =<< liftIO (Z.initInflate wbits) where flush inf = do bs <- liftIO $ Z.flushInflate inf unless (B.null bs) (yield bs) go p inf = do res <- lift (next p) case res of Left r -> return $ Right r Right (bs, p') -> do fromPopper =<< liftIO (Z.feedInflate inf bs) flush inf leftover <- liftIO $ Z.getUnusedInflate inf if B.null leftover then go p' inf else return $ Left (yield leftover >> p') {-# INLINABLE decompress' #-} -- | Compress bytes flowing from a 'Producer'. -- -- See the "Codec.Compression.Zlib" module for details about -- 'Z.CompressionLevel' and 'Z.WindowBits'. -- -- @ -- 'compress' :: 'MonadIO' m -- => 'Z.CompressionLevel' -- -> 'Z.WindowBits' -- -> 'Producer' 'B.ByteString' m r -- -> 'Producer' 'B.ByteString' m r -- @ compress :: MonadIO m => CompressionLevel -> Z.WindowBits -> Proxy x' x () B.ByteString m r -- ^ Decompressed stream -> Proxy x' x () B.ByteString m r -- ^ Compressed stream compress (CompressionLevel clevel) wbits p0 = do def <- liftIO $ Z.initDeflate clevel wbits r <- for p0 $ \bs -> do popper <- liftIO (Z.feedDeflate def bs) fromPopper popper fromPopper $ Z.finishDeflate def return r {-# INLINABLE compress #-} -------------------------------------------------------------------------------- -- $ccz-re-export -- -- The following are re-exported from "Codec.Compression.Zlib" for your -- convenience. -------------------------------------------------------------------------------- -- Compression Levels -- | How hard should we try to compress? newtype CompressionLevel = CompressionLevel Int deriving (Show, Read, Eq, Ord) defaultCompression, noCompression, bestSpeed, bestCompression :: CompressionLevel defaultCompression = CompressionLevel (-1) noCompression = CompressionLevel 0 bestSpeed = CompressionLevel 1 bestCompression = CompressionLevel 9 -- | A specific compression level between 0 and 9. compressionLevel :: Int -> CompressionLevel compressionLevel n | n >= 0 && n <= 9 = CompressionLevel n | otherwise = error "CompressionLevel must be in the range 0..9" windowBits :: Int -> WindowBits windowBits = WindowBits -------------------------------------------------------------------------------- -- Internal stuff -- | Produce values from the given 'Z.Popper' until exhausted. fromPopper :: MonadIO m => Z.Popper -> Producer' B.ByteString m () fromPopper pop = loop where loop = do mbs <- liftIO pop case mbs of PRDone -> return () PRError e -> liftIO $ throwIO e PRNext bs -> yield bs >> loop {-# INLINABLE fromPopper #-}