@@ -1,13 +1,17 @@
11{-# LANGUAGE OverloadedStrings #-}
22--module ProjectM36.Server.WebSocket where
3−
3+-- while the tutd client performs TutorialD parsing on the client, the websocket server will pass tutd to be parsed and executed on the server- otherwise I have to pull in ghcjs as a dependency to allow client-side parsing- that's not appealing because then the frontend is not language-agnostic, but this could change in the future, perhaps by sending different messages over the websocket
4+-- ideally, the wire protocol should not be exposed to a straight string-based API ala SQL, so we could make perhaps a javascript DSL which compiles to the necessary JSON- anaylyze tradeoffs
45import Control.Monad (forever)
56import qualified Data.Text as T
67import qualified Network.WebSockets as WS
78import ProjectM36.Server.RemoteCallTypes.Json ()
9+import ProjectM36.Client.Json ()
810import ProjectM36.Base
9−import qualified Data.Set as S
1011import Data.Aeson
12+import TutorialD.Interpreter
13+import TutorialD.Interpreter.Base
14+import ProjectM36.Client
1115
1216main :: IO ()
1317main = do
@@ -17,10 +21,42 @@ main = do
1721application :: WS.ServerApp
1822application pending = do
1923 conn <- WS.acceptRequest pending
24+ (sessionId, dbconn) <- createConnection conn
2025 forever $ do
2126 msg <- (WS.receiveData conn) :: IO T.Text
27+ let tutdprefix = "executetutd:"
2228 case msg of
23− "db" -> WS.sendTextData conn (encode (Project (AttributeNames (S.fromList ["spam1", "spam2"])) (RelationVariable "elgringo")))
24− str -> WS.sendTextData conn str
29+ _ | tutdprefix `T.isPrefixOf` msg -> do
30+ let tutdString = T.drop (T.length tutdprefix) msg
31+ case parseTutorialD (T.unpack tutdString) of
32+ Left err -> handleOpResult conn (DisplayErrorResult ("parse error: " `T.append` T.pack (show err)))
33+ Right parsed -> do
34+ result <- evalTutorialD sessionId dbconn parsed
35+ handleOpResult conn result
36+ _ -> WS.sendTextData conn ("message not expected" :: T.Text)
2537 return ()
26−
38+
39+notificationCallback :: WS.Connection -> NotificationCallback
40+notificationCallback conn notifName evaldNotif = WS.sendTextData conn (encode (object ["notificationname" .= notifName,
41+ "evaldnotification" .= evaldNotif
42+ ]))
43+
44+--this creates a new database for each connection- perhaps not what we want (?)
45+createConnection :: WS.Connection -> IO (SessionId, Connection)
46+createConnection wsconn = do
47+ eConn <- connectProjectM36 (InProcessConnectionInfo NoPersistence (notificationCallback wsconn))
48+ case eConn of
49+ Left err -> error $ "failed to create database" ++ show err
50+ Right conn -> do
51+ eSessionId <- createSessionAtHead "master" conn
52+ case eSessionId of
53+ Left err -> error $ "failed to create connection on master branch" ++ show err
54+ Right sessionId -> pure (sessionId, conn)
55+
56+handleOpResult :: WS.Connection -> TutorialDOperatorResult -> IO ()
57+handleOpResult conn QuitResult = WS.sendClose conn ("close" :: T.Text)
58+handleOpResult conn (DisplayResult out) = WS.sendTextData conn (encode (object ["display" .= out]))
59+handleOpResult _ (DisplayIOResult ioout) = ioout
60+handleOpResult conn (DisplayErrorResult err) = WS.sendTextData conn (encode (object ["displayerror" .= err]))
61+handleOpResult conn QuietSuccessResult = WS.sendTextData conn (encode (object ["acknowledged" .= True]))
62+handleOpResult conn (DisplayRelationResult rel) = WS.sendTextData conn (encode (object ["displayrelation" .= rel]))