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.
144 lines
4.3 KiB
144 lines
4.3 KiB
|
14 years ago
|
module Network.XMPP.Concurrent.Monad where
|
||
|
|
|
||
|
|
import Network.XMPP.Types
|
||
|
|
|
||
|
|
import Control.Concurrent
|
||
|
|
import Control.Concurrent.STM
|
||
|
|
import Control.Monad.IO.Class
|
||
|
|
import Control.Monad.Trans.Reader
|
||
|
|
import Control.Monad.Trans.State
|
||
|
|
|
||
|
|
import Data.IORef
|
||
|
|
import qualified Data.Map as Map
|
||
|
|
import Data.Text(Text)
|
||
|
|
|
||
|
|
import Network.XMPP.Concurrent.Types
|
||
|
|
|
||
|
|
-- | Register a new IQ listener. IQ matching the type and namespace will
|
||
|
|
-- be put in the channel. IQ of type Result and Error will never be put
|
||
|
|
-- into channels, even though this function won't stop you from registering
|
||
|
|
-- them
|
||
|
|
listenIQChan :: IQType -- ^ type of IQs to receive (Get / Set)
|
||
|
|
-> Text -- ^ namespace of the child element
|
||
|
|
-> XMPPThread (Bool, TChan (IQ, TVar Bool))
|
||
|
|
listenIQChan tp ns = do
|
||
|
|
handlers <- asks iqHandlers
|
||
|
|
liftIO . atomically $ do
|
||
|
|
(byNS, byID) <- readTVar handlers
|
||
|
|
iqCh <- newTChan
|
||
|
|
let (present, byNS') = Map.insertLookupWithKey' (\_ new _ -> new)
|
||
|
|
(tp,ns) iqCh byNS
|
||
|
|
writeTVar handlers (byNS', byID)
|
||
|
|
return $ case present of
|
||
|
|
Nothing -> (True, iqCh)
|
||
|
|
Just iqCh' -> (False, iqCh')
|
||
|
|
|
||
|
|
-- | 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 :: XMPPThread (TChan Message)
|
||
|
|
getMessageChan = do
|
||
|
|
mChR <- asks messagesRef
|
||
|
|
mCh <- liftIO $ readIORef mChR
|
||
|
|
case mCh of
|
||
|
|
Nothing -> do
|
||
|
|
shadow <- asks mShadow
|
||
|
|
mCh' <- liftIO $ atomically $ dupTChan shadow
|
||
|
|
liftIO $ writeIORef mChR (Just mCh')
|
||
|
|
return mCh'
|
||
|
|
Just mCh' -> return mCh'
|
||
|
|
|
||
|
|
-- | see 'getMessageChan'
|
||
|
|
getPresenceChan :: XMPPThread (TChan Presence)
|
||
|
|
getPresenceChan = do
|
||
|
|
pChR <- asks presenceRef
|
||
|
|
pCh <- liftIO $ readIORef pChR
|
||
|
|
case pCh of
|
||
|
|
Nothing -> do
|
||
|
|
shadow <- asks pShadow
|
||
|
|
pCh' <- liftIO $ atomically $ dupTChan shadow
|
||
|
|
liftIO $ writeIORef pChR (Just pCh')
|
||
|
|
return pCh'
|
||
|
|
Just pCh' -> return pCh'
|
||
|
|
|
||
|
|
-- | Drop the local end of the inbound stanza channel
|
||
|
|
-- from our context so it can be GC-ed
|
||
|
|
dropMessageChan :: XMPPThread ()
|
||
|
|
dropMessageChan = do
|
||
|
|
r <- asks messagesRef
|
||
|
|
liftIO $ writeIORef r Nothing
|
||
|
|
|
||
|
|
-- | see 'dropMessageChan'
|
||
|
|
dropPresenceChan :: XMPPThread ()
|
||
|
|
dropPresenceChan = do
|
||
|
|
r <- asks presenceRef
|
||
|
|
liftIO $ writeIORef r Nothing
|
||
|
|
|
||
|
|
-- | Read an element from the inbound stanza channel, acquiring a copy
|
||
|
|
-- of the channel as necessary
|
||
|
|
pullMessage :: XMPPThread Message
|
||
|
|
pullMessage = do
|
||
|
|
c <- getMessageChan
|
||
|
|
st <- liftIO $ atomically $ readTChan c
|
||
|
|
return st
|
||
|
|
|
||
|
|
-- | Read an element from the inbound stanza channel, acquiring a copy
|
||
|
|
-- of the channel as necessary
|
||
|
|
pullPresence :: XMPPThread Presence
|
||
|
|
pullPresence = do
|
||
|
|
c <- getPresenceChan
|
||
|
|
st <- liftIO $ atomically $ readTChan c
|
||
|
|
return st
|
||
|
|
|
||
|
|
|
||
|
|
-- | Send a stanza to the server
|
||
|
|
sendS :: Stanza -> XMPPThread ()
|
||
|
|
sendS a = do
|
||
|
|
out <- asks outCh
|
||
|
|
liftIO . atomically $ writeTChan out a
|
||
|
|
return ()
|
||
|
|
|
||
|
|
-- | Fork a new thread
|
||
|
|
forkXMPP :: XMPPThread () -> XMPPThread ThreadId
|
||
|
|
forkXMPP a = do
|
||
|
|
thread <- ask
|
||
|
|
mCH' <- liftIO $ newIORef Nothing
|
||
|
|
pCH' <- liftIO $ newIORef Nothing
|
||
|
|
liftIO $ forkIO $ runReaderT a (thread {messagesRef = mCH'
|
||
|
|
,presenceRef = pCH'
|
||
|
|
})
|
||
|
|
|
||
|
|
waitForMessage :: (Message -> Bool) -> XMPPThread Message
|
||
|
|
waitForMessage f = do
|
||
|
|
s <- pullMessage
|
||
|
|
if (f s) then
|
||
|
|
return s
|
||
|
|
else do
|
||
|
|
waitForMessage f
|
||
|
|
|
||
|
|
waitForPresence :: (Presence -> Bool) -> XMPPThread Presence
|
||
|
|
waitForPresence f = do
|
||
|
|
s <- pullPresence
|
||
|
|
if (f s) then
|
||
|
|
return s
|
||
|
|
else do
|
||
|
|
waitForPresence f
|
||
|
|
|
||
|
|
|
||
|
|
-- | Run an XMPPMonad action in isolation.
|
||
|
|
-- Reader and writer workers will be temporarily stopped
|
||
|
|
-- and resumed with the new session details once the action returns.
|
||
|
|
-- The Action will run in the reader thread.
|
||
|
|
singleThreaded :: XMPPMonad () -> XMPPThread ()
|
||
|
|
singleThreaded a = do
|
||
|
|
writeLock <- asks writeRef
|
||
|
|
rdr <- asks readerThread
|
||
|
|
_ <- liftIO . atomically $ takeTMVar writeLock -- we replace it with the
|
||
|
|
-- one returned by a
|
||
|
|
liftIO . throwTo rdr . ReaderSignal $ do
|
||
|
|
a
|
||
|
|
out <- gets sConPushBS
|
||
|
|
liftIO . atomically $ putTMVar writeLock out
|
||
|
|
return ()
|
||
|
|
|