module BTree.BinaryFile
( writeWithHeader
, readWithHeader
) where
import Control.Monad (when)
import Control.Error
import Control.Monad.Trans.Class
import Control.Monad.Catch
import Control.Applicative
import Data.Word
import System.IO
import Prelude
import qualified Data.ByteString.Lazy as LBS
import qualified Data.Binary as B
import qualified Data.Binary.Get as B
import qualified Data.Binary.Put as B
import Pipes
data Epilogue = Epilogue { magic :: Word64
, headerLen :: Word64
}
deriving (Show)
epiLength :: Integer
epiLength = 16
magicNumber :: Word64
magicNumber = 0xdeadbeef
instance B.Binary Epilogue where
get = Epilogue <$> B.getWord64le <*> B.getWord64le
put (Epilogue m l) = B.putWord64le m >> B.putWord64le l
writeWithHeader :: (MonadMask m, MonadIO m, B.Binary hdr)
=> FilePath
-> Producer LBS.ByteString m (hdr, r)
-> m r
writeWithHeader fname prod =
bracket (liftIO $ openFile fname WriteMode) (liftIO . hClose)
$ \hdl -> hWriteWithHeader hdl prod
hWriteWithHeader :: (MonadIO m, B.Binary hdr)
=> Handle
-> Producer LBS.ByteString m (hdr, r)
-> m r
hWriteWithHeader h prod = do
(hdr, r) <- runEffect $ for prod (liftIO . LBS.hPut h)
let encoded = B.encode hdr
liftIO $ LBS.hPut h encoded
let epi = Epilogue { magic = magicNumber
, headerLen = fromIntegral $ LBS.length encoded }
liftIO $ LBS.hPut h (B.encode epi)
return r
{-# INLINE writeWithHeader #-}
annotate :: Monad m => String -> ExceptT String m a -> ExceptT String m a
annotate ann = fmapLT ((ann++": ")++)
runGetT :: Monad m => B.Get a -> LBS.ByteString -> ExceptT String m a
runGetT _get bs =
case B.runGetOrFail _get bs of
Left (_, _, e) -> throwE e
Right (_, _, a) -> return a
readWithHeader :: (MonadMask m, MonadIO m, B.Binary hdr)
=> FilePath
-> (hdr -> Handle -> m a)
-> ExceptT String m a
readWithHeader fname action = do
r <- lift $ bracket (liftIO $ openFile fname ReadMode) (liftIO . hClose) $ \h -> runExceptT $ do
liftIO $ hSeek h SeekFromEnd (-epiLength)
epiBytes <- liftIO (LBS.hGet h $ fromIntegral epiLength)
epi <- annotate "Error reading epilogue" (runGetT B.get epiBytes)
when (magic epi /= magicNumber) $
throwE "BinaryFile.readWithHeader: Bad magic number"
let offset = fromIntegral epiLength + fromIntegral (headerLen epi)
liftIO $ hSeek h SeekFromEnd (negate offset)
hdrBytes <- liftIO (LBS.hGet h $ fromIntegral $ headerLen epi)
hdr <- annotate "Error reading header" (runGetT B.get hdrBytes)
liftIO $ hSeek h AbsoluteSeek 0
lift $ action hdr h
ExceptT $ return r