festung-0.9.1.1: src/Festung/Vault/VaultHandler.hs
{-# LANGUAGE NamedFieldPuns #-}
module Festung.Vault.VaultHandler
( VaultOpener
, Vault
, VaultError(..)
, newHandler
, query
, parametrizedQuery
, checkOpener
, updateOpenerPath
) where
import Control.Monad.Trans.Either
import Control.Monad.Trans.Class
import Control.Concurrent
import System.Directory (canonicalizePath)
import System.FilePath ((</>))
import Festung.Config (Config(..))
import Festung.Utils (hoistMEither, mapLeft)
import qualified Festung.Vault.Persistence as P
import Festung.Concurrency.Job
import Festung.Concurrency.Gig
-- | Data necessary to open vault (filename, password, parameters)
type VaultOpener = (FilePath, P.Password, P.VaultParameters)
data VaultError = CouldNotReach
| CouldNotOpen
| VaultError P.Error
deriving (Show)
data VaultCommand = Query (Command String (Either VaultError P.QueryResult))
| ParametrizedQuery (Command (String, [P.Value]) (Either VaultError P.QueryResult))
type Vault = Job (VaultOpener, VaultCommand)
type VaultState = (VaultOpener, P.VaultHandle)
-- | Create a new vault handler job
newHandler :: Config -> VaultOpener -> IO (Either VaultError Vault)
newHandler config@Config{dataDirectory,vaultTimeout} opener@(path, password, params) = runEitherT $ do
canonicalPath <- lift $ canonicalizePath $ dataDirectory </> path
handle <- hoistMEither $ mapLeft (const CouldNotOpen) <$> P.openVault' canonicalPath password params
lift $ do
let state = (opener, handle)
vault <- newGig vaultTimeout state $ handlerLoop config
onExit vault (P.closeVault handle)
return vault
checkOpener :: VaultOpener -> VaultOpener -> IO (Either VaultError ())
checkOpener o1 o2 =
return $ if o1 == o2
then Right ()
else Left CouldNotOpen
updateOpenerPath :: VaultOpener -> FilePath -> VaultOpener
updateOpenerPath (_, password, params) path = (path, password, params)
-- | Vault handler consuming messages send to the processor
--
-- This function is for internal use only
handlerLoop :: Config -> VaultState -> (VaultOpener, VaultCommand) -> IO (JobStatus, VaultState)
handlerLoop _config state@(opener, vault) (cmdOpener, cmd) = do
err <- checkOpener opener cmdOpener
case err of
Left msg -> handleError cmd msg
Right _ -> handleCommand vault cmd
return (KeepGoing, state)
wrapVaultErrors :: Either P.Error a -> Either VaultError a
wrapVaultErrors = mapLeft VaultError
-- FIXME(Antoine): This code is totally copy pasted, we need to find a better
-- solution for this
handleCommand :: P.VaultHandle -> VaultCommand -> IO ()
handleCommand vault (Query (Command query_ responder)) =
putMVar responder =<< wrapVaultErrors <$> P.executeQuery vault query_
handleCommand vault (ParametrizedQuery (Command (query_, params) responder)) =
putMVar responder =<< wrapVaultErrors <$> P.executeParameterizedQuery vault query_ params
-- FIXME(Antoine): This code is totally copy pasted, we need to find a better
-- solution for this. Type kinds, or polymorphism, I don't want
-- to use dynamic types.
handleError :: VaultCommand -> VaultError -> IO ()
handleError (Query (Command _ responder)) err = putMVar responder (Left err)
handleError (ParametrizedQuery (Command _ responder)) err = putMVar responder (Left err)
flattenError :: Maybe (Either VaultError a) -> Either VaultError a
flattenError Nothing = Left CouldNotReach
flattenError (Just e) = e
-- | Execute a query
query :: VaultOpener -> Vault -> String -> IO (Either VaultError P.QueryResult)
query o v q = flattenError <$> sendMappedCommand f v q
where f = (,) o . Query
-- | Execute a parametrized query
parametrizedQuery :: VaultOpener -> Vault -> String -> [P.Value] -> IO (Either VaultError P.QueryResult)
parametrizedQuery o v q p = flattenError <$> sendMappedCommand f v (q, p)
where f = (,) o . ParametrizedQuery