Philipp Balzarek 14 years ago
parent
commit
b816466662
  1. 13
      src/Network/XMPP/Concurrent/IQ.hs
  2. 115
      src/Network/XMPP/Concurrent/Monad.hs
  3. 93
      src/Network/XMPP/Concurrent/Threads.hs
  4. 46
      src/Network/XMPP/Concurrent/Types.hs
  5. 2
      src/Network/XMPP/Message.hs
  6. 350
      src/Network/XMPP/Types.hs

13
src/Network/XMPP/Concurrent/IQ.hs

@ -12,13 +12,14 @@ import Network.XMPP.Concurrent.Monad
import Network.XMPP.Types import Network.XMPP.Types
-- | Sends an IQ, returns a 'TMVar' that will be filled with the first inbound -- | Sends an IQ, returns a 'TMVar' that will be filled with the first inbound
-- IQ with a matching ID that has type @result@ or @error@ -- IQ with a matching ID that has type @result@ or @error@.
sendIQ :: Maybe JID -- ^ Recipient (to) sendIQ :: Maybe JID -- ^ Recipient (to)
-> IQRequestType -- ^ IQ type (Get or Set) -> IQRequestType -- ^ IQ type (@Get@ or @Set@)
-> Maybe LangTag -- ^ Language tag of the payload (Nothing for default) -> Maybe LangTag -- ^ Language tag of the payload (@Nothing@ for
-> Element -- ^ The iq body (there has to be exactly one) -- default)
-> Element -- ^ The IQ body (there has to be exactly one)
-> XMPP (TMVar IQResponse) -> XMPP (TMVar IQResponse)
sendIQ to tp lang body = do -- TODO: add timeout sendIQ to tp lang body = do -- TODO: Add timeout
newId <- liftIO =<< asks idGenerator newId <- liftIO =<< asks idGenerator
handlers <- asks iqHandlers handlers <- asks iqHandlers
ref <- liftIO . atomically $ do ref <- liftIO . atomically $ do
@ -30,7 +31,7 @@ sendIQ to tp lang body = do -- TODO: add timeout
sendStanza . IQRequestS $ IQRequest newId Nothing to lang tp body sendStanza . IQRequestS $ IQRequest newId Nothing to lang tp body
return ref return ref
-- | like 'sendIQ', but waits for the answer IQ -- | Like 'sendIQ', but waits for the answer IQ.
sendIQ' :: Maybe JID sendIQ' :: Maybe JID
-> IQRequestType -> IQRequestType
-> Maybe LangTag -> Maybe LangTag

115
src/Network/XMPP/Concurrent/Monad.hs

