{-# LANGUAGE RecordWildCards #-} {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE TypeOperators #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE ViewPatterns #-} module Thermoprint.Server ( thermoprintServer , Config(..), withPrinters , module Data.Default.Class , module Servant.Server.Internal.Enter , module Thermoprint.Server.Printer ) where import Data.Default.Class import qualified Config.Dyre as Dyre import Data.Map (Map) import qualified Data.Map as Map import Data.Maybe (maybe) import Data.Foldable (mapM_, forM_, foldlM) import Data.Monoid import Control.Monad.Trans.Resource import Control.Monad.Trans.Control import Control.Monad.Logger import Control.Monad.Reader import Control.Monad.IO.Class import Control.Monad.Writer import Control.Concurrent import Data.Text (Text) import qualified Data.Text as T (pack) import qualified Network.Wai.Handler.Warp as Warp import Network.Wai (Application) import Servant.Server (serve) import Servant.Server.Internal.Enter (enter, (:~>)(..)) import Database.Persist.Sql (runMigrationSilent, ConnectionPool, runSqlPool) import Thermoprint.API (thermoprintAPI, PrinterId) import Thermoprint.Server.Database import Thermoprint.Server.Printer import qualified Thermoprint.Server.API as API (thermoprintServer) import Thermoprint.Server.API hiding (thermoprintServer) -- | Compile-time configuration for 'thermoprintServer' data Config = Config { dyreError :: Maybe String -- ^ Set by 'Dyre' -- sent to log as an error , warpSettings :: Warp.Settings -- ^ Configure 'Warp's behaviour , printers :: Map PrinterId Printer } instance Default Config where def = Config { dyreError = Nothing , warpSettings = Warp.defaultSettings , printers = Map.empty } thermoprintServer :: ( MonadLoggerIO m , MonadReader ConnectionPool m , MonadResource m , MonadBaseControl IO m ) => (m :~> IO) -- ^ 'dyre' controls the base of the monad-transformer-stack ('IO') but we let the user specify the rest of it. Therefore we require a specification of how to enter the stack. -> Config -> IO () -- ^ Run the server thermoprintServer io = Dyre.wrapMain $ Dyre.defaultParams { Dyre.projectName = "thermoprint-server" , Dyre.realMain = realMain , Dyre.showError = (\cfg msg -> cfg { dyreError = Just msg }) } where realMain Config{..} = unNat io $ do maybe (return ()) ($(logErrorS) "Dyre" . T.pack) dyreError mapM_ ($(logWarnS) "DB") =<< runSqlPool (runMigrationSilent migrateAll) =<< ask forM_ printers $ liftBaseDiscard forkIO . runPrinter liftIO . Warp.runSettings warpSettings . serve thermoprintAPI . flip enter API.thermoprintServer =<< handlerNat printers withPrinters :: MonadResource m => Config -> [PrinterSpec] -> m Config -- ^ Helper for comfortably specifying a set of 'Printer's withPrinters cfg = fmap (\ps -> cfg { printers = printers cfg <> ps }) . foldlM (\ps p -> Map.insert (nextKey ps) <$> printer p <*> pure ps) Map.empty where nextKey :: Map PrinterId a -> PrinterId nextKey (Map.keys -> keys) | null keys = 0 | otherwise = succ $ maximum keys