{-# OPTIONS_HADDOCK ignore-exports #-}
-- |The Clients module in the PupEvents framework is used by the main
-- application code to send events to the server. Its main function,
-- 'client' returns a pair of "PQueues" that the application uses to send
-- and receive events (written following the specification defined in the
-- Events module).
module PupEventsClient (client) where
import Network.Socket
import System.IO
import Control.Concurrent
import Control.Concurrent.STM
import qualified Control.Exception as C
import Control.Exception.Base (finally)
import Control.Monad
import Text.Parsec
import PupEventsPQueue
import Data.Functor.Identity
-- |The client function is the main entry point for the client code. It creates a socket, spawns two processes ('sendEvents' and 'recvEvents') to handle outgoing and incoming events, and returns the queues used to communicate with those processes.
client :: Maybe [Char] -- ^ The address to connect to. If ommitted we connect to 'localhost'
-> Int -- ^ The number of priorities in the PQueue
-> (a -> Int) -- ^ A function to return the priority level of an event
-> (t -> t -> String) -- ^ A function to return the string representation of an event
-> [ParsecT [Char] () Data.Functor.Identity.Identity a] -- ^ A list of parsers that return Event objects
-> IO (PQueue t, PQueue a, IO ()) -- ^ We return a pair of "PQueues" to use in communicating events. The first is for all events going to the server, the second is for events coming from the server. We also return a function to manually close both the socket and handle we use to connect to the server.
client Nothing priorities lookupPriority lookupUnHandler parsers = client (Just "localhost") priorities lookupPriority lookupUnHandler parsers
client ip priorities lookupPriority lookupUnHandler parsers=
-- get address info
do addrinfos <- getAddrInfo Nothing ip (Just "1267")
let serveraddr = head addrinfos
-- create socket
sock <- socket (addrFamily serveraddr) Stream 6
setSocketOption sock KeepAlive 1
-- connect to the server
connect sock (addrAddress serveraddr)
-- convert to handle for convenience
handle <- socketToHandle sock ReadWriteMode
hSetBuffering handle NoBuffering
-- create pqueue
outqueue <- makeQueues priorities
inqueue <- makeQueues priorities
-- fork communication threads to server
t1 <- forkOS $ sendEvents handle outqueue lookupUnHandler
t2 <- forkOS $ recvEvents handle inqueue lookupPriority parsers
-- outqueue is the outgoing messages to the server
-- inqueue is the incoming messages from the server
return (outqueue, inqueue, {-(hPutStr stderr "shutting down client...\n" >>-} killThread t1 >> killThread t2 >> hClose handle {->> hPutStr stderr "client shutdown.\n")-})
-- |The sendEvents function handles the sending of all outgoing events to the server. It checks for an event, blocks if none is available, and sends it.
sendEvents :: Handle -- ^ Handle to send events on
-> PQueue t -- ^ "PQueue" to listen on
-> (t -> t -> String) -- ^ A function to convert an Event to a string
-> IO ()
sendEvents handle pqueue lookupUnHandler = finally
(forever $
do event <- atomically $
do e <- getThing pqueue
case e of
Nothing -> retry
Just event -> return event
hPutStr handle (lookupUnHandler event event)
hFlush handle
sendEvents handle pqueue lookupUnHandler)
(do hClose handle
--hPutStr stderr ("sendEvents exiting...\n")
)
-- |The recvEvents function receives events until the connection is closed, parses them, and puts them on the queue to be handled by the application.
recvEvents :: Handle -- ^ Handle listen on
-> PQueue a -- ^ "PQueue" to send events on
-> (a -> Int) -- ^ Function to lookup the priority level of an event
-> [ParsecT [Char] () Data.Functor.Identity.Identity a] -- ^ A list of parsers to apply to parse an event
-> IO ()
recvEvents handle pqueue lookupPriority parsers = finally
-- I don't really understand how these two lines work, but I think its
-- got something to do with lazy evalution. they're from RWH.
(do messages <- hGetContents handle
mapM_ toDispatch (nullLines messages))
(do hClose handle
{-hPutStr stderr "recvEvents exiting...\n"-})
where
-- |Attempts to parse an event and send it to the queue.
toDispatch str =
case parse parseMsg "" str of
Left e -> hPutStr stderr $ "ParseError: " ++ show e ++ "\nString: " ++ show str ++ "\n"
Right event -> atomically $ writeThing pqueue (lookupPriority event) event
-- |Applies the parsers given in 'client' until one of them succeeds.
parseMsg = choice parsers
-- |Separates a string on the character sequence \"\0\0\"
nullLines "" = []
nullLines str = x:nullLines xs
where (x, xs) = splitAt (nullLines' 0 str) str
nullLines' n [] = n
nullLines' n ('\0':'\0':str) = n+2
nullLines' n (s:str) = nullLines' (n+1) str