|  | 
|  | 1 | +{-# LANGUAGE ScopedTypeVariables #-}                 | 
|  | 2 | + | 
|  | 3 | +module Pipes.ByteString.Builder | 
|  | 4 | +    ( ChunkSize | 
|  | 5 | +    , build | 
|  | 6 | +    ) where | 
|  | 7 | + | 
|  | 8 | +-- | Use the 'Data.ByteString.Builder' interface to efficiently build up strict | 
|  | 9 | +-- 'ByteString' chunks of bounded size. | 
|  | 10 | + | 
|  | 11 | +import Data.ByteString.Builder as BB | 
|  | 12 | +import Data.ByteString.Builder.Extra as BB | 
|  | 13 | +import qualified Data.ByteString as B | 
|  | 14 | +import Data.ByteString.Unsafe as B | 
|  | 15 | +import Data.Monoid | 
|  | 16 | +import Foreign.Marshal.Alloc (mallocBytes, free) | 
|  | 17 | +import Foreign.Ptr | 
|  | 18 | +import Pipes | 
|  | 19 | +import Data.Word | 
|  | 20 | + | 
|  | 21 | +-- | The default size of chunks to build. | 
|  | 22 | +type ChunkSize = Int | 
|  | 23 | + | 
|  | 24 | +data Buffer = Buffer { tailPtr :: Ptr Word8 | 
|  | 25 | +                     , remaining :: Int | 
|  | 26 | +                     } | 
|  | 27 | + | 
|  | 28 | +-- | Efficiently build up 'ByteString' buffers from 'Builder's of the given  | 
|  | 29 | +-- chunk size. | 
|  | 30 | +build :: forall m r. MonadIO m => ChunkSize -> Producer BB.Builder m r -> Producer B.ByteString m r | 
|  | 31 | +build chunkSz prod0 = newBuffer chunkSz >>= \buf -> nextBuilder buf prod0 | 
|  | 32 | +  where | 
|  | 33 | +    newBuffer :: Int -> Producer B.ByteString m Buffer | 
|  | 34 | +    newBuffer sz = do | 
|  | 35 | +      buf <- liftIO $ mallocBytes sz | 
|  | 36 | +      return $ Buffer buf sz | 
|  | 37 | + | 
|  | 38 | +    -- Await the next Builder | 
|  | 39 | +    nextBuilder :: Buffer -> Producer BB.Builder m r -> Producer B.ByteString m r | 
|  | 40 | +    nextBuilder buf prod = do | 
|  | 41 | +      n <- lift $ next prod | 
|  | 42 | +      case n of | 
|  | 43 | +        Right (builder, prod') -> goWriter buf prod' $ BB.runBuilder builder | 
|  | 44 | +        Left r                 -> finishBuffer buf >> return r | 
|  | 45 | + | 
|  | 46 | +    -- Execute a buffer writer until it is done | 
|  | 47 | +    goWriter :: Buffer -> Producer BB.Builder m r -> BB.BufferWriter -> Producer B.ByteString m r | 
|  | 48 | +    goWriter buf prod writer = do | 
|  | 49 | +      (written, nextB) <- liftIO $ writer (tailPtr buf) (remaining buf) | 
|  | 50 | +      case nextB of | 
|  | 51 | +        Done -> do | 
|  | 52 | +          let buf' = Buffer (tailPtr buf `plusPtr` written) (remaining buf - written) | 
|  | 53 | +          nextBuilder buf' prod | 
|  | 54 | + | 
|  | 55 | +        More minLen writer' -> do | 
|  | 56 | +          finishBuffer buf | 
|  | 57 | +          buf' <- newBuffer (max minLen chunkSz) | 
|  | 58 | +          goWriter buf' prod writer' | 
|  | 59 | + | 
|  | 60 | +        Chunk bs writer' -> do | 
|  | 61 | +          finishBuffer buf | 
|  | 62 | +          yield bs | 
|  | 63 | +          buf' <- newBuffer chunkSz | 
|  | 64 | +          goWriter buf' prod writer' | 
|  | 65 | + | 
|  | 66 | +    -- Yield a buffer | 
|  | 67 | +    finishBuffer :: Buffer -> Producer B.ByteString m () | 
|  | 68 | +    finishBuffer buf = do  | 
|  | 69 | +        let written = chunkSz - remaining buf | 
|  | 70 | +            start = tailPtr buf `plusPtr` (-written) | 
|  | 71 | +        bs <- liftIO $ B.unsafePackCStringFinalizer start written (free start) | 
|  | 72 | +        yield bs | 
0 commit comments