packages feed

persistent-zookeeper-0.0.1: Database/Persist/Zookeeper/ZooUtil.hs

{-# LANGUAGE FlexibleContexts, UndecidableInstances #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE EmptyDataDecls #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# OPTIONS_GHC -fno-warn-orphans #-}
module Database.Persist.Zookeeper.ZooUtil
    where

import qualified Database.Zookeeper as Z
import qualified Data.ByteString.Char8 as B
-- import qualified Data.ByteString.Lazy as BL
import qualified Data.Pool as P
import Control.Concurrent
import Control.Concurrent.STM
import Data.Time
import Data.Maybe

deriving instance Read (Z.ZKError)
deriving instance Read (Z.Stat)

zModify :: Z.Zookeeper
       -> String
       -> (Maybe B.ByteString -> IO (Maybe B.ByteString))
       -> IO (Either Z.ZKError Z.Stat)
zModify  zk key f = do
  v <- Z.get zk key Nothing
  case v of
    Right (con,ver) -> do
      v'' <- f con
      v' <- Z.set zk key v'' (Just (Z.statVersion ver))
      case v' of
        Right _ -> return v'
        Left _ -> zModify zk key f
    Left e -> return $ Left e

zReplace :: Z.Zookeeper
       -> String
       -> (Maybe B.ByteString)
       -> IO (Either Z.ZKError Z.Stat)
zReplace  zk key v'' = do
  v <- Z.get zk key Nothing
  case v of
    Right (_con,ver) -> do
      v' <- Z.set zk key v'' (Just (Z.statVersion ver))
      case v' of
        Right _ -> return v'
        Left _ -> zReplace zk key v''
    Left e -> return $ Left e

zRepSert :: Z.Zookeeper
       -> String
       -> String
       -> (Maybe B.ByteString)
       -> IO (Either Z.ZKError ())
zRepSert  zk dir key v'' = do
  v <- zCreate zk dir key v'' []
  case v of
    Right _ -> return $ Right ()
    Left Z.NodeExistsError -> do
      v' <- zReplace zk key v''
      case v' of
        Right _ -> return $ Right ()
        Left Z.NoNodeError -> do
          zRepSert zk dir key v''
        Left s -> do
          return $ Left s
    Left v' -> return $ Left v'

zCreate :: Z.Zookeeper
       -> String
       -> String
       -> Maybe B.ByteString
       -> [Z.CreateFlag]
       -> IO (Either Z.ZKError String)
zCreate zk dir path value flag = do
  v <- Z.create zk path value Z.OpenAclUnsafe flag
  case v of
    Left Z.NoNodeError -> do
      v' <- Z.create zk dir Nothing Z.OpenAclUnsafe []
      case v' of
        Left v'' -> return $ Left v''
        Right _ -> zCreate zk dir path value flag
    v' -> return v'

data ZooStat = ZooStat {
    zVar :: TMVar (ZAction String)
  , rVar :: TMVar String
  }

data ZAction t = 
    Action (Z.Zookeeper -> IO String)
  | Close

zOpen ::   String
        -> Z.Timeout
        -> Maybe Z.Watcher
        -> Maybe Z.ClientID
        -> IO ZooStat
zOpen endpoint timeout watcher clientId = do
  Z.setDebugLevel Z.ZLogError
  zvar <- atomically $ newEmptyTMVar
  rvar <- atomically $ newEmptyTMVar
  stateVar <- newTVarIO Z.ConnectingState
  let watcher' = if isJust watcher then watcher else (Just (watcherd stateVar))
  _ <- forkIO $ Z.withZookeeper endpoint timeout watcher' clientId $ \z -> do
    atomically $ do
      state <- readTVar stateVar
      case state of
        Z.ConnectingState -> retry
        _ -> return ()
    loop zvar rvar z
  return $ ZooStat zvar rvar
  where
    loop zvar rvar z = do
      var <- atomically $ takeTMVar zvar
      case var of
        Close -> return ()
        Action io -> do
          v <- io z
          atomically $ putTMVar rvar v
          loop zvar rvar z
    watcherd :: TVar Z.State -> Z.Watcher
    watcherd stateVar _z event state _mZnode = do
      case event of
        Z.SessionEvent -> atomically $ writeTVar stateVar state
        _ -> return ()

zExec :: (Read a, Show a) => ZooStat -> (Z.Zookeeper -> IO a) -> IO a
zExec (ZooStat zvar rvar) action = do
  atomically $ do
    putTMVar zvar $ Action $ \zk -> do
      v <- action zk
      return $ show v
  var <- atomically $ do
    takeTMVar rvar
  return $ read var

zClose :: ZooStat -> IO ()
zClose (ZooStat zvar _rvar) = do
  atomically $ do
    putTMVar zvar Close

connect :: String
        -> Z.Timeout
        -> Maybe Z.Watcher
        -> Maybe Z.ClientID
        -> Int
        -> NominalDiffTime
        -> Int
        -> IO (P.Pool ZooStat)
connect endpoint timeout watcher clientId numStripes idleTime maxResources =
  P.createPool
    (zOpen endpoint timeout watcher clientId) 
    zClose
    numStripes idleTime maxResources