-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathApps.hs
85 lines (68 loc) · 2.63 KB
/
Apps.hs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
{-# LANGUAGE OverloadedStrings #-}
module Apps where
import Control.Exception (fromException)
import Control.Monad (forever, when, forM_)
import Control.Monad.IO.Class (liftIO)
import Control.Applicative
import Control.Concurrent.MVar
import Data.Map (Map)
import qualified Data.Map as M
import Data.Monoid (mappend, mconcat)
import Data.Attoparsec
import Data.Attoparsec.Char8 (skipSpace)
import Data.ByteString (ByteString)
import qualified Data.ByteString.Char8 as S
import Network.WebSockets.Lite
echo :: WSLite ()
echo = forever $ recvBS >>= send
close' :: WSLite ()
close' = return ()
data ChatMessage = ChatJoin ByteString
| ChatData ByteString
| ChatError ByteString
chatParser :: Parser ChatMessage
chatParser = ChatJoin <$> (string "join" *> skipSpace *> takeByteString)
<|> ChatData <$> takeByteString
instance UpProtocol ChatMessage where
decode = parseOnly chatParser
instance DownProtocol ChatMessage where
encode (ChatData s) = s
encode (ChatError e) = "error: " `mappend` e
encode (ChatJoin name) = name `mappend` " joined"
type ChatState = MVar (Map ByteString Sink)
newChatState :: IO ChatState
newChatState = newMVar M.empty
chat :: ChatState -> WSLite ()
chat clients = do
name <- recvJoin
sink <- getSink
exists <- liftIO $ modifyMVar clients $ \cs ->
case M.lookup name cs of
Nothing -> return (M.insert name sink cs, False)
Just _ -> return (cs, True)
when exists $ fail' "User already exists."
flip catchError (handleDisconnect name) $ do
welcome name
broadcast $ ChatJoin name
forever $ do
msg <- recv
case msg of
ChatData s -> broadcast $ ChatData $ mconcat [name, ": ", s]
_ -> fail' "invalid message."
where
fail' s = send (ChatError s) >> close
recvJoin = do msg <- recv
case msg of
ChatJoin name -> return name
_ -> fail' "invalid message."
broadcast msg = do
sinks <- M.elems <$> liftIO (readMVar clients)
forM_ sinks (`sendSink` msg)
welcome name = do
users <- filter (/=name) . M.keys <$> liftIO (readMVar clients)
send $ ChatData $ "Welcome! Users: " `mappend` S.intercalate ", " users
handleDisconnect name e = case fromException e of
Just ConnectionClosed -> do
liftIO $ modifyMVar_ clients $ return . M.delete name
broadcast $ ChatData $ mconcat [name, " disconnected."]
_ -> return ()