diff options
author | Joe Crayne <joe@jerkface.net> | 2018-11-13 16:53:24 -0500 |
---|---|---|
committer | Joe Crayne <joe@jerkface.net> | 2018-11-14 01:29:41 -0500 |
commit | 971b23b40e2b519107923dcb6976145e2b83b9cf (patch) | |
tree | 733086bcca436b4726e0507738638f4d06a77b86 /Presence | |
parent | c2cce27bc86c5aefccc5e2afa9b2063e8c915336 (diff) |
MUC: Two-step nominate/comit chat transactions.
Diffstat (limited to 'Presence')
-rw-r--r-- | Presence/Chat.hs | 120 | ||||
-rw-r--r-- | Presence/MUC.hs | 61 | ||||
-rw-r--r-- | Presence/Presence.hs | 10 | ||||
-rw-r--r-- | Presence/Stanza/Build.hs | 7 | ||||
-rw-r--r-- | Presence/Util.hs | 3 | ||||
-rw-r--r-- | Presence/XMPPServer.hs | 62 |
6 files changed, 231 insertions, 32 deletions
diff --git a/Presence/Chat.hs b/Presence/Chat.hs index 47512bf1..172c5242 100644 --- a/Presence/Chat.hs +++ b/Presence/Chat.hs | |||
@@ -1,12 +1,16 @@ | |||
1 | {-# LANGUAGE LambdaCase #-} | ||
1 | module Chat where | 2 | module Chat where |
2 | 3 | ||
4 | import Debug.Trace | ||
3 | import Control.Concurrent.STM | 5 | import Control.Concurrent.STM |
4 | import Control.Monad | 6 | import Control.Monad |
5 | import qualified Data.Map as Map | 7 | import qualified Data.Map as Map |
6 | ;import Data.Map (Map) | 8 | ;import Data.Map (Map) |
7 | import Data.Text | 9 | import Data.Text (Text) |
8 | import Data.Word | 10 | import Data.Word |
9 | 11 | ||
12 | import Util (stripResource) | ||
13 | |||
10 | -- To join a chat room, add a 'ClientRoomLink' to 'roomDesiredLink' with | 14 | -- To join a chat room, add a 'ClientRoomLink' to 'roomDesiredLink' with |
11 | -- 'desireStreamEvent' set to 'Nothing'. | 15 | -- 'desireStreamEvent' set to 'Nothing'. |
12 | -- | 16 | -- |
@@ -16,9 +20,20 @@ data Room k = Room | |||
16 | { roomDesiredTransaction :: TVar (Map k (TVar (Maybe ChatTransaction))) | 20 | { roomDesiredTransaction :: TVar (Map k (TVar (Maybe ChatTransaction))) |
17 | , roomChan :: TChan ChatTransaction | 21 | , roomChan :: TChan ChatTransaction |
18 | , roomFutureSeqNo :: TVar Word64 | 22 | , roomFutureSeqNo :: TVar Word64 |
23 | , roomMembers :: TVar (Map Text{-nick-} (PerMember k)) | ||
24 | , roomAffiliations :: TVar (Map Text{-jid-} Affiliation) | ||
25 | , roomReservations :: TVar (Map Text{-nick-} Text{-jid-}) | ||
26 | } | ||
27 | |||
28 | newtype PerMember k = PerMember | ||
29 | { memberKey :: k | ||
30 | } | ||
31 | |||
32 | newtype Affiliation = Affiliation | ||
33 | { reservedNick :: Text | ||
19 | } | 34 | } |
20 | 35 | ||
21 | data ChatEvent = Join | Part | Action Text | Talk Text | NickChange Text | 36 | data ChatEvent = Join | Part | Action Text | Talk Text -- | NickChange Text |
22 | deriving (Eq,Ord,Show) | 37 | deriving (Eq,Ord,Show) |
23 | 38 | ||
24 | data Membership = Outside | Inside | 39 | data Membership = Outside | Inside |
@@ -62,6 +77,7 @@ newtype RoomHandle = RH (TVar (Maybe ChatTransaction)) | |||
62 | data JoinedRoom k = JoinedRoom | 77 | data JoinedRoom k = JoinedRoom |
63 | { joinedRoom :: Room k | 78 | { joinedRoom :: Room k |
64 | , roomHandle :: RoomHandle | 79 | , roomHandle :: RoomHandle |
80 | , roomTransactions :: TChan ChatTransaction | ||
65 | } | 81 | } |
66 | 82 | ||
67 | newRoom :: STM (Room k) | 83 | newRoom :: STM (Room k) |
@@ -69,10 +85,16 @@ newRoom = do | |||
69 | m <- newTVar Map.empty | 85 | m <- newTVar Map.empty |
70 | c <- newTChan -- newBroadcastTChan | 86 | c <- newTChan -- newBroadcastTChan |
71 | n <- newTVar 0 | 87 | n <- newTVar 0 |
88 | cs <- newTVar Map.empty | ||
89 | as <- newTVar Map.empty | ||
90 | rs <- newTVar Map.empty | ||
72 | return Room | 91 | return Room |
73 | { roomDesiredTransaction = m | 92 | { roomDesiredTransaction = m |
74 | , roomChan = c | 93 | , roomChan = c |
75 | , roomFutureSeqNo = n | 94 | , roomFutureSeqNo = n |
95 | , roomMembers = cs | ||
96 | , roomAffiliations = as | ||
97 | , roomReservations = rs | ||
76 | } | 98 | } |
77 | 99 | ||
78 | 100 | ||
@@ -86,18 +108,18 @@ joinRoom :: Ord k => k | |||
86 | joinRoom k room jid nick = do | 108 | joinRoom k room jid nick = do |
87 | no <- readTVar $ roomFutureSeqNo room | 109 | no <- readTVar $ roomFutureSeqNo room |
88 | v <- newTVar (Just $ ChatTransaction no jid nick [Join]) | 110 | v <- newTVar (Just $ ChatTransaction no jid nick [Join]) |
89 | modifyTVar' (roomDesiredTransaction room) | 111 | modifyTVar' (roomDesiredTransaction room) $ Map.insert k v |
90 | $ Map.insert k v | 112 | c <- dupTChan (roomChan room) |
91 | return $ JoinedRoom room (RH v) | 113 | return $ JoinedRoom room (RH v) c |
92 | 114 | ||
93 | partRoom :: JoinedRoom k -> Maybe Text -> Text -> STM () | 115 | partRoom :: JoinedRoom k -> Maybe Text -> Text -> STM () |
94 | partRoom (JoinedRoom room (RH v)) jid nick = do | 116 | partRoom (JoinedRoom room (RH v) c) jid nick = do |
95 | writeTVar v Nothing -- Cancel pending chat. | 117 | writeTVar v Nothing -- Cancel pending chat. |
96 | sendChat (JoinedRoom room (RH v)) jid nick [Part] | 118 | sendChat (JoinedRoom room (RH v) c) jid nick [Part] |
97 | return () | 119 | return () |
98 | 120 | ||
99 | sendChat :: JoinedRoom k -> Maybe Text -> Text -> [ChatEvent] -> STM Bool | 121 | sendChat :: JoinedRoom k -> Maybe Text -> Text -> [ChatEvent] -> STM Bool |
100 | sendChat (JoinedRoom room (RH v)) jid nick chat = do | 122 | sendChat (JoinedRoom room (RH v) _) jid nick chat = do |
101 | mpending <- readTVar v | 123 | mpending <- readTVar v |
102 | case mpending of | 124 | case mpending of |
103 | Nothing -> do | 125 | Nothing -> do |
@@ -110,9 +132,9 @@ sendChat (JoinedRoom room (RH v)) jid nick chat = do | |||
110 | -- | Blocks until a transaction occurs. Optionally, a failed transaction will | 132 | -- | Blocks until a transaction occurs. Optionally, a failed transaction will |
111 | -- be automatically renewed. | 133 | -- be automatically renewed. |
112 | readRoom :: Ord k => k -> JoinedRoom k -> STM (Bool, ChatTransaction) | 134 | readRoom :: Ord k => k -> JoinedRoom k -> STM (Bool, ChatTransaction) |
113 | readRoom k (JoinedRoom room (RH v)) = do | 135 | readRoom k (JoinedRoom room (RH v) c) = do |
114 | mpending <- readTVar v | 136 | mpending <- readTVar v |
115 | final <- readTChan $ roomChan room | 137 | final <- readTChan c |
116 | case mpending of | 138 | case mpending of |
117 | Just pending -> do | 139 | Just pending -> do |
118 | if pending == final | 140 | if pending == final |
@@ -128,9 +150,75 @@ readRoom k (JoinedRoom room (RH v)) = do | |||
128 | return (False,final) | 150 | return (False,final) |
129 | Nothing -> return (False,final) | 151 | Nothing -> return (False,final) |
130 | 152 | ||
153 | roomOccupants :: Room k-> STM [(Text{-nick-},Maybe Text{-friendly name-})] | ||
154 | roomOccupants room = do | ||
155 | ns <- Map.keys <$> readTVar (roomMembers room) | ||
156 | return $ map (\n -> (n,Just n)) ns | ||
157 | |||
158 | roomReservedNick :: Room k -> Text{-JID-} -> STM (Maybe Text{-nick-}) | ||
159 | roomReservedNick room jid = do | ||
160 | a <- Map.lookup jid <$> readTVar (roomAffiliations room) | ||
161 | return $ reservedNick <$> a | ||
162 | |||
163 | roomFriendlyName :: Room k -> STM (Maybe Text) | ||
164 | roomFriendlyName _ = return Nothing | ||
165 | |||
131 | -- Room implementation interface | 166 | -- Room implementation interface |
132 | 167 | ||
133 | roomCommit :: Room k -> ChatTransaction -> STM () | 168 | data Validation = Malformed | Requires Membership | Denied | Valid Membership Membership |
134 | roomCommit room t = do | 169 | deriving (Eq,Ord,Show,Read) |
135 | modifyTVar' (roomFutureSeqNo room) succ | 170 | |
136 | writeTChan (roomChan room) t | 171 | validateTransaction :: Ord k => Room k -> k -> ChatTransaction -> STM Validation |
172 | validateTransaction room k t@(ChatTransaction no mjid nick xs) | ||
173 | | null xs = return Malformed | ||
174 | | otherwise = case membershipEffect xs of | ||
175 | MembershipEffect Inside what -> | ||
176 | Map.lookup nick <$> readTVar (roomMembers room) >>= \case | ||
177 | Nothing -> return (Requires Inside) | ||
178 | Just p | memberKey p /= k -> return Denied | ||
179 | _ -> return (Valid Inside what) | ||
180 | MembershipEffect Outside what -> do | ||
181 | Map.lookup k <$> return Map.empty {- readTVar (roomDesiredTransaction room) -} >>= \case | ||
182 | Nothing -> Map.lookup nick <$> readTVar (roomMembers room) >>= \case | ||
183 | Nothing -> Map.lookup nick <$> readTVar (roomReservations room) >>= \case | ||
184 | Just rjid | Just jid <- mjid | ||
185 | , stripResource jid == rjid | ||
186 | -> return (Valid Outside what) | ||
187 | Just _ -> return Denied | ||
188 | Nothing -> return (Valid Outside what) | ||
189 | Just _ -> return Denied -- Nick already taken. | ||
190 | Just _ -> return (Requires Outside) | ||
191 | _ -> return Malformed | ||
192 | |||
193 | |||
194 | roomCommit :: Ord k => Room k -> k -> ChatTransaction -> STM () | ||
195 | roomCommit room k t = do | ||
196 | let fin = do | ||
197 | trace "increment seqno!" $ return () | ||
198 | modifyTVar' (roomFutureSeqNo room) succ | ||
199 | writeTChan (roomChan room) t | ||
200 | v <- validateTransaction room k t | ||
201 | trace ("roomCommit " ++ show v) $ return () | ||
202 | case v of | ||
203 | Valid Outside Inside -> do | ||
204 | modifyTVar' (roomMembers room) $ Map.insert (chatSender t) PerMember | ||
205 | { memberKey = k | ||
206 | } | ||
207 | fin | ||
208 | Valid Inside Outside -> do | ||
209 | modifyTVar' (roomMembers room) $ Map.delete (chatSender t) | ||
210 | fin | ||
211 | Valid _ _ -> fin | ||
212 | _ -> return () | ||
213 | |||
214 | roomPending :: Ord k => Room k -> STM (Map k ChatTransaction) | ||
215 | roomPending room = do | ||
216 | no <- readTVar $ roomFutureSeqNo room | ||
217 | m <- Map.mapMaybe (>>= \t -> do guard (chatSeqNo t == no) | ||
218 | return t) | ||
219 | <$> do readTVar (roomDesiredTransaction room) | ||
220 | >>= mapM readTVar | ||
221 | fmap (Map.mapMaybe id) | ||
222 | $ sequence $ Map.mapWithKey (\k t -> validateTransaction room k t >>= \case | ||
223 | Valid _ _ -> return (Just t) | ||
224 | _ -> return Nothing) m | ||
diff --git a/Presence/MUC.hs b/Presence/MUC.hs new file mode 100644 index 00000000..76c53391 --- /dev/null +++ b/Presence/MUC.hs | |||
@@ -0,0 +1,61 @@ | |||
1 | module MUC where | ||
2 | |||
3 | import Control.Monad | ||
4 | import Control.Concurrent.STM | ||
5 | |||
6 | import qualified Data.Map.Strict as Map | ||
7 | ;import Data.Map.Strict (Map) | ||
8 | |||
9 | import Chat | ||
10 | import ConnectionKey | ||
11 | import Data.Text (Text) | ||
12 | |||
13 | data MUC = MUC | ||
14 | { mucRooms :: TVar (Map Text (Room ClientAddress)) | ||
15 | , mucChan :: TChan MUCEvent | ||
16 | } | ||
17 | |||
18 | data MUCEvent = MUCCreate Text{-room-} Text{-JID-} Text{-nick-} (Room ClientAddress) | ||
19 | |||
20 | |||
21 | newMUC :: STM MUC | ||
22 | newMUC = MUC <$> newTVar Map.empty <*> newBroadcastTChan | ||
23 | |||
24 | mucRoomList :: MUC -> IO [(Text{-room-},Maybe Text{-friendly room name-})] | ||
25 | mucRoomList muc = atomically $ do | ||
26 | rs <- Map.toList <$> readTVar (mucRooms muc) | ||
27 | forM rs $ \(rkey,r) -> do | ||
28 | fn <- roomFriendlyName r | ||
29 | return (rkey,fn) | ||
30 | |||
31 | mucRoomOccupants :: MUC -> Text{-room-} -> IO [(Text{-nick-},Maybe Text{-friendly name-})] | ||
32 | mucRoomOccupants muc rkey = atomically $ do | ||
33 | mr <- Map.lookup rkey <$> readTVar (mucRooms muc) | ||
34 | case mr of | ||
35 | Nothing -> return [] | ||
36 | Just r -> roomOccupants r | ||
37 | |||
38 | mucReservedNick :: MUC -> Text{-room-} -> IO (Maybe (Text{-JID-} -> IO (Maybe Text))) | ||
39 | mucReservedNick muc rkey = atomically $ do | ||
40 | mr <- Map.lookup rkey <$> readTVar (mucRooms muc) | ||
41 | case mr of | ||
42 | Nothing -> return Nothing | ||
43 | Just r -> return $ Just $ \jid -> atomically $ roomReservedNick r jid | ||
44 | |||
45 | mucJoinRoom :: MUC -> Text{-JID-} -> Text{-nick-} -> Text{-room-} -> ClientAddress -> IO (JoinedRoom ClientAddress) | ||
46 | mucJoinRoom muc jid nick rkey k = atomically $ do | ||
47 | mr <- Map.lookup rkey <$> readTVar (mucRooms muc) | ||
48 | case mr of | ||
49 | Nothing -> do | ||
50 | -- create room. | ||
51 | r <- newRoom | ||
52 | v <- joinRoom k r (Just jid) nick | ||
53 | modifyTVar' (mucRooms muc) $ Map.insert rkey r | ||
54 | writeTChan (mucChan muc) $ MUCCreate rkey jid nick r | ||
55 | return v | ||
56 | Just r -> do | ||
57 | -- join room. | ||
58 | v <- joinRoom k r (Just jid) nick | ||
59 | return v | ||
60 | |||
61 | |||
diff --git a/Presence/Presence.hs b/Presence/Presence.hs index 0a73aced..f8a18388 100644 --- a/Presence/Presence.hs +++ b/Presence/Presence.hs | |||
@@ -144,10 +144,12 @@ nameForClient state k = do | |||
144 | "." -> textHostName | 144 | "." -> textHostName |
145 | profile -> return profile | 145 | profile -> return profile |
146 | 146 | ||
147 | presenceHooks :: PresenceState stat -> Int -> Maybe SockAddr -- ^ client-to-server bind address | 147 | presenceHooks :: PresenceState stat -> Map Text MUC |
148 | -> Int | ||
149 | -> Maybe SockAddr -- ^ client-to-server bind address | ||
148 | -> Maybe SockAddr -- ^ server-to-server bind address | 150 | -> Maybe SockAddr -- ^ server-to-server bind address |
149 | -> XMPPServerParameters | 151 | -> XMPPServerParameters |
150 | presenceHooks state verbosity mclient mpeer = XMPPServerParameters | 152 | presenceHooks state chats verbosity mclient mpeer = XMPPServerParameters |
151 | { xmppChooseResourceName = chooseResourceName state | 153 | { xmppChooseResourceName = chooseResourceName state |
152 | , xmppTellClientHisName = tellClientHisName state | 154 | , xmppTellClientHisName = tellClientHisName state |
153 | , xmppTellMyNameToClient = nameForClient state | 155 | , xmppTellMyNameToClient = nameForClient state |
@@ -169,7 +171,7 @@ presenceHooks state verbosity mclient mpeer = XMPPServerParameters | |||
169 | , xmppClientInformSubscription = clientInformSubscription state | 171 | , xmppClientInformSubscription = clientInformSubscription state |
170 | , xmppPeerInformSubscription = peerInformSubscription state | 172 | , xmppPeerInformSubscription = peerInformSubscription state |
171 | , xmppVerbosity = return verbosity | 173 | , xmppVerbosity = return verbosity |
172 | , xmppGroupChat = Map.singleton "chat" MUC | 174 | , xmppGroupChat = chats {- Map.singleton "chat" chat |
173 | { mucRoomList = return [("testroom",Just "testroom")] | 175 | { mucRoomList = return [("testroom",Just "testroom")] |
174 | , mucRoomOccupants = \case | 176 | , mucRoomOccupants = \case |
175 | "testroom" -> return [("fakeperson",Nothing)] | 177 | "testroom" -> return [("fakeperson",Nothing)] |
@@ -183,7 +185,7 @@ presenceHooks state verbosity mclient mpeer = XMPPServerParameters | |||
183 | ++ " with nick: " ++ Text.unpack nick | 185 | ++ " with nick: " ++ Text.unpack nick |
184 | -- TODO: broadcast presence to all participants. | 186 | -- TODO: broadcast presence to all participants. |
185 | -- See 7.2.3 of XEP-0045 | 187 | -- See 7.2.3 of XEP-0045 |
186 | } | 188 | -} |
187 | , xmppClientBind = mclient | 189 | , xmppClientBind = mclient |
188 | , xmppPeerBind = mpeer | 190 | , xmppPeerBind = mpeer |
189 | } | 191 | } |
diff --git a/Presence/Stanza/Build.hs b/Presence/Stanza/Build.hs index 5c4d371a..e02684f5 100644 --- a/Presence/Stanza/Build.hs +++ b/Presence/Stanza/Build.hs | |||
@@ -48,7 +48,10 @@ makeInformSubscription namespace from to approved = | |||
48 | , EventEndElement (mkname namespace "presence")] | 48 | , EventEndElement (mkname namespace "presence")] |
49 | 49 | ||
50 | makePresenceStanza :: Text -> Maybe Text -> JabberShow -> IO Stanza | 50 | makePresenceStanza :: Text -> Maybe Text -> JabberShow -> IO Stanza |
51 | makePresenceStanza namespace mjid pstat = do | 51 | makePresenceStanza ns mjid pstat = makePresenceStanzaEx ns mjid pstat [] |
52 | |||
53 | makePresenceStanzaEx :: Text -> Maybe Text -> JabberShow -> [XML.Event]-> IO Stanza | ||
54 | makePresenceStanzaEx namespace mjid pstat es = do | ||
52 | stanzaFromList PresenceStatus { presenceShow = pstat | 55 | stanzaFromList PresenceStatus { presenceShow = pstat |
53 | , presencePriority = Nothing | 56 | , presencePriority = Nothing |
54 | , presenceStatus = [] | 57 | , presenceStatus = [] |
@@ -56,7 +59,7 @@ makePresenceStanza namespace mjid pstat = do | |||
56 | } | 59 | } |
57 | $ [ EventBeginElement (mkname namespace "presence") | 60 | $ [ EventBeginElement (mkname namespace "presence") |
58 | (setFrom $ typ pstat) ] | 61 | (setFrom $ typ pstat) ] |
59 | ++ (shw pstat >>= jabberShow) ++ | 62 | ++ (shw pstat >>= jabberShow) ++ es ++ |
60 | [ EventEndElement (mkname namespace "presence")] | 63 | [ EventEndElement (mkname namespace "presence")] |
61 | where | 64 | where |
62 | setFrom = maybe id | 65 | setFrom = maybe id |
diff --git a/Presence/Util.hs b/Presence/Util.hs index ef98d415..e19b35fd 100644 --- a/Presence/Util.hs +++ b/Presence/Util.hs | |||
@@ -14,6 +14,9 @@ import Network.Address (setPort) | |||
14 | type UserName = Text | 14 | type UserName = Text |
15 | type ResourceName = Text | 15 | type ResourceName = Text |
16 | 16 | ||
17 | stripResource :: Text -> Text | ||
18 | stripResource jid = let (n,h,_) = splitJID jid | ||
19 | in unsplitJID (n,h,Nothing) | ||
17 | 20 | ||
18 | unsplitJID :: (Maybe UserName,Text,Maybe ResourceName) -> Text | 21 | unsplitJID :: (Maybe UserName,Text,Maybe ResourceName) -> Text |
19 | unsplitJID (n,h,r) = username <> h <> resource | 22 | unsplitJID (n,h,r) = username <> h <> resource |
diff --git a/Presence/XMPPServer.hs b/Presence/XMPPServer.hs index 3a255cdd..912bbf0b 100644 --- a/Presence/XMPPServer.hs +++ b/Presence/XMPPServer.hs | |||
@@ -2,6 +2,7 @@ | |||
2 | {-# LANGUAGE DoAndIfThenElse #-} | 2 | {-# LANGUAGE DoAndIfThenElse #-} |
3 | {-# LANGUAGE ExistentialQuantification #-} | 3 | {-# LANGUAGE ExistentialQuantification #-} |
4 | {-# LANGUAGE FlexibleInstances #-} | 4 | {-# LANGUAGE FlexibleInstances #-} |
5 | {-# LANGUAGE LambdaCase #-} | ||
5 | {-# LANGUAGE MultiWayIf #-} | 6 | {-# LANGUAGE MultiWayIf #-} |
6 | {-# LANGUAGE OverloadedStrings #-} | 7 | {-# LANGUAGE OverloadedStrings #-} |
7 | {-# LANGUAGE RankNTypes #-} | 8 | {-# LANGUAGE RankNTypes #-} |
@@ -71,6 +72,7 @@ import Control.Concurrent.Lifted (forkIO,myThreadId,ThreadId) | |||
71 | import GHC.Conc (labelThread) | 72 | import GHC.Conc (labelThread) |
72 | #endif | 73 | #endif |
73 | import Control.Concurrent.STM | 74 | import Control.Concurrent.STM |
75 | import Data.List hiding ((\\)) | ||
74 | -- import Control.Concurrent.STM.TChan | 76 | -- import Control.Concurrent.STM.TChan |
75 | import Network.SocketLike | 77 | import Network.SocketLike |
76 | import Text.Printf | 78 | import Text.Printf |
@@ -108,6 +110,8 @@ import DebugTag | |||
108 | import Stanza.Build | 110 | import Stanza.Build |
109 | import Stanza.Parse | 111 | import Stanza.Parse |
110 | import Stanza.Types | 112 | import Stanza.Types |
113 | import MUC | ||
114 | import Chat | ||
111 | 115 | ||
112 | -- peerport :: PortNumber | 116 | -- peerport :: PortNumber |
113 | -- peerport = 5269 | 117 | -- peerport = 5269 |
@@ -121,13 +125,6 @@ my_uuid = "154ae29f-98f2-4af4-826d-a40c8a188574" | |||
121 | newtype Local a = Local a deriving (Eq,Ord,Show) | 125 | newtype Local a = Local a deriving (Eq,Ord,Show) |
122 | newtype Remote a = Remote a deriving (Eq,Ord,Show) | 126 | newtype Remote a = Remote a deriving (Eq,Ord,Show) |
123 | 127 | ||
124 | data MUC = MUC | ||
125 | { mucRoomList :: IO [(Text{-room-},Maybe Text{-friendly room name-})] | ||
126 | , mucRoomOccupants :: Text{-room-} -> IO [(Text{-nick-},Maybe Text{-friendly name-})] | ||
127 | , mucReservedNick :: Text{-room-} -> IO (Maybe (Text{-JID-} -> IO (Maybe Text))) | ||
128 | , mucJoinRoom :: Text{-room-} -> Text{-nick-} -> ClientAddress -> Stanza -> IO () | ||
129 | } | ||
130 | |||
131 | data XMPPServerParameters = | 128 | data XMPPServerParameters = |
132 | XMPPServerParameters | 129 | XMPPServerParameters |
133 | { -- | Called when a client requests a resource id. The first Maybe indicates | 130 | { -- | Called when a client requests a resource id. The first Maybe indicates |
@@ -1286,6 +1283,8 @@ monitor sv params xmpp = do | |||
1286 | stanzas <- atomically newTChan | 1283 | stanzas <- atomically newTChan |
1287 | quitVar <- atomically newEmptyTMVar | 1284 | quitVar <- atomically newEmptyTMVar |
1288 | pp_mvar <- newMVar () -- Lock for synchronous pretty-printing of stanzas in log. | 1285 | pp_mvar <- newMVar () -- Lock for synchronous pretty-printing of stanzas in log. |
1286 | joined_rooms <- atomically | ||
1287 | $ newTVar (Map.empty :: Map.Map ClientAddress (Map.Map (Text,Text) (TChan Stanza,JoinedRoom ClientAddress))) | ||
1289 | fix $ \loop -> do | 1288 | fix $ \loop -> do |
1290 | action <- atomically $ foldr1 orElse | 1289 | action <- atomically $ foldr1 orElse |
1291 | [ readTChan chan >>= \((addr,u),e) -> return $ do | 1290 | [ readTChan chan >>= \((addr,u),e) -> return $ do |
@@ -1325,7 +1324,7 @@ monitor sv params xmpp = do | |||
1325 | -} | 1324 | -} |
1326 | dup <- cloneStanza stanza | 1325 | dup <- cloneStanza stanza |
1327 | 1326 | ||
1328 | t <- forkIO $ do applyStanza sv quitVar xmpp stanza | 1327 | t <- forkIO $ do applyStanza sv joined_rooms quitVar xmpp stanza |
1329 | forwardStanza quitVar xmpp stanza | 1328 | forwardStanza quitVar xmpp stanza |
1330 | labelThread t $ "process." ++ stanzaTypeString stanza | 1329 | labelThread t $ "process." ++ stanzaTypeString stanza |
1331 | 1330 | ||
@@ -1347,6 +1346,19 @@ monitor sv params xmpp = do | |||
1347 | liftIO $ takeMVar pp_mvar | 1346 | liftIO $ takeMVar pp_mvar |
1348 | runConduit $ stanzaToConduit dup .| prettyPrint typ | 1347 | runConduit $ stanzaToConduit dup .| prettyPrint typ |
1349 | liftIO $ putMVar pp_mvar () | 1348 | liftIO $ putMVar pp_mvar () |
1349 | , do | ||
1350 | m <- readTVar joined_rooms | ||
1351 | foldr orElse retry $ (`map` (do (k,rs) <- Map.toList m | ||
1352 | i <- Map.toList rs | ||
1353 | return (k,i))) | ||
1354 | $ \(k,((rkey,muckey),(replyto,r))) -> do | ||
1355 | (mine,ChatTransaction no cjid cnick es) <- readRoom k r | ||
1356 | return $ do | ||
1357 | me <- xmppTellMyNameToClient xmpp k | ||
1358 | dput XJabber $ "CHAT " ++ Text.unpack rkey ++ ": <" ++ Text.unpack cnick ++ "> " ++ show es | ||
1359 | forM_ es $ \case | ||
1360 | Join | mine -> sendRoomOccupants muckey me cnick rkey r replyto | ||
1361 | _ -> return () | ||
1350 | ] | 1362 | ] |
1351 | action | 1363 | action |
1352 | loop | 1364 | loop |
@@ -1355,6 +1367,30 @@ monitor sv params xmpp = do | |||
1355 | where | 1367 | where |
1356 | _ = str :: String | 1368 | _ = str :: String |
1357 | 1369 | ||
1370 | sendRoomOccupants :: Text -> Text -> Text -> Text -> JoinedRoom k -> TChan Stanza -> IO () | ||
1371 | sendRoomOccupants a me them room r replyto = do | ||
1372 | let roomjid n = room <> "@" <> a <> "." <> me <> "/" <> n | ||
1373 | xs <- map (\(n,m) -> (roomjid n, m)) | ||
1374 | <$> atomically (roomOccupants $ joinedRoom r) | ||
1375 | let (ys,xs') = partition (\(jid,_) -> jid == roomjid them) xs | ||
1376 | forM_ xs $ \(jid,_) -> do | ||
1377 | stanza <- makePresenceStanzaEx "jabber:client" (Just jid) Available | ||
1378 | [ EventBeginElement "{http://jabber.org/protocol/muc#user}x" [] | ||
1379 | , EventEndElement "{http://jabber.org/protocol/muc#user}x" | ||
1380 | ] | ||
1381 | ioWriteChan replyto stanza | ||
1382 | forM_ ys $ \(jid,_) -> do | ||
1383 | stanza <- makePresenceStanzaEx "jabber:client" (Just jid) Available | ||
1384 | [ EventBeginElement "{http://jabber.org/protocol/muc#user}x" [] | ||
1385 | , EventBeginElement "{http://jabber.org/protocol/muc#user}status" | ||
1386 | [ ("code",[ContentText "110"]) -- self-presence code. | ||
1387 | ] | ||
1388 | , EventEndElement "{http://jabber.org/protocol/muc#user}status" | ||
1389 | , EventEndElement "{http://jabber.org/protocol/muc#user}x" | ||
1390 | ] | ||
1391 | ioWriteChan replyto stanza | ||
1392 | |||
1393 | |||
1358 | stanzaTypeString :: StanzaWrap a -> String | 1394 | stanzaTypeString :: StanzaWrap a -> String |
1359 | stanzaTypeString stanza = concat . take 1 . words $ show (stanzaType stanza) | 1395 | stanzaTypeString stanza = concat . take 1 . words $ show (stanzaType stanza) |
1360 | 1396 | ||
@@ -1382,12 +1418,13 @@ lookupService me mucs to = case Text.toLower to of | |||
1382 | _ -> NotMe | 1418 | _ -> NotMe |
1383 | 1419 | ||
1384 | applyStanza :: Server PeerAddress ConnectionData releaseKey Event | 1420 | applyStanza :: Server PeerAddress ConnectionData releaseKey Event |
1421 | -> TVar (Map.Map ClientAddress (Map.Map (Text,Text) (TChan Stanza,JoinedRoom ClientAddress))) | ||
1385 | -> TMVar () | 1422 | -> TMVar () |
1386 | -> XMPPServerParameters | 1423 | -> XMPPServerParameters |
1387 | -> StanzaWrap (LockedChan Event) | 1424 | -> StanzaWrap (LockedChan Event) |
1388 | -> IO () | 1425 | -> IO () |
1389 | 1426 | ||
1390 | applyStanza sv quitVar xmpp stanza = case stanzaOrigin stanza of | 1427 | applyStanza sv joined_rooms quitVar xmpp stanza = case stanzaOrigin stanza of |
1391 | ClientOrigin k replyto -> | 1428 | ClientOrigin k replyto -> |
1392 | case stanzaType stanza of | 1429 | case stanzaType stanza of |
1393 | RequestResource clientsNameForMe wanted -> do | 1430 | RequestResource clientsNameForMe wanted -> do |
@@ -1446,7 +1483,12 @@ applyStanza sv quitVar xmpp stanza = case stanzaOrigin stanza of | |||
1446 | [ ("by", [ContentText roomjid]) ] | 1483 | [ ("by", [ContentText roomjid]) ] |
1447 | sendReply quitVar (Error JidMalformed (head reply)) reply replyto | 1484 | sendReply quitVar (Error JidMalformed (head reply)) reply replyto |
1448 | Just nick -> do | 1485 | Just nick -> do |
1449 | mucJoinRoom muc room nick k stanza | 1486 | jid <- xmppTellClientHisName xmpp k |
1487 | r <- mucJoinRoom muc jid nick room k -- stanza | ||
1488 | atomically $ do | ||
1489 | jrs <- readTVar joined_rooms | ||
1490 | let m = Map.findWithDefault Map.empty k jrs | ||
1491 | writeTVar joined_rooms $ Map.insert k (Map.insert (room,mucname) (replyto,r) m) jrs | ||
1450 | | otherwise -> do | 1492 | | otherwise -> do |
1451 | -- Handle presence stanza that is not a chatroom join. | 1493 | -- Handle presence stanza that is not a chatroom join. |
1452 | xmppInformClientPresence xmpp k stanza | 1494 | xmppInformClientPresence xmpp k stanza |