module Data.Iteratee.BZip
(
enumCompress,
enumDecompress,
BZipParamsException(..),
BZipException(..),
CompressParams(..),
defaultCompressParams,
DecompressParams(..),
defaultDecompressParams,
BlockSize(..),
WorkFactor(..)
)
where
import Control.Exception
import Control.Monad.Trans
import Data.ByteString as BS
import Data.ByteString.Internal
import Data.Iteratee
import Data.Typeable
import Foreign
import Foreign.C
data BZipParamsException
= IncorrectBlockSize !Int
| IncorrectWorkFactor !Int
| IncorrectBufferSize !Int
deriving (Eq,Typeable)
data BZipException
= ConfigError
| MemError
| DataError
| DataErrorMagic
| Unexpected !Int
| IncorrectState
deriving (Eq,Typeable)
data BZipFlush = BZipFlush
deriving (Eq,Typeable)
instance Show BZipFlush where
show BZipFlush = "bzlib: flush requested"
instance Exception BZipFlush
fromFlush :: BZipFlush -> CInt
fromFlush BZipFlush = 1
instance Show BZipParamsException where
show (IncorrectBlockSize size)
= "bzlib: incorrect block size " ++ show size
show (IncorrectWorkFactor wf)
= "bzlib: incorrect work factor " ++ show wf
show (IncorrectBufferSize size)
= "bzlib: incorrect buffer size " ++ show size
instance Show BZipException where
show ConfigError = "bzlib: library is not configure properly"
show MemError = "bzlib: memory allocation failed"
show DataError = "bzlib: input is corrupted"
show DataErrorMagic = "bzlib: magic number does not match"
show (Unexpected n) = "bzlib: unexpected error " ++ show n
show IncorrectState = "bzlib: incorrect state"
instance Exception BZipParamsException
instance Exception BZipException
newtype BZStream = BZStream (ForeignPtr BZStream)
withBZStream :: BZStream -> (Ptr BZStream -> IO a) -> IO a
withBZStream (BZStream fptr) = withForeignPtr fptr
data CompressParams = CompressParams {
compressBlockSize :: BlockSize,
compressWorkFactor :: WorkFactor,
compressBufferSize :: !Int
}
defaultCompressParams :: CompressParams
defaultCompressParams
= CompressParams DefaultBlockSize DefaultWorkFactor (8*1024)
data DecompressParams = DecompressParams {
decompressSaveMemory :: !Bool,
decompressBufferSize :: !Int
}
defaultDecompressParams :: DecompressParams
defaultDecompressParams = DecompressParams False (8*1024)
data BlockSize
= DefaultBlockSize
| BestSpeed
| BestCompression
| CompressionLevel !Int
data WorkFactor
= DefaultWorkFactor
| WorkFactor !Int
fromBlockSize :: BlockSize -> Either BZipParamsException CInt
fromBlockSize DefaultBlockSize = Right $! 6
fromBlockSize BestSpeed = Right $! 1
fromBlockSize BestCompression = Right $! 9
fromBlockSize (CompressionLevel lvl)
| lvl < 0 || lvl > 250 = Left $! IncorrectBlockSize $! fromIntegral lvl
| otherwise = Right $! fromIntegral lvl
fromWorkFactor :: WorkFactor -> Either BZipParamsException CInt
fromWorkFactor DefaultWorkFactor = Right $! 0
fromWorkFactor (WorkFactor wf)
| wf < 0 || wf > 250 = Left $! IncorrectWorkFactor $! fromIntegral wf
| otherwise = Right $! fromIntegral wf
fromErrno :: CInt -> Either BZipException Bool
fromErrno (0) = Right $! True
fromErrno (1) = Right $! True
fromErrno (2) = Right $! True
fromErrno (3) = Right $! True
fromErrno (4) = Right $! False
fromErrno (9) = Left $! ConfigError
fromErrno (3) = Left $! MemError
fromErrno (4) = Left $! DataError
fromErrno (5) = Left $! DataErrorMagic
fromErrno n = Left $! Unexpected $! fromIntegral n
newtype Initial = Initial BZStream
data EmptyIn = EmptyIn !BZStream !ByteString
data FullOut = FullOut !BZStream !ByteString
data Invalid = Invalid !BZStream !ByteString !ByteString
data Finishing = Finishing !BZStream !ByteString
data Flushing = Flushing !BZStream !BZipFlush !ByteString
withByteString :: ByteString -> (Ptr Word8 -> Int -> IO a) -> IO a
withByteString (PS ptr off len) f
= withForeignPtr ptr (\ptr' -> f (ptr' `plusPtr` off) len)
mkByteString :: MonadIO m => Int -> m ByteString
mkByteString s = liftIO $ create s (\_ -> return ())
putOutBuffer :: Int -> BZStream -> IO ByteString
putOutBuffer size bzstr = do
_out <- mkByteString size
withByteString _out $ \ptr len -> withBZStream bzstr $ \bzptr -> do
(\hsc_ptr -> pokeByteOff hsc_ptr 24) bzptr ptr
(\hsc_ptr -> pokeByteOff hsc_ptr 32) bzptr len
return _out
putInBuffer :: BZStream -> ByteString -> IO ()
putInBuffer bzstr _in
= withByteString _in $ \ptr len -> withBZStream bzstr $ \bzptr -> do
(\hsc_ptr -> pokeByteOff hsc_ptr 0) bzptr ptr
(\hsc_ptr -> pokeByteOff hsc_ptr 8) bzptr len
pullOutBuffer :: BZStream -> ByteString -> IO ByteString
pullOutBuffer bzstr _out = withByteString _out $ \ptr _ -> do
next_out <- withBZStream bzstr $ \bzptr -> (\hsc_ptr -> peekByteOff hsc_ptr 24) bzptr
return $! BS.take (next_out `minusPtr` ptr) _out
pullInBuffer :: BZStream -> ByteString -> IO ByteString
pullInBuffer bzstr _in = withByteString _in $ \ptr _ -> do
next_in <- withBZStream bzstr $ \bzptr -> (\hsc_ptr -> peekByteOff hsc_ptr 0) bzptr
return $! BS.drop (next_in `minusPtr` ptr) _in
insertOut :: MonadIO m
=> Int
-> (BZStream -> CInt -> IO CInt)
-> Initial
-> Enumeratee ByteString ByteString m a
insertOut size runf (Initial bzstr) iter = do
_out <- liftIO $ putOutBuffer size bzstr
fill size runf (EmptyIn bzstr _out) iter
fill :: MonadIO m
=> Int
-> (BZStream -> CInt -> IO CInt)
-> EmptyIn
-> Enumeratee ByteString ByteString m a
fill size runf (EmptyIn bzstr _out) iter
= let fill' (Chunk _in)
| not (BS.null _in) = do
liftIO $ putInBuffer bzstr _in
doRun size runf (Invalid bzstr _in _out) iter
| otherwise = fillI
fill' (EOF Nothing) = do
out <- liftIO $ pullOutBuffer bzstr _out
iter' <- lift $ enumPure1Chunk out iter
finish size runf (Finishing bzstr BS.empty) iter'
fill' (EOF (Just err))
= case fromException err of
Just err' -> flush size runf (Flushing bzstr err' _out) iter
Nothing -> throwRecoverableErr err fill'
fillI = liftI fill'
in fillI
swapOut :: MonadIO m
=> Int
-> (BZStream -> CInt -> IO CInt)
-> FullOut
-> Enumeratee ByteString ByteString m a
swapOut size runf (FullOut bzstr _in) iter = do
_out <- liftIO $ putOutBuffer size bzstr
doRun size runf (Invalid bzstr _in _out) iter
doRun :: MonadIO m
=> Int
-> (BZStream -> CInt -> IO CInt)
-> Invalid
-> Enumeratee ByteString ByteString m a
doRun size runf (Invalid bzstr _in _out) iter = do
status <- liftIO $ runf bzstr 0
case fromErrno status of
Left err -> do
_ <- joinIM $ enumErr err iter
throwErr (toException err)
Right False -> do
remaining <- liftIO $ pullInBuffer bzstr _in
out <- liftIO $ pullOutBuffer bzstr _out
iter' <- lift $ enumPure1Chunk out iter
idone iter' (Chunk remaining)
Right True -> do
(avail_in, avail_out) <- liftIO $ withBZStream bzstr $ \bzptr -> do
avail_in <- liftIO $ (\hsc_ptr -> peekByteOff hsc_ptr 8) bzptr
avail_out <- liftIO $ (\hsc_ptr -> peekByteOff hsc_ptr 32) bzptr
return (avail_in, avail_out) :: IO (CInt, CInt)
case avail_out of
0 -> do
out <- liftIO $ pullOutBuffer bzstr _out
iter' <- lift $ enumPure1Chunk out iter
case avail_in of
0 -> insertOut size runf (Initial bzstr) iter'
_ -> swapOut size runf (FullOut bzstr _in) iter'
_ -> case avail_in of
0 -> fill size runf (EmptyIn bzstr _out) iter
_ -> do
_ <- joinIM $ enumErr IncorrectState iter
throwErr (toException IncorrectState)
flush :: MonadIO m
=> Int
-> (BZStream -> CInt -> IO CInt)
-> Flushing
-> Enumeratee ByteString ByteString m a
flush size runf (Flushing bzstr _flush _out) iter = do
status <- liftIO $ runf bzstr (fromFlush _flush)
case fromErrno status of
Left err -> do
_ <- joinIM $ enumErr err iter
throwErr (toException err)
Right False -> do
out <- liftIO $ pullOutBuffer bzstr _out
iter' <- lift $ enumPure1Chunk out iter
idone iter' (Chunk BS.empty)
Right True -> do
(_avail_in, avail_out) <- liftIO $ withBZStream bzstr $ \bzptr -> do
avail_in <- liftIO $ (\hsc_ptr -> peekByteOff hsc_ptr 8) bzptr
avail_out <- liftIO $ (\hsc_ptr -> peekByteOff hsc_ptr 32) bzptr
return (avail_in, avail_out) :: IO (CInt, CInt)
case avail_out of
0 -> do
out <- liftIO $ pullOutBuffer bzstr _out
iter' <- lift $ enumPure1Chunk out iter
out' <- liftIO $ putOutBuffer size bzstr
flush size runf (Flushing bzstr _flush out') iter'
_ -> insertOut size runf (Initial bzstr) iter
finish :: MonadIO m
=> Int
-> (BZStream -> CInt -> IO CInt)
-> Finishing
-> Enumeratee ByteString ByteString m a
finish size runf fin@(Finishing bzstr _in) iter = do
_out <- liftIO $ putOutBuffer size bzstr
status <- liftIO $ runf bzstr 2
case fromErrno status of
Left err -> do
_ <- lift $ enumErr err iter
throwErr (toException err)
Right False -> do
remaining <- liftIO $ pullInBuffer bzstr _in
out <- liftIO $ pullOutBuffer bzstr _out
iter' <- lift $ enumPure1Chunk out iter
idone iter' (Chunk remaining)
Right True -> do
(_avail_in, avail_out) <- liftIO $ withBZStream bzstr $ \bzptr -> do
avail_in <- liftIO $ (\hsc_ptr -> peekByteOff hsc_ptr 8) bzptr
avail_out <- liftIO $ (\hsc_ptr -> peekByteOff hsc_ptr 32) bzptr
return (avail_in, avail_out) :: IO (CInt, CInt)
case avail_out of
0 -> do
out <- liftIO $ pullOutBuffer bzstr _out
iter' <- lift $ enumPure1Chunk out iter
finish size runf fin iter'
_ -> do
_ <- lift $ enumErr (toException IncorrectState) iter
throwErr $! toException IncorrectState
foreign import ccall unsafe "BZ2_bzCompressInit"
compressInit :: Ptr BZStream -> CInt -> CInt
-> CInt -> IO CInt
foreign import ccall unsafe "BZ2_bzDecompressInit"
decompressInit :: Ptr BZStream -> CInt -> CInt
-> IO CInt
foreign import ccall unsafe "BZ2_bzCompress"
compress :: Ptr BZStream -> CInt -> IO CInt
foreign import ccall unsafe "BZ2_bzDecompress"
decompress :: Ptr BZStream -> IO CInt
foreign import ccall unsafe "&BZ2_bzCompressEnd"
compressEnd :: FunPtr (Ptr BZStream -> IO ())
foreign import ccall unsafe "&BZ2_bzDecompressEnd"
decompressEnd :: FunPtr (Ptr BZStream -> IO ())
compress' :: BZStream -> CInt -> IO CInt
compress' bz f = withBZStream bz $ \p -> compress p f
decompress' :: BZStream -> CInt -> IO CInt
decompress' bz _ = withBZStream bz decompress
verboseLevel :: CInt
verboseLevel = 0
mkCompress :: CompressParams -> IO (Either BZipParamsException Initial)
mkCompress (CompressParams blk wf _)
= case fromBlockSize blk of
Left err -> return $! Left $! err
Right blk' -> case fromWorkFactor wf of
Left err -> return $! Left $! err
Right wf' -> do
bzstr <- mallocForeignPtrBytes (80)
withForeignPtr bzstr $ \bzptr -> do
memset (castPtr bzptr) 0 (80)
compressInit bzptr blk' verboseLevel wf' `finally`
addForeignPtrFinalizer compressEnd bzstr
return $! Right $! Initial $ BZStream bzstr
mkDecompress :: DecompressParams -> IO (Either BZipParamsException Initial)
mkDecompress (DecompressParams small _) = do
bzstr <- mallocForeignPtrBytes (80)
withForeignPtr bzstr $ \bzptr -> do
memset (castPtr bzptr) 0 (80)
decompressInit bzptr verboseLevel (if small then 0 else 1) `finally`
addForeignPtrFinalizer decompressEnd bzstr
return $! Right $! Initial $ BZStream bzstr
enumCompress :: MonadIO m
=> CompressParams
-> Enumeratee ByteString ByteString m a
enumCompress cp@(CompressParams _ _ size) iter = do
cmp <- liftIO $ mkCompress cp
case cmp of
Left err -> do
_ <- lift $ enumErr err iter
throwErr (toException err)
Right init' -> insertOut size compress' init' iter
enumDecompress :: MonadIO m
=> DecompressParams
-> Enumeratee ByteString ByteString m a
enumDecompress dp@(DecompressParams _ size) iter = do
dcmp <- liftIO $ mkDecompress dp
case dcmp of
Left err -> do
_ <- lift $ enumErr err iter
throwErr (toException err)
Right init' -> insertOut size decompress' init' iter