    {-# LANGUAGE ScopedTypeVariables #-}
    -- |
    -- Module      : Data.Git.Storage.CacheFile
    -- License     : BSD-style
    -- Maintainer  : Vincent Hanquez <vincent@snarc.org>
    -- Stability   : experimental
    -- Portability : unix
    --
    module Data.Git.Storage.CacheFile (CacheFile, newCacheVal, getCacheVal) where

    import Control.Applicative ((<$>))
    import Control.Concurrent.MVar
    import qualified Control.Exception as E
    import Filesystem.Path
    import Filesystem.Path.CurrentOS (encodeString)
    import Prelude hiding (FilePath)
    import System.PosixCompat.Files (getFileStatus, modificationTime)
    import System.PosixCompat.Types (EpochTime)

    data CacheFile a = CacheFile
        { cacheFilepath :: FilePath
        , cacheRefresh  :: IO a
        , cacheIniVal   :: a
        , cacheLock     :: MVar (MTime, a)
        }

    timeZero = 0

    newCacheVal :: FilePath -> IO a -> a -> IO (CacheFile a)
    newCacheVal path refresh initialVal =
        CacheFile path refresh initialVal <$> newMVar (MTime timeZero, initialVal)

    getCacheVal :: CacheFile a -> IO a
    getCacheVal cachefile = modifyMVar (cacheLock cachefile) getOrRefresh
        where getOrRefresh s@(mtime, cachedVal) = do
                 cMTime <- getMTime $ cacheFilepath cachefile
                 case cMTime of
                      Nothing -> return ((MTime timeZero, cacheIniVal cachefile), cacheIniVal cachefile)
                      Just newMtime | newMtime > mtime -> cacheRefresh cachefile >>= \v -> return ((newMtime, v), v)
                                    | otherwise        -> return (s, cachedVal)

    newtype MTime = MTime EpochTime deriving (Eq,Ord)

    getMTime filepath = (Just . MTime . modificationTime <$> getFileStatus (encodeString filepath))
                `E.catch` \(_ :: E.SomeException) -> return Nothing