packages feed

pipes-s3 (empty) → 0.1.0.0

raw patch · 4 files changed

+235/−0 lines, 4 filesdep +awsdep +basedep +bytestringsetup-changed

Dependencies added: aws, base, bytestring, http-client, http-client-tls, pipes, pipes-bytestring, pipes-safe, resourcet, text, transformers

Files

+ LICENSE view
@@ -0,0 +1,30 @@+Copyright (c) 2016, Ben Gamari++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++    * Redistributions of source code must retain the above copyright+      notice, this list of conditions and the following disclaimer.++    * Redistributions in binary form must reproduce the above+      copyright notice, this list of conditions and the following+      disclaimer in the documentation and/or other materials provided+      with the distribution.++    * Neither the name of Ben Gamari nor the names of other+      contributors may be used to endorse or promote products derived+      from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ pipes-s3.cabal view
@@ -0,0 +1,36 @@+name:                pipes-s3+version:             0.1.0.0+synopsis:            A simple interface for streaming data to and from Amazon S3+description:+    This package provides a simple interface for streaming data to and from+    Amazon's S3 cloud storage service with the @pipes@ package.+homepage:            http://github.com/bgamari/pipes-s3+license:             BSD3+license-file:        LICENSE+author:              Ben Gamari+maintainer:          ben@smart-cactus.org+copyright:           (c) 2016 Ben Gamari+category:            Network+build-type:          Simple+cabal-version:       >=1.10++source-repository head+  type:      git+  location:  git://github.com/bgamari/pipes-s3++library+  exposed-modules:     Pipes.Aws.S3+  other-extensions:    OverloadedStrings, GeneralizedNewtypeDeriving, ScopedTypeVariables+  hs-source-dirs:      src+  default-language:    Haskell2010+  build-depends:       base             >=4.7  && <4.10,+                       transformers     >=0.4  && <0.6,+                       bytestring       >=0.10 && <0.11,+                       text             >=1.2  && <1.3,+                       pipes-bytestring >=2.1  && <2.2,+                       pipes-safe       >=2.2  && <2.3,+                       pipes            >=4.1  && <4.3,+                       http-client      >=0.4  && <0.5,+                       http-client-tls  >=0.2  && <0.3,+                       resourcet        >=1.1  && <1.2,+                       aws              >=0.13 && <0.14
+ src/Pipes/Aws/S3.hs view
@@ -0,0 +1,167 @@+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE ScopedTypeVariables #-}++-- | A simple streaming interface to the AWS S3 storage service.+module Pipes.Aws.S3+   ( Bucket(..)+   , Object(..)+     -- * Downloading+   , fromS3+   , fromS3'+     -- ** Convenient re-exports+   , responseBody+     -- * Uploading+   , ChunkSize+   , toS3+   , toS3'+   ) where++import Control.Monad (unless)+import Data.String (IsString)++import qualified Data.ByteString as BS+import           Data.ByteString (ByteString)+import qualified Data.Text as T++import Pipes+import Pipes.Safe+import qualified Pipes.Prelude as PP+import qualified Pipes.ByteString as PBS+import Control.Monad.Trans.Resource+import Control.Monad.IO.Class+import Network.HTTP.Client+import Network.HTTP.Client.TLS+import qualified Aws+import qualified Aws.Core as Aws+import qualified Aws.S3 as S3++-- | An AWS S3 bucket name+newtype Bucket = Bucket T.Text+               deriving (Eq, Ord, Show, Read, IsString)++-- | An AWS S3 object name+newtype Object = Object T.Text+               deriving (Eq, Ord, Show, Read, IsString)++-- | Download an object from S3+--+-- This initiates an S3 download, requiring that the caller provide a way to+-- construct a 'Producer' from the initial 'Response' to the request (allowing+-- the caller to, e.g., handle failure).+--+-- For instance to merely produced the content of the response,+--+-- @+-- 'fromS3' bucket object responseBody+-- @+--+fromS3 :: MonadSafe m+       => Bucket -> Object+       -> (Response (Producer BS.ByteString m ()) -> Producer BS.ByteString m a)+       -> Producer BS.ByteString m a+fromS3 bucket object handler = do+    cfg <- liftIO Aws.baseConfiguration+    fromS3' cfg bucket object handler++-- | Download an object from S3 explicitly specifying an @aws@ 'Aws.Configuration',+-- which provides credentials and logging configuration.+fromS3' :: MonadSafe m+        => Aws.Configuration -> Bucket -> Object+        -> (Response (Producer BS.ByteString m ()) -> Producer BS.ByteString m a)+        -> Producer BS.ByteString m a+fromS3' cfg (Bucket bucket) (Object object) handler = do+    let s3cfg = Aws.defServiceConfig :: S3.S3Configuration Aws.NormalQuery+    mgr <- liftIO $ newManager tlsManagerSettings+    req <- liftIO $ buildRequest cfg s3cfg $ S3.getObject bucket object+    Pipes.Safe.bracket (liftIO $ responseOpen req mgr) (liftIO . responseClose) $ \resp ->+        handler $ resp { responseBody = from $ brRead $ responseBody resp }++-- Stolen from pipes-http+withHTTP :: MonadSafe m+         => Request+         -> Manager+         -> (Response (Producer ByteString m ()) -> m a)+         -> m a+withHTTP req mgr k =+    Pipes.Safe.bracket (liftIO $ responseOpen req mgr) (liftIO . responseClose) k'+  where+    k' resp = do+        let p = (from . brRead . responseBody) resp+        k (resp { responseBody = p})++from :: MonadIO m => IO ByteString -> Producer ByteString m ()+from io = go+  where+    go = do+        bs <- liftIO io+        unless (BS.null bs) $ do+            yield bs+            go+++buildRequest :: (MonadIO m, Aws.Transaction r a)+             => Aws.Configuration+             -> Aws.ServiceConfiguration r Aws.NormalQuery+             -> r+             -> m Request+buildRequest cfg scfg req = do+    Just cred <- Aws.loadCredentialsDefault+    sigData <- liftIO $ Aws.signatureData Aws.Timestamp cred+    let signed = Aws.signQuery req scfg sigData+    liftIO $ Aws.queryToHttpRequest signed++-- | To maintain healthy streaming uploads are performed in a chunked manner.+-- This is the size of the upload chunk size. Due to S3 interface restrictions+-- this must be at least five megabytes.+type ChunkSize = Int++type ETag = T.Text+type PartN = Integer++-- | Upload content to an S3 object explicitly specifying an @aws@+-- 'Aws.Configuration', which provides credentials and logging configuration.+toS3 :: forall m a. MonadIO m+     => ChunkSize -> Bucket -> Object+     -> Producer BS.ByteString m a+     -> m a+toS3 chunkSize bucket object consumer = do+    cfg <- Aws.baseConfiguration+    toS3' cfg chunkSize bucket object consumer++-- | Upload content to an S3 object.+--+-- This internally uses the S3 multi-part upload interface to achieve streaming+-- upload behavior.+toS3' :: forall m a. MonadIO m+      => Aws.Configuration -> ChunkSize -> Bucket -> Object+      -> Producer BS.ByteString m a+      -> m a+toS3' cfg chunkSize (Bucket bucket) (Object object) consumer = do+    let s3cfg = Aws.defServiceConfig :: S3.S3Configuration Aws.NormalQuery+    mgr <- liftIO $ newManager tlsManagerSettings++    resp1 <- liftIO $ runResourceT+             $ Aws.pureAws cfg s3cfg mgr+             $ S3.postInitiateMultipartUpload bucket object+    let uploadId = S3.imurUploadId resp1++    let uploadPart :: (PartN, BS.ByteString) -> m (PartN, ETag)+        uploadPart (partN, content) = do+            resp <- liftIO $ runResourceT+                    $ Aws.pureAws cfg s3cfg mgr+                    $ S3.uploadPart bucket object partN uploadId (RequestBodyBS content)+            return (partN, S3.uprETag resp)++    (parts, res) <- PP.toListM' $ consumer+                              >-> enumFromP 1+                              >-> PP.mapM uploadPart++    resp2 <- liftIO $ runResourceT+             $ Aws.pureAws cfg s3cfg mgr+             $ S3.postCompleteMultipartUpload bucket object uploadId parts+    return res++enumFromP :: (Monad m, Enum i) => i -> Pipe a (i, a) m r+enumFromP = go+  where+    go i = await >>= \x -> yield (i, x) >> go (succ i)