module SocketsAndPipes.Serve.Loop ( run ) where

import SocketsAndPipes.Serve.ForkBracket
    ( forkBracket, Cleanup (..), ThreadId )

import SocketsAndPipes.Serve.Sockets
    ( PeerSocket, PassiveSocket, accept,
      closePeerSocketPolitely, closePeerSocketAbruptly )

import Control.Monad ( forever )

run ::
    (PeerSocket -> IO a) -- ^ What to do when a new client connects.
    -> PassiveSocket -- ^ A socket that is listening for connections.
    -> IO b
run :: (PeerSocket -> IO a) -> PassiveSocket -> IO b
run PeerSocket -> IO a
server PassiveSocket
s = IO ThreadId -> IO b
forall (f :: * -> *) a b. Applicative f => f a -> f b
forever (PassiveSocket -> (PeerSocket -> IO a) -> IO ThreadId
forall a. PassiveSocket -> (PeerSocket -> IO a) -> IO ThreadId
acceptAndFork PassiveSocket
s PeerSocket -> IO a
server)
{- ^
    Perpetually awaits new connections,
    forking a new thread to handle each one.
-}

acceptAndFork ::
    PassiveSocket -- ^ A socket that is listening for connections.
    -> (PeerSocket -> IO a) -- ^ What to do when a new client connects.
    -> IO ThreadId
acceptAndFork :: PassiveSocket -> (PeerSocket -> IO a) -> IO ThreadId
acceptAndFork PassiveSocket
s = IO PeerSocket
-> Cleanup PeerSocket -> (PeerSocket -> IO a) -> IO ThreadId
forall resource x.
IO resource
-> Cleanup resource -> (resource -> IO x) -> IO ThreadId
forkBracket (PassiveSocket -> IO PeerSocket
accept PassiveSocket
s) Cleanup PeerSocket
socketForkBracketCleanup
{- ^
    Waits until a new client shows up to connect to our server.
    When a peer connects, the socket for talking to them will
    be passed to the given function.
-}

socketForkBracketCleanup :: Cleanup PeerSocket
socketForkBracketCleanup :: Cleanup PeerSocket
socketForkBracketCleanup = Cleanup :: forall resource x y.
(resource -> IO x) -> (resource -> IO y) -> Cleanup resource
Cleanup{PeerSocket -> IO ()
onForkFail :: PeerSocket -> IO ()
onForkFail :: PeerSocket -> IO ()
onForkFail, PeerSocket -> IO ()
onThreadEnd :: PeerSocket -> IO ()
onThreadEnd :: PeerSocket -> IO ()
onThreadEnd}
  where
    onThreadEnd :: PeerSocket -> IO ()
onThreadEnd = -- At the end of the thread:
        PeerSocket -> IO ()
closePeerSocketPolitely -- Politely conclude the connection.

    onForkFail :: PeerSocket -> IO ()
onForkFail = -- If an exception occurs before the thread even starts:
        PeerSocket -> IO ()
closePeerSocketAbruptly -- Just close the socket abruptly.
            -- Since this happens on the main thread, we don't
            -- want to take the time to wait for a graceful close.