module LibLzma
( LzmaStream
, LzmaRet(..)
, IntegrityCheck(..)
, CompressionLevel(..)
, newDecodeLzmaStream
, DecompressParams(..)
, defaultDecompressParams
, newEncodeLzmaStream
, CompressParams(..)
, defaultCompressParams
, runLzmaStream
, CompressStream(..)
, compressIO
) where
import Control.Applicative
import Control.Exception
import Control.Monad
import Data.ByteString (ByteString)
import qualified Data.ByteString.Internal as BS
import qualified Data.ByteString.Unsafe as BS
import qualified Data.ByteString as BS
import Data.Typeable
import Foreign
import Prelude
newtype LzmaStream = LS (ForeignPtr LzmaStream)
data LzmaRet = LzmaRetOK
| LzmaRetStreamEnd
| LzmaRetUnsupportedCheck
| LzmaRetGetCheck
| LzmaRetMemError
| LzmaRetMemlimitError
| LzmaRetFormatError
| LzmaRetOptionsError
| LzmaRetDataError
| LzmaRetBufError
| LzmaRetProgError
deriving (Eq,Ord,Show,Typeable)
instance Exception LzmaRet
toLzmaRet :: Int -> Maybe LzmaRet
toLzmaRet i = case i of
(0) -> Just LzmaRetOK
(1) -> Just LzmaRetStreamEnd
(3) -> Just LzmaRetUnsupportedCheck
(4) -> Just LzmaRetGetCheck
(5) -> Just LzmaRetMemError
(6) -> Just LzmaRetMemlimitError
(7) -> Just LzmaRetFormatError
(8) -> Just LzmaRetOptionsError
(9) -> Just LzmaRetDataError
(10) -> Just LzmaRetBufError
(11) -> Just LzmaRetProgError
_ -> Nothing
data IntegrityCheck = IntegrityCheckNone
| IntegrityCheckCrc32
| IntegrityCheckCrc64
| IntegrityCheckSha256
deriving (Eq,Ord,Show)
data CompressionLevel = CompressionLevel0
| CompressionLevel1
| CompressionLevel2
| CompressionLevel3
| CompressionLevel4
| CompressionLevel5
| CompressionLevel6
| CompressionLevel7
| CompressionLevel8
| CompressionLevel9
deriving (Eq,Ord,Show,Enum)
fromIntegrityCheck :: IntegrityCheck -> Int
fromIntegrityCheck lc = case lc of
IntegrityCheckNone -> 0
IntegrityCheckCrc32 -> 1
IntegrityCheckCrc64 -> 4
IntegrityCheckSha256 -> 10
data DecompressParams = DecompressParams
{ decompressTellNoCheck :: !Bool
, decompressTellUnsupportedCheck :: !Bool
, decompressTellAnyCheck :: !Bool
, decompressConcatenated :: !Bool
, decompressAutoDecoder :: !Bool
, decompressMemLimit :: !Word64
} deriving (Eq,Show)
defaultDecompressParams :: DecompressParams
defaultDecompressParams = DecompressParams {..}
where
decompressTellNoCheck = False
decompressTellUnsupportedCheck = False
decompressTellAnyCheck = False
decompressConcatenated = True
decompressAutoDecoder = False
decompressMemLimit = maxBound
data CompressParams = CompressParams
{ compressIntegrityCheck :: !IntegrityCheck
, compressLevel :: !CompressionLevel
, compressLevelExtreme :: !Bool
} deriving (Eq,Show)
defaultCompressParams :: CompressParams
defaultCompressParams = CompressParams {..}
where
compressIntegrityCheck = IntegrityCheckCrc64
compressLevel = CompressionLevel6
compressLevelExtreme = False
newDecodeLzmaStream :: DecompressParams -> IO (Either LzmaRet LzmaStream)
newDecodeLzmaStream (DecompressParams {..}) = do
fp <- mallocForeignPtrBytes ((136))
addForeignPtrFinalizer c_hs_lzma_done_funptr fp
rc <- withForeignPtr fp (\ptr -> c_hs_lzma_init_decoder ptr decompressAutoDecoder decompressMemLimit flags')
rc' <- maybe (fail "newDecodeLzmaStream: invalid return code") pure $ toLzmaRet rc
return $ case rc' of
LzmaRetOK -> Right (LS fp)
_ -> Left rc'
where
flags' =
(if decompressTellNoCheck then (1) else 0) .|.
(if decompressTellUnsupportedCheck then (2) else 0) .|.
(if decompressTellAnyCheck then (4) else 0) .|.
(if decompressConcatenated then (8) else 0)
newEncodeLzmaStream :: CompressParams -> IO (Either LzmaRet LzmaStream)
newEncodeLzmaStream (CompressParams {..}) = do
fp <- mallocForeignPtrBytes ((136))
addForeignPtrFinalizer c_hs_lzma_done_funptr fp
rc <- withForeignPtr fp (\ptr -> c_hs_lzma_init_encoder ptr preset check)
rc' <- maybe (fail "newDecodeLzmaStream: invalid return code") pure $ toLzmaRet rc
return $ case rc' of
LzmaRetOK -> Right (LS fp)
_ -> Left rc'
where
preset = fromIntegral (fromEnum compressLevel) .|.
(if compressLevelExtreme then (2147483648) else 0)
check = fromIntegrityCheck compressIntegrityCheck
runLzmaStream :: LzmaStream -> ByteString -> Bool -> Int -> IO (LzmaRet,Int,ByteString)
runLzmaStream (LS ls) ibs finish buflen
| buflen <= 0 = fail "runLzmaStream: invalid buflen argument"
| otherwise = withForeignPtr ls $ \lsptr -> do
BS.unsafeUseAsCStringLen ibs $ \(ibsptr, ibslen) -> do
(obuf,rc) <- BS.createAndTrim' buflen $ \bufptr -> do
rc' <- c_hs_lzma_run lsptr action (castPtr ibsptr) ibslen bufptr buflen
rc'' <- maybe (fail "runLzmaStream: invalid return code") pure $ toLzmaRet rc'
availOut <- ((\hsc_ptr -> peekByteOff hsc_ptr 32)) lsptr
unless (buflen >= availOut && availOut >= 0) (fail "runLzmaStream: invalid avail_out")
let produced = buflen availOut
return (0, produced, rc'')
availIn <- ((\hsc_ptr -> peekByteOff hsc_ptr 8)) lsptr
unless (ibslen >= availIn && availIn >= 0) (fail "runLzmaStream: invalid avail_in")
let consumed = ibslen availIn
return (rc, fromIntegral consumed, obuf)
where
action = if finish then (3) else (0)
foreign import ccall "hs_lzma_init_decoder"
c_hs_lzma_init_decoder :: Ptr LzmaStream -> Bool -> Word64 -> Word32 -> IO Int
foreign import ccall "hs_lzma_init_encoder"
c_hs_lzma_init_encoder :: Ptr LzmaStream -> Word32 -> Int -> IO Int
foreign import ccall "hs_lzma_run"
c_hs_lzma_run :: Ptr LzmaStream -> Int -> Ptr Word8 -> Int -> Ptr Word8 -> Int -> IO Int
foreign import ccall "&hs_lzma_done"
c_hs_lzma_done_funptr :: FunPtr (Ptr LzmaStream -> IO ())
data CompressStream m =
CompressInputRequired (ByteString -> m (CompressStream m))
| CompressOutputAvailable !ByteString (m (CompressStream m))
| CompressStreamEnd
compressIO :: CompressParams -> IO (CompressStream IO)
compressIO parms = newEncodeLzmaStream parms >>= either throwIO go
where
bUFSIZ = 32752
go :: LzmaStream -> IO (CompressStream IO)
go ls = return $ CompressInputRequired goInput
where
goInput :: ByteString -> IO (CompressStream IO)
goInput chunk
| BS.null chunk = goFinish
| otherwise = do
(rc, used, obuf) <- runLzmaStream ls chunk False bUFSIZ
unless (used > 0) $ fail "compressIO: input chunk not consumed"
let chunk' = BS.drop used chunk
case rc of
LzmaRetOK
| BS.null obuf -> if BS.null chunk'
then return (CompressInputRequired goInput)
else goInput chunk'
| otherwise -> return (CompressOutputAvailable obuf
(if BS.null chunk'
then return (CompressInputRequired goInput)
else goInput chunk'))
_ -> throwIO rc
goFinish :: IO (CompressStream IO)
goFinish = do
(rc, 0, obuf) <- runLzmaStream ls BS.empty True bUFSIZ
case rc of
LzmaRetOK
| BS.null obuf -> fail "compressIO: empty output chunk"
| otherwise -> return (CompressOutputAvailable obuf goFinish)
LzmaRetStreamEnd
| BS.null obuf -> return CompressStreamEnd
| otherwise -> return (CompressOutputAvailable obuf (return CompressStreamEnd))
_ -> throwIO rc