module Dingo.Internal.Server.State
( ClientSession
, ServerSessionState
, ServerState
, SessionId
, cssClientSessionState
, emptyServerState
, getSession
, getSessionId
, handleCallback
, readCommandWithTimeout
, ssApplicationTitle
, ssBootResourceBundles
) where
import Control.Concurrent.Chan.Strict (Chan, newChan, writeChan)
import Control.DeepSeq.ByteString ()
import Control.Monad (replicateM)
import Data.Aeson (Value(..))
import Data.ByteString (ByteString)
import qualified Data.ByteString as BS
import qualified Data.ByteString.Base64 as B64
import qualified Data.Foldable as F
import Data.HashMap.Strict (HashMap)
import Data.IORef (IORef, atomicModifyIORef, newIORef, readIORef)
import qualified Data.Label as L
import Data.Map (Map)
import qualified Data.Map as M
import Data.Text (Text)
import Dingo.Internal.Application (mkApplication, Application)
import Dingo.Internal.Base (CallbackId, WidgetId)
import Dingo.Internal.CallbackTypes (WrapCallback(..), CallbackM, mkSession, getCommands)
import Dingo.Internal.JavaScript (renderCommandsToJs)
import Dingo.Internal.Queue (readChanTimeout)
import Dingo.Internal.ResourceBundle.Boot (bootResourceBundles)
import Dingo.Internal.Session (SessionState, runSessionT, runCallback)
import Dingo.ResourceBundle (ResourceBundle)
import System.Random (Random(randomRIO))
type SessionId = ByteString
type ServerSessionState = SessionState WrapCallback
data ClientSessionState =
ClientSessionState { _cssClientSessionState :: ServerSessionState
, _cssCommandsForClient :: Chan ByteString
}
$(L.mkLabels [''ClientSessionState])
type ClientSession = (SessionId, IORef ClientSessionState)
data ServerState =
ServerState { _ssClientSessions :: IORef (Map SessionId (IORef ClientSessionState))
, _ssApplicationCallback :: CallbackM ()
, _ssApplicationTitle :: Text
, _ssBootResourceBundles :: [ResourceBundle]
}
$(L.mkLabels [''ServerState])
emptyServerState :: (Application -> CallbackM ()) -> Text -> [ResourceBundle] -> IO ServerState
emptyServerState applicationCallback applicationTitle resourceBundles = do
clientSessions <- newIORef M.empty
return $ ServerState clientSessions (applicationCallback mkApplication) applicationTitle (bootResourceBundles ++ resourceBundles)
generateSessionId :: IO ByteString
generateSessionId = fmap toBase64 $ replicateM 16 $ randomRIO (0,255)
where
toBase64 = B64.encode . BS.pack . map fromInteger
getSession :: ServerState -> Maybe SessionId -> IO ClientSession
getSession serverState mSessionId = do
serverStateM <- readIORef $ L.get ssClientSessions serverState
case mSessionId of
Nothing -> newSession
Just sessionId ->
case M.lookup sessionId serverStateM of
Nothing -> newSession
Just sessionRef -> return (sessionId, sessionRef)
where
newSession :: IO ClientSession
newSession = do
sessionId <- generateSessionId
let app = L.get ssApplicationCallback serverState
let bundles = L.get ssBootResourceBundles serverState
let sessionState = mkSession app bundles
sessionChannel <- newChan
sessionRef <- newIORef $ ClientSessionState sessionState sessionChannel
atomicModifyIORef (L.get ssClientSessions serverState)
(\m -> (M.insert sessionId sessionRef m, ()))
return (sessionId, sessionRef)
getSessionId :: ClientSession -> SessionId
getSessionId = fst
handleCallback :: ClientSession -> CallbackId -> HashMap WidgetId Value -> IO ()
handleCallback clientSession callbackId widgetStates = do
let clientSessionRef = snd clientSession
session <- readIORef clientSessionRef
let sessionState = L.get cssClientSessionState session
(callbackState,sessionState') <-
runSessionT sessionState $ runCallback callbackId widgetStates
let commands = getCommands callbackState
let commandsChan = L.get cssCommandsForClient session
writeChan commandsChan $ renderCommandsToJs $ F.toList commands
atomicModifyIORef clientSessionRef (\_ -> (ClientSessionState sessionState' commandsChan, ()))
readCommandWithTimeout :: ServerState -> ClientSession -> Int -> IO (Maybe ByteString)
readCommandWithTimeout serverState clientSession timeout = do
serverStateM <- readIORef $ L.get ssClientSessions serverState
case M.lookup clientSessionId serverStateM of
Nothing -> return Nothing
Just sessionRef -> do
commandsChan <- fmap (L.get cssCommandsForClient) $ readIORef sessionRef
readChanTimeout commandsChan timeout
where
clientSessionId = fst clientSession