packages feed

eventlog-live-0.5.0.0: src/GHC/Eventlog/Live/Source/Core.hs

module GHC.Eventlog.Live.Source.Core (
  EventlogSourceOptions (..),
  EventlogSourceHandle (..),
  EventlogSourceData (..),
  recv,
) where

import Control.Exception (handle, throwIO)
import Data.ByteString (ByteString)
import Data.ByteString qualified as BS
import Data.Maybe (fromMaybe)
import Network.Socket (Socket)
import Network.Socket.ByteString qualified as SB
import System.IO (Handle)
import System.IO qualified as IO (stdin)
import System.IO.Error (isEOFError)
import System.Timeout (timeout)

{- |
The options for different kinds of eventlog sources.
-}
data EventlogSourceOptions
  = EventlogSourceOptionsStdin
  | EventlogSourceOptionsFile FilePath
  | EventlogSourceOptionsSocketUnix FilePath

{- |
The handles for different kinds of eventlog sources.
-}
data EventlogSourceHandle
  = EventlogSourceHandleStdin
  | EventlogSourceHandleFile Handle
  | EventlogSourceHandleSocketUnix Socket

data EventlogSourceData
  = EventlogSourceData ByteString
  | EventlogSourceTimeout
  | EventlogSourceClosed

{- |
Receive data from an `EventlogSourceHandle`.

__Warning__: The current implementation is blocking on Windows. See the documentation for `timeout`.
-}
recv ::
  -- | The handle to the eventlog source.
  EventlogSourceHandle ->
  -- | The timeout in microseconds.
  Int ->
  -- | The number of bytes to read.
  Int ->
  IO EventlogSourceData
recv = \case
  EventlogSourceHandleStdin -> recvFromHandle IO.stdin
  EventlogSourceHandleFile h -> recvFromHandle h
  EventlogSourceHandleSocketUnix s -> recvFromSocket s

-- Permit a timeout and wrap the result appropriately.

{- |
Internal helper.
Receive data from a `Socket`.
-}
recvFromSocket ::
  -- | The eventlog socket.
  Socket ->
  -- | The timeout in microseconds.
  Int ->
  -- | The number of bytes to read.
  Int ->
  IO EventlogSourceData
recvFromSocket s timeoutMicros chunkSizeBytes =
  withTimeout timeoutMicros $ do
    msg <- SB.recv s chunkSizeBytes
    if BS.null msg
      then pure EventlogSourceClosed
      else pure $ EventlogSourceData msg

{- |
Internal helper.
Receive data from a `Handle`.
-}
recvFromHandle ::
  -- | The handle to the eventlog source.
  Handle ->
  -- | The timeout in microseconds.
  Int ->
  -- | The number of bytes to read.
  Int ->
  IO EventlogSourceData
recvFromHandle h timeoutMicros chunkSizeBytes =
  withTimeout timeoutMicros $
    handleEOFError EventlogSourceClosed $
      EventlogSourceData <$> BS.hGetSome h chunkSizeBytes

{- |
Internal helper.
Allow the IO operation to timeout.
-}
withTimeout ::
  -- | The timeout in microseconds.
  Int ->
  -- | The IO action to read data from the eventlog source.
  IO EventlogSourceData ->
  IO EventlogSourceData
withTimeout timeoutMicros =
  fmap (fromMaybe EventlogSourceTimeout) . timeout timeoutMicros

{- |
Internal helper.
Recover from an EOF error with a default value.
-}
handleEOFError :: a -> IO a -> IO a
handleEOFError d = handle (\e -> if isEOFError e then pure d else throwIO e)