    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 r <- IO Iter
    iter
                 case r of
                   (Sent Int64
    n IO Iter
    cont) ->
                       do let acc' :: Int64
    acc' = (Int64
    acc Int64 -> Int64 -> Int64
    forall a. Num a => a -> a -> a
    + Int64
    n)
    --                      putStrLn $ "Sent " ++ show acc'
                          Int64
    acc' Int64 -> IO Int64 -> IO Int64
    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)
                          Int64 -> IO Int64
    forall a. a -> IO a
    forall (m :: * -> *) a. Monad m => a -> m a
    return (Int64
    acc Int64 -> Int64 -> Int64
    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 Int64 -> Int64 -> Int64
    forall a. Num a => a -> a -> a
    + Int64
    n)
    --                      putStrLn $ "WouldBlock " ++ (show acc')
                          Int64
    acc' Int64 -> IO Int64 -> IO Int64
    forall a b. a -> b -> b
    `seq` Int64 -> IO Iter -> IO Int64
    runIter' Int64
    acc' IO Iter
    cont