1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209
|
{-# LANGUAGE DeriveDataTypeable, FlexibleContexts, GeneralizedNewtypeDeriving
, MultiParamTypeClasses, OverloadedStrings, ScopedTypeVariables, TemplateHaskell
, TypeFamilies, FlexibleInstances #-}
module Main where
import Control.Applicative (Applicative, Alternative, (<$>))
import Control.Exception.Lifted (bracket)
import Control.Monad.Trans.Control (MonadBaseControl)
import Control.Monad (MonadPlus, mplus)
import Control.Monad.Reader (MonadReader, ReaderT(..), ask)
import Control.Monad.Trans (MonadIO(..))
import Data.Acid ( AcidState(..), EventState(..), EventResult(..)
, Query(..), QueryEvent(..), Update(..), UpdateEvent(..)
, IsAcidic(..), makeAcidic, openLocalState
)
import Data.Acid.Local ( createCheckpointAndClose
, openLocalStateFrom
)
import Data.Acid.Advanced (query', update')
import Data.Maybe (fromMaybe)
import Data.SafeCopy (SafeCopy, base, deriveSafeCopy)
import Data.Data (Data, Typeable)
import Data.Lens ((%=), (!=))
import Data.Lens.Template (makeLens)
import Data.Text.Lazy (Text)
import Happstack.Server ( Happstack, HasRqData, Method(GET, POST), Request(rqMethod)
, Response
, ServerPartT(..), WebMonad, FilterMonad, ServerMonad
, askRq, decodeBody, dir, defaultBodyPolicy, lookText
, mapServerPartT, nullConf, nullDir, ok, simpleHTTP
, toResponse
)
import Prelude hiding (head, id)
import System.FilePath ((</>))
import Text.Blaze ((!))
import Text.Blaze.Html4.Strict (body, head, html, input, form, label, p, title, toHtml)
import Text.Blaze.Html4.Strict.Attributes (action, enctype, for, id, method, name, type_, value)
class HasAcidState m st where
getAcidState :: m (AcidState st)
query :: forall event m.
( Functor m
, MonadIO m
, QueryEvent event
, HasAcidState m (EventState event)
) =>
event
-> m (EventResult event)
query event =
do as <- getAcidState
query' (as :: AcidState (EventState event)) event
update :: forall event m.
( Functor m
, MonadIO m
, UpdateEvent event
, HasAcidState m (EventState event)
) =>
event
-> m (EventResult event)
update event =
do as <- getAcidState
update' (as :: AcidState (EventState event)) event
-- | bracket the opening and close of the `AcidState` handle.
-- automatically creates a checkpoint on close
withLocalState :: (MonadBaseControl IO m, MonadIO m, IsAcidic st, Typeable st) =>
Maybe FilePath -- ^ path to state directory
-> st -- ^ initial state value
-> (AcidState st -> m a) -- ^ function which uses the `AcidState` handle
-> m a
withLocalState mPath initialState =
bracket (liftIO $ (maybe openLocalState openLocalStateFrom mPath) initialState)
(liftIO . createCheckpointAndClose)
-- State that stores a hit count
data CountState = CountState { _count :: Integer }
deriving (Eq, Ord, Data, Typeable, Show)
$(deriveSafeCopy 0 'base ''CountState)
$(makeLens ''CountState)
initialCountState :: CountState
initialCountState = CountState { _count = 0 }
incCount :: Update CountState Integer
incCount = count %= succ
$(makeAcidic ''CountState ['incCount])
-- State that stores a greeting
data GreetingState = GreetingState { _greeting :: Text }
deriving (Eq, Ord, Data, Typeable, Show)
$(deriveSafeCopy 0 'base ''GreetingState)
$(makeLens ''GreetingState)
initialGreetingState :: GreetingState
initialGreetingState = GreetingState { _greeting = "Hello" }
getGreeting :: Query GreetingState Text
getGreeting = _greeting <$> ask
setGreeting :: Text -> Update GreetingState Text
setGreeting txt = greeting != txt
$(makeAcidic ''GreetingState ['getGreeting, 'setGreeting])
data Acid = Acid { acidCountState :: AcidState CountState
, acidGreetingState :: AcidState GreetingState
}
withAcid :: Maybe FilePath -> (Acid -> IO a) -> IO a
withAcid mBasePath action =
let basePath = fromMaybe "_state" mBasePath
in withLocalState (Just $ basePath </> "count") initialCountState $ \c ->
withLocalState (Just $ basePath </> "greeting") initialGreetingState $ \g ->
action (Acid c g)
newtype App a = App { unApp :: ServerPartT (ReaderT Acid IO) a }
deriving ( Functor, Alternative, Applicative, Monad, MonadPlus, MonadIO
, HasRqData, ServerMonad ,WebMonad Response, FilterMonad Response
, Happstack, MonadReader Acid)
runApp :: Acid -> App a -> ServerPartT IO a
runApp acid (App sp) = mapServerPartT (flip runReaderT acid) sp
instance HasAcidState App CountState where
getAcidState = acidCountState <$> ask
instance HasAcidState App GreetingState where
getAcidState = acidGreetingState <$> ask
page :: App Response
page =
do nullDir
g <- greet
c <- update IncCount -- ^ a CountState event
ok $ toResponse $
html $ do
head $ do
title "acid-state demo"
body $ do
form ! action "/" ! method "POST" ! enctype "multipart/form-data" $ do
label "new message: " ! for "msg"
input ! type_ "text" ! id "msg" ! name "greeting"
input ! type_ "submit" ! value "update message"
p $ toHtml g
p $ do "This page has been loaded "
toHtml c
" time(s)."
where
greet =
do m <- rqMethod <$> askRq
case m of
POST ->
do decodeBody (defaultBodyPolicy "/tmp/" 0 1000 1000)
newGreeting <- lookText "greeting"
update (SetGreeting newGreeting) -- ^ a GreetingState event
return newGreeting
GET ->
do query GetGreeting -- ^ a GreetingState event
main :: IO ()
main =
withAcid Nothing $ \acid ->
simpleHTTP nullConf $ runApp acid page
newtype FooState = FooState { foo :: Text }
deriving (Eq, Ord, Data, Typeable, SafeCopy)
initialFooState :: FooState
initialFooState = FooState { foo = "foo" }
askFoo :: Query FooState Text
askFoo = foo <$> ask
$(makeAcidic ''FooState ['askFoo])
fooPlugin :: (Happstack m, HasAcidState m FooState) => m Response
fooPlugin =
dir "foo" $ do
txt <- query AskFoo
ok $ toResponse txt
data Acid' = Acid' { acidCountState' :: AcidState CountState
, acidGreetingState' :: AcidState GreetingState
, acidFooState' :: AcidState FooState
}
withAcid' :: Maybe FilePath -> (Acid' -> IO a) -> IO a
withAcid' mBasePath action =
let basePath = fromMaybe "_state" mBasePath
in withLocalState (Just $ basePath </> "count") initialCountState $ \c ->
withLocalState (Just $ basePath </> "greeting") initialGreetingState $ \g ->
withLocalState (Just $ basePath </> "foo") initialFooState $ \f ->
action (Acid' c g f)
newtype App' a = App' { unApp' :: ServerPartT (ReaderT Acid' IO) a }
deriving ( Functor, Alternative, Applicative, Monad, MonadPlus, MonadIO
, HasRqData, ServerMonad ,WebMonad Response, FilterMonad Response
, Happstack, MonadReader Acid')
instance HasAcidState App' FooState where
getAcidState = acidFooState' <$> ask
fooAppPlugin :: App' Response
fooAppPlugin = fooPlugin
fooReaderPlugin :: ReaderT (AcidState FooState) (ServerPartT IO) Response
fooReaderPlugin = fooPlugin
instance HasAcidState (ReaderT (AcidState FooState) (ServerPartT IO)) FooState where
getAcidState = ask
withFooPlugin :: (MonadIO m, MonadBaseControl IO m) =>
FilePath -- ^ path to state directory
-> (ServerPartT IO Response -> m a) -- ^ function that uses fooPlugin
-> m a
withFooPlugin basePath f =
do withLocalState (Just $ basePath </> "foo") initialFooState $ \fooState ->
f $ runReaderT fooReaderPlugin fooState
main' :: IO ()
main' =
withFooPlugin "_state" $ \fooPlugin' ->
withAcid Nothing $ \acid ->
simpleHTTP nullConf $ fooPlugin' `mplus` runApp acid page
|