1105 lines
36 KiB
Plaintext
1105 lines
36 KiB
Plaintext
|
{-# LANGUAGE CPP, ForeignFunctionInterface, DeriveDataTypeable #-}
|
||
|
#if __GLASGOW_HASKELL__ >= 702
|
||
|
{-# LANGUAGE DeriveGeneric #-}
|
||
|
#endif
|
||
|
#if __GLASGOW_HASKELL__ >= 706
|
||
|
{-# LANGUAGE CApiFFI #-}
|
||
|
#endif
|
||
|
-----------------------------------------------------------------------------
|
||
|
-- |
|
||
|
-- Copyright : (c) 2006-2015 Duncan Coutts
|
||
|
-- License : BSD-style
|
||
|
--
|
||
|
-- Maintainer : duncan@community.haskell.org
|
||
|
--
|
||
|
-- Zlib wrapper layer
|
||
|
--
|
||
|
-----------------------------------------------------------------------------
|
||
|
module Codec.Compression.Zlib.Stream (
|
||
|
|
||
|
-- * The Zlib state monad
|
||
|
Stream,
|
||
|
State,
|
||
|
mkState,
|
||
|
runStream,
|
||
|
unsafeLiftIO,
|
||
|
finalise,
|
||
|
|
||
|
-- * Initialisation
|
||
|
deflateInit,
|
||
|
inflateInit,
|
||
|
|
||
|
-- ** Initialisation parameters
|
||
|
Format(..),
|
||
|
gzipFormat,
|
||
|
zlibFormat,
|
||
|
rawFormat,
|
||
|
gzipOrZlibFormat,
|
||
|
formatSupportsDictionary,
|
||
|
CompressionLevel(..),
|
||
|
defaultCompression,
|
||
|
noCompression,
|
||
|
bestSpeed,
|
||
|
bestCompression,
|
||
|
compressionLevel,
|
||
|
Method(..),
|
||
|
deflateMethod,
|
||
|
WindowBits(..),
|
||
|
defaultWindowBits,
|
||
|
windowBits,
|
||
|
MemoryLevel(..),
|
||
|
defaultMemoryLevel,
|
||
|
minMemoryLevel,
|
||
|
maxMemoryLevel,
|
||
|
memoryLevel,
|
||
|
CompressionStrategy(..),
|
||
|
defaultStrategy,
|
||
|
filteredStrategy,
|
||
|
huffmanOnlyStrategy,
|
||
|
|
||
|
-- * The buisness
|
||
|
deflate,
|
||
|
inflate,
|
||
|
Status(..),
|
||
|
Flush(..),
|
||
|
ErrorCode(..),
|
||
|
-- ** Special operations
|
||
|
inflateReset,
|
||
|
|
||
|
-- * Buffer management
|
||
|
-- ** Input buffer
|
||
|
pushInputBuffer,
|
||
|
inputBufferEmpty,
|
||
|
popRemainingInputBuffer,
|
||
|
|
||
|
-- ** Output buffer
|
||
|
pushOutputBuffer,
|
||
|
popOutputBuffer,
|
||
|
outputBufferBytesAvailable,
|
||
|
outputBufferSpaceRemaining,
|
||
|
outputBufferFull,
|
||
|
|
||
|
-- ** Dictionary
|
||
|
deflateSetDictionary,
|
||
|
inflateSetDictionary,
|
||
|
|
||
|
-- ** Dictionary hashes
|
||
|
DictionaryHash,
|
||
|
dictionaryHash,
|
||
|
zeroDictionaryHash,
|
||
|
|
||
|
#ifdef DEBUG
|
||
|
-- * Debugging
|
||
|
consistencyCheck,
|
||
|
dump,
|
||
|
trace,
|
||
|
#endif
|
||
|
|
||
|
) where
|
||
|
|
||
|
-- Note we don't use the MIN_VERSION_* macros here for compatability with
|
||
|
-- old Cabal versions that come with old GHC, that didn't provide these
|
||
|
-- macros for .hsc files. So we use __GLASGOW_HASKELL__ as a proxy.
|
||
|
|
||
|
import Foreign
|
||
|
( Word8, Ptr, nullPtr, plusPtr, peekByteOff, pokeByteOff
|
||
|
, ForeignPtr, FinalizerPtr, mallocForeignPtrBytes, addForeignPtrFinalizer
|
||
|
, withForeignPtr, touchForeignPtr, minusPtr )
|
||
|
#if __GLASGOW_HASKELL__ >= 702
|
||
|
import Foreign.ForeignPtr.Unsafe ( unsafeForeignPtrToPtr )
|
||
|
import System.IO.Unsafe ( unsafePerformIO )
|
||
|
#else
|
||
|
import Foreign ( unsafeForeignPtrToPtr, unsafePerformIO )
|
||
|
#endif
|
||
|
#ifdef __GLASGOW_HASKELL__
|
||
|
import Foreign
|
||
|
( finalizeForeignPtr )
|
||
|
#endif
|
||
|
import Foreign.C
|
||
|
import Data.ByteString.Internal (nullForeignPtr)
|
||
|
import qualified Data.ByteString.Unsafe as B
|
||
|
import Data.ByteString (ByteString)
|
||
|
#if !(__GLASGOW_HASKELL__ >= 710)
|
||
|
import Control.Applicative (Applicative(..))
|
||
|
#endif
|
||
|
import Control.Monad (ap,liftM)
|
||
|
#if MIN_VERSION_base(4,9,0)
|
||
|
import qualified Control.Monad.Fail as Fail
|
||
|
#endif
|
||
|
#if __GLASGOW_HASKELL__ >= 702
|
||
|
#if __GLASGOW_HASKELL__ >= 708
|
||
|
import Control.Monad.ST.Strict
|
||
|
#else
|
||
|
import Control.Monad.ST.Strict hiding (unsafeIOToST)
|
||
|
#endif
|
||
|
import Control.Monad.ST.Unsafe
|
||
|
#else
|
||
|
import Control.Monad.ST.Strict
|
||
|
#endif
|
||
|
import Control.Exception (assert)
|
||
|
import Data.Typeable (Typeable)
|
||
|
#if __GLASGOW_HASKELL__ >= 702
|
||
|
import GHC.Generics (Generic)
|
||
|
#endif
|
||
|
#ifdef DEBUG
|
||
|
import System.IO (hPutStrLn, stderr)
|
||
|
#endif
|
||
|
|
||
|
import Prelude hiding (length)
|
||
|
|
||
|
#include "zlib.h"
|
||
|
|
||
|
|
||
|
pushInputBuffer :: ForeignPtr Word8 -> Int -> Int -> Stream ()
|
||
|
pushInputBuffer inBuf' offset length = do
|
||
|
|
||
|
-- must not push a new input buffer if the last one is not used up
|
||
|
inAvail <- getInAvail
|
||
|
assert (inAvail == 0) $ return ()
|
||
|
|
||
|
-- Now that we're setting a new input buffer, we can be sure that zlib no
|
||
|
-- longer has a reference to the old one. Therefore this is the last point
|
||
|
-- at which the old buffer had to be retained. It's safe to release now.
|
||
|
inBuf <- getInBuf
|
||
|
unsafeLiftIO $ touchForeignPtr inBuf
|
||
|
|
||
|
-- now set the available input buffer ptr and length
|
||
|
setInBuf inBuf'
|
||
|
setInAvail length
|
||
|
setInNext (unsafeForeignPtrToPtr inBuf' `plusPtr` offset)
|
||
|
-- Note the 'unsafe'. We are passing the raw ptr inside inBuf' to zlib.
|
||
|
-- To make this safe we need to hold on to the ForeignPtr for at least as
|
||
|
-- long as zlib is using the underlying raw ptr.
|
||
|
|
||
|
|
||
|
inputBufferEmpty :: Stream Bool
|
||
|
inputBufferEmpty = getInAvail >>= return . (==0)
|
||
|
|
||
|
|
||
|
popRemainingInputBuffer :: Stream (ForeignPtr Word8, Int, Int)
|
||
|
popRemainingInputBuffer = do
|
||
|
|
||
|
inBuf <- getInBuf
|
||
|
inNext <- getInNext
|
||
|
inAvail <- getInAvail
|
||
|
|
||
|
-- there really should be something to pop, otherwise it's silly
|
||
|
assert (inAvail > 0) $ return ()
|
||
|
setInAvail 0
|
||
|
|
||
|
return (inBuf, inNext `minusPtr` unsafeForeignPtrToPtr inBuf, inAvail)
|
||
|
|
||
|
|
||
|
pushOutputBuffer :: ForeignPtr Word8 -> Int -> Int -> Stream ()
|
||
|
pushOutputBuffer outBuf' offset length = do
|
||
|
|
||
|
--must not push a new buffer if there is still data in the old one
|
||
|
outAvail <- getOutAvail
|
||
|
assert (outAvail == 0) $ return ()
|
||
|
-- Note that there may still be free space in the output buffer, that's ok,
|
||
|
-- you might not want to bother completely filling the output buffer say if
|
||
|
-- there's only a few free bytes left.
|
||
|
|
||
|
outBuf <- getOutBuf
|
||
|
unsafeLiftIO $ touchForeignPtr outBuf
|
||
|
|
||
|
-- now set the available input buffer ptr and length
|
||
|
setOutBuf outBuf'
|
||
|
setOutFree length
|
||
|
setOutNext (unsafeForeignPtrToPtr outBuf' `plusPtr` offset)
|
||
|
|
||
|
setOutOffset offset
|
||
|
setOutAvail 0
|
||
|
|
||
|
|
||
|
-- get that part of the output buffer that is currently full
|
||
|
-- (might be 0, use outputBufferBytesAvailable to check)
|
||
|
-- this may leave some space remaining in the buffer, use
|
||
|
-- outputBufferSpaceRemaining to check.
|
||
|
popOutputBuffer :: Stream (ForeignPtr Word8, Int, Int)
|
||
|
popOutputBuffer = do
|
||
|
|
||
|
outBuf <- getOutBuf
|
||
|
outOffset <- getOutOffset
|
||
|
outAvail <- getOutAvail
|
||
|
|
||
|
-- there really should be something to pop, otherwise it's silly
|
||
|
assert (outAvail > 0) $ return ()
|
||
|
|
||
|
setOutOffset (outOffset + outAvail)
|
||
|
setOutAvail 0
|
||
|
|
||
|
return (outBuf, outOffset, outAvail)
|
||
|
|
||
|
|
||
|
-- this is the number of bytes available in the output buffer
|
||
|
outputBufferBytesAvailable :: Stream Int
|
||
|
outputBufferBytesAvailable = getOutAvail
|
||
|
|
||
|
|
||
|
-- you needen't get all the output immediately, you can continue until
|
||
|
-- there is no more output space available, this tells you that amount
|
||
|
outputBufferSpaceRemaining :: Stream Int
|
||
|
outputBufferSpaceRemaining = getOutFree
|
||
|
|
||
|
|
||
|
-- you only need to supply a new buffer when there is no more output buffer
|
||
|
-- space remaining
|
||
|
outputBufferFull :: Stream Bool
|
||
|
outputBufferFull = liftM (==0) outputBufferSpaceRemaining
|
||
|
|
||
|
|
||
|
-- you can only run this when the output buffer is not empty
|
||
|
-- you can run it when the input buffer is empty but it doesn't do anything
|
||
|
-- after running deflate either the output buffer will be full
|
||
|
-- or the input buffer will be empty (or both)
|
||
|
deflate :: Flush -> Stream Status
|
||
|
deflate flush = do
|
||
|
|
||
|
outFree <- getOutFree
|
||
|
|
||
|
-- deflate needs free space in the output buffer
|
||
|
assert (outFree > 0) $ return ()
|
||
|
|
||
|
result <- deflate_ flush
|
||
|
outFree' <- getOutFree
|
||
|
|
||
|
-- number of bytes of extra output there is available as a result of
|
||
|
-- the call to deflate:
|
||
|
let outExtra = outFree - outFree'
|
||
|
|
||
|
outAvail <- getOutAvail
|
||
|
setOutAvail (outAvail + outExtra)
|
||
|
return result
|
||
|
|
||
|
|
||
|
inflate :: Flush -> Stream Status
|
||
|
inflate flush = do
|
||
|
|
||
|
outFree <- getOutFree
|
||
|
|
||
|
-- inflate needs free space in the output buffer
|
||
|
assert (outFree > 0) $ return ()
|
||
|
|
||
|
result <- inflate_ flush
|
||
|
outFree' <- getOutFree
|
||
|
|
||
|
-- number of bytes of extra output there is available as a result of
|
||
|
-- the call to inflate:
|
||
|
let outExtra = outFree - outFree'
|
||
|
|
||
|
outAvail <- getOutAvail
|
||
|
setOutAvail (outAvail + outExtra)
|
||
|
return result
|
||
|
|
||
|
|
||
|
inflateReset :: Stream ()
|
||
|
inflateReset = do
|
||
|
|
||
|
outAvail <- getOutAvail
|
||
|
inAvail <- getInAvail
|
||
|
-- At the point where this is used, all the output should have been consumed
|
||
|
-- and any trailing input should be extracted and resupplied explicitly, not
|
||
|
-- just left.
|
||
|
assert (outAvail == 0 && inAvail == 0) $ return ()
|
||
|
|
||
|
err <- withStreamState $ \zstream ->
|
||
|
c_inflateReset zstream
|
||
|
failIfError err
|
||
|
|
||
|
|
||
|
|
||
|
deflateSetDictionary :: ByteString -> Stream Status
|
||
|
deflateSetDictionary dict = do
|
||
|
err <- withStreamState $ \zstream ->
|
||
|
B.unsafeUseAsCStringLen dict $ \(ptr, len) ->
|
||
|
c_deflateSetDictionary zstream ptr (fromIntegral len)
|
||
|
toStatus err
|
||
|
|
||
|
inflateSetDictionary :: ByteString -> Stream Status
|
||
|
inflateSetDictionary dict = do
|
||
|
err <- withStreamState $ \zstream -> do
|
||
|
B.unsafeUseAsCStringLen dict $ \(ptr, len) ->
|
||
|
c_inflateSetDictionary zstream ptr (fromIntegral len)
|
||
|
toStatus err
|
||
|
|
||
|
-- | A hash of a custom compression dictionary. These hashes are used by
|
||
|
-- zlib as dictionary identifiers.
|
||
|
-- (The particular hash function used is Adler32.)
|
||
|
--
|
||
|
newtype DictionaryHash = DictHash CULong
|
||
|
deriving (Eq, Ord, Read, Show)
|
||
|
|
||
|
-- | Update a running 'DictionaryHash'. You can generate a 'DictionaryHash'
|
||
|
-- from one or more 'ByteString's by starting from 'zeroDictionaryHash', e.g.
|
||
|
--
|
||
|
-- > dictionaryHash zeroDictionaryHash :: ByteString -> DictionaryHash
|
||
|
--
|
||
|
-- or
|
||
|
--
|
||
|
-- > foldl' dictionaryHash zeroDictionaryHash :: [ByteString] -> DictionaryHash
|
||
|
--
|
||
|
dictionaryHash :: DictionaryHash -> ByteString -> DictionaryHash
|
||
|
dictionaryHash (DictHash adler) dict =
|
||
|
unsafePerformIO $
|
||
|
B.unsafeUseAsCStringLen dict $ \(ptr, len) ->
|
||
|
liftM DictHash $ c_adler32 adler ptr (fromIntegral len)
|
||
|
|
||
|
-- | A zero 'DictionaryHash' to use as the initial value with 'dictionaryHash'.
|
||
|
--
|
||
|
zeroDictionaryHash :: DictionaryHash
|
||
|
zeroDictionaryHash = DictHash 0
|
||
|
|
||
|
----------------------------
|
||
|
-- Stream monad
|
||
|
--
|
||
|
|
||
|
newtype Stream a = Z {
|
||
|
unZ :: ForeignPtr StreamState
|
||
|
-> ForeignPtr Word8
|
||
|
-> ForeignPtr Word8
|
||
|
-> Int -> Int
|
||
|
-> IO (ForeignPtr Word8
|
||
|
,ForeignPtr Word8
|
||
|
,Int, Int, a)
|
||
|
}
|
||
|
|
||
|
instance Functor Stream where
|
||
|
fmap = liftM
|
||
|
|
||
|
instance Applicative Stream where
|
||
|
pure = returnZ
|
||
|
(<*>) = ap
|
||
|
(*>) = thenZ_
|
||
|
|
||
|
instance Monad Stream where
|
||
|
(>>=) = thenZ
|
||
|
-- m >>= f = (m `thenZ` \a -> consistencyCheck `thenZ_` returnZ a) `thenZ` f
|
||
|
(>>) = (*>)
|
||
|
|
||
|
#if !MIN_VERSION_base(4,8,0)
|
||
|
return = pure
|
||
|
#endif
|
||
|
|
||
|
#if !MIN_VERSION_base(4,9,0)
|
||
|
fail = (finalise >>) . failZ
|
||
|
#elif !MIN_VERSION_base(4,13,0)
|
||
|
fail = Fail.fail
|
||
|
#endif
|
||
|
|
||
|
#if MIN_VERSION_base(4,9,0)
|
||
|
instance Fail.MonadFail Stream where
|
||
|
fail = (finalise >>) . failZ
|
||
|
#endif
|
||
|
|
||
|
returnZ :: a -> Stream a
|
||
|
returnZ a = Z $ \_ inBuf outBuf outOffset outLength ->
|
||
|
return (inBuf, outBuf, outOffset, outLength, a)
|
||
|
{-# INLINE returnZ #-}
|
||
|
|
||
|
thenZ :: Stream a -> (a -> Stream b) -> Stream b
|
||
|
thenZ (Z m) f =
|
||
|
Z $ \stream inBuf outBuf outOffset outLength ->
|
||
|
m stream inBuf outBuf outOffset outLength >>=
|
||
|
\(inBuf', outBuf', outOffset', outLength', a) ->
|
||
|
unZ (f a) stream inBuf' outBuf' outOffset' outLength'
|
||
|
{-# INLINE thenZ #-}
|
||
|
|
||
|
thenZ_ :: Stream a -> Stream b -> Stream b
|
||
|
thenZ_ (Z m) f =
|
||
|
Z $ \stream inBuf outBuf outOffset outLength ->
|
||
|
m stream inBuf outBuf outOffset outLength >>=
|
||
|
\(inBuf', outBuf', outOffset', outLength', _) ->
|
||
|
unZ f stream inBuf' outBuf' outOffset' outLength'
|
||
|
{-# INLINE thenZ_ #-}
|
||
|
|
||
|
failZ :: String -> Stream a
|
||
|
failZ msg = Z (\_ _ _ _ _ -> fail ("Codec.Compression.Zlib: " ++ msg))
|
||
|
|
||
|
data State s = State !(ForeignPtr StreamState)
|
||
|
!(ForeignPtr Word8)
|
||
|
!(ForeignPtr Word8)
|
||
|
{-# UNPACK #-} !Int
|
||
|
{-# UNPACK #-} !Int
|
||
|
|
||
|
mkState :: ST s (State s)
|
||
|
mkState = unsafeIOToST $ do
|
||
|
stream <- mallocForeignPtrBytes (#{const sizeof(z_stream)})
|
||
|
withForeignPtr stream $ \ptr -> do
|
||
|
#{poke z_stream, msg} ptr nullPtr
|
||
|
#{poke z_stream, zalloc} ptr nullPtr
|
||
|
#{poke z_stream, zfree} ptr nullPtr
|
||
|
#{poke z_stream, opaque} ptr nullPtr
|
||
|
#{poke z_stream, next_in} ptr nullPtr
|
||
|
#{poke z_stream, next_out} ptr nullPtr
|
||
|
#{poke z_stream, avail_in} ptr (0 :: CUInt)
|
||
|
#{poke z_stream, avail_out} ptr (0 :: CUInt)
|
||
|
return (State stream nullForeignPtr nullForeignPtr 0 0)
|
||
|
|
||
|
runStream :: Stream a -> State s -> ST s (a, State s)
|
||
|
runStream (Z m) (State stream inBuf outBuf outOffset outLength) =
|
||
|
unsafeIOToST $
|
||
|
m stream inBuf outBuf outOffset outLength >>=
|
||
|
\(inBuf', outBuf', outOffset', outLength', a) ->
|
||
|
return (a, State stream inBuf' outBuf' outOffset' outLength')
|
||
|
|
||
|
-- This is marked as unsafe because runStream uses unsafeIOToST so anything
|
||
|
-- lifted here can end up being unsafePerformIO'd.
|
||
|
unsafeLiftIO :: IO a -> Stream a
|
||
|
unsafeLiftIO m = Z $ \_stream inBuf outBuf outOffset outLength -> do
|
||
|
a <- m
|
||
|
return (inBuf, outBuf, outOffset, outLength, a)
|
||
|
|
||
|
getStreamState :: Stream (ForeignPtr StreamState)
|
||
|
getStreamState = Z $ \stream inBuf outBuf outOffset outLength -> do
|
||
|
return (inBuf, outBuf, outOffset, outLength, stream)
|
||
|
|
||
|
getInBuf :: Stream (ForeignPtr Word8)
|
||
|
getInBuf = Z $ \_stream inBuf outBuf outOffset outLength -> do
|
||
|
return (inBuf, outBuf, outOffset, outLength, inBuf)
|
||
|
|
||
|
getOutBuf :: Stream (ForeignPtr Word8)
|
||
|
getOutBuf = Z $ \_stream inBuf outBuf outOffset outLength -> do
|
||
|
return (inBuf, outBuf, outOffset, outLength, outBuf)
|
||
|
|
||
|
getOutOffset :: Stream Int
|
||
|
getOutOffset = Z $ \_stream inBuf outBuf outOffset outLength -> do
|
||
|
return (inBuf, outBuf, outOffset, outLength, outOffset)
|
||
|
|
||
|
getOutAvail :: Stream Int
|
||
|
getOutAvail = Z $ \_stream inBuf outBuf outOffset outLength -> do
|
||
|
return (inBuf, outBuf, outOffset, outLength, outLength)
|
||
|
|
||
|
setInBuf :: ForeignPtr Word8 -> Stream ()
|
||
|
setInBuf inBuf = Z $ \_stream _ outBuf outOffset outLength -> do
|
||
|
return (inBuf, outBuf, outOffset, outLength, ())
|
||
|
|
||
|
setOutBuf :: ForeignPtr Word8 -> Stream ()
|
||
|
setOutBuf outBuf = Z $ \_stream inBuf _ outOffset outLength -> do
|
||
|
return (inBuf, outBuf, outOffset, outLength, ())
|
||
|
|
||
|
setOutOffset :: Int -> Stream ()
|
||
|
setOutOffset outOffset = Z $ \_stream inBuf outBuf _ outLength -> do
|
||
|
return (inBuf, outBuf, outOffset, outLength, ())
|
||
|
|
||
|
setOutAvail :: Int -> Stream ()
|
||
|
setOutAvail outLength = Z $ \_stream inBuf outBuf outOffset _ -> do
|
||
|
return (inBuf, outBuf, outOffset, outLength, ())
|
||
|
|
||
|
----------------------------
|
||
|
-- Debug stuff
|
||
|
--
|
||
|
|
||
|
#ifdef DEBUG
|
||
|
trace :: String -> Stream ()
|
||
|
trace = unsafeLiftIO . hPutStrLn stderr
|
||
|
|
||
|
dump :: Stream ()
|
||
|
dump = do
|
||
|
inNext <- getInNext
|
||
|
inAvail <- getInAvail
|
||
|
|
||
|
outNext <- getOutNext
|
||
|
outFree <- getOutFree
|
||
|
outAvail <- getOutAvail
|
||
|
outOffset <- getOutOffset
|
||
|
|
||
|
unsafeLiftIO $ hPutStrLn stderr $
|
||
|
"Stream {\n" ++
|
||
|
" inNext = " ++ show inNext ++ ",\n" ++
|
||
|
" inAvail = " ++ show inAvail ++ ",\n" ++
|
||
|
"\n" ++
|
||
|
" outNext = " ++ show outNext ++ ",\n" ++
|
||
|
" outFree = " ++ show outFree ++ ",\n" ++
|
||
|
" outAvail = " ++ show outAvail ++ ",\n" ++
|
||
|
" outOffset = " ++ show outOffset ++ "\n" ++
|
||
|
"}"
|
||
|
|
||
|
consistencyCheck
|
||
|
|
||
|
consistencyCheck :: Stream ()
|
||
|
consistencyCheck = do
|
||
|
|
||
|
outBuf <- getOutBuf
|
||
|
outOffset <- getOutOffset
|
||
|
outAvail <- getOutAvail
|
||
|
outNext <- getOutNext
|
||
|
|
||
|
let outBufPtr = unsafeForeignPtrToPtr outBuf
|
||
|
|
||
|
assert (outBufPtr `plusPtr` (outOffset + outAvail) == outNext) $ return ()
|
||
|
#endif
|
||
|
|
||
|
|
||
|
----------------------------
|
||
|
-- zlib wrapper layer
|
||
|
--
|
||
|
|
||
|
data Status =
|
||
|
Ok
|
||
|
| StreamEnd
|
||
|
| Error ErrorCode String
|
||
|
|
||
|
data ErrorCode =
|
||
|
NeedDict DictionaryHash
|
||
|
| FileError
|
||
|
| StreamError
|
||
|
| DataError
|
||
|
| MemoryError
|
||
|
| BufferError -- ^ No progress was possible or there was not enough room in
|
||
|
-- the output buffer when 'Finish' is used. Note that
|
||
|
-- 'BuferError' is not fatal, and 'inflate' can be called
|
||
|
-- again with more input and more output space to continue.
|
||
|
| VersionError
|
||
|
| Unexpected
|
||
|
|
||
|
toStatus :: CInt -> Stream Status
|
||
|
toStatus errno = case errno of
|
||
|
(#{const Z_OK}) -> return Ok
|
||
|
(#{const Z_STREAM_END}) -> return StreamEnd
|
||
|
(#{const Z_NEED_DICT}) -> do
|
||
|
adler <- withStreamPtr (#{peek z_stream, adler})
|
||
|
err (NeedDict (DictHash adler)) "custom dictionary needed"
|
||
|
(#{const Z_BUF_ERROR}) -> err BufferError "buffer error"
|
||
|
(#{const Z_ERRNO}) -> err FileError "file error"
|
||
|
(#{const Z_STREAM_ERROR}) -> err StreamError "stream error"
|
||
|
(#{const Z_DATA_ERROR}) -> err DataError "data error"
|
||
|
(#{const Z_MEM_ERROR}) -> err MemoryError "insufficient memory"
|
||
|
(#{const Z_VERSION_ERROR}) -> err VersionError "incompatible zlib version"
|
||
|
other -> return $ Error Unexpected
|
||
|
("unexpected zlib status: " ++ show other)
|
||
|
where
|
||
|
err errCode altMsg = liftM (Error errCode) $ do
|
||
|
msgPtr <- withStreamPtr (#{peek z_stream, msg})
|
||
|
if msgPtr /= nullPtr
|
||
|
then unsafeLiftIO (peekCAString msgPtr)
|
||
|
else return altMsg
|
||
|
|
||
|
failIfError :: CInt -> Stream ()
|
||
|
failIfError errno = toStatus errno >>= \status -> case status of
|
||
|
(Error _ msg) -> fail msg
|
||
|
_ -> return ()
|
||
|
|
||
|
|
||
|
data Flush =
|
||
|
NoFlush
|
||
|
| SyncFlush
|
||
|
| FullFlush
|
||
|
| Finish
|
||
|
-- | Block -- only available in zlib 1.2 and later, uncomment if you need it.
|
||
|
|
||
|
fromFlush :: Flush -> CInt
|
||
|
fromFlush NoFlush = #{const Z_NO_FLUSH}
|
||
|
fromFlush SyncFlush = #{const Z_SYNC_FLUSH}
|
||
|
fromFlush FullFlush = #{const Z_FULL_FLUSH}
|
||
|
fromFlush Finish = #{const Z_FINISH}
|
||
|
-- fromFlush Block = #{const Z_BLOCK}
|
||
|
|
||
|
|
||
|
-- | The format used for compression or decompression. There are three
|
||
|
-- variations.
|
||
|
--
|
||
|
data Format = GZip | Zlib | Raw | GZipOrZlib
|
||
|
deriving (Eq, Ord, Enum, Bounded, Show, Typeable
|
||
|
#if __GLASGOW_HASKELL__ >= 702
|
||
|
, Generic
|
||
|
#endif
|
||
|
)
|
||
|
|
||
|
{-# DEPRECATED GZip "Use gzipFormat. Format constructors will be hidden in version 0.7" #-}
|
||
|
{-# DEPRECATED Zlib "Use zlibFormat. Format constructors will be hidden in version 0.7" #-}
|
||
|
{-# DEPRECATED Raw "Use rawFormat. Format constructors will be hidden in version 0.7" #-}
|
||
|
{-# DEPRECATED GZipOrZlib "Use gzipOrZlibFormat. Format constructors will be hidden in version 0.7" #-}
|
||
|
|
||
|
-- | The gzip format uses a header with a checksum and some optional meta-data
|
||
|
-- about the compressed file. It is intended primarily for compressing
|
||
|
-- individual files but is also sometimes used for network protocols such as
|
||
|
-- HTTP. The format is described in detail in RFC #1952
|
||
|
-- <http://www.ietf.org/rfc/rfc1952.txt>
|
||
|
--
|
||
|
gzipFormat :: Format
|
||
|
gzipFormat = GZip
|
||
|
|
||
|
-- | The zlib format uses a minimal header with a checksum but no other
|
||
|
-- meta-data. It is especially designed for use in network protocols. The
|
||
|
-- format is described in detail in RFC #1950
|
||
|
-- <http://www.ietf.org/rfc/rfc1950.txt>
|
||
|
--
|
||
|
zlibFormat :: Format
|
||
|
zlibFormat = Zlib
|
||
|
|
||
|
-- | The \'raw\' format is just the compressed data stream without any
|
||
|
-- additional header, meta-data or data-integrity checksum. The format is
|
||
|
-- described in detail in RFC #1951 <http://www.ietf.org/rfc/rfc1951.txt>
|
||
|
--
|
||
|
rawFormat :: Format
|
||
|
rawFormat = Raw
|
||
|
|
||
|
-- | This is not a format as such. It enabled zlib or gzip decoding with
|
||
|
-- automatic header detection. This only makes sense for decompression.
|
||
|
--
|
||
|
gzipOrZlibFormat :: Format
|
||
|
gzipOrZlibFormat = GZipOrZlib
|
||
|
|
||
|
formatSupportsDictionary :: Format -> Bool
|
||
|
formatSupportsDictionary Zlib = True
|
||
|
formatSupportsDictionary Raw = True
|
||
|
formatSupportsDictionary _ = False
|
||
|
|
||
|
-- | The compression method
|
||
|
--
|
||
|
data Method = Deflated
|
||
|
deriving (Eq, Ord, Enum, Bounded, Show, Typeable
|
||
|
#if __GLASGOW_HASKELL__ >= 702
|
||
|
, Generic
|
||
|
#endif
|
||
|
)
|
||
|
|
||
|
{-# DEPRECATED Deflated "Use deflateMethod. Method constructors will be hidden in version 0.7" #-}
|
||
|
|
||
|
-- | \'Deflate\' is the only method supported in this version of zlib.
|
||
|
-- Indeed it is likely to be the only method that ever will be supported.
|
||
|
--
|
||
|
deflateMethod :: Method
|
||
|
deflateMethod = Deflated
|
||
|
|
||
|
fromMethod :: Method -> CInt
|
||
|
fromMethod Deflated = #{const Z_DEFLATED}
|
||
|
|
||
|
|
||
|
-- | The compression level parameter controls the amount of compression. This
|
||
|
-- is a trade-off between the amount of compression and the time required to do
|
||
|
-- the compression.
|
||
|
--
|
||
|
data CompressionLevel =
|
||
|
DefaultCompression
|
||
|
| NoCompression
|
||
|
| BestSpeed
|
||
|
| BestCompression
|
||
|
| CompressionLevel Int
|
||
|
deriving (Eq, Show, Typeable
|
||
|
#if __GLASGOW_HASKELL__ >= 702
|
||
|
, Generic
|
||
|
#endif
|
||
|
)
|
||
|
|
||
|
{-# DEPRECATED DefaultCompression "Use defaultCompression. CompressionLevel constructors will be hidden in version 0.7" #-}
|
||
|
{-# DEPRECATED NoCompression "Use noCompression. CompressionLevel constructors will be hidden in version 0.7" #-}
|
||
|
{-# DEPRECATED BestSpeed "Use bestSpeed. CompressionLevel constructors will be hidden in version 0.7" #-}
|
||
|
{-# DEPRECATED BestCompression "Use bestCompression. CompressionLevel constructors will be hidden in version 0.7" #-}
|
||
|
--FIXME: cannot deprecate constructor named the same as the type
|
||
|
{- DEPRECATED CompressionLevel "Use compressionLevel. CompressionLevel constructors will be hidden in version 0.7" -}
|
||
|
|
||
|
-- | The default compression level is 6 (that is, biased towards higher
|
||
|
-- compression at expense of speed).
|
||
|
defaultCompression :: CompressionLevel
|
||
|
defaultCompression = DefaultCompression
|
||
|
|
||
|
-- | No compression, just a block copy.
|
||
|
noCompression :: CompressionLevel
|
||
|
noCompression = CompressionLevel 0
|
||
|
|
||
|
-- | The fastest compression method (less compression)
|
||
|
bestSpeed :: CompressionLevel
|
||
|
bestSpeed = CompressionLevel 1
|
||
|
|
||
|
-- | The slowest compression method (best compression).
|
||
|
bestCompression :: CompressionLevel
|
||
|
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"
|
||
|
|
||
|
fromCompressionLevel :: CompressionLevel -> CInt
|
||
|
fromCompressionLevel DefaultCompression = -1
|
||
|
fromCompressionLevel NoCompression = 0
|
||
|
fromCompressionLevel BestSpeed = 1
|
||
|
fromCompressionLevel BestCompression = 9
|
||
|
fromCompressionLevel (CompressionLevel n)
|
||
|
| n >= 0 && n <= 9 = fromIntegral n
|
||
|
| otherwise = error "CompressLevel must be in the range 1..9"
|
||
|
|
||
|
|
||
|
-- | This specifies the size of the compression window. Larger values of this
|
||
|
-- parameter result in better compression at the expense of higher memory
|
||
|
-- usage.
|
||
|
--
|
||
|
-- The compression window size is the value of the the window bits raised to
|
||
|
-- the power 2. The window bits must be in the range @9..15@ which corresponds
|
||
|
-- to compression window sizes of 512b to 32Kb. The default is 15 which is also
|
||
|
-- the maximum size.
|
||
|
--
|
||
|
-- The total amount of memory used depends on the window bits and the
|
||
|
-- 'MemoryLevel'. See the 'MemoryLevel' for the details.
|
||
|
--
|
||
|
data WindowBits = WindowBits Int
|
||
|
| DefaultWindowBits -- This constructor must be last to make
|
||
|
-- the Ord instance work. The Ord instance
|
||
|
-- is used by the tests.
|
||
|
-- It makse sense because the default value
|
||
|
-- is is also the max value at 15.
|
||
|
deriving (Eq, Ord, Show, Typeable
|
||
|
#if __GLASGOW_HASKELL__ >= 702
|
||
|
, Generic
|
||
|
#endif
|
||
|
)
|
||
|
|
||
|
{-# DEPRECATED DefaultWindowBits "Use defaultWindowBits. WindowBits constructors will be hidden in version 0.7" #-}
|
||
|
--FIXME: cannot deprecate constructor named the same as the type
|
||
|
{- DEPRECATED WindowBits "Use windowBits. WindowBits constructors will be hidden in version 0.7" -}
|
||
|
|
||
|
-- | The default 'WindowBits' is 15 which is also the maximum size.
|
||
|
--
|
||
|
defaultWindowBits :: WindowBits
|
||
|
defaultWindowBits = WindowBits 15
|
||
|
|
||
|
-- | A specific compression window size, specified in bits in the range @9..15@
|
||
|
--
|
||
|
windowBits :: Int -> WindowBits
|
||
|
windowBits n
|
||
|
| n >= 9 && n <= 15 = WindowBits n
|
||
|
| otherwise = error "WindowBits must be in the range 9..15"
|
||
|
|
||
|
fromWindowBits :: Format -> WindowBits-> CInt
|
||
|
fromWindowBits format bits = (formatModifier format) (checkWindowBits bits)
|
||
|
where checkWindowBits DefaultWindowBits = 15
|
||
|
checkWindowBits (WindowBits n)
|
||
|
| n >= 9 && n <= 15 = fromIntegral n
|
||
|
| otherwise = error "WindowBits must be in the range 9..15"
|
||
|
formatModifier Zlib = id
|
||
|
formatModifier GZip = (+16)
|
||
|
formatModifier GZipOrZlib = (+32)
|
||
|
formatModifier Raw = negate
|
||
|
|
||
|
|
||
|
-- | The 'MemoryLevel' parameter specifies how much memory should be allocated
|
||
|
-- for the internal compression state. It is a tradoff between memory usage,
|
||
|
-- compression ratio and compression speed. Using more memory allows faster
|
||
|
-- compression and a better compression ratio.
|
||
|
--
|
||
|
-- The total amount of memory used for compression depends on the 'WindowBits'
|
||
|
-- and the 'MemoryLevel'. For decompression it depends only on the
|
||
|
-- 'WindowBits'. The totals are given by the functions:
|
||
|
--
|
||
|
-- > compressTotal windowBits memLevel = 4 * 2^windowBits + 512 * 2^memLevel
|
||
|
-- > decompressTotal windowBits = 2^windowBits
|
||
|
--
|
||
|
-- For example, for compression with the default @windowBits = 15@ and
|
||
|
-- @memLevel = 8@ uses @256Kb@. So for example a network server with 100
|
||
|
-- concurrent compressed streams would use @25Mb@. The memory per stream can be
|
||
|
-- halved (at the cost of somewhat degraded and slower compressionby) by
|
||
|
-- reducing the @windowBits@ and @memLevel@ by one.
|
||
|
--
|
||
|
-- Decompression takes less memory, the default @windowBits = 15@ corresponds
|
||
|
-- to just @32Kb@.
|
||
|
--
|
||
|
data MemoryLevel =
|
||
|
DefaultMemoryLevel
|
||
|
| MinMemoryLevel
|
||
|
| MaxMemoryLevel
|
||
|
| MemoryLevel Int
|
||
|
deriving (Eq, Show, Typeable
|
||
|
#if __GLASGOW_HASKELL__ >= 702
|
||
|
, Generic
|
||
|
#endif
|
||
|
)
|
||
|
|
||
|
{-# DEPRECATED DefaultMemoryLevel "Use defaultMemoryLevel. MemoryLevel constructors will be hidden in version 0.7" #-}
|
||
|
{-# DEPRECATED MinMemoryLevel "Use minMemoryLevel. MemoryLevel constructors will be hidden in version 0.7" #-}
|
||
|
{-# DEPRECATED MaxMemoryLevel "Use maxMemoryLevel. MemoryLevel constructors will be hidden in version 0.7" #-}
|
||
|
--FIXME: cannot deprecate constructor named the same as the type
|
||
|
{- DEPRECATED MemoryLevel "Use memoryLevel. MemoryLevel constructors will be hidden in version 0.7" -}
|
||
|
|
||
|
-- | The default memory level. (Equivalent to @'memoryLevel' 8@)
|
||
|
--
|
||
|
defaultMemoryLevel :: MemoryLevel
|
||
|
defaultMemoryLevel = MemoryLevel 8
|
||
|
|
||
|
-- | Use minimum memory. This is slow and reduces the compression ratio.
|
||
|
-- (Equivalent to @'memoryLevel' 1@)
|
||
|
--
|
||
|
minMemoryLevel :: MemoryLevel
|
||
|
minMemoryLevel = MemoryLevel 1
|
||
|
|
||
|
-- | Use maximum memory for optimal compression speed.
|
||
|
-- (Equivalent to @'memoryLevel' 9@)
|
||
|
--
|
||
|
maxMemoryLevel :: MemoryLevel
|
||
|
maxMemoryLevel = MemoryLevel 9
|
||
|
|
||
|
-- | A specific level in the range @1..9@
|
||
|
--
|
||
|
memoryLevel :: Int -> MemoryLevel
|
||
|
memoryLevel n
|
||
|
| n >= 1 && n <= 9 = MemoryLevel n
|
||
|
| otherwise = error "MemoryLevel must be in the range 1..9"
|
||
|
|
||
|
fromMemoryLevel :: MemoryLevel -> CInt
|
||
|
fromMemoryLevel DefaultMemoryLevel = 8
|
||
|
fromMemoryLevel MinMemoryLevel = 1
|
||
|
fromMemoryLevel MaxMemoryLevel = 9
|
||
|
fromMemoryLevel (MemoryLevel n)
|
||
|
| n >= 1 && n <= 9 = fromIntegral n
|
||
|
| otherwise = error "MemoryLevel must be in the range 1..9"
|
||
|
|
||
|
|
||
|
-- | The strategy parameter is used to tune the compression algorithm.
|
||
|
--
|
||
|
-- The strategy parameter only affects the compression ratio but not the
|
||
|
-- correctness of the compressed output even if it is not set appropriately.
|
||
|
--
|
||
|
data CompressionStrategy =
|
||
|
DefaultStrategy
|
||
|
| Filtered
|
||
|
| HuffmanOnly
|
||
|
deriving (Eq, Ord, Enum, Bounded, Show, Typeable
|
||
|
#if __GLASGOW_HASKELL__ >= 702
|
||
|
, Generic
|
||
|
#endif
|
||
|
)
|
||
|
|
||
|
{-
|
||
|
-- -- only available in zlib 1.2 and later, uncomment if you need it.
|
||
|
| RLE -- ^ Use 'RLE' to limit match distances to one (run-length
|
||
|
-- encoding). 'RLE' is designed to be almost as fast as
|
||
|
-- 'HuffmanOnly', but give better compression for PNG
|
||
|
-- image data.
|
||
|
| Fixed -- ^ 'Fixed' prevents the use of dynamic Huffman codes,
|
||
|
-- allowing for a simpler decoder for special applications.
|
||
|
-}
|
||
|
|
||
|
{-# DEPRECATED DefaultStrategy "Use defaultStrategy. CompressionStrategy constructors will be hidden in version 0.7" #-}
|
||
|
{-# DEPRECATED Filtered "Use filteredStrategy. CompressionStrategy constructors will be hidden in version 0.7" #-}
|
||
|
{-# DEPRECATED HuffmanOnly "Use huffmanOnlyStrategy. CompressionStrategy constructors will be hidden in version 0.7" #-}
|
||
|
|
||
|
-- | Use this default compression strategy for normal data.
|
||
|
--
|
||
|
defaultStrategy :: CompressionStrategy
|
||
|
defaultStrategy = DefaultStrategy
|
||
|
|
||
|
-- | Use the filtered compression strategy for data produced by a filter (or
|
||
|
-- predictor). Filtered data consists mostly of small values with a somewhat
|
||
|
-- random distribution. In this case, the compression algorithm is tuned to
|
||
|
-- compress them better. The effect of this strategy is to force more Huffman
|
||
|
-- coding and less string matching; it is somewhat intermediate between
|
||
|
-- 'defaultCompressionStrategy' and 'huffmanOnlyCompressionStrategy'.
|
||
|
--
|
||
|
filteredStrategy :: CompressionStrategy
|
||
|
filteredStrategy = Filtered
|
||
|
|
||
|
-- | Use the Huffman-only compression strategy to force Huffman encoding only
|
||
|
-- (no string match).
|
||
|
--
|
||
|
huffmanOnlyStrategy :: CompressionStrategy
|
||
|
huffmanOnlyStrategy = HuffmanOnly
|
||
|
|
||
|
|
||
|
fromCompressionStrategy :: CompressionStrategy -> CInt
|
||
|
fromCompressionStrategy DefaultStrategy = #{const Z_DEFAULT_STRATEGY}
|
||
|
fromCompressionStrategy Filtered = #{const Z_FILTERED}
|
||
|
fromCompressionStrategy HuffmanOnly = #{const Z_HUFFMAN_ONLY}
|
||
|
--fromCompressionStrategy RLE = #{const Z_RLE}
|
||
|
--fromCompressionStrategy Fixed = #{const Z_FIXED}
|
||
|
|
||
|
|
||
|
withStreamPtr :: (Ptr StreamState -> IO a) -> Stream a
|
||
|
withStreamPtr f = do
|
||
|
stream <- getStreamState
|
||
|
unsafeLiftIO (withForeignPtr stream f)
|
||
|
|
||
|
withStreamState :: (StreamState -> IO a) -> Stream a
|
||
|
withStreamState f = do
|
||
|
stream <- getStreamState
|
||
|
unsafeLiftIO (withForeignPtr stream (f . StreamState))
|
||
|
|
||
|
setInAvail :: Int -> Stream ()
|
||
|
setInAvail val = withStreamPtr $ \ptr ->
|
||
|
#{poke z_stream, avail_in} ptr (fromIntegral val :: CUInt)
|
||
|
|
||
|
getInAvail :: Stream Int
|
||
|
getInAvail = liftM (fromIntegral :: CUInt -> Int) $
|
||
|
withStreamPtr (#{peek z_stream, avail_in})
|
||
|
|
||
|
setInNext :: Ptr Word8 -> Stream ()
|
||
|
setInNext val = withStreamPtr (\ptr -> #{poke z_stream, next_in} ptr val)
|
||
|
|
||
|
getInNext :: Stream (Ptr Word8)
|
||
|
getInNext = withStreamPtr (#{peek z_stream, next_in})
|
||
|
|
||
|
setOutFree :: Int -> Stream ()
|
||
|
setOutFree val = withStreamPtr $ \ptr ->
|
||
|
#{poke z_stream, avail_out} ptr (fromIntegral val :: CUInt)
|
||
|
|
||
|
getOutFree :: Stream Int
|
||
|
getOutFree = liftM (fromIntegral :: CUInt -> Int) $
|
||
|
withStreamPtr (#{peek z_stream, avail_out})
|
||
|
|
||
|
setOutNext :: Ptr Word8 -> Stream ()
|
||
|
setOutNext val = withStreamPtr (\ptr -> #{poke z_stream, next_out} ptr val)
|
||
|
|
||
|
#ifdef DEBUG
|
||
|
getOutNext :: Stream (Ptr Word8)
|
||
|
getOutNext = withStreamPtr (#{peek z_stream, next_out})
|
||
|
#endif
|
||
|
|
||
|
inflateInit :: Format -> WindowBits -> Stream ()
|
||
|
inflateInit format bits = do
|
||
|
checkFormatSupported format
|
||
|
err <- withStreamState $ \zstream ->
|
||
|
c_inflateInit2 zstream (fromIntegral (fromWindowBits format bits))
|
||
|
failIfError err
|
||
|
getStreamState >>= unsafeLiftIO . addForeignPtrFinalizer c_inflateEnd
|
||
|
|
||
|
deflateInit :: Format
|
||
|
-> CompressionLevel
|
||
|
-> Method
|
||
|
-> WindowBits
|
||
|
-> MemoryLevel
|
||
|
-> CompressionStrategy
|
||
|
-> Stream ()
|
||
|
deflateInit format compLevel method bits memLevel strategy = do
|
||
|
checkFormatSupported format
|
||
|
err <- withStreamState $ \zstream ->
|
||
|
c_deflateInit2 zstream
|
||
|
(fromCompressionLevel compLevel)
|
||
|
(fromMethod method)
|
||
|
(fromWindowBits format bits)
|
||
|
(fromMemoryLevel memLevel)
|
||
|
(fromCompressionStrategy strategy)
|
||
|
failIfError err
|
||
|
getStreamState >>= unsafeLiftIO . addForeignPtrFinalizer c_deflateEnd
|
||
|
|
||
|
inflate_ :: Flush -> Stream Status
|
||
|
inflate_ flush = do
|
||
|
err <- withStreamState $ \zstream ->
|
||
|
c_inflate zstream (fromFlush flush)
|
||
|
toStatus err
|
||
|
|
||
|
deflate_ :: Flush -> Stream Status
|
||
|
deflate_ flush = do
|
||
|
err <- withStreamState $ \zstream ->
|
||
|
c_deflate zstream (fromFlush flush)
|
||
|
toStatus err
|
||
|
|
||
|
-- | This never needs to be used as the stream's resources will be released
|
||
|
-- automatically when no longer needed, however this can be used to release
|
||
|
-- them early. Only use this when you can guarantee that the stream will no
|
||
|
-- longer be needed, for example if an error occurs or if the stream ends.
|
||
|
--
|
||
|
finalise :: Stream ()
|
||
|
#ifdef __GLASGOW_HASKELL__
|
||
|
--TODO: finalizeForeignPtr is ghc-only
|
||
|
finalise = getStreamState >>= unsafeLiftIO . finalizeForeignPtr
|
||
|
#else
|
||
|
finalise = return ()
|
||
|
#endif
|
||
|
|
||
|
checkFormatSupported :: Format -> Stream ()
|
||
|
checkFormatSupported format = do
|
||
|
version <- unsafeLiftIO (peekCAString =<< c_zlibVersion)
|
||
|
case version of
|
||
|
('1':'.':'1':'.':_)
|
||
|
| format == GZip
|
||
|
|| format == GZipOrZlib
|
||
|
-> fail $ "version 1.1.x of the zlib C library does not support the"
|
||
|
++ " 'gzip' format via the in-memory api, only the 'raw' and "
|
||
|
++ " 'zlib' formats."
|
||
|
_ -> return ()
|
||
|
|
||
|
----------------------
|
||
|
-- The foreign imports
|
||
|
|
||
|
newtype StreamState = StreamState (Ptr StreamState)
|
||
|
|
||
|
-- inflateInit2 and deflateInit2 are actually defined as macros in zlib.h
|
||
|
-- They are defined in terms of inflateInit2_ and deflateInit2_ passing two
|
||
|
-- additional arguments used to detect compatability problems. They pass the
|
||
|
-- version of zlib as a char * and the size of the z_stream struct.
|
||
|
-- If we compile via C then we can avoid this hassle however thats not really
|
||
|
-- kosher since the Haskell FFI is defined at the C ABI level, not the C
|
||
|
-- language level. There is no requirement to compile via C and pick up C
|
||
|
-- headers. So it's much better if we can make it work properly and that'd
|
||
|
-- also allow compiling via ghc's ncg which is a good thing since the C
|
||
|
-- backend is not going to be around forever.
|
||
|
--
|
||
|
-- So we define c_inflateInit2 and c_deflateInit2 here as wrappers around
|
||
|
-- their _ counterparts and pass the extra args.
|
||
|
--
|
||
|
-- As of GHC 7.6, we can import macros directly using the CApiFFI extension.
|
||
|
-- This avoids the need for the hsc2hs #{const_str} construct, which means
|
||
|
-- hsc2hs can run in cross-compilation mode.
|
||
|
|
||
|
##ifdef NON_BLOCKING_FFI
|
||
|
##define SAFTY safe
|
||
|
##else
|
||
|
##define SAFTY unsafe
|
||
|
##endif
|
||
|
|
||
|
#if __GLASGOW_HASKELL__ >= 706
|
||
|
foreign import capi unsafe "zlib.h inflateInit2"
|
||
|
c_inflateInit2 :: StreamState -> CInt -> IO CInt
|
||
|
|
||
|
foreign import capi unsafe "zlib.h deflateInit2"
|
||
|
c_deflateInit2 :: StreamState
|
||
|
-> CInt -> CInt -> CInt -> CInt -> CInt -> IO CInt
|
||
|
#else
|
||
|
foreign import ccall unsafe "zlib.h inflateInit2_"
|
||
|
c_inflateInit2_ :: StreamState -> CInt -> Ptr CChar -> CInt -> IO CInt
|
||
|
|
||
|
c_inflateInit2 :: StreamState -> CInt -> IO CInt
|
||
|
c_inflateInit2 z n =
|
||
|
withCAString #{const_str ZLIB_VERSION} $ \versionStr ->
|
||
|
c_inflateInit2_ z n versionStr (#{const sizeof(z_stream)} :: CInt)
|
||
|
|
||
|
foreign import ccall unsafe "zlib.h deflateInit2_"
|
||
|
c_deflateInit2_ :: StreamState
|
||
|
-> CInt -> CInt -> CInt -> CInt -> CInt
|
||
|
-> Ptr CChar -> CInt
|
||
|
-> IO CInt
|
||
|
|
||
|
c_deflateInit2 :: StreamState
|
||
|
-> CInt -> CInt -> CInt -> CInt -> CInt -> IO CInt
|
||
|
c_deflateInit2 z a b c d e =
|
||
|
withCAString #{const_str ZLIB_VERSION} $ \versionStr ->
|
||
|
c_deflateInit2_ z a b c d e versionStr (#{const sizeof(z_stream)} :: CInt)
|
||
|
#endif
|
||
|
|
||
|
foreign import ccall SAFTY "zlib.h inflate"
|
||
|
c_inflate :: StreamState -> CInt -> IO CInt
|
||
|
|
||
|
foreign import ccall unsafe "zlib.h &inflateEnd"
|
||
|
c_inflateEnd :: FinalizerPtr StreamState
|
||
|
|
||
|
foreign import ccall unsafe "zlib.h inflateReset"
|
||
|
c_inflateReset :: StreamState -> IO CInt
|
||
|
|
||
|
foreign import ccall unsafe "zlib.h deflateSetDictionary"
|
||
|
c_deflateSetDictionary :: StreamState
|
||
|
-> Ptr CChar
|
||
|
-> CUInt
|
||
|
-> IO CInt
|
||
|
|
||
|
foreign import ccall unsafe "zlib.h inflateSetDictionary"
|
||
|
c_inflateSetDictionary :: StreamState
|
||
|
-> Ptr CChar
|
||
|
-> CUInt
|
||
|
-> IO CInt
|
||
|
|
||
|
foreign import ccall SAFTY "zlib.h deflate"
|
||
|
c_deflate :: StreamState -> CInt -> IO CInt
|
||
|
|
||
|
foreign import ccall unsafe "zlib.h &deflateEnd"
|
||
|
c_deflateEnd :: FinalizerPtr StreamState
|
||
|
|
||
|
foreign import ccall unsafe "zlib.h zlibVersion"
|
||
|
c_zlibVersion :: IO CString
|
||
|
|
||
|
foreign import ccall unsafe "zlib.h adler32"
|
||
|
c_adler32 :: CULong
|
||
|
-> Ptr CChar
|
||
|
-> CUInt
|
||
|
-> IO CULong
|