packages feed

cachix-1.6.1: src/Data/Conduit/ByteString.hs

module Data.Conduit.ByteString where

import Conduit (MonadUnliftIO)
import qualified Data.ByteString as BS
import Data.ByteString.Internal (ByteString (PS), mallocByteString)
import Data.ByteString.Unsafe (unsafeIndex)
import Data.Conduit (ConduitT, await, yield, (.|))
import Foreign.ForeignPtr (ForeignPtr)
import Foreign.ForeignPtr.Unsafe (unsafeForeignPtrToPtr)
import Foreign.Storable (pokeByteOff)
import Protolude hiding (hash, yield)

type ChunkSize = Int

minimumChunkSize :: ChunkSize
minimumChunkSize = 5 * 1024 * 1024

chunkStream :: (MonadUnliftIO m) => Maybe ChunkSize -> ConduitT ByteString (Int, ByteString) m ()
chunkStream mChunkSize = do
  processAndChunkOutputRaw chunkSize
    .| enumerateConduit
  where
    chunkSize :: ChunkSize
    chunkSize = maybe minimumChunkSize (max minimumChunkSize) mChunkSize

    -- count from 1
    enumerateConduit :: (Monad m) => ConduitT a (Int, a) m ()
    enumerateConduit = loop 1
      where
        loop i = await >>= maybe (return ()) (go i)
        go i x = do
          yield (i, x)
          loop (i + 1)
    {-# INLINE enumerateConduit #-}

data S = S (ForeignPtr Word8) (Ptr Word8) {-# UNPACK #-} !Int

newS :: ChunkSize -> IO S
newS chunkSize = do
  fptr <- mallocByteString chunkSize
  return (S fptr (unsafeForeignPtrToPtr fptr) 0)

processChunk :: ChunkSize -> ByteString -> S -> IO ([ByteString], S)
processChunk chunkSize input =
  loop identity 0
  where
    loop front idxIn s@(S fptr ptr idxOut)
      | idxIn >= BS.length input = return (front [], s)
      | otherwise = do
          pokeByteOff ptr idxOut (unsafeIndex input idxIn)
          let idxOut' = idxOut + 1
              idxIn' = idxIn + 1
          if idxOut' >= chunkSize
            then do
              let bs = PS fptr 0 idxOut'
              s' <- newS chunkSize
              loop (front . (bs :)) idxIn' s'
            else loop front idxIn' (S fptr ptr idxOut')

processAndChunkOutputRaw :: (MonadIO m) => ChunkSize -> ConduitT ByteString ByteString m ()
processAndChunkOutputRaw chunkSize =
  liftIO (newS chunkSize) >>= loop
  where
    loop s@(S fptr _ len) = do
      mbs <- await
      case mbs of
        Nothing -> yield $ PS fptr 0 len
        Just bs -> do
          (bss, s') <- liftIO $ processChunk chunkSize bs s
          mapM_ yield bss
          loop s'