HsSVN-0.4.2: Subversion/Stream.hsc
module Subversion.Stream
( Stream
, SVN_STREAM_T
, ReadAction
, WriteAction
, CloseAction
, StreamActions(..)
, wrapStream
, withStreamPtr
, newStream
, getEmptyStream
, getStreamForStdOut
, sRead
, sReadBS
, sReadLBS
, sStrictRead
, sStrictReadLBS
, sWrite
, sWriteBS
, sWriteLBS
, sClose
)
where
import Control.Monad
import qualified Data.ByteString as Strict (ByteString)
import qualified Data.ByteString.Char8 as B8 hiding (ByteString)
import qualified Data.ByteString.Lazy as Lazy (ByteString)
import qualified Data.ByteString.Lazy.Char8 as L8 hiding (ByteString)
import Data.ByteString.Internal hiding (ByteString)
import Data.ByteString.Unsafe
import Foreign.C.String
import Foreign.C.Types
import Foreign.ForeignPtr
import Foreign.Ptr
import Foreign.Marshal.Alloc
import Foreign.Marshal.Array
import Foreign.Storable
import GHC.ForeignPtr as GF
import Subversion.Error
import Subversion.Pool
import Subversion.Types
import System.IO.Unsafe
newtype Stream = Stream (ForeignPtr SVN_STREAM_T)
data SVN_STREAM_T
type ReadAction = Int -> IO Strict.ByteString
type WriteAction = Strict.ByteString -> IO Int
type CloseAction = IO ()
data StreamActions
= StreamActions {
saRead :: !ReadAction
, saWrite :: !WriteAction
, saClose :: !CloseAction
}
type ReadCallback = Ptr () -> CString -> Ptr APR_SIZE_T -> IO (Ptr SVN_ERROR_T)
type WriteCallback = Ptr () -> CString -> Ptr APR_SIZE_T -> IO (Ptr SVN_ERROR_T)
type CloseCallback = Ptr () -> IO (Ptr SVN_ERROR_T)
foreign import ccall unsafe "svn_stream_create"
_create :: Ptr () -> Ptr APR_POOL_T -> IO (Ptr SVN_STREAM_T)
foreign import ccall unsafe "svn_stream_set_read"
_set_read :: Ptr SVN_STREAM_T -> FunPtr ReadCallback -> IO ()
foreign import ccall unsafe "svn_stream_set_write"
_set_write :: Ptr SVN_STREAM_T -> FunPtr WriteCallback -> IO ()
foreign import ccall unsafe "svn_stream_set_close"
_set_close :: Ptr SVN_STREAM_T -> FunPtr CloseCallback -> IO ()
foreign import ccall unsafe "svn_stream_empty"
_empty :: Ptr APR_POOL_T -> IO (Ptr SVN_STREAM_T)
foreign import ccall unsafe "svn_stream_for_stdout"
_for_stdout :: Ptr (Ptr SVN_STREAM_T) -> Ptr APR_POOL_T -> IO (Ptr SVN_ERROR_T)
foreign import ccall safe "svn_stream_read"
_read :: Ptr SVN_STREAM_T -> Ptr CChar -> Ptr APR_SIZE_T -> IO (Ptr SVN_ERROR_T)
foreign import ccall safe "svn_stream_write"
_write :: Ptr SVN_STREAM_T -> Ptr CChar -> Ptr APR_SIZE_T -> IO (Ptr SVN_ERROR_T)
foreign import ccall safe "svn_stream_close"
_close :: Ptr SVN_STREAM_T -> IO (Ptr SVN_ERROR_T)
foreign import ccall "wrapper"
mkReadCallback :: ReadCallback -> IO (FunPtr ReadCallback)
foreign import ccall "wrapper"
mkWriteCallback :: WriteCallback -> IO (FunPtr WriteCallback)
foreign import ccall "wrapper"
mkCloseCallback :: CloseCallback -> IO (FunPtr CloseCallback)
wrapStream :: IO () -> Ptr SVN_STREAM_T -> IO Stream
wrapStream finalizer ioPtr
= do io <- newForeignPtr_ ioPtr
GF.addForeignPtrConcFinalizer io finalizer
return $ Stream io
withStreamPtr :: Stream -> (Ptr SVN_STREAM_T -> IO a) -> IO a
withStreamPtr (Stream io) = withForeignPtr io
newStream :: StreamActions -> IO Stream
newStream actions
= do pool <- newPool
withPoolPtr pool $ \ poolPtr ->
do streamPtr <- _create nullPtr poolPtr
readFnPtr <- mkReadFnPtr (saRead actions)
writeFnPtr <- mkWriteFnPtr (saWrite actions)
closeFnPtr <- mkCloseFnPtr (saClose actions)
_set_read streamPtr readFnPtr
_set_write streamPtr writeFnPtr
_set_close streamPtr closeFnPtr
wrapStream (do freeHaskellFunPtr readFnPtr
freeHaskellFunPtr writeFnPtr
freeHaskellFunPtr closeFnPtr
touchPool pool
) streamPtr
where
mkReadFnPtr :: ReadAction -> IO (FunPtr ReadCallback)
mkReadFnPtr ra
= mkReadCallback $ \ _ bufPtr lenPtr ->
do requestedLen <- liftM fromIntegral (peek lenPtr)
resultStr <- ra requestedLen -- FIXME: should catch exceptions
B8.useAsCStringLen resultStr $ \ (resultPtr, resultLen) ->
do when (resultLen > requestedLen)
$ fail "resultLen > requestedLen" -- FIXME
copyArray bufPtr resultPtr resultLen
poke lenPtr (fromIntegral resultLen)
return nullPtr
mkWriteFnPtr :: WriteAction -> IO (FunPtr WriteCallback)
mkWriteFnPtr wa
= mkWriteCallback $ \ _ bufPtr lenPtr ->
do requestedLen <- liftM fromIntegral (peek lenPtr)
inputStr <- B8.packCStringLen (bufPtr, requestedLen)
writtenLen <- wa inputStr -- FIXME: should catch exceptions
poke lenPtr (fromIntegral writtenLen)
return nullPtr
mkCloseFnPtr :: CloseAction -> IO (FunPtr CloseCallback)
mkCloseFnPtr ca
= mkCloseCallback $ \ _ ->
do ca -- FIXME: should catch exceptions
return nullPtr
getEmptyStream :: IO Stream
getEmptyStream
= do pool <- newPool
withPoolPtr pool $ \ poolPtr ->
wrapStream (touchPool pool) =<< _empty poolPtr
getStreamForStdOut :: IO Stream
getStreamForStdOut
= do pool <- newPool
alloca $ \ ioPtrPtr ->
withPoolPtr pool $ \ poolPtr ->
do svnErr $ _for_stdout ioPtrPtr poolPtr
wrapStream (touchPool pool) =<< peek ioPtrPtr
sRead :: Stream -> IO String
sRead io
= liftM L8.unpack $ sReadLBS io
sStrictRead :: Stream -> IO String
sStrictRead io
= liftM L8.unpack $ sStrictReadLBS io
sReadBS :: Stream -> Int -> IO Strict.ByteString
sReadBS io maxLen
= withStreamPtr io $ \ ioPtr ->
createAndTrim maxLen $ \ bufPtr ->
alloca $ \ bufLenPtr ->
do poke bufLenPtr (fromIntegral maxLen)
svnErr $ _read ioPtr (castPtr bufPtr) bufLenPtr
liftM fromIntegral $ peek bufLenPtr
sReadLBS :: Stream -> IO Lazy.ByteString
sReadLBS io = fmap L8.fromChunks lazyRead
where
chunkSize = 32 * 1024
lazyRead = unsafeInterleaveIO loop
loop = do bs <- sReadBS io chunkSize
if B8.null bs then
-- reached EOF
return []
else
do bss <- lazyRead
return (bs:bss)
sStrictReadLBS :: Stream -> IO Lazy.ByteString
sStrictReadLBS io = fmap L8.fromChunks strictRead
where
chunkSize = 32 * 1024
strictRead = do bs <- sReadBS io chunkSize
if B8.null bs then
-- reached EOF
return []
else
do bss <- strictRead
return (bs:bss)
sWrite :: Stream -> String -> IO ()
sWrite io str
= sWriteLBS io (L8.pack str)
sWriteBS :: Stream -> Strict.ByteString -> IO ()
sWriteBS io str
= withStreamPtr io $ \ ioPtr ->
unsafeUseAsCStringLen str $ \ (strPtr, strLen) ->
alloca $ \ strLenPtr ->
do poke strLenPtr (fromIntegral strLen)
svnErr $ _write ioPtr strPtr strLenPtr
sWriteLBS :: Stream -> Lazy.ByteString -> IO ()
sWriteLBS io chunks
= mapM_ (sWriteBS io) (L8.toChunks chunks)
sClose :: Stream -> IO ()
sClose io
= withStreamPtr io $ \ ioPtr ->
svnErr $ _close ioPtr