HsSVN-0.4.2: Subversion/Hash.hsc
#include "HsSVN.h"
module Subversion.Hash
( Hash
, HashValue(..)
, APR_HASH_T
, withHashPtr
, unsafeHashToPtr
, touchHash
, wrapHash
, new
, update
, delete
, lookup
, pairsToHash
, hashToPairs
, hashToValues
, mapHash
, mapHash'
)
where
import Control.Monad
import Foreign.C.String
import Foreign.C.Types
import Foreign.ForeignPtr
import Foreign.Marshal.Alloc
import Foreign.Marshal.Utils hiding (new)
import Foreign.Ptr
import Foreign.Storable
import qualified GHC.ForeignPtr as GF
import Prelude hiding (lookup)
import Subversion.Pool
import Subversion.Types
import System.IO.Unsafe
newtype Hash a = Hash (ForeignPtr APR_HASH_T)
data APR_HASH_T
newtype HashIndex a = HashIndex (ForeignPtr APR_HASH_INDEX_T)
data APR_HASH_INDEX_T
mallocStringForeignPtr :: String -> IO (ForeignPtr CChar)
mallocStringForeignPtr str
= withCStringLen str $ \ (strPtr, len) ->
do strFPtr <- mallocForeignPtrBytes $ len + 1
copyBytes (unsafeForeignPtrToPtr strFPtr) strPtr len
pokeByteOff (unsafeForeignPtrToPtr strFPtr) len '\0'
return strFPtr
class HashValue a where
marshal :: a -> IO (ForeignPtr ())
unmarshal :: IO () -> Ptr () -> IO a
instance HashValue String where
marshal str
= fmap castForeignPtr $ mallocStringForeignPtr str
unmarshal finalizer strPtr
= do str <- peekCString (castPtr strPtr)
finalizer
return str
foreign import ccall unsafe "apr_hash_make"
_make :: Ptr APR_POOL_T -> IO (Ptr APR_HASH_T)
foreign import ccall unsafe "apr_hash_set"
_set :: Ptr APR_HASH_T -> Ptr () -> APR_SSIZE_T -> Ptr () -> IO ()
foreign import ccall unsafe "apr_hash_get"
_get :: Ptr APR_HASH_T -> Ptr () -> APR_SSIZE_T -> IO (Ptr ())
foreign import ccall unsafe "apr_hash_first"
_first :: Ptr APR_POOL_T -> Ptr APR_HASH_T -> IO (Ptr APR_HASH_INDEX_T)
foreign import ccall unsafe "apr_hash_this"
_this :: Ptr APR_HASH_INDEX_T -> Ptr (Ptr ()) -> Ptr APR_SSIZE_T -> Ptr (Ptr ()) -> IO ()
foreign import ccall unsafe "apr_hash_next"
_next :: Ptr APR_HASH_INDEX_T -> IO (Ptr APR_HASH_INDEX_T)
wrapHash :: IO () -> Ptr APR_HASH_T -> IO (Hash a)
wrapHash finalizer hashPtr
= do hash <- newForeignPtr_ hashPtr
GF.addForeignPtrConcFinalizer hash finalizer
return $ Hash hash
withHashPtr :: Hash a -> (Ptr APR_HASH_T -> IO b) -> IO b
withHashPtr (Hash hash) = withForeignPtr hash
unsafeHashToPtr :: Hash a -> Ptr APR_HASH_T
unsafeHashToPtr (Hash hash) = unsafeForeignPtrToPtr hash
touchHash :: Hash a -> IO ()
touchHash (Hash hash) = touchForeignPtr hash
wrapHashIdx :: IO () -> Ptr APR_HASH_INDEX_T -> IO (HashIndex a)
wrapHashIdx finalizer idxPtr
= do idx <- newForeignPtr_ idxPtr
GF.addForeignPtrConcFinalizer idx finalizer
return $ HashIndex idx
withHashIdxPtr :: HashIndex a -> (Ptr APR_HASH_INDEX_T -> IO b) -> IO b
withHashIdxPtr (HashIndex idx) = withForeignPtr idx
touchHashIdx :: HashIndex a -> IO ()
touchHashIdx (HashIndex idx) = touchForeignPtr idx
new :: HashValue a => IO (Hash a)
new = do pool <- newPool
withPoolPtr pool $ \ poolPtr ->
_make poolPtr >>= wrapHash (touchPool pool)
-- Any values which have been put in the Hash will not be freed until
-- Hash gets freed. If this is a problem, we have to let Hash have Map
-- (Ptr ()) (ForeignPtr ()) and maintain it together. Annoying.
update :: HashValue a => Hash a -> String -> a -> IO ()
update (Hash hash) key value
= withForeignPtr hash $ \ hashPtr ->
do keyFPtr <- mallocStringForeignPtr key
valueFPtr <- marshal value
_set hashPtr
(castPtr $ unsafeForeignPtrToPtr keyFPtr)
(#const APR_HASH_KEY_STRING)
(unsafeForeignPtrToPtr valueFPtr)
-- key and value must not be freed before hash.
GF.addForeignPtrConcFinalizer hash
$ do touchForeignPtr keyFPtr
touchForeignPtr valueFPtr
delete :: Hash a -> String -> IO ()
delete hash key
= withHashPtr hash $ \ hashPtr ->
withCStringLen key $ \ (keyPtr, keyLen) ->
_set hashPtr (castPtr keyPtr) (fromIntegral keyLen) nullPtr
lookup :: HashValue a => Hash a -> String -> IO (Maybe a)
lookup hash key
= withHashPtr hash $ \ hashPtr ->
withCStringLen key $ \ (keyPtr, keyLen) ->
do valuePtr <- _get hashPtr (castPtr keyPtr) (fromIntegral keyLen)
if valuePtr == nullPtr then
return Nothing
else
-- valuePtr may be freed at the same time when hash gets
-- freed, but that must not happen before doing
-- unmarshal.
fmap Just $ unmarshal (touchHash hash) valuePtr
getFirst :: Hash a -> IO (Maybe (HashIndex a))
getFirst hash
= do pool <- newPool
withPoolPtr pool $ \ poolPtr ->
withHashPtr hash $ \ hashPtr ->
do idxPtr <- _first poolPtr hashPtr
if idxPtr == nullPtr then
return Nothing
else
liftM Just $ wrapHashIdx (touchPool pool) idxPtr
getThis :: HashValue a => HashIndex a -> IO (String, a)
getThis idx
= do (key, valPtr) <- getThis' idx
val <- unmarshal (touchHashIdx idx) (castPtr valPtr)
return (key, val)
getThis' :: HashIndex a -> IO (String, Ptr a)
getThis' idx
= withHashIdxPtr idx $ \ idxPtr ->
alloca $ \ keyPtrPtr ->
alloca $ \ keyLenPtr ->
alloca $ \ valPtrPtr ->
do _this idxPtr keyPtrPtr keyLenPtr valPtrPtr
keyPtr <- liftM castPtr (peek keyPtrPtr)
keyLen <- liftM fromIntegral (peek keyLenPtr)
key <- peekCStringLen (keyPtr, keyLen)
valPtr <- peek valPtrPtr
return (key, castPtr valPtr)
getNext :: HashIndex a -> IO (Maybe (HashIndex a))
getNext idx
= withHashIdxPtr idx $ \ idxPtr ->
do idxPtr' <- _next idxPtr
if idxPtr' == nullPtr then
return Nothing
else
liftM Just $ wrapHashIdx (touchHashIdx idx) idxPtr'
pairsToHash :: HashValue a => [(String, a)] -> IO (Hash a)
pairsToHash xs
= do hash <- new
mapM_ (uncurry $ update hash) xs
return hash
hashToPairs :: HashValue a => Hash a -> IO [(String, a)]
hashToPairs = mapHash (return . id)
hashToValues :: HashValue a => Hash a -> IO [a]
hashToValues = mapHash (return . snd)
mapHash :: HashValue a => ((String, a) -> IO b) -> Hash a -> IO [b]
mapHash f hash = getFirst hash >>= loop
where
loop Nothing = return []
loop (Just idx) = do x <- f =<< getThis idx
xs <- unsafeInterleaveIO
(getNext idx >>= loop)
return (x:xs)
mapHash' :: ((String, Ptr a) -> IO b) -> Hash a -> IO [b]
mapHash' f hash = getFirst hash >>= loop
where
loop Nothing = return []
loop (Just idx) = do x <- f =<< getThis' idx
xs <- unsafeInterleaveIO
(getNext idx >>= loop)
return (x:xs)