packages feed

HsSVN-0.4.2: Subversion/FileSystem/Revision.hsc

{- -*- haskell -*- -}

-- |An interface to functions that work on an existing
-- (i.e. read-only) revision in a filesystem.

module Subversion.FileSystem.Revision
    ( -- * Type
      Rev

      -- * Running the monad
    , withRevision

      -- * Getting revision info
    , getRevisionNumber

      -- * Accessing revision property
    , getRevisionProp
    , getRevisionProp'
    , getRevisionPropList
    , getRevisionPropList'
    , setRevisionProp

      -- * Getting node history
    , getNodeHistory
    )
    where

import           Control.Monad.Reader
import qualified Data.ByteString.Char8 as B8
import           Foreign.C.String
import           Foreign.Storable
import           Foreign.Marshal.Alloc
import           Foreign.Ptr
import           Subversion.Error
import           Subversion.FileSystem
import           Subversion.FileSystem.Root
import           Subversion.Hash
import           Subversion.Pool
import           Subversion.String
import           Subversion.Types
import           System.IO.Unsafe


{- Monad Rev ----------------------------------------------------------------- -}

-- |@'Rev' a@ is a FS monad which reads data from an existing revision
-- and finally returns @a@. See 'Subversion.FileSystem.Root.MonadFS'.
--
-- Since 'Rev' monad does no transactions,
-- 'Subversion.FileSystem.Root.unsafeIOToFS' isn't really unsafe. You
-- can do any I\/O actions in the monad if you wish.
newtype Rev a = Rev { unRev :: ReaderT FileSystemRoot IO a }

instance Functor Rev where
    fmap f c = Rev (fmap f (unRev c))

instance Monad Rev where
    c >>= f = Rev (unRev c >>= unRev . f)
    return  = Rev . return
    fail    = Rev . fail

instance MonadFS Rev where
    getRoot        = Rev ask
    unsafeIOToFS a = Rev (liftIO a)
    isTransaction  = Rev (return False)


{- functions and types ------------------------------------------------------- -}

data SVN_FS_HISTORY_T


foreign import ccall unsafe "svn_fs_revision_root"
        _revision_root :: Ptr (Ptr SVN_FS_ROOT_T) -> Ptr SVN_FS_T -> SVN_REVNUM_T -> Ptr APR_POOL_T -> IO (Ptr SVN_ERROR_T)

foreign import ccall unsafe "svn_fs_revision_root_revision"
        _revision_root_revision :: Ptr SVN_FS_ROOT_T -> IO SVN_REVNUM_T

foreign import ccall unsafe "svn_fs_revision_prop"
        _revision_prop :: Ptr (Ptr SVN_STRING_T) -> Ptr SVN_FS_T -> SVN_REVNUM_T -> CString -> Ptr APR_POOL_T -> IO (Ptr SVN_ERROR_T)

foreign import ccall unsafe "svn_fs_revision_proplist"
        _revision_proplist :: Ptr (Ptr APR_HASH_T) -> Ptr SVN_FS_T -> SVN_REVNUM_T -> Ptr APR_POOL_T -> IO (Ptr SVN_ERROR_T)

foreign import ccall unsafe "svn_fs_change_rev_prop"
        _change_rev_prop :: Ptr SVN_FS_T -> SVN_REVNUM_T -> CString -> Ptr SVN_STRING_T -> Ptr APR_POOL_T -> IO (Ptr SVN_ERROR_T)

foreign import ccall unsafe "svn_fs_node_history"
        _node_history :: Ptr (Ptr SVN_FS_HISTORY_T) -> Ptr SVN_FS_ROOT_T -> CString -> Ptr APR_POOL_T -> IO (Ptr SVN_ERROR_T)

foreign import ccall unsafe "svn_fs_history_prev"
        _history_prev :: Ptr (Ptr SVN_FS_HISTORY_T) -> Ptr SVN_FS_HISTORY_T -> SVN_BOOLEAN_T -> Ptr APR_POOL_T -> IO (Ptr SVN_ERROR_T)

foreign import ccall unsafe "svn_fs_history_location"
        _history_location :: Ptr CString -> Ptr SVN_REVNUM_T -> Ptr SVN_FS_HISTORY_T -> Ptr APR_POOL_T -> IO (Ptr SVN_ERROR_T)


getRevisionRoot :: FileSystem -> Int -> IO FileSystemRoot
getRevisionRoot fs revNum
    = do pool <- newPool
         alloca $ \ rootPtrPtr ->
             withFSPtr fs $ \ fsPtr ->
             withPoolPtr pool $ \ poolPtr ->
                 do svnErr $ _revision_root rootPtrPtr fsPtr (fromIntegral revNum) poolPtr
                    -- FS Root depends on both pool and fs.
                    wrapFSRoot (touchPool pool >> touchFS fs)
                        =<< peek rootPtrPtr

-- |@'withRevision'@ runs a 'Rev' monad in an IO monad.
withRevision :: FileSystem -> RevNum -> Rev a -> IO a
withRevision fs revNum c
    = getRevisionRoot fs revNum
      >>= runReaderT (unRev c)

-- |Return the revision number.
getRevisionNumber :: Rev RevNum
getRevisionNumber
    = do root <- getRoot
         unsafeIOToFS $ withFSRootPtr root $ \ rootPtr ->
             (fmap fromIntegral (_revision_root_revision rootPtr))

