{-# LANGUAGE FlexibleContexts #-} module Aws.Aws where import Aws.Credentials import Aws.Debug import Aws.Http import Aws.Query import Aws.Response import Aws.S3.Info import Aws.Signature import Aws.SimpleDb.Info import Aws.Transaction import Control.Applicative import Control.Monad.Reader import Data.Attempt (attemptIO) import Data.IORef import Data.Monoid import qualified Control.Exception as E import qualified Data.ByteString as B import qualified Data.Enumerator as En import qualified Network.HTTP.Enumerator as HTTP data Configuration = Configuration { timeInfo :: TimeInfo , credentials :: Credentials , sdbInfo :: SdbInfo , sdbInfoUri :: SdbInfo , s3Info :: S3Info , s3InfoUri :: S3Info } class ConfigurationFetch a where configurationFetch :: Configuration -> a configurationFetchUri :: Configuration -> a configurationFetchUri = configurationFetch instance ConfigurationFetch () where configurationFetch _ = () instance ConfigurationFetch SdbInfo where configurationFetch = sdbInfo configurationFetchUri = sdbInfoUri instance ConfigurationFetch S3Info where configurationFetch = s3Info configurationFetchUri = s3InfoUri baseConfiguration :: MonadIO io => io Configuration baseConfiguration = do Just cr <- loadCredentialsDefault return Configuration { timeInfo = Timestamp , credentials = cr , sdbInfo = sdbHttpsPost sdbUsEast , sdbInfoUri = sdbHttpsGet sdbUsEast , s3Info = s3 HTTP s3EndpointUsClassic False , s3InfoUri = s3 HTTP s3EndpointUsClassic True } -- TODO: better error handling when credentials cannot be loaded debugConfiguration :: MonadIO io => io Configuration debugConfiguration = do c <- baseConfiguration return c { sdbInfo = sdbHttpPost sdbUsEast, sdbInfoUri = sdbHttpGet sdbUsEast } newtype AwsT m a = AwsT { fromAwsT :: ReaderT Configuration m a } type Aws = AwsT IO runAws :: AwsT m a -> Configuration -> m a runAws = runReaderT . fromAwsT runAws' :: MonadIO io => AwsT io a -> io a runAws' a = baseConfiguration >>= runAws a runAwsDebug :: MonadIO io => AwsT io a -> io a runAwsDebug a = debugConfiguration >>= runAws a instance Monad m => Monad (AwsT m) where return = AwsT . return m >>= k = AwsT $ fromAwsT m >>= fromAwsT . k instance MonadIO m => MonadIO (AwsT m) where liftIO = AwsT . liftIO class MonadIO aws => MonadAws aws where configuration :: MonadAws aws => aws Configuration instance MonadIO m => MonadAws (AwsT m) where configuration = AwsT ask aws :: (Transaction r a , ConfigurationFetch (Info r) , MonadAws aws) => r -> aws (Response (ResponseMetadata a) a) aws = unsafeAws unsafeAws :: (MonadAws aws, ResponseIteratee a, Monoid (ResponseMetadata a), SignQuery r, ConfigurationFetch (Info r)) => r -> aws (Response (ResponseMetadata a) a) unsafeAws request = do cfg <- configuration sd <- liftIO $ signatureData <$> timeInfo <*> credentials $ cfg let info = configurationFetch cfg let q = signQuery request info sd debugPrint "String to sign" $ sqStringToSign q let httpRequest = queryToHttpRequest q liftIO $ do metadataRef <- newIORef mempty resp <- attemptIO (id :: E.SomeException -> E.SomeException) $ HTTP.withManager $ En.run_ . HTTP.httpRedirect httpRequest (responseIteratee metadataRef) metadata <- readIORef metadataRef return $ Response metadata resp awsUri :: (SignQuery request , ConfigurationFetch (Info request) , MonadAws aws) => request -> aws B.ByteString awsUri request = do cfg <- configuration let ti = timeInfo cfg cr = credentials cfg info = configurationFetchUri cfg sd <- liftIO $ signatureData ti cr let q = signQuery request info sd debugPrint "String to sign" $ sqStringToSign q return $ queryToUri q