{-# LANGUAGE DeriveAnyClass #-}
module Data.Binary.IO
(
ReaderError (..)
, Reader
, newReader
, Writer
, newWriter
, Duplex
, newDuplex
, CanGet (..)
, read
, CanPut (..)
, write
)
where
import Prelude hiding (read)
import qualified Control.Exception as Exception
import Control.Monad (join)
import Data.Bifunctor (bimap)
import qualified Data.Binary as Binary
import qualified Data.Binary.Get as Binary.Get
import qualified Data.Binary.Put as Binary.Put
import qualified Data.ByteString as ByteString.Strict
import qualified Data.ByteString.Lazy as ByteString
import Data.IORef (IORef, atomicModifyIORef, newIORef)
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
-> Either ReaderError (StationaryReader, a)
runStationaryReader (StationaryReader stream) getter =
bimap withError withSuccess (Binary.Get.runGetOrFail getter stream)
where
withError (remainingBody, offset, errorMessage) =
ReaderGetError
{ readerErrorRemaining = remainingBody
, readerErrorOffset = offset
, readerErrorInput = stream
, readerErrorMessage = errorMessage
}
withSuccess (tailStream, _, value) = (StationaryReader tailStream, value)
newStationaryReader :: Handle -> IO StationaryReader
newStationaryReader handle = do
hSetBinaryMode handle True
StationaryReader <$> ByteString.hGetContents handle
newtype Reader = Reader (IORef StationaryReader)
runReader :: Reader -> Binary.Get a -> IO a
runReader (Reader readerVar) getter =
join $ atomicModifyIORef readerVar $ \posReader ->
case runStationaryReader posReader getter of
Left error -> (posReader, Exception.throwIO error)
Right result -> pure <$> result
newReader
:: Handle
-> IO Reader
newReader handle = do
posReader <- newStationaryReader handle
Reader <$> newIORef 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
-> IO a
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
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)