packages feed

columbia-0.1.0.0: src/Data/Columbia/SeekableStream.hs

{-# LANGUAGE DeriveFunctor, Rank2Types, NoMonomorphismRestriction, Trustworthy #-}

module Data.Columbia.SeekableStream (newTable, SeekableStream, seekableStream, hoistStream, getWord8,
-- ** Stream forming functions
makeIoStream, makeIoStreamChar, unshimmedIOStream, makeByteStringStream, makeGenericStream,
-- ** Stream management functions
_getPosition, _seek, _consumeToken, _seekAtEnd, _isLockLive, getPosition, seek, consumeToken, seekAtEnd, isLockLive, relSeek, peekStream, streamToList) where

import Foreign.Marshal.Utils
import Foreign.Storable
import Data.Word
import Data.Maybe
import Data.Char
import Data.IORef
import System.IO
import System.IO.Error
import System.FileLock
import Control.Monad.State
import Control.Monad.Reader
import Control.Monad.Loops
import Control.Monad
import Control.Exception
import Control.Parallel.Strategies
import qualified Data.ByteString as B
import System.IO.Unsafe
import Unsafe.Coerce
import Data.Columbia.Mapper

data SeekableStream m c = SeekableStream
	{ __consumeToken :: !(m(Maybe c)), __seek :: !(Word32 -> m()), __getPosition :: !(m Word32), __seekAtEnd :: !(m()), __isLockLive :: !(m Bool) } deriving Functor

seekableStream :: m(Maybe c) -> (Word32 -> m()) -> m Word32 -> m() -> m Bool -> SeekableStream m c
seekableStream = SeekableStream

hoistStream :: (forall t. m t -> m2 t) -> SeekableStream m c -> SeekableStream m2 c
hoistStream f s = SeekableStream(f(_consumeToken s)) (\n -> f(_seek s n)) (f(_getPosition s)) (f(_seekAtEnd s)) (f(_isLockLive s))

getWord8 :: Handle -> IO Word8
getWord8 h = with 0$ \p -> do
	n <- hGetBuf h p 1
	when(n==0)$void$hGetChar h -- Cause EOF exception
	peek p

data LockLike = LockLike !() !(IORef Bool)

getLiveReference :: FileLock -> IORef Bool
getLiveReference lock = let LockLike _ r = unsafeCoerce lock in r

makeIoStream :: IORef(Pointer,Pointer) -> Table -> FileLock -> SeekableStream IO Word8
makeIoStream ref table lock = SeekableStream
	(readIORef ref>>= \(n,sz)->if n>=sz then
			return mzero
		else
			mapBlock table n>>=peek>>= \x->(writeIORef ref$!(succ n,sz))>>(return$!return x))
	(\n->modifyIORef' ref(\(_,sz)->(n,sz)))
	(liftM fst$readIORef ref)
	(modifyIORef' ref(\(_,sz)->(sz,sz)))
	(atomicModifyIORef'(getLiveReference lock)$ \b -> (b, b))
makeIoStreamChar :: IORef(Pointer,Pointer) -> Table -> FileLock -> SeekableStream IO Char
makeIoStreamChar ref t = fmap(chr.fromIntegral).makeIoStream ref t

unshimmedIOStream :: Handle -> FileLock -> SeekableStream IO Word8
unshimmedIOStream handle lock = SeekableStream
	(liftM(return$!) (getWord8 handle))
	(hSeek handle AbsoluteSeek. toInteger)
	(liftM fromInteger(hTell handle))
	(hSeek handle SeekFromEnd 0)
	(atomicModifyIORef'(getLiveReference lock)$ \b -> (b, b))

makeByteStringStream :: B.ByteString -> SeekableStream(State Word32) Word8
makeByteStringStream b = SeekableStream
	(do
	n <- get
	let n' = fromIntegral n
	if n' == B.length b then
			return mzero
		else do
			put$!succ n
			return$!return$!B.index b n')
	put
	get
	(put$!fromIntegral$B.length b)
	(return True)

_fst3 (x, _, _) = x

makeGenericStream :: SeekableStream(State(Word32, [t], [t])) t
makeGenericStream = SeekableStream
	(do
	(m, ls, ls2) <- get
	case ls2 of
		x:xs -> do
			put$!(succ m, x:ls, xs)
			return$!return x
		[] -> return mzero)
	(\n -> do
	(m, ls, ls2) <- get
	let n' = fromIntegral n-fromIntegral m
	if n' < 0 then
		let (lsa, lsb) = splitAt(-n') ls in
			put$!using(n, lsb, reverse lsa ++ ls2) (evalTuple3 rseq rseq rseq)
		else let (ls2a, ls2b) = splitAt n' ls2 in
			put$!using(n, reverse ls2a ++ ls, ls2b) (evalTuple3 rseq rseq rseq))
	(liftM _fst3 get)
	(modify(\(n, ls, ls2) -> using(n+fromIntegral(length ls2), reverse ls2++ls, []) (evalTuple3 rseq rseq rseq)))
	(return True)

_consumeToken = __consumeToken
_seek = __seek
_getPosition = __getPosition
_seekAtEnd = __seekAtEnd
_isLockLive = __isLockLive

getPosition :: (Monad m) => ReaderT(SeekableStream m c) m Word32
getPosition = ask>>=lift._getPosition

seek :: (Monad m) => Word32 -> ReaderT(SeekableStream m c) m ()
seek n = ask>>=lift.(`_seek` n)

consumeToken :: (Monad m) => ReaderT(SeekableStream m c) m c
consumeToken = do
	s <- ask
	liftM(maybe(error"consumeToken: end of stream") id)$lift$_consumeToken s

seekAtEnd :: (Monad m) => ReaderT(SeekableStream m c) m ()
seekAtEnd = ask>>=lift._seekAtEnd

isLockLive :: (Monad m) => ReaderT(SeekableStream m c) m Bool
isLockLive = ask>>=lift._isLockLive 

relSeek n = do
	m <- getPosition
	seek$!m+n

peekStream :: (Monad m) => ReaderT(SeekableStream m c) m c
peekStream = do
	x <- consumeToken
	relSeek(-1)
	return x

streamToList :: (Monad m) => SeekableStream m c -> m[c]
streamToList = unfoldM._consumeToken