hask-irc/hask-irc-core/Network/IRC/Client.hs

139 lines
5.7 KiB
Haskell
Raw Normal View History

2014-05-22 01:08:36 +05:30
{-# LANGUAGE TemplateHaskell #-}
2014-05-04 04:28:08 +05:30
module Network.IRC.Client (runBot) where
2014-05-04 04:28:08 +05:30
2014-05-22 01:08:36 +05:30
import qualified System.Log.Logger as HSL
import ClassyPrelude
2014-06-01 23:14:19 +05:30
import Control.Concurrent.Lifted (fork, newChan, threadDelay, myThreadId, Chan)
import Control.Exception.Lifted (throwTo, AsyncException (UserInterrupt))
2014-05-21 11:20:53 +05:30
import Network (PortID (PortNumber), connectTo, withSocketsDo)
import System.IO (hSetBuffering, BufferMode(..))
2014-06-01 23:14:19 +05:30
import System.Log.Formatter (tfLogFormatter)
import System.Log.Handler (setFormatter)
import System.Log.Handler.Simple (streamHandler)
import System.Log.Logger (Priority (..), updateGlobalLogger, rootLoggerName,
setHandlers, setLevel)
2014-05-22 01:08:36 +05:30
import System.Log.Logger.TH (deriveLoggers)
2014-06-01 23:14:19 +05:30
import System.Posix.Signals (installHandler, sigINT, sigTERM, Handler (Catch))
2014-05-04 04:28:08 +05:30
import Network.IRC.Bot
2014-05-04 04:28:08 +05:30
import Network.IRC.Types
import Network.IRC.Util
2014-05-21 00:06:37 +05:30
2014-05-22 01:08:36 +05:30
$(deriveLoggers "HSL" [HSL.DEBUG, HSL.ERROR])
2014-06-01 23:14:19 +05:30
coreMsgHandlerNames :: [MsgHandlerName]
coreMsgHandlerNames = ["pingpong", "help"]
2014-05-21 00:38:01 +05:30
connect :: BotConfig -> IO (Bot, MVar BotStatus, Channel Line, Channel Command, Channel SomeEvent)
connect botConfig@BotConfig { .. } = do
2014-05-22 01:08:36 +05:30
debugM "Connecting ..."
socket <- connectToWithRetry
hSetBuffering socket LineBuffering
2014-05-22 01:08:36 +05:30
debugM "Connected"
2014-05-22 20:59:02 +05:30
lineChan <- newChannel
commandChan <- newChannel
eventChan <- newChannel
mvBotStatus <- newMVar Connected
msgHandlers <- loadMsgHandlers (fst eventChan)
msgHandlerInfo' <- foldM (\m (hn, h) -> getHelp h botConfig >>= \hm -> return $ insertMap hn hm m)
mempty (mapToList msgHandlers)
let botConfig' = botConfig { msgHandlerInfo = msgHandlerInfo'}
return (Bot botConfig' socket msgHandlers, mvBotStatus, lineChan, commandChan, eventChan)
where
connectToWithRetry = connectTo (unpack server) (PortNumber (fromIntegral port))
`catch` (\(e :: SomeException) -> do
2014-05-22 01:08:36 +05:30
errorM ("Error while connecting: " ++ show e ++ ". Waiting.")
threadDelay (5 * oneSec)
connectToWithRetry)
2014-05-21 00:06:37 +05:30
newChannel = (,) <$> newChan <*> newEmptyMVar
2014-06-01 23:14:19 +05:30
mkMsgHandler :: Chan SomeEvent -> MsgHandlerName -> IO (Maybe MsgHandler)
mkMsgHandler eventChan name =
flip (`foldM` Nothing) msgHandlerMakers $ \finalHandler handler ->
case finalHandler of
Just _ -> return finalHandler
Nothing -> handler botConfig eventChan name
2014-05-22 20:59:02 +05:30
loadMsgHandlers eventChan =
flip (`foldM` mempty) (mapKeys msgHandlerInfo) $ \hMap msgHandlerName -> do
debugM . unpack $ "Loading msg handler: " ++ msgHandlerName
2014-06-01 23:14:19 +05:30
mMsgHandler <- mkMsgHandler eventChan msgHandlerName
2014-05-22 20:59:02 +05:30
case mMsgHandler of
Nothing -> do
debugM . unpack $ "No msg handler found with name: " ++ msgHandlerName
return hMap
Just msgHandler -> return $ insertMap msgHandlerName msgHandler hMap
2014-05-21 00:38:01 +05:30
disconnect :: (Bot, MVar BotStatus, Channel Line, Channel Command, Channel SomeEvent) -> IO ()
disconnect (Bot { .. }, mvBotStatus, (_, readLatch), (commandChan, sendLatch), (eventChan, eventLatch)) = do
2014-05-22 01:08:36 +05:30
debugM "Disconnecting ..."
sendCommand commandChan QuitCmd
awaitLatch sendLatch
swapMVar mvBotStatus Disconnected
awaitLatch readLatch
2014-05-21 00:06:37 +05:30
sendEvent eventChan =<< toEvent QuitEvent
awaitLatch eventLatch
2014-05-21 00:38:01 +05:30
unloadMsgHandlers
2014-05-22 01:08:36 +05:30
handle (\(_ :: SomeException) -> return ()) $ hClose socket
debugM "Disconnected"
2014-05-21 00:38:01 +05:30
where
unloadMsgHandlers = forM_ (mapToList msgHandlers) $ \(msgHandlerName, msgHandler) -> do
2014-05-22 01:08:36 +05:30
debugM . unpack $ "Unloading msg handler: " ++ msgHandlerName
2014-05-21 00:38:01 +05:30
stopMsgHandler msgHandler botConfig
2014-05-04 04:28:08 +05:30
2014-06-01 23:14:19 +05:30
runBotIntenal :: BotConfig -> IO ()
runBotIntenal botConfig' = withSocketsDo $ do
status <- run
case status of
2014-06-01 23:14:19 +05:30
Disconnected -> debugM "Restarting .." >> runBotIntenal botConfig
Errored -> debugM "Restarting .." >> runBotIntenal botConfig
Interrupted -> return ()
NickNotAvailable -> return ()
_ -> error "Unsupported status"
where
2014-05-22 20:59:02 +05:30
botConfig = botConfig' {
msgHandlerInfo =
foldl' (\m name -> insertMap name mempty m) mempty
(hashNub $ mapKeys (msgHandlerInfo botConfig') ++ coreMsgHandlerNames)
}
handleErrors :: SomeException -> IO BotStatus
2014-05-15 12:02:31 +05:30
handleErrors e = case fromException e of
2014-05-22 01:08:36 +05:30
Just UserInterrupt -> debugM "User interrupt" >> return Interrupted
_ -> debugM ("Exception! " ++ show e) >> return Errored
2014-06-01 23:14:19 +05:30
run = bracket (connect botConfig) disconnect $
2014-05-21 00:06:37 +05:30
\(bot, mvBotStatus, (lineChan, readLatch), (commandChan, sendLatch), eventChannel) ->
handle handleErrors $ do
2014-06-01 23:14:19 +05:30
debugM $ "Running with config:\n" ++ show botConfig
sendCommand commandChan NickCmd
sendCommand commandChan UserCmd
fork $ sendCommandLoop (commandChan, sendLatch) bot
fork $ readLineLoop mvBotStatus (lineChan, readLatch) bot oneSec
2014-05-21 00:06:37 +05:30
fork $ eventProcessLoop eventChannel lineChan commandChan bot
runIRC bot Connected (messageProcessLoop lineChan commandChan)
2014-06-01 23:14:19 +05:30
runBot :: BotConfig -> IO ()
runBot botConfig = do
-- setup signal handling
mainThreadId <- myThreadId
installHandler sigINT (Catch $ throwTo mainThreadId UserInterrupt) Nothing
installHandler sigTERM (Catch $ throwTo mainThreadId UserInterrupt) Nothing
-- setup logging
hSetBuffering stdout LineBuffering
hSetBuffering stderr LineBuffering
stderrHandler <- streamHandler stderr DEBUG >>= \lh -> return $
setFormatter lh $ tfLogFormatter "%F %T" "[$utcTime] $loggername $prio $msg"
updateGlobalLogger rootLoggerName (setHandlers [stderrHandler] . setLevel DEBUG)
-- run
runBotIntenal botConfig