module Codec.Archive.Unpack.Lazy ( readArchiveBSL , unpackToDirLazy ) where import Codec.Archive.Common import Codec.Archive.Foreign import Codec.Archive.Types import Codec.Archive.Unpack import Control.Monad (void, (<=<)) import Data.ByteString (useAsCStringLen) import qualified Data.ByteString.Lazy as BSL import Data.Functor (($>)) import Data.IORef (modifyIORef, newIORef, readIORef) import Foreign.C.Types import Foreign.Marshal.Alloc (free, mallocBytes) import Foreign.Ptr import Foreign.Storable (poke) import System.IO.Unsafe (unsafePerformIO) foreign import ccall memcpy :: Ptr a -- ^ Destination -> Ptr b -- ^ Source -> CSize -- ^ Size -> IO (Ptr a) -- ^ Pointer to destination -- | In general, this will be more efficient than 'unpackToDir' -- -- @since 1.0.4.0 unpackToDirLazy :: FilePath -- ^ Directory to unpack in -> BSL.ByteString -- ^ 'BSL.ByteString' containing archive -> IO () unpackToDirLazy fp bs = do (a, act) <- bslToArchive bs unpackEntriesFp a fp void $ archive_read_free a act -- | Read an archive lazily. The format of the archive is automatically -- detected. -- -- In general, this will be more efficient than 'readArchiveBS' -- -- @since 1.0.4.0 readArchiveBSL :: BSL.ByteString -> [Entry] readArchiveBSL = unsafePerformIO . (actFreeCallback hsEntries <=< bslToArchive) -- | Lazily stream a 'BSL.ByteString' -- @since 1.0.4.0 bslToArchive :: BSL.ByteString -> IO (Ptr Archive, IO ()) -- ^ Returns an 'IO' action to be used to clean up after we're done with the archive bslToArchive bs = do a <- archive_read_new void $ archive_read_support_format_all a bufPtr <- mallocBytes (32 * 1024) -- default to 32k byte chunks; should really do something more rigorous bsChunksRef <- newIORef bsChunks rc <- mkReadCallback (readBSL bsChunksRef bufPtr) cc <- mkCloseCallback (\_ ptr -> freeHaskellFunPtr rc *> free ptr $> archiveOk) nothingPtr <- mallocBytes 0 sequence_ [ archive_read_set_read_callback a rc , archive_read_set_close_callback a cc , archive_read_set_callback_data a nothingPtr , archive_read_open1 a ] pure (a, freeHaskellFunPtr cc *> free bufPtr) where readBSL bsRef bufPtr _ _ dataPtr = do bs' <- readIORef bsRef case bs' of [] -> pure 0 (x:_) -> do modifyIORef bsRef tail useAsCStringLen x $ \(charPtr, sz) -> do void $ memcpy bufPtr charPtr (fromIntegral sz) poke dataPtr bufPtr $> fromIntegral sz bsChunks = BSL.toChunks bs