packages feed

lnd-client-0.1.0.1: src/LndClient/Util.hs

{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE TypeApplications #-}

module LndClient.Util
  ( retrySilent,
    retryKatip,
    safeFromIntegral,
    spawnLink,
    withSpawnLink,
    readTChanTimeout,
    catchAsync,
    txIdParser,
    txIdHex,
    psbtBtcCli,
    MicroSecondsDelay (..),
    sleep,
  )
where

import qualified Control.Concurrent.Async as Async
import qualified Control.Concurrent.STM.TVar as TVar (registerDelay)
import qualified Control.Concurrent.Thread.Delay as Delay (delay)
import Control.Exception hiding (Handler, catches)
import qualified Data.ByteString as BS (reverse)
import qualified Data.ByteString.Base16 as B16 (decode, encode)
import qualified Data.ByteString.Base64 as B64
import qualified Data.Text.Encoding as T
import LndClient.Data.Type
import LndClient.Import.External

newtype MicroSecondsDelay = MicroSecondsDelay Int

txIdParser :: Text -> Either LndError ByteString
txIdParser xr =
  case B16.decode $ encodeUtf8 xr of
    Right x -> Right $ BS.reverse x
    Left {} -> Left $ FromGrpcError "TX_ID_NON_HEX_BYTES"

txIdHex :: ByteString -> Text
txIdHex = decodeUtf8 . BS.reverse . B16.encode

psbtBtcCli :: ByteString -> Text
psbtBtcCli x = T.decodeUtf8 $ B64.encode x

retrySilent ::
  MonadIO m => m (Either LndError a) -> m (Either LndError a)
retrySilent = this 0
  where
    this (attempt0 :: Integer) f = do
      let attempt = attempt0 + 1
      res <- f
      case res of
        Left (LndError _) ->
          if attempt > 5
            then pure res
            else do
              sleep $ MicroSecondsDelay 300000
              this attempt f
        _ ->
          pure res

retryKatip ::
  KatipContext m => m (Either LndError a) -> m (Either LndError a)
retryKatip = this 0
  where
    this (attempt0 :: Integer) f = do
      let attempt = attempt0 + 1
      res <- f
      case res of
        Left (LndError _) ->
          if attempt > 5
            then pure res
            else do
              sleep $ MicroSecondsDelay 300000
              this attempt f
        _ ->
          pure res

safeFromIntegral ::
  forall a b. (Integral a, Integral b, Bounded b) => a -> Maybe b
safeFromIntegral x =
  if (intX >= intMin) && (intX <= intMax)
    then Just $ fromIntegral x
    else Nothing
  where
    intX = fromIntegral x :: Integer
    intMin = fromIntegral (minBound :: b) :: Integer
    intMax = fromIntegral (maxBound :: b) :: Integer

spawnLink :: (MonadUnliftIO m) => m a -> m (Async a)
spawnLink x =
  withRunInIO $ \run -> do
    pid <- Async.async $ run x
    Async.link pid
    pure pid

withSpawnLink :: (MonadUnliftIO m) => m a -> (Async a -> m b) -> m b
withSpawnLink action inner =
  withRunInIO $ \run ->
    Async.withAsync
      (run action)
      ( \pid -> do
          Async.link pid
          run $ inner pid
      )

readTChanTimeout ::
  MonadUnliftIO m => MicroSecondsDelay -> TChan a -> m (Maybe a)
readTChanTimeout t x = do
  t0 <-
    liftIO
      . TVar.registerDelay
      $ coerce t
  (join <$>)
    . (rightToMaybe <$>)
    . catchAsync
    . atomically
    $ Just <$> readTChan x
      <|> Nothing <$ fini t0

fini :: TVar Bool -> STM ()
fini = check <=< readTVar

catchAsync :: (MonadUnliftIO m) => m a -> m (Either Text a)
catchAsync x =
  (Right <$> x)
    `catches` [ Handler
                  ( \(_ :: BlockedIndefinitelyOnMVar) ->
                      pure $ Left "BlockedIndefinitelyOnMVar"
                  ),
                Handler
                  ( \(_ :: BlockedIndefinitelyOnSTM) ->
                      pure $ Left "BlockedIndefinitelyOnSTM"
                  )
              ]

sleep :: MonadIO m => MicroSecondsDelay -> m ()
sleep = liftIO . Delay.delay . fromIntegral @Int . coerce