#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)