module Network.Socket.SendFile.Iter where import Control.Concurrent (threadWaitWrite) import Data.Int (Int64) import System.Posix.Types (Fd) -- | An iteratee for sendfile -- -- In general, a whole file is not sent by a single call to -- sendfile(), but a series of calls which send successive pieces. -- -- The high-level API in this sendfile library has calls which will -- send the entire file (or an entire requested offset+length), before -- returning. -- -- However, there are instances where you want to be a bit more -- involved in the sending loop. For example, if you want to tickle a -- timeout after each chunk is sent or update a progress bar. -- -- The 'Iter' type gives you that power with out requiring you to -- manage all the low-level details of the sendfile loop. The -- interface is simple and consistant across all platforms. -- -- A call to sendfile() can result in three different states: -- -- (1) the requested number of bytes for that iteration was sent -- successfully, there are more bytes left to send. -- -- (2) some (possibly 0) bytes were sent, but the file descriptor -- would now block if more bytes were written. There are more bytes -- left to send. -- -- (2) All the bytes were sent, and there is nothing left to send. -- -- We handle these three cases by using a type with three -- constructors: -- -- @ -- data Iter -- = Sent Int64 (IO Iter) -- | WouldBlock Int64 Fd (IO Iter) -- | Done Int64 -- @ -- -- All three constructors provide an 'Int64' which represents the -- number of bytes sent for that particular iteration. (Not the total -- byte count). -- -- The 'Sent' and 'WouldBlock' constructors provide 'IO' 'Iter' as their -- final argument. Running this IO action will send the next block of -- data. -- -- The 'WouldBlock' constructor also provides the 'Fd' for the output -- socket. You should not send anymore data until the 'Fd' would not -- block. The easiest way to do that is to use 'threadWaitWrite' to -- suspend the thread until the 'Fd' is available. -- -- A very simple function to drive the Iter might look like: -- -- @ -- runIter :: IO Iter -> IO () -- runIter iter = -- do r <- iter -- case r of -- (Done _n) -> return () -- (Sent _n cont) -> runIter cont -- (WouldBlock _n fd cont) -> -- do threadWaitWrite fd -- runIter cont -- @ -- -- You would use it as the first argument to a *IterWith function, e.g. -- -- @ -- sendFileIterWith runIter outputSocket \"\/path\/to\/file\" 2^16 -- @ -- -- The 'runIter' function provided by this module is similar, but also returns the total number of bytes sent. -- -- NOTE: You must not use the 'Fd' or the 'IO' 'Iter' after the call -- to *IterWith has returned. When the *IterWith functions return, -- the file descriptors may be closed due to finalizers running. data Iter = Sent Int64 (IO Iter) -- ^ number of bytes sent this pass and a continuation to send more | WouldBlock Int64 Fd (IO Iter) -- ^ number of bytes sent, Fd that blocked, continuation to send more. NOTE: The Fd should not be used outside the running of the Iter as it may be freed when the Iter is done | Done Int64 -- ^ number of bytes sent, no more to send -- | A simple function to drive the *IterWith functions. -- It returns the total number of bytes sent. runIter :: IO Iter -> IO Int64 runIter :: IO Iter -> IO Int64 runIter = Int64 -> IO Iter -> IO Int64 runIter' Int64 0 where runIter' :: Int64 -> IO Iter -> IO Int64 runIter' :: Int64 -> IO Iter -> IO Int64 runIter' Int64 acc IO Iter iter = do Iter r <- IO Iter iter case Iter r of (Sent Int64 n IO Iter cont) -> do let acc' :: Int64 acc' = (Int64 acc forall a. Num a => a -> a -> a + Int64 n) -- putStrLn $ "Sent " ++ show acc' Int64 acc' seq :: forall a b. a -> b -> b `seq` Int64 -> IO Iter -> IO Int64 runIter' Int64 acc' IO Iter cont (Done Int64 n) -> do -- putStrLn $ "Done " ++ show (acc + n) forall (m :: * -> *) a. Monad m => a -> m a return (Int64 acc forall a. Num a => a -> a -> a + Int64 n) (WouldBlock Int64 n Fd fd IO Iter cont) -> do Fd -> IO () threadWaitWrite Fd fd let acc' :: Int64 acc' = (Int64 acc forall a. Num a => a -> a -> a + Int64 n) -- putStrLn $ "WouldBlock " ++ (show acc') Int64 acc' seq :: forall a b. a -> b -> b `seq` Int64 -> IO Iter -> IO Int64 runIter' Int64 acc' IO Iter cont