caching-vault-0.1.0.0: src/Data/Cache/Vault.hs
module Data.Cache.Vault
( Cache, newCache
, Key, mintLabeledKey, mintUniqKey
, insert, delete, reset, lookup
)
where
import Control.Concurrent.STM (atomically)
import Data.IORef
import Data.Time
import Data.Typeable
import GHC.Exts
import GHC.Fingerprint
import Prelude hiding (lookup)
import System.IO.Unsafe (unsafePerformIO)
import Unsafe.Coerce (unsafeCoerce)
import qualified Data.Text as T
import qualified StmContainers.Map as M
type KeyRepr = T.Text
data CacheEntry
= CacheEntry
{ ceValidUntil :: Maybe UTCTime
, ceValue :: Any
}
newtype Cache
= Cache { _unCache :: M.Map KeyRepr CacheEntry }
newtype Key a
= Key { unKey :: KeyRepr }
deriving (Show, Eq)
keyCounter :: IORef Int
keyCounter =
unsafePerformIO $ newIORef 0
{-# NOINLINE keyCounter #-}
-- | Mint a globally unique key
mintUniqKey :: IO (Key a)
mintUniqKey =
atomicModifyIORef' keyCounter $ \ctr ->
( ctr + 1
, Key $ "uniq/" <> T.pack (show ctr)
)
-- | Mint a key with a label for a given type. Note that keys
-- with the same label but for different types are different.
mintLabeledKey :: forall a. Typeable a => T.Text -> Key a
mintLabeledKey label =
Key $ "label/" <> label <> "/" <> typeSig
where
typeSig =
let (Fingerprint x1 x2) =
typeRepFingerprint (typeRep (Proxy :: Proxy (Proxy a)))
in T.pack $ show x1 <> "." <> show x2
-- | Create a new cache container.
newCache :: IO Cache
newCache =
Cache <$> M.newIO
-- | Insert a value into the cache with an optional expiry date.
insert :: Key a -> Maybe UTCTime -> a -> Cache -> IO ()
insert k t v (Cache ref) =
atomically $ M.insert val key ref
where
val =
CacheEntry
{ ceValidUntil = t
, ceValue = unsafeCoerce v
}
key = unKey k
-- | Delete a value from the cache.
delete :: Key a -> Cache -> IO ()
delete k (Cache ref) =
atomically $ M.delete (unKey k) ref
-- | Purge all values form the cache.
reset :: Cache -> IO ()
reset (Cache ref) =
atomically $ M.reset ref
-- | Given the current time, lookup a key in the cache.
lookup :: UTCTime -> Key a -> Cache -> IO (Maybe a)
lookup now k (Cache ref) =
do entry <-
atomically $ M.lookup (unKey k) ref
case entry of
Nothing -> pure Nothing
Just e ->
case ceValidUntil e of
Just validUntil | validUntil < now -> pure Nothing
_ -> pure (Just $ unsafeCoerce (ceValue e))