summaryrefslogtreecommitdiff
path: root/dht/Presence/Chat.hs
diff options
context:
space:
mode:
authorJames Crayne <jim.crayne@gmail.com>2019-09-28 13:43:29 -0400
committerJoe Crayne <joe@jerkface.net>2020-01-01 19:27:53 -0500
commit11987749fc6e6d3e53ea737d46d5ab13a16faeb8 (patch)
tree5716463275c2d3e902889db619908ded2a73971c /dht/Presence/Chat.hs
parentadd2c76bced51fde5e9917e7449ef52be70faf87 (diff)
Factor out some new libraries
word64-map: Data.Word64Map network-addr: Network.Address tox-crypto: Crypto.Tox lifted-concurrent: Control.Concurrent.Lifted.Instrument Control.Concurrent.Async.Lifted.Instrument psq-wrap: Data.Wrapper.PSQInt Data.Wrapper.PSQ minmax-psq: Data.MinMaxPSQ tasks: Control.Concurrent.Tasks kad: Network.Kademlia Network.Kademlia.Bootstrap Network.Kademlia.Routing Network.Kademlia.CommonAPI Network.Kademlia.Persistence Network.Kademlia.Search
Diffstat (limited to 'dht/Presence/Chat.hs')
-rw-r--r--dht/Presence/Chat.hs227
1 files changed, 227 insertions, 0 deletions
diff --git a/dht/Presence/Chat.hs b/dht/Presence/Chat.hs
new file mode 100644
index 00000000..03bea44b
--- /dev/null
+++ b/dht/Presence/Chat.hs
@@ -0,0 +1,227 @@
1{-# LANGUAGE LambdaCase #-}
2module Chat where
3
4import Debug.Trace
5import Data.Semigroup
6import Control.Concurrent.STM
7import Control.Monad
8import qualified Data.Map as Map
9 ;import Data.Map (Map)
10import Data.Text (Text)
11import Data.Word
12
13import Util (stripResource)
14
15-- To join a chat room, add a 'ClientRoomLink' to 'roomDesiredLink' with
16-- 'desireStreamEvent' set to 'Nothing'.
17--
18-- To leave a chat room, remove the 'ClientRoomLink' from the map.
19
20data Room k = Room
21 { roomDesiredTransaction :: TVar (Map k (TVar (Maybe ChatTransaction)))
22 , roomChan :: TChan ChatTransaction
23 , roomFutureSeqNo :: TVar Word64
24 , roomMembers :: TVar (Map Text{-nick-} (PerMember k))
25 , roomAffiliations :: TVar (Map Text{-jid-} Affiliation)
26 , roomReservations :: TVar (Map Text{-nick-} Text{-jid-})
27 }
28
29newtype PerMember k = PerMember
30 { memberKey :: k
31 }
32
33newtype Affiliation = Affiliation
34 { reservedNick :: Text
35 }
36
37data ChatEvent = Join | Part | Action Text | Talk Text -- | NickChange Text
38 deriving (Eq,Ord,Show)
39
40data Membership = Outside | Inside
41 deriving (Eq,Ord,Read,Show)
42
43data MembershipEffect = MembershipEffect { fromMembership :: Membership
44 , toMembership :: Membership
45 }
46 | NoMembershipEffect
47 | InvalidMembershipEffect
48 deriving (Eq,Ord,Read,Show)
49
50instance Semigroup MembershipEffect
51
52instance Monoid MembershipEffect where
53 mempty = NoMembershipEffect
54 MembershipEffect a x `mappend` MembershipEffect y b
55 | x == y = MembershipEffect a b
56 | otherwise = InvalidMembershipEffect
57 NoMembershipEffect `mappend` b = b
58 a `mappend` NoMembershipEffect = a
59 _ `mappend` _ = InvalidMembershipEffect
60
61chatEffect :: ChatEvent -> MembershipEffect
62chatEffect Join = MembershipEffect Outside Inside
63chatEffect Part = MembershipEffect Inside Outside
64chatEffect _ = MembershipEffect Inside Inside
65
66membershipEffect :: [ChatEvent] -> MembershipEffect
67membershipEffect xs = foldMap chatEffect xs
68
69
70data ChatTransaction = ChatTransaction
71 { chatSeqNo :: Word64
72 , chatSenderJID :: Maybe Text
73 , chatSender :: Text
74 , chatMessage :: [ChatEvent]
75 }
76 deriving (Eq,Ord,Show)
77
78newtype RoomHandle = RH (TVar (Maybe ChatTransaction))
79
80data JoinedRoom k = JoinedRoom
81 { joinedRoom :: Room k
82 , joinedNick :: Text
83 , roomHandle :: RoomHandle
84 , roomTransactions :: TChan ChatTransaction
85 }
86
87newRoom :: STM (Room k)
88newRoom = do
89 m <- newTVar Map.empty
90 c <- newTChan -- newBroadcastTChan
91 n <- newTVar 0
92 cs <- newTVar Map.empty
93 as <- newTVar Map.empty
94 rs <- newTVar Map.empty
95 return Room
96 { roomDesiredTransaction = m
97 , roomChan = c
98 , roomFutureSeqNo = n
99 , roomMembers = cs
100 , roomAffiliations = as
101 , roomReservations = rs
102 }
103
104
105--- Client interface
106
107joinRoom :: Ord k => k
108 -> Room k
109 -> Maybe Text
110 -> Text
111 -> STM (JoinedRoom k)
112joinRoom k room jid nick = do
113 no <- readTVar $ roomFutureSeqNo room
114 v <- newTVar (Just $ ChatTransaction no jid nick [Join])
115 modifyTVar' (roomDesiredTransaction room) $ Map.insert k v
116 c <- dupTChan (roomChan room)
117 return $ JoinedRoom room nick (RH v) c
118
119partRoom :: JoinedRoom k -> Maybe Text -> STM ()
120partRoom (JoinedRoom room nick (RH v) c) jid = do
121 writeTVar v Nothing -- Cancel pending chat.
122 sendChat (JoinedRoom room nick (RH v) c) jid [Part]
123 return ()
124
125sendChat :: JoinedRoom k -> Maybe Text -> [ChatEvent] -> STM Bool
126sendChat (JoinedRoom room nick (RH v) _) jid chat = do
127 mpending <- readTVar v
128 no <- readTVar $ roomFutureSeqNo room
129 case mpending of
130 Just (ChatTransaction no' _ _ _) | no' >= no -> return False
131 _ -> do
132 writeTVar v (Just $ ChatTransaction no jid nick chat)
133 return True
134
135-- | Blocks until a transaction occurs. Optionally, a failed transaction will
136-- be automatically renewed.
137readRoom :: Ord k => k -> JoinedRoom k -> STM (Bool, ChatTransaction)
138readRoom k (JoinedRoom room _ (RH v) c) = do
139 mpending <- readTVar v
140 final <- readTChan c
141 case mpending of
142 Just pending -> do
143 if pending == final
144 then do
145 writeTVar v Nothing
146 when (Part `elem` chatMessage final) $ do
147 modifyTVar' (roomDesiredTransaction room)
148 $ Map.delete k
149 return (True,final)
150 else do
151 no <- readTVar $ roomFutureSeqNo room
152 writeTVar v $ Just pending { chatSeqNo = no }
153 return (False,final)
154 Nothing -> return (False,final)
155
156roomOccupants :: Room k-> STM [(Text{-nick-},Maybe Text{-friendly name-})]
157roomOccupants room = do
158 ns <- Map.keys <$> readTVar (roomMembers room)
159 return $ map (\n -> (n,Just n)) ns
160
161roomReservedNick :: Room k -> Text{-JID-} -> STM (Maybe Text{-nick-})
162roomReservedNick room jid = do
163 a <- Map.lookup jid <$> readTVar (roomAffiliations room)
164 return $ reservedNick <$> a
165
166roomFriendlyName :: Room k -> STM (Maybe Text)
167roomFriendlyName _ = return Nothing
168
169-- Room implementation interface
170
171data Validation = Malformed | Requires Membership | Denied | Valid Membership Membership
172 deriving (Eq,Ord,Show,Read)
173
174validateTransaction :: Ord k => Room k -> k -> ChatTransaction -> STM Validation
175validateTransaction room k t@(ChatTransaction no mjid nick xs)
176 | null xs = return Malformed
177 | otherwise = case membershipEffect xs of
178 MembershipEffect Inside what ->
179 Map.lookup nick <$> readTVar (roomMembers room) >>= \case
180 Nothing -> return (Requires Inside)
181 Just p | memberKey p /= k -> return Denied
182 _ -> return (Valid Inside what)
183 MembershipEffect Outside what -> do
184 Map.lookup k <$> return Map.empty {- readTVar (roomDesiredTransaction room) -} >>= \case
185 Nothing -> Map.lookup nick <$> readTVar (roomMembers room) >>= \case
186 Nothing -> Map.lookup nick <$> readTVar (roomReservations room) >>= \case
187 Just rjid | Just jid <- mjid
188 , stripResource jid == rjid
189 -> return (Valid Outside what)
190 Just _ -> return Denied
191 Nothing -> return (Valid Outside what)
192 Just _ -> return Denied -- Nick already taken.
193 Just _ -> return (Requires Outside)
194 _ -> return Malformed
195
196
197roomCommit :: Ord k => Room k -> k -> ChatTransaction -> STM ()
198roomCommit room k t = do
199 let fin = do
200 trace "increment seqno!" $ return ()
201 modifyTVar' (roomFutureSeqNo room) succ
202 writeTChan (roomChan room) t
203 v <- validateTransaction room k t
204 trace ("roomCommit " ++ show v ++ " " ++ show t) $ return ()
205 case v of
206 Valid Outside Inside -> do
207 modifyTVar' (roomMembers room) $ Map.insert (chatSender t) PerMember
208 { memberKey = k
209 }
210 fin
211 Valid Inside Outside -> do
212 modifyTVar' (roomMembers room) $ Map.delete (chatSender t)
213 fin
214 Valid _ _ -> fin
215 bad -> trace ("validateTransaction: " ++ show bad) $ return ()
216
217roomPending :: Ord k => Room k -> STM (Map k ChatTransaction)
218roomPending room = do
219 no <- readTVar $ roomFutureSeqNo room
220 m <- Map.mapMaybe (>>= \t -> do guard (chatSeqNo t == no)
221 return t)
222 <$> do readTVar (roomDesiredTransaction room)
223 >>= mapM readTVar
224 fmap (Map.mapMaybe id)
225 $ sequence $ Map.mapWithKey (\k t -> validateTransaction room k t >>= \case
226 Valid _ _ -> return (Just t)
227 _ -> return Nothing) m