packages feed

pdf-toolbox-core-0.1.1: lib/Pdf/Core/IO/Buffer.hs

-- | Buffer abstracts from file IO

module Pdf.Core.IO.Buffer
(
  Buffer(..),
  toInputStream,
  fromHandle,
  fromBytes,
  dropExactly
)
where

import Prelude hiding (read)
import Data.Int
import Data.IORef
import Data.ByteString (ByteString)
import qualified Data.ByteString as ByteString
import Control.Monad
import System.IO
import qualified System.IO.Streams as Streams
import System.IO.Streams.Internal (InputStream(..))
import qualified System.IO.Streams.Internal as Streams

-- | Interface to file
data Buffer = Buffer
  { read :: IO (Maybe ByteString)
  , size :: IO Int64
  , seek :: Int64 -> IO ()
  , back :: Int64 -> IO ()
  , tell :: IO Int64
  }

-- | Convert buffer to 'InputStream'
toInputStream :: Buffer -> InputStream ByteString
toInputStream buf = InputStream
  { Streams._read = read buf
  , Streams._unRead = back buf . fromIntegral . ByteString.length
  }

-- | Make buffer from handle
--
-- Don't touch the handle while using buffer
fromHandle :: Handle -> IO Buffer
-- it is in IO in case we'll need to store intermediate state
fromHandle h = return $ Buffer
  { read = do
      bs <- ByteString.hGetSome h defaultSize
      if ByteString.null bs
        then return Nothing
        else return (Just bs)
  , size = fromIntegral <$> hFileSize h
  , seek = hSeek h AbsoluteSeek . fromIntegral
  , back = hSeek h RelativeSeek . negate . fromIntegral
  , tell = fromIntegral <$> hTell h
  }

-- | Buffer from strict 'ByteString'
--
-- That is mostly for testing
fromBytes :: ByteString -> IO Buffer
fromBytes bs = do
  ref <- newIORef 0
  return Buffer
    { read = do
        pos <- readIORef ref
        let chunk = ByteString.drop pos bs
        modifyIORef ref (+ ByteString.length chunk)
        if ByteString.null chunk
          then return Nothing
          else return (Just chunk)
    , seek = writeIORef ref . fromIntegral
    , size = return $ fromIntegral (ByteString.length bs)
    , back = modifyIORef ref . flip (-) . fromIntegral
    , tell = fromIntegral <$> readIORef ref
    }

-- | Drop specified number of bytes from input stream
dropExactly :: Int -> InputStream ByteString -> IO ()
dropExactly n = void . Streams.readExactly n

defaultSize :: Int
defaultSize = 32752