module LibLzma
( LzmaStream
, LzmaRet(..)
, IntegrityCheck(..)
, CompressionLevel(..)
, newDecodeLzmaStream
, DecompressParams(..)
, defaultDecompressParams
, newEncodeLzmaStream
, CompressParams(..)
, defaultCompressParams
, runLzmaStream
, LzmaAction(..)
) where
import Control.Applicative
import Control.Exception
import Control.Monad
import Control.Monad.ST.Strict (ST)
import Control.Monad.ST.Unsafe (unsafeIOToST)
import Data.ByteString (ByteString)
import qualified Data.ByteString as BS
import qualified Data.ByteString.Internal as BS
import qualified Data.ByteString.Unsafe 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,Read,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,Read,Show,Typeable)
fromIntegrityCheck :: IntegrityCheck -> Int
fromIntegrityCheck lc = case lc of
IntegrityCheckNone -> 0
IntegrityCheckCrc32 -> 1
IntegrityCheckCrc64 -> 4
IntegrityCheckSha256 -> 10
data CompressionLevel = CompressionLevel0
| CompressionLevel1
| CompressionLevel2
| CompressionLevel3
| CompressionLevel4
| CompressionLevel5
| CompressionLevel6
| CompressionLevel7
| CompressionLevel8
| CompressionLevel9
deriving (Eq,Ord,Read,Show,Enum,Typeable)
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 -> ST s (Either LzmaRet LzmaStream)
newDecodeLzmaStream (DecompressParams {..}) = unsafeIOToST $ 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 -> ST s (Either LzmaRet LzmaStream)
newEncodeLzmaStream (CompressParams {..}) = unsafeIOToST $ 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
data LzmaAction = LzmaRun
| LzmaSyncFlush
| LzmaFullFlush
| LzmaFinish
deriving (Eq,Show)
runLzmaStream :: LzmaStream -> ByteString -> LzmaAction -> Int -> ST s (LzmaRet,Int,ByteString)
runLzmaStream (LS ls) ibs action0 buflen
| buflen <= 0 = return (LzmaRetOptionsError,0,BS.empty)
| otherwise = unsafeIOToST $ withForeignPtr ls $ \lsptr ->
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 = case action0 of
LzmaRun -> 0
LzmaSyncFlush -> 1
LzmaFullFlush -> 2
LzmaFinish -> 3
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 ())