-- ---------------------------------------------------------------------------- {- | Module : Holumbus.Network.DoWithServer Copyright : Copyright (C) 2009 Sebastian Reese License : MIT Maintainer : Sebastian Reese (str@holumbus.org) Stability : experimental Portability: portable Version : 0.1 This module provides an interface to start a server listeing on a tcp socket. ( Module is based on simple tcp server found here http://sequence.complete.org/node/258 ) You have to implement 2 functions type ServerAction a = a -> Client a -> [Client a] -> IO [Client a] and type LineConverter a = String -> a to start a server use the doWithServer :: (Show a) => Int -> ServerAction a -> LineConverter a -> String -> IO () function. -} -- ---------------------------------------------------------------------------- module Holumbus.Network.DoWithServer {-# DEPRECATED "this module will be remove in the next release, please use the packages from Holumbus.Distribution.*" #-} ( Client (..) , ServerAction , LineConverter , doWithServer ) where --import Holumbus.Common.Logging import System.Log.Logger import System.IO import Control.Concurrent import Control.Concurrent.STM import Control.Monad (liftM) import Network import Holumbus.Common.Utils ( handleAll ) localLogger :: String localLogger = "Holumbus.Network.DoWithServer" ------------------------------------------------------------------------------------------------------ -- Datatypes -- | the connecting client type data Client a = Client (TChan a) Handle HostName PortNumber instance Show (Client a) where show (Client _ _ host port) = "Client " ++ host ++ ":"++show port instance Eq (Client a) where (==) (Client _ _ host1 port1) (Client _ _ host2 port2) = host1 == host2 && port1 == port2 (/=) c1 c2 = not (c1 == c2) instance Ord (Client a) where compare (Client _ _ host1 port1) (Client _ _ host2 port2) = compare (host1,port1) (host2,port2) -- | type for call back function that is executed within server cycle type ServerAction a = a -> Client a -> [Client a] -> IO [Client a] -- | converts the string read by hGetLine to your datatype type LineConverter a = String -> a ------------------------------------------------------------------------------------------------------ -- | execute a ServerAction wrapped by the tcp server doWithServer :: (Show a) => Int -> ServerAction a -> LineConverter a -> String -> IO () doWithServer port action converter prompt = withSocketsDo $ do servSock <- listenOn . PortNumber . fromIntegral $ port handleAll (\_ -> sClose servSock) $ do acceptChan <- atomically newTChan _ <- forkIO $ acceptLoop servSock acceptChan converter prompt mainLoop servSock acceptChan [] action prompt --`finally` sClose servSock -- | the accept loop for new connections acceptLoop :: Socket -> TChan (Client a) -> LineConverter a -> String -> IO () acceptLoop servSock chan convert prompt = do (handle, host, port) <- accept servSock hPrompt handle prompt ch <- atomically newTChan _ <- forkIO $ clientLoop (Client ch handle host port) convert atomically $ writeTChan chan (Client ch handle host port) acceptLoop servSock chan convert prompt -- | the listenerloops for getting the clients commands clientLoop :: Client a -> LineConverter a -> IO () clientLoop client@(Client chan handle _ _) convert = handleAll (\_ -> do { infoM localLogger $ "Client disconnected: "++show client; hClose handle; return () }) $ do listenLoop (liftM convert $ hGetLine handle) chan -- | the listenerloops for getting the clients commands listenLoop :: IO a -> TChan a -> IO () listenLoop act chan = sequence_ (repeat (act >>= atomically . writeTChan chan)) -- | the main loop combines new client actions (Left) and new command actions (Right) mainLoop :: (Show a) => Socket -> TChan (Client a) -> [(Client a)] -> ServerAction a -> String-> IO () mainLoop servSock acceptChan clients f prompt= do r <- atomically $ (Left `fmap` readTChan acceptChan) `orElse` (Right `fmap` tselect clients) case r of -- a new client Left sender -> do infoM localLogger $ "new client" ++ show sender mainLoop servSock acceptChan (sender:clients) f prompt -- a new command Right (line, sender) -> do debugM localLogger $ (show sender) ++ " [ " ++ show line ++ " ]" clients' <- f line sender clients debugM localLogger $ "Num of Clients: " ++ show (length clients') mainLoop servSock acceptChan (clients') f prompt hPrompt :: Handle -> String -> IO () hPrompt h p = do openhandle <- hIsOpen h if openhandle then do hPutStr h p hFlush h else return () tselect :: [(Client a)] -> STM (a, Client a) tselect = foldl orElse retry . map (\client@(Client ch _ _ _) -> (\line -> (line,client)) `fmap` readTChan ch)