packages feed

bz3-0.1.0.0: src/Codec/Bz3.hs

module Codec.Bz3 ( Bz3Error (..)
                 , decompressFile
                 , compressFile
                 ) where

import           Codec.Bz3.Binary
import           Codec.Bz3.Foreign
import qualified Control.Monad.ST.Lazy        as LazyST
import qualified Control.Monad.ST.Lazy.Unsafe as LazyST
import           Data.Bifunctor               (bimap)
import           Data.Binary.Get              (runGetOrFail)
import           Data.Binary.Put              (runPut)
import qualified Data.ByteString              as BS
import           Data.ByteString.Internal     as BS
import qualified Data.ByteString.Lazy         as BSL
import qualified Data.ByteString.Unsafe       as BS
import           Data.Int                     (Int32)
import           Foreign.ForeignPtr           (ForeignPtr, castForeignPtr,
                                               mallocForeignPtrBytes,
                                               newForeignPtr, withForeignPtr)
import           Foreign.Marshal.Array        (copyArray)
import           Foreign.Ptr                  (castPtr)

newBz3StForeign :: Int32 -> IO (ForeignPtr Bz3St)
newBz3StForeign oSz = castForeignPtr <$> (newForeignPtr bz3Free . castPtr =<< bz3New oSz)

decompressFile :: BSL.ByteString -> BSL.ByteString
decompressFile contents = LazyST.runST $ BSL.fromChunks <$> do
    let Right (bs, _, bSz) = runGetOrFail getFileH contents
    st <- LazyST.unsafeIOToST $ newBz3StForeign (fromIntegral bSz)
    loop st bs
  where
    loop st bs | BSL.null bs = pure []
               | otherwise = do {(dc, rest) <- decNext st bs; (dc:) <$> loop st rest}

-- will fail if bz3st cannot fit exotic big input
compressFile :: BSL.ByteString -> BSL.ByteString
compressFile d = LazyST.runST $ do
    st <- LazyST.unsafeIOToST $ newBz3StForeign bSz
    (sz,bb) <- loop st (BSL.toChunks d)
    let fileH = runPut (putFileH sz)
    pure (fileH<>BSL.fromChunks bb)
  where
    loop _ []     = pure (0, [])
    loop s (b:bs) | BS.length b > feed = let (next,b') = BS.splitAt feed b in loop s (next:b':bs)
    loop s (b:bs) = do
        (csz, e) <- if osz<64
            then pure (0xffffffff, b)
            else encN s b
        let chunk=BSL.toStrict $ runPut (putChunk (Chunk (fromIntegral csz) osz))
        bimap (max osz) ((chunk:).(e:)) <$> loop s bs
      where
        osz=fromIntegral (BS.length b)

    feed=fromIntegral (bz3Bound bSz)

    bSz :: Integral a => a
    bSz=16*1024*1024

encN :: ForeignPtr Bz3St -> BS.ByteString -> LazyST.ST s (Int32, BS.ByteString)
encN st inp = LazyST.unsafeIOToST $
    BS.unsafeUseAsCStringLen inp $ \(d,sz) -> do
        let bufSz = bz3Bound (fromIntegral sz)
        buf <- mallocForeignPtrBytes (fromIntegral bufSz)
        enc <- withForeignPtr buf $ \bb -> do
            copyArray bb d sz
            bz3EncodeBlock st (castPtr bb) (fromIntegral sz)
        pure (enc, BS.BS (castForeignPtr buf) (fromIntegral enc))

decNext :: ForeignPtr Bz3St -> BSL.ByteString -> LazyST.ST s (BS.ByteString, BSL.ByteString)
decNext st inp = LazyST.unsafeIOToST $ do
    let (next, off, Chunk csz osz) = y (runGetOrFail getChunk inp)
    if osz>=64
        then do
            let bufSz = bz3Bound (fromIntegral osz)
                csz32 = fromIntegral csz; osz32 = fromIntegral osz
            buf <- mallocForeignPtrBytes (fromIntegral bufSz)
            let bb=BSL.toStrict (BSL.drop 8 $ BSL.take off inp)
            res <- withForeignPtr buf $ \b -> do
                BS.unsafeUseAsCStringLen bb $ \(p,isz) -> copyArray b (castPtr p) isz
                bz3DecodeBlock st b bufSz csz32 osz32
            case res of
                Right{} -> pure ()
                Left e | e==Bz3Ok -> pure ()
                       | otherwise -> error =<< bz3Strerror st
            pure (BS.BS (castForeignPtr buf) (fromIntegral osz), next)
        else let bb=BSL.toStrict (BSL.drop 8 $ BSL.take off inp) 
             in pure (bb, next)
  where
    y (Right x) = x; y (Left (_, _, e)) = error e