@ -17,29 +17,38 @@ import Network.XMPP.Concurrent.Types
import Network.XMPP.Monad import Network.XMPP.Monad
-- | Register a new IQ listener. IQ requests matching the type and namespace will -- | Register a new IQ listener. IQ requests matching the type and namespace
-- be put in the channel. -- will be put in the channel.
-- --
-- Return the new channel or Nothing if this namespace/'IQRequestType' -- Return the new channel or Nothing if this namespace/'IQRequestType'
-- combination was alread handled -- combination was alread handled.
listenIQChan :: IQRequestType -- ^ type of IQs to receive (Get / Set) listenIQChan :: IQRequestType -- ^ Type of IQs to receive (@Get@ or @Set@)
-> Text -- ^ namespace of the child element -> Text -- ^ Namespace of the child element
-> XMPP (Maybe ( TChan IQRequestTicket)) -> XMPP (Maybe (TChan IQRequestTicket))
listenIQChan tp ns = do listenIQChan tp ns = do
handlers <- asks iqHandlers handlers <- asks iqHandlers
liftIO . atomically $ do liftIO . atomically $ do
(byNS, byID) <- readTVar handlers (byNS, byID) <- readTVar handlers
iqCh <- newTChan iqCh <- newTChan
let (present, byNS') = Map.insertLookupWithKey' (\_ _ old -> old) let (present, byNS') = Map.insertLookupWithKey'
(tp,ns) iqCh byNS (\_ _ old -> old)
(tp, ns)
iqCh
byNS
writeTVar handlers (byNS', byID) writeTVar handlers (byNS', byID)
return $ case present of return $ case present of
Nothing -> Just iqCh Nothing -> Just iqCh
Just _iqCh' -> Nothing Just _iqCh' -> Nothing
-- | get the inbound message channel, duplicates from master if necessary -- | Get a duplicate of the stanza channel
-- please note that once duplicated it will keep filling up, call getStanzaChan :: XMPP (TChan Stanza)
-- 'dropMessageChan' to allow it to be garbage collected getStanzaChan = do
shadow <- asks sShadow
liftIO $ atomically $ dupTChan shadow
-- | Get the inbound stanza channel, duplicates from master if necessary. Please
-- note that once duplicated it will keep filling up, call 'dropMessageChan' to
-- allow it to be garbage collected.
getMessageChan :: XMPP (TChan (Either MessageError Message)) getMessageChan :: XMPP (TChan (Either MessageError Message))
getMessageChan = do getMessageChan = do
mChR <- asks messagesRef mChR <- asks messagesRef
@ -52,13 +61,7 @@ getMessageChan = do
return mCh' return mCh'
Just mCh' -> return mCh' Just mCh' -> return mCh'
-- | Get a duplicate of the stanza channel -- | Analogous to 'getMessageChan'.
getStanzaChan :: XMPP (TChan Stanza)
getStanzaChan = do
shadow <- asks sShadow
liftIO $ atomically $ dupTChan shadow
-- | see 'getMessageChan'
getPresenceChan :: XMPP (TChan (Either PresenceError Presence)) getPresenceChan :: XMPP (TChan (Either PresenceError Presence))
getPresenceChan = do getPresenceChan = do
pChR <- asks presenceRef pChR <- asks presenceRef
@ -71,54 +74,55 @@ getPresenceChan = do
return pCh' return pCh'
Just pCh' -> return pCh' Just pCh' -> return pCh'
-- | Drop the local end of the inbound stanza channel -- | Drop the local end of the inbound stanza channel from our context so it can
-- from our context so it can be GC-ed -- be GC-ed.
dropMessageChan :: XMPP () dropMessageChan :: XMPP ()
dropMessageChan = do dropMessageChan = do
r <- asks messagesRef r <- asks messagesRef
liftIO $ writeIORef r Nothing liftIO $ writeIORef r Nothing
-- | see 'dropMessageChan' -- | Analogous to 'dropMessageChan'.
dropPresenceChan :: XMPP () dropPresenceChan :: XMPP ()
dropPresenceChan = do dropPresenceChan = do
r <- asks presenceRef r <- asks presenceRef
liftIO $ writeIORef r Nothing liftIO $ writeIORef r Nothing
-- | Read an element from the inbound stanza channel, acquiring a copy -- | Read an element from the inbound stanza channel, acquiring a copy of the
-- of the channel as necessary -- channel as necessary.
pullMessage :: XMPP (Either MessageError Message) pullMessage :: XMPP (Either MessageError Message)
pullMessage = do pullMessage = do
c <- getMessageChan c <- getMessageChan
liftIO $ atomically $ readTChan c liftIO $ atomically $ readTChan c
-- | Read an element from the inbound stanza channel, acquiring a copy -- | Read an element from the inbound stanza channel, acquiring a copy of the
-- of the channel as necessary -- channel as necessary.
pullPresence :: XMPP (Either PresenceError Presence) pullPresence :: XMPP (Either PresenceError Presence)
pullPresence = do pullPresence = do
c <- getPresenceChan c <- getPresenceChan
liftIO $ atomically $ readTChan c liftIO $ atomically $ readTChan c
-- | Send a stanza to the server -- | Send a stanza to the server.
sendStanza :: Stanza -> XMPP () sendStanza :: Stanza -> XMPP ()
sendStanza a = do sendStanza a = do
out <- asks outCh out <- asks outCh
liftIO . atomically $ writeTChan out a liftIO . atomically $ writeTChan out a
return () return ()
-- | Create a forked session object without forking a thread -- | Create a forked session object without forking a thread.
forkSession :: Session -> IO Session forkSession :: Session -> IO Session
forkSession sess = do forkSession sess = do
mCH' <- newIORef Nothing mCH' <- newIORef Nothing
pCH' <- newIORef Nothing pCH' <- newIORef Nothing
return $ sess {messagesRef = mCH' ,presenceRef = pCH'} return $ sess {messagesRef = mCH', presenceRef = pCH'}
-- | Fork a new thread -- | Fork a new thread.
fork :: XMPP () -> XMPP ThreadId fork :: XMPP () -> XMPP ThreadId
fork a = do fork a = do
sess <- ask sess <- ask
sess' <- liftIO $ forkSession sess sess' <- liftIO $ forkSession sess
liftIO $ forkIO $ runReaderT a sess' liftIO $ forkIO $ runReaderT a sess'
-- | Pulls a message and returns it if the given predicate returns @True@.
filterMessages :: (MessageError -> Bool) filterMessages :: (MessageError -> Bool)
-> (Message -> Bool) -> (Message -> Bool)
-> XMPP (Either MessageError Message) -> XMPP (Either MessageError Message)
@ -130,6 +134,8 @@ filterMessages f g = do
Right m | g m -> return $ Right m Right m | g m -> return $ Right m
| otherwise -> filterMessages f g | otherwise -> filterMessages f g
-- | Pulls a (non-error) message and returns it if the given predicate returns
-- @True@.
waitForMessage :: (Message -> Bool) -> XMPP Message waitForMessage :: (Message -> Bool) -> XMPP Message
waitForMessage f = do waitForMessage f = do
s <- pullMessage s <- pullMessage
@ -138,6 +144,7 @@ waitForMessage f = do
Right m | f m -> return m Right m | f m -> return m
| otherwise -> waitForMessage f | otherwise -> waitForMessage f
-- | Pulls an error message and returns it if the given predicate returns @True@.
waitForMessageError :: (MessageError -> Bool) -> XMPP MessageError waitForMessageError :: (MessageError -> Bool) -> XMPP MessageError
waitForMessageError f = do waitForMessageError f = do
s <- pullMessage s <- pullMessage
@ -146,6 +153,8 @@ waitForMessageError f = do
Left m | f m -> return m Left m | f m -> return m
| otherwise -> waitForMessageError f | otherwise -> waitForMessageError f
-- | Pulls a (non-error) presence and returns it if the given predicate returns
-- @True@.
waitForPresence :: (Presence -> Bool) -> XMPP Presence waitForPresence :: (Presence -> Bool) -> XMPP Presence
waitForPresence f = do waitForPresence f = do
s <- pullPresence s <- pullPresence
@ -154,11 +163,12 @@ waitForPresence f = do
Right m | f m -> return m Right m | f m -> return m
| otherwise -> waitForPresence f | otherwise -> waitForPresence f
-- | Run an XMPPMonad action in isolation. -- TODO: Wait for presence error?
-- Reader and writer workers will be temporarily stopped
-- and resumed with the new session details once the action returns. -- | Run an XMPPMonad action in isolation. Reader and writer workers will be
-- The Action will run in the calling thread/ -- temporarily stopped and resumed with the new session details once the action
-- Any uncaught exceptions will be interpreted as connection failure -- returns. The action will run in the calling thread. Any uncaught exceptions
-- will be interpreted as connection failure.
withConnection :: XMPPConMonad a -> XMPP (Either StreamError a) withConnection :: XMPPConMonad a -> XMPP (Either StreamError a)
withConnection a = do withConnection a = do
readerId <- asks readerThread readerId <- asks readerThread
@ -166,68 +176,79 @@ withConnection a = do
write <- asks writeRef write <- asks writeRef
wait <- liftIO $ newEmptyTMVarIO wait <- liftIO $ newEmptyTMVarIO
liftIO . Ex.mask_ $ do liftIO . Ex.mask_ $ do
-- Suspends the reader until the lock (wait) is released (set to `()').
throwTo readerId $ Interrupt wait throwTo readerId $ Interrupt wait
s <- Ex.catch ( atomically $ do -- We acquire the write and stateRef locks, to make sure that this is
-- the only thread that can write to the stream and to perform a
-- withConnection calculation. Afterwards, we release the lock and
-- fetches an updated state.
s <- Ex.catch
(atomically $ do
_ <- takeTMVar write _ <- takeTMVar write
s <- takeTMVar stateRef s <- takeTMVar stateRef
putTMVar wait () putTMVar wait ()
return s return s
) )
(\e -> atomically (putTMVar wait ()) -- If we catch an exception, we have failed to take the MVars above.
>> Ex.throwIO (e :: Ex.SomeException) (\e -> atomically (putTMVar wait ()) >>
-- No MVar taken Ex.throwIO (e :: Ex.SomeException)
) )
Ex.catches ( do -- Run the XMPPMonad action, save the (possibly updated) states, release
-- the locks, and return the result.
Ex.catches
(do
(res, s') <- runStateT a s (res, s') <- runStateT a s
atomically $ do atomically $ do
putTMVar write (sConPushBS s') putTMVar write (sConPushBS s')
putTMVar stateRef s' putTMVar stateRef s'
return $ Right res return $ Right res
) )
-- we treat all Exceptions as fatal -- We treat all Exceptions as fatal. If we catch a StreamError, we
-- return it. Otherwise, we throw an exception.
[ Ex.Handler $ \e -> return $ Left (e :: StreamError) [ Ex.Handler $ \e -> return $ Left (e :: StreamError)
, Ex.Handler $ \e -> runStateT xmppKillConnection s , Ex.Handler $ \e -> runStateT xmppKillConnection s
>> Ex.throwIO (e :: Ex.SomeException) >> Ex.throwIO (e :: Ex.SomeException)
] ]
-- | Send a presence Stanza -- | Send a presence stanza.
sendPresence :: Presence -> XMPP () sendPresence :: Presence -> XMPP ()
sendPresence = sendStanza . PresenceS sendPresence = sendStanza . PresenceS
-- | Send a Message Stanza -- | Send a message stanza.
sendMessage :: Message -> XMPP () sendMessage :: Message -> XMPP ()
sendMessage = sendStanza . MessageS sendMessage = sendStanza . MessageS
-- | Executes a function to update the event handlers.
modifyHandlers :: (EventHandlers -> EventHandlers) -> XMPP () modifyHandlers :: (EventHandlers -> EventHandlers) -> XMPP ()
modifyHandlers f = do modifyHandlers f = do
eh <- asks eventHandlers eh <- asks eventHandlers
liftIO . atomically $ writeTVar eh . f =<< readTVar eh liftIO . atomically $ writeTVar eh . f =<< readTVar eh
-- | Sets the handler to be executed when the session ends.
setSessionEndHandler :: XMPP () -> XMPP () setSessionEndHandler :: XMPP () -> XMPP ()
setSessionEndHandler eh = do setSessionEndHandler eh = do
r <- ask r <- ask
modifyHandlers (\s -> s{sessionEndHandler = runReaderT eh r}) modifyHandlers (\s -> s{sessionEndHandler = runReaderT eh r})
-- | Sets the handler to be executed when the server connection is closed.
setConnectionClosedHandler :: (StreamError -> XMPP ()) -> XMPP () setConnectionClosedHandler :: (StreamError -> XMPP ()) -> XMPP ()
setConnectionClosedHandler eh = do setConnectionClosedHandler eh = do
r <- ask r <- ask
modifyHandlers (\s -> s{connectionClosedHandler = \e -> runReaderT (eh e) r}) modifyHandlers (\s -> s{connectionClosedHandler = \e -> runReaderT (eh e) r})
-- | run an event handler -- | Run an event handler.
runHandler :: (EventHandlers -> IO a) -> XMPP a runHandler :: (EventHandlers -> IO a) -> XMPP a
runHandler h = do runHandler h = do
eh <- liftIO . atomically . readTVar =<< asks eventHandlers eh <- liftIO . atomically . readTVar =<< asks eventHandlers
liftIO $ h eh liftIO $ h eh
-- | End the current xmpp session -- | End the current XMPP session.
endSession :: XMPP () endSession :: XMPP ()
endSession = do -- TODO: This has to be idempotent (is it?) endSession = do -- TODO: This has to be idempotent (is it?)
void $ withConnection xmppKillConnection void $ withConnection xmppKillConnection
liftIO =<< asks stopThreads liftIO =<< asks stopThreads
runHandler sessionEndHandler runHandler sessionEndHandler
-- | Close the connection to the server -- | Close the connection to the server.
closeConnection :: XMPP () closeConnection :: XMPP ()
closeConnection = void $ withConnection xmppKillConnection closeConnection = void $ withConnection xmppKillConnection

93
src/Network/XMPP/Concurrent/Threads.hs

@ -29,6 +29,8 @@ import Text.XML.Stream.Elements
import GHC.IO (unsafeUnmask) import GHC.IO (unsafeUnmask)
-- Worker to read stanzas from the stream and concurrently distribute them to
-- all listener threads.
readWorker :: TChan (Either MessageError Message) readWorker :: TChan (Either MessageError Message)
-> TChan (Either PresenceError Presence) -> TChan (Either PresenceError Presence)
-> TChan Stanza -> TChan Stanza
@ -84,24 +86,30 @@ readWorker messageC presenceC stanzaC iqHands handlers stateRef =
IQRequestS i -> handleIQRequest iqHands i IQRequestS i -> handleIQRequest iqHands i
IQResultS i -> handleIQResponse iqHands (Right i) IQResultS i -> handleIQResponse iqHands (Right i)
IQErrorS i -> handleIQResponse iqHands (Left i) IQErrorS i -> handleIQResponse iqHands (Left i)
where where
-- Defining an Control.Exception.allowInterrupt equivalent for -- Defining an Control.Exception.allowInterrupt equivalent for GHC 7
-- GHC 7 compatibility. -- compatibility.
allowInterrupt :: IO () allowInterrupt :: IO ()
allowInterrupt = unsafeUnmask $ return () allowInterrupt = unsafeUnmask $ return ()
-- Call the connection closed handlers.
noCon :: TVar EventHandlers -> StreamError -> IO (Maybe a) noCon :: TVar EventHandlers -> StreamError -> IO (Maybe a)
noCon h e = do noCon h e = do
hands <- atomically $ readTVar h hands <- atomically $ readTVar h
_ <- forkIO $ connectionClosedHandler hands e _ <- forkIO $ connectionClosedHandler hands e
return Nothing return Nothing
-- While waiting for the first semaphore(s) to flip we might receive -- While waiting for the first semaphore(s) to flip we might receive another
-- another interrupt. When that happens we add it's semaphore to the -- interrupt. When that happens we add it's semaphore to the list and retry
-- list and retry waiting -- waiting. We do this because we might receive another interrupt while
-- recovering from the last one. We do this because we might receive another
-- interrupt while we're waiting for a mutex to unlock; if that happens, the
-- new interrupt is added to the list and is waited for as well.
handleInterrupts :: [TMVar ()] -> IO [()] handleInterrupts :: [TMVar ()] -> IO [()]
handleInterrupts ts = handleInterrupts ts =
Ex.catch (atomically $ forM ts takeTMVar) Ex.catch (atomically $ forM ts takeTMVar)
( \(Interrupt t) -> handleInterrupts (t:ts)) (\(Interrupt t) -> handleInterrupts (t:ts))
-- If the IQ request has a namespace, sent it through the appropriate channel.
handleIQRequest :: TVar IQHandlers -> IQRequest -> STM () handleIQRequest :: TVar IQHandlers -> IQRequest -> STM ()
handleIQRequest handlers iq = do handleIQRequest handlers iq = do
(byNS, _) <- readTVar handlers (byNS, _) <- readTVar handlers
@ -111,36 +119,37 @@ handleIQRequest handlers iq = do
Just ch -> do Just ch -> do
sent <- newTVar False sent <- newTVar False
writeTChan ch $ IQRequestTicket sent iq writeTChan ch $ IQRequestTicket sent iq
handleIQResponse :: TVar IQHandlers -> Either IQError IQResult -> STM () handleIQResponse :: TVar IQHandlers -> Either IQError IQResult -> STM ()
handleIQResponse handlers iq = do handleIQResponse handlers iq = do
(byNS, byID) <- readTVar handlers (byNS, byID) <- readTVar handlers
case Map.updateLookupWithKey (\_ _ -> Nothing) (iqID iq) byID of case Map.updateLookupWithKey (\_ _ -> Nothing) (iqID iq) byID of
(Nothing, _) -> return () -- we are not supposed (Nothing, _) -> return () -- We are not supposed to send an error.
-- to send an error
(Just tmvar, byID') -> do (Just tmvar, byID') -> do
_ <- tryPutTMVar tmvar iq -- don't block _ <- tryPutTMVar tmvar iq -- Don't block.
writeTVar handlers (byNS, byID') writeTVar handlers (byNS, byID')
where where
iqID (Left err) = iqErrorID err iqID (Left err) = iqErrorID err
iqID (Right iq') = iqResultID iq' iqID (Right iq') = iqResultID iq'
-- Worker to write stanzas to the stream concurrently.
writeWorker :: TChan Stanza -> TMVar (BS.ByteString -> IO Bool) -> IO () writeWorker :: TChan Stanza -> TMVar (BS.ByteString -> IO Bool) -> IO ()
writeWorker stCh writeR = forever $ do writeWorker stCh writeR = forever $ do
(write, next) <- atomically $ (,) <$> (write, next) <- atomically $ (,) <$>
takeTMVar writeR <*> takeTMVar writeR <*>
readTChan stCh readTChan stCh
r <- write $ renderElement (pickleElem xpStanza next) r <- write $ renderElement (pickleElem xpStanza next)
unless r $ do unless r $ do -- If the writing failed, the connection is dead.
atomically $ unGetTChan stCh next -- connection is dead atomically $ unGetTChan stCh next
threadDelay 250000 -- avoid free spinning threadDelay 250000 -- Avoid free spinning.
atomically $ putTMVar writeR write atomically $ putTMVar writeR write -- Put it back.
-- Two streams: input and output. Threads read from input stream and write to output stream. -- Two streams: input and output. Threads read from input stream and write to
-- | Runs thread in XmppState monad -- output stream.
-- returns channel of incoming and outgoing stances, respectively -- | Runs thread in XmppState monad. Returns channel of incoming and outgoing
-- and an Action to stop the Threads and close the connection -- stances, respectively, and an Action to stop the Threads and close the
startThreads -- connection.
:: IO ( TChan (Either MessageError Message) startThreads :: IO ( TChan (Either MessageError Message)
, TChan (Either PresenceError Presence) , TChan (Either PresenceError Presence)
, TChan Stanza , TChan Stanza
, TVar IQHandlers , TVar IQHandlers
@ -151,27 +160,38 @@ startThreads
, ThreadId , ThreadId
, TVar EventHandlers , TVar EventHandlers
) )
startThreads = do startThreads = do
writeLock <- newTMVarIO (\_ -> return False) writeLock <- newTMVarIO (\_ -> return False)
messageC <- newTChanIO messageC <- newTChanIO
presenceC <- newTChanIO presenceC <- newTChanIO
outC <- newTChanIO outC <- newTChanIO
stanzaC <- newTChanIO stanzaC <- newTChanIO
handlers <- newTVarIO ( Map.empty, Map.empty) handlers <- newTVarIO (Map.empty, Map.empty)
eh <- newTVarIO zeroEventHandlers eh <- newTVarIO zeroEventHandlers
conS <- newTMVarIO xmppNoConnection conS <- newTMVarIO xmppNoConnection
lw <- forkIO $ writeWorker outC writeLock lw <- forkIO $ writeWorker outC writeLock
cp <- forkIO $ connPersist writeLock cp <- forkIO $ connPersist writeLock
rd <- forkIO $ readWorker messageC presenceC stanzaC handlers eh conS rd <- forkIO $ readWorker messageC presenceC stanzaC handlers eh conS
return (messageC, presenceC, stanzaC, handlers, outC return ( messageC
, presenceC
, stanzaC
, handlers
, outC
, killConnection writeLock [lw, rd, cp] , killConnection writeLock [lw, rd, cp]
, writeLock, conS ,rd, eh) , writeLock
, conS
, rd
, eh)
where where
killConnection writeLock threads = liftIO $ do killConnection writeLock threads = liftIO $ do
_ <- atomically $ takeTMVar writeLock -- Should we put it back? _ <- atomically $ takeTMVar writeLock -- Should we put it back?
_ <- forM threads killThread _ <- forM threads killThread
return() return ()
zeroEventHandlers :: EventHandlers
zeroEventHandlers = EventHandlers
{ sessionEndHandler = return ()
, connectionClosedHandler = \_ -> return ()
}
-- | Creates and initializes a new XMPP session. -- | Creates and initializes a new XMPP session.
newSession :: IO Session newSession :: IO Session
@ -184,19 +204,34 @@ newSession = do
curId <- readTVar idRef curId <- readTVar idRef
writeTVar idRef (curId + 1 :: Integer) writeTVar idRef (curId + 1 :: Integer)
return . read. show $ curId return . read. show $ curId
return (Session workermCh workerpCh mC pC sC outC hand writeR rdr getId return $ Session
conS eh stopThreads') mC
pC
sC
workermCh
workerpCh
outC
hand
writeR
rdr
getId
conS
eh
stopThreads'
-- | Creates a new session and runs the given XMPP computation.
withNewSession :: XMPP b -> IO (Session, b) withNewSession :: XMPP b -> IO (Session, b)
withNewSession a = do withNewSession a = do
sess <- newSession sess <- newSession
ret <- runReaderT a sess ret <- runReaderT a sess
return (sess, ret) return (sess, ret)
-- | Runs the given XMPP computation in the given session.
withSession :: Session -> XMPP a -> IO a withSession :: Session -> XMPP a -> IO a
withSession = flip runReaderT withSession = flip runReaderT
-- | Sends a blank space every 30 seconds to keep the connection alive -- Acquires the write lock, pushes a space, and releases the lock.
-- | Sends a blank space every 30 seconds to keep the connection alive.
connPersist :: TMVar (BS.ByteString -> IO Bool) -> IO () connPersist :: TMVar (BS.ByteString -> IO Bool) -> IO ()
connPersist lock = forever $ do connPersist lock = forever $ do
pushBS <- atomically $ takeTMVar lock pushBS <- atomically $ takeTMVar lock

46
src/Network/XMPP/Concurrent/Types.hs

@ -14,52 +14,56 @@ import qualified Data.Map as Map
import Data.Text(Text) import Data.Text(Text)
import Data.Typeable import Data.Typeable
import Network.XMPP.Types import Network.XMPP.Types
-- Map between the IQ request type and the "query" namespace pair, and the TChan
-- for the IQ request and "sent" boolean pair.
type IQHandlers = (Map.Map (IQRequestType, Text) (TChan IQRequestTicket) type IQHandlers = (Map.Map (IQRequestType, Text) (TChan IQRequestTicket)
, Map.Map StanzaId (TMVar IQResponse) , Map.Map StanzaId (TMVar IQResponse)
) )
-- Handlers to be run when the XMPP session ends and when the XMPP connection is
-- closed.
data EventHandlers = EventHandlers data EventHandlers = EventHandlers
{ sessionEndHandler :: IO () { sessionEndHandler :: IO ()
, connectionClosedHandler :: StreamError -> IO () , connectionClosedHandler :: StreamError -> IO ()
} }
zeroEventHandlers :: EventHandlers -- The Session object is the XMPP (ReaderT) state.
zeroEventHandlers = EventHandlers data Session = Session
{ sessionEndHandler = return () { -- The original master channels that the reader puts stanzas
, connectionClosedHandler = \_ -> return () -- into. These are cloned by @get{STanza,Message,Presence}Chan
} -- on demand when first used by the thread and are stored in the
-- {message,presence}Ref fields below.
data Session = Session { messagesRef :: IORef (Maybe ( TChan (Either mShadow :: TChan (Either MessageError Message)
MessageError , pShadow :: TChan (Either PresenceError Presence)
Message
)))
, presenceRef :: IORef (Maybe (TChan (Either
PresenceError Presence )))
, mShadow :: TChan (Either MessageError
Message)
-- the original chan
, pShadow :: TChan (Either PresenceError
Presence)
-- the original chan
, sShadow :: TChan Stanza -- All stanzas , sShadow :: TChan Stanza -- All stanzas
-- The cloned copies of the original/shadow channels. They are
-- thread-local (as opposed to the shadow channels) and contains all
-- stanzas received after the cloning of the shadow channels.
, messagesRef :: IORef (Maybe (TChan (Either MessageError Message)))
, presenceRef :: IORef (Maybe (TChan (Either PresenceError Presence)))
, outCh :: TChan Stanza , outCh :: TChan Stanza
, iqHandlers :: TVar IQHandlers , iqHandlers :: TVar IQHandlers
, writeRef :: TMVar (BS.ByteString -> IO Bool ) -- Writing lock, so that only one thread could write to the stream at any
-- given time.
, writeRef :: TMVar (BS.ByteString -> IO Bool)
, readerThread :: ThreadId , readerThread :: ThreadId
, idGenerator :: IO StanzaId , idGenerator :: IO StanzaId
-- Lock (used by withConnection) to make sure that a maximum of one
-- XMPPConMonad calculation is executed at any given time.
, conStateRef :: TMVar XmppConnection , conStateRef :: TMVar XmppConnection
, eventHandlers :: TVar EventHandlers , eventHandlers :: TVar EventHandlers
, stopThreads :: IO () , stopThreads :: IO ()
} }
-- XMPP is a monad for concurrent XMPP usage.
type XMPP a = ReaderT Session IO a type XMPP a = ReaderT Session IO a
-- Interrupt is used to signal to the reader thread that it should stop.
data Interrupt = Interrupt (TMVar ()) deriving Typeable data Interrupt = Interrupt (TMVar ()) deriving Typeable
instance Show Interrupt where show _ = "<Interrupt>" instance Show Interrupt where show _ = "<Interrupt>"
instance Ex.Exception Interrupt instance Ex.Exception Interrupt
data IQRequestTicket = IQRequestTicket data IQRequestTicket = IQRequestTicket

2
src/Network/XMPP/Message.hs

@ -23,7 +23,7 @@ message = Message { messageID = Nothing
-- Produce an answer message with the given payload, switching the "from" and -- Produce an answer message with the given payload, switching the "from" and
-- "to" attributes in the original message. -- "to" attributes in the original message.
answerMessage :: Message -> [Node] -> Maybe Message answerMessage :: Message -> [Element] -> Maybe Message
answerMessage Message{messageFrom = Just frm, ..} payload = answerMessage Message{messageFrom = Just frm, ..} payload =
Just Message{ messageFrom = messageTo Just Message{ messageFrom = messageTo
, messageID = Nothing , messageID = Nothing

350
src/Network/XMPP/Types.hs

@ -1,15 +1,10 @@
{-# LANGUAGE DeriveDataTypeable #-} {-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE TupleSections #-} {-# LANGUAGE TupleSections #-}
-- Copyright © 2010-2012 Jon Kristensen. See the LICENSE file in the
-- Pontarius distribution for more details.
{-# OPTIONS_HADDOCK hide #-}
{-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE StandaloneDeriving #-} {-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE OverloadedStrings #-}
{-# OPTIONS_HADDOCK hide #-}
module Network.XMPP.Types module Network.XMPP.Types
( IQError(..) ( IQError(..)
@ -27,7 +22,6 @@ module Network.XMPP.Types
, PresenceType(..) , PresenceType(..)
, SaslError(..) , SaslError(..)
, SaslFailure(..) , SaslFailure(..)
, ServerAddress(..)
, ServerFeatures(..) , ServerFeatures(..)
, Stanza(..) , Stanza(..)
, StanzaError(..) , StanzaError(..)
@ -46,8 +40,6 @@ module Network.XMPP.Types
) )
where where
-- import Network.XMPP.Utilities (idGenerator)
import Control.Applicative((<$>)) import Control.Applicative((<$>))
import Control.Exception import Control.Exception
import Control.Monad.IO.Class import Control.Monad.IO.Class
@ -69,24 +61,9 @@ import Network.XMPP.JID
import System.IO import System.IO
-- =============================================================================
-- STANZA TYPES
-- =============================================================================
-- TODO: Would a Stanza class such as the one below be useful sometimes?
--
-- class Stanza a where
-- stanzaID :: a -> Maybe StanzaID
-- stanzaFrom :: a -> Maybe From
-- stanzaTo :: a -> Maybe To
-- stanzaXMLLang :: a -> Maybe XMLLang
-- | -- |
-- Wraps a string of random characters that, when using an appropriate -- Wraps a string of random characters that, when using an appropriate
-- @IDGenerator@, is guaranteed to be unique for the XMPP session. -- @IDGenerator@, is guaranteed to be unique for the XMPP session.
-- Stanza identifiers are generated by Pontarius.
data StanzaId = SI !Text deriving (Eq, Ord) data StanzaId = SI !Text deriving (Eq, Ord)
@ -99,13 +76,8 @@ instance Read StanzaId where
instance IsString StanzaId where instance IsString StanzaId where
fromString = SI . Text.pack fromString = SI . Text.pack
-- An Info/Query (IQ) stanza is either of the type "request" ("get" or -- | The XMPP communication primities (Message, Presence and Info/Query) are
-- "set") or "response" ("result" or "error"). The @IQ@ type wraps -- called stanzas.
-- these two sub-types.
--
-- Objects of this type cannot be generated by Pontarius applications,
-- but are only created internally.
data Stanza = IQRequestS IQRequest data Stanza = IQRequestS IQRequest
| IQResultS IQResult | IQResultS IQResult
| IQErrorS IQError | IQErrorS IQError
@ -115,19 +87,17 @@ data Stanza = IQRequestS IQRequest
| PresenceErrorS PresenceError | PresenceErrorS PresenceError
deriving Show deriving Show
-- | -- | A "request" Info/Query (IQ) stanza is one with either "get" or "set" as
-- A "request" Info/Query (IQ) stanza is one with either "get" or -- type. They are guaranteed to always contain a payload.
-- "set" as type. They are guaranteed to always contain a payload.
data IQRequest = IQRequest { iqRequestID :: StanzaId data IQRequest = IQRequest { iqRequestID :: StanzaId
, iqRequestFrom :: Maybe JID , iqRequestFrom :: Maybe JID
, iqRequestTo :: Maybe JID , iqRequestTo :: Maybe JID
, iqRequestLangTag :: Maybe LangTag , iqRequestLangTag :: Maybe LangTag
, iqRequestType :: IQRequestType , iqRequestType :: IQRequestType
, iqRequestPayload :: Element , iqRequestPayload :: Element
} } deriving Show
deriving (Show)
-- | The type of request that is made -- | The type of IQ request that is made.
data IQRequestType = Get | Set deriving (Eq, Ord) data IQRequestType = Get | Set deriving (Eq, Ord)
instance Show IQRequestType where instance Show IQRequestType where
@ -139,48 +109,44 @@ instance Read IQRequestType where
readsPrec _ "set" = [(Set, "")] readsPrec _ "set" = [(Set, "")]
readsPrec _ _ = [] readsPrec _ _ = []
-- | A "response" Info/Query (IQ) stanza is eitheran 'IQError' or an IQ stanza -- | A "response" Info/Query (IQ) stanza is either an 'IQError' or an IQ stanza
-- with the type "result" ('IQResult') -- with the type "result" ('IQResult').
type IQResponse = Either IQError IQResult type IQResponse = Either IQError IQResult
-- | The answer to an IQ request -- | The (non-error) answer to an IQ request.
data IQResult = IQResult { iqResultID :: StanzaId data IQResult = IQResult { iqResultID :: StanzaId
, iqResultFrom :: Maybe JID , iqResultFrom :: Maybe JID
, iqResultTo :: Maybe JID , iqResultTo :: Maybe JID
, iqResultLangTag :: Maybe LangTag , iqResultLangTag :: Maybe LangTag
, iqResultPayload :: Maybe Element } , iqResultPayload :: Maybe Element
deriving (Show) } deriving Show
-- | The answer to an IQ request that generated an error -- | The answer to an IQ request that generated an error.
data IQError = IQError { iqErrorID :: StanzaId data IQError = IQError { iqErrorID :: StanzaId
, iqErrorFrom :: Maybe JID , iqErrorFrom :: Maybe JID
, iqErrorTo :: Maybe JID , iqErrorTo :: Maybe JID
, iqErrorLangTag :: Maybe LangTag , iqErrorLangTag :: Maybe LangTag
, iqErrorStanzaError :: StanzaError , iqErrorStanzaError :: StanzaError
, iqErrorPayload :: Maybe Element -- should this be []? , iqErrorPayload :: Maybe Element -- should this be []?
} } deriving Show
deriving (Show)
-- | The message stanza. Used for /push/ type communication -- | The message stanza. Used for /push/ type communication.
data Message = Message { messageID :: Maybe StanzaId data Message = Message { messageID :: Maybe StanzaId
, messageFrom :: Maybe JID , messageFrom :: Maybe JID
, messageTo :: Maybe JID , messageTo :: Maybe JID
, messageLangTag :: Maybe LangTag , messageLangTag :: Maybe LangTag
, messageType :: MessageType , messageType :: MessageType
, messagePayload :: [Element] , messagePayload :: [Element]
} } deriving Show
deriving (Show)
-- | An error stanza generated in response to a 'Message' -- | An error stanza generated in response to a 'Message'.
data MessageError = MessageError { messageErrorID :: Maybe StanzaId data MessageError = MessageError { messageErrorID :: Maybe StanzaId
, messageErrorFrom :: Maybe JID , messageErrorFrom :: Maybe JID
, messageErrorTo :: Maybe JID , messageErrorTo :: Maybe JID
, messageErrorLangTag :: Maybe LangTag , messageErrorLangTag :: Maybe LangTag
, messageErrorStanzaError :: StanzaError , messageErrorStanzaError :: StanzaError
, messageErrorPayload :: [Element] , messageErrorPayload :: [Element]
} } deriving (Show)
deriving (Show)
-- | The type of a Message being sent -- | The type of a Message being sent
@ -191,42 +157,35 @@ data MessageType = -- | The message is sent in the context of a one-to-one chat
-- one-to-one chat between the two parties, including an -- one-to-one chat between the two parties, including an
-- appropriate conversation history. -- appropriate conversation history.
Chat Chat
-- | The message is sent in the context of a -- | The message is sent in the context of a multi-user chat
-- multi-user chat environment (similar to that of -- environment (similar to that of @IRC@). Typically a
-- @IRC@). Typically a receiving client will
-- present a message of type /groupchat/ in an
-- interface that enables many-to-many chat
-- between the parties, including a roster of
-- parties in the chatroom and an appropriate
-- conversation history.
| GroupChat
-- | The message provides an alert, a
-- notification, or other transient information to
-- which no reply is expected (e.g., news
-- headlines, sports updates, near-real-time
-- market data, or syndicated content). Because no
-- reply to the message is expected, typically a
-- receiving client will present a message of type -- receiving client will present a message of type
-- /headline/ in an interface that appropriately -- /groupchat/ in an interface that enables many-to-many
-- differentiates the message from standalone -- chat between the parties, including a roster of parties
-- messages, chat messages, and groupchat messages -- in the chatroom and an appropriate conversation history.
-- (e.g., by not providing the recipient with the | GroupChat
-- | The message provides an alert, a notification, or other
-- transient information to which no reply is expected
-- (e.g., news headlines, sports updates, near-real-time
-- market data, or syndicated content). Because no reply to
-- the message is expected, typically a receiving client
-- will present a message of type /headline/ in an interface
-- that appropriately differentiates the message from
-- standalone messages, chat messages, and groupchat
-- messages (e.g., by not providing the recipient with the
-- ability to reply). -- ability to reply).
| Headline | Headline
-- | The message is a standalone message that is -- | The message is a standalone message that is sent outside
-- sent outside the context of a one-to-one -- the context of a one-to-one conversation or groupchat, and
-- conversation or groupchat, and to which it is -- to which it is expected that the recipient will reply.
-- expected that the recipient will -- Typically a receiving client will present a message of
-- reply. Typically a receiving client will -- type /normal/ in an interface that enables the recipient
-- present a message of type /normal/ in an -- to reply, but without a conversation history.
-- interface that enables the recipient to reply,
-- but without a conversation history.
-- --
-- This is the /default/ value -- This is the /default/ value.
| Normal | Normal
deriving (Eq) deriving (Eq)
instance Show MessageType where instance Show MessageType where
show Chat = "chat" show Chat = "chat"
show GroupChat = "groupchat" show GroupChat = "groupchat"
@ -234,43 +193,33 @@ instance Show MessageType where
show Normal = "normal" show Normal = "normal"
instance Read MessageType where instance Read MessageType where
readsPrec _ "chat" = [( Chat ,"")] readsPrec _ "chat" = [(Chat, "")]
readsPrec _ "groupchat" = [( GroupChat ,"")] readsPrec _ "groupchat" = [(GroupChat, "")]
readsPrec _ "headline" = [( Headline ,"")] readsPrec _ "headline" = [(Headline, "")]
readsPrec _ "normal" = [( Normal ,"")] readsPrec _ "normal" = [(Normal, "")]
readsPrec _ _ = [( Normal ,"")] readsPrec _ _ = [(Normal, "")]
-- |
-- Objects of this type cannot be generated by Pontarius applications,
-- but are only created internally.
-- | The presence stanza. Used for communicating status updates.
data Presence = Presence { presenceID :: Maybe StanzaId data Presence = Presence { presenceID :: Maybe StanzaId
, presenceFrom :: Maybe JID , presenceFrom :: Maybe JID
, presenceTo :: Maybe JID , presenceTo :: Maybe JID
, presenceLangTag :: Maybe LangTag , presenceLangTag :: Maybe LangTag
, presenceType :: Maybe PresenceType , presenceType :: Maybe PresenceType
, presencePayload :: [Element] , presencePayload :: [Element]
} } deriving Show
deriving (Show)
-- |
-- Objects of this type cannot be generated by Pontarius applications,
-- but are only created internally.
-- | An error stanza generated in response to a 'Presence'.
data PresenceError = PresenceError { presenceErrorID :: Maybe StanzaId data PresenceError = PresenceError { presenceErrorID :: Maybe StanzaId
, presenceErrorFrom :: Maybe JID , presenceErrorFrom :: Maybe JID
, presenceErrorTo :: Maybe JID , presenceErrorTo :: Maybe JID
, presenceErrorLangTag :: Maybe LangTag , presenceErrorLangTag :: Maybe LangTag
, presenceErrorStanzaError :: StanzaError , presenceErrorStanzaError :: StanzaError
, presenceErrorPayload :: [Element] , presenceErrorPayload :: [Element]
} } deriving Show
deriving (Show)
-- |
-- @PresenceType@ holds XMPP presence types. The "error" message type
-- is left out as errors are using @PresenceError@.
-- | @PresenceType@ holds XMPP presence types. The "error" message type is left
-- out as errors are using @PresenceError@.
data PresenceType = Subscribe | -- ^ Sender wants to subscribe to presence data PresenceType = Subscribe | -- ^ Sender wants to subscribe to presence
Subscribed | -- ^ Sender has approved the subscription Subscribed | -- ^ Sender has approved the subscription
Unsubscribe | -- ^ Sender is unsubscribing from presence Unsubscribe | -- ^ Sender is unsubscribing from presence
@ -281,7 +230,6 @@ data PresenceType = Subscribe | -- ^ Sender wants to subscribe to presence
Default | Default |
Unavailable deriving (Eq) Unavailable deriving (Eq)
instance Show PresenceType where instance Show PresenceType where
show Subscribe = "subscribe" show Subscribe = "subscribe"
show Subscribed = "subscribed" show Subscribed = "subscribed"
@ -292,14 +240,14 @@ instance Show PresenceType where
show Unavailable = "unavailable" show Unavailable = "unavailable"
instance Read PresenceType where instance Read PresenceType where
readsPrec _ "" = [( Default ,"")] readsPrec _ "" = [(Default, "")]
readsPrec _ "available" = [( Default ,"")] readsPrec _ "available" = [(Default, "")]
readsPrec _ "unavailable" = [( Unavailable ,"")] readsPrec _ "unavailable" = [(Unavailable, "")]
readsPrec _ "subscribe" = [( Subscribe ,"")] readsPrec _ "subscribe" = [(Subscribe, "")]
readsPrec _ "subscribed" = [( Subscribed ,"")] readsPrec _ "subscribed" = [(Subscribed, "")]
readsPrec _ "unsubscribe" = [( Unsubscribe ,"")] readsPrec _ "unsubscribe" = [(Unsubscribe, "")]
readsPrec _ "unsubscribed" = [( Unsubscribed ,"")] readsPrec _ "unsubscribed" = [(Unsubscribed, "")]
readsPrec _ "probe" = [( Probe ,"")] readsPrec _ "probe" = [(Probe, "")]
readsPrec _ _ = [] readsPrec _ _ = []
--data ShowType = Available --data ShowType = Available
@ -327,22 +275,18 @@ instance Read PresenceType where
-- readsPrec _ _ = [] -- readsPrec _ _ = []
-- | -- | All stanzas (IQ, message, presence) can cause errors, which in the XMPP
-- All stanzas (IQ, message, presence) can cause errors, which in the XMPP
-- stream looks like <stanza-kind to='sender' type='error'>. These errors are -- stream looks like <stanza-kind to='sender' type='error'>. These errors are
-- wrapped in the @StanzaError@ type. -- wrapped in the @StanzaError@ type.
-- TODO: Sender XML is (optional and is) not yet included.
-- TODO: Sender XML is (optional and is) not included. data StanzaError = StanzaError
data StanzaError = StanzaError { stanzaErrorType :: StanzaErrorType { stanzaErrorType :: StanzaErrorType
, stanzaErrorCondition :: StanzaErrorCondition , stanzaErrorCondition :: StanzaErrorCondition
, stanzaErrorText :: Maybe (Maybe LangTag, Text) , stanzaErrorText :: Maybe (Maybe LangTag, Text)
, stanzaErrorApplicationSpecificCondition :: , stanzaErrorApplicationSpecificCondition :: Maybe Element
Maybe Element } deriving (Eq, Show) } deriving (Eq, Show)
-- |
-- @StanzaError@s always have one of these types.
-- | @StanzaError@s always have one of these types.
data StanzaErrorType = Cancel | -- ^ Error is unrecoverable - do not retry data StanzaErrorType = Cancel | -- ^ Error is unrecoverable - do not retry
Continue | -- ^ Conditition was a warning - proceed Continue | -- ^ Conditition was a warning - proceed
Modify | -- ^ Change the data and retry Modify | -- ^ Change the data and retry
@ -350,7 +294,6 @@ data StanzaErrorType = Cancel | -- ^ Error is unrecoverable - do not retry
Wait -- ^ Error is temporary - wait and retry Wait -- ^ Error is temporary - wait and retry
deriving (Eq) deriving (Eq)
instance Show StanzaErrorType where instance Show StanzaErrorType where
show Cancel = "cancel" show Cancel = "cancel"
show Continue = "continue" show Continue = "continue"
@ -366,48 +309,42 @@ instance Read StanzaErrorType where
readsPrec _ "wait" = [( Wait , "")] readsPrec _ "wait" = [( Wait , "")]
readsPrec _ _ = [] readsPrec _ _ = []
-- | Stanza errors are accommodated with one of the error conditions listed
-- | -- below.
-- Stanza errors are accommodated with one of the error conditions listed below. data StanzaErrorCondition = BadRequest -- ^ Malformed XML.
| Conflict -- ^ Resource or session with
data StanzaErrorCondition = BadRequest | -- ^ Malformed XML -- name already exists.
Conflict | -- ^ Resource or session | FeatureNotImplemented
-- with name already | Forbidden -- ^ Insufficient permissions.
-- exists | Gone -- ^ Entity can no longer be
FeatureNotImplemented | -- contacted at this
Forbidden | -- ^ Insufficient -- address.
-- permissions | InternalServerError
Gone | -- ^ Entity can no longer | ItemNotFound
-- be contacted at this | JIDMalformed
-- address | NotAcceptable -- ^ Does not meet policy
InternalServerError | -- criteria.
ItemNotFound | | NotAllowed -- ^ No entity may perform
JIDMalformed | -- this action.
NotAcceptable | -- ^ Does not meet policy | NotAuthorized -- ^ Must provide proper
-- criteria -- credentials.
NotAllowed | -- ^ No entity may perform | PaymentRequired
-- this action | RecipientUnavailable -- ^ Temporarily unavailable.
NotAuthorized | -- ^ Must provide proper | Redirect -- ^ Redirecting to other
-- credentials
PaymentRequired |
RecipientUnavailable | -- ^ Temporarily
-- unavailable
Redirect | -- ^ Redirecting to other
-- entity, usually -- entity, usually
-- temporarily -- temporarily.
RegistrationRequired | | RegistrationRequired
RemoteServerNotFound | | RemoteServerNotFound
RemoteServerTimeout | | RemoteServerTimeout
ResourceConstraint | -- ^ Entity lacks the | ResourceConstraint -- ^ Entity lacks the
-- necessary system -- necessary system
-- resources -- resources.
ServiceUnavailable | | ServiceUnavailable
SubscriptionRequired | | SubscriptionRequired
UndefinedCondition | -- ^ Application-specific | UndefinedCondition -- ^ Application-specific
-- condition -- condition.
UnexpectedRequest -- ^ Badly timed request | UnexpectedRequest -- ^ Badly timed request.
deriving (Eq) deriving Eq
instance Show StanzaErrorCondition where instance Show StanzaErrorCondition where
show BadRequest = "bad-request" show BadRequest = "bad-request"
@ -468,35 +405,33 @@ data SaslFailure = SaslFailure { saslFailureCondition :: SaslError
) )
} deriving Show } deriving Show
data SaslError = SaslAborted -- ^ Client aborted.
data SaslError = SaslAborted -- ^ Client aborted
| SaslAccountDisabled -- ^ The account has been temporarily | SaslAccountDisabled -- ^ The account has been temporarily
-- disabled -- disabled.
| SaslCredentialsExpired -- ^ The authentication failed because | SaslCredentialsExpired -- ^ The authentication failed because
-- the credentials have expired -- the credentials have expired.
| SaslEncryptionRequired -- ^ The mechanism requested cannot be | SaslEncryptionRequired -- ^ The mechanism requested cannot be
-- used the confidentiality and -- used the confidentiality and
-- integrity of the underlying -- integrity of the underlying
-- stream is protected (typically -- stream is protected (typically
-- with TLS) -- with TLS).
| SaslIncorrectEncoding -- ^ The base64 encoding is incorrect | SaslIncorrectEncoding -- ^ The base64 encoding is incorrect.
| SaslInvalidAuthzid -- ^ The authzid has an incorrect | SaslInvalidAuthzid -- ^ The authzid has an incorrect
-- format or the initiating entity does -- format or the initiating entity
-- not have the appropriate permissions -- does not have the appropriate
-- to authorize that ID -- permissions to authorize that ID.
| SaslInvalidMechanism -- ^ The mechanism is not supported by | SaslInvalidMechanism -- ^ The mechanism is not supported by
-- the receiving entity -- the receiving entity.
| SaslMalformedRequest -- ^ Invalid syntax | SaslMalformedRequest -- ^ Invalid syntax.
| SaslMechanismTooWeak -- ^ The receiving entity policy | SaslMechanismTooWeak -- ^ The receiving entity policy
-- requires a stronger mechanism -- requires a stronger mechanism.
| SaslNotAuthorized -- ^ Invalid credentials | SaslNotAuthorized -- ^ Invalid credentials provided, or
-- provided, or some -- some generic authentication
-- generic authentication -- failure has occurred.
-- failure has occurred
| SaslTemporaryAuthFailure -- ^ There receiving entity reported a | SaslTemporaryAuthFailure -- ^ There receiving entity reported a
-- temporary error condition; the -- temporary error condition; the
-- initiating entity is recommended -- initiating entity is recommended
-- to try again later -- to try again later.
instance Show SaslError where instance Show SaslError where
show SaslAborted = "aborted" show SaslAborted = "aborted"
@ -525,14 +460,11 @@ instance Read SaslError where
readsPrec _ "temporary-auth-failure" = [(SaslTemporaryAuthFailure , "")] readsPrec _ "temporary-auth-failure" = [(SaslTemporaryAuthFailure , "")]
readsPrec _ _ = [] readsPrec _ _ = []
-- | Readability type for host name Texts. -- data ServerAddress = ServerAddress N.HostName N.PortNumber deriving (Eq)
-- type HostName = Text -- This is defined in Network as well
data ServerAddress = ServerAddress N.HostName N.PortNumber deriving (Eq)
-- TODO: document the error cases -- TODO: document the error cases
data StreamErrorCondition = StreamBadFormat data StreamErrorCondition
= StreamBadFormat
| StreamBadNamespacePrefix | StreamBadNamespacePrefix
| StreamConflict | StreamConflict
| StreamConnectionTimeout | StreamConnectionTimeout
@ -601,7 +533,8 @@ instance Read StreamErrorCondition where
readsPrec _ "not-authorized" = [(StreamNotAuthorized , "")] readsPrec _ "not-authorized" = [(StreamNotAuthorized , "")]
readsPrec _ "not-well-formed" = [(StreamNotWellFormed , "")] readsPrec _ "not-well-formed" = [(StreamNotWellFormed , "")]
readsPrec _ "policy-violation" = [(StreamPolicyViolation , "")] readsPrec _ "policy-violation" = [(StreamPolicyViolation , "")]
readsPrec _ "remote-connection-failed" = [(StreamRemoteConnectionFailed , "")] readsPrec _ "remote-connection-failed" =
[(StreamRemoteConnectionFailed, "")]
readsPrec _ "reset" = [(StreamReset , "")] readsPrec _ "reset" = [(StreamReset , "")]
readsPrec _ "resource-constraint" = [(StreamResourceConstraint , "")] readsPrec _ "resource-constraint" = [(StreamResourceConstraint , "")]
readsPrec _ "restricted-xml" = [(StreamRestrictedXml , "")] readsPrec _ "restricted-xml" = [(StreamRestrictedXml , "")]
@ -610,7 +543,7 @@ instance Read StreamErrorCondition where
readsPrec _ "undefined-condition" = [(StreamUndefinedCondition , "")] readsPrec _ "undefined-condition" = [(StreamUndefinedCondition , "")]
readsPrec _ "unsupported-encoding" = [(StreamUnsupportedEncoding , "")] readsPrec _ "unsupported-encoding" = [(StreamUnsupportedEncoding , "")]
readsPrec _ "unsupported-feature" = [(StreamUnsupportedFeature , "")] readsPrec _ "unsupported-feature" = [(StreamUnsupportedFeature , "")]
readsPrec _ "unsupported-stanza-type" = [(StreamUnsupportedStanzaType , "")] readsPrec _ "unsupported-stanza-type" = [(StreamUnsupportedStanzaType, "")]
readsPrec _ "unsupported-version" = [(StreamUnsupportedVersion , "")] readsPrec _ "unsupported-version" = [(StreamUnsupportedVersion , "")]
readsPrec _ _ = [(StreamUndefinedCondition , "")] readsPrec _ _ = [(StreamUndefinedCondition , "")]
@ -620,12 +553,12 @@ data XmppStreamError = XmppStreamError
, errorXML :: Maybe Element , errorXML :: Maybe Element
} deriving (Show, Eq) } deriving (Show, Eq)
data StreamError = StreamError XmppStreamError data StreamError = StreamError XmppStreamError
| StreamWrongVersion Text | StreamWrongVersion Text
| StreamXMLError String | StreamXMLError String -- If stream pickling goes wrong.
| StreamConnectionError | StreamConnectionError
deriving (Show, Eq, Typeable) deriving (Show, Eq, Typeable)
instance Exception StreamError instance Exception StreamError
instance Error StreamError where noMsg = StreamConnectionError instance Error StreamError where noMsg = StreamConnectionError
@ -641,39 +574,33 @@ instance Error StreamError where noMsg = StreamConnectionError
newtype IdGenerator = IdGenerator (IO Text) newtype IdGenerator = IdGenerator (IO Text)
--- other stuff -- Version numbers are displayed as "<major>.<minor>".
data Version = Version { majorVersion :: Integer data Version = Version { majorVersion :: Integer
, minorVersion :: Integer } deriving (Eq) , minorVersion :: Integer } deriving (Eq)
-- Version numbers are displayed as "<major>.<minor>".
instance Show Version where instance Show Version where
show (Version major minor) = (show major) ++ "." ++ (show minor) show (Version major minor) = (show major) ++ "." ++ (show minor)
-- If the major version numbers are not equal, compare them. Otherwise, compare -- If the major version numbers are not equal, compare them. Otherwise, compare
-- the minor version numbers. -- the minor version numbers.
instance Ord Version where instance Ord Version where
compare (Version amajor aminor) (Version bmajor bminor) compare (Version amajor aminor) (Version bmajor bminor)
| amajor /= bmajor = compare amajor bmajor | amajor /= bmajor = compare amajor bmajor
| otherwise = compare aminor bminor | otherwise = compare aminor bminor
-- The language tag in the form of "en-US". It has a primary tag, followed by a
-- number of subtags.
data LangTag = LangTag { primaryTag :: Text data LangTag = LangTag { primaryTag :: Text
, subtags :: [Text] } , subtags :: [Text] }
deriving (Eq) -- TODO: remove deriving (Eq) -- TODO: remove
-- Displays the language tag in the form of "en-US".
instance Show LangTag where instance Show LangTag where
show (LangTag p []) = Text.unpack p show (LangTag p []) = Text.unpack p
show (LangTag p s) = Text.unpack . Text.concat show (LangTag p s) = Text.unpack . Text.concat
$ [p, "-", Text.intercalate "-" s] -- TODO: clean up $ [p, "-", Text.intercalate "-" s] -- TODO: clean up
-- Parses a Text string to a list of LangTag objects. TODO: Why?
parseLangTag :: Text -> [LangTag] parseLangTag :: Text -> [LangTag]
parseLangTag txt = case Text.splitOn "-" txt of parseLangTag txt = case Text.splitOn "-" txt of
[] -> [] [] -> []
@ -682,9 +609,8 @@ parseLangTag txt = case Text.splitOn "-" txt of
instance Read LangTag where instance Read LangTag where
readsPrec _ txt = (,"") <$> (parseLangTag $ Text.pack txt) readsPrec _ txt = (,"") <$> (parseLangTag $ Text.pack txt)
-- Two language tags are considered equal of they contain the same tags (case-insensitive). -- Two language tags are considered equal of they contain the same tags
-- (case-insensitive).
-- TODO: port
-- instance Eq LangTag where -- instance Eq LangTag where
-- (LangTag ap as) == (LangTag bp bs) -- (LangTag ap as) == (LangTag bp bs)
@ -698,15 +624,12 @@ data ServerFeatures = SF
, other :: [Element] , other :: [Element]
} deriving Show } deriving Show
data XmppConnectionState = XmppConnectionClosed -- ^ No connection at data XmppConnectionState
-- this point = XmppConnectionClosed -- ^ No connection at this point.
| XmppConnectionPlain -- ^ Connection | XmppConnectionPlain -- ^ Connection established, but not secured.
-- established, but | XmppConnectionSecured -- ^ Connection established and secured via TLS.
-- not secured
| XmppConnectionSecured -- ^ Connection
-- established and
-- secured via TLS
deriving (Show, Eq, Typeable) deriving (Show, Eq, Typeable)
data XmppConnection = XmppConnection data XmppConnection = XmppConnection
{ sConSrc :: Source IO Event { sConSrc :: Source IO Event
, sRawSrc :: Source IO BS.ByteString , sRawSrc :: Source IO BS.ByteString
@ -725,12 +648,11 @@ data XmppConnection = XmppConnection
-- The XMPP monad transformer. Contains internal state in order to -- The XMPP monad transformer. Contains internal state in order to
-- work with Pontarius. Pontarius clients needs to operate in this -- work with Pontarius. Pontarius clients needs to operate in this
-- context. -- context.
newtype XMPPT m a = XMPPT { runXMPPT :: StateT XmppConnection m a } deriving (Monad, MonadIO) newtype XMPPT m a = XMPPT { runXMPPT :: StateT XmppConnection m a } deriving (Monad, MonadIO)
-- | Low-level and single-threaded XMPP monad. See @XMPP@ for a concurrent
-- implementation.
type XMPPConMonad a = StateT XmppConnection IO a type XMPPConMonad a = StateT XmppConnection IO a
-- Make XMPPT derive the Monad and MonadIO instances. -- Make XMPPT derive the Monad and MonadIO instances.
deriving instance (Monad m, MonadIO m) => MonadState (XmppConnection) (XMPPT m) deriving instance (Monad m, MonadIO m) => MonadState (XmppConnection) (XMPPT m)

Loading…
Cancel
Save