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/Chat.hs | |
parent | c2cce27bc86c5aefccc5e2afa9b2063e8c915336 (diff) |
MUC: Two-step nominate/comit chat transactions.
Diffstat (limited to 'Presence/Chat.hs')
-rw-r--r-- | Presence/Chat.hs | 120 |
1 files changed, 104 insertions, 16 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 | ||