From d4fd7ca58070ade0e595c50ae04ee98050011820 Mon Sep 17 00:00:00 2001 From: Philipp Balzarek Date: Thu, 24 Jul 2014 13:16:06 +0200 Subject: [PATCH] add presence Tracker --- pontarius-xmpp.cabal | 1 + source/Network/Xmpp/IM/PresenceTracker.hs | 104 ++++++++++++++++++++++ 2 files changed, 105 insertions(+) create mode 100644 source/Network/Xmpp/IM/PresenceTracker.hs diff --git a/pontarius-xmpp.cabal b/pontarius-xmpp.cabal index 3507453..b2c0b84 100644 --- a/pontarius-xmpp.cabal +++ b/pontarius-xmpp.cabal @@ -93,6 +93,7 @@ Library , Network.Xmpp.Concurrent.Types , Network.Xmpp.IM.Message , Network.Xmpp.IM.Presence + , Network.Xmpp.IM.PresenceTracker , Network.Xmpp.IM.Roster , Network.Xmpp.IM.Roster.Types , Network.Xmpp.Marshal diff --git a/source/Network/Xmpp/IM/PresenceTracker.hs b/source/Network/Xmpp/IM/PresenceTracker.hs new file mode 100644 index 0000000..fd82595 --- /dev/null +++ b/source/Network/Xmpp/IM/PresenceTracker.hs @@ -0,0 +1,104 @@ +{-# LANGUAGE RankNTypes #-} +module Network.Xmpp.IM.PresenceTracker where + +import Control.Applicative +import Control.Concurrent.STM +import Control.Monad (guard) +import Data.Foldable +import Data.Map.Strict (Map) +import qualified Data.Map.Strict as Map +import Data.Maybe +import Data.Traversable +import Lens.Family2 +import Lens.Family2.Stock +import Network.Xmpp.Concurrent.Types +import Network.Xmpp.IM.Presence +import Network.Xmpp.IM.Roster +import Network.Xmpp.Lens hiding (Lens, Traversal) +import Network.Xmpp.Types +import Prelude hiding (mapM) + +-- Map from bare JIDs to a map of full JIDs to show maybe status. +-- +-- Invariants: +-- * The outer map should not have entries for bare JIDs that have no +-- available resource, i.e. the inner map should never be empty +-- +-- * The inner map keys' local and domain part coincide with the outer keys' +newtype Peers = Peers { unPeers :: Map Jid (Map Jid (Maybe IMPresence))} + deriving (Show) + +_peers :: Iso Peers (Map Jid (Map Jid (Maybe IMPresence))) +_peers = mkIso unPeers Peers + +data PeerStatus = PeerAvailable (Maybe IMPresence) + | PeerUnavailable + deriving (Show) + +_PeerAvailable :: Prism PeerStatus (Maybe IMPresence) +_PeerAvailable = prism' PeerAvailable fromPeerAvailable + where + fromPeerAvailable (PeerAvailable pa) = Just pa + fromPeerAvailable _ = Nothing + +_PeerUnavailable :: Prism PeerStatus () +_PeerUnavailable = prism' (const PeerUnavailable) fromPeerUnavailable + where + fromPeerUnavailable PeerUnavailable = Just () + fromPeerUnavailable _ = Nothing + +_PeerStatus :: Iso (Maybe (Maybe IMPresence)) PeerStatus +_PeerStatus = mkIso toPeerStatus fromPeerStatus + where + toPeerStatus (Nothing) = PeerUnavailable + toPeerStatus (Just imp) = PeerAvailable imp + fromPeerStatus PeerUnavailable = Nothing + fromPeerStatus (PeerAvailable imp) = Just imp + +maybeMap :: Iso (Maybe (Map a b)) (Map a b) +maybeMap = mkIso maybeToMap mapToMaybe + where + maybeToMap Nothing = Map.empty + maybeToMap (Just m) = m + mapToMaybe m | Map.null m = Nothing + | otherwise = Just m + +peerStatusL :: Jid -> Lens' Peers PeerStatus +peerStatusL j = _peers . at (toBare j) . maybeMap . at j . _PeerStatus + +peerMapPeerAvailable :: Jid -> Peers -> Bool +peerMapPeerAvailable j = not . nullOf (peerStatusL j . _PeerAvailable) + +statusTracker :: (Stanza -> IO (Either XmppFailure ())) + -> IO (Plugin', TVar Peers) +statusTracker out = do + sessRef <- newTVarIO Nothing + peerMap <- newTVarIO (Peers Map.empty) + + return (Plugin' { inHandler = handleIn sessRef peerMap + , outHandler = out + , onSessionUp = atomically . writeTVar sessRef . Just + }, peerMap) + where + handleIn sessRef peers st _ = do + mbRoster <- atomically (mapM getRoster' =<< readTVar sessRef) + let mbPr = do + pr <- st ^? _Presence -- Only act on presence stanzas + fr <- pr ^? from . _Just . _isFull -- Only act on full JIDs + roster <- mbRoster + -- Check that the from address is in our roster. This means that + -- deactivating the roster effectively turns off this plugin + guard $ Map.member (toBare fr) (roster ^. itemsL) + return (pr, fr) + forM_ mbPr $ \(pr, fr) -> + case presenceType pr of + Available -> atomically . modifyTVar peers + $ set (peerStatusL fr) + (PeerAvailable (getIMPresence pr)) + Unavailable -> atomically . modifyTVar peers + $ set (peerStatusL fr) PeerUnavailable + _ -> return () + return [(st, [])] + +isPeerAvailable :: Jid -> TVar Peers -> STM Bool +isPeerAvailable j peerRef = peerMapPeerAvailable j <$> readTVar peerRef