Switch to unliftio and lts-11.1 (#83)

- Also add .stylish-haskell.yaml
- Re-implements `limitedMapConcurrently` using STM
- Dependencies clean up in cabal file
- Fix shadow warnings and other build warnings
This commit is contained in:
Aditya Manthramurthy 2018-03-26 14:04:25 -07:00 committed by GitHub
parent 38b67b4dab
commit 51b3e51d46
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 427 additions and 165 deletions

233
.stylish-haskell.yaml Normal file
View File

@ -0,0 +1,233 @@
# stylish-haskell configuration file
# ==================================
# The stylish-haskell tool is mainly configured by specifying steps. These steps
# are a list, so they have an order, and one specific step may appear more than
# once (if needed). Each file is processed by these steps in the given order.
steps:
# Convert some ASCII sequences to their Unicode equivalents. This is disabled
# by default.
# - unicode_syntax:
# # In order to make this work, we also need to insert the UnicodeSyntax
# # language pragma. If this flag is set to true, we insert it when it's
# # not already present. You may want to disable it if you configure
# # language extensions using some other method than pragmas. Default:
# # true.
# add_language_pragma: true
# Align the right hand side of some elements. This is quite conservative
# and only applies to statements where each element occupies a single
# line.
- simple_align:
cases: true
top_level_patterns: true
records: true
# Import cleanup
- imports:
# There are different ways we can align names and lists.
#
# - global: Align the import names and import list throughout the entire
# file.
#
# - file: Like global, but don't add padding when there are no qualified
# imports in the file.
#
# - group: Only align the imports per group (a group is formed by adjacent
# import lines).
#
# - none: Do not perform any alignment.
#
# Default: global.
align: global
# The following options affect only import list alignment.
#
# List align has following options:
#
# - after_alias: Import list is aligned with end of import including
# 'as' and 'hiding' keywords.
#
# > import qualified Data.List as List (concat, foldl, foldr, head,
# > init, last, length)
#
# - with_alias: Import list is aligned with start of alias or hiding.
#
# > import qualified Data.List as List (concat, foldl, foldr, head,
# > init, last, length)
#
# - new_line: Import list starts always on new line.
#
# > import qualified Data.List as List
# > (concat, foldl, foldr, head, init, last, length)
#
# Default: after_alias
list_align: after_alias
# Right-pad the module names to align imports in a group:
#
# - true: a little more readable
#
# > import qualified Data.List as List (concat, foldl, foldr,
# > init, last, length)
# > import qualified Data.List.Extra as List (concat, foldl, foldr,
# > init, last, length)
#
# - false: diff-safe
#
# > import qualified Data.List as List (concat, foldl, foldr, init,
# > last, length)
# > import qualified Data.List.Extra as List (concat, foldl, foldr,
# > init, last, length)
#
# Default: true
pad_module_names: true
# Long list align style takes effect when import is too long. This is
# determined by 'columns' setting.
#
# - inline: This option will put as much specs on same line as possible.
#
# - new_line: Import list will start on new line.
#
# - new_line_multiline: Import list will start on new line when it's
# short enough to fit to single line. Otherwise it'll be multiline.
#
# - multiline: One line per import list entry.
# Type with constructor list acts like single import.
#
# > import qualified Data.Map as M
# > ( empty
# > , singleton
# > , ...
# > , delete
# > )
#
# Default: inline
long_list_align: inline
# Align empty list (importing instances)
#
# Empty list align has following options
#
# - inherit: inherit list_align setting
#
# - right_after: () is right after the module name:
#
# > import Vector.Instances ()
#
# Default: inherit
empty_list_align: inherit
# List padding determines indentation of import list on lines after import.
# This option affects 'long_list_align'.
#
# - <integer>: constant value
#
# - module_name: align under start of module name.
# Useful for 'file' and 'group' align settings.
list_padding: 4
# Separate lists option affects formatting of import list for type
# or class. The only difference is single space between type and list
# of constructors, selectors and class functions.
#
# - true: There is single space between Foldable type and list of it's
# functions.
#
# > import Data.Foldable (Foldable (fold, foldl, foldMap))
#
# - false: There is no space between Foldable type and list of it's
# functions.
#
# > import Data.Foldable (Foldable(fold, foldl, foldMap))
#
# Default: true
separate_lists: true
# Space surround option affects formatting of import lists on a single
# line. The only difference is single space after the initial
# parenthesis and a single space before the terminal parenthesis.
#
# - true: There is single space associated with the enclosing
# parenthesis.
#
# > import Data.Foo ( foo )
#
# - false: There is no space associated with the enclosing parenthesis
#
# > import Data.Foo (foo)
#
# Default: false
space_surround: false
# Language pragmas
- language_pragmas:
# We can generate different styles of language pragma lists.
#
# - vertical: Vertical-spaced language pragmas, one per line.
#
# - compact: A more compact style.
#
# - compact_line: Similar to compact, but wrap each line with
# `{-#LANGUAGE #-}'.
#
# Default: vertical.
style: vertical
# Align affects alignment of closing pragma brackets.
#
# - true: Brackets are aligned in same column.
#
# - false: Brackets are not aligned together. There is only one space
# between actual import and closing bracket.
#
# Default: true
align: true
# stylish-haskell can detect redundancy of some language pragmas. If this
# is set to true, it will remove those redundant pragmas. Default: true.
remove_redundant: true
# Replace tabs by spaces. This is disabled by default.
# - tabs:
# # Number of spaces to use for each tab. Default: 8, as specified by the
# # Haskell report.
# spaces: 8
# Remove trailing whitespace
- trailing_whitespace: {}
# A common setting is the number of columns (parts of) code will be wrapped
# to. Different steps take this into account. Default: 80.
columns: 80
# By default, line endings are converted according to the OS. You can override
# preferred format here.
#
# - native: Native newline format. CRLF on Windows, LF on other OSes.
#
# - lf: Convert to LF ("\n").
#
# - crlf: Convert to CRLF ("\r\n").
#
# Default: native.
newline: native
# Sometimes, language extensions are specified in a cabal file or from the
# command line instead of using language pragmas in the file. stylish-haskell
# needs to be aware of these, so it can parse the file correctly.
#
# No language extensions are enabled by default.
language_extensions:
- BangPatterns
- FlexibleContexts
- FlexibleInstances
- MultiParamTypeClasses
- MultiWayIf
- NoImplicitPrelude
- OverloadedStrings
- RankNTypes
- ScopedTypeVariables
- TupleSections
- TypeFamilies

View File

@ -46,12 +46,10 @@ library
build-depends: base >= 4.7 && < 5 build-depends: base >= 4.7 && < 5
, protolude >= 0.1.6 , protolude >= 0.1.6
, aeson , aeson
, async
, base64-bytestring , base64-bytestring
, bytestring , bytestring
, case-insensitive , case-insensitive
, conduit , conduit
, conduit-combinators
, conduit-extra , conduit-extra
, containers , containers
, cryptonite , cryptonite
@ -62,22 +60,19 @@ library
, http-client , http-client
, http-conduit , http-conduit
, http-types , http-types
, lifted-async
, lifted-base
, memory , memory
, monad-control
, resourcet , resourcet
, text , text
, text-format , text-format
, time , time
, transformers , transformers
, transformers-base , unliftio
, vector , unliftio-core
, xml-conduit , xml-conduit
default-language: Haskell2010 default-language: Haskell2010
default-extensions: FlexibleContexts default-extensions: BangPatterns
, FlexibleContexts
, FlexibleInstances , FlexibleInstances
, BangPatterns
, MultiParamTypeClasses , MultiParamTypeClasses
, MultiWayIf , MultiWayIf
, NoImplicitPrelude , NoImplicitPrelude
@ -100,12 +95,12 @@ test-suite minio-hs-live-server-test
default-extensions: BangPatterns default-extensions: BangPatterns
, FlexibleContexts , FlexibleContexts
, FlexibleInstances , FlexibleInstances
, OverloadedStrings
, NoImplicitPrelude
, MultiParamTypeClasses , MultiParamTypeClasses
, MultiWayIf , MultiWayIf
, ScopedTypeVariables , NoImplicitPrelude
, OverloadedStrings
, RankNTypes , RankNTypes
, ScopedTypeVariables
, TupleSections , TupleSections
, TypeFamilies , TypeFamilies
other-modules: Lib.Prelude other-modules: Lib.Prelude
@ -133,12 +128,10 @@ test-suite minio-hs-live-server-test
, minio-hs , minio-hs
, protolude >= 0.1.6 , protolude >= 0.1.6
, aeson , aeson
, async
, base64-bytestring , base64-bytestring
, bytestring , bytestring
, case-insensitive , case-insensitive
, conduit , conduit
, conduit-combinators
, conduit-extra , conduit-extra
, containers , containers
, cryptonite , cryptonite
@ -150,10 +143,7 @@ test-suite minio-hs-live-server-test
, http-client , http-client
, http-conduit , http-conduit
, http-types , http-types
, lifted-async
, lifted-base
, memory , memory
, monad-control
, QuickCheck , QuickCheck
, resourcet , resourcet
, tasty , tasty
@ -165,8 +155,8 @@ test-suite minio-hs-live-server-test
, text-format , text-format
, time , time
, transformers , transformers
, transformers-base , unliftio
, vector , unliftio-core
, xml-conduit , xml-conduit
if !flag(live-test) if !flag(live-test)
buildable: False buildable: False
@ -179,12 +169,10 @@ test-suite minio-hs-test
, minio-hs , minio-hs
, protolude >= 0.1.6 , protolude >= 0.1.6
, aeson , aeson
, async
, base64-bytestring , base64-bytestring
, bytestring , bytestring
, case-insensitive , case-insensitive
, conduit , conduit
, conduit-combinators
, conduit-extra , conduit-extra
, containers , containers
, cryptonite , cryptonite
@ -192,14 +180,10 @@ test-suite minio-hs-test
, data-default , data-default
, directory , directory
, exceptions , exceptions
, filepath
, http-client , http-client
, http-conduit , http-conduit
, http-types , http-types
, lifted-async
, lifted-base
, memory , memory
, monad-control
, QuickCheck , QuickCheck
, resourcet , resourcet
, tasty , tasty
@ -211,20 +195,20 @@ test-suite minio-hs-test
, text-format , text-format
, time , time
, transformers , transformers
, transformers-base , unliftio
, vector , unliftio-core
, xml-conduit , xml-conduit
ghc-options: -Wall -threaded -rtsopts -with-rtsopts=-N ghc-options: -Wall -threaded -rtsopts -with-rtsopts=-N
default-language: Haskell2010 default-language: Haskell2010
default-extensions: BangPatterns default-extensions: BangPatterns
, FlexibleContexts , FlexibleContexts
, FlexibleInstances , FlexibleInstances
, OverloadedStrings
, NoImplicitPrelude
, MultiParamTypeClasses , MultiParamTypeClasses
, MultiWayIf , MultiWayIf
, ScopedTypeVariables , NoImplicitPrelude
, OverloadedStrings
, RankNTypes , RankNTypes
, ScopedTypeVariables
, TupleSections , TupleSections
, TypeFamilies , TypeFamilies
other-modules: Lib.Prelude other-modules: Lib.Prelude

View File

@ -179,7 +179,6 @@ import qualified Data.Conduit as C
import qualified Data.Conduit.Binary as CB import qualified Data.Conduit.Binary as CB
import qualified Data.Conduit.Combinators as CC import qualified Data.Conduit.Combinators as CC
import Data.Default (def) import Data.Default (def)
import qualified Data.Map as Map
import Lib.Prelude import Lib.Prelude
@ -189,6 +188,7 @@ import Network.Minio.Errors
import Network.Minio.ListOps import Network.Minio.ListOps
import Network.Minio.PutObject import Network.Minio.PutObject
import Network.Minio.S3API import Network.Minio.S3API
import Network.Minio.Utils
-- | Lists buckets. -- | Lists buckets.
listBuckets :: Minio [BucketInfo] listBuckets :: Minio [BucketInfo]
@ -200,7 +200,7 @@ listBuckets = getService
fGetObject :: Bucket -> Object -> FilePath -> GetObjectOptions -> Minio () fGetObject :: Bucket -> Object -> FilePath -> GetObjectOptions -> Minio ()
fGetObject bucket object fp opts = do fGetObject bucket object fp opts = do
src <- getObject bucket object opts src <- getObject bucket object opts
src C.$$+- CB.sinkFileCautious fp C.connect src $ CB.sinkFileCautious fp
-- | Upload the given file to the given object. -- | Upload the given file to the given object.
fPutObject :: Bucket -> Object -> FilePath fPutObject :: Bucket -> Object -> FilePath
@ -212,7 +212,7 @@ fPutObject bucket object f opts =
-- known; this helps the library select optimal part sizes to perform -- known; this helps the library select optimal part sizes to perform
-- a multipart upload. If not specified, it is assumed that the object -- a multipart upload. If not specified, it is assumed that the object
-- can be potentially 5TiB and selects multipart sizes appropriately. -- can be potentially 5TiB and selects multipart sizes appropriately.
putObject :: Bucket -> Object -> C.Producer Minio ByteString putObject :: Bucket -> Object -> C.ConduitM () ByteString Minio ()
-> Maybe Int64 -> PutObjectOptions -> Minio () -> Maybe Int64 -> PutObjectOptions -> Minio ()
putObject bucket object src sizeMay opts = putObject bucket object src sizeMay opts =
void $ putObjectInternal bucket object opts $ ODStream src sizeMay void $ putObjectInternal bucket object opts $ ODStream src sizeMay
@ -223,15 +223,18 @@ putObject bucket object src sizeMay opts =
-- copy operation if the new object is to be greater than 5GiB in -- copy operation if the new object is to be greater than 5GiB in
-- size. -- size.
copyObject :: DestinationInfo -> SourceInfo -> Minio () copyObject :: DestinationInfo -> SourceInfo -> Minio ()
copyObject dstInfo srcInfo = void $ copyObjectInternal (dstBucket dstInfo) (dstObject dstInfo) srcInfo copyObject dstInfo srcInfo = void $ copyObjectInternal (dstBucket dstInfo)
(dstObject dstInfo) srcInfo
-- | Remove an object from the object store. -- | Remove an object from the object store.
removeObject :: Bucket -> Object -> Minio () removeObject :: Bucket -> Object -> Minio ()
removeObject = deleteObject removeObject = deleteObject
-- | Get an object from the object store as a resumable source (conduit). -- | Get an object from the object store as a resumable source (conduit).
getObject :: Bucket -> Object -> GetObjectOptions -> Minio (C.ResumableSource Minio ByteString) getObject :: Bucket -> Object -> GetObjectOptions
getObject bucket object opts = snd <$> getObject' bucket object [] (gooToHeaders opts) -> Minio (C.ConduitM () ByteString Minio ())
getObject bucket object opts = snd <$> getObject' bucket object []
(gooToHeaders opts)
-- | Get an object's metadata from the object store. -- | Get an object's metadata from the object store.
statObject :: Bucket -> Object -> Minio ObjectInfo statObject :: Bucket -> Object -> Minio ObjectInfo
@ -245,21 +248,21 @@ makeBucket :: Bucket -> Maybe Region -> Minio ()
makeBucket bucket regionMay = do makeBucket bucket regionMay = do
region <- maybe (asks $ connectRegion . mcConnInfo) return regionMay region <- maybe (asks $ connectRegion . mcConnInfo) return regionMay
putBucket bucket region putBucket bucket region
modify (Map.insert bucket region) addToRegionCache bucket region
-- | Removes a bucket from the object store. -- | Removes a bucket from the object store.
removeBucket :: Bucket -> Minio () removeBucket :: Bucket -> Minio ()
removeBucket bucket = do removeBucket bucket = do
deleteBucket bucket deleteBucket bucket
modify (Map.delete bucket) deleteFromRegionCache bucket
-- | Query the object store if a given bucket is present. -- | Query the object store if a given bucket is present.
bucketExists :: Bucket -> Minio Bool bucketExists :: Bucket -> Minio Bool
bucketExists = headBucket bucketExists = headBucket
-- | Removes an ongoing multipart upload of an object. -- | Removes an ongoing multipart upload of an object.
removeIncompleteUpload :: Bucket -> Object -> Minio () removeIncompleteUpload :: Bucket -> Object -> Minio ()
removeIncompleteUpload bucket object = do removeIncompleteUpload bucket object = do
uploads <- listIncompleteUploads bucket (Just object) False C.$$ CC.sinkList uploads <- C.runConduit $ listIncompleteUploads bucket (Just object) False
C..| CC.sinkList
mapM_ (abortMultipartUpload bucket object) (uiUploadId <$> uploads) mapM_ (abortMultipartUpload bucket object) (uiUploadId <$> uploads)

View File

@ -15,8 +15,7 @@
-- --
module Network.Minio.API module Network.Minio.API
( ( connect
connect
, RequestInfo(..) , RequestInfo(..)
, runMinio , runMinio
, executeRequest , executeRequest
@ -86,10 +85,10 @@ getLocation bucket = do
discoverRegion :: RequestInfo -> Minio (Maybe Region) discoverRegion :: RequestInfo -> Minio (Maybe Region)
discoverRegion ri = runMaybeT $ do discoverRegion ri = runMaybeT $ do
bucket <- MaybeT $ return $ riBucket ri bucket <- MaybeT $ return $ riBucket ri
regionMay <- gets (Map.lookup bucket) regionMay <- lift $ lookupRegionCache bucket
maybe (do maybe (do
l <- lift $ getLocation bucket l <- lift $ getLocation bucket
modify $ Map.insert bucket l lift $ addToRegionCache bucket l
return l return l
) return regionMay ) return regionMay
@ -161,7 +160,7 @@ executeRequest ri = do
mkStreamRequest :: RequestInfo mkStreamRequest :: RequestInfo
-> Minio (Response (C.ResumableSource Minio ByteString)) -> Minio (Response (C.ConduitM () ByteString Minio ()))
mkStreamRequest ri = do mkStreamRequest ri = do
req <- buildRequest ri req <- buildRequest ri
mgr <- asks mcConnManager mgr <- asks mcConnManager

View File

@ -18,9 +18,11 @@
{-# LANGUAGE TypeFamilies #-} {-# LANGUAGE TypeFamilies #-}
module Network.Minio.Data where module Network.Minio.Data where
import Control.Monad.Base import Control.Concurrent.MVar (MVar)
import qualified Control.Concurrent.MVar as M
import qualified Control.Monad.Catch as MC import qualified Control.Monad.Catch as MC
import Control.Monad.Trans.Control import Control.Monad.IO.Unlift (MonadUnliftIO, UnliftIO (..),
askUnliftIO, withUnliftIO)
import Control.Monad.Trans.Resource import Control.Monad.Trans.Resource
import qualified Data.ByteString as B import qualified Data.ByteString as B
@ -195,7 +197,7 @@ data PutObjectOptions = PutObjectOptions {
, pooStorageClass :: Maybe Text , pooStorageClass :: Maybe Text
, pooUserMetadata :: [(Text, Text)] , pooUserMetadata :: [(Text, Text)]
, pooNumThreads :: Maybe Word , pooNumThreads :: Maybe Word
} deriving (Show, Eq) } deriving (Show, Eq)
-- Provide a default instance -- Provide a default instance
instance Default PutObjectOptions where instance Default PutObjectOptions where
@ -498,7 +500,7 @@ type UrlExpiry = Int
type RegionMap = Map.Map Bucket Region type RegionMap = Map.Map Bucket Region
newtype Minio a = Minio { newtype Minio a = Minio {
unMinio :: ReaderT MinioConn (StateT RegionMap (ResourceT IO)) a unMinio :: ReaderT MinioConn (ResourceT IO) a
} }
deriving ( deriving (
Functor Functor
@ -506,38 +508,38 @@ newtype Minio a = Minio {
, Monad , Monad
, MonadIO , MonadIO
, MonadReader MinioConn , MonadReader MinioConn
, MonadState RegionMap
, MonadThrow , MonadThrow
, MonadCatch , MonadCatch
, MonadBase IO
, MonadResource , MonadResource
) )
instance MonadBaseControl IO Minio where instance MonadUnliftIO Minio where
type StM Minio a = (a, RegionMap) askUnliftIO = Minio $ ReaderT $ \r ->
liftBaseWith f = Minio $ liftBaseWith $ \q -> f (q . unMinio) withUnliftIO $ \u ->
restoreM = Minio . restoreM return (UnliftIO (unliftIO u . flip runReaderT r . unMinio))
-- | MinioConn holds connection info and a connection pool -- | MinioConn holds connection info and a connection pool
data MinioConn = MinioConn { data MinioConn = MinioConn
mcConnInfo :: ConnectInfo { mcConnInfo :: ConnectInfo
, mcConnManager :: NC.Manager , mcConnManager :: NC.Manager
, mcRegionMap :: MVar RegionMap
} }
-- | Takes connection information and returns a connection object to -- | Takes connection information and returns a connection object to
-- be passed to 'runMinio' -- be passed to 'runMinio'
connect :: ConnectInfo -> IO MinioConn connect :: ConnectInfo -> IO MinioConn
connect ci = do connect ci = do
let settings = bool defaultManagerSettings NC.tlsManagerSettings $ let settings | connectIsSecure ci = NC.tlsManagerSettings
connectIsSecure ci | otherwise = defaultManagerSettings
mgr <- NC.newManager settings mgr <- NC.newManager settings
return $ MinioConn ci mgr rMapMVar <- M.newMVar Map.empty
return $ MinioConn ci mgr rMapMVar
-- | Run the Minio action and return the result or an error. -- | Run the Minio action and return the result or an error.
runMinio :: ConnectInfo -> Minio a -> IO (Either MinioErr a) runMinio :: ConnectInfo -> Minio a -> IO (Either MinioErr a)
runMinio ci m = do runMinio ci m = do
conn <- liftIO $ connect ci conn <- liftIO $ connect ci
runResourceT . flip evalStateT Map.empty . flip runReaderT conn . unMinio $ runResourceT . flip runReaderT conn . unMinio $
fmap Right m `MC.catches` fmap Right m `MC.catches`
[ MC.Handler handlerServiceErr [ MC.Handler handlerServiceErr
, MC.Handler handlerHE , MC.Handler handlerHE

View File

@ -28,37 +28,38 @@ module Network.Minio.Data.Crypto
, digestToBase16 , digestToBase16
) where ) where
import Crypto.Hash (SHA256(..), MD5(..), hashWith, Digest) import Crypto.Hash (Digest, MD5 (..), SHA256 (..),
import Crypto.Hash.Conduit (sinkHash) hashWith)
import Crypto.MAC.HMAC (hmac, HMAC) import Crypto.Hash.Conduit (sinkHash)
import Data.ByteArray (ByteArrayAccess, convert) import Crypto.MAC.HMAC (HMAC, hmac)
import Data.ByteArray.Encoding (convertToBase, Base(Base16)) import Data.ByteArray (ByteArrayAccess, convert)
import qualified Data.Conduit as C import Data.ByteArray.Encoding (Base (Base16), convertToBase)
import qualified Data.Conduit as C
import Lib.Prelude import Lib.Prelude
hashSHA256 :: ByteString -> ByteString hashSHA256 :: ByteString -> ByteString
hashSHA256 = digestToBase16 . hashWith SHA256 hashSHA256 = digestToBase16 . hashWith SHA256
hashSHA256FromSource :: Monad m => C.Producer m ByteString -> m ByteString hashSHA256FromSource :: Monad m => C.ConduitM () ByteString m () -> m ByteString
hashSHA256FromSource src = do hashSHA256FromSource src = do
digest <- src C.$$ sinkSHA256Hash digest <- C.connect src sinkSHA256Hash
return $ digestToBase16 digest return $ digestToBase16 digest
where where
-- To help with type inference -- To help with type inference
sinkSHA256Hash :: Monad m => C.Consumer ByteString m (Digest SHA256) sinkSHA256Hash :: Monad m => C.ConduitM ByteString Void m (Digest SHA256)
sinkSHA256Hash = sinkHash sinkSHA256Hash = sinkHash
hashMD5 :: ByteString -> ByteString hashMD5 :: ByteString -> ByteString
hashMD5 = digestToBase16 . hashWith MD5 hashMD5 = digestToBase16 . hashWith MD5
hashMD5FromSource :: Monad m => C.Producer m ByteString -> m ByteString hashMD5FromSource :: Monad m => C.ConduitM () ByteString m () -> m ByteString
hashMD5FromSource src = do hashMD5FromSource src = do
digest <- src C.$$ sinkMD5Hash digest <- C.connect src sinkMD5Hash
return $ digestToBase16 digest return $ digestToBase16 digest
where where
-- To help with type inference -- To help with type inference
sinkMD5Hash :: Monad m => C.Consumer ByteString m (Digest MD5) sinkMD5Hash :: Monad m => C.ConduitM ByteString Void m (Digest MD5)
sinkMD5Hash = sinkHash sinkMD5Hash = sinkHash
hmacSHA256 :: ByteString -> ByteString -> HMAC SHA256 hmacSHA256 :: ByteString -> ByteString -> HMAC SHA256

View File

@ -16,9 +16,9 @@
module Network.Minio.ListOps where module Network.Minio.ListOps where
import qualified Data.Conduit as C import qualified Data.Conduit as C
import qualified Data.Conduit.Combinators as CC import qualified Data.Conduit.Combinators as CC
import qualified Data.Conduit.List as CL import qualified Data.Conduit.List as CL
import Lib.Prelude import Lib.Prelude
@ -27,10 +27,10 @@ import Network.Minio.S3API
-- | List objects in a bucket matching the given prefix. If recurse is -- | List objects in a bucket matching the given prefix. If recurse is
-- set to True objects matching prefix are recursively listed. -- set to True objects matching prefix are recursively listed.
listObjects :: Bucket -> Maybe Text -> Bool -> C.Producer Minio ObjectInfo listObjects :: Bucket -> Maybe Text -> Bool -> C.ConduitM () ObjectInfo Minio ()
listObjects bucket prefix recurse = loop Nothing listObjects bucket prefix recurse = loop Nothing
where where
loop :: Maybe Text -> C.Producer Minio ObjectInfo loop :: Maybe Text -> C.ConduitM () ObjectInfo Minio ()
loop nextToken = do loop nextToken = do
let let
delimiter = bool (Just "/") Nothing recurse delimiter = bool (Just "/") Nothing recurse
@ -42,10 +42,11 @@ listObjects bucket prefix recurse = loop Nothing
-- | List objects in a bucket matching the given prefix. If recurse is -- | List objects in a bucket matching the given prefix. If recurse is
-- set to True objects matching prefix are recursively listed. -- set to True objects matching prefix are recursively listed.
listObjectsV1 :: Bucket -> Maybe Text -> Bool -> C.Producer Minio ObjectInfo listObjectsV1 :: Bucket -> Maybe Text -> Bool
-> C.ConduitM () ObjectInfo Minio ()
listObjectsV1 bucket prefix recurse = loop Nothing listObjectsV1 bucket prefix recurse = loop Nothing
where where
loop :: Maybe Text -> C.Producer Minio ObjectInfo loop :: Maybe Text -> C.ConduitM () ObjectInfo Minio ()
loop nextMarker = do loop nextMarker = do
let let
delimiter = bool (Just "/") Nothing recurse delimiter = bool (Just "/") Nothing recurse
@ -59,10 +60,10 @@ listObjectsV1 bucket prefix recurse = loop Nothing
-- recurse is set to True incomplete uploads for the given prefix are -- recurse is set to True incomplete uploads for the given prefix are
-- recursively listed. -- recursively listed.
listIncompleteUploads :: Bucket -> Maybe Text -> Bool listIncompleteUploads :: Bucket -> Maybe Text -> Bool
-> C.Producer Minio UploadInfo -> C.ConduitM () UploadInfo Minio ()
listIncompleteUploads bucket prefix recurse = loop Nothing Nothing listIncompleteUploads bucket prefix recurse = loop Nothing Nothing
where where
loop :: Maybe Text -> Maybe Text -> C.Producer Minio UploadInfo loop :: Maybe Text -> Maybe Text -> C.ConduitM () UploadInfo Minio ()
loop nextKeyMarker nextUploadIdMarker = do loop nextKeyMarker nextUploadIdMarker = do
let let
delimiter = bool (Just "/") Nothing recurse delimiter = bool (Just "/") Nothing recurse
@ -71,7 +72,8 @@ listIncompleteUploads bucket prefix recurse = loop Nothing Nothing
nextKeyMarker nextUploadIdMarker Nothing nextKeyMarker nextUploadIdMarker Nothing
aggrSizes <- lift $ forM (lurUploads res) $ \(uKey, uId, _) -> do aggrSizes <- lift $ forM (lurUploads res) $ \(uKey, uId, _) -> do
partInfos <- listIncompleteParts bucket uKey uId C.$$ CC.sinkList partInfos <- C.runConduit $ listIncompleteParts bucket uKey uId
C..| CC.sinkList
return $ foldl (\sizeSofar p -> opiSize p + sizeSofar) 0 partInfos return $ foldl (\sizeSofar p -> opiSize p + sizeSofar) 0 partInfos
CL.sourceList $ CL.sourceList $
@ -86,10 +88,10 @@ listIncompleteUploads bucket prefix recurse = loop Nothing Nothing
-- | List object parts of an ongoing multipart upload for given -- | List object parts of an ongoing multipart upload for given
-- bucket, object and uploadId. -- bucket, object and uploadId.
listIncompleteParts :: Bucket -> Object -> UploadId listIncompleteParts :: Bucket -> Object -> UploadId
-> C.Producer Minio ObjectPartInfo -> C.ConduitM () ObjectPartInfo Minio ()
listIncompleteParts bucket object uploadId = loop Nothing listIncompleteParts bucket object uploadId = loop Nothing
where where
loop :: Maybe Text -> C.Producer Minio ObjectPartInfo loop :: Maybe Text -> C.ConduitM () ObjectPartInfo Minio ()
loop nextPartMarker = do loop nextPartMarker = do
res <- lift $ listIncompleteParts' bucket object uploadId Nothing res <- lift $ listIncompleteParts' bucket object uploadId Nothing
nextPartMarker nextPartMarker

View File

@ -46,9 +46,14 @@ import Network.Minio.Utils
-- For streams also, a size may be provided. This is useful to limit -- For streams also, a size may be provided. This is useful to limit
-- the input - if it is not provided, upload will continue until the -- the input - if it is not provided, upload will continue until the
-- stream ends or the object reaches `maxObjectsize` size. -- stream ends or the object reaches `maxObjectsize` size.
data ObjectData m = data ObjectData m
ODFile FilePath (Maybe Int64) -- ^ Takes filepath and optional size. = ODFile FilePath (Maybe Int64) -- ^ Takes filepath and optional
| ODStream (C.Producer m ByteString) (Maybe Int64) -- ^ Pass size in bytes as maybe if known. -- size.
| ODStream (C.ConduitM () ByteString m ()) (Maybe Int64) -- ^ Pass
-- size
-- (bytes)
-- if
-- known.
-- | Put an object from ObjectData. This high-level API handles -- | Put an object from ObjectData. This high-level API handles
-- objects of all sizes, and even if the object size is unknown. -- objects of all sizes, and even if the object size is unknown.
@ -108,7 +113,7 @@ parallelMultipartUpload b o opts filePath size = do
-- | Upload multipart object from conduit source sequentially -- | Upload multipart object from conduit source sequentially
sequentialMultipartUpload :: Bucket -> Object -> PutObjectOptions sequentialMultipartUpload :: Bucket -> Object -> PutObjectOptions
-> Maybe Int64 -> Maybe Int64
-> C.Producer Minio ByteString -> C.ConduitM () ByteString Minio ()
-> Minio ETag -> Minio ETag
sequentialMultipartUpload b o opts sizeMay src = do sequentialMultipartUpload b o opts sizeMay src = do
-- get a new upload id. -- get a new upload id.
@ -117,11 +122,12 @@ sequentialMultipartUpload b o opts sizeMay src = do
-- upload parts in loop -- upload parts in loop
let partSizes = selectPartSizes $ maybe maxObjectSize identity sizeMay let partSizes = selectPartSizes $ maybe maxObjectSize identity sizeMay
(pnums, _, sizes) = List.unzip3 partSizes (pnums, _, sizes) = List.unzip3 partSizes
uploadedParts <- src uploadedParts <- C.runConduit
$ src
C..| chunkBSConduit sizes C..| chunkBSConduit sizes
C..| CL.map PayloadBS C..| CL.map PayloadBS
C..| uploadPart' uploadId pnums C..| uploadPart' uploadId pnums
C.$$ CC.sinkList C..| CC.sinkList
-- complete multipart upload -- complete multipart upload
completeMultipartUpload b o uploadId uploadedParts completeMultipartUpload b o uploadId uploadedParts

View File

@ -114,7 +114,7 @@ getService = do
-- | GET an object from the service and return the response headers -- | GET an object from the service and return the response headers
-- and a conduit source for the object content -- and a conduit source for the object content
getObject' :: Bucket -> Object -> HT.Query -> [HT.Header] getObject' :: Bucket -> Object -> HT.Query -> [HT.Header]
-> Minio ([HT.Header], C.ResumableSource Minio ByteString) -> Minio ([HT.Header], C.ConduitM () ByteString Minio ())
getObject' bucket object queryParams headers = do getObject' bucket object queryParams headers = do
resp <- mkStreamRequest reqInfo resp <- mkStreamRequest reqInfo
return (NC.responseHeaders resp, NC.responseBody resp) return (NC.responseHeaders resp, NC.responseBody resp)

View File

@ -16,71 +16,71 @@
module Network.Minio.Utils where module Network.Minio.Utils where
import qualified Control.Concurrent.Async.Lifted as A import qualified Control.Monad.Catch as MC
import qualified Control.Concurrent.QSem.Lifted as Q import Control.Monad.IO.Unlift (MonadUnliftIO)
import qualified Control.Exception.Lifted as ExL import qualified Control.Monad.Trans.Resource as R
import qualified Control.Monad.Catch as MC import qualified Data.ByteString as B
import qualified Control.Monad.Trans.Resource as R import qualified Data.ByteString.Lazy as LB
import Data.CaseInsensitive (mk, original)
import qualified Data.Map as Map import qualified Data.Conduit as C
import qualified Data.ByteString as B import qualified Data.Conduit.Binary as CB
import qualified Data.ByteString.Lazy as LB import qualified Data.List as List
import Data.CaseInsensitive (mk) import qualified Data.Map as Map
import Data.CaseInsensitive (original) import qualified Data.Text as T
import qualified Data.Conduit as C import Data.Text.Encoding.Error (lenientDecode)
import qualified Data.Conduit.Binary as CB import Data.Text.Read (decimal)
import qualified Data.List as List import Data.Time (defaultTimeLocale, parseTimeM,
import qualified Data.Text as T rfc822DateFormat)
import Data.Text.Encoding.Error (lenientDecode) import Network.HTTP.Conduit (Response)
import Data.Text.Read (decimal) import qualified Network.HTTP.Conduit as NC
import Data.Time import qualified Network.HTTP.Types as HT
import Network.HTTP.Conduit (Response) import qualified Network.HTTP.Types.Header as Hdr
import qualified Network.HTTP.Conduit as NC import qualified System.IO as IO
import qualified Network.HTTP.Types as HT import qualified UnliftIO.Async as A
import qualified Network.HTTP.Types.Header as Hdr import qualified UnliftIO.Exception as UEx
import qualified System.IO as IO import qualified UnliftIO.MVar as UM
import qualified UnliftIO.STM as U
import Lib.Prelude import Lib.Prelude
import Network.Minio.Data import Network.Minio.Data
import Network.Minio.Data.ByteString import Network.Minio.Data.ByteString
import Network.Minio.XmlParser (parseErrResponse) import Network.Minio.XmlParser (parseErrResponse)
allocateReadFile :: (R.MonadResource m, R.MonadResourceBase m, MonadCatch m) allocateReadFile :: (MonadUnliftIO m, R.MonadResource m, MonadCatch m)
=> FilePath -> m (R.ReleaseKey, Handle) => FilePath -> m (R.ReleaseKey, Handle)
allocateReadFile fp = do allocateReadFile fp = do
(rk, hdlE) <- R.allocate (openReadFile fp) cleanup (rk, hdlE) <- R.allocate (openReadFile fp) cleanup
either (\(e :: IOException) -> throwM e) (return . (rk,)) hdlE either (\(e :: IOException) -> throwM e) (return . (rk,)) hdlE
where where
openReadFile f = ExL.try $ IO.openBinaryFile f IO.ReadMode openReadFile f = UEx.try $ IO.openBinaryFile f IO.ReadMode
cleanup = either (const $ return ()) IO.hClose cleanup = either (const $ return ()) IO.hClose
-- | Queries the file size from the handle. Catches any file operation -- | Queries the file size from the handle. Catches any file operation
-- exceptions and returns Nothing instead. -- exceptions and returns Nothing instead.
getFileSize :: (R.MonadResourceBase m, R.MonadResource m) getFileSize :: (MonadUnliftIO m, R.MonadResource m)
=> Handle -> m (Maybe Int64) => Handle -> m (Maybe Int64)
getFileSize h = do getFileSize h = do
resE <- liftIO $ try $ fromIntegral <$> IO.hFileSize h resE <- liftIO $ try $ fromIntegral <$> IO.hFileSize h
case resE of case resE of
Left (_ :: IOException) -> return Nothing Left (_ :: IOException) -> return Nothing
Right s -> return $ Just s Right s -> return $ Just s
-- | Queries if handle is seekable. Catches any file operation -- | Queries if handle is seekable. Catches any file operation
-- exceptions and return False instead. -- exceptions and return False instead.
isHandleSeekable :: (R.MonadResource m, R.MonadResourceBase m) isHandleSeekable :: (R.MonadResource m, MonadUnliftIO m)
=> Handle -> m Bool => Handle -> m Bool
isHandleSeekable h = do isHandleSeekable h = do
resE <- liftIO $ try $ IO.hIsSeekable h resE <- liftIO $ try $ IO.hIsSeekable h
case resE of case resE of
Left (_ :: IOException) -> return False Left (_ :: IOException) -> return False
Right v -> return v Right v -> return v
-- | Helper function that opens a handle to the filepath and performs -- | Helper function that opens a handle to the filepath and performs
-- the given action on it. Exceptions of type MError are caught and -- the given action on it. Exceptions of type MError are caught and
-- returned - both during file handle allocation and when the action -- returned - both during file handle allocation and when the action
-- is run. -- is run.
withNewHandle :: (R.MonadResourceBase m, R.MonadResource m, MonadCatch m) withNewHandle :: (MonadUnliftIO m, R.MonadResource m, MonadCatch m)
=> FilePath -> (Handle -> m a) -> m (Either IOException a) => FilePath -> (Handle -> m a) -> m (Either IOException a)
withNewHandle fp fileAction = do withNewHandle fp fileAction = do
-- opening a handle can throw MError exception. -- opening a handle can throw MError exception.
@ -150,16 +150,17 @@ httpLbs req mgr = do
contentTypeMay resp = lookupHeader Hdr.hContentType $ contentTypeMay resp = lookupHeader Hdr.hContentType $
NC.responseHeaders resp NC.responseHeaders resp
http :: (R.MonadResourceBase m, R.MonadResource m) http :: (MonadUnliftIO m, MonadThrow m, R.MonadResource m)
=> NC.Request -> NC.Manager => NC.Request -> NC.Manager
-> m (Response (C.ResumableSource m ByteString)) -> m (Response (C.ConduitT () ByteString m ()))
http req mgr = do http req mgr = do
respE <- tryHttpEx $ NC.http req mgr respE <- tryHttpEx $ NC.http req mgr
resp <- either throwM return respE resp <- either throwM return respE
unless (isSuccessStatus $ NC.responseStatus resp) $ unless (isSuccessStatus $ NC.responseStatus resp) $
case contentTypeMay resp of case contentTypeMay resp of
Just "application/xml" -> do Just "application/xml" -> do
respBody <- NC.responseBody resp C.$$+- CB.sinkLbs respBody <- C.connect (NC.responseBody resp) CB.sinkLbs
--respBody <- C.unsealConduitT (NC.responseBody resp) C.$$+- CB.sinkLbs
sErr <- parseErrResponse respBody sErr <- parseErrResponse respBody
throwM sErr throwM sErr
@ -171,23 +172,34 @@ http req mgr = do
return resp return resp
where where
tryHttpEx :: (R.MonadResourceBase m) => m a tryHttpEx :: (MonadUnliftIO m) => m a
-> m (Either NC.HttpException a) -> m (Either NC.HttpException a)
tryHttpEx = ExL.try tryHttpEx = UEx.try
contentTypeMay resp = lookupHeader Hdr.hContentType $ NC.responseHeaders resp contentTypeMay resp = lookupHeader Hdr.hContentType $ NC.responseHeaders resp
-- Similar to mapConcurrently but limits the number of threads that -- Similar to mapConcurrently but limits the number of threads that
-- can run using a quantity semaphore. -- can run using a quantity semaphore.
limitedMapConcurrently :: (MonadIO m, R.MonadBaseControl IO m) limitedMapConcurrently :: MonadUnliftIO m
=> Int -> (t -> m a) -> [t] -> m [a] => Int -> (t -> m a) -> [t] -> m [a]
limitedMapConcurrently 0 _ _ = return []
limitedMapConcurrently count act args = do limitedMapConcurrently count act args = do
qSem <- liftIO $ Q.newQSem count t' <- U.newTVarIO count
threads <- mapM (A.async . wThread qSem) args threads <- mapM (A.async . wThread t') args
mapM A.wait threads mapM A.wait threads
where where
-- grab 1 unit from semaphore, run action and release it wThread t arg =
wThread qs arg = UEx.bracket_ (waitSem t) (signalSem t) $ act arg
ExL.bracket_ (Q.waitQSem qs) (Q.signalQSem qs) $ act arg
-- quantity semaphore implementation using TVar
waitSem t = U.atomically $ do
v <- U.readTVar t
if v > 0
then U.writeTVar t (v-1)
else U.retrySTM
signalSem t = U.atomically $ do
v <- U.readTVar t
U.writeTVar t (v+1)
-- helper function to 'drop' empty optional parameter. -- helper function to 'drop' empty optional parameter.
mkQuery :: Text -> Maybe Text -> Maybe (Text, Text) mkQuery :: Text -> Maybe Text -> Maybe (Text, Text)
@ -199,7 +211,7 @@ mkOptionalParams :: [(Text, Maybe Text)] -> HT.Query
mkOptionalParams params = HT.toQuery $ uncurry mkQuery <$> params mkOptionalParams params = HT.toQuery $ uncurry mkQuery <$> params
chunkBSConduit :: (Monad m, Integral a) chunkBSConduit :: (Monad m, Integral a)
=> [a] -> C.Conduit ByteString m ByteString => [a] -> C.ConduitM ByteString ByteString m ()
chunkBSConduit s = loop 0 [] s chunkBSConduit s = loop 0 [] s
where where
loop _ _ [] = return () loop _ _ [] = return ()
@ -231,3 +243,19 @@ selectPartSizes size = uncurry (List.zip3 [1..]) $
| st > sz = [] | st > sz = []
| st + m >= sz = [(st, sz - st)] | st + m >= sz = [(st, sz - st)]
| otherwise = (st, m) : loop (st + m) sz | otherwise = (st, m) : loop (st + m) sz
lookupRegionCache :: Bucket -> Minio (Maybe Region)
lookupRegionCache b = do
rMVar <- asks mcRegionMap
rMap <- UM.readMVar rMVar
return $ Map.lookup b rMap
addToRegionCache :: Bucket -> Region -> Minio ()
addToRegionCache b region = do
rMVar <- asks mcRegionMap
UM.modifyMVar_ rMVar $ return . Map.insert b region
deleteFromRegionCache :: Bucket -> Minio ()
deleteFromRegionCache b = do
rMVar <- asks mcRegionMap
UM.modifyMVar_ rMVar $ return . Map.delete b

View File

@ -15,7 +15,7 @@
# resolver: # resolver:
# name: custom-snapshot # name: custom-snapshot
# location: "./custom-snapshot.yaml" # location: "./custom-snapshot.yaml"
resolver: lts-9.1 resolver: lts-11.1
# User packages to be built. # User packages to be built.
# Various formats can be used as shown in the example below. # Various formats can be used as shown in the example below.

View File

@ -19,15 +19,10 @@ import Test.Tasty
import Test.Tasty.HUnit import Test.Tasty.HUnit
import Test.Tasty.QuickCheck as QC import Test.Tasty.QuickCheck as QC
import Lib.Prelude
import System.Directory (getTemporaryDirectory)
import qualified System.IO as SIO
import qualified Control.Monad.Catch as MC import qualified Control.Monad.Catch as MC
import qualified Control.Monad.Trans.Resource as R import qualified Control.Monad.Trans.Resource as R
import qualified Data.ByteString as BS import qualified Data.ByteString as BS
import Data.Conduit (yield, ($$)) import Data.Conduit (yield)
import qualified Data.Conduit as C import qualified Data.Conduit as C
import qualified Data.Conduit.Binary as CB import qualified Data.Conduit.Binary as CB
import Data.Conduit.Combinators (sinkList) import Data.Conduit.Combinators (sinkList)
@ -39,7 +34,11 @@ import qualified Data.Time as Time
import qualified Network.HTTP.Client.MultipartFormData as Form import qualified Network.HTTP.Client.MultipartFormData as Form
import qualified Network.HTTP.Conduit as NC import qualified Network.HTTP.Conduit as NC
import qualified Network.HTTP.Types as HT import qualified Network.HTTP.Types as HT
import System.Directory (getTemporaryDirectory)
import System.Environment (lookupEnv) import System.Environment (lookupEnv)
import qualified System.IO as SIO
import Lib.Prelude
import Network.Minio import Network.Minio
import Network.Minio.Data import Network.Minio.Data
@ -54,7 +53,7 @@ tests :: TestTree
tests = testGroup "Tests" [liveServerUnitTests] tests = testGroup "Tests" [liveServerUnitTests]
-- conduit that generates random binary stream of given length -- conduit that generates random binary stream of given length
randomDataSrc :: MonadIO m => Int64 -> C.Producer m ByteString randomDataSrc :: MonadIO m => Int64 -> C.ConduitM () ByteString m ()
randomDataSrc s' = genBS s' randomDataSrc s' = genBS s'
where where
concatIt bs n = BS.concat $ replicate (fromIntegral q) bs ++ concatIt bs n = BS.concat $ replicate (fromIntegral q) bs ++
@ -72,7 +71,7 @@ randomDataSrc s' = genBS s'
mkRandFile :: R.MonadResource m => Int64 -> m FilePath mkRandFile :: R.MonadResource m => Int64 -> m FilePath
mkRandFile size = do mkRandFile size = do
dir <- liftIO $ getTemporaryDirectory dir <- liftIO $ getTemporaryDirectory
randomDataSrc size C.$$ CB.sinkTempFile dir "miniohstest.random" C.runConduit $ randomDataSrc size C..| CB.sinkTempFile dir "miniohstest.random"
funTestBucketPrefix :: Text funTestBucketPrefix :: Text
funTestBucketPrefix = "miniohstest-" funTestBucketPrefix = "miniohstest-"
@ -158,13 +157,14 @@ highLevelListingTest = funTestWithBucket "High-level listObjects Test" $
\obj -> fPutObject bucket obj "/etc/lsb-release" def \obj -> fPutObject bucket obj "/etc/lsb-release" def
step "High-level listing of objects" step "High-level listing of objects"
objects <- listObjects bucket Nothing True $$ sinkList objects <- C.runConduit $ listObjects bucket Nothing True C..| sinkList
liftIO $ assertEqual "Objects match failed!" (sort expectedObjects) liftIO $ assertEqual "Objects match failed!" (sort expectedObjects)
(map oiObject objects) (map oiObject objects)
step "High-level listing of objects (version 1)" step "High-level listing of objects (version 1)"
objectsV1 <- listObjectsV1 bucket Nothing True $$ sinkList objectsV1 <- C.runConduit $ listObjectsV1 bucket Nothing True C..|
sinkList
liftIO $ assertEqual "Objects match failed!" (sort expectedObjects) liftIO $ assertEqual "Objects match failed!" (sort expectedObjects)
(map oiObject objectsV1) (map oiObject objectsV1)
@ -181,7 +181,9 @@ highLevelListingTest = funTestWithBucket "High-level listObjects Test" $
liftIO $ (T.length uid > 0) @? ("Got an empty multipartUpload Id.") liftIO $ (T.length uid > 0) @? ("Got an empty multipartUpload Id.")
step "High-level listing of incomplete multipart uploads" step "High-level listing of incomplete multipart uploads"
uploads <- listIncompleteUploads bucket (Just "newmpupload") True $$ sinkList uploads <- C.runConduit $
listIncompleteUploads bucket (Just "newmpupload") True C..|
sinkList
liftIO $ length uploads @?= 10 liftIO $ length uploads @?= 10
step "cleanup" step "cleanup"
@ -202,7 +204,8 @@ highLevelListingTest = funTestWithBucket "High-level listObjects Test" $
putObjectPart bucket object uid pnum [] $ PayloadH h 0 mb5 putObjectPart bucket object uid pnum [] $ PayloadH h 0 mb5
step "fetch list parts" step "fetch list parts"
incompleteParts <- listIncompleteParts bucket object uid $$ sinkList incompleteParts <- C.runConduit $ listIncompleteParts bucket object uid
C..| sinkList
liftIO $ length incompleteParts @?= 10 liftIO $ length incompleteParts @?= 10
step "cleanup" step "cleanup"
@ -318,7 +321,8 @@ liveServerUnitTests = testGroup "Unit tests against a live server"
step "remove ongoing upload" step "remove ongoing upload"
removeIncompleteUpload bucket object removeIncompleteUpload bucket object
uploads <- listIncompleteUploads bucket (Just object) False C.$$ sinkList uploads <- C.runConduit $ listIncompleteUploads bucket (Just object) False
C..| sinkList
liftIO $ (null uploads) @? "removeIncompleteUploads didn't complete successfully" liftIO $ (null uploads) @? "removeIncompleteUploads didn't complete successfully"
, funTestWithBucket "putObject contentType tests" $ \step bucket -> do , funTestWithBucket "putObject contentType tests" $ \step bucket -> do
@ -345,11 +349,11 @@ liveServerUnitTests = testGroup "Unit tests against a live server"
} }
oiCE <- headObject bucket object oiCE <- headObject bucket object
let m = oiMetadata oiCE let m' = oiMetadata oiCE
step "Validate content-encoding" step "Validate content-encoding"
liftIO $ assertEqual "Content-Encoding did not match" (Just "identity") liftIO $ assertEqual "Content-Encoding did not match" (Just "identity")
(Map.lookup "Content-Encoding" m) (Map.lookup "Content-Encoding" m')
step "Cleanup actions" step "Cleanup actions"
@ -475,8 +479,8 @@ liveServerUnitTests = testGroup "Unit tests against a live server"
void $ completeMultipartUpload bucket copyObj uid parts void $ completeMultipartUpload bucket copyObj uid parts
step "verify copied object size" step "verify copied object size"
oi <- headObject bucket copyObj oi' <- headObject bucket copyObj
let s' = oiSize oi let s' = oiSize oi'
liftIO $ (s' == mb15) @? "Size failed to match" liftIO $ (s' == mb15) @? "Size failed to match"
@ -491,8 +495,8 @@ liveServerUnitTests = testGroup "Unit tests against a live server"
step "Prepare" step "Prepare"
forM_ (zip srcs sizes) $ \(src, size) -> do forM_ (zip srcs sizes) $ \(src, size) -> do
inputFile <- mkRandFile size inputFile' <- mkRandFile size
fPutObject bucket src inputFile def fPutObject bucket src inputFile' def
step "make small and large object copy" step "make small and large object copy"
forM_ (zip copyObjs srcs) $ \(cp, src) -> forM_ (zip copyObjs srcs) $ \(cp, src) ->
@ -510,8 +514,8 @@ liveServerUnitTests = testGroup "Unit tests against a live server"
size = 15 * 1024 * 1024 size = 15 * 1024 * 1024
step "Prepare" step "Prepare"
inputFile <- mkRandFile size inputFile' <- mkRandFile size
fPutObject bucket src inputFile def fPutObject bucket src inputFile' def
step "copy last 10MiB of object" step "copy last 10MiB of object"
copyObject def { dstBucket = bucket, dstObject = copyObj } def{ copyObject def { dstBucket = bucket, dstObject = copyObj } def{
@ -579,18 +583,18 @@ basicTests = funTestWithBucket "Basic tests" $ \step bucket -> do
_ -> return () _ -> return ()
step "fGetObject an object with no matching etag, check for exception" step "fGetObject an object with no matching etag, check for exception"
resE <- MC.try $ fGetObject bucket "lsb-release" outFile def{ resE1 <- MC.try $ fGetObject bucket "lsb-release" outFile def{
gooIfMatch = (Just "invalid-etag") gooIfMatch = (Just "invalid-etag")
} }
case resE of case resE1 of
Left exn -> liftIO $ exn @?= ServiceErr "PreconditionFailed" "At least one of the pre-conditions you specified did not hold" Left exn -> liftIO $ exn @?= ServiceErr "PreconditionFailed" "At least one of the pre-conditions you specified did not hold"
_ -> return () _ -> return ()
step "fGetObject an object with no valid range, check for exception" step "fGetObject an object with no valid range, check for exception"
resE <- MC.try $ fGetObject bucket "lsb-release" outFile def{ resE2 <- MC.try $ fGetObject bucket "lsb-release" outFile def{
gooRange = (Just $ HT.ByteRangeFromTo 100 200) gooRange = (Just $ HT.ByteRangeFromTo 100 200)
} }
case resE of case resE2 of
Left exn -> liftIO $ exn @?= ServiceErr "InvalidRange" "The requested range is not satisfiable" Left exn -> liftIO $ exn @?= ServiceErr "InvalidRange" "The requested range is not satisfiable"
_ -> return () _ -> return ()
@ -600,8 +604,8 @@ basicTests = funTestWithBucket "Basic tests" $ \step bucket -> do
} }
step "fGetObject a non-existent object and check for NoSuchKey exception" step "fGetObject a non-existent object and check for NoSuchKey exception"
resE <- MC.try $ fGetObject bucket "noSuchKey" outFile def resE3 <- MC.try $ fGetObject bucket "noSuchKey" outFile def
case resE of case resE3 of
Left exn -> liftIO $ exn @?= NoSuchKey Left exn -> liftIO $ exn @?= NoSuchKey
_ -> return () _ -> return ()
@ -658,7 +662,7 @@ presignedUrlFunTest = funTestWithBucket "presigned Url tests" $
"presigned GET failed" "presigned GET failed"
-- read content from file to compare with response above -- read content from file to compare with response above
bs <- CB.sourceFile inputFile $$ CB.sinkLbs bs <- C.runConduit $ CB.sourceFile inputFile C..| CB.sinkLbs
liftIO $ (bs == NC.responseBody getResp) @? liftIO $ (bs == NC.responseBody getResp) @?
"presigned put and get got mismatched data" "presigned put and get got mismatched data"
@ -693,7 +697,7 @@ presignedUrlFunTest = funTestWithBucket "presigned Url tests" $
"presigned GET failed (presignedGetObjectUrl)" "presigned GET failed (presignedGetObjectUrl)"
-- read content from file to compare with response above -- read content from file to compare with response above
bs2 <- CB.sourceFile testFile $$ CB.sinkLbs bs2 <- C.runConduit $ CB.sourceFile testFile C..| CB.sinkLbs
liftIO $ (bs2 == NC.responseBody getResp2) @? liftIO $ (bs2 == NC.responseBody getResp2) @?
"presigned put and get got mismatched data (presigned*Url)" "presigned put and get got mismatched data (presigned*Url)"