packages feed

happstack-6.0.0: templates/project/src/Main.hs

module Main where

import Data.Version (showVersion)

import Control.Concurrent (MVar, forkIO, killThread)
import Control.Exception (bracket)

import System.Environment (getArgs)
import System.Log.Logger (Priority(..), logM)
import System.Exit (exitWith, ExitCode(..), exitFailure)
import System.Console.GetOpt 

import Happstack.Util.Cron (cron)
import Happstack.State (waitForTermination)
import Happstack.Server
  ( Conf(port)
  , simpleHTTP
  , nullConf
  , validator
  , wdgHTMLValidator
  )
import Happstack.State
  ( Component
  , Proxy(..)
  , Methods
  , TxControl
  , Saver(Queue, FileSaver)
  , runTxSystem
  , shutdownSystem
  , createCheckpoint
  )

import Logger (withLogger)
import Routes (routes)
import State  (AppState)

-- NOTE: You need to change this file name such that it reflects the name of
-- your project in the .cabal file, because it is auto-generated by cabal.
import Paths_guestbook (version)


------------------------------------------------------------------------------
-- Main Function
------------------------------------------------------------------------------

main = withLogger $ do

  -- convert command line arguments into list of flags
  flags <- parseConfig =<< getArgs

  -- display information like help or version, if required
  displayInfo flags

  -- run server
  runServer flags


-- | Display information required by flags. Currently, this is either the help
-- message or the version information.
displayInfo :: [Flag] -> IO ()
displayInfo flags
  | any isHelp    flags = putStr helpMessage >> exitWith ExitSuccess
  | any isVersion flags = putStr versionInfo >> exitWith ExitSuccess
  | otherwise           = return ()


------------------------------------------------------------------------------
-- Command Line Interface Content
------------------------------------------------------------------------------

-- | Program name used to identify your application. 
-- It influences the version and help message, as well as the directory name
-- for the state of your application.
progName = "guestbook-server"

fullName = "Happstack Guestbook Example"

copyrightInfo = "Copyright (C) 2009 <your-name-here>"
licenceInfo = "See the accompanying licence for copying conditions."

-- | Version information extracted from Paths_guestbook
versionInfo = unlines
  [ fullName ++ " (" ++ progName ++ "), version " ++ showVersion version
  , copyrightInfo
  , licenceInfo
  ]

-- | A simple usage message listing all flags possible.
helpMessage = usageInfo header opts
  where 
  header = "Usage: "++progName++" [OPTION...]"


------------------------------------------------------------------------------
-- Server
------------------------------------------------------------------------------

-- | Configuration information
data AppConf
    = AppConf { httpConf :: Conf
              , store :: FilePath
              , static :: FilePath 
              }

-- | Default configuration
-- FIXME: Should incorporate directories queried via Paths_guestbook module
defaultConf :: String -> AppConf
defaultConf progName
    = AppConf { httpConf = nullConf
              , store    = "_local/" ++ progName ++ "_state"
              , static   = "public"
              }

-- | Run the server with the given configuration.
runServer :: [Flag] -> IO ()
runServer flags = do
  -- combine all server config flags
  let appConf = foldr ($) (defaultConf progName) [f | ServerConfig f <- flags] 

  -- start the state system
  withSystemState' (store appConf) stateProxy $ \control -> do
    
     -- start the http server
     withThread (simpleHTTP (httpConf appConf) routes) $ do

       -- checkpoint the state once a day
       withThread (cron (60*60*24) (createCheckpoint control)) $ do
  
          -- wait for termination signal
          logM "Happstack.Server" NOTICE "System running, press 'e <ENTER>' or Ctrl-C to stop server" 
          waitForTermination

  where
  startSystemState' :: (Component st, Methods st) => String -> Proxy st -> IO (MVar TxControl)
  startSystemState' = runTxSystem . Queue . FileSaver

  withSystemState' :: (Component st, Methods st) => String -> Proxy st -> (MVar TxControl -> IO a) -> IO a
  withSystemState' name proxy action = 
    bracket (startSystemState' name proxy) createCheckpointAndShutdown action

  withThread init action = bracket (forkIO $ init) (killThread) (\_ -> action)

  createCheckpointAndShutdown control = do
    logM "Happstack.Server" NOTICE "Creating system checkpoint" 
    createCheckpoint control
    logM "Happstack.Server" NOTICE "System shutdown" 
    shutdownSystem control

  -- simon.meier@inf.ethz.ch: I currently don't know what this is used for!
  -- FIXME: Add sensible comment.
  stateProxy :: Proxy AppState
  stateProxy = Proxy


------------------------------------------------------------------------------
-- Command Line Parsing
------------------------------------------------------------------------------

-- | Flags
data Flag = 
    ServerConfig (AppConf -> AppConf)
  | Help
  | Version

-- Flag selectors
isHelp    flag = case flag of Help    -> True; _ -> False
isVersion flag = case flag of Version -> True; _ -> False

-- | Command line options.
opts :: [OptDescr Flag]
opts = [ Option [] ["http-port"]   (ReqArg setPort "port")                        "Port to bind http server"
       , Option [] ["no-validate"] (NoArg $ setValidator Nothing)                 "Turn off HTML validation"
       , Option [] ["validate"]    (NoArg $ setValidator (Just wdgHTMLValidator)) "Turn on HTML validation"
       , Option [] ["store"]       (ReqArg setMacidDir "PATH")                    "The directory used for database storage."
       , Option [] ["static"]      (ReqArg setStaticDir "PATH")                   "The directory searched for static files" 
       , Option [] ["version"]     (NoArg Version)                                "Display version information" 
       , Option [] ["help"]        (NoArg Help)                                   "Display this help message" 
       ]
     where
     setPort p      = ServerConfig $ \c -> c { httpConf = (httpConf c) {port      = read p} }
     setValidator v = ServerConfig $ \c -> c { httpConf = (httpConf c) {validator = v     } }
     setMacidDir p  = ServerConfig $ \c -> c { store = p }
     setStaticDir p = ServerConfig $ \c -> c { static = p }

-- | Parse the command line arguments into a list of flags. Exits with usage
-- message, in case of failure.
parseConfig :: [String] -> IO [Flag]
parseConfig args = case getOpt Permute opts args of
  (flags,_,[]) -> return flags
  (_,_,errs)   -> do
    logM progName ERROR ("Failure while parsing command line:\n"++unlines errs)
    putStr helpMessage
    exitFailure