module ZooKeeper.Recipe.Utils
  ( -- * Types
    SequenceNumWithGUID(..)
  , mkSequenceNumWithGUID
  , extractSeqNum

    -- * ZNode operations
  , createSeqEphemeralZNode
  ) where

import           Control.Exception
import qualified Data.List           as L
import           Z.Data.CBytes       (CBytes)
import qualified Z.Data.CBytes       as CB
import           ZooKeeper
import           ZooKeeper.Exception
import           ZooKeeper.Types

--------------------------------------------------------------------------------

-- | Represenets a name of a SEQUENCE|EPHEMERAL znode. It contains two parts,
-- a GUID, and a sequence number. The GUID is used for handleing recoverable
-- exceptions so we only care about the sequence number part when we comparing
-- two of them.
newtype SequenceNumWithGUID = SequenceNumWithGUID
  { SequenceNumWithGUID -> CBytes
unSequenceNumWithGUID :: CBytes
  }

mkSequenceNumWithGUID :: CBytes -> SequenceNumWithGUID
mkSequenceNumWithGUID :: CBytes -> SequenceNumWithGUID
mkSequenceNumWithGUID = CBytes -> SequenceNumWithGUID
SequenceNumWithGUID

instance Eq SequenceNumWithGUID where
  (SequenceNumWithGUID CBytes
s1) == :: SequenceNumWithGUID -> SequenceNumWithGUID -> Bool
== (SequenceNumWithGUID CBytes
s2) =
    CBytes -> CBytes
extractSeqNum CBytes
s1 CBytes -> CBytes -> Bool
forall a. Eq a => a -> a -> Bool
== CBytes -> CBytes
extractSeqNum CBytes
s2

instance Ord SequenceNumWithGUID where
  (SequenceNumWithGUID CBytes
s1) <= :: SequenceNumWithGUID -> SequenceNumWithGUID -> Bool
<= (SequenceNumWithGUID CBytes
s2) =
    CBytes -> CBytes
extractSeqNum CBytes
s1 CBytes -> CBytes -> Bool
forall a. Ord a => a -> a -> Bool
<= CBytes -> CBytes
extractSeqNum CBytes
s2

instance Show SequenceNumWithGUID where
  show :: SequenceNumWithGUID -> String
show (SequenceNumWithGUID CBytes
s) = CBytes -> String
CB.unpack CBytes
s

-- | Exrtact only the sequence number part from an `SequenceNumWithGUID`.
extractSeqNum :: CBytes -> CBytes
extractSeqNum :: CBytes -> CBytes
extractSeqNum = String -> CBytes
CB.pack (String -> CBytes) -> (CBytes -> String) -> CBytes -> CBytes
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ShowS
forall a. [a] -> [a]
reverse ShowS -> (CBytes -> String) -> CBytes -> String
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Char -> Bool) -> ShowS
forall a. (a -> Bool) -> [a] -> [a]
takeWhile (Char -> Char -> Bool
forall a. Eq a => a -> a -> Bool
/= Char
'_') ShowS -> (CBytes -> String) -> CBytes -> String
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ShowS
forall a. [a] -> [a]
reverse ShowS -> (CBytes -> String) -> CBytes -> String
forall b c a. (b -> c) -> (a -> b) -> a -> c
. CBytes -> String
CB.unpack

--------------------------------------------------------------------------------

-- | Creates a sequential and ephemeral znode with specified prefix
-- and GUID. The created znode is as `prefixPath/GUID-n_0000000001`.
-- Note that it uses a GUID to handle recoverable exceptions, see
-- [this](https://zookeeper.apache.org/doc/r3.7.0/recipes.html#sc_recipes_GuidNote)
-- for more details.
createSeqEphemeralZNode :: ZHandle -> CBytes -> CBytes -> IO StringCompletion
createSeqEphemeralZNode :: ZHandle -> CBytes -> CBytes -> IO StringCompletion
createSeqEphemeralZNode ZHandle
zk CBytes
prefixPath CBytes
guid = do
  let seqPath :: CBytes
seqPath = CBytes
prefixPath CBytes -> CBytes -> CBytes
forall a. Semigroup a => a -> a -> a
<> CBytes
"/" CBytes -> CBytes -> CBytes
forall a. Semigroup a => a -> a -> a
<> CBytes
guid CBytes -> CBytes -> CBytes
forall a. Semigroup a => a -> a -> a
<> CBytes
"_"
  IO StringCompletion
-> [Handler StringCompletion] -> IO StringCompletion
forall a. IO a -> [Handler a] -> IO a
catches (HasCallStack =>
ZHandle
-> CBytes
-> Maybe Bytes
-> AclVector
-> CreateMode
-> IO StringCompletion
ZHandle
-> CBytes
-> Maybe Bytes
-> AclVector
-> CreateMode
-> IO StringCompletion
zooCreate ZHandle
zk CBytes
seqPath Maybe Bytes
forall a. Maybe a
Nothing AclVector
zooOpenAclUnsafe CreateMode
ZooEphemeralSequential)
    [ (ZCONNECTIONLOSS -> IO StringCompletion)
-> Handler StringCompletion
forall a e. Exception e => (e -> IO a) -> Handler a
Handler (\(ZCONNECTIONLOSS
_ :: ZCONNECTIONLOSS  ) -> IO StringCompletion
retry)
    , (ZOPERATIONTIMEOUT -> IO StringCompletion)
-> Handler StringCompletion
forall a e. Exception e => (e -> IO a) -> Handler a
Handler (\(ZOPERATIONTIMEOUT
_ :: ZOPERATIONTIMEOUT) -> IO StringCompletion
retry)
    ]
  where
    retry :: IO StringCompletion
    retry :: IO StringCompletion
retry = do
      (StringsCompletion (StringVector [CBytes]
children)) <- HasCallStack => ZHandle -> CBytes -> IO StringsCompletion
ZHandle -> CBytes -> IO StringsCompletion
zooGetChildren ZHandle
zk CBytes
prefixPath
      case (CBytes -> Bool) -> [CBytes] -> Maybe CBytes
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Maybe a
L.find (\CBytes
child -> CBytes -> String
CB.unpack CBytes
guid String -> String -> Bool
forall a. Eq a => [a] -> [a] -> Bool
`L.isSubsequenceOf` CBytes -> String
CB.unpack CBytes
child) [CBytes]
children of
        Just CBytes
child -> StringCompletion -> IO StringCompletion
forall (m :: * -> *) a. Monad m => a -> m a
return (StringCompletion -> IO StringCompletion)
-> StringCompletion -> IO StringCompletion
forall a b. (a -> b) -> a -> b
$ CBytes -> StringCompletion
StringCompletion CBytes
child
        Maybe CBytes
Nothing    -> ZHandle -> CBytes -> CBytes -> IO StringCompletion
createSeqEphemeralZNode ZHandle
zk CBytes
prefixPath CBytes
guid