packages feed

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