-- Search for comments to find commentary of interest
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE OverloadedLists #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeSynonymInstances #-}
module Main where
import Control.Exception
import Control.Monad
import Data.Aeson
import Data.ByteString.Internal
import Data.Void
import Foreign.C.Error
import Foreign.C.Types
import Foreign.ForeignPtr
import Foreign.Ptr
import GHC.Generics
import Observe.Event
import Observe.Event.DSL
import Observe.Event.Render.JSON
import Observe.Event.Render.JSON.DSL.Compile
import Observe.Event.Render.JSON.Handle
import System.FilePath
import System.IO.Temp
import System.Posix.Files
import System.Posix.IO
import System.Posix.Types
-- Pretend this is in a separate module File where
deriving instance Show Errno
deriving instance ToJSON Errno
deriving instance Generic Errno
deriving instance ToJSON CInt
deriving instance Generic CInt
deriving instance ToJSON Fd
deriving instance Generic Fd
deriving instance ToJSON ByteCount
deriving instance Generic ByteCount
-- Define our selector type and give it instances to render as JSON
compile $
SelectorSpec
"file" -- Creates a type FileSelector
[ ["open", "file"] -- Creates a constructor OpenFile :: FileSelector OpenField
≔ FieldSpec
"open" -- Creates a type OpenField
[ "filename" ≔ ''FilePath, -- creates a constructor Filename :: !FilePath -> OpenField
["file", "fd"] ≔ ''Fd -- creates a constructor FileFd :: !Fd -> OpenField
],
"write"
≔ FieldSpec
"write"
[ ["bytes", "asked"] ≔ ''ByteCount,
["bytes", "actual"] ≔ ''ByteCount
]
]
-- We take an EventBackend, polymorphic in r, supporting our domain-specific selector type
writeToFile :: EventBackend IO r FileSelector -> FilePath -> ByteString -> IO ()
writeToFile backend path bs = do
let (fptr, base_off, sz) = toForeignPtr bs
-- We start an event, selected by OpenFile
fd <- withEvent backend OpenFile $ \ev -> do
-- We add a Filename field to our current active event
addField ev $ Filename path
fd <- openFd path WriteOnly (Just regularFileMode) defaultFileFlags
when (fd == -1) $ do
errno <- getErrno
-- Throw an exception which we can render as JSON
throw $ BadOpen path errno
addField ev $ FileFd fd
pure fd
withForeignPtr fptr $ \ptr -> do
let bcSz = fromIntegral sz
go :: ByteCount -> IO ()
go offset = do
newOffset <- withEvent backend Write $ \ev -> do
let ct = bcSz - offset
addField ev $ BytesAsked ct
written <- fdWriteBuf fd (plusPtr ptr (base_off + fromIntegral offset)) ct
addField ev $ BytesActual written
pure $ offset + written
when (newOffset < bcSz) $
go newOffset
go 0
closeFd fd
pure ()
-- Define a new exception that can be used with simpleJsonStderrBackend
data BadOpen = BadOpen
{ path :: !FilePath,
errno :: !Errno
}
deriving (Show, ToJSON, Generic)
-- Our exception is beneath SomeJSONException in the hierarchy
instance Exception BadOpen where
toException = jsonExceptionToException
fromException = jsonExceptionFromException
-- end module File
compile $
SelectorSpec
"main"
[ ["using", "temp", "dir"] ≔ ''FilePath, -- Creates a constructor UsingTempDir :: MainSelector FilePath
"writing" ≔ Inject ''FileSelector -- Creates a constructor Writing :: FileSelector x -> MainSelector x
]
-- Note a different selector type than writeToFile
instrumentedMain :: EventBackend IO r MainSelector -> IO ()
instrumentedMain backend = do
withEvent backend UsingTempDir $ \ev -> do
withSystemTempDirectory "example" $ \dir -> do
addField ev dir
-- Pass a new EventBackend where all parentless events are made children of our current event
writeToFile (subEventBackend Writing ev) (dir </> "example.txt") "example"
main :: IO ()
main =
-- Initialize a backend to write JSON to stderr and use it.
simpleJsonStderrBackend defaultRenderSelectorJSON >>= instrumentedMain