You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
164 lines
5.3 KiB
164 lines
5.3 KiB
|
14 years ago
|
{-# LANGUAGE ScopedTypeVariables #-}
|
||
|
14 years ago
|
{-# LANGUAGE OverloadedStrings #-}
|
||
|
|
|
||
|
14 years ago
|
module Network.Xmpp.Monad where
|
||
|
14 years ago
|
|
||
|
14 years ago
|
import Control.Applicative((<$>))
|
||
|
|
import Control.Monad
|
||
|
|
import Control.Monad.IO.Class
|
||
|
|
import Control.Monad.Trans.Class
|
||
|
14 years ago
|
--import Control.Monad.Trans.Resource
|
||
|
14 years ago
|
import qualified Control.Exception.Lifted as Ex
|
||
|
|
import qualified GHC.IO.Exception as GIE
|
||
|
14 years ago
|
import Control.Monad.State.Strict
|
||
|
14 years ago
|
|
||
|
14 years ago
|
import Data.ByteString as BS
|
||
|
|
import Data.Conduit
|
||
|
14 years ago
|
import qualified Data.Conduit.List as CL
|
||
|
14 years ago
|
import Data.Conduit.BufferedSource
|
||
|
14 years ago
|
import Data.Conduit.Binary as CB
|
||
|
|
import Data.Text(Text)
|
||
|
|
import Data.XML.Pickle
|
||
|
|
import Data.XML.Types
|
||
|
14 years ago
|
|
||
|
14 years ago
|
import Network
|
||
|
14 years ago
|
import Network.Xmpp.Types
|
||
|
|
import Network.Xmpp.Marshal
|
||
|
|
import Network.Xmpp.Pickle
|
||
|
14 years ago
|
|
||
|
14 years ago
|
import System.IO
|
||
|
14 years ago
|
|
||
|
14 years ago
|
import Text.XML.Stream.Elements
|
||
|
|
import Text.XML.Stream.Parse as XP
|
||
|
14 years ago
|
import Text.XML.Unresolved(InvalidEventStream(..))
|
||
|
14 years ago
|
|
||
|
14 years ago
|
pushElement :: Element -> XmppConMonad Bool
|
||
|
14 years ago
|
pushElement x = do
|
||
|
|
sink <- gets sConPushBS
|
||
|
|
liftIO . sink $ renderElement x
|
||
|
|
|
||
|
14 years ago
|
pushStanza :: Stanza -> XmppConMonad Bool
|
||
|
14 years ago
|
pushStanza = pushElement . pickleElem xpStanza
|
||
|
|
|
||
|
14 years ago
|
pushOpenElement :: Element -> XmppConMonad Bool
|
||
|
14 years ago
|
pushOpenElement e = do
|
||
|
|
sink <- gets sConPushBS
|
||
|
|
liftIO . sink $ renderOpenElement e
|
||
|
|
|
||
|
|
-- `Connect-and-resumes' the given sink to the connection source, and pulls a
|
||
|
|
-- `b' value.
|
||
|
14 years ago
|
pullToSink :: Sink Event IO b -> XmppConMonad b
|
||
|
14 years ago
|
pullToSink snk = do
|
||
|
|
source <- gets sConSrc
|
||
|
|
(_, r) <- lift $ source $$+ snk
|
||
|
|
return r
|
||
|
14 years ago
|
|
||
|
14 years ago
|
pullElement :: XmppConMonad Element
|
||
|
14 years ago
|
pullElement = do
|
||
|
14 years ago
|
Ex.catch (do
|
||
|
14 years ago
|
e <- pullToSink (elements =$ CL.head)
|
||
|
14 years ago
|
case e of
|
||
|
|
Nothing -> liftIO $ Ex.throwIO StreamConnectionError
|
||
|
|
Just r -> return r
|
||
|
|
)
|
||
|
|
(\(InvalidEventStream s) -> liftIO . Ex.throwIO $ StreamXMLError s)
|
||
|
|
|
||
|
14 years ago
|
-- Pulls an element and unpickles it.
|
||
|
14 years ago
|
pullPickle :: PU [Node] a -> XmppConMonad a
|
||
|
14 years ago
|
pullPickle p = do
|
||
|
|
res <- unpickleElem p <$> pullElement
|
||
|
|
case res of
|
||
|
|
Left e -> liftIO . Ex.throwIO $ StreamXMLError e
|
||
|
|
Right r -> return r
|
||
|
|
|
||
|
14 years ago
|
-- Pulls a stanza from the stream. Throws an error on failure.
|
||
|
14 years ago
|
pullStanza :: XmppConMonad Stanza
|
||
|
14 years ago
|
pullStanza = do
|
||
|
14 years ago
|
res <- pullPickle xpStreamStanza
|
||
|
14 years ago
|
case res of
|
||
|
|
Left e -> liftIO . Ex.throwIO $ StreamError e
|
||
|
|
Right r -> return r
|
||
|
14 years ago
|
|
||
|
14 years ago
|
-- Performs the given IO operation, catches any errors and re-throws everything
|
||
|
|
-- except the `ResourceVanished' error.
|
||
|
|
catchPush :: IO () -> IO Bool
|
||
|
|
catchPush p = Ex.catch
|
||
|
|
(p >> return True)
|
||
|
|
(\e -> case GIE.ioe_type e of
|
||
|
|
GIE.ResourceVanished -> return False
|
||
|
|
_ -> Ex.throwIO e
|
||
|
|
)
|
||
|
|
|
||
|
|
-- XmppConnection state used when there is no connection.
|
||
|
14 years ago
|
xmppNoConnection :: XmppConnection
|
||
|
|
xmppNoConnection = XmppConnection
|
||
|
14 years ago
|
{ sConSrc = zeroSource
|
||
|
|
, sRawSrc = zeroSource
|
||
|
14 years ago
|
, sConPushBS = \_ -> return False -- Nothing has been sent.
|
||
|
14 years ago
|
, sConHandle = Nothing
|
||
|
|
, sFeatures = SF Nothing [] []
|
||
|
14 years ago
|
, sConnectionState = XmppConnectionClosed
|
||
|
14 years ago
|
, sHostname = Nothing
|
||
|
|
, sUsername = Nothing
|
||
|
|
, sResource = Nothing
|
||
|
14 years ago
|
, sCloseConnection = return ()
|
||
|
14 years ago
|
}
|
||
|
14 years ago
|
where
|
||
|
|
zeroSource :: Source IO output
|
||
|
|
zeroSource = liftIO . Ex.throwIO $ StreamConnectionError
|
||
|
14 years ago
|
|
||
|
14 years ago
|
-- Connects to the given hostname on port 5222 (TODO: Make this dynamic) and
|
||
|
14 years ago
|
-- updates the XmppConMonad XmppConnection state.
|
||
|
|
xmppRawConnect :: HostName -> Text -> XmppConMonad ()
|
||
|
14 years ago
|
xmppRawConnect host hostname = do
|
||
|
14 years ago
|
uname <- gets sUsername
|
||
|
|
con <- liftIO $ do
|
||
|
|
con <- connectTo host (PortNumber 5222)
|
||
|
|
hSetBuffering con NoBuffering
|
||
|
|
return con
|
||
|
|
let raw = sourceHandle con
|
||
|
|
src <- liftIO . bufferSource $ raw $= XP.parseBytes def
|
||
|
|
let st = XmppConnection
|
||
|
|
src
|
||
|
|
raw
|
||
|
|
(catchPush . BS.hPut con)
|
||
|
|
(Just con)
|
||
|
|
(SF Nothing [] [])
|
||
|
|
XmppConnectionPlain
|
||
|
|
(Just hostname)
|
||
|
|
uname
|
||
|
|
Nothing
|
||
|
|
(hClose con)
|
||
|
|
put st
|
||
|
|
|
||
|
14 years ago
|
-- Execute a XmppConMonad computation.
|
||
|
|
xmppNewSession :: XmppConMonad a -> IO (a, XmppConnection)
|
||
|
14 years ago
|
xmppNewSession action = runStateT action xmppNoConnection
|
||
|
14 years ago
|
|
||
|
14 years ago
|
-- Closes the connection and updates the XmppConMonad XmppConnection state.
|
||
|
|
xmppKillConnection :: XmppConMonad ()
|
||
|
14 years ago
|
xmppKillConnection = do
|
||
|
|
cc <- gets sCloseConnection
|
||
|
14 years ago
|
void . liftIO $ (Ex.try cc :: IO (Either Ex.SomeException ()))
|
||
|
14 years ago
|
put xmppNoConnection
|
||
|
14 years ago
|
|
||
|
14 years ago
|
-- Sends an IQ request and waits for the response. If the response ID does not
|
||
|
|
-- match the outgoing ID, an error is thrown.
|
||
|
|
xmppSendIQ' :: StanzaId
|
||
|
|
-> Maybe JID
|
||
|
|
-> IQRequestType
|
||
|
|
-> Maybe LangTag
|
||
|
|
-> Element
|
||
|
14 years ago
|
-> XmppConMonad (Either IQError IQResult)
|
||
|
14 years ago
|
xmppSendIQ' iqID to tp lang body = do
|
||
|
14 years ago
|
pushStanza . IQRequestS $ IQRequest iqID Nothing to lang tp body
|
||
|
14 years ago
|
res <- pullPickle $ xpEither xpIQError xpIQResult
|
||
|
|
case res of
|
||
|
|
Left e -> return $ Left e
|
||
|
|
Right iq' -> do
|
||
|
14 years ago
|
unless
|
||
|
|
(iqID == iqResultID iq') . liftIO . Ex.throwIO $
|
||
|
|
StreamXMLError
|
||
|
|
("In xmppSendIQ' IDs don't match: " ++ show iqID ++ " /= " ++
|
||
|
|
show (iqResultID iq') ++ " .")
|
||
|
|
return $ Right iq'
|