summaryrefslogtreecommitdiff
path: root/Presence
diff options
context:
space:
mode:
authorJoe Crayne <joe@jerkface.net>2018-11-12 19:56:37 -0500
committerJoe Crayne <joe@jerkface.net>2018-11-14 01:29:40 -0500
commite2d9490ed416de581ab98ba40fcba0ea13c348e9 (patch)
tree74db6c2003684b9774f386a9a37a02fd226adbee /Presence
parenta6e8c91b4f3b08d7be388ea0d588d95a1d8a5b06 (diff)
wip: Transactional chat.
Diffstat (limited to 'Presence')
-rw-r--r--Presence/Chat.hs136
1 files changed, 136 insertions, 0 deletions
diff --git a/Presence/Chat.hs b/Presence/Chat.hs
new file mode 100644
index 00000000..47512bf1
--- /dev/null
+++ b/Presence/Chat.hs
@@ -0,0 +1,136 @@
1module Chat where
2
3import Control.Concurrent.STM
4import Control.Monad
5import qualified Data.Map as Map
6 ;import Data.Map (Map)
7import Data.Text
8import Data.Word
9
10-- To join a chat room, add a 'ClientRoomLink' to 'roomDesiredLink' with
11-- 'desireStreamEvent' set to 'Nothing'.
12--
13-- To leave a chat room, remove the 'ClientRoomLink' from the map.
14
15data Room k = Room
16 { roomDesiredTransaction :: TVar (Map k (TVar (Maybe ChatTransaction)))
17 , roomChan :: TChan ChatTransaction
18 , roomFutureSeqNo :: TVar Word64
19 }
20
21data ChatEvent = Join | Part | Action Text | Talk Text | NickChange Text
22 deriving (Eq,Ord,Show)
23
24data Membership = Outside | Inside
25 deriving (Eq,Ord,Read,Show)
26
27data MembershipEffect = MembershipEffect { fromMembership :: Membership
28 , toMembership :: Membership
29 }
30 | NoMembershipEffect
31 | InvalidMembershipEffect
32 deriving (Eq,Ord,Read,Show)
33
34instance Monoid MembershipEffect where
35 mempty = NoMembershipEffect
36 MembershipEffect a x `mappend` MembershipEffect y b
37 | x == y = MembershipEffect a b
38 | otherwise = InvalidMembershipEffect
39 NoMembershipEffect `mappend` b = b
40 a `mappend` NoMembershipEffect = a
41 _ `mappend` _ = InvalidMembershipEffect
42
43chatEffect :: ChatEvent -> MembershipEffect
44chatEffect Join = MembershipEffect Outside Inside
45chatEffect Part = MembershipEffect Inside Outside
46chatEffect _ = MembershipEffect Inside Inside
47
48membershipEffect :: [ChatEvent] -> MembershipEffect
49membershipEffect xs = foldMap chatEffect xs
50
51
52data ChatTransaction = ChatTransaction
53 { chatSeqNo :: Word64
54 , chatSenderJID :: Maybe Text
55 , chatSender :: Text
56 , chatMessage :: [ChatEvent]
57 }
58 deriving (Eq,Ord,Show)
59
60newtype RoomHandle = RH (TVar (Maybe ChatTransaction))
61
62data JoinedRoom k = JoinedRoom
63 { joinedRoom :: Room k
64 , roomHandle :: RoomHandle
65 }
66
67newRoom :: STM (Room k)
68newRoom = do
69 m <- newTVar Map.empty
70 c <- newTChan -- newBroadcastTChan
71 n <- newTVar 0
72 return Room
73 { roomDesiredTransaction = m
74 , roomChan = c
75 , roomFutureSeqNo = n
76 }
77
78
79--- Client interface
80
81joinRoom :: Ord k => k
82 -> Room k
83 -> Maybe Text
84 -> Text
85 -> STM (JoinedRoom k)
86joinRoom k room jid nick = do
87 no <- readTVar $ roomFutureSeqNo room
88 v <- newTVar (Just $ ChatTransaction no jid nick [Join])
89 modifyTVar' (roomDesiredTransaction room)
90 $ Map.insert k v
91 return $ JoinedRoom room (RH v)
92
93partRoom :: JoinedRoom k -> Maybe Text -> Text -> STM ()
94partRoom (JoinedRoom room (RH v)) jid nick = do
95 writeTVar v Nothing -- Cancel pending chat.
96 sendChat (JoinedRoom room (RH v)) jid nick [Part]
97 return ()
98
99sendChat :: JoinedRoom k -> Maybe Text -> Text -> [ChatEvent] -> STM Bool
100sendChat (JoinedRoom room (RH v)) jid nick chat = do
101 mpending <- readTVar v
102 case mpending of
103 Nothing -> do
104 no <- readTVar $ roomFutureSeqNo room
105 writeTVar v (Just $ ChatTransaction no jid nick chat)
106 return True
107 Just pending -> do
108 return False
109
110-- | Blocks until a transaction occurs. Optionally, a failed transaction will
111-- be automatically renewed.
112readRoom :: Ord k => k -> JoinedRoom k -> STM (Bool, ChatTransaction)
113readRoom k (JoinedRoom room (RH v)) = do
114 mpending <- readTVar v
115 final <- readTChan $ roomChan room
116 case mpending of
117 Just pending -> do
118 if pending == final
119 then do
120 writeTVar v Nothing
121 when (Part `elem` chatMessage final) $ do
122 modifyTVar' (roomDesiredTransaction room)
123 $ Map.delete k
124 return (True,final)
125 else do
126 no <- readTVar $ roomFutureSeqNo room
127 writeTVar v $ Just pending { chatSeqNo = no }
128 return (False,final)
129 Nothing -> return (False,final)
130
131-- Room implementation interface
132
133roomCommit :: Room k -> ChatTransaction -> STM ()
134roomCommit room t = do
135 modifyTVar' (roomFutureSeqNo room) succ
136 writeTChan (roomChan room) t