-- |@'getRevisionProp' propName@ returns the value of the property
-- named @propName@ of the revision.
getRevisionProp :: String -> Rev (Maybe String)
getRevisionProp name
    = do root   <- getRoot
         fs     <- unsafeIOToFS $ getRootFS root
         revNum <- getRevisionNumber
         unsafeIOToFS $ getRevisionProp' fs revNum name

-- |@'getRevisionProp'' fs revNum propName@ returns the value of the
-- property named @propName@ of revision @revNum@ of filesystem @fs@.
getRevisionProp' :: FileSystem -> RevNum -> String -> IO (Maybe String)
getRevisionProp' fs revNum name
    = do pool <- newPool
         alloca $ \ valPtrPtr ->
             withFSPtr   fs   $ \ fsPtr   ->
             withCString name $ \ namePtr ->
             withPoolPtr pool $ \ poolPtr ->
             do svnErr $ _revision_prop valPtrPtr fsPtr (fromIntegral revNum) namePtr poolPtr
                prop <- peekSvnString' =<< peek valPtrPtr
                -- We read prop in the pool so we don't want pool to
                -- be freed that time.
                touchPool pool
                return $ fmap B8.unpack prop

-- |Return the entire property list of the revision.
getRevisionPropList :: Rev [(String, String)]
getRevisionPropList
    = do root   <- getRoot
         fs     <- unsafeIOToFS $ getRootFS root
         revNum <- getRevisionNumber
         unsafeIOToFS $ getRevisionPropList' fs revNum

-- |Return the entire property list of a revision.
getRevisionPropList' :: FileSystem -> RevNum -> IO [(String, String)]
getRevisionPropList' fs revNum
    = do pool <- newPool
         alloca $ \ hashPtrPtr ->
             withFSPtr   fs   $ \ fsPtr   ->
             withPoolPtr pool $ \ poolPtr ->
             do svnErr $ _revision_proplist hashPtrPtr fsPtr (fromIntegral revNum) poolPtr
                hash <- wrapHash (touchPool pool) =<< peek hashPtrPtr
                mapHash' (\ (n, v)
                              -> fmap ((,) n . B8.unpack) (peekSvnString v))
                         hash

-- |Change, add or delete a property on a revision. Note that revision
-- properties are non-historied: you can change them after the
-- revision has been comitted. They are not protected via
-- transactions.
setRevisionProp :: FileSystem   -- ^ The transaction
                -> RevNum       -- ^ The revision
                -> String       -- ^ The property name
                -> Maybe String -- ^ The property value
                -> IO ()
setRevisionProp fs revNum name valStr
    = do pool <- newPool
         let value = fmap B8.pack valStr
         withFSPtr fs $ \ fsPtr ->
             withCString    name  $ \ namePtr  ->
             withSvnString' value $ \ valuePtr ->
             withPoolPtr    pool  $ \ poolPtr  ->
             svnErr $ _change_rev_prop fsPtr (fromIntegral revNum) namePtr valuePtr poolPtr

-- |@'getNodeHistory'@ /lazily/ reads the change history of given node
-- in a filesystem. The most recent change comes first in the
-- resulting list.
--
-- Revisions in the resulting list will be older than or the same age
-- as the revision of that node in the target revision of 'Rev'
-- monad. That is, if the 'Rev' monad is running on revision @X@, and
-- the node was modified in some revisions younger than @X@, those
-- revisions younger than @X@ will not be included in the list.
getNodeHistory
    :: Bool                     -- ^ If this is true, stepping
                                --   backwards in history would cross
                                --   a copy operation. This is usually
                                --   the desired behavior.
    -> FilePath                 -- ^ The path to node you want to read
                                --   history.
    -> Rev [(RevNum, FilePath)] -- ^ A list of @(revNum, nodePath)@:
                                --   the node was modified somehow at
                                --   revision @revNum@, and at that
                                --   time the node was located on
                                --   @nodePath@.
getNodeHistory crossCopies path
    = do pool <- unsafeIOToFS newPool
         root <- getRoot
         unsafeIOToFS $ alloca $ \ histPtrPtr ->
             withFSRootPtr root $ \ rootPtr ->
             withCString   path $ \ pathPtr ->
             withPoolPtr   pool $ \ poolPtr ->
             do svnErr $ _node_history histPtrPtr rootPtr pathPtr poolPtr
                lazyReadHist pool =<< peek histPtrPtr
    where
      lazyReadHist pool histPtr = unsafeInterleaveIO $ readHist pool histPtr

      readHist :: Pool -> Ptr SVN_FS_HISTORY_T -> IO [(RevNum, FilePath)]
      readHist pool histPtr
          = alloca           $ \ histPtrPtr ->
            withPoolPtr pool $ \ poolPtr    ->
            do svnErr $ _history_prev histPtrPtr histPtr (marshalBool crossCopies) poolPtr
               got <- peek histPtrPtr

               if got == nullPtr then
                   -- We reached at the end of history. The pool may
                   -- be freed from now.
                   touchPool pool >> return []
                 else
                   do x  <- getHistLocation got pool
                      xs <- lazyReadHist pool got
                      return (x:xs)

      getHistLocation :: Ptr SVN_FS_HISTORY_T -> Pool -> IO (RevNum, FilePath)
      getHistLocation histPtr pool
          = alloca           $ \ pathPtrPtr ->
            alloca           $ \ revNumPtr  ->
            withPoolPtr pool $ \ poolPtr    ->
            do svnErr $ _history_location pathPtrPtr revNumPtr histPtr poolPtr
               revNum <- return . fromIntegral =<< peek revNumPtr
               path'  <- peekCString           =<< peek pathPtrPtr
               return (revNum, path')