summaryrefslogtreecommitdiff
path: root/tests/examplefiles/AcidStateAdvanced.hs
diff options
context:
space:
mode:
Diffstat (limited to 'tests/examplefiles/AcidStateAdvanced.hs')
-rw-r--r--tests/examplefiles/AcidStateAdvanced.hs209
1 files changed, 209 insertions, 0 deletions
diff --git a/tests/examplefiles/AcidStateAdvanced.hs b/tests/examplefiles/AcidStateAdvanced.hs
new file mode 100644
index 00000000..9e3e7718
--- /dev/null
+++ b/tests/examplefiles/AcidStateAdvanced.hs
@@ -0,0 +1,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