diff options
author | Joe Crayne <joe@jerkface.net> | 2018-11-12 19:56:37 -0500 |
---|---|---|
committer | Joe Crayne <joe@jerkface.net> | 2018-11-14 01:29:40 -0500 |
commit | e2d9490ed416de581ab98ba40fcba0ea13c348e9 (patch) | |
tree | 74db6c2003684b9774f386a9a37a02fd226adbee /Presence | |
parent | a6e8c91b4f3b08d7be388ea0d588d95a1d8a5b06 (diff) |
wip: Transactional chat.
Diffstat (limited to 'Presence')
-rw-r--r-- | Presence/Chat.hs | 136 |
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 @@ | |||
1 | module Chat where | ||
2 | |||
3 | import Control.Concurrent.STM | ||
4 | import Control.Monad | ||
5 | import qualified Data.Map as Map | ||
6 | ;import Data.Map (Map) | ||
7 | import Data.Text | ||
8 | import 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 | |||
15 | data Room k = Room | ||
16 | { roomDesiredTransaction :: TVar (Map k (TVar (Maybe ChatTransaction))) | ||
17 | , roomChan :: TChan ChatTransaction | ||
18 | , roomFutureSeqNo :: TVar Word64 | ||
19 | } | ||
20 | |||
21 | data ChatEvent = Join | Part | Action Text | Talk Text | NickChange Text | ||
22 | deriving (Eq,Ord,Show) | ||
23 | |||
24 | data Membership = Outside | Inside | ||
25 | deriving (Eq,Ord,Read,Show) | ||
26 | |||
27 | data MembershipEffect = MembershipEffect { fromMembership :: Membership | ||
28 | , toMembership :: Membership | ||
29 | } | ||
30 | | NoMembershipEffect | ||
31 | | InvalidMembershipEffect | ||
32 | deriving (Eq,Ord,Read,Show) | ||
33 | |||
34 | instance 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 | |||
43 | chatEffect :: ChatEvent -> MembershipEffect | ||
44 | chatEffect Join = MembershipEffect Outside Inside | ||
45 | chatEffect Part = MembershipEffect Inside Outside | ||
46 | chatEffect _ = MembershipEffect Inside Inside | ||
47 | |||
48 | membershipEffect :: [ChatEvent] -> MembershipEffect | ||
49 | membershipEffect xs = foldMap chatEffect xs | ||
50 | |||
51 | |||
52 | data ChatTransaction = ChatTransaction | ||
53 | { chatSeqNo :: Word64 | ||
54 | , chatSenderJID :: Maybe Text | ||
55 | , chatSender :: Text | ||
56 | , chatMessage :: [ChatEvent] | ||
57 | } | ||
58 | deriving (Eq,Ord,Show) | ||
59 | |||
60 | newtype RoomHandle = RH (TVar (Maybe ChatTransaction)) | ||
61 | |||
62 | data JoinedRoom k = JoinedRoom | ||
63 | { joinedRoom :: Room k | ||
64 | , roomHandle :: RoomHandle | ||
65 | } | ||
66 | |||
67 | newRoom :: STM (Room k) | ||
68 | newRoom = 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 | |||
81 | joinRoom :: Ord k => k | ||
82 | -> Room k | ||
83 | -> Maybe Text | ||
84 | -> Text | ||
85 | -> STM (JoinedRoom k) | ||
86 | joinRoom 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 | |||
93 | partRoom :: JoinedRoom k -> Maybe Text -> Text -> STM () | ||
94 | partRoom (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 | |||
99 | sendChat :: JoinedRoom k -> Maybe Text -> Text -> [ChatEvent] -> STM Bool | ||
100 | sendChat (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. | ||
112 | readRoom :: Ord k => k -> JoinedRoom k -> STM (Bool, ChatTransaction) | ||
113 | readRoom 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 | |||
133 | roomCommit :: Room k -> ChatTransaction -> STM () | ||
134 | roomCommit room t = do | ||
135 | modifyTVar' (roomFutureSeqNo room) succ | ||
136 | writeTChan (roomChan room) t | ||