module Literate.Web.Live where import Control.Applicative (Applicative (..)) import Control.Concurrent (threadDelay) import Control.Concurrent.STM (STM) import Control.Concurrent.STM qualified as STM import Control.Monad (void) import Data.ByteString.Builder qualified as BSB import Data.Function (const, ($), (&)) import Data.Functor ((<&>)) import Data.Map.Strict qualified as Map import Data.Maybe (Maybe (..), maybe) import Data.Text (Text) import Network.Wai qualified as Wai import Network.Wai.Handler.Warp qualified as Warp import Network.Wai.Handler.WebSockets qualified as WaiWs import Network.WebSockets qualified as WS import System.IO (IO) import System.IO qualified as Sys import Prelude (Bounded (..)) import Literate.Web qualified as Web import Literate.Web.Live.HTTP import Literate.Web.Live.WebSocket runLive :: Maybe () -> Warp.HostPreference -> Maybe Warp.Port -> STM model -> (model -> IO (Map.Map Web.OutputPath (IO BSB.Builder))) -> IO () runLive mWsOpts host portMaybe modelSTM siteMapBSB = do iniModel <- STM.atomically modelSTM let siteMap model = siteMapBSB model <&> (<&> (<&> BSB.toLazyByteString)) settings = Warp.defaultSettings & Warp.setHost host app = case mWsOpts of Nothing -> httpApp modelSTM siteMap Nothing Just () -> do WaiWs.websocketsOr WS.defaultConnectionOptions (webSocketApp iniModel modelSTM siteMap) (httpApp modelSTM siteMap (Just webSocketClientJS)) banner port = do Sys.hPrint Sys.stderr (host, "port" :: Text, port, maybe "no ws" (const "ws") mWsOpts :: Text) warpRunSettings settings portMaybe banner app -- Like Warp.runSettings but takes *optional* port. When no port is set, -- a free (random) port is used. warpRunSettings :: Warp.Settings -> Maybe Warp.Port -> (Warp.Port -> IO a) -> Wai.Application -> IO () warpRunSettings settings portMaybe banner app = do case portMaybe of Nothing -> Warp.withApplicationSettings settings (pure app) $ \port -> do void $ banner port threadDelay maxBound Just port -> do void $ banner port Warp.runSettings (Warp.setPort port settings) app