http-semantics-0.0.0: Network/HTTP/Semantics/FillBuf.hs
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE ScopedTypeVariables #-}
module Network.HTTP.Semantics.FillBuf (
-- * Filling a buffer
Next (..),
DynaNext,
BytesFilled,
StreamingChunk (..),
fillBuilderBodyGetNext,
fillFileBodyGetNext,
fillStreamBodyGetNext,
) where
import Control.Monad
import qualified Data.ByteString as BS
import Data.ByteString.Builder (Builder)
import qualified Data.ByteString.Builder.Extra as B
import Data.ByteString.Internal
import Data.Int (Int64)
import Foreign.Ptr (plusPtr)
import Network.ByteOrder
import Network.HTTP.Semantics.Client
----------------------------------------------------------------
-- type DynaNext = Buffer -> BufferSize -> WindowSize -> IO Next
type DynaNext = Buffer -> BufferSize -> Int -> IO Next
type BytesFilled = Int
data Next
= Next
BytesFilled -- payload length
Bool -- require flushing
(Maybe DynaNext)
----------------------------------------------------------------
data Leftover
= LZero
| LOne B.BufferWriter
| LTwo ByteString B.BufferWriter
----------------------------------------------------------------
data StreamingChunk
= StreamingFinished (IO ())
| StreamingFlush
| StreamingBuilder Builder
----------------------------------------------------------------
fillBuilderBodyGetNext :: Builder -> DynaNext
fillBuilderBodyGetNext bb buf siz lim = do
let room = min siz lim
(len, signal) <- B.runBuilder bb buf room
return $ nextForBuilder len signal
fillFileBodyGetNext
:: PositionRead -> FileOffset -> ByteCount -> IO () -> DynaNext
fillFileBodyGetNext pread start bytecount refresh buf siz lim = do
let room = min siz lim
len <- pread start (mini room bytecount) buf
let len' = fromIntegral len
return $ nextForFile len' pread (start + len) (bytecount - len) refresh
fillStreamBodyGetNext :: IO (Maybe StreamingChunk) -> DynaNext
fillStreamBodyGetNext takeQ buf siz lim = do
let room = min siz lim
(cont, len, reqflush, leftover) <- runStreamBuilder buf room takeQ
return $ nextForStream cont len reqflush leftover takeQ
----------------------------------------------------------------
fillBufBuilder :: Leftover -> DynaNext
fillBufBuilder leftover buf0 siz0 lim = do
let room = min siz0 lim
case leftover of
LZero -> error "fillBufBuilder: LZero"
LOne writer -> do
(len, signal) <- writer buf0 room
getNext len signal
LTwo bs writer
| BS.length bs <= room -> do
buf1 <- copy buf0 bs
let len1 = BS.length bs
(len2, signal) <- writer buf1 (room - len1)
getNext (len1 + len2) signal
| otherwise -> do
let (bs1, bs2) = BS.splitAt room bs
void $ copy buf0 bs1
getNext room (B.Chunk bs2 writer)
where
getNext l s = return $ nextForBuilder l s
nextForBuilder :: BytesFilled -> B.Next -> Next
nextForBuilder len B.Done =
Next len True Nothing -- let's flush
nextForBuilder len (B.More _ writer) =
Next len False $ Just (fillBufBuilder (LOne writer))
nextForBuilder len (B.Chunk bs writer) =
Next len False $ Just (fillBufBuilder (LTwo bs writer))
----------------------------------------------------------------
runStreamBuilder
:: Buffer
-> BufferSize
-> IO (Maybe StreamingChunk)
-> IO
( Bool -- continue
, BytesFilled
, Bool -- require flusing
, Leftover
)
runStreamBuilder buf0 room0 takeQ = loop buf0 room0 0
where
loop buf room total = do
mbuilder <- takeQ
case mbuilder of
Nothing -> return (True, total, False, LZero)
Just (StreamingBuilder builder) -> do
(len, signal) <- B.runBuilder builder buf room
let total' = total + len
case signal of
B.Done -> loop (buf `plusPtr` len) (room - len) total'
B.More _ writer -> return (True, total', False, LOne writer)
B.Chunk bs writer -> return (True, total', False, LTwo bs writer)
Just StreamingFlush -> return (True, total, True, LZero)
Just (StreamingFinished dec) -> do
dec
return (False, total, True, LZero)
fillBufStream :: Leftover -> IO (Maybe StreamingChunk) -> DynaNext
fillBufStream leftover0 takeQ buf0 siz0 lim0 = do
let room0 = min siz0 lim0
case leftover0 of
LZero -> do
(cont, len, reqflush, leftover) <- runStreamBuilder buf0 room0 takeQ
getNext cont len reqflush leftover
LOne writer -> write writer buf0 room0 0
LTwo bs writer
| BS.length bs <= room0 -> do
buf1 <- copy buf0 bs
let len = BS.length bs
write writer buf1 (room0 - len) len
| otherwise -> do
let (bs1, bs2) = BS.splitAt room0 bs
void $ copy buf0 bs1
getNext True room0 False $ LTwo bs2 writer
where
getNext :: Bool -> BytesFilled -> Bool -> Leftover -> IO Next
getNext cont len reqflush l = return $ nextForStream cont len reqflush l takeQ
write
:: (Buffer -> BufferSize -> IO (Int, B.Next))
-> Buffer
-> BufferSize
-> Int
-> IO Next
write writer1 buf room sofar = do
(len, signal) <- writer1 buf room
case signal of
B.Done -> do
(cont, extra, reqflush, leftover) <-
runStreamBuilder (buf `plusPtr` len) (room - len) takeQ
let total = sofar + len + extra
getNext cont total reqflush leftover
B.More _ writer -> do
let total = sofar + len
getNext True total False $ LOne writer
B.Chunk bs writer -> do
let total = sofar + len
getNext True total False $ LTwo bs writer
nextForStream
:: Bool
-> BytesFilled
-> Bool
-> Leftover
-> IO (Maybe StreamingChunk)
-> Next
nextForStream False len reqflush _ _ = Next len reqflush Nothing
nextForStream True len reqflush leftOrZero takeQ =
Next len reqflush $ Just (fillBufStream leftOrZero takeQ)
----------------------------------------------------------------
fillBufFile :: PositionRead -> FileOffset -> ByteCount -> IO () -> DynaNext
fillBufFile pread start bytes refresh buf siz lim = do
let room = min siz lim
len <- pread start (mini room bytes) buf
refresh
let len' = fromIntegral len
return $ nextForFile len' pread (start + len) (bytes - len) refresh
nextForFile
:: BytesFilled -> PositionRead -> FileOffset -> ByteCount -> IO () -> Next
nextForFile 0 _ _ _ _ = Next 0 True Nothing -- let's flush
nextForFile len _ _ 0 _ = Next len False Nothing
nextForFile len pread start bytes refresh =
Next len False $ Just $ fillBufFile pread start bytes refresh
{-# INLINE mini #-}
mini :: Int -> Int64 -> Int64
mini i n
| fromIntegral i < n = fromIntegral i
| otherwise = n