{-# LANGUAGE DeriveAnyClass #-}
module Data.Binary.IO
(
ReaderError (..)
, Reader
, newReader
, Writer
, newWriter
, Duplex
, newDuplex
, CanGet (..)
, read
, readWith
, CanPut (..)
, write
)
where
import Prelude hiding (read)
import qualified Control.Exception as Exception
import qualified Control.Concurrent.MVar as MVar
import qualified Data.ByteString.Lazy as ByteString
import qualified Data.ByteString as ByteString.Strict
import qualified Data.Binary.Get as Binary.Get
import qualified Data.Binary.Put as Binary.Put
import qualified Data.Binary as Binary
import System.IO (Handle, hSetBinaryMode)
data ReaderError = ReaderGetError
{ readerErrorRemaining :: !ByteString.ByteString
, readerErrorOffset :: !Binary.Get.ByteOffset
, readerErrorInput :: !ByteString.ByteString
, readerErrorMessage :: !String
}
deriving (Show, Exception.Exception)
newtype StationaryReader = StationaryReader ByteString.ByteString
runStationaryReader :: StationaryReader -> Binary.Get.Get a -> IO (StationaryReader, a)
runStationaryReader (StationaryReader stream) getter = do
result <- Exception.evaluate (Binary.Get.runGetOrFail getter stream)
case result of
Left (remainingBody, offset, errorMessage) ->
Exception.throw ReaderGetError
{ readerErrorRemaining = remainingBody
, readerErrorOffset = offset
, readerErrorInput = stream
, readerErrorMessage = errorMessage
}
Right (tailStream, _, value) ->
pure (StationaryReader tailStream, value)
newStationaryReader :: Handle -> IO StationaryReader
newStationaryReader handle = do
hSetBinaryMode handle True
StationaryReader <$> ByteString.hGetContents handle
newtype Reader = Reader (MVar.MVar StationaryReader)
runReader :: Reader -> Binary.Get a -> (a -> IO b) -> IO b
runReader (Reader readerVar) getter continue =
MVar.modifyMVar readerVar $ \posReader -> do
toReturn <- runStationaryReader posReader getter
traverse continue toReturn
newReader
:: Handle
-> IO Reader
newReader handle = do
posReader <- newStationaryReader handle
Reader <$> MVar.newMVar posReader
newtype Writer = Writer Handle
runWriter :: Writer -> Binary.Put -> IO ()
runWriter (Writer handle) putter =
writeBytesAtomically handle (Binary.Put.runPut putter)
newWriter
:: Handle
-> Writer
newWriter = Writer
data Duplex = Duplex
{ duplexWriter :: !Writer
, duplexReader :: !Reader
}
newDuplex
:: Handle
-> IO Duplex
newDuplex handle =
Duplex (newWriter handle) <$> newReader handle
class CanGet r where
runGet
:: r
-> Binary.Get a
-> (a -> IO b)
-> IO b
instance CanGet Reader where
runGet = runReader
instance CanGet Duplex where
runGet = runGet . duplexReader
class CanPut w where
runPut
:: w
-> Binary.Put
-> IO ()
instance CanPut Handle where
runPut handle putter = writeBytesAtomically handle (Binary.Put.runPut putter)
instance CanPut Writer where
runPut = runWriter
instance CanPut Duplex where
runPut = runPut . duplexWriter
read
:: (CanGet r, Binary.Binary a)
=> r
-> IO a
read reader =
runGet reader Binary.get pure
readWith
:: (CanGet r, Binary.Binary a)
=> r
-> (a -> IO b)
-> IO b
readWith reader =
runGet reader Binary.get
write
:: (CanPut w, Binary.Binary a)
=> w
-> a
-> IO ()
write writer value =
runPut writer (Binary.put value)
writeBytesAtomically
:: Handle
-> ByteString.ByteString
-> IO ()
writeBytesAtomically handle payload =
ByteString.Strict.hPut handle (ByteString.toStrict payload)