packages feed

snap-core-0.2.5: project_template/default/src/Common.hs

{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE OverloadedStrings #-}

module Common where

import           Data.ByteString.Char8 (ByteString)
import qualified Data.ByteString.Char8 as B
import           Data.Maybe
import qualified Data.Text as T
import           Control.Applicative
import           Control.Concurrent
import           Control.Exception (SomeException)
import           Control.Monad
import           Control.Monad.CatchIO
import           Control.Monad.Trans
import           Prelude hiding (catch)
import           Snap.Http.Server
import           Snap.Types
import           Snap.Util.FileServe
import           Snap.Util.GZip
import           System
import           System.Posix.Env
import           Text.Templating.Heist
import           Text.Templating.Heist.Splices.Static
import qualified Text.XHtmlCombinators.Escape as XH


setLocaleToUTF8 :: IO ()
setLocaleToUTF8 = do
    mapM_ (\k -> setEnv k "en_US.UTF-8" True)
          [ "LANG"
          , "LC_CTYPE"
          , "LC_NUMERIC"
          , "LC_TIME"
          , "LC_COLLATE"
          , "LC_MONETARY"
          , "LC_MESSAGES"
          , "LC_PAPER"
          , "LC_NAME"
          , "LC_ADDRESS"
          , "LC_TELEPHONE"
          , "LC_MEASUREMENT"
          , "LC_IDENTIFICATION"
          , "LC_ALL" ]


------------------------------------------------------------------------------
-- General purpose code.  This code will eventually get moved into Snap once
-- we have a good place to put it.
------------------------------------------------------------------------------

------------------------------------------------------------------------------
-- |
renderTmpl :: MVar (TemplateState Snap)
           -> ByteString
           -> Snap ()
renderTmpl tsMVar n = do
    ts <- liftIO $ readMVar tsMVar
    maybe pass writeBS =<< renderTemplate ts n


templateServe :: TemplateState Snap
              -> MVar (TemplateState Snap)
              -> StaticTagState
              -> Snap ()
templateServe orig tsMVar staticState = do
    p
    modifyResponse $ setContentType "text/html"

  where
    p = ifTop (renderTmpl tsMVar "index") <|>
        path "admin/reload" (reloadTemplates orig tsMVar staticState) <|>
        (renderTmpl tsMVar . B.pack =<< getSafePath)


loadError :: String -> String
loadError str = "Error loading templates\n"++str

reloadTemplates :: TemplateState Snap
                -> MVar (TemplateState Snap)
                -> StaticTagState
                -> Snap ()
reloadTemplates origTs tsMVar staticState = do
    liftIO $ clearStaticTagCache staticState
    ts <- liftIO $ loadTemplates "templates" origTs
    either bad good ts
  where
    bad msg = do writeBS $ B.pack $ loadError msg ++ "Keeping old templates."
    good ts = do liftIO $ modifyMVar_ tsMVar (const $ return ts)
                 writeBS "Templates loaded successfully"


basicHandlers :: TemplateState Snap
              -> MVar (TemplateState Snap)
              -> StaticTagState
              -> Snap ()
              -> Snap ()
basicHandlers origTs tsMVar staticState userHandlers =
    catch500 $ withCompression $
        userHandlers <|>
        templateServe origTs tsMVar staticState


catch500 :: Snap a -> Snap ()
catch500 m = (m >> return ()) `catch` \(e::SomeException) -> do
    let t = T.pack $ show e
    putResponse r
    writeBS "<html><head><title>Internal Server Error</title></head>"
    writeBS "<body><h1>Internal Server Error</h1>"
    writeBS "<p>A web handler threw an exception. Details:</p>"
    writeBS "<pre>\n"
    writeText $ XH.escape t
    writeBS "\n</pre></body></html>"

  where
    r = setContentType "text/html" $
        setResponseStatus 500 "Internal Server Error" emptyResponse

data AppConfig = AppConfig {
    templateDir :: FilePath,
    accessLog :: Maybe FilePath,
    errorLog :: Maybe FilePath
}

quickServer :: AppConfig -> Snap () -> IO ()
quickServer config siteHandlers = do
    args   <- getArgs
    port   <- case args of
                []       -> error "You must specify a port!" >> exitFailure
                (port:_) -> return $ read port

    setLocaleToUTF8

    (origTs,staticState) <- bindStaticTag emptyTemplateState

    ets <- loadTemplates (templateDir config) origTs
    let ts = either error id ets
    either (\s -> putStrLn (loadError s) >> exitFailure) (const $ return ()) ets
    tsMVar <- newMVar $ ts

    (try $ httpServe "*" port "myserver"
             (accessLog config)
             (errorLog config)
             (basicHandlers origTs tsMVar staticState siteHandlers))
             :: IO (Either SomeException ())

    threadDelay 1000000
    putStrLn "exiting"
    return ()