{-# LANGUAGE MultiParamTypeClasses #-}
module Arbor.File.Format.Asif.Write
(
writeAsif
, buildAsifBytestring
, lazyByteStringSegment
, nullTerminatedStringSegment
, textSegment
, asciiSegment
, boolSegment
, word8Segment
, word16Segment
, word32Segment
, word64Segment
, int8Segment
, int16Segment
, int32Segment
, int64Segment
, ipv4Segment
, ipv6Segment
, utcTimeMicrosSegment
, genericInitial
, genericStep
, genericExtract
, genericFold
)
where
import Arbor.File.Format.Asif.ByteString.Builder
import Arbor.File.Format.Asif.Data.Ip (ipv4ToWord32, ipv6ToWord32x4)
import Arbor.File.Format.Asif.Type
import Arbor.File.Format.Asif.Whatever (Whatever (..))
import Conduit
import Control.Foldl
import Control.Lens
import Control.Monad.IO.Class (liftIO)
import Control.Monad.Trans.Resource (MonadResource)
import Data.Int
import Data.Profunctor (lmap)
import Data.Semigroup ((<>))
import Data.Word
import System.IO (Handle, SeekMode (AbsoluteSeek), hFlush, hSeek)
import System.IO.Temp (openTempFile)
import qualified Arbor.File.Format.Asif.Format as F
import qualified Data.ByteString.Builder as BB
import qualified Data.ByteString.Lazy as LBS
import qualified Data.IP as IP
import qualified Data.Text as T
import qualified Data.Text.Encoding as T
import qualified Data.Text.Lazy as TL
import qualified Data.Text.Lazy.Encoding as TE
import qualified Data.Thyme.Clock.POSIX as TY
import qualified Data.Thyme.Time.Core as TY
writeAsif :: (Foldable f, MonadResource m)
=> Handle
-> String
-> Maybe TY.POSIXTime
-> FoldM m a [Segment Handle]
-> f a
-> m ()
writeAsif hOutput asifType mTimestamp fld foldable = do
segments <- foldM fld foldable
contents <- segmentsC asifType mTimestamp segments
runConduit $ contents .| sinkHandle hOutput
liftIO $ hFlush hOutput
buildAsifBytestring :: (Foldable f, MonadResource m)
=> String
-> Maybe TY.POSIXTime
-> FoldM m a [Segment Handle]
-> f a
-> m LBS.ByteString
buildAsifBytestring asifType mTimestamp fld foldable = do
(_, _, h) <- openTempFile Nothing "asif"
writeAsif h asifType mTimestamp fld foldable
liftIO $ hSeek h AbsoluteSeek 0
liftIO $ LBS.hGetContents h
lazyByteStringSegment :: MonadResource m => Whatever F.Format -> (a -> LBS.ByteString) -> T.Text -> FoldM m a [Segment Handle]
lazyByteStringSegment = genericFold BB.lazyByteString
nullTerminatedStringSegment :: MonadResource m => (a -> T.Text) -> T.Text -> FoldM m a [Segment Handle]
nullTerminatedStringSegment f t = FoldM step initial extract
where
initial = genericInitial t
step h b = do
liftIO $ BB.hPutBuilder h $ BB.byteString (T.encodeUtf8 . f $ b) <> BB.word8 0
pure h
extract = genericExtract t (Known F.StringZ)
textSegment :: MonadResource m => (a -> T.Text) -> T.Text -> FoldM m a [Segment Handle]
textSegment f = genericFold TE.encodeUtf8Builder (Known F.Text) (TL.fromStrict . f)
asciiSegment :: MonadResource m => (a -> Char) -> T.Text -> FoldM m a [Segment Handle]
asciiSegment = genericFold BB.char8 (Known F.Char)
boolSegment :: MonadResource m => (a -> Bool) -> T.Text -> FoldM m a [Segment Handle]
boolSegment f = genericFold BB.word8 (Known F.Bool) (bool2word8 . f)
where
bool2word8 False = 0
bool2word8 True = 1
word8Segment :: MonadResource m => (a -> Word8) -> T.Text -> FoldM m a [Segment Handle]
word8Segment = genericFold BB.word8 (Known F.Word8)
word16Segment :: MonadResource m => (a -> Word16) -> T.Text -> FoldM m a [Segment Handle]
word16Segment = genericFold BB.word16LE (Known F.Word16LE)
word32Segment :: MonadResource m => (a -> Word32) -> T.Text -> FoldM m a [Segment Handle]
word32Segment = genericFold BB.word32LE (Known F.Word32LE)
word64Segment :: MonadResource m => (a -> Word64) -> T.Text -> FoldM m a [Segment Handle]
word64Segment = genericFold BB.word64LE (Known F.Word64LE)
int8Segment :: MonadResource m => (a -> Int8) -> T.Text -> FoldM m a [Segment Handle]
int8Segment = genericFold BB.int8 (Known F.Int8)
int16Segment :: MonadResource m => (a -> Int16) -> T.Text -> FoldM m a [Segment Handle]
int16Segment = genericFold BB.int16LE (Known F.Int16LE)
int32Segment :: MonadResource m => (a -> Int32) -> T.Text -> FoldM m a [Segment Handle]
int32Segment = genericFold BB.int32LE (Known F.Int32LE)
int64Segment :: MonadResource m => (a -> Int64) -> T.Text -> FoldM m a [Segment Handle]
int64Segment = genericFold BB.int64LE (Known F.Int64LE)
ipv4Segment :: MonadResource m => (a -> IP.IPv4) -> T.Text -> FoldM m a [Segment Handle]
ipv4Segment f = genericFold BB.word32LE (Known F.Ipv4) (ipv4ToWord32 . f)
ipv6Segment :: MonadResource m => (a -> IP.IPv6) -> T.Text -> FoldM m a [Segment Handle]
ipv6Segment f = genericFold encoding (Known F.Ipv6) extract
where
encoding = Prelude.foldMap BB.word32BE
extract = tupleToList . ipv6ToWord32x4 . f
tupleToList (w1,w2,w3,w4) = [w1,w2,w3,w4]
utcTimeMicrosSegment :: MonadResource m => (a -> TY.UTCTime) -> T.Text -> FoldM m a [Segment Handle]
utcTimeMicrosSegment f = genericFold BB.int64LE (Known F.TimeMicros64LE) (fromTime . f)
where
fromTime :: TY.UTCTime -> Int64
fromTime = view (TY.posixTime . TY.microseconds)
genericInitial :: MonadResource m => T.Text -> m Handle
genericInitial name = do
(_, _, h) <- openTempFile Nothing (T.unpack name)
pure h
genericStep :: MonadResource m => (a -> BB.Builder) -> Handle -> a -> m Handle
genericStep enc h b = do
liftIO $ BB.hPutBuilder h $ enc b
pure h
genericExtract :: MonadResource m => T.Text -> Whatever F.Format -> Handle -> m [Segment Handle]
genericExtract filen typ h = pure [segment h $ metaFilename filen <> metaFormat typ]
genericFold :: MonadResource m => (a -> BB.Builder) -> Whatever F.Format -> (b -> a) -> T.Text -> FoldM m b [Segment Handle]
genericFold enc fmt f t = lmap f $ FoldM (genericStep enc) (genericInitial t) (genericExtract t fmt)