{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE MultiParamTypeClasses #-}
module Data.CAS.RemoteCache
( Cacher(..)
, PullResult(..), PushResult(..), AliasResult(..)
, NoCache(..), memoryCache
, pullAsArchive, pushAsArchive
) where
import qualified Codec.Archive.Tar as Tar
import Control.Concurrent.MVar
import Control.Monad.IO.Class (MonadIO, liftIO)
import Data.ByteString.Lazy (ByteString)
import Data.CAS.ContentHashable
import Data.Map.Strict (Map)
import qualified Data.Map.Strict as Map
import Path
data PullResult a
= PullOK a
| NotInCache
| PullError String
deriving (Eq, Ord, Show)
data PushResult
= PushOK
| PushError String
deriving (Eq, Ord, Show)
data AliasResult
= AliasOK
| TargetNotInCache
| AliasError String
class Monad m => Cacher m a where
push ::
a
-> ContentHash
-> Maybe ContentHash
-> Path Abs Dir
-> m PushResult
pull :: a -> ContentHash -> Path Abs Dir -> m (PullResult ())
pushAsArchive ::
MonadIO m
=> (ContentHash -> ContentHash -> m (Either String ()))
-> (ContentHash -> ByteString -> m PushResult)
-> ContentHash
-> Maybe ContentHash
-> Path Abs Dir
-> m PushResult
pushAsArchive alias pushArchive primaryKey mSecondaryKey path = do
archive <- liftIO $ Tar.write <$> Tar.pack (toFilePath path) ["."]
pushArchive primaryKey archive >>= \case
PushError e -> pure $ PushError e
res ->
case mSecondaryKey of
Just secondaryKey ->
alias primaryKey secondaryKey >>= \case
Left err -> pure $ PushError err
Right () -> pure res
Nothing -> pure res
pullAsArchive ::
MonadIO m
=> (ContentHash -> m (PullResult ByteString))
-> ContentHash
-> Path Abs Dir
-> m (PullResult ())
pullAsArchive pullArchive hash path =
pullArchive hash >>= \case
PullOK archive -> do
liftIO $ Tar.unpack (toFilePath path) $ Tar.read archive
pure $ PullOK ()
NotInCache -> pure NotInCache
PullError e -> pure $ PullError e
data NoCache = NoCache
instance Monad m => Cacher m NoCache where
pull _ _ _ = pure NotInCache
push _ _ _ _ = pure PushOK
data MemoryCache = MemoryCache (MVar (Map ContentHash ByteString))
instance MonadIO m => Cacher m MemoryCache where
pull (MemoryCache cacheVar) = pullAsArchive $ \hash -> do
cacheMap <- liftIO $ readMVar cacheVar
case Map.lookup hash cacheMap of
Nothing -> pure NotInCache
Just x -> pure (PullOK x)
push (MemoryCache cacheVar) = pushAsArchive alias $ \hash content -> do
liftIO $ modifyMVar_
cacheVar
(\cacheMap -> pure $ Map.insert hash content cacheMap)
pure PushOK
where
alias from to = liftIO $ Right <$> modifyMVar_ cacheVar
(\cacheMap -> pure $ Map.insert to (cacheMap Map.! from) cacheMap)
memoryCache :: MonadIO m => m MemoryCache
memoryCache = liftIO $ MemoryCache <$> newMVar mempty
instance Cacher m a => Cacher m (Maybe a) where
pull (Just x) = pull x
pull Nothing = pull NoCache
push (Just x) = push x
push Nothing = push NoCache