-- The following directives make GHC do some menial work on our -- behalf. {-# LANGUAGE DeriveDataTypeable, GeneralizedNewtypeDeriving #-} -- We export our RangedIO monad's type constructor, but not its value -- constructor. This keeps the implementation abstract. module ReadHelper ( RangedIO, runRangedIO, rangedRead ) where import Data.ByteString.Lazy (ByteString, hGet) import Data.Typeable (Typeable) import Control.Exception (Exception, throwIO) import Control.Monad.Reader import System.IO (Handle, hTell) -- A shiny new GHC 6.10 extensible exception. We'll throw an -- exception of this type if rangedRead tries to read outside of its -- intended range. data RangeError = RangeError deriving (Show, Typeable) instance Exception RangeError -- | A special-purpose monad that can only perform ranged file reads. newtype RangedIO a = RangedIO { runRIO :: ReaderT (Handle,Integer) IO a } deriving (Monad, MonadIO, MonadReader (Handle,Integer)) -- | Our monad's execution function. runRangedIO :: RangedIO a -> Handle -> Integer -> IO a runRangedIO = curry . runReaderT . runRIO -- | Given a number of bytes to read, return a ByteString of at most -- that length, within the given limit. rangedRead :: Integer -> RangedIO ByteString rangedRead k = do (h,limit) <- ask off <- liftIO (hTell h) when (off >= limit) $ liftIO (throwIO RangeError) liftIO $ hGet h (fromIntegral (min k (limit - off)))