{-|
Module :
Description :
Copyright : (c) Alexander Vieth, 2015
Licence : BSD3
Maintainer : aovieth@gmail.com
Stability : experimental
Portability : non-portable (GHC only)
-}
{-# LANGUAGE AutoDeriveTypeable #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE RankNTypes #-}
module Types.Server (
ServerState(..)
, Server
, withAdminCredentials
, withUserCredentialsForGame
, modifyGameState
, serverState
, runDiplomacyServer
) where
import Control.DeepSeq
import Control.Monad.Trans.Except
import Control.Monad.Trans.State as S
import Control.Monad.State.Class as SC
import Control.Concurrent.STM
import Control.Concurrent.STM.TVar
import Data.Stream as Stream
import Data.ByteString as BS
import Data.ByteString.Lazy as BL
import qualified Data.Map as M
import Data.Functor.Identity
import Data.Hourglass
import System.Hourglass
import System.Random
import Rest.Error
import Types.GameId
import Types.Credentials
import Types.GameState
data ServerState = ServerState {
adminCredentials :: Credentials
, games :: M.Map GameId (Password, GameState)
, currentTime :: Elapsed
, randomDoubles :: Stream.Stream Double
, clientHtml :: BL.ByteString
}
type Server = StateT ServerState Identity
withAdminCredentials
:: MonadState ServerState m
=> Credentials
-> ExceptT (Reason e) m t
-> ExceptT (Reason e) m t
withAdminCredentials creds term = do
state <- SC.get
if adminCredentials state == creds
then term
else throwE AuthenticationFailed
withUserCredentialsForGame
:: MonadState ServerState m
=> Credentials
-> GameId
-> (GameStateView -> ExceptT (Reason e) m t)
-> ExceptT (Reason e) m t
withUserCredentialsForGame creds gameId term = do
state <- SC.get
let game = M.lookup gameId (games state)
case game of
Nothing -> throwE NotFound
Just (_, gameState) -> case gameStateMatchCredentials creds gameState of
Nothing -> throwE AuthenticationFailed
Just gameStateView -> term gameStateView
modifyGameState
:: MonadState ServerState m
=> GameId
-> (GameState -> ExceptT (Reason e) m GameState)
-> ExceptT (Reason e) m ()
modifyGameState gameId f = do
state <- SC.get
let game = M.lookup gameId (games state)
case game of
Nothing -> throwE NotFound
Just (password, gameState) -> do
modified <- f gameState
SC.put (state { games = M.alter (const (Just (password, force modified))) gameId (games state) })
return ()
serverState :: Username -> Password -> IO ServerState
serverState adminUsername adminPassword = do
let games = M.empty
g <- getStdGen
let ds = unfold random g :: Stream.Stream Double
t <- timeCurrent
bs <- BL.readFile "client.html"
return $ ServerState (Credentials adminUsername adminPassword) games t ds bs
runDiplomacyServer :: TVar ServerState -> (forall a . Server a -> IO a)
runDiplomacyServer tvar m = do
-- Always refurnish the state with random doubles.
g <- getStdGen
let ds = unfold random g :: Stream.Stream Double
t <- timeCurrent
bs <- BL.readFile "client.html"
atomically $ do
state <- readTVar tvar
-- It's important to update the time and random doubles before we
-- run the term @m@.
let state' = state { currentTime = t, randomDoubles = ds, clientHtml = bs }
let (x, nextState) = runIdentity (runStateT m state')
writeTVar tvar nextState
return x