iterIO-0.1: Data/IterIO/Zlib.hs
module Data.IterIO.Zlib (-- * Codec and Inum functions
ZState, deflateInit2, inflateInit2
, inumZState, inumZlib, inumGzip, inumGunzip
-- * Constants from zlib.h
, max_wbits, max_mem_level, def_mem_level, zlib_version
, z_DEFAULT_COMPRESSION
, ZStrategy, z_FILTERED, z_HUFFMAN_ONLY, z_RLE
, z_FIXED, z_DEFAULT_STRATEGY
, ZMethod, z_DEFLATED
) where
import Prelude hiding (null)
import Control.Exception (throwIO, ErrorCall(..))
import Control.Monad.State.Strict
-- import qualified Data.ByteString as S
import qualified Data.ByteString.Internal as S
import qualified Data.ByteString.Lazy as L
import qualified Data.ByteString.Lazy.Internal as L
import Foreign
import Foreign.C
import Data.IterIO.Iter
import Data.IterIO.Inum
import Data.IterIO.ZlibInt
-- | State used by 'inumZState', the most generic zlib 'Inum'.
-- Create the state using 'deflateInit2' or 'inflateInit2'.
data ZState = ZState { zStream :: (ForeignPtr ZStream)
, zOp :: (ZFlush -> IO CInt)
, zFinish :: !ZFlush
, zInChunk :: !(ForeignPtr Word8)
, zOutChunk :: !(ForeignPtr Word8)
, zOut :: L.ByteString -> L.ByteString
}
defaultZState :: ZState
defaultZState = ZState { zStream = error "must allocate zStream"
, zOp = error "must define zOp"
, zFinish = z_FINISH
, zInChunk = S.nullForeignPtr
, zOutChunk = S.nullForeignPtr
, zOut = id
}
newZStream :: (Ptr ZStream -> IO CInt) -> IO (ForeignPtr ZStream)
newZStream initfn = do
zs <- mallocForeignPtrBytes z_stream_size
withForeignPtr zs $ \ptr ->
do _ <- S.memset (castPtr ptr) 0 z_stream_size
err <- initfn ptr
when (err /= z_OK) $ throwIO $ ErrorCall "newZStream: init failed"
return zs
-- | Create a 'ZState' for compression. See the description of
-- @deflateInit2@ in the zlib.h C header file for a more detailed
-- description of the arguments. Note in particular that the value of
-- @windowBits@ determines the encapsulation format of the compressed
-- data:
--
-- * 8..15 = zlib format
--
-- * 24..31 = gzip format
--
-- * -8..-15 = means raw zlib format with no header
deflateInit2 :: CInt
-- ^ Compression level (use 'z_DEFAULT_COMPRESSION' for default)
-> ZMethod
-- ^ Method (use 'z_DEFLATED')
-> CInt
-- ^ @windowBits@ (e.g., 'max_wbits')
-> CInt
-- ^ @memLevel@ (e.g., 'def_mem_level')
-> ZStrategy
-- ^ strategy (e.g., 'z_DEFAULT_STRATEGY')
-> IO ZState
deflateInit2 level method windowBits memLevel strategy = do
z <- newZStream $ \ptr -> (c_deflateInit2 ptr level method windowBits
memLevel strategy zlib_version z_stream_size)
addForeignPtrFinalizer c_deflateEnd z
return defaultZState { zStream = z
, zOp = \flush -> withForeignPtr z $ \zp ->
c_deflate zp flush
}
-- | Create a 'Zstate' for uncompression. See the description of
-- @inflateInit2@ in the zlib.h C header file for a more detailed
-- description of the arguments. Note in particular that the value of
-- @windowBits@ determines the encapsulation format of the compressed
-- data:
--
-- * 8..15 = zlib format
--
-- * 24..31 = gzip format
--
-- * 40..47 = automatically determine zlib/gzip format
--
-- * -8..-15 = means raw zlib format with no header
inflateInit2 :: CInt
-- ^ windowBits
-> IO ZState
inflateInit2 windowBits = do
z <- newZStream $ \ptr -> (c_inflateInit2 ptr windowBits
zlib_version z_stream_size)
addForeignPtrFinalizer c_inflateEnd z
return defaultZState { zStream = z
, zOp = \flush -> withForeignPtr z $ \zp ->
c_inflate zp flush
, zFinish = z_NO_FLUSH
-- Library documentation makes it sound like
-- you don't need Z_FINISH for inflating, and
-- it could cause problems if the output buffer
-- is not large enough.
}
type ZM = StateT ZState IO
withZFP :: (ZState -> ForeignPtr a) -> (Ptr a -> ZM b) -> ZM b
withZFP field k = StateT $ \zs ->
withForeignPtr (field zs) $ \v -> (runStateT $ k v) zs
zPeek :: (Storable a) => (Ptr ZStream -> Ptr a) -> ZM a
zPeek f = withZFP zStream $ liftIO . peek . f
zPoke :: (Storable a) => (Ptr ZStream -> Ptr a) -> a -> ZM ()
zPoke f a = withZFP zStream $ liftIO . flip poke a . f
zPokeFP :: (Ptr ZStream -> Ptr (Ptr Word8)) -> ForeignPtr Word8 -> Int -> ZM ()
zPokeFP f fp offset = withZFP zStream $ \z ->
liftIO $ withForeignPtr fp $ \p ->
poke (f z) $ p `plusPtr` offset
zMinusPtr :: (Ptr ZStream -> Ptr (Ptr Word8))
-> (ZState -> ForeignPtr Word8)
-> ZM Int
zMinusPtr curf basef = withZFP basef $ \base ->
if base == nullPtr
then return 0
else do
cur <- zPeek curf
return $ cur `minusPtr` base
zPushIn :: L.ByteString -> ZM L.ByteString
zPushIn s = do
avail <- zPeek avail_in
if avail > 0 then return s else pushit s
where
pushit (L.Chunk h t) = do
let (fp, offset, len) = S.toForeignPtr h
modify $ \zs -> zs { zInChunk = fp }
zPokeFP next_in fp offset
zPoke avail_in $ fromIntegral len
return t
pushit L.Empty = return L.Empty
zPopIn :: L.ByteString -> ZM L.ByteString
zPopIn s = do
len <- zPeek avail_in
if len <= 0
then return s
else do
fptr <- gets zInChunk
offset <- zMinusPtr next_in zInChunk
zPoke avail_in 0
return $ L.chunk (S.fromForeignPtr fptr offset $ fromIntegral len) s
zOutLen :: ZM Int
zOutLen = zMinusPtr next_out zOutChunk
zPopOut :: ZM ()
zPopOut = do
len <- zOutLen
when (len > 0) $ do
ochunk <- liftM (\c -> S.fromForeignPtr c 0 len) $ gets zOutChunk
out <- liftM (. L.chunk ochunk) $ gets zOut
modify $ \zs -> zs { zOutChunk = S.nullForeignPtr
, zOut = out }
zPoke avail_out 0
zMkSpace :: ZM ()
zMkSpace = do
avail <- zPeek avail_out
when (avail <= 0) $ do
zPopOut
nchunk <- liftIO $ S.mallocByteString L.defaultChunkSize
zPokeFP next_out nchunk 0
zPoke avail_out $ fromIntegral L.defaultChunkSize
modify $ \zs -> zs { zOutChunk = nchunk }
zExec :: ZFlush -> ZM CInt
zExec flush = do
zMkSpace
op <- gets zOp
r <- withZFP zInChunk $ \_ -> liftIO $ op flush
avail <- zPeek avail_out
case () of
_ | r == z_OK && avail == 0 -> zExec flush
_ | r == z_NEED_DICT -> liftIO $ throwIO $ ErrorCall "zlib NEED_DICT"
_ | r == z_STREAM_END -> do zPopOut
return r
_ | r < 0 -> do cm <- zPeek msg
m <- if cm == nullPtr
then return $ "zlib failed ("
++ show r ++ ")"
else liftIO $ peekCString cm
liftIO $ throwIO $ ErrorCall m
_ | otherwise -> return r
-- | The most general zlib 'Inum', which can take any 'ZState' created
-- by 'deflateInit2' or 'inflateInit2'.
inumZState :: (MonadIO m) =>
ZState
-> Inum L.ByteString L.ByteString m a
inumZState = mkInumM . loop
where
loop zs0 = do
(Chunk dat eof) <- chunkI
((r, rest), zs) <- liftIO (runStateT (runz eof dat) zs0)
ungetI rest
done <- ifeed $ zOut zs L.empty
unless (done || eof || r == z_STREAM_END) $ loop zs { zOut = id }
runz False L.Empty = return (z_OK, L.Empty)
runz eof s0 = do
s <- zPushIn s0
flush <- if eof && L.null s then gets zFinish else return z_NO_FLUSH
r <- zExec flush
if r == z_STREAM_END || L.null s
then do s' <- zPopIn s; return (r, s')
else runz eof s
-- | An 'Inum' that compresses in zlib format. To uncompress, use
-- 'inumGunzip'.
inumZlib :: (MonadIO m) => Inum L.ByteString L.ByteString m a
inumZlib iter = do
zs <- liftIO (deflateInit2 z_DEFAULT_COMPRESSION z_DEFLATED max_wbits
def_mem_level z_DEFAULT_STRATEGY)
inumZState zs iter
-- | An 'Inum' that compresses in gzip format.
inumGzip :: (MonadIO m) => Inum L.ByteString L.ByteString m a
inumGzip iter = do
zs <- liftIO (deflateInit2 z_DEFAULT_COMPRESSION z_DEFLATED (16 + max_wbits)
def_mem_level z_DEFAULT_STRATEGY)
inumZState zs iter
-- | An 'Inum' that uncompresses a data in either the zlib or gzip
-- format. Note that this only uncompresses one gzip stream. Thus,
-- if you feed in the concatenation of multiple gzipped files,
-- @inumGunzip@ will stop after the first one. If this is not what
-- you want, then use @'inumRepeat' inumGunzip@ to decode repeated
-- gzip streams.
inumGunzip :: (MonadIO m) => Inum L.ByteString L.ByteString m a
inumGunzip iter = do
zs <- liftIO $ inflateInit2 (32 + max_wbits)
inumZState zs iter
-- Local Variables:
-- haskell-program-name: "ghci -lz"
-- End: