packages feed

hs-asapo-0.9.0: lib/Asapo/Either/Common.hs

{-# LANGUAGE BlockArguments #-}
{-# LANGUAGE NamedFieldPuns #-}

module Asapo.Either.Common
  ( SourceType (..),
    InstanceId (..),
    PipelineStep (..),
    Beamline (..),
    withPtr,
    Beamtime (..),
    messageIdFromInt,
    MessageId (..),
    stringHandleToText,
    DataSource (..),
    Token (..),
    timespecToUTC,
    SourceCredentials (..),
    withCredentials,
    withText,
    StreamName (..),
    withCStringN,
    withCStringNToText,
    withConstText,
    withConstCString,
    peekConstCStringText,
    peekCStringText,
    stringHandleToTextUnsafe,
    nominalDiffToMillis,
    retrieveStreamInfoFromC,
    StreamInfo (..),
  )
where

-- \|
-- Description : Utility module with common definitions
--
-- You shouldn't need to explicitly import anything from here
import Asapo.Raw.Common (AsapoSourceCredentialsHandle, AsapoStreamInfoHandle, AsapoStringHandle (AsapoStringHandle), ConstCString, asapo_create_source_credentials, asapo_free_source_credentials, asapo_stream_info_get_finished, asapo_stream_info_get_last_id, asapo_stream_info_get_name, asapo_stream_info_get_next_stream, asapo_stream_info_get_timestamp_created, asapo_stream_info_get_timestamp_last_entry, asapo_string_c_str, kProcessed, kRaw)
import Control.Applicative (pure)
import Control.Exception (bracket)
import Control.Monad (Monad ((>>=)), (>=>))
import Data.Bool (Bool, otherwise)
import Data.Eq ((==))
import Data.Function (($), (.))
import Data.Functor ((<$>))
import Data.Int (Int)
import Data.Maybe (Maybe (Just, Nothing), fromJust)
import Data.Ord ((>))
import Data.String (String)
import Data.Text (Text, pack, unpack)
import Data.Time (NominalDiffTime, zonedTimeToUTC)
import Data.Time.Clock (UTCTime, addUTCTime)
import qualified Data.Time.RFC3339 as RFC3339
import Data.Word (Word64)
import Foreign (Ptr, Storable (peek), nullPtr, with)
import Foreign.C (CChar)
import Foreign.C.ConstPtr (ConstPtr (ConstPtr, unConstPtr))
import Foreign.C.String (CString, peekCString, withCString)
import Foreign.Marshal (alloca, mallocArray)
import Foreign.Marshal.Alloc (free)
import System.Clock (TimeSpec, toNanoSecs)
import System.IO (IO)
import Text.Show (Show)
import Prelude (Fractional ((/)), Integral, Num (fromInteger, (*)), RealFrac (round), fromIntegral)

withText :: Text -> (CString -> IO a) -> IO a
withText t = withCString (unpack t)

nominalDiffToMillis :: (Integral a) => NominalDiffTime -> a
nominalDiffToMillis = round . (* 1000)

newtype MessageId = MessageId Word64 deriving (Show)

messageIdFromInt :: (Integral a) => a -> MessageId
messageIdFromInt = MessageId . fromIntegral

newtype StreamName = StreamName Text deriving (Show)

data SourceType = RawSource | ProcessedSource

newtype InstanceId = InstanceId Text

newtype PipelineStep = PipelineStep Text

newtype Beamtime = Beamtime Text

newtype Beamline = Beamline Text

newtype DataSource = DataSource Text

newtype Token = Token Text

data SourceCredentials = SourceCredentials
  { sourceType :: SourceType,
    instanceId :: InstanceId,
    pipelineStep :: PipelineStep,
    beamtime :: Beamtime,
    beamline :: Beamline,
    dataSource :: DataSource,
    token :: Token
  }

withCredentials :: SourceCredentials -> (AsapoSourceCredentialsHandle -> IO a) -> IO a
withCredentials
  ( SourceCredentials
      { sourceType,
        instanceId = InstanceId instanceId',
        pipelineStep = PipelineStep pipelineStep',
        beamtime = Beamtime beamtime',
        beamline = Beamline beamline',
        dataSource = DataSource dataSource',
        token = Token token'
      }
    )
  f = do
    let convertSourceType RawSource = kRaw
        convertSourceType ProcessedSource = kProcessed
        createCredentialsWithText = withText instanceId' \instanceId'' -> withText pipelineStep' \pipelineStep'' -> withText beamtime' \beamtime'' -> withText beamline' \beamline'' -> withText dataSource' \dataSource'' ->
          withText token' $
            asapo_create_source_credentials
              (convertSourceType sourceType)
              instanceId''
              pipelineStep''
              beamtime''
              beamline''
              dataSource''
    bracket createCredentialsWithText asapo_free_source_credentials f

peekCStringText :: CString -> IO Text
peekCStringText = (pack <$>) . peekCString

peekConstCStringText :: ConstPtr CChar -> IO Text
peekConstCStringText = (pack <$>) . peekCString . unConstPtr

withConstCString :: String -> (ConstCString -> IO b) -> IO b
withConstCString s f = withCString s (f . ConstPtr)

withConstText :: Text -> (ConstCString -> IO a) -> IO a
withConstText t = withConstCString (unpack t)

withCStringN :: Int -> (CString -> IO a) -> IO a
withCStringN size = bracket (mallocArray size) free

withCStringNToText :: Int -> (CString -> IO ()) -> IO Text
withCStringNToText size f =
  withCStringN size \ptr -> do
    f ptr
    pack <$> peekCString ptr

data StreamInfo = StreamInfo
  { streamInfoLastId :: MessageId,
    streamInfoName :: StreamName,
    streamInfoFinished :: Bool,
    streamInfoNextStream :: Text,
    streamInfoCreated :: UTCTime,
    streamInfoLastEntry :: UTCTime
  }
  deriving (Show)

-- Thanks to
--
-- https://github.com/imoverclocked/convert-times/blob/7f9b45bea8e62dbf14a156a8229b68e07efec5a1/app/Main.hs
timespecToUTC :: TimeSpec -> UTCTime
timespecToUTC sc_time =
  let scEpochInUTC :: UTCTime
      scEpochInUTC = zonedTimeToUTC $ fromJust $ RFC3339.parseTimeRFC3339 "1970-01-01T00:00:00.00Z"
      sc2diffTime = fromInteger (toNanoSecs sc_time) / 1000000000 :: NominalDiffTime
   in addUTCTime sc2diffTime scEpochInUTC

retrieveStreamInfoFromC :: AsapoStreamInfoHandle -> IO StreamInfo
retrieveStreamInfoFromC infoHandle = do
  lastId <- asapo_stream_info_get_last_id infoHandle
  name <- asapo_stream_info_get_name infoHandle >>= peekConstCStringText
  nextStream <- asapo_stream_info_get_next_stream infoHandle >>= peekConstCStringText
  finished <- asapo_stream_info_get_finished infoHandle
  created <- alloca \timespecPtr -> do
    asapo_stream_info_get_timestamp_created infoHandle timespecPtr
    timespec <- peek timespecPtr
    pure (timespecToUTC timespec)
  lastEntry <- alloca \timespecPtr -> do
    asapo_stream_info_get_timestamp_last_entry infoHandle timespecPtr
    timespec <- peek timespecPtr
    pure (timespecToUTC timespec)
  pure (StreamInfo (MessageId (fromIntegral lastId)) (StreamName name) (finished > 0) nextStream created lastEntry)

stringHandleToText :: AsapoStringHandle -> IO (Maybe Text)
stringHandleToText handle@(AsapoStringHandle handlePtr)
  | handlePtr == nullPtr = pure Nothing
  | otherwise = Just <$> (asapo_string_c_str handle >>= peekConstCStringText)

stringHandleToTextUnsafe :: AsapoStringHandle -> IO Text
stringHandleToTextUnsafe = asapo_string_c_str >=> peekConstCStringText

withPtr :: (Storable a) => a -> (Ptr a -> IO b) -> IO (a, b)
withPtr h f = with h \hPtr -> do
  result <- f hPtr
  first <- peek hPtr
  pure (first, result)